From 142b9ff2433b9ab152a59f4abf0df27c84a848ab Mon Sep 17 00:00:00 2001 From: Ted John Date: Sat, 30 Jul 2022 15:50:54 +0100 Subject: [PATCH] Optimise object selection search * Do case insensitive string matching for object search. * Return as soon as first field is a hit. --- .../windows/EditorObjectSelection.cpp | 59 ++++++++++--------- src/openrct2/core/String.cpp | 20 +++++++ src/openrct2/core/String.hpp | 1 + 3 files changed, 52 insertions(+), 28 deletions(-) diff --git a/src/openrct2-ui/windows/EditorObjectSelection.cpp b/src/openrct2-ui/windows/EditorObjectSelection.cpp index baeb5d0686..13677b8e3c 100644 --- a/src/openrct2-ui/windows/EditorObjectSelection.cpp +++ b/src/openrct2-ui/windows/EditorObjectSelection.cpp @@ -1135,7 +1135,7 @@ private: uint8_t selectionFlags = _objectSelectionFlags[i]; const ObjectRepositoryItem* item = &items[i]; if (item->Type == GetSelectedObjectType() && !(selectionFlags & ObjectSelectionFlags::Flag6) && FilterSource(item) - && FilterString(item) && FilterChunks(item) && FilterSelected(selectionFlags)) + && FilterString(*item) && FilterChunks(item) && FilterSelected(selectionFlags)) { auto filter = std::make_unique(); filter->ride.category[0] = 0; @@ -1319,12 +1319,32 @@ private: return false; } - static bool IsFilterInAuthor(const std::vector& authors, const std::string& filterUpper) + static bool IsFilterInName(const ObjectRepositoryItem& item, std::string_view filter) { - for (auto& author : authors) + return String::Contains(item.Name, filter, true); + } + + static bool IsFilterInRideType(const ObjectRepositoryItem& item, std::string_view filter) + { + if (item.Type == ObjectType::Ride) { - bool inAuthor = String::ToUpper(author).find(filterUpper) != std::string::npos; - if (inAuthor) + auto rideTypeName = language_get_string(GetRideTypeStringId(&item)); + if (String::Contains(rideTypeName, filter, true)) + return true; + } + return false; + } + + static bool IsFilterInFilename(const ObjectRepositoryItem& item, std::string_view filter) + { + return String::Contains(item.Path, filter, true); + } + + static bool IsFilterInAuthor(const ObjectRepositoryItem& item, std::string_view filter) + { + for (auto& author : item.Authors) + { + if (String::Contains(author, filter, true)) { return true; } @@ -1332,32 +1352,15 @@ private: return false; } - bool FilterString(const ObjectRepositoryItem* item) + bool FilterString(const ObjectRepositoryItem& item) { // Nothing to search for - if (_filter_string[0] == '\0') + std::string_view filter = _filter_string; + if (filter.empty()) return true; - // Object doesn't have a name - if (item->Name.empty()) - return false; - - // Get ride type - const char* rideTypeName = language_get_string(GetRideTypeStringId(item)); - - // Get object name (ride/vehicle for rides) and type name (rides only) in uppercase - const auto nameUpper = String::ToUpper(item->Name); - const auto typeUpper = String::ToUpper(rideTypeName); - const auto pathUpper = String::ToUpper(item->Path); - const auto filterUpper = String::ToUpper(_filter_string); - - // Check if the searched string exists in the name, ride type, filename, or authors field - bool inName = nameUpper.find(filterUpper) != std::string::npos; - bool inRideType = (item->Type == ObjectType::Ride) && typeUpper.find(filterUpper) != std::string::npos; - bool inPath = pathUpper.find(filterUpper) != std::string::npos; - bool inAuthor = IsFilterInAuthor(item->Authors, filterUpper); - - return inName || inRideType || inPath || inAuthor; + return IsFilterInName(item, filter) || IsFilterInRideType(item, filter) || IsFilterInFilename(item, filter) + || IsFilterInAuthor(item, filter); } bool SourcesMatch(ObjectSourceGame source) @@ -1425,7 +1428,7 @@ private: for (size_t i = 0; i < numObjects; i++) { const ObjectRepositoryItem* item = &items[i]; - if (FilterSource(item) && FilterString(item) && FilterChunks(item) && FilterSelected(selectionFlags[i])) + if (FilterSource(item) && FilterString(*item) && FilterChunks(item) && FilterSelected(selectionFlags[i])) { _filter_object_counts[EnumValue(item->Type)]++; } diff --git a/src/openrct2/core/String.cpp b/src/openrct2/core/String.cpp index 5eb7a8660d..43a5d2a6c0 100644 --- a/src/openrct2/core/String.cpp +++ b/src/openrct2/core/String.cpp @@ -242,6 +242,26 @@ namespace String return false; } + bool Contains(std::string_view haystack, std::string_view needle, bool ignoreCase) + { + if (needle.size() > haystack.size()) + return false; + + if (!ignoreCase) + return haystack.find(needle) != std::string_view::npos; + + auto end = haystack.size() - needle.size(); + for (size_t start = 0; start <= end; start++) + { + auto sub = haystack.substr(start, needle.size()); + if (Equals(sub, needle, ignoreCase)) + { + return true; + } + } + return false; + } + size_t IndexOf(const utf8* str, utf8 match, size_t startIndex) { const utf8* ch = str + startIndex; diff --git a/src/openrct2/core/String.hpp b/src/openrct2/core/String.hpp index 549469389d..923ae787af 100644 --- a/src/openrct2/core/String.hpp +++ b/src/openrct2/core/String.hpp @@ -61,6 +61,7 @@ namespace String bool Equals(const utf8* a, const utf8* b, bool ignoreCase = false); bool StartsWith(std::string_view str, std::string_view match, bool ignoreCase = false); bool EndsWith(std::string_view str, std::string_view match, bool ignoreCase = false); + bool Contains(std::string_view haystack, std::string_view needle, bool ignoreCase = false); size_t IndexOf(const utf8* str, utf8 match, size_t startIndex = 0); ptrdiff_t LastIndexOf(const utf8* str, utf8 match);