From 72b56146eef2f023b014ea5954cb6c84bff4b1ff Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 25 Mar 2025 14:22:54 +0900 Subject: [PATCH 1/7] Refactor tracker to be a bit more stateless Removes storage of `selectedBeatmap` that was referenced through multiple class-level methods. To expound a bit, this structure felt better (or otherwise passing `APIBeatmap` through methods) alongside removal of the `#nullable disable`, otherwise each method would check `selectedBeatmap != null`. --- ...eneOnlinePlayBeatmapAvailabilityTracker.cs | 3 +- .../TestSceneMultiplayerSpectateButton.cs | 2 +- .../DailyChallenge/DailyChallenge.cs | 2 +- .../DailyChallenge/DailyChallengeIntro.cs | 2 +- .../Screens/OnlinePlay/Match/RoomSubScreen.cs | 2 +- .../OnlinePlayBeatmapAvailabilityTracker.cs | 122 ++++++++---------- .../Playlists/PlaylistsRoomSubScreen.cs | 2 +- .../IOnlinePlayTestSceneDependencies.cs | 1 - .../OnlinePlayTestSceneDependencies.cs | 1 - 9 files changed, 64 insertions(+), 73 deletions(-) rename osu.Game/{Online/Rooms => Screens/OnlinePlay}/OnlinePlayBeatmapAvailabilityTracker.cs (58%) diff --git a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs index ae3451c3e0..5326b36e5e 100644 --- a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs +++ b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs @@ -28,6 +28,7 @@ using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Rooms; using osu.Game.Rulesets; +using osu.Game.Screens.OnlinePlay; using osu.Game.Tests.Resources; using osu.Game.Tests.Visual; @@ -110,7 +111,7 @@ namespace osu.Game.Tests.Online beatmapLookupCache, availabilityTracker = new OnlinePlayBeatmapAvailabilityTracker { - SelectedItem = { BindTarget = selectedItem, } + PlaylistItem = { BindTarget = selectedItem, } } } }; diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs index ff5436a87d..90b633c8f3 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs @@ -52,7 +52,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("create button", () => { - AvailabilityTracker.SelectedItem.Value = room.Playlist.First(); + AvailabilityTracker.PlaylistItem.Value = room.Playlist.First(); importedSet = beatmaps.GetAllUsableBeatmapSets().First(); Beatmap.Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First()); diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs index 5c8b500c93..2e78a69e4a 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs @@ -378,7 +378,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge { base.LoadComplete(); - beatmapAvailabilityTracker.SelectedItem.Value = playlistItem; + beatmapAvailabilityTracker.PlaylistItem.Value = playlistItem; beatmapAvailabilityTracker.Availability.BindValueChanged(_ => TrySetDailyChallengeBeatmap(this, beatmapManager, rulesets, musicController, playlistItem), true); userModsSelectOverlayRegistration = overlayManager?.RegisterBlockingOverlay(userModsSelectOverlay); diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs index 7fddb8d1c4..d414e3c54f 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs @@ -352,7 +352,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge { base.OnEntering(e); - beatmapAvailabilityTracker.SelectedItem.Value = item; + beatmapAvailabilityTracker.PlaylistItem.Value = item; beatmapAvailabilityTracker.Availability.BindValueChanged(availability => { if (shouldBePlayingMusic && availability.NewValue.State == DownloadState.LocallyAvailable) diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs index f924ff6980..07011c1626 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs @@ -268,7 +268,7 @@ namespace osu.Game.Screens.OnlinePlay.Match SelectedItem.BindValueChanged(_ => updateSpecifics()); UserMods.BindValueChanged(_ => updateSpecifics()); - beatmapAvailabilityTracker.SelectedItem.BindTo(SelectedItem); + beatmapAvailabilityTracker.PlaylistItem.BindTo(SelectedItem); beatmapAvailabilityTracker.Availability.BindValueChanged(_ => updateSpecifics()); userModsSelectOverlayRegistration = overlayManager?.RegisterBlockingOverlay(UserModsSelectOverlay); diff --git a/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs b/osu.Game/Screens/OnlinePlay/OnlinePlayBeatmapAvailabilityTracker.cs similarity index 58% rename from osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs rename to osu.Game/Screens/OnlinePlay/OnlinePlayBeatmapAvailabilityTracker.cs index 45f52f3cd8..bda618d1fa 100644 --- a/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlayBeatmapAvailabilityTracker.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Diagnostics; using System.Linq; @@ -16,10 +14,12 @@ using osu.Framework.Logging; using osu.Framework.Threading; using osu.Game.Beatmaps; using osu.Game.Database; +using osu.Game.Online; using osu.Game.Online.API.Requests.Responses; +using osu.Game.Online.Rooms; using Realms; -namespace osu.Game.Online.Rooms +namespace osu.Game.Screens.OnlinePlay { /// /// Represent a checksum-verifying beatmap availability tracker usable for online play screens. @@ -29,7 +29,15 @@ namespace osu.Game.Online.Rooms /// public partial class OnlinePlayBeatmapAvailabilityTracker : CompositeComponent { - public readonly Bindable SelectedItem = new Bindable(); + /// + /// The current availability of 's beatmap. + /// + public IBindable Availability => availability; + + /// + /// The playlist item to track the availability of. + /// + public readonly Bindable PlaylistItem = new Bindable(); [Resolved] private RealmAccess realm { get; set; } = null!; @@ -37,23 +45,17 @@ namespace osu.Game.Online.Rooms [Resolved] private BeatmapLookupCache beatmapLookupCache { get; set; } = null!; - /// - /// The availability state of the currently selected playlist item. - /// - public virtual IBindable Availability => availability; - private readonly Bindable availability = new Bindable(BeatmapAvailability.NotDownloaded()); - private ScheduledDelegate progressUpdate; - private BeatmapDownloadTracker downloadTracker; - private IDisposable realmSubscription; - private APIBeatmap selectedBeatmap; + private ScheduledDelegate? progressUpdate; + private BeatmapDownloadTracker? downloadTracker; + private IDisposable? realmSubscription; protected override void LoadComplete() { base.LoadComplete(); - SelectedItem.BindValueChanged(item => + PlaylistItem.BindValueChanged(item => { // the underlying playlist is regularly cleared for maintenance purposes (things which probably need to be fixed eventually). // to avoid exposing a state change when there may actually be none, ignore all nulls for now. @@ -69,30 +71,29 @@ namespace osu.Game.Online.Rooms // This is just for safety. availability.Value = BeatmapAvailability.Unknown(); - downloadTracker?.RemoveAndDisposeImmediately(); - selectedBeatmap = null; + cancelTracking(); beatmapLookupCache.GetBeatmapAsync(item.NewValue.Beatmap.OnlineID).ContinueWith(task => Schedule(() => { var beatmap = task.GetResultSafely(); - if (beatmap != null && SelectedItem.Value?.Beatmap.OnlineID == beatmap.OnlineID) - { - selectedBeatmap = beatmap; - beginTracking(); - } + if (beatmap != null && PlaylistItem.Value?.Beatmap.OnlineID == beatmap.OnlineID) + startTracking(beatmap); }), TaskContinuationOptions.OnlyOnRanToCompletion); }, true); } - private void beginTracking() + private void cancelTracking() { - Debug.Assert(selectedBeatmap.BeatmapSet != null); + downloadTracker?.RemoveAndDisposeImmediately(); + realmSubscription?.Dispose(); + } - downloadTracker = new BeatmapDownloadTracker(selectedBeatmap.BeatmapSet); - - AddInternal(downloadTracker); + private void startTracking(APIBeatmap beatmap) + { + Debug.Assert(beatmap.BeatmapSet != null); + downloadTracker = new BeatmapDownloadTracker(beatmap.BeatmapSet); downloadTracker.State.BindValueChanged(_ => Scheduler.AddOnce(updateAvailability), true); downloadTracker.Progress.BindValueChanged(_ => { @@ -105,64 +106,55 @@ namespace osu.Game.Online.Rooms progressUpdate = Scheduler.AddDelayed(updateAvailability, progressUpdate == null ? 0 : 500); }, true); + AddInternal(downloadTracker); + // handles changes to hash that didn't occur from the import process (ie. a user editing the beatmap in the editor, somehow). - realmSubscription?.Dispose(); - realmSubscription = realm.RegisterForNotifications(_ => filteredBeatmaps(), (_, changes) => + realmSubscription = realm.RegisterForNotifications(_ => queryBeatmap(), (_, changes) => { if (changes == null) return; Scheduler.AddOnce(updateAvailability); }); - } - private void updateAvailability() - { - if (downloadTracker == null || selectedBeatmap == null) - return; - - switch (downloadTracker.State.Value) + void updateAvailability() { - case DownloadState.Unknown: - availability.Value = BeatmapAvailability.Unknown(); - break; + switch (downloadTracker.State.Value) + { + case DownloadState.Unknown: + availability.Value = BeatmapAvailability.Unknown(); + break; - case DownloadState.NotDownloaded: - availability.Value = BeatmapAvailability.NotDownloaded(); - break; + case DownloadState.NotDownloaded: + availability.Value = BeatmapAvailability.NotDownloaded(); + break; - case DownloadState.Downloading: - availability.Value = BeatmapAvailability.Downloading((float)downloadTracker.Progress.Value); - break; + case DownloadState.Downloading: + availability.Value = BeatmapAvailability.Downloading((float)downloadTracker.Progress.Value); + break; - case DownloadState.Importing: - availability.Value = BeatmapAvailability.Importing(); - break; + case DownloadState.Importing: + availability.Value = BeatmapAvailability.Importing(); + break; - case DownloadState.LocallyAvailable: - bool available = filteredBeatmaps().Any(); + case DownloadState.LocallyAvailable: + bool available = queryBeatmap().Any(); - availability.Value = available ? BeatmapAvailability.LocallyAvailable() : BeatmapAvailability.NotDownloaded(); + availability.Value = available ? BeatmapAvailability.LocallyAvailable() : BeatmapAvailability.NotDownloaded(); - // only display a message to the user if a download seems to have just completed. - if (!available && downloadTracker.Progress.Value == 1) - Logger.Log("The imported beatmap set does not match the online version.", LoggingTarget.Runtime, LogLevel.Important); + // only display a message to the user if a download seems to have just completed. + if (!available && downloadTracker.Progress.Value == 1) + Logger.Log("The imported beatmap set does not match the online version.", LoggingTarget.Runtime, LogLevel.Important); - break; + break; - default: - throw new ArgumentOutOfRangeException(); + default: + throw new ArgumentOutOfRangeException(); + } } - } - private IQueryable filteredBeatmaps() - { - int onlineId = selectedBeatmap.OnlineID; - string checksum = selectedBeatmap.MD5Hash; - - return realm.Realm - .All() - .Filter("OnlineID == $0 && MD5Hash == $1 && BeatmapSet.DeletePending == false", onlineId, checksum); + IQueryable queryBeatmap() => + realm.Realm.All().Filter("OnlineID == $0 && MD5Hash == $1 && BeatmapSet.DeletePending == false", beatmap.OnlineID, beatmap.MD5Hash); } protected override void Dispose(bool isDisposing) diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs index ae31e55da5..4ada9a99fd 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs @@ -454,7 +454,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists SelectedItem.BindValueChanged(onSelectedItemChanged); - beatmapAvailabilityTracker.SelectedItem.BindTo(SelectedItem); + beatmapAvailabilityTracker.PlaylistItem.BindTo(SelectedItem); beatmapAvailabilityTracker.Availability.BindValueChanged(_ => updateGameplayState()); UserBeatmap.BindValueChanged(_ => updateGameplayState()); diff --git a/osu.Game/Tests/Visual/OnlinePlay/IOnlinePlayTestSceneDependencies.cs b/osu.Game/Tests/Visual/OnlinePlay/IOnlinePlayTestSceneDependencies.cs index 60730ee9a4..861aa079f4 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/IOnlinePlayTestSceneDependencies.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/IOnlinePlayTestSceneDependencies.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using osu.Game.Database; -using osu.Game.Online.Rooms; using osu.Game.Screens.OnlinePlay; namespace osu.Game.Tests.Visual.OnlinePlay diff --git a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestSceneDependencies.cs b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestSceneDependencies.cs index 9537c7958c..ca680fc5ba 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestSceneDependencies.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestSceneDependencies.cs @@ -6,7 +6,6 @@ using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Game.Database; -using osu.Game.Online.Rooms; using osu.Game.Overlays; using osu.Game.Screens.OnlinePlay; From 53ece9396a99ddbeee345b406c9ee8a569def29a Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 25 Mar 2025 14:47:39 +0900 Subject: [PATCH 2/7] Split tracker into per-system implementations --- ...enePlaylistsBeatmapAvailabilityTracker.cs} | 57 +++++++++---------- .../TestSceneMultiplayerMatchFooter.cs | 24 +++++--- .../TestSceneMultiplayerSpectateButton.cs | 42 ++++++++------ .../TestSceneUpdateBeatmapSetButton.cs | 10 ++-- .../DailyChallenge/DailyChallenge.cs | 8 ++- ...ailyChallengeBeatmapAvailabilityTracker.cs | 15 +++++ .../DailyChallenge/DailyChallengeIntro.cs | 7 ++- .../Screens/OnlinePlay/Match/RoomSubScreen.cs | 5 +- .../MultiplayerBeatmapAvailabilityTracker.cs | 40 +++++++++++++ .../OnlinePlayBeatmapAvailabilityTracker.cs | 6 +- .../PlaylistsBeatmapAvailabilityTracker.cs | 13 +++++ .../Playlists/PlaylistsRoomSubScreen.cs | 14 +++-- .../IOnlinePlayTestSceneDependencies.cs | 5 -- .../Visual/OnlinePlay/OnlinePlayTestScene.cs | 1 - .../OnlinePlayTestSceneDependencies.cs | 3 - 15 files changed, 163 insertions(+), 87 deletions(-) rename osu.Game.Tests/Online/{TestSceneOnlinePlayBeatmapAvailabilityTracker.cs => TestScenePlaylistsBeatmapAvailabilityTracker.cs} (83%) create mode 100644 osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeBeatmapAvailabilityTracker.cs create mode 100644 osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerBeatmapAvailabilityTracker.cs create mode 100644 osu.Game/Screens/OnlinePlay/Playlists/PlaylistsBeatmapAvailabilityTracker.cs diff --git a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs b/osu.Game.Tests/Online/TestScenePlaylistsBeatmapAvailabilityTracker.cs similarity index 83% rename from osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs rename to osu.Game.Tests/Online/TestScenePlaylistsBeatmapAvailabilityTracker.cs index 5326b36e5e..220c23b5bc 100644 --- a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs +++ b/osu.Game.Tests/Online/TestScenePlaylistsBeatmapAvailabilityTracker.cs @@ -1,18 +1,14 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Threading; -using JetBrains.Annotations; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; -using osu.Framework.Bindables; using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.IO.Stores; @@ -27,32 +23,29 @@ using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Rooms; -using osu.Game.Rulesets; using osu.Game.Screens.OnlinePlay; +using osu.Game.Screens.OnlinePlay.Playlists; using osu.Game.Tests.Resources; using osu.Game.Tests.Visual; namespace osu.Game.Tests.Online { [HeadlessTest] - public partial class TestSceneOnlinePlayBeatmapAvailabilityTracker : OsuTestScene + public partial class TestScenePlaylistsBeatmapAvailabilityTracker : OsuTestScene { - private RulesetStore rulesets; - private TestBeatmapManager beatmaps; - private TestBeatmapModelDownloader beatmapDownloader; + private TestBeatmapManager beatmaps = null!; + private TestBeatmapModelDownloader beatmapDownloader = null!; - private string testBeatmapFile; - private BeatmapInfo testBeatmapInfo; - private BeatmapSetInfo testBeatmapSet; + private string testBeatmapFile = null!; + private BeatmapInfo testBeatmapInfo = null!; + private BeatmapSetInfo testBeatmapSet = null!; - private readonly Bindable selectedItem = new Bindable(); - private OnlinePlayBeatmapAvailabilityTracker availabilityTracker; + private OnlinePlayBeatmapAvailabilityTracker availabilityTracker = null!; [BackgroundDependencyLoader] private void load(AudioManager audio, GameHost host) { - Dependencies.Cache(rulesets = new RealmRulesetStore(Realm)); - Dependencies.CacheAs(beatmaps = new TestBeatmapManager(LocalStorage, Realm, rulesets, API, audio, Resources, host, Beatmap.Default)); + Dependencies.CacheAs(beatmaps = new TestBeatmapManager(LocalStorage, Realm, API, audio, Resources, host, Beatmap.Default)); Dependencies.CacheAs(beatmapDownloader = new TestBeatmapModelDownloader(beatmaps, API)); } @@ -83,16 +76,11 @@ namespace osu.Game.Tests.Online testBeatmapFile = TestResources.GetQuickTestBeatmapForImport(); testBeatmapInfo = getTestBeatmapInfo(testBeatmapFile); - testBeatmapSet = testBeatmapInfo.BeatmapSet; + testBeatmapSet = testBeatmapInfo.BeatmapSet!; Realm.Write(r => r.RemoveAll()); Realm.Write(r => r.RemoveAll()); - selectedItem.Value = new PlaylistItem(testBeatmapInfo) - { - RulesetID = testBeatmapInfo.Ruleset.OnlineID, - }; - recreateChildren(); }); @@ -109,9 +97,15 @@ namespace osu.Game.Tests.Online Children = new Drawable[] { beatmapLookupCache, - availabilityTracker = new OnlinePlayBeatmapAvailabilityTracker + availabilityTracker = new PlaylistsBeatmapAvailabilityTracker { - PlaylistItem = { BindTarget = selectedItem, } + PlaylistItem = + { + Value = new PlaylistItem(testBeatmapInfo) + { + RulesetID = testBeatmapInfo.Ruleset.OnlineID, + }, + } } } }; @@ -126,10 +120,10 @@ namespace osu.Game.Tests.Online AddStep("start downloading", () => beatmapDownloader.Download(testBeatmapSet)); addAvailabilityCheckStep("state downloading 0%", () => BeatmapAvailability.Downloading(0.0f)); - AddStep("set progress 40%", () => ((TestDownloadRequest)beatmapDownloader.GetExistingDownload(testBeatmapSet))!.SetProgress(0.4f)); + AddStep("set progress 40%", () => ((TestDownloadRequest)beatmapDownloader.GetExistingDownload(testBeatmapSet)!).SetProgress(0.4f)); addAvailabilityCheckStep("state downloading 40%", () => BeatmapAvailability.Downloading(0.4f)); - AddStep("finish download", () => ((TestDownloadRequest)beatmapDownloader.GetExistingDownload(testBeatmapSet))!.TriggerSuccess(testBeatmapFile)); + AddStep("finish download", () => ((TestDownloadRequest)beatmapDownloader.GetExistingDownload(testBeatmapSet)!).TriggerSuccess(testBeatmapFile)); addAvailabilityCheckStep("state importing", BeatmapAvailability.Importing); AddStep("allow importing", () => beatmaps.AllowImport.Set()); @@ -204,10 +198,10 @@ namespace osu.Game.Tests.Online { public readonly ManualResetEventSlim AllowImport = new ManualResetEventSlim(); - public Live CurrentImport { get; private set; } + public Live? CurrentImport { get; private set; } - public TestBeatmapManager(Storage storage, RealmAccess realm, RulesetStore rulesets, IAPIProvider api, [NotNull] AudioManager audioManager, IResourceStore resources, - GameHost host = null, WorkingBeatmap defaultBeatmap = null) + public TestBeatmapManager(Storage storage, RealmAccess realm, IAPIProvider api, AudioManager audioManager, IResourceStore resources, + GameHost? host = null, WorkingBeatmap? defaultBeatmap = null) : base(storage, realm, api, audioManager, resources, host, defaultBeatmap) { } @@ -227,12 +221,13 @@ namespace osu.Game.Tests.Online this.testBeatmapManager = testBeatmapManager; } - public override Live ImportModel(BeatmapSetInfo item, ArchiveReader archive = null, ImportParameters parameters = default, CancellationToken cancellationToken = default) + public override Live? ImportModel(BeatmapSetInfo item, ArchiveReader? archive = null, ImportParameters parameters = default, + CancellationToken cancellationToken = default) { if (!testBeatmapManager.AllowImport.Wait(TimeSpan.FromSeconds(10), cancellationToken)) throw new TimeoutException("Timeout waiting for import to be allowed."); - return (testBeatmapManager.CurrentImport = base.ImportModel(item, archive, parameters, cancellationToken)); + return testBeatmapManager.CurrentImport = base.ImportModel(item, archive, parameters, cancellationToken); } } } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchFooter.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchFooter.cs index c2d3b17ccb..eedd2c8f33 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchFooter.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchFooter.cs @@ -4,6 +4,8 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; +using osu.Game.Screens.OnlinePlay; +using osu.Game.Screens.OnlinePlay.Multiplayer; using osu.Game.Screens.OnlinePlay.Multiplayer.Match; namespace osu.Game.Tests.Visual.Multiplayer @@ -16,18 +18,26 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("create footer", () => { - Child = new PopoverContainer + Child = new DependencyProvidingContainer { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, - Child = new Container + CachedDependencies = + [ + (typeof(OnlinePlayBeatmapAvailabilityTracker), new MultiplayerBeatmapAvailabilityTracker()) + ], + Child = new PopoverContainer { Anchor = Anchor.Centre, Origin = Anchor.Centre, - RelativeSizeAxes = Axes.X, - Height = 50, - Child = new MultiplayerMatchFooter() + RelativeSizeAxes = Axes.Both, + Child = new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.X, + Height = 50, + Child = new MultiplayerMatchFooter() + } } }; }); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs index 90b633c8f3..5f94e74ce9 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs @@ -17,6 +17,8 @@ using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Multiplayer; using osu.Game.Online.Rooms; using osu.Game.Rulesets; +using osu.Game.Screens.OnlinePlay; +using osu.Game.Screens.OnlinePlay.Multiplayer; using osu.Game.Screens.OnlinePlay.Multiplayer.Match; using osu.Game.Tests.Resources; using osuTK; @@ -52,31 +54,37 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("create button", () => { - AvailabilityTracker.PlaylistItem.Value = room.Playlist.First(); - importedSet = beatmaps.GetAllUsableBeatmapSets().First(); Beatmap.Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First()); - Child = new PopoverContainer + Child = new DependencyProvidingContainer { RelativeSizeAxes = Axes.Both, - Child = new FillFlowContainer + CachedDependencies = + [ + (typeof(OnlinePlayBeatmapAvailabilityTracker), new MultiplayerBeatmapAvailabilityTracker()) + ], + Child = new PopoverContainer { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Vertical, - Children = new Drawable[] + RelativeSizeAxes = Axes.Both, + Child = new FillFlowContainer { - spectateButton = new MultiplayerSpectateButton + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Children = new Drawable[] { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(200, 50) - }, - startControl = new MatchStartControl - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(200, 50) + spectateButton = new MultiplayerSpectateButton + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(200, 50) + }, + startControl = new MatchStartControl + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(200, 50) + } } } } diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneUpdateBeatmapSetButton.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneUpdateBeatmapSetButton.cs index ff0f35576c..2311c360ff 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneUpdateBeatmapSetButton.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneUpdateBeatmapSetButton.cs @@ -28,7 +28,7 @@ namespace osu.Game.Tests.Visual.SongSelect { private BeatmapCarousel carousel = null!; - private TestSceneOnlinePlayBeatmapAvailabilityTracker.TestBeatmapModelDownloader beatmapDownloader = null!; + private TestScenePlaylistsBeatmapAvailabilityTracker.TestBeatmapModelDownloader beatmapDownloader = null!; private BeatmapSetInfo testBeatmapSetInfo = null!; @@ -41,7 +41,7 @@ namespace osu.Game.Tests.Visual.SongSelect var importer = parent.Get(); - dependencies.CacheAs(beatmapDownloader = new TestSceneOnlinePlayBeatmapAvailabilityTracker.TestBeatmapModelDownloader(importer, API)); + dependencies.CacheAs(beatmapDownloader = new TestScenePlaylistsBeatmapAvailabilityTracker.TestBeatmapModelDownloader(importer, API)); return dependencies; } @@ -85,7 +85,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddUntilStep("progress download to completion", () => { - if (downloadRequest is TestSceneOnlinePlayBeatmapAvailabilityTracker.TestDownloadRequest testRequest) + if (downloadRequest is TestScenePlaylistsBeatmapAvailabilityTracker.TestDownloadRequest testRequest) { testRequest.SetProgress(testRequest.Progress + 0.1f); @@ -135,7 +135,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddUntilStep("progress download to failure", () => { - if (downloadRequest is TestSceneOnlinePlayBeatmapAvailabilityTracker.TestDownloadRequest testRequest) + if (downloadRequest is TestScenePlaylistsBeatmapAvailabilityTracker.TestDownloadRequest testRequest) { testRequest.SetProgress(testRequest.Progress + 0.1f); @@ -226,7 +226,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddUntilStep("progress download to completion", () => { - if (downloadRequest is TestSceneOnlinePlayBeatmapAvailabilityTracker.TestDownloadRequest testRequest) + if (downloadRequest is TestScenePlaylistsBeatmapAvailabilityTracker.TestDownloadRequest testRequest) { testRequest.SetProgress(testRequest.Progress + 0.1f); diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs index 2e78a69e4a..cb881ccfe5 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs @@ -71,8 +71,8 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge [Cached] private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Plum); - [Cached] - private readonly OnlinePlayBeatmapAvailabilityTracker beatmapAvailabilityTracker = new OnlinePlayBeatmapAvailabilityTracker(); + [Cached(typeof(OnlinePlayBeatmapAvailabilityTracker))] + private readonly DailyChallengeBeatmapAvailabilityTracker beatmapAvailabilityTracker; [Resolved] private OsuGame? game { get; set; } @@ -113,8 +113,11 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge public DailyChallenge(Room room) { this.room = room; + playlistItem = room.Playlist.Single(); Padding = new MarginPadding { Horizontal = -HORIZONTAL_OVERFLOW_PADDING }; + + beatmapAvailabilityTracker = new DailyChallengeBeatmapAvailabilityTracker(playlistItem); } [BackgroundDependencyLoader] @@ -378,7 +381,6 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge { base.LoadComplete(); - beatmapAvailabilityTracker.PlaylistItem.Value = playlistItem; beatmapAvailabilityTracker.Availability.BindValueChanged(_ => TrySetDailyChallengeBeatmap(this, beatmapManager, rulesets, musicController, playlistItem), true); userModsSelectOverlayRegistration = overlayManager?.RegisterBlockingOverlay(userModsSelectOverlay); diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeBeatmapAvailabilityTracker.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeBeatmapAvailabilityTracker.cs new file mode 100644 index 0000000000..2693978129 --- /dev/null +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeBeatmapAvailabilityTracker.cs @@ -0,0 +1,15 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Online.Rooms; + +namespace osu.Game.Screens.OnlinePlay.DailyChallenge +{ + public class DailyChallengeBeatmapAvailabilityTracker : OnlinePlayBeatmapAvailabilityTracker + { + public DailyChallengeBeatmapAvailabilityTracker(PlaylistItem item) + { + PlaylistItem.Value = item; + } + } +} diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs index d414e3c54f..5b423fbc6d 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs @@ -56,8 +56,8 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge [Cached] private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Plum); - [Cached] - private readonly OnlinePlayBeatmapAvailabilityTracker beatmapAvailabilityTracker = new OnlinePlayBeatmapAvailabilityTracker(); + [Cached(typeof(OnlinePlayBeatmapAvailabilityTracker))] + private readonly DailyChallengeBeatmapAvailabilityTracker beatmapAvailabilityTracker; private bool shouldBePlayingMusic; @@ -91,6 +91,8 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge item = room.Playlist.Single(); ValidForResume = false; + + beatmapAvailabilityTracker = new DailyChallengeBeatmapAvailabilityTracker(item); } protected override BackgroundScreen CreateBackground() => new DailyChallengeIntroBackgroundScreen(colourProvider); @@ -352,7 +354,6 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge { base.OnEntering(e); - beatmapAvailabilityTracker.PlaylistItem.Value = item; beatmapAvailabilityTracker.Availability.BindValueChanged(availability => { if (shouldBePlayingMusic && availability.NewValue.State == DownloadState.LocallyAvailable) diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs index 07011c1626..eca59c8393 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs @@ -94,8 +94,8 @@ namespace osu.Game.Screens.OnlinePlay.Match [Resolved(canBeNull: true)] protected IDialogOverlay? DialogOverlay { get; private set; } - [Cached] - private readonly OnlinePlayBeatmapAvailabilityTracker beatmapAvailabilityTracker = new OnlinePlayBeatmapAvailabilityTracker(); + [Cached(typeof(OnlinePlayBeatmapAvailabilityTracker))] + private readonly MultiplayerBeatmapAvailabilityTracker beatmapAvailabilityTracker = new MultiplayerBeatmapAvailabilityTracker(); protected IBindable BeatmapAvailability => beatmapAvailabilityTracker.Availability; @@ -268,7 +268,6 @@ namespace osu.Game.Screens.OnlinePlay.Match SelectedItem.BindValueChanged(_ => updateSpecifics()); UserMods.BindValueChanged(_ => updateSpecifics()); - beatmapAvailabilityTracker.PlaylistItem.BindTo(SelectedItem); beatmapAvailabilityTracker.Availability.BindValueChanged(_ => updateSpecifics()); userModsSelectOverlayRegistration = overlayManager?.RegisterBlockingOverlay(UserModsSelectOverlay); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerBeatmapAvailabilityTracker.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerBeatmapAvailabilityTracker.cs new file mode 100644 index 0000000000..b608edc448 --- /dev/null +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerBeatmapAvailabilityTracker.cs @@ -0,0 +1,40 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Extensions.ObjectExtensions; +using osu.Game.Online.Multiplayer; +using osu.Game.Online.Rooms; + +namespace osu.Game.Screens.OnlinePlay.Multiplayer +{ + public class MultiplayerBeatmapAvailabilityTracker : OnlinePlayBeatmapAvailabilityTracker + { + [Resolved] + private MultiplayerClient client { get; set; } = null!; + + protected override void LoadComplete() + { + base.LoadComplete(); + + client.RoomUpdated += onRoomUpdated; + onRoomUpdated(); + } + + private void onRoomUpdated() + { + if (client.Room == null) + return; + + PlaylistItem.Value = new PlaylistItem(client.Room.CurrentPlaylistItem); + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + if (client.IsNotNull()) + client.RoomUpdated -= onRoomUpdated; + } + } +} diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlayBeatmapAvailabilityTracker.cs b/osu.Game/Screens/OnlinePlay/OnlinePlayBeatmapAvailabilityTracker.cs index bda618d1fa..ae0b4a9943 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlayBeatmapAvailabilityTracker.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlayBeatmapAvailabilityTracker.cs @@ -27,17 +27,17 @@ namespace osu.Game.Screens.OnlinePlay /// This differs from a regular download tracking composite as this accounts for the /// databased beatmap set's checksum, to disallow from playing with an altered version of the beatmap. /// - public partial class OnlinePlayBeatmapAvailabilityTracker : CompositeComponent + public abstract partial class OnlinePlayBeatmapAvailabilityTracker : CompositeComponent { /// /// The current availability of 's beatmap. /// - public IBindable Availability => availability; + public virtual IBindable Availability => availability; // Virtual for mocking in some tests. /// /// The playlist item to track the availability of. /// - public readonly Bindable PlaylistItem = new Bindable(); + protected readonly Bindable PlaylistItem = new Bindable(); [Resolved] private RealmAccess realm { get; set; } = null!; diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsBeatmapAvailabilityTracker.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsBeatmapAvailabilityTracker.cs new file mode 100644 index 0000000000..dd2f187329 --- /dev/null +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsBeatmapAvailabilityTracker.cs @@ -0,0 +1,13 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Bindables; +using osu.Game.Online.Rooms; + +namespace osu.Game.Screens.OnlinePlay.Playlists +{ + public class PlaylistsBeatmapAvailabilityTracker : OnlinePlayBeatmapAvailabilityTracker + { + public new Bindable PlaylistItem => base.PlaylistItem; + } +} diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs index 4ada9a99fd..89416e66bf 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs @@ -110,8 +110,8 @@ namespace osu.Game.Screens.OnlinePlay.Playlists [Resolved] private IDialogOverlay? dialogOverlay { get; set; } - [Cached] - private readonly OnlinePlayBeatmapAvailabilityTracker beatmapAvailabilityTracker = new OnlinePlayBeatmapAvailabilityTracker(); + [Cached(typeof(OnlinePlayBeatmapAvailabilityTracker))] + private readonly PlaylistsBeatmapAvailabilityTracker beatmapAvailabilityTracker; protected readonly Bindable SelectedItem = new Bindable(); protected readonly Bindable UserBeatmap = new Bindable(); @@ -146,6 +146,11 @@ namespace osu.Game.Screens.OnlinePlay.Playlists Activity.Value = new UserActivity.InLobby(room); Padding = new MarginPadding { Top = Header.HEIGHT }; + + beatmapAvailabilityTracker = new PlaylistsBeatmapAvailabilityTracker + { + PlaylistItem = { BindTarget = SelectedItem } + }; } [BackgroundDependencyLoader] @@ -451,12 +456,9 @@ namespace osu.Game.Screens.OnlinePlay.Playlists room.PropertyChanged += onRoomPropertyChanged; isIdle.BindValueChanged(_ => updatePollingRate(), true); - - SelectedItem.BindValueChanged(onSelectedItemChanged); - - beatmapAvailabilityTracker.PlaylistItem.BindTo(SelectedItem); beatmapAvailabilityTracker.Availability.BindValueChanged(_ => updateGameplayState()); + SelectedItem.BindValueChanged(onSelectedItemChanged); UserBeatmap.BindValueChanged(_ => updateGameplayState()); UserMods.BindValueChanged(_ => updateGameplayState()); UserRuleset.BindValueChanged(_ => diff --git a/osu.Game/Tests/Visual/OnlinePlay/IOnlinePlayTestSceneDependencies.cs b/osu.Game/Tests/Visual/OnlinePlay/IOnlinePlayTestSceneDependencies.cs index 861aa079f4..0468c18076 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/IOnlinePlayTestSceneDependencies.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/IOnlinePlayTestSceneDependencies.cs @@ -16,11 +16,6 @@ namespace osu.Game.Tests.Visual.OnlinePlay /// OngoingOperationTracker OngoingOperationTracker { get; } - /// - /// The cached . - /// - OnlinePlayBeatmapAvailabilityTracker AvailabilityTracker { get; } - /// /// The cached . /// diff --git a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs index ce8df36590..914d187864 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs @@ -22,7 +22,6 @@ namespace osu.Game.Tests.Visual.OnlinePlay public abstract partial class OnlinePlayTestScene : ScreenTestScene, IOnlinePlayTestSceneDependencies { public OngoingOperationTracker OngoingOperationTracker => OnlinePlayDependencies.OngoingOperationTracker; - public OnlinePlayBeatmapAvailabilityTracker AvailabilityTracker => OnlinePlayDependencies.AvailabilityTracker; public TestUserLookupCache UserLookupCache => OnlinePlayDependencies.UserLookupCache; public BeatmapLookupCache BeatmapLookupCache => OnlinePlayDependencies.BeatmapLookupCache; diff --git a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestSceneDependencies.cs b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestSceneDependencies.cs index ca680fc5ba..9d66a44008 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestSceneDependencies.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestSceneDependencies.cs @@ -17,7 +17,6 @@ namespace osu.Game.Tests.Visual.OnlinePlay public class OnlinePlayTestSceneDependencies : IReadOnlyDependencyContainer, IOnlinePlayTestSceneDependencies { public OngoingOperationTracker OngoingOperationTracker { get; } - public OnlinePlayBeatmapAvailabilityTracker AvailabilityTracker { get; } public TestRoomRequestsHandler RequestsHandler { get; } public TestUserLookupCache UserLookupCache { get; } public BeatmapLookupCache BeatmapLookupCache { get; } @@ -34,7 +33,6 @@ namespace osu.Game.Tests.Visual.OnlinePlay { RequestsHandler = new TestRoomRequestsHandler(); OngoingOperationTracker = new OngoingOperationTracker(); - AvailabilityTracker = new OnlinePlayBeatmapAvailabilityTracker(); UserLookupCache = new TestUserLookupCache(); BeatmapLookupCache = new BeatmapLookupCache(); @@ -42,7 +40,6 @@ namespace osu.Game.Tests.Visual.OnlinePlay CacheAs(RequestsHandler); CacheAs(OngoingOperationTracker); - CacheAs(AvailabilityTracker); CacheAs(new OverlayColourProvider(OverlayColourScheme.Plum)); CacheAs(UserLookupCache); CacheAs(BeatmapLookupCache); From bc2b7aae1cdde8875f18d8315226e06c1d323b8e Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 25 Mar 2025 15:41:03 +0900 Subject: [PATCH 3/7] Adjust tests to add tracker to hierarchy --- .../TestSceneMultiplayerMatchFooter.cs | 28 +++++++----- .../TestSceneMultiplayerSpectateButton.cs | 44 +++++++++++-------- 2 files changed, 42 insertions(+), 30 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchFooter.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchFooter.cs index eedd2c8f33..a6ce03c129 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchFooter.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchFooter.cs @@ -18,27 +18,33 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("create footer", () => { + MultiplayerBeatmapAvailabilityTracker tracker = new MultiplayerBeatmapAvailabilityTracker(); + Child = new DependencyProvidingContainer { RelativeSizeAxes = Axes.Both, CachedDependencies = [ - (typeof(OnlinePlayBeatmapAvailabilityTracker), new MultiplayerBeatmapAvailabilityTracker()) + (typeof(OnlinePlayBeatmapAvailabilityTracker), tracker) ], - Child = new PopoverContainer - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - Child = new Container + Children = + [ + tracker, + new PopoverContainer { Anchor = Anchor.Centre, Origin = Anchor.Centre, - RelativeSizeAxes = Axes.X, - Height = 50, - Child = new MultiplayerMatchFooter() + RelativeSizeAxes = Axes.Both, + Child = new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.X, + Height = 50, + Child = new MultiplayerMatchFooter() + } } - } + ] }; }); } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs index 5f94e74ce9..12bc3c1418 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs @@ -57,37 +57,43 @@ namespace osu.Game.Tests.Visual.Multiplayer importedSet = beatmaps.GetAllUsableBeatmapSets().First(); Beatmap.Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First()); + MultiplayerBeatmapAvailabilityTracker tracker = new MultiplayerBeatmapAvailabilityTracker(); + Child = new DependencyProvidingContainer { RelativeSizeAxes = Axes.Both, CachedDependencies = [ - (typeof(OnlinePlayBeatmapAvailabilityTracker), new MultiplayerBeatmapAvailabilityTracker()) + (typeof(OnlinePlayBeatmapAvailabilityTracker), tracker) ], - Child = new PopoverContainer - { - RelativeSizeAxes = Axes.Both, - Child = new FillFlowContainer + Children = + [ + tracker, + new PopoverContainer { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Vertical, - Children = new Drawable[] + RelativeSizeAxes = Axes.Both, + Child = new FillFlowContainer { - spectateButton = new MultiplayerSpectateButton + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Children = new Drawable[] { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(200, 50) - }, - startControl = new MatchStartControl - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(200, 50) + spectateButton = new MultiplayerSpectateButton + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(200, 50) + }, + startControl = new MatchStartControl + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(200, 50) + } } } } - } + ] }; }); } From a1894c9193710849c9bf4ab29f03368f9f9fb6fb Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 25 Mar 2025 16:02:04 +0900 Subject: [PATCH 4/7] Add tests for multiplayer implementation --- ...neMultiplayerBeatmapAvailabilityTracker.cs | 185 ++++++++++++++++++ .../Multiplayer/TestMultiplayerClient.cs | 16 +- 2 files changed, 198 insertions(+), 3 deletions(-) create mode 100644 osu.Game.Tests/Online/TestSceneMultiplayerBeatmapAvailabilityTracker.cs diff --git a/osu.Game.Tests/Online/TestSceneMultiplayerBeatmapAvailabilityTracker.cs b/osu.Game.Tests/Online/TestSceneMultiplayerBeatmapAvailabilityTracker.cs new file mode 100644 index 0000000000..bcc48b1986 --- /dev/null +++ b/osu.Game.Tests/Online/TestSceneMultiplayerBeatmapAvailabilityTracker.cs @@ -0,0 +1,185 @@ +// Copyright (c) ppy Pty Ltd . 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 NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Extensions; +using osu.Framework.Platform; +using osu.Game.Beatmaps; +using osu.Game.Online; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Online.Multiplayer; +using osu.Game.Online.Rooms; +using osu.Game.Rulesets; +using osu.Game.Screens.OnlinePlay.Multiplayer; +using osu.Game.Tests.Resources; +using osu.Game.Tests.Visual.Multiplayer; +using osu.Game.Utils; + +namespace osu.Game.Tests.Online +{ + public class TestSceneMultiplayerBeatmapAvailabilityTracker : MultiplayerTestScene + { + private BeatmapManager beatmapManager = null!; + private BeatmapInfo availableBeatmap = null!; + private BeatmapInfo unavailableBeatmap = null!; + + private MultiplayerBeatmapAvailabilityTracker tracker = null!; + + [BackgroundDependencyLoader] + private void load(GameHost host) + { + Dependencies.Cache(new RealmRulesetStore(Realm)); + Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Realm, null, Audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(Realm); + + beatmapManager.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); + + var importedSet = beatmapManager.GetAllUsableBeatmapSets().First(); + availableBeatmap = importedSet.Beatmaps[0]; + unavailableBeatmap = importedSet.Beatmaps[1]; + + Realm.Write(r => r.Remove(r.Find(unavailableBeatmap.ID)!)); + } + + public override void SetUpSteps() + { + base.SetUpSteps(); + + AddStep("setup tracker", () => + { + DummyAPIAccess api = (DummyAPIAccess)API; + Func? defaultRequestHandler = api.HandleRequest; + + api.HandleRequest = req => + { + switch (req) + { + case GetBeatmapsRequest beatmapsReq: + var availableApiBeatmap = CreateAPIBeatmap(); + availableApiBeatmap.OnlineID = availableBeatmap.OnlineID; + availableApiBeatmap.OnlineBeatmapSetID = availableBeatmap.BeatmapSet!.OnlineID; + availableApiBeatmap.Checksum = availableBeatmap.MD5Hash; + availableApiBeatmap.BeatmapSet!.OnlineID = availableBeatmap.BeatmapSet!.OnlineID; + + var unavailableApiBeatmap = CreateAPIBeatmap(); + unavailableApiBeatmap.OnlineID = unavailableBeatmap.OnlineID; + unavailableApiBeatmap.OnlineBeatmapSetID = unavailableBeatmap.BeatmapSet!.OnlineID; + unavailableApiBeatmap.Checksum = unavailableBeatmap.MD5Hash; + unavailableApiBeatmap.BeatmapSet!.OnlineID = unavailableBeatmap.BeatmapSet!.OnlineID; + + beatmapsReq.TriggerSuccess(new GetBeatmapsResponse + { + Beatmaps = new List + { + availableApiBeatmap, + unavailableApiBeatmap + } + }); + return true; + + default: + return defaultRequestHandler?.Invoke(req) ?? false; + } + }; + + Child = tracker = new MultiplayerBeatmapAvailabilityTracker(); + }); + } + + [Test] + public void TestEnterRoomWithNotDownloadedBeatmap() + { + AddStep("join room", () => + { + var room = CreateDefaultRoom(); + room.Playlist = [new PlaylistItem(unavailableBeatmap)]; + JoinRoom(room); + }); + + WaitForJoined(); + + AddUntilStep("beatmap is not available", () => tracker.Availability.Value.State, () => Is.EqualTo(DownloadState.NotDownloaded)); + } + + [Test] + public void TestEnterRoomWithLocallyAvailableBeatmap() + { + AddStep("join room", () => + { + var room = CreateDefaultRoom(); + room.Playlist = [new PlaylistItem(availableBeatmap)]; + JoinRoom(room); + }); + + WaitForJoined(); + + AddUntilStep("beatmap is available", () => tracker.Availability.Value.State, () => Is.EqualTo(DownloadState.LocallyAvailable)); + } + + [Test] + public void TestAvailabilityUpdatesOnItemEdit() + { + AddStep("join room", () => + { + var room = CreateDefaultRoom(); + room.Playlist = [new PlaylistItem(availableBeatmap)]; + JoinRoom(room); + }); + + WaitForJoined(); + + AddUntilStep("beatmap is available", () => tracker.Availability.Value.State, () => Is.EqualTo(DownloadState.LocallyAvailable)); + + AddStep("change item to not downloaded beatmap", () => + { + PlaylistItem newItem = new PlaylistItem(MultiplayerClient.ClientRoom!.CurrentPlaylistItem).With(beatmap: new Optional(unavailableBeatmap)); + MultiplayerClient.EditPlaylistItem(new MultiplayerPlaylistItem(newItem)).WaitSafely(); + }); + + AddUntilStep("beatmap is not available", () => tracker.Availability.Value.State, () => Is.EqualTo(DownloadState.NotDownloaded)); + + AddStep("change item to downloaded beatmap", () => + { + PlaylistItem newItem = new PlaylistItem(MultiplayerClient.ClientRoom!.CurrentPlaylistItem).With(beatmap: new Optional(availableBeatmap)); + MultiplayerClient.EditPlaylistItem(new MultiplayerPlaylistItem(newItem)).WaitSafely(); + }); + + AddUntilStep("beatmap is available", () => tracker.Availability.Value.State, () => Is.EqualTo(DownloadState.LocallyAvailable)); + } + + [Test] + public void TestAvailabilityUpdatesOnSettingsChange() + { + AddStep("join room", () => + { + var room = CreateDefaultRoom(); + room.Playlist = [new PlaylistItem(availableBeatmap), new PlaylistItem(unavailableBeatmap)]; + JoinRoom(room); + }); + + WaitForJoined(); + + AddUntilStep("beatmap is available", () => tracker.Availability.Value.State, () => Is.EqualTo(DownloadState.LocallyAvailable)); + + AddStep("change settings to not downloaded beatmap", () => MultiplayerClient.ChangeServerRoomSettings(new MultiplayerRoomSettings(MultiplayerClient.ClientAPIRoom!) + { + PlaylistItemId = MultiplayerClient.ServerRoom!.Playlist[1].ID + }).WaitSafely()); + + AddUntilStep("beatmap is not available", () => tracker.Availability.Value.State, () => Is.EqualTo(DownloadState.NotDownloaded)); + + AddStep("change settings to downloaded beatmap", () => MultiplayerClient.ChangeServerRoomSettings(new MultiplayerRoomSettings(MultiplayerClient.ClientAPIRoom!) + { + PlaylistItemId = MultiplayerClient.ServerRoom!.Playlist[0].ID + }).WaitSafely()); + + AddUntilStep("beatmap is available", () => tracker.Availability.Value.State, () => Is.EqualTo(DownloadState.LocallyAvailable)); + } + } +} diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index febd7f54ff..bc73f853ec 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -298,14 +298,24 @@ namespace osu.Game.Tests.Visual.Multiplayer return ((IMultiplayerClient)this).UserKicked(clone(user)); } + /// + /// Simulates a change to the server-side room's settings without any other change. + /// + public async Task ChangeServerRoomSettings(MultiplayerRoomSettings settings) + { + Debug.Assert(ServerRoom != null); + + ServerRoom.Settings = settings; + + await ((IMultiplayerClient)this).SettingsChanged(clone(settings)).ConfigureAwait(false); + } + public override async Task ChangeSettings(MultiplayerRoomSettings settings) { - settings = clone(settings); - Debug.Assert(ServerRoom != null); - Debug.Assert(currentItem != null); // Server is authoritative for the time being. + settings = clone(settings); settings.PlaylistItemId = ServerRoom.Settings.PlaylistItemId; ServerRoom.Settings = settings; From 5793ad4a636ad98d5e603196d57da49828eda888 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 25 Mar 2025 16:05:33 +0900 Subject: [PATCH 5/7] Partial classes --- .../Online/TestSceneMultiplayerBeatmapAvailabilityTracker.cs | 2 +- .../DailyChallenge/DailyChallengeBeatmapAvailabilityTracker.cs | 2 +- .../Multiplayer/MultiplayerBeatmapAvailabilityTracker.cs | 2 +- .../OnlinePlay/Playlists/PlaylistsBeatmapAvailabilityTracker.cs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Online/TestSceneMultiplayerBeatmapAvailabilityTracker.cs b/osu.Game.Tests/Online/TestSceneMultiplayerBeatmapAvailabilityTracker.cs index bcc48b1986..643fb2e0cc 100644 --- a/osu.Game.Tests/Online/TestSceneMultiplayerBeatmapAvailabilityTracker.cs +++ b/osu.Game.Tests/Online/TestSceneMultiplayerBeatmapAvailabilityTracker.cs @@ -23,7 +23,7 @@ using osu.Game.Utils; namespace osu.Game.Tests.Online { - public class TestSceneMultiplayerBeatmapAvailabilityTracker : MultiplayerTestScene + public partial class TestSceneMultiplayerBeatmapAvailabilityTracker : MultiplayerTestScene { private BeatmapManager beatmapManager = null!; private BeatmapInfo availableBeatmap = null!; diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeBeatmapAvailabilityTracker.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeBeatmapAvailabilityTracker.cs index 2693978129..828a8d85ca 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeBeatmapAvailabilityTracker.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeBeatmapAvailabilityTracker.cs @@ -5,7 +5,7 @@ using osu.Game.Online.Rooms; namespace osu.Game.Screens.OnlinePlay.DailyChallenge { - public class DailyChallengeBeatmapAvailabilityTracker : OnlinePlayBeatmapAvailabilityTracker + public partial class DailyChallengeBeatmapAvailabilityTracker : OnlinePlayBeatmapAvailabilityTracker { public DailyChallengeBeatmapAvailabilityTracker(PlaylistItem item) { diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerBeatmapAvailabilityTracker.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerBeatmapAvailabilityTracker.cs index b608edc448..71d2d36ee2 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerBeatmapAvailabilityTracker.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerBeatmapAvailabilityTracker.cs @@ -8,7 +8,7 @@ using osu.Game.Online.Rooms; namespace osu.Game.Screens.OnlinePlay.Multiplayer { - public class MultiplayerBeatmapAvailabilityTracker : OnlinePlayBeatmapAvailabilityTracker + public partial class MultiplayerBeatmapAvailabilityTracker : OnlinePlayBeatmapAvailabilityTracker { [Resolved] private MultiplayerClient client { get; set; } = null!; diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsBeatmapAvailabilityTracker.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsBeatmapAvailabilityTracker.cs index dd2f187329..37d3a3f2d4 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsBeatmapAvailabilityTracker.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsBeatmapAvailabilityTracker.cs @@ -6,7 +6,7 @@ using osu.Game.Online.Rooms; namespace osu.Game.Screens.OnlinePlay.Playlists { - public class PlaylistsBeatmapAvailabilityTracker : OnlinePlayBeatmapAvailabilityTracker + public partial class PlaylistsBeatmapAvailabilityTracker : OnlinePlayBeatmapAvailabilityTracker { public new Bindable PlaylistItem => base.PlaylistItem; } From b83a69b029fd31003ecead4393e3b46f80cc9f3c Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 25 Mar 2025 16:08:13 +0900 Subject: [PATCH 6/7] Remove unnecessary ruleset store --- .../Online/TestSceneMultiplayerBeatmapAvailabilityTracker.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game.Tests/Online/TestSceneMultiplayerBeatmapAvailabilityTracker.cs b/osu.Game.Tests/Online/TestSceneMultiplayerBeatmapAvailabilityTracker.cs index 643fb2e0cc..746115400a 100644 --- a/osu.Game.Tests/Online/TestSceneMultiplayerBeatmapAvailabilityTracker.cs +++ b/osu.Game.Tests/Online/TestSceneMultiplayerBeatmapAvailabilityTracker.cs @@ -15,7 +15,6 @@ using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Multiplayer; using osu.Game.Online.Rooms; -using osu.Game.Rulesets; using osu.Game.Screens.OnlinePlay.Multiplayer; using osu.Game.Tests.Resources; using osu.Game.Tests.Visual.Multiplayer; @@ -34,7 +33,6 @@ namespace osu.Game.Tests.Online [BackgroundDependencyLoader] private void load(GameHost host) { - Dependencies.Cache(new RealmRulesetStore(Realm)); Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Realm, null, Audio, Resources, host, Beatmap.Default)); Dependencies.Cache(Realm); From 0392af3d4b56918a57d08356ce012ed5cc1cec31 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 25 Mar 2025 16:19:46 +0900 Subject: [PATCH 7/7] Make test headless --- .../Online/TestSceneMultiplayerBeatmapAvailabilityTracker.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Tests/Online/TestSceneMultiplayerBeatmapAvailabilityTracker.cs b/osu.Game.Tests/Online/TestSceneMultiplayerBeatmapAvailabilityTracker.cs index 746115400a..41ffd9c9a9 100644 --- a/osu.Game.Tests/Online/TestSceneMultiplayerBeatmapAvailabilityTracker.cs +++ b/osu.Game.Tests/Online/TestSceneMultiplayerBeatmapAvailabilityTracker.cs @@ -8,6 +8,7 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Extensions; using osu.Framework.Platform; +using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Online; using osu.Game.Online.API; @@ -22,6 +23,7 @@ using osu.Game.Utils; namespace osu.Game.Tests.Online { + [HeadlessTest] public partial class TestSceneMultiplayerBeatmapAvailabilityTracker : MultiplayerTestScene { private BeatmapManager beatmapManager = null!;