diff --git a/osu.Game.Tests/Online/TestSceneMultiplayerBeatmapAvailabilityTracker.cs b/osu.Game.Tests/Online/TestSceneMultiplayerBeatmapAvailabilityTracker.cs new file mode 100644 index 0000000000..41ffd9c9a9 --- /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.Framework.Testing; +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.Screens.OnlinePlay.Multiplayer; +using osu.Game.Tests.Resources; +using osu.Game.Tests.Visual.Multiplayer; +using osu.Game.Utils; + +namespace osu.Game.Tests.Online +{ + [HeadlessTest] + public partial 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(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/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 ae3451c3e0..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,31 +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)); } @@ -82,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(); }); @@ -108,9 +97,15 @@ namespace osu.Game.Tests.Online Children = new Drawable[] { beatmapLookupCache, - availabilityTracker = new OnlinePlayBeatmapAvailabilityTracker + availabilityTracker = new PlaylistsBeatmapAvailabilityTracker { - SelectedItem = { BindTarget = selectedItem, } + PlaylistItem = + { + Value = new PlaylistItem(testBeatmapInfo) + { + RulesetID = testBeatmapInfo.Ruleset.OnlineID, + }, + } } } }; @@ -125,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()); @@ -203,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) { } @@ -226,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..a6ce03c129 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,19 +18,33 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("create footer", () => { - Child = new PopoverContainer + MultiplayerBeatmapAvailabilityTracker tracker = new MultiplayerBeatmapAvailabilityTracker(); + + Child = new DependencyProvidingContainer { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, - Child = new Container - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.X, - Height = 50, - Child = new MultiplayerMatchFooter() - } + CachedDependencies = + [ + (typeof(OnlinePlayBeatmapAvailabilityTracker), tracker) + ], + Children = + [ + tracker, + new PopoverContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + 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 ff5436a87d..12bc3c1418 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,34 +54,46 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("create button", () => { - AvailabilityTracker.SelectedItem.Value = room.Playlist.First(); - importedSet = beatmaps.GetAllUsableBeatmapSets().First(); Beatmap.Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First()); - Child = new PopoverContainer + MultiplayerBeatmapAvailabilityTracker tracker = new MultiplayerBeatmapAvailabilityTracker(); + + Child = new DependencyProvidingContainer { RelativeSizeAxes = Axes.Both, - Child = new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Vertical, - Children = new Drawable[] + CachedDependencies = + [ + (typeof(OnlinePlayBeatmapAvailabilityTracker), tracker) + ], + Children = + [ + tracker, + new PopoverContainer { - spectateButton = new MultiplayerSpectateButton + RelativeSizeAxes = Axes.Both, + Child = new FillFlowContainer { - 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) + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + 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 5c8b500c93..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.SelectedItem.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..828a8d85ca --- /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 partial 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 7fddb8d1c4..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.SelectedItem.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..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.SelectedItem.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..71d2d36ee2 --- /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 partial 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/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs b/osu.Game/Screens/OnlinePlay/OnlinePlayBeatmapAvailabilityTracker.cs similarity index 57% rename from osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs rename to osu.Game/Screens/OnlinePlay/OnlinePlayBeatmapAvailabilityTracker.cs index 45f52f3cd8..ae0b4a9943 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. @@ -27,9 +27,17 @@ namespace osu.Game.Online.Rooms /// 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 { - public readonly Bindable SelectedItem = new Bindable(); + /// + /// The current availability of 's beatmap. + /// + public virtual IBindable Availability => availability; // Virtual for mocking in some tests. + + /// + /// The playlist item to track the availability of. + /// + protected 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/PlaylistsBeatmapAvailabilityTracker.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsBeatmapAvailabilityTracker.cs new file mode 100644 index 0000000000..37d3a3f2d4 --- /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 partial 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 ae31e55da5..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.SelectedItem.BindTo(SelectedItem); beatmapAvailabilityTracker.Availability.BindValueChanged(_ => updateGameplayState()); + SelectedItem.BindValueChanged(onSelectedItemChanged); UserBeatmap.BindValueChanged(_ => updateGameplayState()); UserMods.BindValueChanged(_ => updateGameplayState()); UserRuleset.BindValueChanged(_ => 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; diff --git a/osu.Game/Tests/Visual/OnlinePlay/IOnlinePlayTestSceneDependencies.cs b/osu.Game/Tests/Visual/OnlinePlay/IOnlinePlayTestSceneDependencies.cs index 60730ee9a4..0468c18076 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 @@ -17,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 9537c7958c..9d66a44008 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; @@ -18,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; } @@ -35,7 +33,6 @@ namespace osu.Game.Tests.Visual.OnlinePlay { RequestsHandler = new TestRoomRequestsHandler(); OngoingOperationTracker = new OngoingOperationTracker(); - AvailabilityTracker = new OnlinePlayBeatmapAvailabilityTracker(); UserLookupCache = new TestUserLookupCache(); BeatmapLookupCache = new BeatmapLookupCache(); @@ -43,7 +40,6 @@ namespace osu.Game.Tests.Visual.OnlinePlay CacheAs(RequestsHandler); CacheAs(OngoingOperationTracker); - CacheAs(AvailabilityTracker); CacheAs(new OverlayColourProvider(OverlayColourScheme.Plum)); CacheAs(UserLookupCache); CacheAs(BeatmapLookupCache);