1
0
mirror of https://github.com/ppy/osu.git synced 2025-01-18 11:52:54 +08:00

Merge pull request #31076 from peppy/beatmap-store-interface

Access beatmap store via abstract base class
This commit is contained in:
Bartłomiej Dach 2024-12-11 17:48:32 +09:00 committed by GitHub
commit 4e4a99decc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 126 additions and 79 deletions

View File

@ -49,17 +49,17 @@ namespace osu.Game.Tests.Visual.Background
[BackgroundDependencyLoader]
private void load(GameHost host, AudioManager audio)
{
DetachedBeatmapStore detachedBeatmapStore;
BeatmapStore beatmapStore;
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.CacheAs(beatmapStore = new RealmDetachedBeatmapStore());
Dependencies.Cache(Realm);
manager.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely();
Add(detachedBeatmapStore);
Add(beatmapStore);
Beatmap.SetDefault();
}

View File

@ -44,14 +44,14 @@ namespace osu.Game.Tests.Visual.Multiplayer
[BackgroundDependencyLoader]
private void load(GameHost host, AudioManager audio)
{
DetachedBeatmapStore detachedBeatmapStore;
BeatmapStore beatmapStore;
Dependencies.Cache(new RealmRulesetStore(Realm));
Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, null, audio, Resources, host, Beatmap.Default));
Dependencies.Cache(detachedBeatmapStore = new DetachedBeatmapStore());
Dependencies.CacheAs(beatmapStore = new RealmDetachedBeatmapStore());
Dependencies.Cache(Realm);
Add(detachedBeatmapStore);
Add(beatmapStore);
}
public override void SetUpSteps()

View File

@ -66,14 +66,14 @@ namespace osu.Game.Tests.Visual.Multiplayer
[BackgroundDependencyLoader]
private void load(GameHost host, AudioManager audio)
{
DetachedBeatmapStore detachedBeatmapStore;
BeatmapStore beatmapStore;
Dependencies.Cache(new RealmRulesetStore(Realm));
Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, API, audio, Resources, host, Beatmap.Default));
Dependencies.Cache(detachedBeatmapStore = new DetachedBeatmapStore());
Dependencies.CacheAs(beatmapStore = new RealmDetachedBeatmapStore());
Dependencies.Cache(Realm);
Add(detachedBeatmapStore);
Add(beatmapStore);
}
public override void SetUpSteps()

View File

@ -46,16 +46,16 @@ namespace osu.Game.Tests.Visual.Multiplayer
[BackgroundDependencyLoader]
private void load(GameHost host, AudioManager audio)
{
DetachedBeatmapStore detachedBeatmapStore;
BeatmapStore beatmapStore;
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.CacheAs(beatmapStore = new RealmDetachedBeatmapStore());
Dependencies.Cache(Realm);
importedBeatmapSet = manager.Import(TestResources.CreateTestBeatmapSetInfo(8, rulesets.AvailableRulesets.ToArray()))!;
Add(detachedBeatmapStore);
Add(beatmapStore);
}
private void setUp()

View File

@ -31,18 +31,18 @@ namespace osu.Game.Tests.Visual.Multiplayer
[BackgroundDependencyLoader]
private void load(GameHost host, AudioManager audio)
{
DetachedBeatmapStore detachedBeatmapStore;
BeatmapStore beatmapStore;
Dependencies.Cache(new RealmRulesetStore(Realm));
Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Realm, null, audio, Resources, host, Beatmap.Default));
Dependencies.Cache(detachedBeatmapStore = new DetachedBeatmapStore());
Dependencies.CacheAs(beatmapStore = new RealmDetachedBeatmapStore());
Dependencies.Cache(Realm);
var beatmapSet = TestResources.CreateTestBeatmapSetInfo();
manager.Import(beatmapSet);
Add(detachedBeatmapStore);
Add(beatmapStore);
}
public override void SetUpSteps()

View File

@ -16,6 +16,7 @@ using osu.Framework.Testing;
using osu.Framework.Utils;
using osu.Game.Beatmaps;
using osu.Game.Configuration;
using osu.Game.Database;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Catch;
using osu.Game.Rulesets.Osu;
@ -23,6 +24,7 @@ using osu.Game.Rulesets.Taiko;
using osu.Game.Screens.Select;
using osu.Game.Screens.Select.Carousel;
using osu.Game.Screens.Select.Filter;
using osu.Game.Tests.Beatmaps;
using osu.Game.Tests.Resources;
using osuTK.Input;
@ -42,6 +44,9 @@ namespace osu.Game.Tests.Visual.SongSelect
private const int set_count = 5;
private const int diff_count = 3;
[Cached(typeof(BeatmapStore))]
private TestBeatmapStore beatmaps = new TestBeatmapStore();
[BackgroundDependencyLoader]
private void load(RulesetStore rulesets)
{
@ -1329,7 +1334,8 @@ namespace osu.Game.Tests.Visual.SongSelect
carouselAdjust?.Invoke(carousel);
carousel.BeatmapSets = beatmapSets;
beatmaps.BeatmapSets.Clear();
beatmaps.BeatmapSets.AddRange(beatmapSets);
(target ?? this).Child = carousel;
});

View File

@ -56,20 +56,20 @@ namespace osu.Game.Tests.Visual.SongSelect
[BackgroundDependencyLoader]
private void load(GameHost host, AudioManager audio)
{
DetachedBeatmapStore detachedBeatmapStore;
BeatmapStore beatmapStore;
// 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.CacheAs(beatmapStore = new RealmDetachedBeatmapStore());
Dependencies.Cache(music = new MusicController());
// required to get bindables attached
Add(music);
Add(detachedBeatmapStore);
Add(beatmapStore);
Dependencies.Cache(config = new OsuConfigManager(LocalStorage));
}

View File

@ -2,7 +2,6 @@
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
@ -10,12 +9,14 @@ using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Graphics;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Database;
using osu.Game.Online.API;
using osu.Game.Overlays;
using osu.Game.Overlays.Dialog;
using osu.Game.Screens.Select;
using osu.Game.Screens.Select.Carousel;
using osu.Game.Screens.Select.Filter;
using osu.Game.Tests.Beatmaps;
using osu.Game.Tests.Online;
using osu.Game.Tests.Resources;
using osuTK.Input;
@ -31,6 +32,9 @@ namespace osu.Game.Tests.Visual.SongSelect
private BeatmapSetInfo testBeatmapSetInfo = null!;
[Cached(typeof(BeatmapStore))]
private TestBeatmapStore beatmaps = new TestBeatmapStore();
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
{
var dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
@ -246,13 +250,12 @@ namespace osu.Game.Tests.Visual.SongSelect
private BeatmapCarousel createCarousel()
{
beatmaps.BeatmapSets.Clear();
beatmaps.BeatmapSets.Add(testBeatmapSetInfo = TestResources.CreateTestBeatmapSetInfo(5));
return carousel = new BeatmapCarousel(new FilterCriteria())
{
RelativeSizeAxes = Axes.Both,
BeatmapSets = new List<BeatmapSetInfo>
{
(testBeatmapSetInfo = TestResources.CreateTestBeatmapSetInfo(5)),
}
};
}
}

View File

@ -3,8 +3,10 @@
using osu.Framework.Allocation;
using osu.Framework.Screens;
using osu.Game.Database;
using osu.Game.Overlays;
using osu.Game.Overlays.FirstRunSetup;
using osu.Game.Tests.Beatmaps;
namespace osu.Game.Tests.Visual.UserInterface
{
@ -13,6 +15,9 @@ namespace osu.Game.Tests.Visual.UserInterface
[Cached]
private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple);
[Cached(typeof(BeatmapStore))]
private BeatmapStore beatmapStore = new TestBeatmapStore();
public TestSceneFirstRunScreenUIScale()
{
AddStep("load screen", () =>

View File

@ -17,12 +17,14 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Screens;
using osu.Framework.Testing;
using osu.Game.Configuration;
using osu.Game.Database;
using osu.Game.Localisation;
using osu.Game.Overlays;
using osu.Game.Overlays.FirstRunSetup;
using osu.Game.Overlays.Notifications;
using osu.Game.Screens;
using osu.Game.Screens.Footer;
using osu.Game.Tests.Beatmaps;
using osuTK;
using osuTK.Input;
@ -47,6 +49,7 @@ namespace osu.Game.Tests.Visual.UserInterface
Dependencies.Cache(LocalConfig = new OsuConfigManager(LocalStorage));
Dependencies.CacheAs<IPerformFromScreenRunner>(performer.Object);
Dependencies.CacheAs<INotificationOverlay>(notificationOverlay.Object);
Dependencies.CacheAs<BeatmapStore>(new TestBeatmapStore());
}
[SetUpSteps]

View File

@ -0,0 +1,35 @@
// 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.Threading;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Game.Beatmaps;
namespace osu.Game.Database
{
/// <summary>
/// A store which contains a thread-safe representation of beatmaps available game-wide.
/// This exposes changes to available beatmaps, such as post-import or deletion.
/// </summary>
/// <remarks>
/// The main goal of classes which implement this interface should be to provide change
/// tracking and thread safety in a performant way, rather than having to worry about such
/// concerns at the point of usage.
/// </remarks>
public abstract partial class BeatmapStore : Component
{
/// <summary>
/// Get all available beatmaps.
/// </summary>
/// <param name="cancellationToken">A cancellation token which allows early abort from the operation.</param>
/// <returns>A bindable list of all available beatmap sets.</returns>
/// <remarks>
/// This operation may block during the initial load process.
///
/// It is generally expected that once a beatmap store is in a good state, the overhead of this call
/// should be negligible.
/// </remarks>
public abstract IBindableList<BeatmapSetInfo> GetBeatmapSets(CancellationToken? cancellationToken);
}
}

View File

@ -8,14 +8,13 @@ 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
public partial class RealmDetachedBeatmapStore : BeatmapStore
{
private readonly ManualResetEventSlim loaded = new ManualResetEventSlim();
@ -28,7 +27,7 @@ namespace osu.Game.Database
[Resolved]
private RealmAccess realm { get; set; } = null!;
public IBindableList<BeatmapSetInfo> GetDetachedBeatmaps(CancellationToken? cancellationToken)
public override IBindableList<BeatmapSetInfo> GetBeatmapSets(CancellationToken? cancellationToken)
{
loaded.Wait(cancellationToken ?? CancellationToken.None);
return detachedBeatmapSets.GetBoundCopy();

View File

@ -1143,7 +1143,7 @@ namespace osu.Game
loadComponentSingleFile(new MedalOverlay(), topMostOverlayContent.Add);
loadComponentSingleFile(new BackgroundDataStoreProcessor(), Add);
loadComponentSingleFile(new DetachedBeatmapStore(), Add, true);
loadComponentSingleFile<BeatmapStore>(new RealmDetachedBeatmapStore(), Add, true);
Add(externalLinkOpener = new ExternalLinkOpener());
Add(new MusicKeyBindingHandler());

View File

@ -112,27 +112,13 @@ namespace osu.Game.Screens.Select
[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>();
internal IEnumerable<BeatmapSetInfo> BeatmapSets
{
get => beatmapSets.Select(g => g.BeatmapSet);
set
{
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);
}
}
internal IEnumerable<BeatmapSetInfo> BeatmapSets => beatmapSets.Select(g => g.BeatmapSet);
private void loadNewRoot()
{
@ -234,7 +220,7 @@ namespace osu.Game.Screens.Select
}
[BackgroundDependencyLoader]
private void load(OsuConfigManager config, AudioManager audio, CancellationToken? cancellationToken)
private void load(OsuConfigManager config, AudioManager audio, BeatmapStore beatmaps, CancellationToken? cancellationToken)
{
spinSample = audio.Samples.Get("SongSelect/random-spin");
randomSelectSample = audio.Samples.Get(@"SongSelect/select-random");
@ -244,16 +230,10 @@ namespace osu.Game.Screens.Select
RightClickScrollingEnabled.BindValueChanged(enabled => Scroll.RightMouseScrollbar = enabled.NewValue, true);
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).
detachedBeatmapSets = detachedBeatmapStore.GetDetachedBeatmaps(cancellationToken);
detachedBeatmapSets = beatmaps.GetBeatmapSets(cancellationToken);
detachedBeatmapSets.BindCollectionChanged(beatmapSetsChanged);
loadNewRoot();
}
}
private readonly HashSet<BeatmapSetInfo> setsRequiringUpdate = new HashSet<BeatmapSetInfo>();
private readonly HashSet<BeatmapSetInfo> setsRequiringRemoval = new HashSet<BeatmapSetInfo>();

View File

@ -10,8 +10,31 @@ namespace osu.Game.Screens.Select.Carousel
/// <summary>
/// A group which ensures only one item is selected.
/// </summary>
public class CarouselGroup : CarouselItem
public abstract class CarouselGroup : CarouselItem
{
protected CarouselGroup(List<CarouselItem>? items = null)
{
if (items != null) this.items = items;
State.ValueChanged += state =>
{
switch (state.NewValue)
{
case CarouselItemState.Collapsed:
case CarouselItemState.NotSelected:
this.items.ForEach(c => c.State.Value = CarouselItemState.Collapsed);
break;
case CarouselItemState.Selected:
this.items.ForEach(c =>
{
if (c.State.Value == CarouselItemState.Collapsed) c.State.Value = CarouselItemState.NotSelected;
});
break;
}
};
}
public override DrawableCarouselItem? CreateDrawableRepresentation() => null;
public SlimReadOnlyListWrapper<CarouselItem> Items => items.AsSlimReadOnly();
@ -67,29 +90,6 @@ namespace osu.Game.Screens.Select.Carousel
TotalItemsNotFiltered++;
}
public CarouselGroup(List<CarouselItem>? items = null)
{
if (items != null) this.items = items;
State.ValueChanged += state =>
{
switch (state.NewValue)
{
case CarouselItemState.Collapsed:
case CarouselItemState.NotSelected:
this.items.ForEach(c => c.State.Value = CarouselItemState.Collapsed);
break;
case CarouselItemState.Selected:
this.items.ForEach(c =>
{
if (c.State.Value == CarouselItemState.Collapsed) c.State.Value = CarouselItemState.NotSelected;
});
break;
}
};
}
public override void Filter(FilterCriteria criteria)
{
base.Filter(criteria);

View File

@ -10,9 +10,9 @@ namespace osu.Game.Screens.Select.Carousel
/// <summary>
/// A group which ensures at least one item is selected (if the group itself is selected).
/// </summary>
public class CarouselGroupEagerSelect : CarouselGroup
public abstract class CarouselGroupEagerSelect : CarouselGroup
{
public CarouselGroupEagerSelect()
protected CarouselGroupEagerSelect()
{
State.ValueChanged += state =>
{

View File

@ -0,0 +1,16 @@
// 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.Threading;
using osu.Framework.Bindables;
using osu.Game.Beatmaps;
using osu.Game.Database;
namespace osu.Game.Tests.Beatmaps
{
internal partial class TestBeatmapStore : BeatmapStore
{
public readonly BindableList<BeatmapSetInfo> BeatmapSets = new BindableList<BeatmapSetInfo>();
public override IBindableList<BeatmapSetInfo> GetBeatmapSets(CancellationToken? cancellationToken) => BeatmapSets;
}
}