diff options
| author | 2019-01-14 00:44:46 +1100 | |
|---|---|---|
| committer | 2019-01-14 00:44:46 +1100 | |
| commit | 3d84943f72df5d69db5e5cff1d71887fcc23c422 (patch) | |
| tree | 893bbfe9575c70b5b95311642937bc92bfb633ba /src/frontend | |
| parent | 9d7b4d0faa64f42665b1c19b28b5eb15c0f40d29 (diff) | |
Make search not suck as badly (use algorithm derived from Jaro-Winkler similarity to match search strings, match on more useful stuff)
Diffstat (limited to 'src/frontend')
| -rw-r--r-- | src/frontend/mame/clifront.cpp | 16 | ||||
| -rw-r--r-- | src/frontend/mame/ui/auditmenu.cpp | 11 | ||||
| -rw-r--r-- | src/frontend/mame/ui/selector.cpp | 13 | ||||
| -rw-r--r-- | src/frontend/mame/ui/selector.h | 16 | ||||
| -rw-r--r-- | src/frontend/mame/ui/selgame.cpp | 331 | ||||
| -rw-r--r-- | src/frontend/mame/ui/selgame.h | 16 | ||||
| -rw-r--r-- | src/frontend/mame/ui/selsoft.cpp | 19 | ||||
| -rw-r--r-- | src/frontend/mame/ui/simpleselgame.cpp | 2 | ||||
| -rw-r--r-- | src/frontend/mame/ui/utils.cpp | 184 | ||||
| -rw-r--r-- | src/frontend/mame/ui/utils.h | 43 |
10 files changed, 417 insertions, 234 deletions
diff --git a/src/frontend/mame/clifront.cpp b/src/frontend/mame/clifront.cpp index 97147c76dc9..276c73f0e63 100644 --- a/src/frontend/mame/clifront.cpp +++ b/src/frontend/mame/clifront.cpp @@ -295,14 +295,22 @@ int cli_frontend::execute(std::vector<std::string> &args) // get the top 16 approximate matches driver_enumerator drivlist(m_options); int matches[16]; - drivlist.find_approximate_matches(m_options.attempted_system_name().c_str(), ARRAY_LENGTH(matches), matches); + drivlist.find_approximate_matches(m_options.attempted_system_name(), ARRAY_LENGTH(matches), matches); + + // work out how wide the titles need to be + int titlelen(0); + for (int match : matches) + titlelen = std::max(titlelen, int(strlen(drivlist.driver(match).type.fullname()))); // print them out osd_printf_error("\n\"%s\" approximately matches the following\n" "supported machines (best match first):\n\n", m_options.attempted_system_name().c_str()); - for (auto & matche : matches) - if (matche != -1) - osd_printf_error("%-18s%s\n", drivlist.driver(matche).name, drivlist.driver(matche).type.fullname()); + for (int match : matches) + { + game_driver const &drv(drivlist.driver(match)); + if (match != -1) + osd_printf_error("%s", util::string_format("%-18s%-*s(%s, %s)\n", drv.name, titlelen + 2, drv.type.fullname(), drv.manufacturer, drv.year).c_str()); + } } } catch (emu_exception &) diff --git a/src/frontend/mame/ui/auditmenu.cpp b/src/frontend/mame/ui/auditmenu.cpp index 0349e1c2949..7d5cbfd7305 100644 --- a/src/frontend/mame/ui/auditmenu.cpp +++ b/src/frontend/mame/ui/auditmenu.cpp @@ -207,24 +207,21 @@ void menu_audit::audit_fast() void menu_audit::audit_all() { - m_availablesorted.clear(); driver_enumerator enumerator(machine().options()); media_auditor auditor(enumerator); + std::vector<bool> available(driver_list::total(), false); while (enumerator.next()) { m_current.store(&enumerator.driver()); media_auditor::summary const summary(auditor.audit_media(AUDIT_VALIDATE_FAST)); // if everything looks good, include the driver - m_availablesorted.emplace_back(enumerator.driver(), (summary == media_auditor::CORRECT) || (summary == media_auditor::BEST_AVAILABLE) || (summary == media_auditor::NONE_NEEDED)); + available[enumerator.current()] = (summary == media_auditor::CORRECT) || (summary == media_auditor::BEST_AVAILABLE) || (summary == media_auditor::NONE_NEEDED); ++m_audited; } - // sort - std::stable_sort( - m_availablesorted.begin(), - m_availablesorted.end(), - [] (ui_system_info const &a, ui_system_info const &b) { return sorted_game_list(a.driver, b.driver); }); + for (ui_system_info &info : m_availablesorted) + info.available = available[info.index]; } void menu_audit::save_available_machines() diff --git a/src/frontend/mame/ui/selector.cpp b/src/frontend/mame/ui/selector.cpp index 59aa51f9d13..7f5441afc2c 100644 --- a/src/frontend/mame/ui/selector.cpp +++ b/src/frontend/mame/ui/selector.cpp @@ -136,12 +136,17 @@ void menu_selector::custom_render(void *selectedref, float top, float bottom, fl void menu_selector::find_matches(const char *str) { // allocate memory to track the penalty value - std::vector<int> penalty(VISIBLE_GAMES_IN_SEARCH, 9999); - int index = 0; + m_ucs_items.reserve(m_str_items.size()); + std::vector<double> penalty(VISIBLE_GAMES_IN_SEARCH, 1.0); + std::u32string const search(ustr_from_utf8(normalize_unicode(str, unicode_normalization_form::D, true))); - for (; index < m_str_items.size(); ++index) + int index = 0; + for ( ; index < m_str_items.size(); ++index) { - int curpenalty = fuzzy_substring(str, m_str_items[index]); + assert(m_ucs_items.size() >= index); + if (m_ucs_items.size() == index) + m_ucs_items.emplace_back(ustr_from_utf8(normalize_unicode(m_str_items[index], unicode_normalization_form::D, true))); + double const curpenalty(util::edit_distance(search, m_ucs_items[index])); // insert into the sorted table of matches for (int matchnum = VISIBLE_GAMES_IN_SEARCH - 1; matchnum >= 0; --matchnum) diff --git a/src/frontend/mame/ui/selector.h b/src/frontend/mame/ui/selector.h index bd6a87c4ef9..f3921536267 100644 --- a/src/frontend/mame/ui/selector.h +++ b/src/frontend/mame/ui/selector.h @@ -7,14 +7,15 @@ Internal UI user interface. ***************************************************************************/ +#ifndef MAME_FRONTEND_UI_SELECTOR_H +#define MAME_FRONTEND_UI_SELECTOR_H #pragma once -#ifndef MAME_FRONTEND_UI_SELECTOR_H -#define MAME_FRONTEND_UI_SELECTOR_H #include "ui/menu.h" + namespace ui { //------------------------------------------------- @@ -44,11 +45,12 @@ private: void find_matches(const char *str); - std::string m_search; - std::vector<std::string> m_str_items; - std::function<void (int)> m_handler; - int m_initial; - std::string *m_searchlist[VISIBLE_GAMES_IN_SEARCH + 1]; + std::string m_search; + std::vector<std::string> m_str_items; + std::function<void (int)> m_handler; + std::vector<std::u32string> m_ucs_items; + int m_initial; + std::string *m_searchlist[VISIBLE_GAMES_IN_SEARCH + 1]; }; } // namespace ui diff --git a/src/frontend/mame/ui/selgame.cpp b/src/frontend/mame/ui/selgame.cpp index 0b47fa1d7d1..acc9dd666b1 100644 --- a/src/frontend/mame/ui/selgame.cpp +++ b/src/frontend/mame/ui/selgame.cpp @@ -31,17 +31,168 @@ #include "uiinput.h" #include "luaengine.h" +#include <atomic> +#include <condition_variable> #include <cstring> #include <iterator> +#include <memory> +#include <mutex> +#include <thread> extern const char UI_VERSION_TAG[]; namespace ui { -bool menu_select_game::first_start = true; -std::vector<const game_driver *> menu_select_game::m_sortedlist; -int menu_select_game::m_isabios = 0; +class menu_select_game::persistent_data +{ +public: + enum available : unsigned + { + AVAIL_NONE = 0, + AVAIL_SORTED_LIST = 1 << 0, + AVAIL_BIOS_COUNT = 1 << 1, + AVAIL_UCS_SHORTNAME = 1 << 2, + AVAIL_UCS_DESCRIPTION = 1 << 3, + AVAIL_UCS_MANUF_DESC = 1 << 4 + }; + + persistent_data() + : m_started(false) + , m_available(AVAIL_NONE) + , m_bios_count(0) + { + } + + ~persistent_data() + { + if (m_thread) + m_thread->join(); + } + + void cache_data() + { + std::unique_lock<std::mutex> lock(m_mutex); + do_start_caching(); + } + + bool is_available(available desired) + { + return (m_available.load(std::memory_order_acquire) & desired) == desired; + } + + void wait_available(available desired) + { + if (!is_available(desired)) + { + std::unique_lock<std::mutex> lock(m_mutex); + do_start_caching(); + m_condition.wait(lock, [this, desired] () { return is_available(desired); }); + } + } + + std::vector<ui_system_info> &sorted_list() + { + wait_available(AVAIL_SORTED_LIST); + return m_sorted_list; + } + + int bios_count() + { + wait_available(AVAIL_BIOS_COUNT); + return m_bios_count; + } + + bool unavailable_systems() + { + wait_available(AVAIL_SORTED_LIST); + return std::find_if(m_sorted_list.begin(), m_sorted_list.end(), [] (ui_system_info const &info) { return !info.available; }) != m_sorted_list.end(); + } + +private: + void notify_available(available value) + { + std::unique_lock<std::mutex> lock(m_mutex); + m_available.fetch_or(value, std::memory_order_release); + m_condition.notify_all(); + } + + void do_start_caching() + { + if (!m_started) + { + m_started = true; + m_thread = std::make_unique<std::thread>([this] { do_cache_data(); }); + } + } + + void do_cache_data() + { + // generate full list + m_sorted_list.reserve(driver_list::total()); + std::unordered_set<std::string> manufacturers, years; + for (int x = 0; x < driver_list::total(); ++x) + { + game_driver const &driver(driver_list::driver(x)); + if (&driver != &GAME_NAME(___empty)) + { + if (driver.flags & machine_flags::IS_BIOS_ROOT) + ++m_bios_count; + + m_sorted_list.emplace_back(driver, x, false); + c_mnfct::add(driver.manufacturer); + c_year::add(driver.year); + } + } + + // notify that BIOS count is valie + notify_available(AVAIL_BIOS_COUNT); + + // sort drivers and notify + std::stable_sort( + m_sorted_list.begin(), + m_sorted_list.end(), + [] (ui_system_info const &lhs, ui_system_info const &rhs) { return sorted_game_list(lhs.driver, rhs.driver); }); + notify_available(AVAIL_SORTED_LIST); + + // convert shortnames to UCS-4 + for (ui_system_info &info : m_sorted_list) + info.ucs_shortname = ustr_from_utf8(normalize_unicode(info.driver->name, unicode_normalization_form::D, true)); + notify_available(AVAIL_UCS_SHORTNAME); + + // convert descriptions to UCS-4 + for (ui_system_info &info : m_sorted_list) + info.ucs_description = ustr_from_utf8(normalize_unicode(info.driver->type.fullname(), unicode_normalization_form::D, true)); + notify_available(AVAIL_UCS_DESCRIPTION); + + // convert "<manufacturer> <description>" to UCS-4 + std::string buf; + for (ui_system_info &info : m_sorted_list) + { + buf.assign(info.driver->manufacturer); + buf.append(1, ' '); + buf.append(info.driver->type.fullname()); + info.ucs_manufacturer_description = ustr_from_utf8(normalize_unicode(buf, unicode_normalization_form::D, true)); + } + notify_available(AVAIL_UCS_MANUF_DESC); + + // sort manufacturers and years + c_mnfct::finalise(); + c_year::finalise(); + } + + std::mutex m_mutex; + std::condition_variable m_condition; + std::unique_ptr<std::thread> m_thread; + std::atomic<bool> m_started; + std::atomic<unsigned> m_available; + std::vector<ui_system_info> m_sorted_list; + int m_bios_count; +}; + +menu_select_game::persistent_data menu_select_game::s_persistent_data; +bool menu_select_game::s_first_start = true; + //------------------------------------------------- // ctor @@ -54,7 +205,7 @@ menu_select_game::menu_select_game(mame_ui_manager &mui, render_container &conta ui_options &moptions = mui.options(); // load drivers cache - init_sorted_list(); + s_persistent_data.cache_data(); // check if there are available icons ui_globals::has_icons = false; @@ -74,8 +225,9 @@ menu_select_game::menu_select_game(mame_ui_manager &mui, render_container &conta if (!load_available_machines()) build_available_list(); - if (first_start) + if (s_first_start) { + //s_first_start = false; TODO: why wansn't it ever clearing the first start flag? reselect_last::set_driver(moptions.last_used_machine()); ui_globals::rpanel = std::min<int>(std::max<int>(moptions.last_right_panel(), RP_FIRST), RP_LAST); @@ -121,7 +273,6 @@ menu_select_game::menu_select_game(mame_ui_manager &mui, render_container &conta ui_globals::default_image = true; ui_globals::panels_status = moptions.hide_panels(); ui_globals::curdats_total = 1; - m_searchlist[0] = nullptr; } //------------------------------------------------- @@ -326,12 +477,12 @@ void menu_select_game::handle() break; case IPT_UI_AUDIT_FAST: - if (std::find_if(m_availsortedlist.begin(), m_availsortedlist.end(), [] (ui_system_info const &info) { return !info.available; }) != m_availsortedlist.end()) - menu::stack_push<menu_audit>(ui(), container(), m_availsortedlist, menu_audit::mode::FAST); + if (s_persistent_data.unavailable_systems()) + menu::stack_push<menu_audit>(ui(), container(), s_persistent_data.sorted_list(), menu_audit::mode::FAST); break; case IPT_UI_AUDIT_ALL: - menu::stack_push<menu_audit>(ui(), container(), m_availsortedlist, menu_audit::mode::ALL); + menu::stack_push<menu_audit>(ui(), container(), s_persistent_data.sorted_list(), menu_audit::mode::ALL); break; } } @@ -368,10 +519,11 @@ void menu_select_game::populate(float &customtop, float &custombottom) // if filter is set on category, build category list auto const it(main_filters::filters.find(main_filters::actual)); + std::vector<ui_system_info> const &sorted(s_persistent_data.sorted_list()); if (main_filters::filters.end() == it) - m_displaylist = m_availsortedlist; + std::copy(sorted.begin(), sorted.end(), std::back_inserter(m_displaylist)); else - it->second->apply(m_availsortedlist.begin(), m_availsortedlist.end(), std::back_inserter(m_displaylist)); + it->second->apply(sorted.begin(), sorted.end(), std::back_inserter(m_displaylist)); // iterate over entries int curitem = 0; @@ -400,7 +552,7 @@ void menu_select_game::populate(float &customtop, float &custombottom) int curitem = 0; // iterate over entries - for (auto & favmap : mame_machine_manager::instance()->favorite().m_list) + for (auto &favmap : mame_machine_manager::instance()->favorite().m_list) { auto flags = flags_ui | FLAG_UI_FAVORITE; if (favmap.second.startempty == 1) @@ -562,18 +714,9 @@ void menu_select_game::build_available_list() } } - // sort - m_availsortedlist.reserve(total); - for (std::size_t x = 0; total > x; ++x) - { - game_driver const &driver(driver_list::driver(x)); - if (&driver != &GAME_NAME(___empty)) - m_availsortedlist.emplace_back(driver, included[x]); - } - std::stable_sort( - m_availsortedlist.begin(), - m_availsortedlist.end(), - [] (ui_system_info const &a, ui_system_info const &b) { return sorted_game_list(a.driver, b.driver); }); + // copy into the persistent sorted list + for (ui_system_info &info : s_persistent_data.sorted_list()) + info.available = included[info.index]; } @@ -791,48 +934,72 @@ void menu_select_game::change_info_pane(int delta) void menu_select_game::populate_search() { - // allocate memory to track the penalty value - std::vector<int> penalty(VISIBLE_GAMES_IN_SEARCH, 9999); - int index = 0; - for (; index < m_displaylist.size(); ++index) + // ensure search list is populated + if (m_searchlist.empty()) { - // pick the best match between driver name and description - int curpenalty = fuzzy_substring(m_search, m_displaylist[index].driver->type.fullname()); - int tmp = fuzzy_substring(m_search, m_displaylist[index].driver->name); - curpenalty = std::min(curpenalty, tmp); + std::vector<ui_system_info> const &sorted(s_persistent_data.sorted_list()); + m_searchlist.reserve(sorted.size()); + for (ui_system_info const &info : sorted) + m_searchlist.emplace_back(1.0, std::ref(info)); + } - // insert into the sorted table of matches - for (int matchnum = VISIBLE_GAMES_IN_SEARCH - 1; matchnum >= 0; --matchnum) - { - // stop if we're worse than the current entry - if (curpenalty >= penalty[matchnum]) - break; + // keep track of what we matched against + const std::u32string ucs_search(ustr_from_utf8(normalize_unicode(m_search, unicode_normalization_form::D, true))); + unsigned matched(0); + + // match shortnames + if (s_persistent_data.is_available(persistent_data::AVAIL_UCS_SHORTNAME)) + { + matched |= persistent_data::AVAIL_UCS_SHORTNAME; + for (std::pair<double, std::reference_wrapper<ui_system_info const> > &info : m_searchlist) + info.first = util::edit_distance(ucs_search, info.second.get().ucs_shortname); + } - // as long as this isn't the last entry, bump this one down - if (matchnum < VISIBLE_GAMES_IN_SEARCH - 1) + // match descriptions + if (s_persistent_data.is_available(persistent_data::AVAIL_UCS_DESCRIPTION)) + { + matched |= persistent_data::AVAIL_UCS_DESCRIPTION; + for (std::pair<double, std::reference_wrapper<ui_system_info const> > &info : m_searchlist) + { + if (info.first) { - penalty[matchnum + 1] = penalty[matchnum]; - m_searchlist[matchnum + 1] = m_searchlist[matchnum]; + double const penalty(util::edit_distance(ucs_search, info.second.get().ucs_description)); + info.first = (std::min)(penalty, info.first); } + } + } - m_searchlist[matchnum] = m_displaylist[index].driver; - penalty[matchnum] = curpenalty; + // match "<manufacturer> <description>" + if (s_persistent_data.is_available(persistent_data::AVAIL_UCS_MANUF_DESC)) + { + matched |= persistent_data::AVAIL_UCS_MANUF_DESC; + for (std::pair<double, std::reference_wrapper<ui_system_info const> > &info : m_searchlist) + { + if (info.first) + { + double const penalty(util::edit_distance(ucs_search, info.second.get().ucs_manufacturer_description)); + info.first = (std::min)(penalty, info.first); + } } } - (index < VISIBLE_GAMES_IN_SEARCH) ? m_searchlist[index] = nullptr : m_searchlist[VISIBLE_GAMES_IN_SEARCH] = nullptr; + // sort according to edit distance and put up to 200 in the menu + std::stable_sort( + m_searchlist.begin(), + m_searchlist.end(), + [] (auto const &lhs, auto const &rhs) { return lhs.first < rhs.first; }); uint32_t flags_ui = FLAG_LEFT_ARROW | FLAG_RIGHT_ARROW; - for (int curitem = 0; m_searchlist[curitem]; ++curitem) + for (int curitem = 0; (std::min)(m_searchlist.size(), std::size_t(200)) > curitem; ++curitem) { - bool cloneof = strcmp(m_searchlist[curitem]->parent, "0"); + game_driver const &drv(*m_searchlist[curitem].second.get().driver); + bool cloneof = strcmp(drv.parent, "0") != 0; if (cloneof) { - int cx = driver_list::find(m_searchlist[curitem]->parent); + int const cx = driver_list::find(drv.parent); if (cx != -1 && ((driver_list::driver(cx).flags & machine_flags::IS_BIOS_ROOT) != 0)) cloneof = false; } - item_append(m_searchlist[curitem]->type.fullname(), "", (!cloneof) ? flags_ui : (FLAG_INVERT | flags_ui), - (void *)m_searchlist[curitem]); + item_append(drv.type.fullname(), "", !cloneof ? flags_ui : (FLAG_INVERT | flags_ui), (void *)&drv); } } @@ -992,8 +1159,8 @@ void menu_select_game::inkey_export() std::vector<game_driver const *> list; if (!m_search.empty()) { - for (int curitem = 0; m_searchlist[curitem]; ++curitem) - list.push_back(m_searchlist[curitem]); + for (int curitem = 0; (std::min)(m_searchlist.size(), std::size_t(200)); ++curitem) + list.push_back(m_searchlist[curitem].second.get().driver); } else { @@ -1020,41 +1187,6 @@ void menu_select_game::inkey_export() } //------------------------------------------------- -// save drivers infos to file -//------------------------------------------------- - -void menu_select_game::init_sorted_list() -{ - if (!m_sortedlist.empty()) - return; - - // generate full list - std::unordered_set<std::string> manufacturers, years; - for (int x = 0; x < driver_list::total(); ++x) - { - game_driver const &driver(driver_list::driver(x)); - if (&driver != &GAME_NAME(___empty)) - { - if (driver.flags & machine_flags::IS_BIOS_ROOT) - m_isabios++; - - m_sortedlist.push_back(&driver); - manufacturers.emplace(c_mnfct::getname(driver.manufacturer)); - years.emplace(driver.year); - } - } - - // sort manufacturers - years and driver - for (auto it = manufacturers.begin(); manufacturers.end() != it; it = manufacturers.erase(it)) - c_mnfct::ui.emplace_back(*it); - std::sort(c_mnfct::ui.begin(), c_mnfct::ui.end(), [] (std::string const &x, std::string const &y) { return 0 > core_stricmp(x.c_str(), y.c_str()); }); - for (auto it = years.begin(); years.end() != it; it = years.erase(it)) - c_year::ui.emplace_back(*it); - std::stable_sort(c_year::ui.begin(), c_year::ui.end()); - std::stable_sort(m_sortedlist.begin(), m_sortedlist.end(), sorted_game_list); -} - -//------------------------------------------------- // load drivers infos from file //------------------------------------------------- @@ -1093,27 +1225,18 @@ bool menu_select_game::load_available_machines() else available.emplace(std::move(readbuf)); } + file.close(); // turn it into the sorted system list we all love - m_availsortedlist.reserve(driver_list::total()); - for (std::size_t x = 0; driver_list::total() > x; ++x) + for (ui_system_info &info : s_persistent_data.sorted_list()) { - game_driver const &driver(driver_list::driver(x)); - if (&driver != &GAME_NAME(___empty)) - { - std::unordered_set<std::string>::iterator const it(available.find(&driver.name[0])); - bool const found(available.end() != it); - m_availsortedlist.emplace_back(driver, found); - if (found) - available.erase(it); - } + std::unordered_set<std::string>::iterator const it(available.find(&info.driver->name[0])); + bool const found(available.end() != it); + info.available = found; + if (found) + available.erase(it); } - std::stable_sort( - m_availsortedlist.begin(), - m_availsortedlist.end(), - [] (ui_system_info const &a, ui_system_info const &b) { return sorted_game_list(a.driver, b.driver); }); - file.close(); return true; } @@ -1170,7 +1293,7 @@ void menu_select_game::make_topbox_text(std::string &line0, std::string &line1, bare_build_version, visible_items, (driver_list::total() - 1), - m_isabios); + s_persistent_data.bios_count()); if (isfavorite()) { diff --git a/src/frontend/mame/ui/selgame.h b/src/frontend/mame/ui/selgame.h index 5778b2c544c..d3ab264ed50 100644 --- a/src/frontend/mame/ui/selgame.h +++ b/src/frontend/mame/ui/selgame.h @@ -15,6 +15,8 @@ #include "ui/selmenu.h" #include "ui/utils.h" +#include <functional> + class media_auditor; @@ -37,15 +39,14 @@ private: CONF_PLUGINS, }; - enum { VISIBLE_GAMES_IN_SEARCH = 200 }; - static bool first_start; - static int m_isabios; + class persistent_data; + + std::vector<std::reference_wrapper<ui_system_info const> > m_displaylist; - static std::vector<const game_driver *> m_sortedlist; - std::vector<ui_system_info> m_availsortedlist; - std::vector<ui_system_info> m_displaylist; + static persistent_data s_persistent_data; + static bool s_first_start; - const game_driver *m_searchlist[VISIBLE_GAMES_IN_SEARCH + 1]; + std::vector<std::pair<double, std::reference_wrapper<ui_system_info const> > > m_searchlist; virtual void populate(float &customtop, float &custombottom) override; virtual void handle() override; @@ -75,7 +76,6 @@ private: bool isfavorite() const; void populate_search(); - void init_sorted_list(); bool load_available_machines(); void load_custom_filters(); diff --git a/src/frontend/mame/ui/selsoft.cpp b/src/frontend/mame/ui/selsoft.cpp index e7ebc3effa2..e1eb1f75c33 100644 --- a/src/frontend/mame/ui/selsoft.cpp +++ b/src/frontend/mame/ui/selsoft.cpp @@ -490,15 +490,20 @@ void menu_select_software::load_sw_custom_filters() void menu_select_software::find_matches(const char *str, int count) { // allocate memory to track the penalty value - std::vector<int> penalty(count, 9999); - int index = 0; + std::vector<double> penalty(count, 1.0); + std::u32string const search(ustr_from_utf8(normalize_unicode(str, unicode_normalization_form::D, true))); - for (; index < m_displaylist.size(); ++index) + int index = 0; + for ( ; index < m_displaylist.size(); ++index) { - // pick the best match between driver name and description - int curpenalty = fuzzy_substring(str, m_displaylist[index]->longname); - int tmp = fuzzy_substring(str, m_displaylist[index]->shortname); - curpenalty = std::min(curpenalty, tmp); + // pick the best match between shortname and longname + // TODO: search alternate title as well + double curpenalty(util::edit_distance(search, ustr_from_utf8(normalize_unicode(m_displaylist[index]->shortname, unicode_normalization_form::D, true)))); + if (curpenalty) + { + double const tmp(util::edit_distance(search, ustr_from_utf8(normalize_unicode(m_displaylist[index]->longname, unicode_normalization_form::D, true)))); + curpenalty = (std::min)(curpenalty, tmp); + } // insert into the sorted table of matches for (int matchnum = count - 1; matchnum >= 0; --matchnum) diff --git a/src/frontend/mame/ui/simpleselgame.cpp b/src/frontend/mame/ui/simpleselgame.cpp index f81b161dac1..604c2b32d41 100644 --- a/src/frontend/mame/ui/simpleselgame.cpp +++ b/src/frontend/mame/ui/simpleselgame.cpp @@ -250,7 +250,7 @@ void simple_menu_select_game::populate(float &customtop, float &custombottom) // otherwise, rebuild the match list assert(m_drivlist != nullptr); if (!m_search.empty() || m_matchlist[0] == -1 || m_rerandomize) - m_drivlist->find_approximate_matches(m_search.c_str(), matchcount, m_matchlist); + m_drivlist->find_approximate_matches(m_search, matchcount, m_matchlist); m_rerandomize = false; // iterate over entries diff --git a/src/frontend/mame/ui/utils.cpp b/src/frontend/mame/ui/utils.cpp index 1a8ff0ba476..f82435d3864 100644 --- a/src/frontend/mame/ui/utils.cpp +++ b/src/frontend/mame/ui/utils.cpp @@ -22,7 +22,9 @@ #include "romload.h" #include "softlist.h" +#include <atomic> #include <bitset> +#include <condition_variable> #include <cstdlib> #include <cstring> #include <iterator> @@ -97,6 +99,26 @@ constexpr char const *SOFTWARE_FILTER_NAMES[software_filter::COUNT] = { //------------------------------------------------- +// static filter data +//------------------------------------------------- + +std::mutex f_filter_data_mutex; +std::condition_variable f_filter_data_condition; +std::atomic<bool> f_mnfct_finalised(false), f_year_finalised(false); +std::vector<std::string> f_mnfct_ui, f_year_ui; +std::unordered_set<std::string> f_mnfct_tmp, f_year_tmp; + +std::string trim_manufacturer(std::string const &mfg) +{ + size_t const found(mfg.find('(')); + if ((found != std::string::npos) && (found > 0)) + return mfg.substr(0, found - 1); + else + return mfg; +} + + +//------------------------------------------------- // base implementation for simple filters //------------------------------------------------- @@ -740,7 +762,7 @@ class manufacturer_machine_filter : public choice_filter_impl_base<machine_filte { public: manufacturer_machine_filter(char const *value, emu_file *file, unsigned indent) - : choice_filter_impl_base<machine_filter, machine_filter::MANUFACTURER>(c_mnfct::ui, value) + : choice_filter_impl_base<machine_filter, machine_filter::MANUFACTURER>(c_mnfct::ui(), value) { } @@ -751,7 +773,7 @@ public: else if (!selection_valid()) return false; - std::string const name(c_mnfct::getname(system.driver->manufacturer)); + std::string const name(trim_manufacturer(system.driver->manufacturer)); return !name.empty() && (selection_text() == name); } }; @@ -761,7 +783,7 @@ class year_machine_filter : public choice_filter_impl_base<machine_filter, machi { public: year_machine_filter(char const *value, emu_file *file, unsigned indent) - : choice_filter_impl_base<machine_filter, machine_filter::YEAR>(c_year::ui, value) + : choice_filter_impl_base<machine_filter, machine_filter::YEAR>(c_year::ui(), value) { } @@ -1709,18 +1731,96 @@ software_filter::ptr software_filter::create(emu_file &file, software_filter_dat return nullptr; } + +//------------------------------------------------- +// set manufacturers +//------------------------------------------------- + +void c_mnfct::add(std::string &&mfg) +{ + assert(!f_mnfct_finalised.load(std::memory_order_acquire)); + + size_t const found(mfg.find('(')); + if ((found != std::string::npos) && (found > 0)) + mfg.resize(found - 1); + + f_mnfct_tmp.emplace(std::move(mfg)); +} + +void c_mnfct::finalise() +{ + assert(!f_mnfct_finalised.load(std::memory_order_acquire)); + + f_mnfct_ui.reserve(f_mnfct_tmp.size()); + for (auto it = f_mnfct_tmp.begin(); f_mnfct_tmp.end() != it; it = f_mnfct_tmp.erase(it)) + f_mnfct_ui.emplace_back(*it); + std::sort( + f_mnfct_ui.begin(), + f_mnfct_ui.end(), + [] (std::string const &x, std::string const &y) { return 0 > core_stricmp(x.c_str(), y.c_str()); }); + + std::unique_lock<std::mutex> lock(f_filter_data_mutex); + f_mnfct_finalised.store(true, std::memory_order_release); + f_filter_data_condition.notify_all(); +} + +std::vector<std::string> const &c_mnfct::ui() +{ + if (!f_mnfct_finalised.load(std::memory_order_acquire)) + { + std::unique_lock<std::mutex> lock(f_filter_data_mutex); + f_filter_data_condition.wait(lock, [] () { return f_mnfct_finalised.load(std::memory_order_acquire); }); + } + + return f_mnfct_ui; +} + + +//------------------------------------------------- +// set years +//------------------------------------------------- + +void c_year::add(std::string &&year) +{ + assert(!f_year_finalised.load(std::memory_order_acquire)); + + f_year_tmp.emplace(std::move(year)); +} + +void c_year::finalise() +{ + assert(!f_year_finalised.load(std::memory_order_acquire)); + + f_year_ui.reserve(f_year_tmp.size()); + for (auto it = f_year_tmp.begin(); f_year_tmp.end() != it; it = f_year_tmp.erase(it)) + f_year_ui.emplace_back(*it); + std::sort( + f_year_ui.begin(), + f_year_ui.end(), + [] (std::string const &x, std::string const &y) { return 0 > core_stricmp(x.c_str(), y.c_str()); }); + + std::unique_lock<std::mutex> lock(f_filter_data_mutex); + f_year_finalised.store(true, std::memory_order_release); + f_filter_data_condition.notify_all(); +} + +std::vector<std::string> const &c_year::ui() +{ + if (!f_year_finalised.load(std::memory_order_acquire)) + { + std::unique_lock<std::mutex> lock(f_filter_data_mutex); + f_filter_data_condition.wait(lock, [] () { return f_year_finalised.load(std::memory_order_acquire); }); + } + + return f_year_ui; +} + } // namesapce ui extern const char UI_VERSION_TAG[]; const char UI_VERSION_TAG[] = "# UI INFO "; -// Years index -std::vector<std::string> c_year::ui; - -// Manufacturers index -std::vector<std::string> c_mnfct::ui; - // Main filters ui::machine_filter::type main_filters::actual = ui::machine_filter::ALL; std::map<ui::machine_filter::type, ui::machine_filter::ptr> main_filters::filters; @@ -1782,72 +1882,6 @@ std::vector<std::string> tokenize(const std::string &text, char sep) return tokens; } -//------------------------------------------------- -// search a substring with even partial matching -//------------------------------------------------- - -int fuzzy_substring(std::string s_needle, std::string s_haystack) -{ - if (s_needle.empty()) - return s_haystack.size(); - if (s_haystack.empty()) - return s_needle.size(); - - strmakelower(s_needle); - strmakelower(s_haystack); - - if (s_needle == s_haystack) - return 0; - if (s_haystack.find(s_needle) != std::string::npos) - return 0; - - auto *row1 = global_alloc_array_clear<int>(s_haystack.size() + 2); - auto *row2 = global_alloc_array_clear<int>(s_haystack.size() + 2); - - for (int i = 0; i < s_needle.size(); ++i) - { - row2[0] = i + 1; - for (int j = 0; j < s_haystack.size(); ++j) - { - int cost = (s_needle[i] == s_haystack[j]) ? 0 : 1; - row2[j + 1] = std::min(row1[j + 1] + 1, std::min(row2[j] + 1, row1[j] + cost)); - } - - int *tmp = row1; - row1 = row2; - row2 = tmp; - } - - int *first, *smallest; - first = smallest = row1; - int *last = row1 + s_haystack.size(); - - while (++first != last) - if (*first < *smallest) - smallest = first; - - int rv = *smallest; - global_free_array(row1); - global_free_array(row2); - - return rv; -} - -//------------------------------------------------- -// set manufacturers -//------------------------------------------------- - -std::string c_mnfct::getname(const char *str) -{ - std::string name(str); - size_t found = name.find("("); - - if (found != std::string::npos) - return (name.substr(0, found - 1)); - else - return name; -} - ui_software_info::ui_software_info( software_info const &info, diff --git a/src/frontend/mame/ui/utils.h b/src/frontend/mame/ui/utils.h index aaedd75854e..b7dbbb255b2 100644 --- a/src/frontend/mame/ui/utils.h +++ b/src/frontend/mame/ui/utils.h @@ -31,10 +31,15 @@ class render_container; struct ui_system_info { ui_system_info() { } - ui_system_info(game_driver const &d, bool a) : driver(&d), available(a) { } + ui_system_info(game_driver const &d, int index, bool a) : driver(&d), available(a) { } game_driver const *driver = nullptr; + int index; bool available = false; + + std::u32string ucs_shortname; + std::u32string ucs_description; + std::u32string ucs_manufacturer_description; }; struct ui_software_info @@ -259,6 +264,24 @@ protected: DECLARE_ENUM_INCDEC_OPERATORS(software_filter::type) +// Manufacturers +struct c_mnfct +{ + static void add(std::string &&mfg); + static void finalise(); + + static std::vector<std::string> const &ui(); +}; + +// Years +struct c_year +{ + static void add(std::string &&year); + static void finalise(); + + static std::vector<std::string> const &ui(); +}; + } // namespace ui #define MAX_CHAR_INFO 256 @@ -326,26 +349,13 @@ enum // GLOBAL CLASS struct ui_globals { - static uint8_t curimage_view, curdats_view, curdats_total, cur_sw_dats_view, cur_sw_dats_total, rpanel; + static uint8_t curimage_view, curdats_view, curdats_total, cur_sw_dats_view, cur_sw_dats_total, rpanel; static bool switch_image, redraw_icon, default_image, reset; static int visible_main_lines, visible_sw_lines; - static uint16_t panels_status; + static uint16_t panels_status; static bool has_icons; }; -// Manufacturers -struct c_mnfct -{ - static std::string getname(const char *str); - static std::vector<std::string> ui; -}; - -// Years -struct c_year -{ - static std::vector<std::string> ui; -}; - struct main_filters { static ui::machine_filter::type actual; @@ -353,7 +363,6 @@ struct main_filters }; // GLOBAL FUNCTIONS -int fuzzy_substring(std::string needle, std::string haystack); char* chartrimcarriage(char str[]); const char* strensure(const char* s); int getprecisionchr(const char* s); |
