mirror of
https://github.com/ppy/osu.git
synced 2025-01-12 19:03:08 +08:00
Merge pull request #29639 from peppy/detached-beatmap-cache
Improve song select load and reload with large beatmap databases
This commit is contained in:
commit
4811481483
@ -18,6 +18,7 @@ using osu.Framework.Screens;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
@ -48,13 +49,18 @@ namespace osu.Game.Tests.Visual.Background
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(GameHost host, AudioManager audio)
|
||||
{
|
||||
DetachedBeatmapStore detachedBeatmapStore;
|
||||
|
||||
Dependencies.Cache(rulesets = new RealmRulesetStore(Realm));
|
||||
Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Realm, null, audio, Resources, host, Beatmap.Default));
|
||||
Dependencies.Cache(new OsuConfigManager(LocalStorage));
|
||||
Dependencies.Cache(detachedBeatmapStore = new DetachedBeatmapStore());
|
||||
Dependencies.Cache(Realm);
|
||||
|
||||
manager.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely();
|
||||
|
||||
Add(detachedBeatmapStore);
|
||||
|
||||
Beatmap.SetDefault();
|
||||
}
|
||||
|
||||
|
@ -36,7 +36,7 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
() => Is.EqualTo(1));
|
||||
|
||||
AddStep("enter song select", () => Game.ChildrenOfType<ButtonSystem>().Single().OnSolo?.Invoke());
|
||||
AddUntilStep("entered song select", () => Game.ScreenStack.CurrentScreen is PlaySongSelect);
|
||||
AddUntilStep("entered song select", () => Game.ScreenStack.CurrentScreen is PlaySongSelect songSelect && songSelect.BeatmapSetsLoaded);
|
||||
|
||||
addStepClickLink("00:00:000 (1)", waitForSeek: false);
|
||||
AddUntilStep("received 'must be in edit'",
|
||||
@ -138,7 +138,7 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
AddUntilStep("Wait for song select", () =>
|
||||
Game.Beatmap.Value.BeatmapSetInfo.Equals(beatmapSet)
|
||||
&& Game.ScreenStack.CurrentScreen is PlaySongSelect songSelect
|
||||
&& songSelect.IsLoaded
|
||||
&& songSelect.BeatmapSetsLoaded
|
||||
);
|
||||
AddStep("Switch ruleset", () => Game.Ruleset.Value = ruleset);
|
||||
AddStep("Open editor for ruleset", () =>
|
||||
|
@ -12,6 +12,7 @@ using osu.Framework.Platform;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Online.Multiplayer;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Rulesets;
|
||||
@ -45,9 +46,14 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(GameHost host, AudioManager audio)
|
||||
{
|
||||
DetachedBeatmapStore detachedBeatmapStore;
|
||||
|
||||
Dependencies.Cache(new RealmRulesetStore(Realm));
|
||||
Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, null, audio, Resources, host, Beatmap.Default));
|
||||
Dependencies.Cache(detachedBeatmapStore = new DetachedBeatmapStore());
|
||||
Dependencies.Cache(Realm);
|
||||
|
||||
Add(detachedBeatmapStore);
|
||||
}
|
||||
|
||||
public override void SetUpSteps()
|
||||
|
@ -19,6 +19,7 @@ using osu.Framework.Testing;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
@ -65,9 +66,14 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(GameHost host, AudioManager audio)
|
||||
{
|
||||
DetachedBeatmapStore detachedBeatmapStore;
|
||||
|
||||
Dependencies.Cache(new RealmRulesetStore(Realm));
|
||||
Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, API, audio, Resources, host, Beatmap.Default));
|
||||
Dependencies.Cache(detachedBeatmapStore = new DetachedBeatmapStore());
|
||||
Dependencies.Cache(Realm);
|
||||
|
||||
Add(detachedBeatmapStore);
|
||||
}
|
||||
|
||||
public override void SetUpSteps()
|
||||
|
@ -45,11 +45,16 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(GameHost host, AudioManager audio)
|
||||
{
|
||||
DetachedBeatmapStore detachedBeatmapStore;
|
||||
|
||||
Dependencies.Cache(rulesets = new RealmRulesetStore(Realm));
|
||||
Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Realm, null, audio, Resources, host, Beatmap.Default));
|
||||
Dependencies.Cache(detachedBeatmapStore = new DetachedBeatmapStore());
|
||||
Dependencies.Cache(Realm);
|
||||
|
||||
importedBeatmapSet = manager.Import(TestResources.CreateTestBeatmapSetInfo(8, rulesets.AvailableRulesets.ToArray()));
|
||||
|
||||
Add(detachedBeatmapStore);
|
||||
}
|
||||
|
||||
public override void SetUpSteps()
|
||||
|
@ -12,6 +12,7 @@ using osu.Framework.Platform;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
@ -33,13 +34,18 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(GameHost host, AudioManager audio)
|
||||
{
|
||||
DetachedBeatmapStore detachedBeatmapStore;
|
||||
|
||||
Dependencies.Cache(new RealmRulesetStore(Realm));
|
||||
Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Realm, null, audio, Resources, host, Beatmap.Default));
|
||||
Dependencies.Cache(detachedBeatmapStore = new DetachedBeatmapStore());
|
||||
Dependencies.Cache(Realm);
|
||||
|
||||
var beatmapSet = TestResources.CreateTestBeatmapSetInfo();
|
||||
|
||||
manager.Import(beatmapSet);
|
||||
|
||||
Add(detachedBeatmapStore);
|
||||
}
|
||||
|
||||
public override void SetUpSteps()
|
||||
|
@ -165,16 +165,19 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
}
|
||||
|
||||
[Test]
|
||||
[Solo]
|
||||
public void TestEditorGameplayTestAlwaysUsesOriginalRuleset()
|
||||
{
|
||||
prepareBeatmap();
|
||||
|
||||
AddStep("switch ruleset", () => Game.Ruleset.Value = new ManiaRuleset().RulesetInfo);
|
||||
AddStep("switch ruleset at song select", () => Game.Ruleset.Value = new ManiaRuleset().RulesetInfo);
|
||||
|
||||
AddStep("open editor", () => ((PlaySongSelect)Game.ScreenStack.CurrentScreen).Edit(beatmapSet.Beatmaps.First(beatmap => beatmap.Ruleset.OnlineID == 0)));
|
||||
AddUntilStep("wait for editor open", () => Game.ScreenStack.CurrentScreen is Editor editor && editor.ReadyForUse);
|
||||
AddStep("test gameplay", () => getEditor().TestGameplay());
|
||||
|
||||
AddUntilStep("wait for editor open", () => Game.ScreenStack.CurrentScreen is Editor editor && editor.ReadyForUse);
|
||||
AddAssert("editor ruleset is osu!", () => Game.Ruleset.Value, () => Is.EqualTo(new OsuRuleset().RulesetInfo));
|
||||
|
||||
AddStep("test gameplay", () => getEditor().TestGameplay());
|
||||
AddUntilStep("wait for player", () =>
|
||||
{
|
||||
// notifications may fire at almost any inopportune time and cause annoying test failures.
|
||||
@ -183,8 +186,7 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
Game.CloseAllOverlays();
|
||||
return Game.ScreenStack.CurrentScreen is EditorPlayer editorPlayer && editorPlayer.IsLoaded;
|
||||
});
|
||||
|
||||
AddAssert("current ruleset is osu!", () => Game.Ruleset.Value.Equals(new OsuRuleset().RulesetInfo));
|
||||
AddAssert("gameplay ruleset is osu!", () => Game.Ruleset.Value, () => Is.EqualTo(new OsuRuleset().RulesetInfo));
|
||||
|
||||
AddStep("exit to song select", () => Game.PerformFromScreen(_ => { }, typeof(PlaySongSelect).Yield()));
|
||||
AddUntilStep("wait for song select", () => Game.ScreenStack.CurrentScreen is PlaySongSelect);
|
||||
@ -352,7 +354,7 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
AddUntilStep("wait for song select",
|
||||
() => Game.Beatmap.Value.BeatmapSetInfo.Equals(beatmapSet)
|
||||
&& Game.ScreenStack.CurrentScreen is PlaySongSelect songSelect
|
||||
&& songSelect.IsLoaded);
|
||||
&& songSelect.BeatmapSetsLoaded);
|
||||
}
|
||||
|
||||
private void openEditor()
|
||||
|
@ -176,6 +176,12 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
|
||||
private void confirmBeatmapInSongSelect(Func<BeatmapSetInfo> getImport)
|
||||
{
|
||||
AddUntilStep("wait for carousel loaded", () =>
|
||||
{
|
||||
var songSelect = (Screens.Select.SongSelect)Game.ScreenStack.CurrentScreen;
|
||||
return songSelect.ChildrenOfType<BeatmapCarousel>().SingleOrDefault()?.IsLoaded == true;
|
||||
});
|
||||
|
||||
AddUntilStep("beatmap in song select", () =>
|
||||
{
|
||||
var songSelect = (Screens.Select.SongSelect)Game.ScreenStack.CurrentScreen;
|
||||
@ -187,7 +193,7 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
{
|
||||
AddStep("present beatmap", () => Game.PresentBeatmap(getImport()));
|
||||
|
||||
AddUntilStep("wait for song select", () => Game.ScreenStack.CurrentScreen is Screens.Select.SongSelect songSelect && songSelect.IsLoaded);
|
||||
AddUntilStep("wait for song select", () => Game.ScreenStack.CurrentScreen is Screens.Select.SongSelect songSelect && songSelect.BeatmapSetsLoaded);
|
||||
AddUntilStep("correct beatmap displayed", () => Game.Beatmap.Value.BeatmapSetInfo.OnlineID, () => Is.EqualTo(getImport().OnlineID));
|
||||
AddAssert("correct ruleset selected", () => Game.Ruleset.Value, () => Is.EqualTo(getImport().Beatmaps.First().Ruleset));
|
||||
}
|
||||
@ -197,7 +203,7 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
Predicate<BeatmapInfo> pred = b => b.OnlineID == importedID * 1024 + 2;
|
||||
AddStep("present difficulty", () => Game.PresentBeatmap(getImport(), pred));
|
||||
|
||||
AddUntilStep("wait for song select", () => Game.ScreenStack.CurrentScreen is Screens.Select.SongSelect songSelect && songSelect.IsLoaded);
|
||||
AddUntilStep("wait for song select", () => Game.ScreenStack.CurrentScreen is Screens.Select.SongSelect songSelect && songSelect.BeatmapSetsLoaded);
|
||||
AddUntilStep("correct beatmap displayed", () => Game.Beatmap.Value.BeatmapInfo.OnlineID, () => Is.EqualTo(importedID * 1024 + 2));
|
||||
AddAssert("correct ruleset selected", () => Game.Ruleset.Value.OnlineID, () => Is.EqualTo(expectedRulesetOnlineID ?? getImport().Beatmaps.First().Ruleset.OnlineID));
|
||||
}
|
||||
|
@ -1035,9 +1035,11 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
[Test]
|
||||
public void TestTouchScreenDetectionInGame()
|
||||
{
|
||||
BeatmapSetInfo beatmapSet = null;
|
||||
|
||||
PushAndConfirm(() => new TestPlaySongSelect());
|
||||
AddStep("import beatmap", () => BeatmapImportHelper.LoadQuickOszIntoOsu(Game).WaitSafely());
|
||||
AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault);
|
||||
AddStep("import beatmap", () => beatmapSet = BeatmapImportHelper.LoadQuickOszIntoOsu(Game).GetResultSafely());
|
||||
AddUntilStep("wait for selected", () => Game.Beatmap.Value.BeatmapSetInfo.Equals(beatmapSet));
|
||||
AddStep("select", () => InputManager.Key(Key.Enter));
|
||||
|
||||
Player player = null;
|
||||
|
@ -6,6 +6,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||
@ -52,11 +53,11 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
{
|
||||
createCarousel(new List<BeatmapSetInfo>());
|
||||
|
||||
AddStep("filter to ruleset 0", () => carousel.Filter(new FilterCriteria
|
||||
AddStep("filter to ruleset 0", () => carousel.FilterImmediately(new FilterCriteria
|
||||
{
|
||||
Ruleset = rulesets.AvailableRulesets.ElementAt(0),
|
||||
AllowConvertedBeatmaps = true,
|
||||
}, false));
|
||||
}));
|
||||
|
||||
AddStep("add mixed ruleset beatmapset", () =>
|
||||
{
|
||||
@ -78,11 +79,11 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
&& visibleBeatmapPanels.Count(p => ((CarouselBeatmap)p.Item)!.BeatmapInfo.Ruleset.OnlineID == 0) == 1;
|
||||
});
|
||||
|
||||
AddStep("filter to ruleset 1", () => carousel.Filter(new FilterCriteria
|
||||
AddStep("filter to ruleset 1", () => carousel.FilterImmediately(new FilterCriteria
|
||||
{
|
||||
Ruleset = rulesets.AvailableRulesets.ElementAt(1),
|
||||
AllowConvertedBeatmaps = true,
|
||||
}, false));
|
||||
}));
|
||||
|
||||
AddUntilStep("wait for filtered difficulties", () =>
|
||||
{
|
||||
@ -93,11 +94,11 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
&& visibleBeatmapPanels.Count(p => ((CarouselBeatmap)p.Item)!.BeatmapInfo.Ruleset.OnlineID == 1) == 1;
|
||||
});
|
||||
|
||||
AddStep("filter to ruleset 2", () => carousel.Filter(new FilterCriteria
|
||||
AddStep("filter to ruleset 2", () => carousel.FilterImmediately(new FilterCriteria
|
||||
{
|
||||
Ruleset = rulesets.AvailableRulesets.ElementAt(2),
|
||||
AllowConvertedBeatmaps = true,
|
||||
}, false));
|
||||
}));
|
||||
|
||||
AddUntilStep("wait for filtered difficulties", () =>
|
||||
{
|
||||
@ -344,7 +345,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
// basic filtering
|
||||
setSelected(1, 1);
|
||||
|
||||
AddStep("Filter", () => carousel.Filter(new FilterCriteria { SearchText = carousel.BeatmapSets.ElementAt(2).Metadata.Title }, false));
|
||||
AddStep("Filter", () => carousel.FilterImmediately(new FilterCriteria { SearchText = carousel.BeatmapSets.ElementAt(2).Metadata.Title }));
|
||||
checkVisibleItemCount(diff: false, count: 1);
|
||||
checkVisibleItemCount(diff: true, count: 3);
|
||||
waitForSelection(3, 1);
|
||||
@ -360,13 +361,13 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
// test filtering some difficulties (and keeping current beatmap set selected).
|
||||
|
||||
setSelected(1, 2);
|
||||
AddStep("Filter some difficulties", () => carousel.Filter(new FilterCriteria { SearchText = "Normal" }, false));
|
||||
AddStep("Filter some difficulties", () => carousel.FilterImmediately(new FilterCriteria { SearchText = "Normal" }));
|
||||
waitForSelection(1, 1);
|
||||
|
||||
AddStep("Un-filter", () => carousel.Filter(new FilterCriteria(), false));
|
||||
AddStep("Un-filter", () => carousel.FilterImmediately(new FilterCriteria()));
|
||||
waitForSelection(1, 1);
|
||||
|
||||
AddStep("Filter all", () => carousel.Filter(new FilterCriteria { SearchText = "Dingo" }, false));
|
||||
AddStep("Filter all", () => carousel.FilterImmediately(new FilterCriteria { SearchText = "Dingo" }));
|
||||
|
||||
checkVisibleItemCount(false, 0);
|
||||
checkVisibleItemCount(true, 0);
|
||||
@ -378,7 +379,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
advanceSelection(false);
|
||||
AddAssert("Selection is null", () => currentSelection == null);
|
||||
|
||||
AddStep("Un-filter", () => carousel.Filter(new FilterCriteria(), false));
|
||||
AddStep("Un-filter", () => carousel.FilterImmediately(new FilterCriteria()));
|
||||
|
||||
AddAssert("Selection is non-null", () => currentSelection != null);
|
||||
|
||||
@ -399,7 +400,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
|
||||
setSelected(1, 3);
|
||||
|
||||
AddStep("Apply a range filter", () => carousel.Filter(new FilterCriteria
|
||||
AddStep("Apply a range filter", () => carousel.FilterImmediately(new FilterCriteria
|
||||
{
|
||||
SearchText = searchText,
|
||||
StarDifficulty = new FilterCriteria.OptionalRange<double>
|
||||
@ -408,7 +409,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
Max = 5.5,
|
||||
IsLowerInclusive = true
|
||||
}
|
||||
}, false));
|
||||
}));
|
||||
|
||||
// should reselect the buffered selection.
|
||||
waitForSelection(3, 2);
|
||||
@ -445,13 +446,13 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
AddAssert("ensure repeat", () => selectedSets.Contains(carousel.SelectedBeatmapSet));
|
||||
|
||||
AddStep("Add set with 100 difficulties", () => carousel.UpdateBeatmapSet(TestResources.CreateTestBeatmapSetInfo(100, rulesets.AvailableRulesets.ToArray())));
|
||||
AddStep("Filter Extra", () => carousel.Filter(new FilterCriteria { SearchText = "Extra 10" }, false));
|
||||
AddStep("Filter Extra", () => carousel.FilterImmediately(new FilterCriteria { SearchText = "Extra 10" }));
|
||||
checkInvisibleDifficultiesUnselectable();
|
||||
checkInvisibleDifficultiesUnselectable();
|
||||
checkInvisibleDifficultiesUnselectable();
|
||||
checkInvisibleDifficultiesUnselectable();
|
||||
checkInvisibleDifficultiesUnselectable();
|
||||
AddStep("Un-filter", () => carousel.Filter(new FilterCriteria(), false));
|
||||
AddStep("Un-filter", () => carousel.FilterImmediately(new FilterCriteria()));
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -527,7 +528,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
|
||||
loadBeatmaps(setCount: local_set_count, diffCount: local_diff_count);
|
||||
|
||||
AddStep("Sort by difficulty", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Difficulty }, false));
|
||||
AddStep("Sort by difficulty", () => carousel.FilterImmediately(new FilterCriteria { Sort = SortMode.Difficulty }));
|
||||
|
||||
checkVisibleItemCount(false, local_set_count * local_diff_count);
|
||||
|
||||
@ -566,7 +567,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
loadBeatmaps(sets, () => new FilterCriteria { Ruleset = rulesets.AvailableRulesets.ElementAt(0) });
|
||||
|
||||
AddStep("Set non-empty mode filter", () =>
|
||||
carousel.Filter(new FilterCriteria { Ruleset = rulesets.AvailableRulesets.ElementAt(1) }, false));
|
||||
carousel.FilterImmediately(new FilterCriteria { Ruleset = rulesets.AvailableRulesets.ElementAt(1) }));
|
||||
|
||||
AddAssert("Something is selected", () => carousel.SelectedBeatmapInfo != null);
|
||||
}
|
||||
@ -601,7 +602,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
|
||||
loadBeatmaps(sets);
|
||||
|
||||
AddStep("Sort by date submitted", () => carousel.Filter(new FilterCriteria { Sort = SortMode.DateSubmitted }, false));
|
||||
AddStep("Sort by date submitted", () => carousel.FilterImmediately(new FilterCriteria { Sort = SortMode.DateSubmitted }));
|
||||
checkVisibleItemCount(diff: false, count: 10);
|
||||
checkVisibleItemCount(diff: true, count: 5);
|
||||
|
||||
@ -610,11 +611,11 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
AddAssert("rest are at start", () => carousel.Items.OfType<DrawableCarouselBeatmapSet>().TakeWhile(i => i.Item is CarouselBeatmapSet s && s.BeatmapSet.DateSubmitted != null).Count(),
|
||||
() => Is.EqualTo(6));
|
||||
|
||||
AddStep("Sort by date submitted and string", () => carousel.Filter(new FilterCriteria
|
||||
AddStep("Sort by date submitted and string", () => carousel.FilterImmediately(new FilterCriteria
|
||||
{
|
||||
Sort = SortMode.DateSubmitted,
|
||||
SearchText = zzz_string
|
||||
}, false));
|
||||
}));
|
||||
checkVisibleItemCount(diff: false, count: 5);
|
||||
checkVisibleItemCount(diff: true, count: 5);
|
||||
|
||||
@ -658,10 +659,10 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
|
||||
loadBeatmaps(sets);
|
||||
|
||||
AddStep("Sort by author", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Author }, false));
|
||||
AddStep("Sort by author", () => carousel.FilterImmediately(new FilterCriteria { Sort = SortMode.Author }));
|
||||
AddAssert($"Check {zzz_uppercase} is last", () => carousel.BeatmapSets.Last().Metadata.Author.Username == zzz_uppercase);
|
||||
AddAssert($"Check {zzz_lowercase} is second last", () => carousel.BeatmapSets.SkipLast(1).Last().Metadata.Author.Username == zzz_lowercase);
|
||||
AddStep("Sort by artist", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Artist }, false));
|
||||
AddStep("Sort by artist", () => carousel.FilterImmediately(new FilterCriteria { Sort = SortMode.Artist }));
|
||||
AddAssert($"Check {zzz_uppercase} is last", () => carousel.BeatmapSets.Last().Metadata.Artist == zzz_uppercase);
|
||||
AddAssert($"Check {zzz_lowercase} is second last", () => carousel.BeatmapSets.SkipLast(1).Last().Metadata.Artist == zzz_lowercase);
|
||||
}
|
||||
@ -703,7 +704,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
|
||||
loadBeatmaps(sets);
|
||||
|
||||
AddStep("Sort by artist", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Artist }, false));
|
||||
AddStep("Sort by artist", () => carousel.FilterImmediately(new FilterCriteria { Sort = SortMode.Artist }));
|
||||
AddAssert("Check last item", () =>
|
||||
{
|
||||
var lastItem = carousel.BeatmapSets.Last();
|
||||
@ -746,10 +747,10 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
|
||||
loadBeatmaps(sets);
|
||||
|
||||
AddStep("Sort by title", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Title }, false));
|
||||
AddStep("Sort by title", () => carousel.FilterImmediately(new FilterCriteria { Sort = SortMode.Title }));
|
||||
AddAssert("Items remain in descending added order", () => carousel.BeatmapSets.Select(s => s.DateAdded), () => Is.Ordered.Descending);
|
||||
|
||||
AddStep("Sort by artist", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Artist }, false));
|
||||
AddStep("Sort by artist", () => carousel.FilterImmediately(new FilterCriteria { Sort = SortMode.Artist }));
|
||||
AddAssert("Items remain in descending added order", () => carousel.BeatmapSets.Select(s => s.DateAdded), () => Is.Ordered.Descending);
|
||||
}
|
||||
|
||||
@ -786,7 +787,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
|
||||
loadBeatmaps(sets);
|
||||
|
||||
AddStep("Sort by artist", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Artist }, false));
|
||||
AddStep("Sort by artist", () => carousel.FilterImmediately(new FilterCriteria { Sort = SortMode.Artist }));
|
||||
|
||||
AddAssert("Items in descending added order", () => carousel.BeatmapSets.Select(s => s.DateAdded), () => Is.Ordered.Descending);
|
||||
AddStep("Save order", () => originalOrder = carousel.BeatmapSets.Select(s => s.ID).ToArray());
|
||||
@ -796,7 +797,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
|
||||
AddAssert("Order didn't change", () => carousel.BeatmapSets.Select(s => s.ID), () => Is.EqualTo(originalOrder));
|
||||
|
||||
AddStep("Sort by title", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Title }, false));
|
||||
AddStep("Sort by title", () => carousel.FilterImmediately(new FilterCriteria { Sort = SortMode.Title }));
|
||||
AddAssert("Order didn't change", () => carousel.BeatmapSets.Select(s => s.ID), () => Is.EqualTo(originalOrder));
|
||||
}
|
||||
|
||||
@ -833,7 +834,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
|
||||
loadBeatmaps(sets);
|
||||
|
||||
AddStep("Sort by artist", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Artist }, false));
|
||||
AddStep("Sort by artist", () => carousel.FilterImmediately(new FilterCriteria { Sort = SortMode.Artist }));
|
||||
|
||||
AddAssert("Items in descending added order", () => carousel.BeatmapSets.Select(s => s.DateAdded), () => Is.Ordered.Descending);
|
||||
AddStep("Save order", () => originalOrder = carousel.BeatmapSets.Select(s => s.ID).ToArray());
|
||||
@ -858,7 +859,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
|
||||
AddAssert("Order didn't change", () => carousel.BeatmapSets.Select(s => s.ID), () => Is.EqualTo(originalOrder));
|
||||
|
||||
AddStep("Sort by title", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Title }, false));
|
||||
AddStep("Sort by title", () => carousel.FilterImmediately(new FilterCriteria { Sort = SortMode.Title }));
|
||||
AddAssert("Order didn't change", () => carousel.BeatmapSets.Select(s => s.ID), () => Is.EqualTo(originalOrder));
|
||||
}
|
||||
|
||||
@ -885,12 +886,12 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
|
||||
loadBeatmaps(sets);
|
||||
|
||||
AddStep("Sort by difficulty", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Difficulty }, false));
|
||||
AddStep("Sort by difficulty", () => carousel.FilterImmediately(new FilterCriteria { Sort = SortMode.Difficulty }));
|
||||
|
||||
checkVisibleItemCount(false, local_set_count * local_diff_count);
|
||||
checkVisibleItemCount(true, 1);
|
||||
|
||||
AddStep("Filter to normal", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Difficulty, SearchText = "Normal" }, false));
|
||||
AddStep("Filter to normal", () => carousel.FilterImmediately(new FilterCriteria { Sort = SortMode.Difficulty, SearchText = "Normal" }));
|
||||
checkVisibleItemCount(false, local_set_count);
|
||||
checkVisibleItemCount(true, 1);
|
||||
|
||||
@ -901,7 +902,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
.Count(p => ((CarouselBeatmapSet)p.Item)!.Beatmaps.Single().BeatmapInfo.DifficultyName.StartsWith("Normal", StringComparison.Ordinal)) == local_set_count;
|
||||
});
|
||||
|
||||
AddStep("Filter to insane", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Difficulty, SearchText = "Insane" }, false));
|
||||
AddStep("Filter to insane", () => carousel.FilterImmediately(new FilterCriteria { Sort = SortMode.Difficulty, SearchText = "Insane" }));
|
||||
checkVisibleItemCount(false, local_set_count);
|
||||
checkVisibleItemCount(true, 1);
|
||||
|
||||
@ -1022,7 +1023,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
carousel.UpdateBeatmapSet(testMixed);
|
||||
});
|
||||
AddStep("filter to ruleset 0", () =>
|
||||
carousel.Filter(new FilterCriteria { Ruleset = rulesets.AvailableRulesets.ElementAt(0) }, false));
|
||||
carousel.FilterImmediately(new FilterCriteria { Ruleset = rulesets.AvailableRulesets.ElementAt(0) }));
|
||||
AddStep("select filtered map skipping filtered", () => carousel.SelectBeatmap(testMixed.Beatmaps[1], false));
|
||||
AddAssert("unfiltered beatmap not selected", () => carousel.SelectedBeatmapInfo?.Ruleset.OnlineID == 0);
|
||||
|
||||
@ -1068,12 +1069,12 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
{
|
||||
AddStep("Toggle non-matching filter", () =>
|
||||
{
|
||||
carousel.Filter(new FilterCriteria { SearchText = Guid.NewGuid().ToString() }, false);
|
||||
carousel.FilterImmediately(new FilterCriteria { SearchText = Guid.NewGuid().ToString() });
|
||||
});
|
||||
|
||||
AddStep("Restore no filter", () =>
|
||||
{
|
||||
carousel.Filter(new FilterCriteria(), false);
|
||||
carousel.FilterImmediately(new FilterCriteria());
|
||||
eagerSelectedIDs.Add(carousel.SelectedBeatmapSet!.ID);
|
||||
});
|
||||
}
|
||||
@ -1097,7 +1098,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
|
||||
loadBeatmaps(manySets);
|
||||
|
||||
AddStep("Sort by difficulty", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Difficulty }, false));
|
||||
AddStep("Sort by difficulty", () => carousel.FilterImmediately(new FilterCriteria { Sort = SortMode.Difficulty }));
|
||||
|
||||
advanceSelection(direction: 1, diff: false);
|
||||
|
||||
@ -1105,12 +1106,12 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
{
|
||||
AddStep("Toggle non-matching filter", () =>
|
||||
{
|
||||
carousel.Filter(new FilterCriteria { SearchText = Guid.NewGuid().ToString() }, false);
|
||||
carousel.FilterImmediately(new FilterCriteria { SearchText = Guid.NewGuid().ToString() });
|
||||
});
|
||||
|
||||
AddStep("Restore no filter", () =>
|
||||
{
|
||||
carousel.Filter(new FilterCriteria(), false);
|
||||
carousel.FilterImmediately(new FilterCriteria());
|
||||
eagerSelectedIDs.Add(carousel.SelectedBeatmapSet!.ID);
|
||||
});
|
||||
}
|
||||
@ -1185,7 +1186,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
|
||||
AddStep($"Set ruleset to {rulesetInfo.ShortName}", () =>
|
||||
{
|
||||
carousel.Filter(new FilterCriteria { Ruleset = rulesetInfo, Sort = SortMode.Title }, false);
|
||||
carousel.FilterImmediately(new FilterCriteria { Ruleset = rulesetInfo, Sort = SortMode.Title });
|
||||
});
|
||||
waitForSelection(i + 1, 1);
|
||||
}
|
||||
@ -1223,12 +1224,12 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
setSelected(i, 1);
|
||||
AddStep("Set ruleset to taiko", () =>
|
||||
{
|
||||
carousel.Filter(new FilterCriteria { Ruleset = rulesets.AvailableRulesets.ElementAt(1), Sort = SortMode.Title }, false);
|
||||
carousel.FilterImmediately(new FilterCriteria { Ruleset = rulesets.AvailableRulesets.ElementAt(1), Sort = SortMode.Title });
|
||||
});
|
||||
waitForSelection(i - 1, 1);
|
||||
AddStep("Remove ruleset filter", () =>
|
||||
{
|
||||
carousel.Filter(new FilterCriteria { Sort = SortMode.Title }, false);
|
||||
carousel.FilterImmediately(new FilterCriteria { Sort = SortMode.Title });
|
||||
});
|
||||
}
|
||||
|
||||
@ -1268,26 +1269,23 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
}
|
||||
}
|
||||
|
||||
createCarousel(beatmapSets, c =>
|
||||
createCarousel(beatmapSets, initialCriteria, c =>
|
||||
{
|
||||
carouselAdjust?.Invoke(c);
|
||||
|
||||
carousel.Filter(initialCriteria?.Invoke() ?? new FilterCriteria());
|
||||
carousel.BeatmapSetsChanged = () => changed = true;
|
||||
carousel.BeatmapSets = beatmapSets;
|
||||
carouselAdjust?.Invoke(c);
|
||||
});
|
||||
|
||||
AddUntilStep("Wait for load", () => changed);
|
||||
}
|
||||
|
||||
private void createCarousel(List<BeatmapSetInfo> beatmapSets, Action<BeatmapCarousel> carouselAdjust = null, Container target = null)
|
||||
private void createCarousel(List<BeatmapSetInfo> beatmapSets, [CanBeNull] Func<FilterCriteria> initialCriteria = null, Action<BeatmapCarousel> carouselAdjust = null, Container target = null)
|
||||
{
|
||||
AddStep("Create carousel", () =>
|
||||
{
|
||||
selectedSets.Clear();
|
||||
eagerSelectedIDs.Clear();
|
||||
|
||||
carousel = new TestBeatmapCarousel
|
||||
carousel = new TestBeatmapCarousel(initialCriteria?.Invoke() ?? new FilterCriteria())
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
};
|
||||
@ -1389,6 +1387,11 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
|
||||
private partial class TestBeatmapCarousel : BeatmapCarousel
|
||||
{
|
||||
public TestBeatmapCarousel(FilterCriteria criteria)
|
||||
: base(criteria)
|
||||
{
|
||||
}
|
||||
|
||||
public bool PendingFilterTask => PendingFilter != null;
|
||||
|
||||
public IEnumerable<DrawableCarouselItem> Items
|
||||
@ -1410,6 +1413,12 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void FilterImmediately(FilterCriteria newCriteria)
|
||||
{
|
||||
Filter(newCriteria);
|
||||
FlushPendingFilterOperations();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -56,16 +56,20 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(GameHost host, AudioManager audio)
|
||||
{
|
||||
DetachedBeatmapStore detachedBeatmapStore;
|
||||
|
||||
// These DI caches are required to ensure for interactive runs this test scene doesn't nuke all user beatmaps in the local install.
|
||||
// At a point we have isolated interactive test runs enough, this can likely be removed.
|
||||
Dependencies.Cache(rulesets = new RealmRulesetStore(Realm));
|
||||
Dependencies.Cache(Realm);
|
||||
Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Realm, null, audio, Resources, host, defaultBeatmap = Beatmap.Default));
|
||||
Dependencies.Cache(detachedBeatmapStore = new DetachedBeatmapStore());
|
||||
|
||||
Dependencies.Cache(music = new MusicController());
|
||||
|
||||
// required to get bindables attached
|
||||
Add(music);
|
||||
Add(detachedBeatmapStore);
|
||||
|
||||
Dependencies.Cache(config = new OsuConfigManager(LocalStorage));
|
||||
}
|
||||
@ -242,7 +246,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
|
||||
createSongSelect();
|
||||
|
||||
AddAssert("filter count is 1", () => songSelect?.FilterCount == 1);
|
||||
AddAssert("filter count is 0", () => songSelect?.FilterCount, () => Is.EqualTo(0));
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -362,7 +366,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
|
||||
AddStep("return", () => songSelect!.MakeCurrent());
|
||||
AddUntilStep("wait for current", () => songSelect!.IsCurrentScreen());
|
||||
AddAssert("filter count is 1", () => songSelect!.FilterCount == 1);
|
||||
AddAssert("filter count is 0", () => songSelect!.FilterCount, () => Is.EqualTo(0));
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -382,7 +386,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
|
||||
AddStep("return", () => songSelect!.MakeCurrent());
|
||||
AddUntilStep("wait for current", () => songSelect!.IsCurrentScreen());
|
||||
AddAssert("filter count is 2", () => songSelect!.FilterCount == 2);
|
||||
AddAssert("filter count is 1", () => songSelect!.FilterCount, () => Is.EqualTo(1));
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -1270,11 +1274,11 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
|
||||
// Mod that is guaranteed to never re-filter.
|
||||
AddStep("add non-filterable mod", () => SelectedMods.Value = new Mod[] { new OsuModCinema() });
|
||||
AddAssert("filter count is 1", () => songSelect!.FilterCount, () => Is.EqualTo(1));
|
||||
AddAssert("filter count is 0", () => songSelect!.FilterCount, () => Is.EqualTo(0));
|
||||
|
||||
// Removing the mod should still not re-filter.
|
||||
AddStep("remove non-filterable mod", () => SelectedMods.Value = Array.Empty<Mod>());
|
||||
AddAssert("filter count is 1", () => songSelect!.FilterCount, () => Is.EqualTo(1));
|
||||
AddAssert("filter count is 0", () => songSelect!.FilterCount, () => Is.EqualTo(0));
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -1286,35 +1290,35 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
|
||||
// Change to mania ruleset.
|
||||
AddStep("filter to mania ruleset", () => Ruleset.Value = rulesets.AvailableRulesets.First(r => r.OnlineID == 3));
|
||||
AddAssert("filter count is 2", () => songSelect!.FilterCount, () => Is.EqualTo(2));
|
||||
AddAssert("filter count is 2", () => songSelect!.FilterCount, () => Is.EqualTo(1));
|
||||
|
||||
// Apply a mod, but this should NOT re-filter because there's no search text.
|
||||
AddStep("add filterable mod", () => SelectedMods.Value = new Mod[] { new ManiaModKey3() });
|
||||
AddAssert("filter count is 2", () => songSelect!.FilterCount, () => Is.EqualTo(2));
|
||||
AddAssert("filter count is 1", () => songSelect!.FilterCount, () => Is.EqualTo(1));
|
||||
|
||||
// Set search text. Should re-filter.
|
||||
AddStep("set search text to match mods", () => songSelect!.FilterControl.CurrentTextSearch.Value = "keys=3");
|
||||
AddAssert("filter count is 3", () => songSelect!.FilterCount, () => Is.EqualTo(3));
|
||||
AddAssert("filter count is 2", () => songSelect!.FilterCount, () => Is.EqualTo(2));
|
||||
|
||||
// Change filterable mod. Should re-filter.
|
||||
AddStep("change new filterable mod", () => SelectedMods.Value = new Mod[] { new ManiaModKey5() });
|
||||
AddAssert("filter count is 4", () => songSelect!.FilterCount, () => Is.EqualTo(4));
|
||||
AddAssert("filter count is 3", () => songSelect!.FilterCount, () => Is.EqualTo(3));
|
||||
|
||||
// Add non-filterable mod. Should NOT re-filter.
|
||||
AddStep("apply non-filterable mod", () => SelectedMods.Value = new Mod[] { new ManiaModNoFail(), new ManiaModKey5() });
|
||||
AddAssert("filter count is 4", () => songSelect!.FilterCount, () => Is.EqualTo(4));
|
||||
AddAssert("filter count is 3", () => songSelect!.FilterCount, () => Is.EqualTo(3));
|
||||
|
||||
// Remove filterable mod. Should re-filter.
|
||||
AddStep("remove filterable mod", () => SelectedMods.Value = new Mod[] { new ManiaModNoFail() });
|
||||
AddAssert("filter count is 5", () => songSelect!.FilterCount, () => Is.EqualTo(5));
|
||||
AddAssert("filter count is 4", () => songSelect!.FilterCount, () => Is.EqualTo(4));
|
||||
|
||||
// Remove non-filterable mod. Should NOT re-filter.
|
||||
AddStep("remove filterable mod", () => SelectedMods.Value = Array.Empty<Mod>());
|
||||
AddAssert("filter count is 5", () => songSelect!.FilterCount, () => Is.EqualTo(5));
|
||||
AddAssert("filter count is 4", () => songSelect!.FilterCount, () => Is.EqualTo(4));
|
||||
|
||||
// Add filterable mod. Should re-filter.
|
||||
AddStep("add filterable mod", () => SelectedMods.Value = new Mod[] { new ManiaModKey3() });
|
||||
AddAssert("filter count is 6", () => songSelect!.FilterCount, () => Is.EqualTo(6));
|
||||
AddAssert("filter count is 5", () => songSelect!.FilterCount, () => Is.EqualTo(5));
|
||||
}
|
||||
|
||||
private void waitForInitialSelection()
|
||||
@ -1397,8 +1401,6 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
{
|
||||
public Action? StartRequested;
|
||||
|
||||
public new Bindable<RulesetInfo> Ruleset => base.Ruleset;
|
||||
|
||||
public new FilterControl FilterControl => base.FilterControl;
|
||||
|
||||
public WorkingBeatmap CurrentBeatmap => Beatmap.Value;
|
||||
@ -1408,18 +1410,18 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
|
||||
public new void PresentScore(ScoreInfo score) => base.PresentScore(score);
|
||||
|
||||
public int FilterCount;
|
||||
|
||||
protected override bool OnStart()
|
||||
{
|
||||
StartRequested?.Invoke();
|
||||
return base.OnStart();
|
||||
}
|
||||
|
||||
public int FilterCount;
|
||||
|
||||
protected override void ApplyFilterToCarousel(FilterCriteria criteria)
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
FilterCount++;
|
||||
base.ApplyFilterToCarousel(criteria);
|
||||
FilterControl.FilterChanged += _ => FilterCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -246,7 +246,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
|
||||
private BeatmapCarousel createCarousel()
|
||||
{
|
||||
return carousel = new BeatmapCarousel
|
||||
return carousel = new BeatmapCarousel(new FilterCriteria())
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
BeatmapSets = new List<BeatmapSetInfo>
|
||||
|
163
osu.Game/Database/DetachedBeatmapStore.cs
Normal file
163
osu.Game/Database/DetachedBeatmapStore.cs
Normal file
@ -0,0 +1,163 @@
|
||||
// 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;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Online.Multiplayer;
|
||||
using Realms;
|
||||
|
||||
namespace osu.Game.Database
|
||||
{
|
||||
public partial class DetachedBeatmapStore : Component
|
||||
{
|
||||
private readonly ManualResetEventSlim loaded = new ManualResetEventSlim();
|
||||
|
||||
private readonly BindableList<BeatmapSetInfo> detachedBeatmapSets = new BindableList<BeatmapSetInfo>();
|
||||
|
||||
private IDisposable? realmSubscription;
|
||||
|
||||
private readonly Queue<OperationArgs> pendingOperations = new Queue<OperationArgs>();
|
||||
|
||||
[Resolved]
|
||||
private RealmAccess realm { get; set; } = null!;
|
||||
|
||||
public IBindableList<BeatmapSetInfo> GetDetachedBeatmaps(CancellationToken? cancellationToken)
|
||||
{
|
||||
loaded.Wait(cancellationToken ?? CancellationToken.None);
|
||||
return detachedBeatmapSets.GetBoundCopy();
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
realmSubscription = realm.RegisterForNotifications(r => r.All<BeatmapSetInfo>().Where(s => !s.DeletePending && !s.Protected), beatmapSetsChanged);
|
||||
}
|
||||
|
||||
private void beatmapSetsChanged(IRealmCollection<BeatmapSetInfo> sender, ChangeSet? changes)
|
||||
{
|
||||
if (changes == null)
|
||||
{
|
||||
if (detachedBeatmapSets.Count > 0 && sender.Count == 0)
|
||||
{
|
||||
// Usually we'd reset stuff here, but doing so triggers a silly flow which ends up deadlocking realm.
|
||||
// Additionally, user should not be at song select when realm is blocking all operations in the first place.
|
||||
//
|
||||
// Note that due to the catch-up logic below, once operations are restored we will still be in a roughly
|
||||
// correct state. The only things that this return will change is the carousel will not empty *during* the blocking
|
||||
// operation.
|
||||
return;
|
||||
}
|
||||
|
||||
// Detaching beatmaps takes some time, so let's make sure it doesn't run on the update thread.
|
||||
var frozenSets = sender.Freeze();
|
||||
|
||||
Task.Factory.StartNew(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
realm.Run(_ =>
|
||||
{
|
||||
var detached = frozenSets.Detach();
|
||||
|
||||
detachedBeatmapSets.Clear();
|
||||
detachedBeatmapSets.AddRange(detached);
|
||||
});
|
||||
}
|
||||
finally
|
||||
{
|
||||
loaded.Set();
|
||||
}
|
||||
}, TaskCreationOptions.LongRunning).FireAndForget();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (int i in changes.DeletedIndices.OrderDescending())
|
||||
{
|
||||
pendingOperations.Enqueue(new OperationArgs
|
||||
{
|
||||
Type = OperationType.Remove,
|
||||
Index = i,
|
||||
});
|
||||
}
|
||||
|
||||
foreach (int i in changes.InsertedIndices)
|
||||
{
|
||||
pendingOperations.Enqueue(new OperationArgs
|
||||
{
|
||||
Type = OperationType.Insert,
|
||||
BeatmapSet = sender[i].Detach(),
|
||||
Index = i,
|
||||
});
|
||||
}
|
||||
|
||||
foreach (int i in changes.NewModifiedIndices)
|
||||
{
|
||||
pendingOperations.Enqueue(new OperationArgs
|
||||
{
|
||||
Type = OperationType.Update,
|
||||
BeatmapSet = sender[i].Detach(),
|
||||
Index = i,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
// We can't start processing operations until we have finished detaching the initial list.
|
||||
if (!loaded.IsSet)
|
||||
return;
|
||||
|
||||
// If this ever leads to performance issues, we could dequeue a limited number of operations per update frame.
|
||||
while (pendingOperations.TryDequeue(out var op))
|
||||
{
|
||||
switch (op.Type)
|
||||
{
|
||||
case OperationType.Insert:
|
||||
detachedBeatmapSets.Insert(op.Index, op.BeatmapSet!);
|
||||
break;
|
||||
|
||||
case OperationType.Update:
|
||||
detachedBeatmapSets.ReplaceRange(op.Index, 1, new[] { op.BeatmapSet! });
|
||||
break;
|
||||
|
||||
case OperationType.Remove:
|
||||
detachedBeatmapSets.RemoveAt(op.Index);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
|
||||
loaded.Set();
|
||||
loaded.Dispose();
|
||||
realmSubscription?.Dispose();
|
||||
}
|
||||
|
||||
private record OperationArgs
|
||||
{
|
||||
public OperationType Type;
|
||||
public BeatmapSetInfo? BeatmapSet;
|
||||
public int Index;
|
||||
}
|
||||
|
||||
private enum OperationType
|
||||
{
|
||||
Insert,
|
||||
Update,
|
||||
Remove
|
||||
}
|
||||
}
|
||||
}
|
@ -1141,6 +1141,7 @@ namespace osu.Game
|
||||
loadComponentSingleFile(new MedalOverlay(), topMostOverlayContent.Add);
|
||||
|
||||
loadComponentSingleFile(new BackgroundDataStoreProcessor(), Add);
|
||||
loadComponentSingleFile(new DetachedBeatmapStore(), Add, true);
|
||||
|
||||
Add(difficultyRecommender);
|
||||
Add(externalLinkOpener = new ExternalLinkOpener());
|
||||
|
@ -54,8 +54,6 @@ namespace osu.Game.Screens.Menu
|
||||
|
||||
public override bool? AllowGlobalTrackControl => true;
|
||||
|
||||
private Screen songSelect;
|
||||
|
||||
private MenuSideFlashes sideFlashes;
|
||||
|
||||
protected ButtonSystem Buttons;
|
||||
@ -220,26 +218,11 @@ namespace osu.Game.Screens.Menu
|
||||
Buttons.OnBeatmapListing = () => beatmapListing?.ToggleVisibility();
|
||||
|
||||
reappearSampleSwoosh = audio.Samples.Get(@"Menu/reappear-swoosh");
|
||||
|
||||
preloadSongSelect();
|
||||
}
|
||||
|
||||
public void ReturnToOsuLogo() => Buttons.State = ButtonSystemState.Initial;
|
||||
|
||||
private void preloadSongSelect()
|
||||
{
|
||||
if (songSelect == null)
|
||||
LoadComponentAsync(songSelect = new PlaySongSelect());
|
||||
}
|
||||
|
||||
private void loadSoloSongSelect() => this.Push(consumeSongSelect());
|
||||
|
||||
private Screen consumeSongSelect()
|
||||
{
|
||||
var s = songSelect;
|
||||
songSelect = null;
|
||||
return s;
|
||||
}
|
||||
private void loadSoloSongSelect() => this.Push(new PlaySongSelect());
|
||||
|
||||
public override void OnEntering(ScreenTransitionEvent e)
|
||||
{
|
||||
@ -373,9 +356,6 @@ namespace osu.Game.Screens.Menu
|
||||
|
||||
ApplyToBackground(b => (b as BackgroundScreenDefault)?.Next());
|
||||
|
||||
// we may have consumed our preloaded instance, so let's make another.
|
||||
preloadSongSelect();
|
||||
|
||||
musicController.EnsurePlayingSomething();
|
||||
|
||||
// Cycle tip on resuming
|
||||
|
@ -3,8 +3,10 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Audio.Sample;
|
||||
@ -21,6 +23,7 @@ using osu.Framework.Utils;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Extensions;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Input.Bindings;
|
||||
using osu.Game.Screens.Select.Carousel;
|
||||
@ -76,8 +79,6 @@ namespace osu.Game.Screens.Select
|
||||
|
||||
private CarouselBeatmapSet? selectedBeatmapSet;
|
||||
|
||||
private List<BeatmapSetInfo> originalBeatmapSetsDetached = new List<BeatmapSetInfo>();
|
||||
|
||||
/// <summary>
|
||||
/// Raised when the <see cref="SelectedBeatmapInfo"/> is changed.
|
||||
/// </summary>
|
||||
@ -109,28 +110,38 @@ namespace osu.Game.Screens.Select
|
||||
[Cached]
|
||||
protected readonly CarouselScrollContainer Scroll;
|
||||
|
||||
[Resolved]
|
||||
private RealmAccess realm { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private DetachedBeatmapStore? detachedBeatmapStore { get; set; }
|
||||
|
||||
private IBindableList<BeatmapSetInfo>? detachedBeatmapSets;
|
||||
|
||||
private readonly NoResultsPlaceholder noResultsPlaceholder;
|
||||
|
||||
private IEnumerable<CarouselBeatmapSet> beatmapSets => root.Items.OfType<CarouselBeatmapSet>();
|
||||
|
||||
// todo: only used for testing, maybe remove.
|
||||
private bool loadedTestBeatmaps;
|
||||
|
||||
public IEnumerable<BeatmapSetInfo> BeatmapSets
|
||||
internal IEnumerable<BeatmapSetInfo> BeatmapSets
|
||||
{
|
||||
get => beatmapSets.Select(g => g.BeatmapSet);
|
||||
set
|
||||
{
|
||||
loadedTestBeatmaps = true;
|
||||
Schedule(() => loadBeatmapSets(value));
|
||||
if (LoadState != LoadState.NotLoaded)
|
||||
throw new InvalidOperationException("If not using a realm source, beatmap sets must be set before load.");
|
||||
|
||||
detachedBeatmapSets = new BindableList<BeatmapSetInfo>(value);
|
||||
Schedule(loadNewRoot);
|
||||
}
|
||||
}
|
||||
|
||||
private void loadBeatmapSets(IEnumerable<BeatmapSetInfo> beatmapSets)
|
||||
private void loadNewRoot()
|
||||
{
|
||||
originalBeatmapSetsDetached = beatmapSets.Detach();
|
||||
// Ensure no changes are made to the list while we are initialising items.
|
||||
// We'll catch up on changes via subscriptions anyway.
|
||||
BeatmapSetInfo[] loadableSets = detachedBeatmapSets!.ToArray();
|
||||
|
||||
if (selectedBeatmapSet != null && !originalBeatmapSetsDetached.Contains(selectedBeatmapSet.BeatmapSet))
|
||||
if (selectedBeatmapSet != null && !loadableSets.Contains(selectedBeatmapSet.BeatmapSet))
|
||||
selectedBeatmapSet = null;
|
||||
|
||||
var selectedBeatmapBefore = selectedBeatmap?.BeatmapInfo;
|
||||
@ -139,7 +150,7 @@ namespace osu.Game.Screens.Select
|
||||
|
||||
if (beatmapsSplitOut)
|
||||
{
|
||||
var carouselBeatmapSets = originalBeatmapSetsDetached.SelectMany(s => s.Beatmaps).Select(b =>
|
||||
var carouselBeatmapSets = loadableSets.SelectMany(s => s.Beatmaps).Select(b =>
|
||||
{
|
||||
return createCarouselSet(new BeatmapSetInfo(new[] { b })
|
||||
{
|
||||
@ -153,25 +164,18 @@ namespace osu.Game.Screens.Select
|
||||
}
|
||||
else
|
||||
{
|
||||
var carouselBeatmapSets = originalBeatmapSetsDetached.Select(createCarouselSet).OfType<CarouselBeatmapSet>();
|
||||
var carouselBeatmapSets = loadableSets.Select(createCarouselSet).OfType<CarouselBeatmapSet>();
|
||||
|
||||
newRoot.AddItems(carouselBeatmapSets);
|
||||
}
|
||||
|
||||
root = newRoot;
|
||||
root.Filter(activeCriteria);
|
||||
|
||||
Scroll.Clear(false);
|
||||
itemsCache.Invalidate();
|
||||
ScrollToSelected();
|
||||
|
||||
applyActiveCriteria(false);
|
||||
|
||||
if (loadedTestBeatmaps)
|
||||
{
|
||||
invalidateAfterChange();
|
||||
BeatmapSetsLoaded = true;
|
||||
}
|
||||
|
||||
// Restore selection
|
||||
if (selectedBeatmapBefore != null && newRoot.BeatmapSetsByID.TryGetValue(selectedBeatmapBefore.BeatmapSet!.ID, out var newSelectionCandidates))
|
||||
{
|
||||
@ -180,6 +184,12 @@ namespace osu.Game.Screens.Select
|
||||
if (found != null)
|
||||
found.State.Value = CarouselItemState.Selected;
|
||||
}
|
||||
|
||||
Schedule(() =>
|
||||
{
|
||||
invalidateAfterChange();
|
||||
BeatmapSetsLoaded = true;
|
||||
});
|
||||
}
|
||||
|
||||
private readonly List<CarouselItem> visibleItems = new List<CarouselItem>();
|
||||
@ -195,7 +205,6 @@ namespace osu.Game.Screens.Select
|
||||
|
||||
private CarouselRoot root;
|
||||
|
||||
private IDisposable? subscriptionSets;
|
||||
private IDisposable? subscriptionBeatmaps;
|
||||
|
||||
private readonly DrawablePool<DrawableCarouselBeatmapSet> setPool = new DrawablePool<DrawableCarouselBeatmapSet>(100);
|
||||
@ -205,7 +214,7 @@ namespace osu.Game.Screens.Select
|
||||
|
||||
private int visibleSetsCount;
|
||||
|
||||
public BeatmapCarousel()
|
||||
public BeatmapCarousel(FilterCriteria initialCriterial)
|
||||
{
|
||||
root = new CarouselRoot(this);
|
||||
InternalChild = new Container
|
||||
@ -227,10 +236,12 @@ namespace osu.Game.Screens.Select
|
||||
noResultsPlaceholder = new NoResultsPlaceholder()
|
||||
}
|
||||
};
|
||||
|
||||
activeCriteria = initialCriterial;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuConfigManager config, AudioManager audio)
|
||||
private void load(OsuConfigManager config, AudioManager audio, CancellationToken? cancellationToken)
|
||||
{
|
||||
spinSample = audio.Samples.Get("SongSelect/random-spin");
|
||||
randomSelectSample = audio.Samples.Get(@"SongSelect/select-random");
|
||||
@ -241,97 +252,61 @@ namespace osu.Game.Screens.Select
|
||||
RightClickScrollingEnabled.ValueChanged += enabled => Scroll.RightMouseScrollbar = enabled.NewValue;
|
||||
RightClickScrollingEnabled.TriggerChange();
|
||||
|
||||
if (!loadedTestBeatmaps)
|
||||
if (detachedBeatmapStore != null && detachedBeatmapSets == null)
|
||||
{
|
||||
// This is performing an unnecessary second lookup on realm (in addition to the subscription), but for performance reasons
|
||||
// we require it to be separate: the subscription's initial callback (with `ChangeSet` of `null`) will run on the update
|
||||
// thread. If we attempt to detach beatmaps in this callback the game will fall over (it takes time).
|
||||
realm.Run(r => loadBeatmapSets(getBeatmapSets(r)));
|
||||
detachedBeatmapSets = detachedBeatmapStore.GetDetachedBeatmaps(cancellationToken);
|
||||
detachedBeatmapSets.BindCollectionChanged(beatmapSetsChanged);
|
||||
loadNewRoot();
|
||||
}
|
||||
}
|
||||
|
||||
[Resolved]
|
||||
private RealmAccess realm { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Track GUIDs of all sets in realm to allow handling deletions.
|
||||
/// </summary>
|
||||
private readonly List<Guid> realmBeatmapSets = new List<Guid>();
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
subscriptionSets = realm.RegisterForNotifications(getBeatmapSets, beatmapSetsChanged);
|
||||
subscriptionBeatmaps = realm.RegisterForNotifications(r => r.All<BeatmapInfo>().Where(b => !b.Hidden), beatmapsChanged);
|
||||
}
|
||||
|
||||
private readonly HashSet<Guid> setsRequiringUpdate = new HashSet<Guid>();
|
||||
private readonly HashSet<Guid> setsRequiringRemoval = new HashSet<Guid>();
|
||||
private readonly HashSet<BeatmapSetInfo> setsRequiringUpdate = new HashSet<BeatmapSetInfo>();
|
||||
private readonly HashSet<BeatmapSetInfo> setsRequiringRemoval = new HashSet<BeatmapSetInfo>();
|
||||
|
||||
private void beatmapSetsChanged(IRealmCollection<BeatmapSetInfo> sender, ChangeSet? changes)
|
||||
private void beatmapSetsChanged(object? beatmaps, NotifyCollectionChangedEventArgs changed)
|
||||
{
|
||||
// If loading test beatmaps, avoid overwriting with realm subscription callbacks.
|
||||
if (loadedTestBeatmaps)
|
||||
return;
|
||||
IEnumerable<BeatmapSetInfo>? newBeatmapSets = changed.NewItems?.Cast<BeatmapSetInfo>();
|
||||
|
||||
if (changes == null)
|
||||
switch (changed.Action)
|
||||
{
|
||||
realmBeatmapSets.Clear();
|
||||
realmBeatmapSets.AddRange(sender.Select(r => r.ID));
|
||||
case NotifyCollectionChangedAction.Add:
|
||||
HashSet<Guid> newBeatmapSetIDs = newBeatmapSets!.Select(s => s.ID).ToHashSet();
|
||||
|
||||
if (originalBeatmapSetsDetached.Count > 0 && sender.Count == 0)
|
||||
{
|
||||
// Usually we'd reset stuff here, but doing so triggers a silly flow which ends up deadlocking realm.
|
||||
// Additionally, user should not be at song select when realm is blocking all operations in the first place.
|
||||
//
|
||||
// Note that due to the catch-up logic below, once operations are restored we will still be in a roughly
|
||||
// correct state. The only things that this return will change is the carousel will not empty *during* the blocking
|
||||
// operation.
|
||||
return;
|
||||
}
|
||||
setsRequiringRemoval.RemoveWhere(s => newBeatmapSetIDs.Contains(s.ID));
|
||||
setsRequiringUpdate.AddRange(newBeatmapSets!);
|
||||
break;
|
||||
|
||||
// Do a full two-way check for missing (or incorrectly present) beatmaps.
|
||||
// Let's assume that the worst that can happen is deletions or additions.
|
||||
case NotifyCollectionChangedAction.Remove:
|
||||
IEnumerable<BeatmapSetInfo> oldBeatmapSets = changed.OldItems!.Cast<BeatmapSetInfo>();
|
||||
HashSet<Guid> oldBeatmapSetIDs = oldBeatmapSets.Select(s => s.ID).ToHashSet();
|
||||
|
||||
setsRequiringUpdate.RemoveWhere(s => oldBeatmapSetIDs.Contains(s.ID));
|
||||
setsRequiringRemoval.AddRange(oldBeatmapSets);
|
||||
break;
|
||||
|
||||
case NotifyCollectionChangedAction.Replace:
|
||||
setsRequiringUpdate.AddRange(newBeatmapSets!);
|
||||
break;
|
||||
|
||||
case NotifyCollectionChangedAction.Move:
|
||||
setsRequiringUpdate.AddRange(newBeatmapSets!);
|
||||
break;
|
||||
|
||||
case NotifyCollectionChangedAction.Reset:
|
||||
setsRequiringRemoval.Clear();
|
||||
setsRequiringUpdate.Clear();
|
||||
|
||||
foreach (Guid id in realmBeatmapSets)
|
||||
{
|
||||
if (!root.BeatmapSetsByID.ContainsKey(id))
|
||||
setsRequiringUpdate.Add(id);
|
||||
}
|
||||
|
||||
foreach (Guid id in root.BeatmapSetsByID.Keys)
|
||||
{
|
||||
if (!realmBeatmapSets.Contains(id))
|
||||
setsRequiringRemoval.Add(id);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (int i in changes.DeletedIndices.OrderDescending())
|
||||
{
|
||||
Guid id = realmBeatmapSets[i];
|
||||
|
||||
setsRequiringRemoval.Add(id);
|
||||
setsRequiringUpdate.Remove(id);
|
||||
|
||||
realmBeatmapSets.RemoveAt(i);
|
||||
}
|
||||
|
||||
foreach (int i in changes.InsertedIndices)
|
||||
{
|
||||
Guid id = sender[i].ID;
|
||||
|
||||
setsRequiringRemoval.Remove(id);
|
||||
setsRequiringUpdate.Add(id);
|
||||
|
||||
realmBeatmapSets.Insert(i, id);
|
||||
}
|
||||
|
||||
foreach (int i in changes.NewModifiedIndices)
|
||||
setsRequiringUpdate.Add(sender[i].ID);
|
||||
loadNewRoot();
|
||||
break;
|
||||
}
|
||||
|
||||
Scheduler.AddOnce(processBeatmapChanges);
|
||||
@ -345,9 +320,9 @@ namespace osu.Game.Screens.Select
|
||||
{
|
||||
try
|
||||
{
|
||||
foreach (var set in setsRequiringRemoval) removeBeatmapSet(set);
|
||||
foreach (var set in setsRequiringRemoval) removeBeatmapSet(set.ID);
|
||||
|
||||
foreach (var set in setsRequiringUpdate) updateBeatmapSet(fetchFromID(set)!);
|
||||
foreach (var set in setsRequiringUpdate) updateBeatmapSet(set);
|
||||
|
||||
if (setsRequiringRemoval.Count > 0 && SelectedBeatmapInfo != null)
|
||||
{
|
||||
@ -365,7 +340,7 @@ namespace osu.Game.Screens.Select
|
||||
// This relies on the full update operation being in a single transaction, so please don't change that.
|
||||
foreach (var set in setsRequiringUpdate)
|
||||
{
|
||||
foreach (var beatmapInfo in fetchFromID(set)!.Beatmaps)
|
||||
foreach (var beatmapInfo in set.Beatmaps)
|
||||
{
|
||||
if (!((IBeatmapMetadataInfo)beatmapInfo.Metadata).Equals(SelectedBeatmapInfo.Metadata)) continue;
|
||||
|
||||
@ -380,7 +355,7 @@ namespace osu.Game.Screens.Select
|
||||
|
||||
// If a direct selection couldn't be made, it's feasible that the difficulty name (or beatmap metadata) changed.
|
||||
// Let's attempt to follow set-level selection anyway.
|
||||
SelectBeatmap(fetchFromID(setsRequiringUpdate.First())!.Beatmaps.First());
|
||||
SelectBeatmap(setsRequiringUpdate.First().Beatmaps.First());
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -425,8 +400,6 @@ namespace osu.Game.Screens.Select
|
||||
invalidateAfterChange();
|
||||
}
|
||||
|
||||
private IQueryable<BeatmapSetInfo> getBeatmapSets(Realm realm) => realm.All<BeatmapSetInfo>().Where(s => !s.DeletePending && !s.Protected);
|
||||
|
||||
public void RemoveBeatmapSet(BeatmapSetInfo beatmapSet) => Schedule(() =>
|
||||
{
|
||||
removeBeatmapSet(beatmapSet.ID);
|
||||
@ -438,8 +411,6 @@ namespace osu.Game.Screens.Select
|
||||
if (!root.BeatmapSetsByID.TryGetValue(beatmapSetID, out var existingSets))
|
||||
return;
|
||||
|
||||
originalBeatmapSetsDetached.RemoveAll(set => set.ID == beatmapSetID);
|
||||
|
||||
foreach (var set in existingSets)
|
||||
{
|
||||
foreach (var beatmap in set.Beatmaps)
|
||||
@ -450,24 +421,14 @@ namespace osu.Game.Screens.Select
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateBeatmapSet(BeatmapSetInfo beatmapSet)
|
||||
{
|
||||
beatmapSet = beatmapSet.Detach();
|
||||
|
||||
Schedule(() =>
|
||||
public void UpdateBeatmapSet(BeatmapSetInfo beatmapSet) => Schedule(() =>
|
||||
{
|
||||
updateBeatmapSet(beatmapSet);
|
||||
invalidateAfterChange();
|
||||
});
|
||||
}
|
||||
|
||||
private void updateBeatmapSet(BeatmapSetInfo beatmapSet)
|
||||
{
|
||||
beatmapSet = beatmapSet.Detach();
|
||||
|
||||
originalBeatmapSetsDetached.RemoveAll(set => set.ID == beatmapSet.ID);
|
||||
originalBeatmapSetsDetached.Add(beatmapSet);
|
||||
|
||||
var newSets = new List<CarouselBeatmapSet>();
|
||||
|
||||
if (beatmapsSplitOut)
|
||||
@ -696,7 +657,7 @@ namespace osu.Game.Screens.Select
|
||||
item.State.Value = CarouselItemState.Selected;
|
||||
}
|
||||
|
||||
private FilterCriteria activeCriteria = new FilterCriteria();
|
||||
private FilterCriteria activeCriteria;
|
||||
|
||||
protected ScheduledDelegate? PendingFilter;
|
||||
|
||||
@ -733,12 +694,12 @@ namespace osu.Game.Screens.Select
|
||||
}
|
||||
}
|
||||
|
||||
public void Filter(FilterCriteria? newCriteria, bool debounce = true)
|
||||
public void Filter(FilterCriteria? newCriteria)
|
||||
{
|
||||
if (newCriteria != null)
|
||||
activeCriteria = newCriteria;
|
||||
|
||||
applyActiveCriteria(debounce);
|
||||
applyActiveCriteria(true);
|
||||
}
|
||||
|
||||
private bool beatmapsSplitOut;
|
||||
@ -766,7 +727,7 @@ namespace osu.Game.Screens.Select
|
||||
if (activeCriteria.SplitOutDifficulties != beatmapsSplitOut)
|
||||
{
|
||||
beatmapsSplitOut = activeCriteria.SplitOutDifficulties;
|
||||
loadBeatmapSets(originalBeatmapSetsDetached);
|
||||
loadNewRoot();
|
||||
return;
|
||||
}
|
||||
|
||||
@ -1315,7 +1276,6 @@ namespace osu.Game.Screens.Select
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
|
||||
subscriptionSets?.Dispose();
|
||||
subscriptionBeatmaps?.Dispose();
|
||||
}
|
||||
}
|
||||
|
@ -162,20 +162,6 @@ namespace osu.Game.Screens.Select
|
||||
ApplyToBackground(applyBlurToBackground);
|
||||
});
|
||||
|
||||
LoadComponentAsync(Carousel = new BeatmapCarousel
|
||||
{
|
||||
AllowSelection = false, // delay any selection until our bindables are ready to make a good choice.
|
||||
Anchor = Anchor.CentreRight,
|
||||
Origin = Anchor.CentreRight,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
BleedTop = FilterControl.HEIGHT,
|
||||
BleedBottom = Select.Footer.HEIGHT,
|
||||
SelectionChanged = updateSelectedBeatmap,
|
||||
BeatmapSetsChanged = carouselBeatmapsLoaded,
|
||||
FilterApplied = () => Scheduler.AddOnce(updateVisibleBeatmapCount),
|
||||
GetRecommendedBeatmap = s => recommender?.GetRecommendedBeatmap(s),
|
||||
}, c => carouselContainer.Child = c);
|
||||
|
||||
// initial value transfer is required for FilterControl (it uses our re-cached bindables in its async load for the initial filter).
|
||||
transferRulesetValue();
|
||||
|
||||
@ -227,7 +213,6 @@ namespace osu.Game.Screens.Select
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = FilterControl.HEIGHT,
|
||||
FilterChanged = ApplyFilterToCarousel,
|
||||
},
|
||||
new GridContainer // used for max width implementation
|
||||
{
|
||||
@ -328,6 +313,23 @@ namespace osu.Game.Screens.Select
|
||||
modSpeedHotkeyHandler = new ModSpeedHotkeyHandler(),
|
||||
});
|
||||
|
||||
// Important to load this after the filter control is loaded (so we have initial filter criteria prepared).
|
||||
LoadComponentAsync(Carousel = new BeatmapCarousel(FilterControl.CreateCriteria())
|
||||
{
|
||||
AllowSelection = false, // delay any selection until our bindables are ready to make a good choice.
|
||||
Anchor = Anchor.CentreRight,
|
||||
Origin = Anchor.CentreRight,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
BleedTop = FilterControl.HEIGHT,
|
||||
BleedBottom = Select.Footer.HEIGHT,
|
||||
SelectionChanged = updateSelectedBeatmap,
|
||||
BeatmapSetsChanged = carouselBeatmapsLoaded,
|
||||
FilterApplied = () => Scheduler.AddOnce(updateVisibleBeatmapCount),
|
||||
GetRecommendedBeatmap = s => recommender?.GetRecommendedBeatmap(s),
|
||||
}, c => carouselContainer.Child = c);
|
||||
|
||||
FilterControl.FilterChanged = Carousel.Filter;
|
||||
|
||||
if (ShowSongSelectFooter)
|
||||
{
|
||||
AddRangeInternal(new Drawable[]
|
||||
@ -401,14 +403,6 @@ namespace osu.Game.Screens.Select
|
||||
|
||||
protected virtual ModSelectOverlay CreateModSelectOverlay() => new SoloModSelectOverlay();
|
||||
|
||||
protected virtual void ApplyFilterToCarousel(FilterCriteria criteria)
|
||||
{
|
||||
// if not the current screen, we want to get carousel in a good presentation state before displaying (resume or enter).
|
||||
bool shouldDebounce = this.IsCurrentScreen();
|
||||
|
||||
Carousel.Filter(criteria, shouldDebounce);
|
||||
}
|
||||
|
||||
private DependencyContainer dependencies = null!;
|
||||
|
||||
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
|
||||
@ -434,7 +428,8 @@ namespace osu.Game.Screens.Select
|
||||
|
||||
// Forced refetch is important here to guarantee correct invalidation across all difficulties.
|
||||
Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmapInfo ?? beatmapInfoNoDebounce, true);
|
||||
this.Push(new EditorLoader());
|
||||
|
||||
FinaliseSelection(customStartAction: () => this.Push(new EditorLoader()));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -992,6 +987,7 @@ namespace osu.Game.Screens.Select
|
||||
|
||||
// if we have a pending filter operation, we want to run it now.
|
||||
// it could change selection (ie. if the ruleset has been changed).
|
||||
if (IsLoaded)
|
||||
Carousel.FlushPendingFilterOperations();
|
||||
|
||||
return true;
|
||||
|
Loading…
Reference in New Issue
Block a user