From 4bacbc89585b2a2306f395748b9d0b16882abcf8 Mon Sep 17 00:00:00 2001 From: Stenzek Date: Tue, 13 May 2025 21:43:28 +1000 Subject: [PATCH] Qt: Eliminate heap allocations in GameListSortModel More of an issue since the filter is now checking multiple fields. --- src/common/string_util.h | 17 ++++++++++++++++- src/duckstation-qt/gamelistwidget.cpp | 24 +++++++++++++++--------- 2 files changed, 31 insertions(+), 10 deletions(-) diff --git a/src/common/string_util.h b/src/common/string_util.h index bff1cb25a..5cc005ed0 100644 --- a/src/common/string_util.h +++ b/src/common/string_util.h @@ -29,6 +29,16 @@ namespace StringUtil { +/// Returns the given character in uppercase. +ALWAYS_INLINE static char ToLower(char ch) +{ + return ch + ((static_cast(ch) >= 'A' && static_cast(ch) <= 'Z') ? ('a' - 'A') : 0); +} +ALWAYS_INLINE static char ToUpper(char ch) +{ + return ch - ((static_cast(ch) >= 'a' && static_cast(ch) <= 'z') ? ('a' - 'A') : 0); +} + /// Checks if a wildcard matches a search string. bool WildcardMatch(const char* subject, const char* mask, bool case_sensitive = true); @@ -81,6 +91,11 @@ static inline int CompareNoCase(std::string_view s1, std::string_view s2) const int compare_res = (compare_len > 0) ? Strncasecmp(s1.data(), s2.data(), compare_len) : 0; return (compare_len != 0) ? compare_res : ((s1_len < s2_len) ? -1 : ((s1_len > s2_len) ? 1 : 0)); } +static inline bool ContainsNoCase(std::string_view s1, std::string_view s2) +{ + return (std::search(s1.begin(), s1.end(), s2.begin(), s2.end(), + [](char lhs, char rhs) { return (ToLower(lhs) == ToLower(rhs)); }) != s1.end()); +} /// Wrapper around std::from_chars template::value, bool> = true> @@ -234,7 +249,7 @@ inline std::string ToChars(bool value, int base) } /// Returns true if the given character is whitespace. -static inline bool IsWhitespace(char ch) +ALWAYS_INLINE static bool IsWhitespace(char ch) { return ((ch >= 0x09 && ch <= 0x0D) || // horizontal tab, line feed, vertical tab, form feed, carriage return ch == 0x20); // space diff --git a/src/duckstation-qt/gamelistwidget.cpp b/src/duckstation-qt/gamelistwidget.cpp index 7139a4c30..c2b927c4a 100644 --- a/src/duckstation-qt/gamelistwidget.cpp +++ b/src/duckstation-qt/gamelistwidget.cpp @@ -30,6 +30,7 @@ #include #include #include +#include static constexpr float MIN_SCALE = 0.1f; static constexpr float MAX_SCALE = 2.0f; @@ -938,9 +939,10 @@ public: m_filter_region = region; invalidateRowsFilter(); } - void setFilterName(const QString& name) + void setFilterName(std::string name) { - m_filter_name = name; + m_filter_name = std::move(name); + std::transform(m_filter_name.begin(), m_filter_name.end(), m_filter_name.begin(), StringUtil::ToLower); invalidateRowsFilter(); } @@ -970,11 +972,15 @@ public: if (m_filter_region != DiscRegion::Count && entry->region != m_filter_region) return false; - if (!m_filter_name.isEmpty() && - !QString::fromStdString(entry->path).contains(m_filter_name, Qt::CaseInsensitive) && - !QString::fromStdString(entry->serial).contains(m_filter_name, Qt::CaseInsensitive) && - !QString::fromStdString(entry->title).contains(m_filter_name, Qt::CaseInsensitive)) - return false; + if (!m_filter_name.empty()) + { + if (!((!entry->IsDiscSet() && !entry->path.empty() && StringUtil::ContainsNoCase(entry->path, m_filter_name)) || + (!entry->serial.empty() && StringUtil::ContainsNoCase(entry->serial, m_filter_name)) || + (!entry->title.empty() && StringUtil::ContainsNoCase(entry->title, m_filter_name)))) + { + return false; + } + } return QSortFilterProxyModel::filterAcceptsRow(source_row, source_parent); } @@ -988,7 +994,7 @@ private: GameListModel* m_model; GameList::EntryType m_filter_type = GameList::EntryType::MaxCount; DiscRegion m_filter_region = DiscRegion::Count; - QString m_filter_name; + std::string m_filter_name; bool m_merge_disc_sets = true; }; @@ -1155,7 +1161,7 @@ void GameListWidget::initialize() m_sort_model->setFilterRegion((index == 0) ? DiscRegion::Count : static_cast(index - 1)); }); connect(m_ui.searchText, &QLineEdit::textChanged, this, - [this](const QString& text) { m_sort_model->setFilterName(text); }); + [this](const QString& text) { m_sort_model->setFilterName(text.toStdString()); }); connect(m_ui.searchText, &QLineEdit::returnPressed, this, &GameListWidget::onSearchReturnPressed); GameListCenterIconStyleDelegate* center_icon_delegate = new GameListCenterIconStyleDelegate(this);