mirror of
https://github.com/ppy/osu
synced 2024-12-15 19:36:34 +00:00
Merge pull request #25679 from peppy/song-select-search-performance
Improve song select search performance
This commit is contained in:
commit
566d336470
@ -1,8 +1,8 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Screens.Select;
|
||||
|
||||
namespace osu.Game.Beatmaps
|
||||
{
|
||||
@ -29,20 +29,22 @@ namespace osu.Game.Beatmaps
|
||||
return new RomanisableString($"{metadata.GetPreferred(true)}".Trim(), $"{metadata.GetPreferred(false)}".Trim());
|
||||
}
|
||||
|
||||
public static List<string> GetSearchableTerms(this IBeatmapInfo beatmapInfo)
|
||||
public static bool Match(this IBeatmapInfo beatmapInfo, params FilterCriteria.OptionalTextFilter[] filters)
|
||||
{
|
||||
var termsList = new List<string>(BeatmapMetadataInfoExtensions.MAX_SEARCHABLE_TERM_COUNT + 1);
|
||||
|
||||
addIfNotNull(beatmapInfo.DifficultyName);
|
||||
|
||||
BeatmapMetadataInfoExtensions.CollectSearchableTerms(beatmapInfo.Metadata, termsList);
|
||||
return termsList;
|
||||
|
||||
void addIfNotNull(string? s)
|
||||
foreach (var filter in filters)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(s))
|
||||
termsList.Add(s);
|
||||
if (filter.Matches(beatmapInfo.DifficultyName))
|
||||
continue;
|
||||
|
||||
if (BeatmapMetadataInfoExtensions.Match(beatmapInfo.Metadata, filter))
|
||||
continue;
|
||||
|
||||
// failed to match a single filter at all - fail the whole match.
|
||||
return false;
|
||||
}
|
||||
|
||||
// got through all filters without failing any - pass the whole match.
|
||||
return true;
|
||||
}
|
||||
|
||||
private static string getVersionString(IBeatmapInfo beatmapInfo) => string.IsNullOrEmpty(beatmapInfo.DifficultyName) ? string.Empty : $"[{beatmapInfo.DifficultyName}]";
|
||||
|
@ -3,11 +3,14 @@
|
||||
|
||||
using System.Collections.Generic;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Screens.Select;
|
||||
|
||||
namespace osu.Game.Beatmaps
|
||||
{
|
||||
public static class BeatmapMetadataInfoExtensions
|
||||
{
|
||||
internal const int MAX_SEARCHABLE_TERM_COUNT = 7;
|
||||
|
||||
/// <summary>
|
||||
/// An array of all searchable terms provided in contained metadata.
|
||||
/// </summary>
|
||||
@ -18,7 +21,18 @@ namespace osu.Game.Beatmaps
|
||||
return termsList.ToArray();
|
||||
}
|
||||
|
||||
internal const int MAX_SEARCHABLE_TERM_COUNT = 7;
|
||||
public static bool Match(IBeatmapMetadataInfo metadataInfo, FilterCriteria.OptionalTextFilter filter)
|
||||
{
|
||||
if (filter.Matches(metadataInfo.Author.Username)) return true;
|
||||
if (filter.Matches(metadataInfo.Artist)) return true;
|
||||
if (filter.Matches(metadataInfo.ArtistUnicode)) return true;
|
||||
if (filter.Matches(metadataInfo.Title)) return true;
|
||||
if (filter.Matches(metadataInfo.TitleUnicode)) return true;
|
||||
if (filter.Matches(metadataInfo.Source)) return true;
|
||||
if (filter.Matches(metadataInfo.Tags)) return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
internal static void CollectSearchableTerms(IBeatmapMetadataInfo metadataInfo, IList<string> termsList)
|
||||
{
|
||||
|
@ -41,6 +41,21 @@ namespace osu.Game.Screens.Select.Carousel
|
||||
return match;
|
||||
}
|
||||
|
||||
if (criteria.SearchTerms.Length > 0)
|
||||
{
|
||||
match = BeatmapInfo.Match(criteria.SearchTerms);
|
||||
|
||||
// if a match wasn't found via text matching of terms, do a second catch-all check matching against online IDs.
|
||||
// this should be done after text matching so we can prioritise matching numbers in metadata.
|
||||
if (!match && criteria.SearchNumber.HasValue)
|
||||
{
|
||||
match = (BeatmapInfo.OnlineID == criteria.SearchNumber.Value) ||
|
||||
(BeatmapInfo.BeatmapSet?.OnlineID == criteria.SearchNumber.Value);
|
||||
}
|
||||
}
|
||||
|
||||
if (!match) return false;
|
||||
|
||||
match &= !criteria.StarDifficulty.HasFilter || criteria.StarDifficulty.IsInRange(BeatmapInfo.StarRating);
|
||||
match &= !criteria.ApproachRate.HasFilter || criteria.ApproachRate.IsInRange(BeatmapInfo.Difficulty.ApproachRate);
|
||||
match &= !criteria.DrainRate.HasFilter || criteria.DrainRate.IsInRange(BeatmapInfo.Difficulty.DrainRate);
|
||||
@ -64,40 +79,6 @@ namespace osu.Game.Screens.Select.Carousel
|
||||
|
||||
if (!match) return false;
|
||||
|
||||
if (criteria.SearchTerms.Length > 0)
|
||||
{
|
||||
var searchableTerms = BeatmapInfo.GetSearchableTerms();
|
||||
|
||||
foreach (FilterCriteria.OptionalTextFilter criteriaTerm in criteria.SearchTerms)
|
||||
{
|
||||
bool any = false;
|
||||
|
||||
// ReSharper disable once ForeachCanBeConvertedToQueryUsingAnotherGetEnumerator
|
||||
foreach (string searchTerm in searchableTerms)
|
||||
{
|
||||
if (!criteriaTerm.Matches(searchTerm)) continue;
|
||||
|
||||
any = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (any) continue;
|
||||
|
||||
match = false;
|
||||
break;
|
||||
}
|
||||
|
||||
// if a match wasn't found via text matching of terms, do a second catch-all check matching against online IDs.
|
||||
// this should be done after text matching so we can prioritise matching numbers in metadata.
|
||||
if (!match && criteria.SearchNumber.HasValue)
|
||||
{
|
||||
match = (BeatmapInfo.OnlineID == criteria.SearchNumber.Value) ||
|
||||
(BeatmapInfo.BeatmapSet?.OnlineID == criteria.SearchNumber.Value);
|
||||
}
|
||||
}
|
||||
|
||||
if (!match) return false;
|
||||
|
||||
match &= criteria.CollectionBeatmapMD5Hashes?.Contains(BeatmapInfo.MD5Hash) ?? true;
|
||||
if (match && criteria.RulesetCriteria != null)
|
||||
match &= criteria.RulesetCriteria.Matches(BeatmapInfo);
|
||||
|
@ -86,16 +86,20 @@ namespace osu.Game.Screens.Select.Carousel
|
||||
|
||||
items.ForEach(c => c.Filter(criteria));
|
||||
|
||||
criteriaComparer = Comparer<CarouselItem>.Create((x, y) =>
|
||||
// Sorting is expensive, so only perform if it's actually changed.
|
||||
if (lastCriteria?.Sort != criteria.Sort)
|
||||
{
|
||||
int comparison = x.CompareTo(criteria, y);
|
||||
if (comparison != 0)
|
||||
return comparison;
|
||||
criteriaComparer = Comparer<CarouselItem>.Create((x, y) =>
|
||||
{
|
||||
int comparison = x.CompareTo(criteria, y);
|
||||
if (comparison != 0)
|
||||
return comparison;
|
||||
|
||||
return x.ItemID.CompareTo(y.ItemID);
|
||||
});
|
||||
return x.ItemID.CompareTo(y.ItemID);
|
||||
});
|
||||
|
||||
items.Sort(criteriaComparer);
|
||||
items.Sort(criteriaComparer);
|
||||
}
|
||||
|
||||
lastCriteria = criteria;
|
||||
}
|
||||
|
@ -176,13 +176,15 @@ namespace osu.Game.Screens.Select
|
||||
{
|
||||
default:
|
||||
case MatchMode.Substring:
|
||||
return value.Contains(SearchTerm, StringComparison.InvariantCultureIgnoreCase);
|
||||
// Note that we are using ordinal here to avoid performance issues caused by globalisation concerns.
|
||||
// See https://github.com/ppy/osu/issues/11571 / https://github.com/dotnet/docs/issues/18423.
|
||||
return value.Contains(SearchTerm, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
case MatchMode.IsolatedPhrase:
|
||||
return Regex.IsMatch(value, $@"(^|\s){Regex.Escape(searchTerm)}($|\s)", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
|
||||
|
||||
case MatchMode.FullPhrase:
|
||||
return CultureInfo.InvariantCulture.CompareInfo.Compare(value, searchTerm, CompareOptions.IgnoreCase) == 0;
|
||||
return CultureInfo.InvariantCulture.CompareInfo.Compare(value, searchTerm, CompareOptions.OrdinalIgnoreCase) == 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user