From 9eecb23fefa623356228bc916dba930e784dc035 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 10 Jun 2021 21:35:52 +0900 Subject: [PATCH 001/168] Only seek to the end on first frame arrival --- osu.Game/Screens/Play/SpectatorPlayer.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/Play/SpectatorPlayer.cs b/osu.Game/Screens/Play/SpectatorPlayer.cs index 0286b6b8a6..304bc4d7ed 100644 --- a/osu.Game/Screens/Play/SpectatorPlayer.cs +++ b/osu.Game/Screens/Play/SpectatorPlayer.cs @@ -48,7 +48,6 @@ namespace osu.Game.Screens.Play base.StartGameplay(); spectatorClient.OnNewFrames += userSentFrames; - seekToGameplay(); } private void userSentFrames(int userId, FrameDataBundle bundle) From 5073ede115f1c3da87e4c8055b8b39bbde31bdd8 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 10 Jun 2021 21:38:20 +0900 Subject: [PATCH 002/168] Clear existing frames when starting gameplay --- osu.Game/Screens/Play/SpectatorPlayer.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Screens/Play/SpectatorPlayer.cs b/osu.Game/Screens/Play/SpectatorPlayer.cs index 304bc4d7ed..4d3b5eefb1 100644 --- a/osu.Game/Screens/Play/SpectatorPlayer.cs +++ b/osu.Game/Screens/Play/SpectatorPlayer.cs @@ -47,6 +47,8 @@ namespace osu.Game.Screens.Play { base.StartGameplay(); + // Start gameplay along with the very first arrival frame (the latest one). + score.Replay.Frames.Clear(); spectatorClient.OnNewFrames += userSentFrames; } From e18f4cd4bae79bd52801857a6e4deeefff3f35d5 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 10 Jun 2021 21:39:13 +0900 Subject: [PATCH 003/168] Combine methods --- osu.Game/Screens/Play/SpectatorPlayer.cs | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/osu.Game/Screens/Play/SpectatorPlayer.cs b/osu.Game/Screens/Play/SpectatorPlayer.cs index 4d3b5eefb1..f662a479ec 100644 --- a/osu.Game/Screens/Play/SpectatorPlayer.cs +++ b/osu.Game/Screens/Play/SpectatorPlayer.cs @@ -63,6 +63,8 @@ namespace osu.Game.Screens.Play if (!this.IsCurrentScreen()) return; + bool isFirstBundle = score.Replay.Frames.Count == 0; + foreach (var frame in bundle.Frames) { IConvertibleReplayFrame convertibleFrame = GameplayRuleset.CreateConvertibleReplayFrame(); @@ -74,19 +76,8 @@ namespace osu.Game.Screens.Play score.Replay.Frames.Add(convertedFrame); } - seekToGameplay(); - } - - private bool seekedToGameplay; - - private void seekToGameplay() - { - if (seekedToGameplay || score.Replay.Frames.Count == 0) - return; - - NonFrameStableSeek(score.Replay.Frames[0].Time); - - seekedToGameplay = true; + if (isFirstBundle && score.Replay.Frames.Count > 0) + NonFrameStableSeek(score.Replay.Frames[0].Time); } protected override Score CreateScore() => score; From 58d71e4aead45fd8a756d574d904ded7ee5e3e3e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 10 Jun 2021 22:41:38 +0900 Subject: [PATCH 004/168] Remove local "next frame" storage --- .../Visual/Gameplay/TestSceneSpectator.cs | 17 +++---- .../TestSceneMultiSpectatorLeaderboard.cs | 4 +- .../TestSceneMultiSpectatorScreen.cs | 12 +---- .../Visual/Spectator/TestSpectatorClient.cs | 44 ++++++++++++++----- 4 files changed, 42 insertions(+), 35 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs index 6eeb3596a8..0926b8778b 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs @@ -41,8 +41,6 @@ namespace osu.Game.Tests.Visual.Gameplay [Resolved] private OsuGameBase game { get; set; } - private int nextFrame; - private BeatmapSetInfo importedBeatmap; private int importedBeatmapId; @@ -51,8 +49,6 @@ namespace osu.Game.Tests.Visual.Gameplay { base.SetUpSteps(); - AddStep("reset sent frames", () => nextFrame = 0); - AddStep("import beatmap", () => { importedBeatmap = ImportBeatmapTest.LoadOszIntoOsu(game, virtualTrack: true).Result; @@ -105,7 +101,8 @@ namespace osu.Game.Tests.Visual.Gameplay waitForPlayer(); checkPaused(true); - sendFrames(1000); // send enough frames to ensure play won't be paused + // send enough frames to ensure play won't be paused + sendFrames(100); checkPaused(false); } @@ -114,12 +111,12 @@ namespace osu.Game.Tests.Visual.Gameplay public void TestSpectatingDuringGameplay() { start(); + sendFrames(300); loadSpectatingScreen(); waitForPlayer(); - AddStep("advance frame count", () => nextFrame = 300); - sendFrames(); + sendFrames(300); AddUntilStep("playing from correct point in time", () => player.ChildrenOfType().First().FrameStableClock.CurrentTime > 30000); } @@ -220,11 +217,7 @@ namespace osu.Game.Tests.Visual.Gameplay private void sendFrames(int count = 10) { - AddStep("send frames", () => - { - testSpectatorClient.SendFrames(streamingUser.Id, nextFrame, count); - nextFrame += count; - }); + AddStep("send frames", () => testSpectatorClient.SendFrames(streamingUser.Id, count)); } private void loadSpectatingScreen() diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs index 5ad35be0ec..7f8a44fb98 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs @@ -96,10 +96,10 @@ namespace osu.Game.Tests.Visual.Multiplayer // For player 2, send frames in sets of 10. for (int i = 0; i < 100; i++) { - spectatorClient.SendFrames(PLAYER_1_ID, i, 1); + spectatorClient.SendFrames(PLAYER_1_ID, 1); if (i % 10 == 0) - spectatorClient.SendFrames(PLAYER_2_ID, i, 10); + spectatorClient.SendFrames(PLAYER_2_ID, 10); } }); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs index b91391c409..f5032fc2a5 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs @@ -37,7 +37,6 @@ namespace osu.Game.Tests.Visual.Multiplayer private MultiSpectatorScreen spectatorScreen; private readonly List playingUserIds = new List(); - private readonly Dictionary nextFrame = new Dictionary(); private BeatmapSetInfo importedSet; private BeatmapInfo importedBeatmap; @@ -55,8 +54,6 @@ namespace osu.Game.Tests.Visual.Multiplayer { base.SetUpSteps(); - AddStep("reset sent frames", () => nextFrame.Clear()); - AddStep("add streaming client", () => { Remove(spectatorClient); @@ -80,8 +77,6 @@ namespace osu.Game.Tests.Visual.Multiplayer Client.CurrentMatchPlayingUserIds.Add(PLAYER_2_ID); playingUserIds.Add(PLAYER_1_ID); playingUserIds.Add(PLAYER_2_ID); - nextFrame[PLAYER_1_ID] = 0; - nextFrame[PLAYER_2_ID] = 0; }); loadSpectateScreen(false); @@ -253,7 +248,6 @@ namespace osu.Game.Tests.Visual.Multiplayer Client.CurrentMatchPlayingUserIds.Add(id); spectatorClient.StartPlay(id, beatmapId ?? importedBeatmapId); playingUserIds.Add(id); - nextFrame[id] = 0; } }); } @@ -264,7 +258,6 @@ namespace osu.Game.Tests.Visual.Multiplayer { spectatorClient.EndPlay(userId); playingUserIds.Remove(userId); - nextFrame.Remove(userId); }); } @@ -275,10 +268,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("send frames", () => { foreach (int id in userIds) - { - spectatorClient.SendFrames(id, nextFrame[id], count); - nextFrame[id] += count; - } + spectatorClient.SendFrames(id, count); }); } diff --git a/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs b/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs index c7aa43b377..f206d4f8b0 100644 --- a/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs +++ b/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs @@ -20,9 +20,15 @@ namespace osu.Game.Tests.Visual.Spectator { public class TestSpectatorClient : SpectatorClient { + /// + /// Maximum number of frames sent per bundle via . + /// + public const int FRAME_BUNDLE_SIZE = 10; + public override IBindable IsConnected { get; } = new Bindable(true); private readonly Dictionary userBeatmapDictionary = new Dictionary(); + private readonly Dictionary userNextFrameDictionary = new Dictionary(); [Resolved] private IAPIProvider api { get; set; } = null!; @@ -35,6 +41,7 @@ namespace osu.Game.Tests.Visual.Spectator public void StartPlay(int userId, int beatmapId) { userBeatmapDictionary[userId] = beatmapId; + userNextFrameDictionary[userId] = 0; sendPlayingState(userId); } @@ -57,24 +64,41 @@ namespace osu.Game.Tests.Visual.Spectator public new void Schedule(Action action) => base.Schedule(action); /// - /// Sends frames for an arbitrary user. + /// Sends frames for an arbitrary user, in bundles containing 10 frames each. /// /// The user to send frames for. - /// The frame index. - /// The number of frames to send. - public void SendFrames(int userId, int index, int count) + /// The total number of frames to send. + public void SendFrames(int userId, int count) { var frames = new List(); - for (int i = index; i < index + count; i++) - { - var buttonState = i == index + count - 1 ? ReplayButtonState.None : ReplayButtonState.Left1; + int currentFrameIndex = userNextFrameDictionary[userId]; + int lastFrameIndex = currentFrameIndex + count - 1; - frames.Add(new LegacyReplayFrame(i * 100, RNG.Next(0, 512), RNG.Next(0, 512), buttonState)); + for (; currentFrameIndex <= lastFrameIndex; currentFrameIndex++) + { + // This is done in the next frame so that currentFrameIndex is updated to the correct value. + if (frames.Count == FRAME_BUNDLE_SIZE) + flush(); + + var buttonState = currentFrameIndex == lastFrameIndex ? ReplayButtonState.None : ReplayButtonState.Left1; + frames.Add(new LegacyReplayFrame(currentFrameIndex * 100, RNG.Next(0, 512), RNG.Next(0, 512), buttonState)); } - var bundle = new FrameDataBundle(new ScoreInfo { Combo = index + count }, frames); - ((ISpectatorClient)this).UserSentFrames(userId, bundle); + flush(); + + userNextFrameDictionary[userId] = currentFrameIndex; + + void flush() + { + if (frames.Count == 0) + return; + + var bundle = new FrameDataBundle(new ScoreInfo { Combo = currentFrameIndex }, frames.ToArray()); + ((ISpectatorClient)this).UserSentFrames(userId, bundle); + + frames.Clear(); + } } protected override Task BeginPlayingInternal(SpectatorState state) From 169c98f963e3703e6d2166081b43ab3ac3dec66a Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 11 Jun 2021 00:25:19 +0300 Subject: [PATCH 005/168] Add skin providing ruleset resources in `RulesetSkinProvidingContainer` --- osu.Game/Skinning/RulesetResourcesSkin.cs | 51 +++++++++++++++++++ .../Skinning/RulesetSkinProvidingContainer.cs | 10 ++++ 2 files changed, 61 insertions(+) create mode 100644 osu.Game/Skinning/RulesetResourcesSkin.cs diff --git a/osu.Game/Skinning/RulesetResourcesSkin.cs b/osu.Game/Skinning/RulesetResourcesSkin.cs new file mode 100644 index 0000000000..1905a8a899 --- /dev/null +++ b/osu.Game/Skinning/RulesetResourcesSkin.cs @@ -0,0 +1,51 @@ +// 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.Audio; +using osu.Framework.Audio.Sample; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.OpenGL.Textures; +using osu.Framework.Graphics.Textures; +using osu.Framework.IO.Stores; +using osu.Framework.Platform; +using osu.Game.Audio; +using osu.Game.Rulesets; + +namespace osu.Game.Skinning +{ + /// + /// An providing the resources of the ruleset for accessibility during lookups. + /// + public class RulesetResourcesSkin : ISkin + { + private readonly TextureStore rulesetTextures; + private readonly ISampleStore rulesetSamples; + + public RulesetResourcesSkin(Ruleset ruleset, GameHost host, AudioManager audio) + { + IResourceStore rulesetResources = ruleset.CreateResourceStore(); + + rulesetTextures = new TextureStore(host.CreateTextureLoaderStore(new NamespacedResourceStore(rulesetResources, @"Textures"))); + rulesetSamples = audio.GetSampleStore(new NamespacedResourceStore(rulesetResources, @"Samples")); + } + + public Drawable GetDrawableComponent(ISkinComponent component) => null; + + public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => rulesetTextures.Get(componentName, wrapModeS, wrapModeT); + + public ISample GetSample(ISampleInfo sampleInfo) + { + foreach (var lookup in sampleInfo.LookupNames) + { + ISample sample = rulesetSamples.Get(lookup); + if (sample != null) + return sample; + } + + return null; + } + + public IBindable GetConfig(TLookup lookup) => null; + } +} diff --git a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs index 8087043230..3d11b58a01 100644 --- a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs +++ b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs @@ -3,8 +3,10 @@ using JetBrains.Annotations; using osu.Framework.Allocation; +using osu.Framework.Audio; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Platform; using osu.Game.Beatmaps; using osu.Game.Rulesets; @@ -35,6 +37,12 @@ namespace osu.Game.Skinning }; } + [Resolved] + private GameHost host { get; set; } + + [Resolved] + private AudioManager audio { get; set; } + [Resolved] private SkinManager skinManager { get; set; } @@ -60,6 +68,8 @@ namespace osu.Game.Skinning SkinSources.Add(Ruleset.CreateLegacySkinProvider(skinManager.DefaultLegacySkin, Beatmap)); SkinSources.Add(Ruleset.CreateLegacySkinProvider(skinManager.DefaultSkin, Beatmap)); + + SkinSources.Add(new RulesetResourcesSkin(Ruleset, host, audio)); } } } From ca3c45363a8d7c20b6e42c5676f38c7fb97b4515 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 10 Jun 2021 23:59:59 +0300 Subject: [PATCH 006/168] Add test coverage --- .../TestSceneRulesetSkinProvidingContainer.cs | 60 +++++++++++++++++++ .../Testing/TestSceneRulesetDependencies.cs | 2 +- 2 files changed, 61 insertions(+), 1 deletion(-) create mode 100644 osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs diff --git a/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs b/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs new file mode 100644 index 0000000000..41f1465c90 --- /dev/null +++ b/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs @@ -0,0 +1,60 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Audio.Sample; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.OpenGL.Textures; +using osu.Framework.Graphics.Textures; +using osu.Game.Audio; +using osu.Game.Rulesets; +using osu.Game.Skinning; +using osu.Game.Tests.Testing; +using osu.Game.Tests.Visual; + +namespace osu.Game.Tests.Rulesets +{ + public class TestSceneRulesetSkinProvidingContainer : OsuTestScene + { + private RulesetSkinProvidingContainer rulesetSkinProvider; + private SkinRequester requester; + + protected override Ruleset CreateRuleset() => new TestSceneRulesetDependencies.TestRuleset(); + + [SetUp] + public void SetUp() => Schedule(() => + { + Child = rulesetSkinProvider = new RulesetSkinProvidingContainer(Ruleset.Value.CreateInstance(), Beatmap.Value.Beatmap, Beatmap.Value.Skin) + .WithChild(requester = new SkinRequester()); + }); + + [Test] + public void TestRulesetResources() + { + AddAssert("ruleset texture retrieved via skin", () => requester.GetTexture("test-image") != null); + AddAssert("ruleset sample retrieved via skin", () => requester.GetSample(new SampleInfo("test-sample")) != null); + } + + private class SkinRequester : Drawable, ISkin + { + private ISkinSource skin; + + [BackgroundDependencyLoader] + private void load(ISkinSource skin) + { + this.skin = skin; + } + + public Drawable GetDrawableComponent(ISkinComponent component) => skin.GetDrawableComponent(component); + + public Texture GetTexture(string componentName, WrapMode wrapModeS = default, WrapMode wrapModeT = default) => skin.GetTexture(componentName); + + public ISample GetSample(ISampleInfo sampleInfo) => skin.GetSample(sampleInfo); + + public IBindable GetConfig(TLookup lookup) => skin.GetConfig(lookup); + } + } +} diff --git a/osu.Game.Tests/Testing/TestSceneRulesetDependencies.cs b/osu.Game.Tests/Testing/TestSceneRulesetDependencies.cs index 97087e31ab..fb50da32f3 100644 --- a/osu.Game.Tests/Testing/TestSceneRulesetDependencies.cs +++ b/osu.Game.Tests/Testing/TestSceneRulesetDependencies.cs @@ -52,7 +52,7 @@ namespace osu.Game.Tests.Testing Dependencies.Get() != null); } - private class TestRuleset : Ruleset + public class TestRuleset : Ruleset { public override string Description => string.Empty; public override string ShortName => string.Empty; From fe48ce4bd5d80ec5b039a5a88a7fa5df9fcfa749 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 11 Jun 2021 00:58:05 +0300 Subject: [PATCH 007/168] Remove unaccessed field It was a warning... --- .../Rulesets/TestSceneRulesetSkinProvidingContainer.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs b/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs index 41f1465c90..e9f9766725 100644 --- a/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs +++ b/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs @@ -19,7 +19,6 @@ namespace osu.Game.Tests.Rulesets { public class TestSceneRulesetSkinProvidingContainer : OsuTestScene { - private RulesetSkinProvidingContainer rulesetSkinProvider; private SkinRequester requester; protected override Ruleset CreateRuleset() => new TestSceneRulesetDependencies.TestRuleset(); @@ -27,7 +26,7 @@ namespace osu.Game.Tests.Rulesets [SetUp] public void SetUp() => Schedule(() => { - Child = rulesetSkinProvider = new RulesetSkinProvidingContainer(Ruleset.Value.CreateInstance(), Beatmap.Value.Beatmap, Beatmap.Value.Skin) + Child = new RulesetSkinProvidingContainer(Ruleset.Value.CreateInstance(), Beatmap.Value.Beatmap, Beatmap.Value.Skin) .WithChild(requester = new SkinRequester()); }); From 1c67ef7c9190ae8bdfe36cce0ed6d6e5576b040c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 11 Jun 2021 16:23:59 +0900 Subject: [PATCH 008/168] Make catchup clock support seeking --- .../Multiplayer/Spectate/CatchUpSpectatorPlayerClock.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/CatchUpSpectatorPlayerClock.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/CatchUpSpectatorPlayerClock.cs index 9e1a020eca..20d12d62a3 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/CatchUpSpectatorPlayerClock.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/CatchUpSpectatorPlayerClock.cs @@ -34,7 +34,11 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate public void Stop() => IsRunning = false; - public bool Seek(double position) => true; + public bool Seek(double position) + { + CurrentTime = position; + return true; + } public void ResetSpeedAdjustments() { From 75d825c85cb01be1a55e56e8a2adfb1dd745c4db Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 11 Jun 2021 16:24:52 +0900 Subject: [PATCH 009/168] Dont control master clock from sync manager --- .../Multiplayer/Spectate/CatchUpSyncManager.cs | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/CatchUpSyncManager.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/CatchUpSyncManager.cs index efc12eaaa5..2d0e88238d 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/CatchUpSyncManager.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/CatchUpSyncManager.cs @@ -63,7 +63,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate } updateCatchup(); - updateMasterClock(); } /// @@ -133,21 +132,5 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate } } } - - /// - /// Updates the master clock's running state. - /// - private void updateMasterClock() - { - bool anyInSync = playerClocks.Any(s => !s.IsCatchingUp); - - if (MasterClock.IsRunning != anyInSync) - { - if (anyInSync) - MasterClock.Start(); - else - MasterClock.Stop(); - } - } } } From a99cb79738f61d4cacbbda13c9b87283fba04733 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 11 Jun 2021 16:25:45 +0900 Subject: [PATCH 010/168] Seek master clock on multi-spectator start --- .../Multiplayer/Spectate/CatchUpSyncManager.cs | 13 +++++++++++-- .../Multiplayer/Spectate/ISyncManager.cs | 6 ++++++ .../Multiplayer/Spectate/MultiSpectatorScreen.cs | 14 ++++++++++++++ 3 files changed, 31 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/CatchUpSyncManager.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/CatchUpSyncManager.cs index 2d0e88238d..0f7466e44b 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/CatchUpSyncManager.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/CatchUpSyncManager.cs @@ -1,6 +1,7 @@ // 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 osu.Framework.Graphics; @@ -33,6 +34,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate /// public IAdjustableClock MasterClock { get; } + public event Action ReadyToStart; + /// /// The player clocks. /// @@ -80,14 +83,20 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate int readyCount = playerClocks.Count(s => !s.WaitingOnFrames.Value); if (readyCount == playerClocks.Count) - return hasStarted = true; + return performStart(); if (readyCount > 0) { firstStartAttemptTime ??= Time.Current; if (Time.Current - firstStartAttemptTime > MAXIMUM_START_DELAY) - return hasStarted = true; + return performStart(); + } + + bool performStart() + { + ReadyToStart?.Invoke(); + return hasStarted = true; } return false; diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/ISyncManager.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/ISyncManager.cs index bd698108f6..f59c1a75f7 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/ISyncManager.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/ISyncManager.cs @@ -1,6 +1,7 @@ // 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 osu.Framework.Timing; namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate @@ -15,6 +16,11 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate /// IAdjustableClock MasterClock { get; } + /// + /// An event which is invoked when gameplay is ready to start. + /// + event Action ReadyToStart; + /// /// Adds an to manage. /// diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs index 983daac909..e401c45933 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs @@ -101,6 +101,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, }, leaderboardContainer.Add); + + syncManager.ReadyToStart += onReadyToStart; } protected override void LoadComplete() @@ -129,6 +131,18 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate private bool isCandidateAudioSource([CanBeNull] ISpectatorPlayerClock clock) => clock?.IsRunning == true && !clock.IsCatchingUp && !clock.WaitingOnFrames.Value; + private void onReadyToStart() + { + var startTime = instances.Where(i => i.Score != null) + .SelectMany(i => i.Score.Replay.Frames) + .Select(f => f.Time) + .DefaultIfEmpty(0) + .Max(); + + masterClockContainer.Seek(startTime); + masterClockContainer.Start(); + } + protected override void OnUserStateChanged(int userId, SpectatorState spectatorState) { } From 9eaaac6bb790616db3dcb259669e2b5d48b74b39 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 11 Jun 2021 17:59:27 +0900 Subject: [PATCH 011/168] Remove master clock state assertions --- .../OnlinePlay/TestSceneCatchUpSyncManager.cs | 24 +++++++------------ 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/osu.Game.Tests/OnlinePlay/TestSceneCatchUpSyncManager.cs b/osu.Game.Tests/OnlinePlay/TestSceneCatchUpSyncManager.cs index d4e591cf09..6851df3832 100644 --- a/osu.Game.Tests/OnlinePlay/TestSceneCatchUpSyncManager.cs +++ b/osu.Game.Tests/OnlinePlay/TestSceneCatchUpSyncManager.cs @@ -31,32 +31,24 @@ namespace osu.Game.Tests.OnlinePlay } [Test] - public void TestMasterClockStartsWhenAllPlayerClocksHaveFrames() + public void TestPlayerClocksStartWhenAllHaveFrames() { setWaiting(() => player1, false); - assertMasterState(false); assertPlayerClockState(() => player1, false); assertPlayerClockState(() => player2, false); setWaiting(() => player2, false); - assertMasterState(true); assertPlayerClockState(() => player1, true); assertPlayerClockState(() => player2, true); } [Test] - public void TestMasterClockDoesNotStartWhenNoneReadyForMaximumDelayTime() - { - AddWaitStep($"wait {CatchUpSyncManager.MAXIMUM_START_DELAY} milliseconds", (int)Math.Ceiling(CatchUpSyncManager.MAXIMUM_START_DELAY / TimePerAction)); - assertMasterState(false); - } - - [Test] - public void TestMasterClockStartsWhenAnyReadyForMaximumDelayTime() + public void TestReadyPlayersStartWhenReadyForMaximumDelayTime() { setWaiting(() => player1, false); AddWaitStep($"wait {CatchUpSyncManager.MAXIMUM_START_DELAY} milliseconds", (int)Math.Ceiling(CatchUpSyncManager.MAXIMUM_START_DELAY / TimePerAction)); - assertMasterState(true); + assertPlayerClockState(() => player1, true); + assertPlayerClockState(() => player2, false); } [Test] @@ -153,9 +145,6 @@ namespace osu.Game.Tests.OnlinePlay private void setPlayerClockTime(Func playerClock, double offsetFromMaster) => AddStep($"set player clock {playerClock().Id} = master - {offsetFromMaster}", () => playerClock().Seek(master.CurrentTime - offsetFromMaster)); - private void assertMasterState(bool running) - => AddAssert($"master clock {(running ? "is" : "is not")} running", () => master.IsRunning == running); - private void assertCatchingUp(Func playerClock, bool catchingUp) => AddAssert($"player clock {playerClock().Id} {(catchingUp ? "is" : "is not")} catching up", () => playerClock().IsCatchingUp == catchingUp); @@ -201,6 +190,11 @@ namespace osu.Game.Tests.OnlinePlay private class TestManualClock : ManualClock, IAdjustableClock { + public TestManualClock() + { + IsRunning = true; + } + public void Start() => IsRunning = true; public void Stop() => IsRunning = false; From e9ebbd298d4c7129123e40c1f8b8352abfd9ed96 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 11 Jun 2021 18:13:54 +0900 Subject: [PATCH 012/168] Add a few more tests --- .../TestSceneMultiSpectatorScreen.cs | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs index f5032fc2a5..35d096a92e 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs @@ -12,6 +12,7 @@ using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Database; using osu.Game.Online.Spectator; +using osu.Game.Rulesets.UI; using osu.Game.Screens.OnlinePlay.Multiplayer.Spectate; using osu.Game.Screens.Play; using osu.Game.Tests.Beatmaps.IO; @@ -224,6 +225,36 @@ namespace osu.Game.Tests.Visual.Multiplayer assertMuted(PLAYER_2_ID, true); } + [Test] + public void TestSpectatingDuringGameplay() + { + var players = new[] { PLAYER_1_ID, PLAYER_2_ID }; + + start(players); + sendFrames(players, 300); + + loadSpectateScreen(); + sendFrames(players, 300); + + AddUntilStep("playing from correct point in time", () => this.ChildrenOfType().All(r => r.FrameStableClock.CurrentTime > 30000)); + } + + [Test] + public void TestSpectatingDuringGameplayWithLateFrames() + { + start(new[] { PLAYER_1_ID, PLAYER_2_ID }); + sendFrames(new[] { PLAYER_1_ID, PLAYER_2_ID }, 300); + + loadSpectateScreen(); + sendFrames(PLAYER_1_ID, 300); + + AddWaitStep("wait maximum start delay seconds", (int)(CatchUpSyncManager.MAXIMUM_START_DELAY / TimePerAction)); + checkPaused(PLAYER_1_ID, false); + + sendFrames(PLAYER_2_ID, 300); + AddUntilStep("player 2 playing from correct point in time", () => getPlayer(PLAYER_2_ID).ChildrenOfType().Single().FrameStableClock.CurrentTime > 30000); + } + private void loadSpectateScreen(bool waitForPlayerLoad = true) { AddStep("load screen", () => From 263b8ff097ed688e3fe0ff6e9bb90d1a17c93057 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 11 Jun 2021 18:14:37 +0900 Subject: [PATCH 013/168] Wait for full player load --- osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs index fe79e5db72..95ccc08608 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs @@ -27,7 +27,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate /// /// Whether a is loaded in the area. /// - public bool PlayerLoaded => stack?.CurrentScreen is Player; + public bool PlayerLoaded => (stack?.CurrentScreen as Player)?.IsLoaded == true; /// /// The user id this corresponds to. From 59eda70c12204f0ed71a551d711d1bb7aa1adbb3 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 11 Jun 2021 18:39:50 +0900 Subject: [PATCH 014/168] Seek to the least most-recent frame instead --- .../OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs index e401c45933..1ea63e454a 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs @@ -133,11 +133,13 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate private void onReadyToStart() { + // Seek the master clock to the gameplay time. + // This is chosen as the first available frame in the players' replays, which matches the seek by each individual SpectatorPlayer. var startTime = instances.Where(i => i.Score != null) .SelectMany(i => i.Score.Replay.Frames) .Select(f => f.Time) .DefaultIfEmpty(0) - .Max(); + .Min(); masterClockContainer.Seek(startTime); masterClockContainer.Start(); From 0a8daab4f78e369481e4bff7c382e2bd845386fd Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 11 Jun 2021 19:15:53 +0900 Subject: [PATCH 015/168] Pause master clock when too far ahead --- .../TestSceneMultiSpectatorScreen.cs | 17 +++++++++++++++ .../Spectate/CatchUpSyncManager.cs | 21 ++++++++++++++++--- .../Multiplayer/Spectate/ISyncManager.cs | 10 +++++++-- .../Multiplayer/Spectate/MasterClockState.cs | 18 ++++++++++++++++ .../Spectate/MultiSpectatorScreen.cs | 10 +++++++++ 5 files changed, 71 insertions(+), 5 deletions(-) create mode 100644 osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MasterClockState.cs diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs index 35d096a92e..18eb7e1c1e 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs @@ -103,6 +103,23 @@ namespace osu.Game.Tests.Visual.Multiplayer AddWaitStep("wait a bit", 20); } + [Test] + public void TestTimeDoesNotProgressWhileAllPlayersPaused() + { + start(new[] { PLAYER_1_ID, PLAYER_2_ID }); + loadSpectateScreen(); + + sendFrames(PLAYER_1_ID, 20); + sendFrames(PLAYER_2_ID, 10); + + checkPaused(PLAYER_2_ID, true); + checkPausedInstant(PLAYER_1_ID, false); + AddAssert("master clock still running", () => this.ChildrenOfType().Single().IsRunning); + + checkPaused(PLAYER_1_ID, true); + AddUntilStep("master clock paused", () => !this.ChildrenOfType().Single().IsRunning); + } + [Test] public void TestPlayersMustStartSimultaneously() { diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/CatchUpSyncManager.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/CatchUpSyncManager.cs index 0f7466e44b..781123f5bb 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/CatchUpSyncManager.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/CatchUpSyncManager.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Timing; @@ -29,18 +30,22 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate /// public const double MAXIMUM_START_DELAY = 15000; + public event Action ReadyToStart; + /// /// The master clock which is used to control the timing of all player clocks clocks. /// public IAdjustableClock MasterClock { get; } - public event Action ReadyToStart; + public IBindable MasterState => masterState; /// /// The player clocks. /// private readonly List playerClocks = new List(); + private readonly Bindable masterState = new Bindable(); + private bool hasStarted; private double? firstStartAttemptTime; @@ -65,7 +70,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate return; } - updateCatchup(); + updatePlayerCatchup(); + updateMasterState(); } /// @@ -105,7 +111,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate /// /// Updates the catchup states of all player clocks clocks. /// - private void updateCatchup() + private void updatePlayerCatchup() { for (int i = 0; i < playerClocks.Count; i++) { @@ -141,5 +147,14 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate } } } + + /// + /// Updates the state of the master clock. + /// + private void updateMasterState() + { + bool anyInSync = playerClocks.Any(s => !s.IsCatchingUp); + masterState.Value = anyInSync ? MasterClockState.Synchronised : MasterClockState.TooFarAhead; + } } } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/ISyncManager.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/ISyncManager.cs index f59c1a75f7..3c644ccb78 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/ISyncManager.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/ISyncManager.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using osu.Framework.Bindables; using osu.Framework.Timing; namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate @@ -11,15 +12,20 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate /// public interface ISyncManager { + /// + /// An event which is invoked when gameplay is ready to start. + /// + event Action ReadyToStart; + /// /// The master clock which player clocks should synchronise to. /// IAdjustableClock MasterClock { get; } /// - /// An event which is invoked when gameplay is ready to start. + /// An event which is invoked when the state of is changed. /// - event Action ReadyToStart; + IBindable MasterState { get; } /// /// Adds an to manage. diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MasterClockState.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MasterClockState.cs new file mode 100644 index 0000000000..8982d1669d --- /dev/null +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MasterClockState.cs @@ -0,0 +1,18 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate +{ + public enum MasterClockState + { + /// + /// The master clock is synchronised with at least one player clock. + /// + Synchronised, + + /// + /// The master clock is too far ahead of any player clock and needs to slow down. + /// + TooFarAhead + } +} diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs index 1ea63e454a..ee1968aa5f 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs @@ -5,6 +5,7 @@ using System; using System.Linq; using JetBrains.Annotations; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Online.Multiplayer; @@ -103,6 +104,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate }, leaderboardContainer.Add); syncManager.ReadyToStart += onReadyToStart; + syncManager.MasterState.BindValueChanged(onMasterStateChanged, true); } protected override void LoadComplete() @@ -145,6 +147,14 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate masterClockContainer.Start(); } + private void onMasterStateChanged(ValueChangedEvent state) + { + if (state.NewValue == MasterClockState.Synchronised) + masterClockContainer.Start(); + else + masterClockContainer.Stop(); + } + protected override void OnUserStateChanged(int userId, SpectatorState spectatorState) { } From 9f163f7f20ca155ab331d63d2fae99fdb1c2591d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 11 Jun 2021 19:23:25 +0900 Subject: [PATCH 016/168] Use switch statement to be more explicit about state --- .../Multiplayer/Spectate/MultiSpectatorScreen.cs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs index ee1968aa5f..013e5551cf 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs @@ -149,10 +149,16 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate private void onMasterStateChanged(ValueChangedEvent state) { - if (state.NewValue == MasterClockState.Synchronised) - masterClockContainer.Start(); - else - masterClockContainer.Stop(); + switch (state.NewValue) + { + case MasterClockState.Synchronised: + masterClockContainer.Start(); + break; + + case MasterClockState.TooFarAhead: + masterClockContainer.Stop(); + break; + } } protected override void OnUserStateChanged(int userId, SpectatorState spectatorState) From 27da3dc75a408b44ddf4dc1a38f4f0a9e55b8a57 Mon Sep 17 00:00:00 2001 From: JimmyC7834 Date: Sat, 19 Jun 2021 20:54:24 +0800 Subject: [PATCH 017/168] added supporter-only-filter content --- .../BeatmapListingFilterControl.cs | 10 +- osu.Game/Overlays/BeatmapListingOverlay.cs | 114 +++++++++++++++++- 2 files changed, 122 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs index 1935a250b7..3436a1b3b2 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs @@ -24,6 +24,7 @@ namespace osu.Game.Overlays.BeatmapListing { /// /// Fired when a search finishes. Contains only new items in the case of pagination. + /// Null when non-supporter user used supporter-only filters /// public Action> SearchFinished; @@ -212,7 +213,14 @@ namespace osu.Game.Overlays.BeatmapListing lastResponse = response; getSetsRequest = null; - SearchFinished?.Invoke(sets); + if (!api.LocalUser.Value.IsSupporter && (searchControl.Ranks.Any() || searchControl.Played.Value != SearchPlayed.Any)) + { + SearchFinished?.Invoke(null); + } + else + { + SearchFinished?.Invoke(sets); + } }; api.Queue(getSetsRequest); diff --git a/osu.Game/Overlays/BeatmapListingOverlay.cs b/osu.Game/Overlays/BeatmapListingOverlay.cs index 5e65cd9488..63b9d3d34a 100644 --- a/osu.Game/Overlays/BeatmapListingOverlay.cs +++ b/osu.Game/Overlays/BeatmapListingOverlay.cs @@ -15,7 +15,9 @@ using osu.Framework.Graphics.Textures; using osu.Framework.Input.Events; using osu.Game.Audio; using osu.Game.Beatmaps; +using osu.Game.Graphics; using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.Containers; using osu.Game.Overlays.BeatmapListing; using osu.Game.Overlays.BeatmapListing.Panels; using osu.Game.Resources.Localisation.Web; @@ -33,6 +35,7 @@ namespace osu.Game.Overlays private Container panelTarget; private FillFlowContainer foundContent; private NotFoundDrawable notFoundContent; + private SupporterRequiredDrawable supporterRequiredContent; private BeatmapListingFilterControl filterControl; public BeatmapListingOverlay() @@ -76,6 +79,7 @@ namespace osu.Game.Overlays { foundContent = new FillFlowContainer(), notFoundContent = new NotFoundDrawable(), + supporterRequiredContent = new SupporterRequiredDrawable(), } } }, @@ -117,6 +121,13 @@ namespace osu.Game.Overlays private void onSearchFinished(List beatmaps) { + // non-supporter user used supporter-only filters + if (beatmaps == null) + { + LoadComponentAsync(supporterRequiredContent, addContentToPlaceholder, (cancellationToken = new CancellationTokenSource()).Token); + return; + } + var newPanels = beatmaps.Select(b => new GridBeatmapPanel(b) { Anchor = Anchor.TopCentre, @@ -170,7 +181,7 @@ namespace osu.Game.Overlays { var transform = lastContent.FadeOut(100, Easing.OutQuint); - if (lastContent == notFoundContent) + if (lastContent == notFoundContent || lastContent == supporterRequiredContent) { // not found display may be used multiple times, so don't expire/dispose it. transform.Schedule(() => panelTarget.Remove(lastContent)); @@ -240,6 +251,107 @@ namespace osu.Game.Overlays } } + public class SupporterRequiredDrawable : CompositeDrawable + { + public SupporterRequiredDrawable() + { + RelativeSizeAxes = Axes.X; + Height = 250; + Alpha = 0; + Margin = new MarginPadding { Top = 15 }; + } + + [BackgroundDependencyLoader] + private void load(TextureStore textures) + { + AddInternal(new FillFlowContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Y, + AutoSizeAxes = Axes.X, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(10, 0), + Children = new Drawable[] + { + new Sprite + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + FillMode = FillMode.Fit, + Texture = textures.Get(@"Online/supporter-required"), + }, + createSupportRequiredText(), + } + }); + } + + private Drawable createSupportRequiredText() + { + LinkFlowContainer linkFlowContainer; + string[] text = BeatmapsStrings.ListingSearchSupporterFilterQuoteDefault( + BeatmapsStrings.ListingSearchFiltersRank.ToString(), + "{1}" + ).ToString().Split("{1}"); + + // var titleContainer = new Container + // { + // RelativeSizeAxes = Axes.X, + // Margin = new MarginPadding { Vertical = 5 }, + // Children = new Drawable[] + // { + // linkFlowContainer = new LinkFlowContainer + // { + // Anchor = Anchor.Centre, + // Origin = Anchor.Centre, + // } + // } + // }; + + linkFlowContainer = new LinkFlowContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.Both, + Margin = new MarginPadding + { + Bottom = 10, + } + }; + + linkFlowContainer.AddText( + text[0], + t => + { + t.Font = OsuFont.GetFont(size: 16); + t.Colour = Colour4.White; + } + ); + + linkFlowContainer.AddLink( + BeatmapsStrings.ListingSearchSupporterFilterQuoteLinkText.ToString(), + "https://osu.ppy.sh/store/products/supporter-tag", + t => + { + t.Font = OsuFont.GetFont(size: 16); + t.Colour = Colour4.AliceBlue; + } + ); + + linkFlowContainer.AddText( + text[1], + t => + { + t.Font = OsuFont.GetFont(size: 16); + t.Colour = Colour4.White; + } + ); + + return linkFlowContainer; + } + } + private const double time_between_fetches = 500; private double lastFetchDisplayedTime; From 42fdfbb9a1d2504da309c96ca23cdee4829819b2 Mon Sep 17 00:00:00 2001 From: JimmyC7834 Date: Sun, 20 Jun 2021 17:17:07 +0800 Subject: [PATCH 018/168] added visual tests --- .../Online/TestSceneBeatmapListingOverlay.cs | 74 +++++++++++++++++++ osu.Game/Overlays/BeatmapListingOverlay.cs | 32 +++----- 2 files changed, 83 insertions(+), 23 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs index 156d6b744e..86008ce173 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs @@ -14,6 +14,7 @@ using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays; using osu.Game.Overlays.BeatmapListing; using osu.Game.Rulesets; +using osu.Game.Users; namespace osu.Game.Tests.Visual.Online { @@ -58,6 +59,79 @@ namespace osu.Game.Tests.Visual.Online AddUntilStep("placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); } + [Test] + public void TestSupporterOnlyFiltersPlaceholder() { + + AddStep("toggle non-supporter", () => + { + // non-supporter user + ((DummyAPIAccess)API).LocalUser.Value = new User + { + Username = API.LocalUser.Value.Username, + Id = API.LocalUser.Value.Id + 1, + IsSupporter = false, + }; + }); + AddStep("fetch for 1 beatmap", () => fetchFor(CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet)); + + AddStep("toggle Random Rank Achieved filter", () => { + overlay.ChildrenOfType().Single().Ranks.Clear(); + Scoring.ScoreRank r = (Scoring.ScoreRank)(TestContext.CurrentContext.Random.NextShort() % 8); + overlay.ChildrenOfType().Single().Ranks.Add(r); + // overlay.ChildrenOfType().Single().Ranks. + }); + + AddUntilStep("supporter-placeholder show", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); + + AddStep("Clear Rank Achieved filter", () => { + overlay.ChildrenOfType().Single().Ranks.Clear(); + }); + AddUntilStep("supporter-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + + AddStep("toggle Random Played filter", () => { + SearchPlayed r = (SearchPlayed)(TestContext.CurrentContext.Random.NextShort() % 2 + 1); + overlay.ChildrenOfType().Single().Played.Value = r; + }); + + AddUntilStep("supporter-placeholder show", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); + + AddStep("Clear Played filter", () => { + overlay.ChildrenOfType().Single().Played.Value = SearchPlayed.Any; + }); + AddUntilStep("supporter-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + + AddStep("toggle supporter", () => + { + // supporter user + ((DummyAPIAccess)API).LocalUser.Value.IsSupporter = true; + }); + + AddStep("toggle Random Rank Achieved filter", () => { + overlay.ChildrenOfType().Single().Ranks.Clear(); + Scoring.ScoreRank r = (Scoring.ScoreRank)(TestContext.CurrentContext.Random.NextShort() % 8); + overlay.ChildrenOfType().Single().Ranks.Add(r); + }); + + AddUntilStep("supporter-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + + AddStep("Clear Rank Achieved filter", () => { + overlay.ChildrenOfType().Single().Ranks.Clear(); + }); + AddUntilStep("supporter-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + + AddStep("toggle Random Played filter", () => { + SearchPlayed r = (SearchPlayed)(TestContext.CurrentContext.Random.NextShort() % 2 + 1); + overlay.ChildrenOfType().Single().Played.Value = r; + }); + + AddUntilStep("supporter-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + + AddStep("Clear Played filter", () => { + overlay.ChildrenOfType().Single().Played.Value = SearchPlayed.Any; + }); + AddUntilStep("supporter-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + } + private void fetchFor(params BeatmapSetInfo[] beatmaps) { setsForResponse.Clear(); diff --git a/osu.Game/Overlays/BeatmapListingOverlay.cs b/osu.Game/Overlays/BeatmapListingOverlay.cs index 63b9d3d34a..3dec6de03a 100644 --- a/osu.Game/Overlays/BeatmapListingOverlay.cs +++ b/osu.Game/Overlays/BeatmapListingOverlay.cs @@ -181,11 +181,16 @@ namespace osu.Game.Overlays { var transform = lastContent.FadeOut(100, Easing.OutQuint); - if (lastContent == notFoundContent || lastContent == supporterRequiredContent) + if (lastContent == notFoundContent) { // not found display may be used multiple times, so don't expire/dispose it. transform.Schedule(() => panelTarget.Remove(lastContent)); } + else if (lastContent == supporterRequiredContent) + { + // supporter required display may be used multiple times, so don't expire/dispose it. + transform.Schedule(() => panelTarget.Remove(supporterRequiredContent)); + } else { // Consider the case when the new content is smaller than the last content. @@ -256,9 +261,8 @@ namespace osu.Game.Overlays public SupporterRequiredDrawable() { RelativeSizeAxes = Axes.X; - Height = 250; + Height = 225; Alpha = 0; - Margin = new MarginPadding { Top = 15 }; } [BackgroundDependencyLoader] @@ -271,7 +275,6 @@ namespace osu.Game.Overlays RelativeSizeAxes = Axes.Y, AutoSizeAxes = Axes.X, Direction = FillDirection.Horizontal, - Spacing = new Vector2(10, 0), Children = new Drawable[] { new Sprite @@ -290,24 +293,7 @@ namespace osu.Game.Overlays private Drawable createSupportRequiredText() { LinkFlowContainer linkFlowContainer; - string[] text = BeatmapsStrings.ListingSearchSupporterFilterQuoteDefault( - BeatmapsStrings.ListingSearchFiltersRank.ToString(), - "{1}" - ).ToString().Split("{1}"); - - // var titleContainer = new Container - // { - // RelativeSizeAxes = Axes.X, - // Margin = new MarginPadding { Vertical = 5 }, - // Children = new Drawable[] - // { - // linkFlowContainer = new LinkFlowContainer - // { - // Anchor = Anchor.Centre, - // Origin = Anchor.Centre, - // } - // } - // }; + string[] text = BeatmapsStrings.ListingSearchSupporterFilterQuoteDefault(BeatmapsStrings.ListingSearchFiltersRank.ToString(), "{1}").ToString().Split("{1}"); linkFlowContainer = new LinkFlowContainer { @@ -335,7 +321,7 @@ namespace osu.Game.Overlays t => { t.Font = OsuFont.GetFont(size: 16); - t.Colour = Colour4.AliceBlue; + t.Colour = Colour4.FromHex("#A6C8D9"); } ); From e7aeba8d039cd8b3dfcaad98ed440d1cb4e9f620 Mon Sep 17 00:00:00 2001 From: JimmyC7834 Date: Sun, 20 Jun 2021 18:28:43 +0800 Subject: [PATCH 019/168] added more visual tests --- .../Online/TestSceneBeatmapListingOverlay.cs | 153 +++++++++++------- .../BeatmapListingFilterControl.cs | 1 + 2 files changed, 99 insertions(+), 55 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs index 86008ce173..eebaea545a 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs @@ -40,6 +40,16 @@ namespace osu.Game.Tests.Visual.Online return true; }; + + AddStep("initialize dummy", () => + { + // non-supporter user + ((DummyAPIAccess)API).LocalUser.Value = new User + { + Username = "TestBot", + Id = API.LocalUser.Value.Id + 1, + }; + }); } [Test] @@ -60,78 +70,95 @@ namespace osu.Game.Tests.Visual.Online } [Test] - public void TestSupporterOnlyFiltersPlaceholder() { + public void TestSupporterOnlyFiltersPlaceholderNoBeatmaps() + { + AddStep("set dummy as non-supporter", () => ((DummyAPIAccess)API).LocalUser.Value.IsSupporter = false); - AddStep("toggle non-supporter", () => - { - // non-supporter user - ((DummyAPIAccess)API).LocalUser.Value = new User - { - Username = API.LocalUser.Value.Username, - Id = API.LocalUser.Value.Id + 1, - IsSupporter = false, - }; - }); - AddStep("fetch for 1 beatmap", () => fetchFor(CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet)); + // test non-supporter on Rank Achieved filter + toggleRandomRankFilter(); + AddUntilStep("supporter-placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); + AddUntilStep("not-found-placeholder hidden", () => !overlay.ChildrenOfType().Any()); - AddStep("toggle Random Rank Achieved filter", () => { - overlay.ChildrenOfType().Single().Ranks.Clear(); - Scoring.ScoreRank r = (Scoring.ScoreRank)(TestContext.CurrentContext.Random.NextShort() % 8); - overlay.ChildrenOfType().Single().Ranks.Add(r); - // overlay.ChildrenOfType().Single().Ranks. - }); - - AddUntilStep("supporter-placeholder show", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); - - AddStep("Clear Rank Achieved filter", () => { - overlay.ChildrenOfType().Single().Ranks.Clear(); - }); + AddStep("Clear Rank Achieved filter", () => overlay.ChildrenOfType().Single().Ranks.Clear()); AddUntilStep("supporter-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + AddUntilStep("not-found-placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); - AddStep("toggle Random Played filter", () => { - SearchPlayed r = (SearchPlayed)(TestContext.CurrentContext.Random.NextShort() % 2 + 1); - overlay.ChildrenOfType().Single().Played.Value = r; - }); + // test non-supporter on Played filter + toggleRandomSupporterOnlyPlayedFilter(); + AddUntilStep("supporter-placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); + AddUntilStep("not-found-placeholder hidden", () => !overlay.ChildrenOfType().Any()); - AddUntilStep("supporter-placeholder show", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); - - AddStep("Clear Played filter", () => { - overlay.ChildrenOfType().Single().Played.Value = SearchPlayed.Any; - }); + AddStep("Set Played filter to Any", () => overlay.ChildrenOfType().Single().Played.Value = SearchPlayed.Any); AddUntilStep("supporter-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + AddUntilStep("not-found-placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); - AddStep("toggle supporter", () => - { - // supporter user - ((DummyAPIAccess)API).LocalUser.Value.IsSupporter = true; - }); - - AddStep("toggle Random Rank Achieved filter", () => { - overlay.ChildrenOfType().Single().Ranks.Clear(); - Scoring.ScoreRank r = (Scoring.ScoreRank)(TestContext.CurrentContext.Random.NextShort() % 8); - overlay.ChildrenOfType().Single().Ranks.Add(r); - }); + AddStep("set dummy as supporter", () => ((DummyAPIAccess)API).LocalUser.Value.IsSupporter = true); + // test supporter on Rank Achieved filter + toggleRandomRankFilter(); AddUntilStep("supporter-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + AddUntilStep("not-found-placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); - AddStep("Clear Rank Achieved filter", () => { - overlay.ChildrenOfType().Single().Ranks.Clear(); - }); + AddStep("Clear Rank Achieved filter", () => overlay.ChildrenOfType().Single().Ranks.Clear()); AddUntilStep("supporter-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + AddUntilStep("not-found-placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); - AddStep("toggle Random Played filter", () => { - SearchPlayed r = (SearchPlayed)(TestContext.CurrentContext.Random.NextShort() % 2 + 1); - overlay.ChildrenOfType().Single().Played.Value = r; - }); - + // test supporter on Played filter + toggleRandomSupporterOnlyPlayedFilter(); AddUntilStep("supporter-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + AddUntilStep("not-found-placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); - AddStep("Clear Played filter", () => { - overlay.ChildrenOfType().Single().Played.Value = SearchPlayed.Any; - }); + AddStep("Set Played filter to Any", () => overlay.ChildrenOfType().Single().Played.Value = SearchPlayed.Any); AddUntilStep("supporter-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + AddUntilStep("not-found-placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); } + [Test] + public void TestSupporterOnlyFiltersPlaceholderOneBeatmap() + { + AddStep("fetch for 1 beatmap", () => fetchFor(CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet)); + AddStep("set dummy as non-supporter", () => ((DummyAPIAccess)API).LocalUser.Value.IsSupporter = false); + + // test non-supporter on Rank Achieved filter + toggleRandomRankFilter(); + AddUntilStep("supporter-placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); + AddUntilStep("not-found-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + + AddStep("Clear Rank Achieved filter", () => overlay.ChildrenOfType().Single().Ranks.Clear()); + AddUntilStep("supporter-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + AddUntilStep("not-found-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + + // test non-supporter on Played filter + toggleRandomSupporterOnlyPlayedFilter(); + AddUntilStep("supporter-placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); + AddUntilStep("not-found-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + + AddStep("Set Played filter to Any", () => overlay.ChildrenOfType().Single().Played.Value = SearchPlayed.Any); + AddUntilStep("supporter-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + AddUntilStep("not-found-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + + AddStep("set dummy as supporter", () => ((DummyAPIAccess)API).LocalUser.Value.IsSupporter = true); + + // test supporter on Rank Achieved filter + toggleRandomRankFilter(); + AddUntilStep("supporter-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + AddUntilStep("not-found-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + + AddStep("Clear Rank Achieved filter", () => overlay.ChildrenOfType().Single().Ranks.Clear()); + AddUntilStep("supporter-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + AddUntilStep("not-found-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + + // test supporter on Played filter + toggleRandomSupporterOnlyPlayedFilter(); + AddUntilStep("supporter-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + AddUntilStep("not-found-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + + AddStep("Set Played filter to Any", () => overlay.ChildrenOfType().Single().Played.Value = SearchPlayed.Any); + AddUntilStep("supporter-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + AddUntilStep("not-found-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + } + + private void fetchFor(params BeatmapSetInfo[] beatmaps) { setsForResponse.Clear(); @@ -141,6 +168,22 @@ namespace osu.Game.Tests.Visual.Online overlay.ChildrenOfType().Single().Query.TriggerChange(); } + private void toggleRandomRankFilter() + { + short r = TestContext.CurrentContext.Random.NextShort(); + AddStep("toggle Random Rank Achieved filter", () => + { + overlay.ChildrenOfType().Single().Ranks.Clear(); + overlay.ChildrenOfType().Single().Ranks.Add((Scoring.ScoreRank)(r % 8)); + }); + } + + private void toggleRandomSupporterOnlyPlayedFilter() + { + short r = TestContext.CurrentContext.Random.NextShort(); + AddStep("toggle Random Played filter", () => overlay.ChildrenOfType().Single().Played.Value = (SearchPlayed)(r % 2 + 1)); + } + private class TestAPIBeatmapSet : APIBeatmapSet { private readonly BeatmapSetInfo beatmapSet; diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs index 3436a1b3b2..7b7f742b73 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs @@ -213,6 +213,7 @@ namespace osu.Game.Overlays.BeatmapListing lastResponse = response; getSetsRequest = null; + // check if an non-supporter user used supporter-only filters if (!api.LocalUser.Value.IsSupporter && (searchControl.Ranks.Any() || searchControl.Played.Value != SearchPlayed.Any)) { SearchFinished?.Invoke(null); From 996503eb2d350aeae8252e9504541c82300da234 Mon Sep 17 00:00:00 2001 From: JimmyC7834 Date: Sun, 20 Jun 2021 21:23:54 +0800 Subject: [PATCH 020/168] fixed filter text display, added visual tests --- .../Online/TestSceneBeatmapListingOverlay.cs | 106 ++++++++++++------ .../BeatmapListingFilterControl.cs | 8 +- osu.Game/Overlays/BeatmapListingOverlay.cs | 45 ++++---- 3 files changed, 103 insertions(+), 56 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs index eebaea545a..9128f72d6e 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs @@ -72,45 +72,56 @@ namespace osu.Game.Tests.Visual.Online [Test] public void TestSupporterOnlyFiltersPlaceholderNoBeatmaps() { + AddStep("fetch for 0 beatmaps", () => fetchFor()); AddStep("set dummy as non-supporter", () => ((DummyAPIAccess)API).LocalUser.Value.IsSupporter = false); // test non-supporter on Rank Achieved filter toggleRandomRankFilter(); - AddUntilStep("supporter-placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); - AddUntilStep("not-found-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + expectedPlaceholderShown(true, false); AddStep("Clear Rank Achieved filter", () => overlay.ChildrenOfType().Single().Ranks.Clear()); - AddUntilStep("supporter-placeholder hidden", () => !overlay.ChildrenOfType().Any()); - AddUntilStep("not-found-placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); + expectedPlaceholderShown(false, true); // test non-supporter on Played filter toggleRandomSupporterOnlyPlayedFilter(); - AddUntilStep("supporter-placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); - AddUntilStep("not-found-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + expectedPlaceholderShown(true, false); AddStep("Set Played filter to Any", () => overlay.ChildrenOfType().Single().Played.Value = SearchPlayed.Any); - AddUntilStep("supporter-placeholder hidden", () => !overlay.ChildrenOfType().Any()); - AddUntilStep("not-found-placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); + expectedPlaceholderShown(false, true); + + // test non-supporter on both Rank Achieved and Played filter + toggleRandomRankFilter(); + toggleRandomSupporterOnlyPlayedFilter(); + expectedPlaceholderShown(true, false); + + AddStep("Clear Rank Achieved filter", () => overlay.ChildrenOfType().Single().Ranks.Clear()); + AddStep("Set Played filter to Any", () => overlay.ChildrenOfType().Single().Played.Value = SearchPlayed.Any); + expectedPlaceholderShown(false, true); AddStep("set dummy as supporter", () => ((DummyAPIAccess)API).LocalUser.Value.IsSupporter = true); // test supporter on Rank Achieved filter toggleRandomRankFilter(); - AddUntilStep("supporter-placeholder hidden", () => !overlay.ChildrenOfType().Any()); - AddUntilStep("not-found-placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); + expectedPlaceholderShown(false, true); AddStep("Clear Rank Achieved filter", () => overlay.ChildrenOfType().Single().Ranks.Clear()); - AddUntilStep("supporter-placeholder hidden", () => !overlay.ChildrenOfType().Any()); - AddUntilStep("not-found-placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); + expectedPlaceholderShown(false, true); // test supporter on Played filter toggleRandomSupporterOnlyPlayedFilter(); - AddUntilStep("supporter-placeholder hidden", () => !overlay.ChildrenOfType().Any()); - AddUntilStep("not-found-placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); + expectedPlaceholderShown(false, true); AddStep("Set Played filter to Any", () => overlay.ChildrenOfType().Single().Played.Value = SearchPlayed.Any); - AddUntilStep("supporter-placeholder hidden", () => !overlay.ChildrenOfType().Any()); - AddUntilStep("not-found-placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); + expectedPlaceholderShown(false, true); + + // test supporter on both Rank Achieved and Played filter + toggleRandomRankFilter(); + toggleRandomSupporterOnlyPlayedFilter(); + expectedPlaceholderShown(false, true); + + AddStep("Clear Rank Achieved filter", () => overlay.ChildrenOfType().Single().Ranks.Clear()); + AddStep("Set Played filter to Any", () => overlay.ChildrenOfType().Single().Played.Value = SearchPlayed.Any); + expectedPlaceholderShown(false, true); } [Test] @@ -121,41 +132,51 @@ namespace osu.Game.Tests.Visual.Online // test non-supporter on Rank Achieved filter toggleRandomRankFilter(); - AddUntilStep("supporter-placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); - AddUntilStep("not-found-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + expectedPlaceholderShown(true, false); AddStep("Clear Rank Achieved filter", () => overlay.ChildrenOfType().Single().Ranks.Clear()); - AddUntilStep("supporter-placeholder hidden", () => !overlay.ChildrenOfType().Any()); - AddUntilStep("not-found-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + expectedPlaceholderShown(false, false); // test non-supporter on Played filter toggleRandomSupporterOnlyPlayedFilter(); - AddUntilStep("supporter-placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); - AddUntilStep("not-found-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + expectedPlaceholderShown(true, false); AddStep("Set Played filter to Any", () => overlay.ChildrenOfType().Single().Played.Value = SearchPlayed.Any); - AddUntilStep("supporter-placeholder hidden", () => !overlay.ChildrenOfType().Any()); - AddUntilStep("not-found-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + expectedPlaceholderShown(false, false); + + // test non-supporter on both Rank Achieved and Played filter + toggleRandomRankFilter(); + toggleRandomSupporterOnlyPlayedFilter(); + expectedPlaceholderShown(true, false); + + AddStep("Clear Rank Achieved filter", () => overlay.ChildrenOfType().Single().Ranks.Clear()); + AddStep("Set Played filter to Any", () => overlay.ChildrenOfType().Single().Played.Value = SearchPlayed.Any); + expectedPlaceholderShown(false, false); AddStep("set dummy as supporter", () => ((DummyAPIAccess)API).LocalUser.Value.IsSupporter = true); // test supporter on Rank Achieved filter toggleRandomRankFilter(); - AddUntilStep("supporter-placeholder hidden", () => !overlay.ChildrenOfType().Any()); - AddUntilStep("not-found-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + expectedPlaceholderShown(false, false); AddStep("Clear Rank Achieved filter", () => overlay.ChildrenOfType().Single().Ranks.Clear()); - AddUntilStep("supporter-placeholder hidden", () => !overlay.ChildrenOfType().Any()); - AddUntilStep("not-found-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + expectedPlaceholderShown(false, false); // test supporter on Played filter toggleRandomSupporterOnlyPlayedFilter(); - AddUntilStep("supporter-placeholder hidden", () => !overlay.ChildrenOfType().Any()); - AddUntilStep("not-found-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + expectedPlaceholderShown(false, false); AddStep("Set Played filter to Any", () => overlay.ChildrenOfType().Single().Played.Value = SearchPlayed.Any); - AddUntilStep("supporter-placeholder hidden", () => !overlay.ChildrenOfType().Any()); - AddUntilStep("not-found-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + expectedPlaceholderShown(false, false); + + // test supporter on both Rank Achieved and Played filter + toggleRandomRankFilter(); + toggleRandomSupporterOnlyPlayedFilter(); + expectedPlaceholderShown(false, false); + + AddStep("Set Played filter to Any", () => overlay.ChildrenOfType().Single().Played.Value = SearchPlayed.Any); + AddStep("Clear Rank Achieved filter", () => overlay.ChildrenOfType().Single().Ranks.Clear()); + expectedPlaceholderShown(false, false); } @@ -184,6 +205,27 @@ namespace osu.Game.Tests.Visual.Online AddStep("toggle Random Played filter", () => overlay.ChildrenOfType().Single().Played.Value = (SearchPlayed)(r % 2 + 1)); } + private void expectedPlaceholderShown(bool supporterRequiredShown, bool notFoundShown) + { + if (supporterRequiredShown) + { + AddUntilStep("supporter-placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); + } + else + { + AddUntilStep("supporter-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + } + + if (notFoundShown) + { + AddUntilStep("not-found-placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); + } + else + { + AddUntilStep("not-found-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + } + } + private class TestAPIBeatmapSet : APIBeatmapSet { private readonly BeatmapSetInfo beatmapSet; diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs index 7b7f742b73..6e83dc0bf4 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs @@ -24,9 +24,9 @@ namespace osu.Game.Overlays.BeatmapListing { /// /// Fired when a search finishes. Contains only new items in the case of pagination. - /// Null when non-supporter user used supporter-only filters + /// Fired with BeatmapListingSearchControl when non-supporter user used supporter-only filters. /// - public Action> SearchFinished; + public Action, BeatmapListingSearchControl> SearchFinished; /// /// Fired when search criteria change. @@ -216,11 +216,11 @@ namespace osu.Game.Overlays.BeatmapListing // check if an non-supporter user used supporter-only filters if (!api.LocalUser.Value.IsSupporter && (searchControl.Ranks.Any() || searchControl.Played.Value != SearchPlayed.Any)) { - SearchFinished?.Invoke(null); + SearchFinished?.Invoke(sets, searchControl); } else { - SearchFinished?.Invoke(sets); + SearchFinished?.Invoke(sets, null); } }; diff --git a/osu.Game/Overlays/BeatmapListingOverlay.cs b/osu.Game/Overlays/BeatmapListingOverlay.cs index 3dec6de03a..90f1e6932d 100644 --- a/osu.Game/Overlays/BeatmapListingOverlay.cs +++ b/osu.Game/Overlays/BeatmapListingOverlay.cs @@ -119,11 +119,17 @@ namespace osu.Game.Overlays private Task panelLoadDelegate; - private void onSearchFinished(List beatmaps) + private void onSearchFinished(List beatmaps, BeatmapListingSearchControl searchControl) { // non-supporter user used supporter-only filters - if (beatmaps == null) + if (searchControl != null) { + // compose filter string + List filtersStrs = new List(); + if (searchControl.Ranks.Any()) filtersStrs.Add(BeatmapsStrings.ListingSearchFiltersRank.ToString()); + if (searchControl.Played.Value != SearchPlayed.Any) filtersStrs.Add(BeatmapsStrings.ListingSearchFiltersPlayed.ToString()); + supporterRequiredContent.UpdateSupportRequiredText(string.Join(" and ", filtersStrs)); + LoadComponentAsync(supporterRequiredContent, addContentToPlaceholder, (cancellationToken = new CancellationTokenSource()).Token); return; } @@ -258,11 +264,24 @@ namespace osu.Game.Overlays public class SupporterRequiredDrawable : CompositeDrawable { + private LinkFlowContainer linkFlowContainer; + public SupporterRequiredDrawable() { RelativeSizeAxes = Axes.X; Height = 225; Alpha = 0; + + linkFlowContainer = linkFlowContainer = new LinkFlowContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.Both, + Margin = new MarginPadding + { + Bottom = 10, + } + }; } [BackgroundDependencyLoader] @@ -285,27 +304,15 @@ namespace osu.Game.Overlays FillMode = FillMode.Fit, Texture = textures.Get(@"Online/supporter-required"), }, - createSupportRequiredText(), + linkFlowContainer, } }); } - private Drawable createSupportRequiredText() - { - LinkFlowContainer linkFlowContainer; - string[] text = BeatmapsStrings.ListingSearchSupporterFilterQuoteDefault(BeatmapsStrings.ListingSearchFiltersRank.ToString(), "{1}").ToString().Split("{1}"); - - linkFlowContainer = new LinkFlowContainer - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - AutoSizeAxes = Axes.Both, - Margin = new MarginPadding - { - Bottom = 10, - } - }; + public void UpdateSupportRequiredText(string filtersStr) { + string[] text = BeatmapsStrings.ListingSearchSupporterFilterQuoteDefault(filtersStr, "{1}").ToString().Split("{1}"); + linkFlowContainer.Clear(); linkFlowContainer.AddText( text[0], t => @@ -333,8 +340,6 @@ namespace osu.Game.Overlays t.Colour = Colour4.White; } ); - - return linkFlowContainer; } } From d0a8b748238dfa03b9174db624c34bac0259ad2a Mon Sep 17 00:00:00 2001 From: JimmyC7834 Date: Sun, 20 Jun 2021 21:28:57 +0800 Subject: [PATCH 021/168] fixed filter text order --- osu.Game/Overlays/BeatmapListingOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/BeatmapListingOverlay.cs b/osu.Game/Overlays/BeatmapListingOverlay.cs index 90f1e6932d..ceb033380c 100644 --- a/osu.Game/Overlays/BeatmapListingOverlay.cs +++ b/osu.Game/Overlays/BeatmapListingOverlay.cs @@ -126,8 +126,8 @@ namespace osu.Game.Overlays { // compose filter string List filtersStrs = new List(); - if (searchControl.Ranks.Any()) filtersStrs.Add(BeatmapsStrings.ListingSearchFiltersRank.ToString()); if (searchControl.Played.Value != SearchPlayed.Any) filtersStrs.Add(BeatmapsStrings.ListingSearchFiltersPlayed.ToString()); + if (searchControl.Ranks.Any()) filtersStrs.Add(BeatmapsStrings.ListingSearchFiltersRank.ToString()); supporterRequiredContent.UpdateSupportRequiredText(string.Join(" and ", filtersStrs)); LoadComponentAsync(supporterRequiredContent, addContentToPlaceholder, (cancellationToken = new CancellationTokenSource()).Token); From 0707642b76d94b81f3ef061bc6f207f7edf85764 Mon Sep 17 00:00:00 2001 From: JimmyC7834 Date: Mon, 21 Jun 2021 14:25:20 +0800 Subject: [PATCH 022/168] fixed SupporterRequiredDrawable --- osu.Game/Overlays/BeatmapListingOverlay.cs | 89 +++++++++------------- 1 file changed, 34 insertions(+), 55 deletions(-) diff --git a/osu.Game/Overlays/BeatmapListingOverlay.cs b/osu.Game/Overlays/BeatmapListingOverlay.cs index ceb033380c..5ddad1c9fc 100644 --- a/osu.Game/Overlays/BeatmapListingOverlay.cs +++ b/osu.Game/Overlays/BeatmapListingOverlay.cs @@ -13,6 +13,7 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Framework.Input.Events; +using osu.Framework.Localisation; using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Graphics; @@ -121,25 +122,20 @@ namespace osu.Game.Overlays private void onSearchFinished(List beatmaps, BeatmapListingSearchControl searchControl) { - // non-supporter user used supporter-only filters - if (searchControl != null) - { - // compose filter string - List filtersStrs = new List(); - if (searchControl.Played.Value != SearchPlayed.Any) filtersStrs.Add(BeatmapsStrings.ListingSearchFiltersPlayed.ToString()); - if (searchControl.Ranks.Any()) filtersStrs.Add(BeatmapsStrings.ListingSearchFiltersRank.ToString()); - supporterRequiredContent.UpdateSupportRequiredText(string.Join(" and ", filtersStrs)); - - LoadComponentAsync(supporterRequiredContent, addContentToPlaceholder, (cancellationToken = new CancellationTokenSource()).Token); - return; - } - var newPanels = beatmaps.Select(b => new GridBeatmapPanel(b) { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, }); + // non-supporter user used supporter-only filters + if (searchControl != null) + { + supporterRequiredContent.UpdateText(searchControl.Played.Value != SearchPlayed.Any, searchControl.Ranks.Any()); + LoadComponentAsync(supporterRequiredContent, addContentToPlaceholder, (cancellationToken = new CancellationTokenSource()).Token); + return; + } + if (filterControl.CurrentPage == 0) { //No matches case @@ -262,26 +258,16 @@ namespace osu.Game.Overlays } } + // using string literals as there's no proper processing for LocalizeStrings yet public class SupporterRequiredDrawable : CompositeDrawable { - private LinkFlowContainer linkFlowContainer; + private OsuSpriteText supporterRequiredText; public SupporterRequiredDrawable() { RelativeSizeAxes = Axes.X; Height = 225; Alpha = 0; - - linkFlowContainer = linkFlowContainer = new LinkFlowContainer - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - AutoSizeAxes = Axes.Both, - Margin = new MarginPadding - { - Bottom = 10, - } - }; } [BackgroundDependencyLoader] @@ -304,42 +290,35 @@ namespace osu.Game.Overlays FillMode = FillMode.Fit, Texture = textures.Get(@"Online/supporter-required"), }, - linkFlowContainer, + supporterRequiredText = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Margin = new MarginPadding { Bottom = 10 }, + }, + createSupporterTagLink(), } }); } - public void UpdateSupportRequiredText(string filtersStr) { - string[] text = BeatmapsStrings.ListingSearchSupporterFilterQuoteDefault(filtersStr, "{1}").ToString().Split("{1}"); + public void UpdateText(bool playedFilter, bool rankFilter) { + List filters = new List(); + if (playedFilter) filters.Add(BeatmapsStrings.ListingSearchFiltersPlayed.ToString()); + if (rankFilter) filters.Add(BeatmapsStrings.ListingSearchFiltersRank.ToString()); + supporterRequiredText.Text = BeatmapsStrings.ListingSearchSupporterFilterQuoteDefault(string.Join(" and ", filters), "").ToString(); + } - linkFlowContainer.Clear(); - linkFlowContainer.AddText( - text[0], - t => - { - t.Font = OsuFont.GetFont(size: 16); - t.Colour = Colour4.White; - } - ); + public Drawable createSupporterTagLink() { + LinkFlowContainer supporterTagLink = new LinkFlowContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.Both, + Margin = new MarginPadding { Bottom = 10 }, + }; - linkFlowContainer.AddLink( - BeatmapsStrings.ListingSearchSupporterFilterQuoteLinkText.ToString(), - "https://osu.ppy.sh/store/products/supporter-tag", - t => - { - t.Font = OsuFont.GetFont(size: 16); - t.Colour = Colour4.FromHex("#A6C8D9"); - } - ); - - linkFlowContainer.AddText( - text[1], - t => - { - t.Font = OsuFont.GetFont(size: 16); - t.Colour = Colour4.White; - } - ); + supporterTagLink.AddLink(BeatmapsStrings.ListingSearchSupporterFilterQuoteLinkText.ToString(), Online.Chat.LinkAction.External, "https://osu.ppy.sh/store/products/supporter-tag"); + return supporterTagLink; } } From 33674563041102d02722fd98366eab6d3c48aa92 Mon Sep 17 00:00:00 2001 From: JimmyC7834 Date: Mon, 21 Jun 2021 14:30:54 +0800 Subject: [PATCH 023/168] fixed SupporterRequiredDrawable style --- osu.Game/Overlays/BeatmapListingOverlay.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Overlays/BeatmapListingOverlay.cs b/osu.Game/Overlays/BeatmapListingOverlay.cs index 5ddad1c9fc..42a7253276 100644 --- a/osu.Game/Overlays/BeatmapListingOverlay.cs +++ b/osu.Game/Overlays/BeatmapListingOverlay.cs @@ -294,6 +294,8 @@ namespace osu.Game.Overlays { Anchor = Anchor.Centre, Origin = Anchor.Centre, + Font = OsuFont.GetFont(size: 16), + Colour = Colour4.White, Margin = new MarginPadding { Bottom = 10 }, }, createSupporterTagLink(), From b42aedeb8171352bb56ed5334b0a347f43865335 Mon Sep 17 00:00:00 2001 From: JimmyC7834 Date: Mon, 21 Jun 2021 14:43:54 +0800 Subject: [PATCH 024/168] fixed code style --- osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs index 9128f72d6e..2146ea333a 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs @@ -179,7 +179,6 @@ namespace osu.Game.Tests.Visual.Online expectedPlaceholderShown(false, false); } - private void fetchFor(params BeatmapSetInfo[] beatmaps) { setsForResponse.Clear(); From 22cc1e14527c14bb2aa16052cdd5bb3ce27072dc Mon Sep 17 00:00:00 2001 From: JimmyC7834 Date: Mon, 21 Jun 2021 15:31:47 +0800 Subject: [PATCH 025/168] fixed code style base on code analysis --- osu.Game/Overlays/BeatmapListingOverlay.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/BeatmapListingOverlay.cs b/osu.Game/Overlays/BeatmapListingOverlay.cs index 42a7253276..407b737db5 100644 --- a/osu.Game/Overlays/BeatmapListingOverlay.cs +++ b/osu.Game/Overlays/BeatmapListingOverlay.cs @@ -13,7 +13,6 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Framework.Input.Events; -using osu.Framework.Localisation; using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Graphics; @@ -280,7 +279,7 @@ namespace osu.Game.Overlays RelativeSizeAxes = Axes.Y, AutoSizeAxes = Axes.X, Direction = FillDirection.Horizontal, - Children = new Drawable[] + Children = new[] { new Sprite { @@ -303,14 +302,16 @@ namespace osu.Game.Overlays }); } - public void UpdateText(bool playedFilter, bool rankFilter) { + public void UpdateText(bool playedFilter, bool rankFilter) + { List filters = new List(); if (playedFilter) filters.Add(BeatmapsStrings.ListingSearchFiltersPlayed.ToString()); if (rankFilter) filters.Add(BeatmapsStrings.ListingSearchFiltersRank.ToString()); supporterRequiredText.Text = BeatmapsStrings.ListingSearchSupporterFilterQuoteDefault(string.Join(" and ", filters), "").ToString(); } - public Drawable createSupporterTagLink() { + private Drawable createSupporterTagLink() + { LinkFlowContainer supporterTagLink = new LinkFlowContainer { Anchor = Anchor.Centre, From b162da5ee0265726f99b8c847b7b31512cae0e88 Mon Sep 17 00:00:00 2001 From: JimmyC7834 Date: Mon, 21 Jun 2021 15:47:47 +0800 Subject: [PATCH 026/168] minor code change --- osu.Game/Overlays/BeatmapListingOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/BeatmapListingOverlay.cs b/osu.Game/Overlays/BeatmapListingOverlay.cs index 407b737db5..578e70e630 100644 --- a/osu.Game/Overlays/BeatmapListingOverlay.cs +++ b/osu.Game/Overlays/BeatmapListingOverlay.cs @@ -320,7 +320,7 @@ namespace osu.Game.Overlays Margin = new MarginPadding { Bottom = 10 }, }; - supporterTagLink.AddLink(BeatmapsStrings.ListingSearchSupporterFilterQuoteLinkText.ToString(), Online.Chat.LinkAction.External, "https://osu.ppy.sh/store/products/supporter-tag"); + supporterTagLink.AddLink(BeatmapsStrings.ListingSearchSupporterFilterQuoteLinkText.ToString(), "https://osu.ppy.sh/store/products/supporter-tag"); return supporterTagLink; } } From 0d17fb425973e8935220d06a2f40ff3223b23b86 Mon Sep 17 00:00:00 2001 From: JimmyC7834 Date: Tue, 22 Jun 2021 13:53:21 +0800 Subject: [PATCH 027/168] fixed code --- .../Online/TestSceneBeatmapListingOverlay.cs | 194 +++++++++--------- .../BeatmapListingFilterControl.cs | 41 +++- osu.Game/Overlays/BeatmapListingOverlay.cs | 70 +++---- 3 files changed, 170 insertions(+), 135 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs index 2146ea333a..cd382c2bb2 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs @@ -24,6 +24,8 @@ namespace osu.Game.Tests.Visual.Online private BeatmapListingOverlay overlay; + private BeatmapListingSearchControl searchControl => overlay.ChildrenOfType().Single(); + [BackgroundDependencyLoader] private void load() { @@ -70,113 +72,123 @@ namespace osu.Game.Tests.Visual.Online } [Test] - public void TestSupporterOnlyFiltersPlaceholderNoBeatmaps() + public void TestNonSupportUseSupporterOnlyFiltersPlaceholderNoBeatmaps() { AddStep("fetch for 0 beatmaps", () => fetchFor()); AddStep("set dummy as non-supporter", () => ((DummyAPIAccess)API).LocalUser.Value.IsSupporter = false); // test non-supporter on Rank Achieved filter - toggleRandomRankFilter(); - expectedPlaceholderShown(true, false); + toggleRankFilter(Scoring.ScoreRank.XH); + supporterRequiredPlaceholderShown(); - AddStep("Clear Rank Achieved filter", () => overlay.ChildrenOfType().Single().Ranks.Clear()); - expectedPlaceholderShown(false, true); + AddStep("Clear Rank Achieved filter", () => searchControl.Ranks.Clear()); + notFoundPlaceholderShown(); // test non-supporter on Played filter - toggleRandomSupporterOnlyPlayedFilter(); - expectedPlaceholderShown(true, false); + toggleSupporterOnlyPlayedFilter(SearchPlayed.Played); + supporterRequiredPlaceholderShown(); - AddStep("Set Played filter to Any", () => overlay.ChildrenOfType().Single().Played.Value = SearchPlayed.Any); - expectedPlaceholderShown(false, true); + AddStep("Set Played filter to Any", () => searchControl.Played.Value = SearchPlayed.Any); + notFoundPlaceholderShown(); // test non-supporter on both Rank Achieved and Played filter - toggleRandomRankFilter(); - toggleRandomSupporterOnlyPlayedFilter(); - expectedPlaceholderShown(true, false); + toggleRankFilter(Scoring.ScoreRank.XH); + toggleSupporterOnlyPlayedFilter(SearchPlayed.Played); + supporterRequiredPlaceholderShown(); - AddStep("Clear Rank Achieved filter", () => overlay.ChildrenOfType().Single().Ranks.Clear()); - AddStep("Set Played filter to Any", () => overlay.ChildrenOfType().Single().Played.Value = SearchPlayed.Any); - expectedPlaceholderShown(false, true); - - AddStep("set dummy as supporter", () => ((DummyAPIAccess)API).LocalUser.Value.IsSupporter = true); - - // test supporter on Rank Achieved filter - toggleRandomRankFilter(); - expectedPlaceholderShown(false, true); - - AddStep("Clear Rank Achieved filter", () => overlay.ChildrenOfType().Single().Ranks.Clear()); - expectedPlaceholderShown(false, true); - - // test supporter on Played filter - toggleRandomSupporterOnlyPlayedFilter(); - expectedPlaceholderShown(false, true); - - AddStep("Set Played filter to Any", () => overlay.ChildrenOfType().Single().Played.Value = SearchPlayed.Any); - expectedPlaceholderShown(false, true); - - // test supporter on both Rank Achieved and Played filter - toggleRandomRankFilter(); - toggleRandomSupporterOnlyPlayedFilter(); - expectedPlaceholderShown(false, true); - - AddStep("Clear Rank Achieved filter", () => overlay.ChildrenOfType().Single().Ranks.Clear()); - AddStep("Set Played filter to Any", () => overlay.ChildrenOfType().Single().Played.Value = SearchPlayed.Any); - expectedPlaceholderShown(false, true); + AddStep("Clear Rank Achieved filter", () => searchControl.Ranks.Clear()); + AddStep("Set Played filter to Any", () => searchControl.Played.Value = SearchPlayed.Any); + notFoundPlaceholderShown(); } [Test] - public void TestSupporterOnlyFiltersPlaceholderOneBeatmap() + public void TestSupportUseSupporterOnlyFiltersPlaceholderNoBeatmaps() + { + AddStep("fetch for 0 beatmaps", () => fetchFor()); + AddStep("set dummy as supporter", () => ((DummyAPIAccess)API).LocalUser.Value.IsSupporter = true); + + // test supporter on Rank Achieved filter + toggleRankFilter(Scoring.ScoreRank.XH); + notFoundPlaceholderShown(); + + AddStep("Clear Rank Achieved filter", () => searchControl.Ranks.Clear()); + notFoundPlaceholderShown(); + + // test supporter on Played filter + toggleSupporterOnlyPlayedFilter(SearchPlayed.Played); + notFoundPlaceholderShown(); + + AddStep("Set Played filter to Any", () => searchControl.Played.Value = SearchPlayed.Any); + notFoundPlaceholderShown(); + + // test supporter on both Rank Achieved and Played filter + toggleRankFilter(Scoring.ScoreRank.XH); + toggleSupporterOnlyPlayedFilter(SearchPlayed.Played); + notFoundPlaceholderShown(); + + AddStep("Clear Rank Achieved filter", () => searchControl.Ranks.Clear()); + AddStep("Set Played filter to Any", () => searchControl.Played.Value = SearchPlayed.Any); + notFoundPlaceholderShown(); + } + + [Test] + public void TestNonSupporterUseSupporterOnlyFiltersPlaceholderOneBeatmap() { AddStep("fetch for 1 beatmap", () => fetchFor(CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet)); AddStep("set dummy as non-supporter", () => ((DummyAPIAccess)API).LocalUser.Value.IsSupporter = false); // test non-supporter on Rank Achieved filter - toggleRandomRankFilter(); - expectedPlaceholderShown(true, false); + toggleRankFilter(Scoring.ScoreRank.XH); + supporterRequiredPlaceholderShown(); - AddStep("Clear Rank Achieved filter", () => overlay.ChildrenOfType().Single().Ranks.Clear()); - expectedPlaceholderShown(false, false); + AddStep("Clear Rank Achieved filter", () => searchControl.Ranks.Clear()); + noPlaceholderShown(); // test non-supporter on Played filter - toggleRandomSupporterOnlyPlayedFilter(); - expectedPlaceholderShown(true, false); + toggleSupporterOnlyPlayedFilter(SearchPlayed.Played); + supporterRequiredPlaceholderShown(); - AddStep("Set Played filter to Any", () => overlay.ChildrenOfType().Single().Played.Value = SearchPlayed.Any); - expectedPlaceholderShown(false, false); + AddStep("Set Played filter to Any", () => searchControl.Played.Value = SearchPlayed.Any); + noPlaceholderShown(); // test non-supporter on both Rank Achieved and Played filter - toggleRandomRankFilter(); - toggleRandomSupporterOnlyPlayedFilter(); - expectedPlaceholderShown(true, false); + toggleRankFilter(Scoring.ScoreRank.XH); + toggleSupporterOnlyPlayedFilter(SearchPlayed.Played); + supporterRequiredPlaceholderShown(); - AddStep("Clear Rank Achieved filter", () => overlay.ChildrenOfType().Single().Ranks.Clear()); - AddStep("Set Played filter to Any", () => overlay.ChildrenOfType().Single().Played.Value = SearchPlayed.Any); - expectedPlaceholderShown(false, false); + AddStep("Clear Rank Achieved filter", () => searchControl.Ranks.Clear()); + AddStep("Set Played filter to Any", () => searchControl.Played.Value = SearchPlayed.Any); + noPlaceholderShown(); + } + [Test] + public void TestSupporterUseSupporterOnlyFiltersPlaceholderOneBeatmap() + { + AddStep("fetch for 1 beatmap", () => fetchFor(CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet)); AddStep("set dummy as supporter", () => ((DummyAPIAccess)API).LocalUser.Value.IsSupporter = true); // test supporter on Rank Achieved filter - toggleRandomRankFilter(); - expectedPlaceholderShown(false, false); + toggleRankFilter(Scoring.ScoreRank.XH); + noPlaceholderShown(); - AddStep("Clear Rank Achieved filter", () => overlay.ChildrenOfType().Single().Ranks.Clear()); - expectedPlaceholderShown(false, false); + AddStep("Clear Rank Achieved filter", () => searchControl.Ranks.Clear()); + noPlaceholderShown(); // test supporter on Played filter - toggleRandomSupporterOnlyPlayedFilter(); - expectedPlaceholderShown(false, false); + toggleSupporterOnlyPlayedFilter(SearchPlayed.Played); + noPlaceholderShown(); - AddStep("Set Played filter to Any", () => overlay.ChildrenOfType().Single().Played.Value = SearchPlayed.Any); - expectedPlaceholderShown(false, false); + AddStep("Set Played filter to Any", () => searchControl.Played.Value = SearchPlayed.Any); + noPlaceholderShown(); // test supporter on both Rank Achieved and Played filter - toggleRandomRankFilter(); - toggleRandomSupporterOnlyPlayedFilter(); - expectedPlaceholderShown(false, false); + toggleRankFilter(Scoring.ScoreRank.XH); + toggleSupporterOnlyPlayedFilter(SearchPlayed.Played); + noPlaceholderShown(); - AddStep("Set Played filter to Any", () => overlay.ChildrenOfType().Single().Played.Value = SearchPlayed.Any); - AddStep("Clear Rank Achieved filter", () => overlay.ChildrenOfType().Single().Ranks.Clear()); - expectedPlaceholderShown(false, false); + AddStep("Set Played filter to Any", () => searchControl.Played.Value = SearchPlayed.Any); + AddStep("Clear Rank Achieved filter", () => searchControl.Ranks.Clear()); + noPlaceholderShown(); } private void fetchFor(params BeatmapSetInfo[] beatmaps) @@ -185,44 +197,36 @@ namespace osu.Game.Tests.Visual.Online setsForResponse.AddRange(beatmaps.Select(b => new TestAPIBeatmapSet(b))); // trigger arbitrary change for fetching. - overlay.ChildrenOfType().Single().Query.TriggerChange(); + searchControl.Query.TriggerChange(); } - private void toggleRandomRankFilter() + private void toggleRankFilter(Scoring.ScoreRank rank) { - short r = TestContext.CurrentContext.Random.NextShort(); - AddStep("toggle Random Rank Achieved filter", () => + AddStep("toggle Rank Achieved filter", () => { - overlay.ChildrenOfType().Single().Ranks.Clear(); - overlay.ChildrenOfType().Single().Ranks.Add((Scoring.ScoreRank)(r % 8)); + searchControl.Ranks.Clear(); + searchControl.Ranks.Add(rank); }); } - private void toggleRandomSupporterOnlyPlayedFilter() + private void toggleSupporterOnlyPlayedFilter(SearchPlayed played) { - short r = TestContext.CurrentContext.Random.NextShort(); - AddStep("toggle Random Played filter", () => overlay.ChildrenOfType().Single().Played.Value = (SearchPlayed)(r % 2 + 1)); + AddStep("toggle Played filter", () => searchControl.Played.Value = played); } - private void expectedPlaceholderShown(bool supporterRequiredShown, bool notFoundShown) + private void supporterRequiredPlaceholderShown() { - if (supporterRequiredShown) - { - AddUntilStep("supporter-placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); - } - else - { - AddUntilStep("supporter-placeholder hidden", () => !overlay.ChildrenOfType().Any()); - } + AddUntilStep("supporter-placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); + } - if (notFoundShown) - { - AddUntilStep("not-found-placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); - } - else - { - AddUntilStep("not-found-placeholder hidden", () => !overlay.ChildrenOfType().Any()); - } + private void notFoundPlaceholderShown() + { + AddUntilStep("not-found-placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); + } + + private void noPlaceholderShown() + { + AddUntilStep("no placeholder shown", () => !overlay.ChildrenOfType().Any() && !overlay.ChildrenOfType().Any()); } private class TestAPIBeatmapSet : APIBeatmapSet diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs index 6e83dc0bf4..f49d913bb2 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs @@ -10,11 +10,13 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; +using osu.Framework.Localisation; using osu.Framework.Threading; using osu.Game.Beatmaps; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Rulesets; +using osu.Game.Resources.Localisation.Web; using osuTK; using osuTK.Graphics; @@ -26,7 +28,7 @@ namespace osu.Game.Overlays.BeatmapListing /// Fired when a search finishes. Contains only new items in the case of pagination. /// Fired with BeatmapListingSearchControl when non-supporter user used supporter-only filters. /// - public Action, BeatmapListingSearchControl> SearchFinished; + public Action SearchFinished; /// /// Fired when search criteria change. @@ -216,11 +218,19 @@ namespace osu.Game.Overlays.BeatmapListing // check if an non-supporter user used supporter-only filters if (!api.LocalUser.Value.IsSupporter && (searchControl.Ranks.Any() || searchControl.Played.Value != SearchPlayed.Any)) { - SearchFinished?.Invoke(sets, searchControl); + List filters = new List(); + + if (searchControl.Played.Value != SearchPlayed.Any) + filters.Add(BeatmapsStrings.ListingSearchFiltersPlayed); + + if (searchControl.Ranks.Any()) + filters.Add(BeatmapsStrings.ListingSearchFiltersRank); + + SearchFinished?.Invoke(SearchResult.SupporterOnlyFilter(filters)); } else { - SearchFinished?.Invoke(sets, null); + SearchFinished?.Invoke(SearchResult.ResultsReturned(sets)); } }; @@ -246,5 +256,30 @@ namespace osu.Game.Overlays.BeatmapListing base.Dispose(isDisposing); } + + public enum SearchResultType + { + ResultsReturned, + SupporterOnlyFilter + } + + public struct SearchResult + { + public SearchResultType Type { get; private set; } + public List Results { get; private set; } + public List Filters { get; private set; } + + public static SearchResult ResultsReturned(List results) => new SearchResult + { + Type = SearchResultType.ResultsReturned, + Results = results + }; + + public static SearchResult SupporterOnlyFilter(List filters) => new SearchResult + { + Type = SearchResultType.SupporterOnlyFilter, + Filters = filters + }; + } } } diff --git a/osu.Game/Overlays/BeatmapListingOverlay.cs b/osu.Game/Overlays/BeatmapListingOverlay.cs index 578e70e630..63800e6585 100644 --- a/osu.Game/Overlays/BeatmapListingOverlay.cs +++ b/osu.Game/Overlays/BeatmapListingOverlay.cs @@ -7,6 +7,7 @@ using System.Threading; using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.Localisation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -119,28 +120,28 @@ namespace osu.Game.Overlays private Task panelLoadDelegate; - private void onSearchFinished(List beatmaps, BeatmapListingSearchControl searchControl) + private void onSearchFinished(BeatmapListingFilterControl.SearchResult searchResult) { - var newPanels = beatmaps.Select(b => new GridBeatmapPanel(b) + // non-supporter user used supporter-only filters + if (searchResult.Type == BeatmapListingFilterControl.SearchResultType.SupporterOnlyFilter) + { + supporterRequiredContent.UpdateText(searchResult.Filters); + addContentToPlaceholder(supporterRequiredContent); + return; + } + + var newPanels = searchResult.Results.Select(b => new GridBeatmapPanel(b) { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, }); - // non-supporter user used supporter-only filters - if (searchControl != null) - { - supporterRequiredContent.UpdateText(searchControl.Played.Value != SearchPlayed.Any, searchControl.Ranks.Any()); - LoadComponentAsync(supporterRequiredContent, addContentToPlaceholder, (cancellationToken = new CancellationTokenSource()).Token); - return; - } - if (filterControl.CurrentPage == 0) { //No matches case if (!newPanels.Any()) { - LoadComponentAsync(notFoundContent, addContentToPlaceholder, (cancellationToken = new CancellationTokenSource()).Token); + addContentToPlaceholder(notFoundContent); return; } @@ -182,16 +183,11 @@ namespace osu.Game.Overlays { var transform = lastContent.FadeOut(100, Easing.OutQuint); - if (lastContent == notFoundContent) + if (lastContent == notFoundContent || lastContent == supporterRequiredContent) { - // not found display may be used multiple times, so don't expire/dispose it. + // the placeholder may be used multiple times, so don't expire/dispose it. transform.Schedule(() => panelTarget.Remove(lastContent)); } - else if (lastContent == supporterRequiredContent) - { - // supporter required display may be used multiple times, so don't expire/dispose it. - transform.Schedule(() => panelTarget.Remove(supporterRequiredContent)); - } else { // Consider the case when the new content is smaller than the last content. @@ -260,7 +256,7 @@ namespace osu.Game.Overlays // using string literals as there's no proper processing for LocalizeStrings yet public class SupporterRequiredDrawable : CompositeDrawable { - private OsuSpriteText supporterRequiredText; + private OsuSpriteText filtersText; public SupporterRequiredDrawable() { @@ -289,30 +285,20 @@ namespace osu.Game.Overlays FillMode = FillMode.Fit, Texture = textures.Get(@"Online/supporter-required"), }, - supporterRequiredText = new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Font = OsuFont.GetFont(size: 16), - Colour = Colour4.White, - Margin = new MarginPadding { Bottom = 10 }, - }, - createSupporterTagLink(), + createSupporterText(), } }); } - public void UpdateText(bool playedFilter, bool rankFilter) + public void UpdateText(List filters) { - List filters = new List(); - if (playedFilter) filters.Add(BeatmapsStrings.ListingSearchFiltersPlayed.ToString()); - if (rankFilter) filters.Add(BeatmapsStrings.ListingSearchFiltersRank.ToString()); - supporterRequiredText.Text = BeatmapsStrings.ListingSearchSupporterFilterQuoteDefault(string.Join(" and ", filters), "").ToString(); + // use string literals for now + filtersText.Text = BeatmapsStrings.ListingSearchSupporterFilterQuoteDefault(string.Join(" and ", filters), "").ToString(); } - private Drawable createSupporterTagLink() + private Drawable createSupporterText() { - LinkFlowContainer supporterTagLink = new LinkFlowContainer + LinkFlowContainer supporterRequiredText = new LinkFlowContainer { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -320,8 +306,18 @@ namespace osu.Game.Overlays Margin = new MarginPadding { Bottom = 10 }, }; - supporterTagLink.AddLink(BeatmapsStrings.ListingSearchSupporterFilterQuoteLinkText.ToString(), "https://osu.ppy.sh/store/products/supporter-tag"); - return supporterTagLink; + filtersText = (OsuSpriteText)supporterRequiredText.AddText( + "_", + t => + { + t.Font = OsuFont.GetFont(size: 16); + t.Colour = Colour4.White; + } + ).First(); + + supporterRequiredText.AddLink(BeatmapsStrings.ListingSearchSupporterFilterQuoteLinkText.ToString(), @"/store/products/supporter-tag"); + + return supporterRequiredText; } } From a4b66bec2e570af80da16610bc99b859fa4651bd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 22 Jun 2021 18:18:25 +0900 Subject: [PATCH 028/168] Ensure realm contexts are flushed when update thread changes native thread --- osu.Game/OsuGameBase.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 3a08ef684f..fb083ea7d5 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -182,6 +182,13 @@ namespace osu.Game dependencies.Cache(contextFactory = new DatabaseContextFactory(Storage)); dependencies.Cache(realmFactory = new RealmContextFactory(Storage)); + + Host.UpdateThreadChanging += () => + { + var blocking = realmFactory.BlockAllOperations(); + Schedule(() => blocking.Dispose()); + }; + AddInternal(realmFactory); dependencies.CacheAs(Storage); From b9a9174168f62115acb2bcc78f9054d6a248c27e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 22 Jun 2021 18:26:42 +0900 Subject: [PATCH 029/168] Remove live realm bindings for now --- osu.Game/Overlays/Toolbar/ToolbarButton.cs | 37 +++++++++------------- 1 file changed, 15 insertions(+), 22 deletions(-) diff --git a/osu.Game/Overlays/Toolbar/ToolbarButton.cs b/osu.Game/Overlays/Toolbar/ToolbarButton.cs index 432c52c2e9..d2e07c7d15 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarButton.cs @@ -159,28 +159,6 @@ namespace osu.Game.Overlays.Toolbar }; } - private RealmKeyBinding realmKeyBinding; - - protected override void LoadComplete() - { - base.LoadComplete(); - - if (Hotkey == null) return; - - realmKeyBinding = realmFactory.Context.All().FirstOrDefault(rkb => rkb.RulesetID == null && rkb.ActionInt == (int)Hotkey.Value); - - if (realmKeyBinding != null) - { - realmKeyBinding.PropertyChanged += (sender, args) => - { - if (args.PropertyName == nameof(realmKeyBinding.KeyCombinationString)) - updateKeyBindingTooltip(); - }; - } - - updateKeyBindingTooltip(); - } - protected override bool OnMouseDown(MouseDownEvent e) => true; protected override bool OnClick(ClickEvent e) @@ -196,6 +174,7 @@ namespace osu.Game.Overlays.Toolbar HoverBackground.FadeIn(200); tooltipContainer.FadeIn(100); + return base.OnHover(e); } @@ -222,6 +201,20 @@ namespace osu.Game.Overlays.Toolbar private void updateKeyBindingTooltip() { + if (Hotkey == null) return; + + var realmKeyBinding = realmFactory.Context.All().FirstOrDefault(rkb => rkb.RulesetID == null && rkb.ActionInt == (int)Hotkey.Value); + + // TODO: temporarily disabled to avoid crashes when querying after ExecutionState is changed. + // if (realmKeyBinding != null) + // { + // realmKeyBinding.PropertyChanged += (sender, args) => + // { + // if (args.PropertyName == nameof(realmKeyBinding.KeyCombinationString)) + // updateKeyBindingTooltip(); + // }; + // } + if (realmKeyBinding != null) { var keyBindingString = realmKeyBinding.KeyCombination.ReadableString(); From f03c2bab481df4a343171b34e9eb1303095cee47 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 22 Jun 2021 22:45:13 +0900 Subject: [PATCH 030/168] Update event name in line with framework changes --- osu.Game/OsuGameBase.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index fb083ea7d5..43c81783fe 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -183,7 +183,7 @@ namespace osu.Game dependencies.Cache(realmFactory = new RealmContextFactory(Storage)); - Host.UpdateThreadChanging += () => + Host.UpdateThreadPausing += () => { var blocking = realmFactory.BlockAllOperations(); Schedule(() => blocking.Dispose()); From ee84364d7ca7eea57d37257c198694ec08d6fb80 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 22 Jun 2021 20:38:24 +0300 Subject: [PATCH 031/168] Resolve conflict issues --- osu.Game/Skinning/RulesetSkinProvidingContainer.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs index 4a4773fce4..e46ba6d857 100644 --- a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs +++ b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs @@ -51,7 +51,6 @@ namespace osu.Game.Skinning private AudioManager audio { get; set; } [Resolved] - private SkinManager skinManager { get; set; } private ISkinSource skinSource { get; set; } [BackgroundDependencyLoader] @@ -84,6 +83,8 @@ namespace osu.Game.Skinning break; } } + + SkinSources.Add(new RulesetResourcesSkin(Ruleset, host, audio)); } protected ISkin GetLegacyRulesetTransformedSkin(ISkin legacySkin) @@ -102,9 +103,6 @@ namespace osu.Game.Skinning { base.Dispose(isDisposing); - SkinSources.Add(Ruleset.CreateLegacySkinProvider(skinManager.DefaultSkin, Beatmap)); - - SkinSources.Add(new RulesetResourcesSkin(Ruleset, host, audio)); if (skinSource != null) skinSource.SourceChanged -= OnSourceChanged; } From e4d17bd75773c52b61ec26c610483aec2ba4b34d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 23 Jun 2021 15:06:28 +0900 Subject: [PATCH 032/168] Remove commented code This will be reverted in a future change, no need to keep it around. --- osu.Game/Overlays/Toolbar/ToolbarButton.cs | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/osu.Game/Overlays/Toolbar/ToolbarButton.cs b/osu.Game/Overlays/Toolbar/ToolbarButton.cs index d2e07c7d15..4a33f9e296 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarButton.cs @@ -205,16 +205,6 @@ namespace osu.Game.Overlays.Toolbar var realmKeyBinding = realmFactory.Context.All().FirstOrDefault(rkb => rkb.RulesetID == null && rkb.ActionInt == (int)Hotkey.Value); - // TODO: temporarily disabled to avoid crashes when querying after ExecutionState is changed. - // if (realmKeyBinding != null) - // { - // realmKeyBinding.PropertyChanged += (sender, args) => - // { - // if (args.PropertyName == nameof(realmKeyBinding.KeyCombinationString)) - // updateKeyBindingTooltip(); - // }; - // } - if (realmKeyBinding != null) { var keyBindingString = realmKeyBinding.KeyCombination.ReadableString(); From 0db06c727b47c43b0207af22054a26c774c8c5b9 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 23 Jun 2021 09:41:45 +0300 Subject: [PATCH 033/168] Dispose resource stores on finalizer --- osu.Game/Skinning/RulesetResourcesSkin.cs | 30 +++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/osu.Game/Skinning/RulesetResourcesSkin.cs b/osu.Game/Skinning/RulesetResourcesSkin.cs index 1905a8a899..60b22a8e67 100644 --- a/osu.Game/Skinning/RulesetResourcesSkin.cs +++ b/osu.Game/Skinning/RulesetResourcesSkin.cs @@ -1,6 +1,7 @@ // 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 osu.Framework.Audio; using osu.Framework.Audio.Sample; using osu.Framework.Bindables; @@ -47,5 +48,34 @@ namespace osu.Game.Skinning } public IBindable GetConfig(TLookup lookup) => null; + + #region Disposal + + ~RulesetResourcesSkin() + { + // required to potentially clean up sample store from audio hierarchy. + Dispose(false); + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + private bool isDisposed; + + protected virtual void Dispose(bool isDisposing) + { + if (isDisposed) + return; + + isDisposed = true; + + rulesetTextures?.Dispose(); + rulesetSamples?.Dispose(); + } + + #endregion } } From 2e6800f5862f1815727b967a444f951fdbc89c1f Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 23 Jun 2021 09:52:00 +0300 Subject: [PATCH 034/168] Enable NRT in `RulesetResourcesSkin` --- osu.Game/Skinning/RulesetResourcesSkin.cs | 29 +++++++++++++---------- 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/osu.Game/Skinning/RulesetResourcesSkin.cs b/osu.Game/Skinning/RulesetResourcesSkin.cs index 60b22a8e67..5cf2eec338 100644 --- a/osu.Game/Skinning/RulesetResourcesSkin.cs +++ b/osu.Game/Skinning/RulesetResourcesSkin.cs @@ -1,6 +1,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +#nullable enable + using System; using osu.Framework.Audio; using osu.Framework.Audio.Sample; @@ -20,26 +22,29 @@ namespace osu.Game.Skinning /// public class RulesetResourcesSkin : ISkin { - private readonly TextureStore rulesetTextures; - private readonly ISampleStore rulesetSamples; + private readonly TextureStore? textures; + private readonly ISampleStore? samples; public RulesetResourcesSkin(Ruleset ruleset, GameHost host, AudioManager audio) { - IResourceStore rulesetResources = ruleset.CreateResourceStore(); + IResourceStore? resources = ruleset.CreateResourceStore(); - rulesetTextures = new TextureStore(host.CreateTextureLoaderStore(new NamespacedResourceStore(rulesetResources, @"Textures"))); - rulesetSamples = audio.GetSampleStore(new NamespacedResourceStore(rulesetResources, @"Samples")); + if (resources != null) + { + textures = new TextureStore(host.CreateTextureLoaderStore(new NamespacedResourceStore(resources, @"Textures"))); + samples = audio.GetSampleStore(new NamespacedResourceStore(resources, @"Samples")); + } } - public Drawable GetDrawableComponent(ISkinComponent component) => null; + public Drawable? GetDrawableComponent(ISkinComponent component) => null; - public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => rulesetTextures.Get(componentName, wrapModeS, wrapModeT); + public Texture? GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => textures?.Get(componentName, wrapModeS, wrapModeT); - public ISample GetSample(ISampleInfo sampleInfo) + public ISample? GetSample(ISampleInfo sampleInfo) { foreach (var lookup in sampleInfo.LookupNames) { - ISample sample = rulesetSamples.Get(lookup); + ISample? sample = samples?.Get(lookup); if (sample != null) return sample; } @@ -47,7 +52,7 @@ namespace osu.Game.Skinning return null; } - public IBindable GetConfig(TLookup lookup) => null; + public IBindable? GetConfig(TLookup lookup) => null; #region Disposal @@ -72,8 +77,8 @@ namespace osu.Game.Skinning isDisposed = true; - rulesetTextures?.Dispose(); - rulesetSamples?.Dispose(); + textures?.Dispose(); + samples?.Dispose(); } #endregion From 53fa22988097e9f22d6b16f8de7ce512a1a1bbab Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 23 Jun 2021 10:09:25 +0300 Subject: [PATCH 035/168] Add ruleset resources skin before `SkinManager.DefaultSkin` --- osu.Game/Skinning/RulesetSkinProvidingContainer.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs index e46ba6d857..c117df4b09 100644 --- a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs +++ b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs @@ -50,6 +50,9 @@ namespace osu.Game.Skinning [Resolved] private AudioManager audio { get; set; } + [Resolved] + private SkinManager skinManager { get; set; } + [Resolved] private ISkinSource skinSource { get; set; } @@ -84,7 +87,7 @@ namespace osu.Game.Skinning } } - SkinSources.Add(new RulesetResourcesSkin(Ruleset, host, audio)); + SkinSources.Insert(SkinSources.IndexOf(skinManager.DefaultSkin), new RulesetResourcesSkin(Ruleset, host, audio)); } protected ISkin GetLegacyRulesetTransformedSkin(ISkin legacySkin) From d48446990616a5d10c81b35a4aa4470283dc5757 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 23 Jun 2021 10:44:21 +0300 Subject: [PATCH 036/168] Handle case where `SkinManager` sources aren't part of `AllSources` In tests. --- osu.Game/Skinning/RulesetSkinProvidingContainer.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs index c117df4b09..83e2d398f9 100644 --- a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs +++ b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs @@ -87,7 +87,11 @@ namespace osu.Game.Skinning } } - SkinSources.Insert(SkinSources.IndexOf(skinManager.DefaultSkin), new RulesetResourcesSkin(Ruleset, host, audio)); + var defaultSkinIndex = SkinSources.IndexOf(skinManager.DefaultSkin); + if (defaultSkinIndex >= 0) + SkinSources.Insert(defaultSkinIndex, new RulesetResourcesSkin(Ruleset, host, audio)); + else + SkinSources.Add(new RulesetResourcesSkin(Ruleset, host, audio)); } protected ISkin GetLegacyRulesetTransformedSkin(ISkin legacySkin) From ed0552a9e8cce45220498581eec036f9640fde3b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 23 Jun 2021 17:19:18 +0900 Subject: [PATCH 037/168] Add failing test for FK constraint conflict on reimporting modified beatmap with scores present --- .../Beatmaps/IO/ImportBeatmapTest.cs | 72 ++++++++++++++++++- osu.Game.Tests/Scores/IO/ImportScoreTest.cs | 16 ++--- 2 files changed, 77 insertions(+), 11 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs index 0d117f8755..66b5cbed0c 100644 --- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs @@ -19,7 +19,9 @@ using osu.Game.Database; using osu.Game.IO; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Scoring; using osu.Game.Tests.Resources; +using osu.Game.Tests.Scores.IO; using osu.Game.Users; using SharpCompress.Archives; using SharpCompress.Archives.Zip; @@ -185,10 +187,58 @@ namespace osu.Game.Tests.Beatmaps.IO } } - private string hashFile(string filename) + [Test] + public async Task TestImportThenImportWithChangedHashedFile() { - using (var s = File.OpenRead(filename)) - return s.ComputeMD5Hash(); + using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest))) + { + try + { + var osu = LoadOsuIntoHost(host); + + var temp = TestResources.GetTestBeatmapForImport(); + + string extractedFolder = $"{temp}_extracted"; + Directory.CreateDirectory(extractedFolder); + + try + { + var imported = await LoadOszIntoOsu(osu); + + await createScoreForBeatmap(osu, imported.Beatmaps.First()); + + using (var zip = ZipArchive.Open(temp)) + zip.WriteToDirectory(extractedFolder); + + // arbitrary write to hashed file + // this triggers the special BeatmapManager.PreImport deletion/replacement flow. + using (var sw = new FileInfo(Directory.GetFiles(extractedFolder, "*.osu").First()).AppendText()) + await sw.WriteLineAsync("// changed"); + + using (var zip = ZipArchive.Create()) + { + zip.AddAllFromDirectory(extractedFolder); + zip.SaveTo(temp, new ZipWriterOptions(CompressionType.Deflate)); + } + + var importedSecondTime = await osu.Dependencies.Get().Import(new ImportTask(temp)); + + ensureLoaded(osu); + + // check the newly "imported" beatmap is not the original. + Assert.IsTrue(imported.ID != importedSecondTime.ID); + Assert.IsTrue(imported.Beatmaps.First().ID != importedSecondTime.Beatmaps.First().ID); + } + finally + { + Directory.Delete(extractedFolder, true); + } + } + finally + { + host.Exit(); + } + } } [Test] @@ -895,6 +945,16 @@ namespace osu.Game.Tests.Beatmaps.IO Assert.IsTrue(manager.QueryBeatmapSets(_ => true).First().DeletePending); } + private Task createScoreForBeatmap(OsuGameBase osu, BeatmapInfo beatmap) + { + return ImportScoreTest.LoadScoreIntoOsu(osu, new ScoreInfo + { + OnlineScoreID = 2, + Beatmap = beatmap, + BeatmapInfoID = beatmap.ID + }, new ImportScoreTest.TestArchiveReader()); + } + private void checkBeatmapSetCount(OsuGameBase osu, int expected, bool includeDeletePending = false) { var manager = osu.Dependencies.Get(); @@ -904,6 +964,12 @@ namespace osu.Game.Tests.Beatmaps.IO : manager.GetAllUsableBeatmapSets().Count); } + private string hashFile(string filename) + { + using (var s = File.OpenRead(filename)) + return s.ComputeMD5Hash(); + } + private void checkBeatmapCount(OsuGameBase osu, int expected) { Assert.AreEqual(expected, osu.Dependencies.Get().QueryBeatmaps(_ => true).ToList().Count); diff --git a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs index 7522aca5dc..cd7d744f53 100644 --- a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs +++ b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs @@ -43,7 +43,7 @@ namespace osu.Game.Tests.Scores.IO OnlineScoreID = 12345, }; - var imported = await loadScoreIntoOsu(osu, toImport); + var imported = await LoadScoreIntoOsu(osu, toImport); Assert.AreEqual(toImport.Rank, imported.Rank); Assert.AreEqual(toImport.TotalScore, imported.TotalScore); @@ -75,7 +75,7 @@ namespace osu.Game.Tests.Scores.IO Mods = new Mod[] { new OsuModHardRock(), new OsuModDoubleTime() }, }; - var imported = await loadScoreIntoOsu(osu, toImport); + var imported = await LoadScoreIntoOsu(osu, toImport); Assert.IsTrue(imported.Mods.Any(m => m is OsuModHardRock)); Assert.IsTrue(imported.Mods.Any(m => m is OsuModDoubleTime)); @@ -105,7 +105,7 @@ namespace osu.Game.Tests.Scores.IO } }; - var imported = await loadScoreIntoOsu(osu, toImport); + var imported = await LoadScoreIntoOsu(osu, toImport); Assert.AreEqual(toImport.Statistics[HitResult.Perfect], imported.Statistics[HitResult.Perfect]); Assert.AreEqual(toImport.Statistics[HitResult.Miss], imported.Statistics[HitResult.Miss]); @@ -136,7 +136,7 @@ namespace osu.Game.Tests.Scores.IO } }; - var imported = await loadScoreIntoOsu(osu, toImport); + var imported = await LoadScoreIntoOsu(osu, toImport); var beatmapManager = osu.Dependencies.Get(); var scoreManager = osu.Dependencies.Get(); @@ -144,7 +144,7 @@ namespace osu.Game.Tests.Scores.IO beatmapManager.Delete(beatmapManager.QueryBeatmapSet(s => s.Beatmaps.Any(b => b.ID == imported.Beatmap.ID))); Assert.That(scoreManager.Query(s => s.ID == imported.ID).DeletePending, Is.EqualTo(true)); - var secondImport = await loadScoreIntoOsu(osu, imported); + var secondImport = await LoadScoreIntoOsu(osu, imported); Assert.That(secondImport, Is.Null); } finally @@ -163,7 +163,7 @@ namespace osu.Game.Tests.Scores.IO { var osu = LoadOsuIntoHost(host, true); - await loadScoreIntoOsu(osu, new ScoreInfo { OnlineScoreID = 2 }, new TestArchiveReader()); + await LoadScoreIntoOsu(osu, new ScoreInfo { OnlineScoreID = 2 }, new TestArchiveReader()); var scoreManager = osu.Dependencies.Get(); @@ -177,7 +177,7 @@ namespace osu.Game.Tests.Scores.IO } } - private async Task loadScoreIntoOsu(OsuGameBase osu, ScoreInfo score, ArchiveReader archive = null) + public static async Task LoadScoreIntoOsu(OsuGameBase osu, ScoreInfo score, ArchiveReader archive = null) { var beatmapManager = osu.Dependencies.Get(); @@ -190,7 +190,7 @@ namespace osu.Game.Tests.Scores.IO return scoreManager.GetAllUsableScores().FirstOrDefault(); } - private class TestArchiveReader : ArchiveReader + internal class TestArchiveReader : ArchiveReader { public TestArchiveReader() : base("test_archive") From dcba7bf779fa2ab78a884cc17671eefa15a71082 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 23 Jun 2021 17:19:32 +0900 Subject: [PATCH 038/168] Fix import flow potentially hitting foreign key constraint --- osu.Game/Beatmaps/BeatmapManager.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 00af06703d..e93d8c6eb5 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -181,8 +181,13 @@ namespace osu.Game.Beatmaps if (existingOnlineId != null) { Delete(existingOnlineId); - beatmaps.PurgeDeletable(s => s.ID == existingOnlineId.ID); - LogForModel(beatmapSet, $"Found existing beatmap set with same OnlineBeatmapSetID ({beatmapSet.OnlineBeatmapSetID}). It has been purged."); + + // in order to avoid a unique key constraint, immediately remove the online ID from the previous set. + existingOnlineId.OnlineBeatmapSetID = null; + foreach (var b in existingOnlineId.Beatmaps) + b.OnlineBeatmapID = null; + + LogForModel(beatmapSet, $"Found existing beatmap set with same OnlineBeatmapSetID ({beatmapSet.OnlineBeatmapSetID}). It has been deleted."); } } } From f6180b7e6a8adfa6560da82b4fe6805d5577f61e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 23 Jun 2021 17:37:24 +0900 Subject: [PATCH 039/168] Mark `static` methods as such --- osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs index 66b5cbed0c..3b355da359 100644 --- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs @@ -945,7 +945,7 @@ namespace osu.Game.Tests.Beatmaps.IO Assert.IsTrue(manager.QueryBeatmapSets(_ => true).First().DeletePending); } - private Task createScoreForBeatmap(OsuGameBase osu, BeatmapInfo beatmap) + private static Task createScoreForBeatmap(OsuGameBase osu, BeatmapInfo beatmap) { return ImportScoreTest.LoadScoreIntoOsu(osu, new ScoreInfo { @@ -955,7 +955,7 @@ namespace osu.Game.Tests.Beatmaps.IO }, new ImportScoreTest.TestArchiveReader()); } - private void checkBeatmapSetCount(OsuGameBase osu, int expected, bool includeDeletePending = false) + private static void checkBeatmapSetCount(OsuGameBase osu, int expected, bool includeDeletePending = false) { var manager = osu.Dependencies.Get(); @@ -964,18 +964,18 @@ namespace osu.Game.Tests.Beatmaps.IO : manager.GetAllUsableBeatmapSets().Count); } - private string hashFile(string filename) + private static string hashFile(string filename) { using (var s = File.OpenRead(filename)) return s.ComputeMD5Hash(); } - private void checkBeatmapCount(OsuGameBase osu, int expected) + private static void checkBeatmapCount(OsuGameBase osu, int expected) { Assert.AreEqual(expected, osu.Dependencies.Get().QueryBeatmaps(_ => true).ToList().Count); } - private void checkSingleReferencedFileCount(OsuGameBase osu, int expected) + private static void checkSingleReferencedFileCount(OsuGameBase osu, int expected) { Assert.AreEqual(expected, osu.Dependencies.Get().QueryFiles(f => f.ReferenceCount == 1).Count()); } From d148656108320559df37f00998d9346c3e2f41e4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 23 Jun 2021 18:08:34 +0900 Subject: [PATCH 040/168] Update in line with framework event structural changes (and add unbind) --- osu.Game/OsuGameBase.cs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 43c81783fe..f81eaa08a5 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -183,11 +183,7 @@ namespace osu.Game dependencies.Cache(realmFactory = new RealmContextFactory(Storage)); - Host.UpdateThreadPausing += () => - { - var blocking = realmFactory.BlockAllOperations(); - Schedule(() => blocking.Dispose()); - }; + Host.UpdateThread.ThreadPausing += onUpdateThreadPausing; AddInternal(realmFactory); @@ -363,6 +359,12 @@ namespace osu.Game Ruleset.BindValueChanged(onRulesetChanged); } + private void onUpdateThreadPausing() + { + var blocking = realmFactory.BlockAllOperations(); + Schedule(() => blocking.Dispose()); + } + protected override void LoadComplete() { base.LoadComplete(); @@ -496,6 +498,9 @@ namespace osu.Game LocalConfig?.Dispose(); contextFactory.FlushConnections(); + + if (Host != null) + Host.UpdateThread.ThreadPausing -= onUpdateThreadPausing; } } } From 1bbfbb0d8e117ac9f5dad5a4f34aa3e1726ada2a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 23 Jun 2021 19:30:08 +0900 Subject: [PATCH 041/168] Fix test that never should have worked This was only working by luck until now. It was "correctly" matching on null online ID (see logic at https://github.com/ppy/osu/blob/abc96057b2cf50e02e0ec939645f6421684495d4/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs#L199-L207). Now it works by actually matching on the online ID. --- .../TestScenePlaylistsRoomSubScreen.cs | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs index a08a91314b..c4da2ab48a 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs @@ -111,10 +111,27 @@ namespace osu.Game.Tests.Visual.Playlists public void TestBeatmapUpdatedOnReImport() { BeatmapSetInfo importedSet = null; + TestBeatmap beatmap = null; + + // this step is required to make sure the further imports actually get online IDs. + // all the playlist logic relies on online ID matching. + AddStep("remove all matching online IDs", () => + { + beatmap = new TestBeatmap(new OsuRuleset().RulesetInfo); + + var existing = manager.QueryBeatmapSets(s => s.OnlineBeatmapSetID == beatmap.BeatmapInfo.BeatmapSet.OnlineBeatmapSetID).ToList(); + + foreach (var s in existing) + { + s.OnlineBeatmapSetID = null; + foreach (var b in s.Beatmaps) + b.OnlineBeatmapID = null; + manager.Update(s); + } + }); AddStep("import altered beatmap", () => { - var beatmap = new TestBeatmap(new OsuRuleset().RulesetInfo); beatmap.BeatmapInfo.BaseDifficulty.CircleSize = 1; importedSet = manager.Import(beatmap.BeatmapInfo.BeatmapSet).Result; From 01a5d998a542c7d9e88d071525c7b1cbbbf46fd5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 23 Jun 2021 20:29:02 +0900 Subject: [PATCH 042/168] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 1dc99bb60a..d0aff7b15e 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,7 +52,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 3c52405f8e..0418e58593 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -36,7 +36,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/osu.iOS.props b/osu.iOS.props index 3689ce51f2..6e2e169149 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -93,7 +93,7 @@ - + From cd6f17537531203eaa7f1782e15c73ba520f5e11 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 24 Jun 2021 13:29:06 +0900 Subject: [PATCH 043/168] Ensure beatmap is reloaded before each playlist room test run --- .../Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs index c4da2ab48a..6d7a254ab9 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs @@ -40,8 +40,6 @@ namespace osu.Game.Tests.Visual.Playlists Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); Dependencies.Cache(manager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, Beatmap.Default)); - manager.Import(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo.BeatmapSet).Wait(); - ((DummyAPIAccess)API).HandleRequest = req => { switch (req) @@ -58,6 +56,7 @@ namespace osu.Game.Tests.Visual.Playlists [SetUpSteps] public void SetupSteps() { + AddStep("ensure has beatmap", () => manager.Import(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo.BeatmapSet).Wait()); AddStep("load match", () => LoadScreen(match = new TestPlaylistsRoomSubScreen(Room))); AddUntilStep("wait for load", () => match.IsCurrentScreen()); } From 63ab40ec24c24d9221f942f12cd2d60a3365bd42 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 24 Jun 2021 14:37:26 +0900 Subject: [PATCH 044/168] Fix potential deadlocking behaviour (and convert `ResetEvent` to `Semaphore`) --- osu.Game/Database/RealmContextFactory.cs | 71 ++++++++++++++---------- 1 file changed, 43 insertions(+), 28 deletions(-) diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index ed5931dd2b..4d81f8676f 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -26,6 +26,11 @@ namespace osu.Game.Database /// private readonly object writeLock = new object(); + /// + /// Lock object which is held during sections. + /// + private readonly SemaphoreSlim blockingLock = new SemaphoreSlim(1); + private static readonly GlobalStatistic reads = GlobalStatistics.Get("Realm", "Get (Read)"); private static readonly GlobalStatistic writes = GlobalStatistics.Get("Realm", "Get (Write)"); private static readonly GlobalStatistic refreshes = GlobalStatistics.Get("Realm", "Dirty Refreshes"); @@ -33,8 +38,6 @@ namespace osu.Game.Database private static readonly GlobalStatistic pending_writes = GlobalStatistics.Get("Realm", "Pending writes"); private static readonly GlobalStatistic active_usages = GlobalStatistics.Get("Realm", "Active usages"); - private readonly ManualResetEventSlim blockingResetEvent = new ManualResetEventSlim(true); - private Realm context; public Realm Context @@ -64,7 +67,7 @@ namespace osu.Game.Database public RealmUsage GetForRead() { reads.Value++; - return new RealmUsage(this); + return new RealmUsage(createContext()); } public RealmWriteUsage GetForWrite() @@ -73,8 +76,13 @@ namespace osu.Game.Database pending_writes.Value++; Monitor.Enter(writeLock); + return new RealmWriteUsage(createContext(), writeComplete); + } - return new RealmWriteUsage(this); + private void writeComplete() + { + Monitor.Exit(writeLock); + pending_writes.Value--; } protected override void Update() @@ -87,15 +95,22 @@ namespace osu.Game.Database private Realm createContext() { - blockingResetEvent.Wait(); - - contexts_created.Value++; - - return Realm.GetInstance(new RealmConfiguration(storage.GetFullPath($"{database_name}.realm", true)) + try { - SchemaVersion = schema_version, - MigrationCallback = onMigration, - }); + blockingLock.Wait(); + + contexts_created.Value++; + + return Realm.GetInstance(new RealmConfiguration(storage.GetFullPath($"{database_name}.realm", true)) + { + SchemaVersion = schema_version, + MigrationCallback = onMigration, + }); + } + finally + { + blockingLock.Release(); + } } private void onMigration(Migration migration, ulong lastSchemaVersion) @@ -113,21 +128,23 @@ namespace osu.Game.Database { base.Dispose(isDisposing); - BlockAllOperations(); + // In the standard case, operations will already be blocked by the Update thread "pausing" from GameHost exit. + // This avoids waiting (forever) on an already entered semaphore. + if (context != null || active_usages.Value > 0) + BlockAllOperations(); + + blockingLock?.Dispose(); } public IDisposable BlockAllOperations() { - blockingResetEvent.Reset(); + blockingLock.Wait(); flushContexts(); - return new InvokeOnDisposal(this, r => endBlockingSection()); + return new InvokeOnDisposal(this, endBlockingSection); } - private void endBlockingSection() - { - blockingResetEvent.Set(); - } + private static void endBlockingSection(RealmContextFactory factory) => factory.blockingLock.Release(); private void flushContexts() { @@ -148,13 +165,10 @@ namespace osu.Game.Database { public readonly Realm Realm; - protected readonly RealmContextFactory Factory; - - internal RealmUsage(RealmContextFactory factory) + internal RealmUsage(Realm context) { active_usages.Value++; - Factory = factory; - Realm = factory.createContext(); + Realm = context; } /// @@ -172,11 +186,13 @@ namespace osu.Game.Database /// public class RealmWriteUsage : RealmUsage { + private readonly Action onWriteComplete; private readonly Transaction transaction; - internal RealmWriteUsage(RealmContextFactory factory) - : base(factory) + internal RealmWriteUsage(Realm context, Action onWriteComplete) + : base(context) { + this.onWriteComplete = onWriteComplete; transaction = Realm.BeginWrite(); } @@ -200,8 +216,7 @@ namespace osu.Game.Database base.Dispose(); - Monitor.Exit(Factory.writeLock); - pending_writes.Value--; + onWriteComplete(); } } } From 27735eeedba9d3947ff403d1db12924f789b13ed Mon Sep 17 00:00:00 2001 From: JimmyC7834 Date: Thu, 24 Jun 2021 13:45:38 +0800 Subject: [PATCH 045/168] fixed code --- .../BeatmapListingFilterControl.cs | 23 ++++++++----- osu.Game/Overlays/BeatmapListingOverlay.cs | 34 +++++++------------ 2 files changed, 28 insertions(+), 29 deletions(-) diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs index f49d913bb2..b6a0846407 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs @@ -25,8 +25,9 @@ namespace osu.Game.Overlays.BeatmapListing public class BeatmapListingFilterControl : CompositeDrawable { /// - /// Fired when a search finishes. Contains only new items in the case of pagination. - /// Fired with BeatmapListingSearchControl when non-supporter user used supporter-only filters. + /// Fired when a search finishes. + /// SearchFinished.Type = ResultsReturned when results returned. Contains only new items in the case of pagination. + /// SearchFinished.Type = SupporterOnlyFilter when a non-supporter user applied supporter-only filters. /// public Action SearchFinished; @@ -216,7 +217,7 @@ namespace osu.Game.Overlays.BeatmapListing getSetsRequest = null; // check if an non-supporter user used supporter-only filters - if (!api.LocalUser.Value.IsSupporter && (searchControl.Ranks.Any() || searchControl.Played.Value != SearchPlayed.Any)) + if (!api.LocalUser.Value.IsSupporter) { List filters = new List(); @@ -226,12 +227,14 @@ namespace osu.Game.Overlays.BeatmapListing if (searchControl.Ranks.Any()) filters.Add(BeatmapsStrings.ListingSearchFiltersRank); - SearchFinished?.Invoke(SearchResult.SupporterOnlyFilter(filters)); - } - else - { - SearchFinished?.Invoke(SearchResult.ResultsReturned(sets)); + if (filters.Any()) + { + SearchFinished?.Invoke(SearchResult.SupporterOnlyFilter(filters)); + return; + } } + + SearchFinished?.Invoke(SearchResult.ResultsReturned(sets)); }; api.Queue(getSetsRequest); @@ -259,10 +262,14 @@ namespace osu.Game.Overlays.BeatmapListing public enum SearchResultType { + // returned with Results ResultsReturned, + // non-supporter user applied supporter-only filters SupporterOnlyFilter } + // Results only valid when Type == ResultsReturned + // Filters only valid when Type == SupporterOnlyFilter public struct SearchResult { public SearchResultType Type { get; private set; } diff --git a/osu.Game/Overlays/BeatmapListingOverlay.cs b/osu.Game/Overlays/BeatmapListingOverlay.cs index 63800e6585..c2ba3d5bc0 100644 --- a/osu.Game/Overlays/BeatmapListingOverlay.cs +++ b/osu.Game/Overlays/BeatmapListingOverlay.cs @@ -256,7 +256,7 @@ namespace osu.Game.Overlays // using string literals as there's no proper processing for LocalizeStrings yet public class SupporterRequiredDrawable : CompositeDrawable { - private OsuSpriteText filtersText; + private LinkFlowContainer supporterRequiredText; public SupporterRequiredDrawable() { @@ -275,7 +275,7 @@ namespace osu.Game.Overlays RelativeSizeAxes = Axes.Y, AutoSizeAxes = Axes.X, Direction = FillDirection.Horizontal, - Children = new[] + Children = new Drawable[] { new Sprite { @@ -285,39 +285,31 @@ namespace osu.Game.Overlays FillMode = FillMode.Fit, Texture = textures.Get(@"Online/supporter-required"), }, - createSupporterText(), + supporterRequiredText = new LinkFlowContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.Both, + Margin = new MarginPadding { Bottom = 10 }, + }, } }); } public void UpdateText(List filters) { - // use string literals for now - filtersText.Text = BeatmapsStrings.ListingSearchSupporterFilterQuoteDefault(string.Join(" and ", filters), "").ToString(); - } + supporterRequiredText.Clear(); - private Drawable createSupporterText() - { - LinkFlowContainer supporterRequiredText = new LinkFlowContainer - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - AutoSizeAxes = Axes.Both, - Margin = new MarginPadding { Bottom = 10 }, - }; - - filtersText = (OsuSpriteText)supporterRequiredText.AddText( - "_", + supporterRequiredText.AddText( + BeatmapsStrings.ListingSearchSupporterFilterQuoteDefault(string.Join(" and ", filters), "").ToString(), t => { t.Font = OsuFont.GetFont(size: 16); t.Colour = Colour4.White; } - ).First(); + ); supporterRequiredText.AddLink(BeatmapsStrings.ListingSearchSupporterFilterQuoteLinkText.ToString(), @"/store/products/supporter-tag"); - - return supporterRequiredText; } } From c9ec4b9da46bb1692fa86a5650e8efe8bbedcb95 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 24 Jun 2021 00:28:39 +0900 Subject: [PATCH 046/168] Remove RoomTestScene inheritance from simple test scenes --- .../Multiplayer/TestSceneLoungeRoomInfo.cs | 34 +++---- .../TestSceneMatchBeatmapDetailArea.cs | 23 +++-- .../Multiplayer/TestSceneMatchHeader.cs | 71 +++++++------ .../Multiplayer/TestSceneMatchLeaderboard.cs | 99 ++++++++++--------- .../TestSceneMultiplayerMatchSongSelect.cs | 2 +- .../TestSceneMultiplayerRoomManager.cs | 2 +- .../TestScenePlaylistsMatchSettingsOverlay.cs | 32 +++--- .../TestScenePlaylistsParticipantsList.cs | 17 ++-- osu.Game/Tests/Visual/TestRoomContainer.cs | 38 +++++++ 9 files changed, 190 insertions(+), 128 deletions(-) create mode 100644 osu.Game/Tests/Visual/TestRoomContainer.cs diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomInfo.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomInfo.cs index 9f24347ae9..a8cbd8b7ef 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomInfo.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomInfo.cs @@ -10,28 +10,28 @@ using osu.Game.Users; namespace osu.Game.Tests.Visual.Multiplayer { - public class TestSceneLoungeRoomInfo : RoomTestScene + public class TestSceneLoungeRoomInfo : OsuTestScene { + private TestRoomContainer roomContainer; + [SetUp] - public new void Setup() => Schedule(() => + public void Setup() => Schedule(() => { - Child = new RoomInfo + Child = roomContainer = new TestRoomContainer { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Width = 500 + Child = new RoomInfo + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Width = 500 + } }; }); - public override void SetUpSteps() - { - // Todo: Temp - } - [Test] public void TestNonSelectedRoom() { - AddStep("set null room", () => Room.RoomID.Value = null); + AddStep("set null room", () => roomContainer.Room.RoomID.Value = null); } [Test] @@ -39,11 +39,11 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("set open room", () => { - Room.RoomID.Value = 0; - Room.Name.Value = "Room 0"; - Room.Host.Value = new User { Username = "peppy", Id = 2 }; - Room.EndDate.Value = DateTimeOffset.Now.AddMonths(1); - Room.Status.Value = new RoomStatusOpen(); + roomContainer.Room.RoomID.Value = 0; + roomContainer.Room.Name.Value = "Room 0"; + roomContainer.Room.Host.Value = new User { Username = "peppy", Id = 2 }; + roomContainer.Room.EndDate.Value = DateTimeOffset.Now.AddMonths(1); + roomContainer.Room.Status.Value = new RoomStatusOpen(); }); } } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapDetailArea.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapDetailArea.cs index 9ad9f2c883..2a254b1573 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapDetailArea.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapDetailArea.cs @@ -15,7 +15,7 @@ using osuTK; namespace osu.Game.Tests.Visual.Multiplayer { - public class TestSceneMatchBeatmapDetailArea : RoomTestScene + public class TestSceneMatchBeatmapDetailArea : OsuTestScene { [Resolved] private BeatmapManager beatmapManager { get; set; } @@ -23,23 +23,28 @@ namespace osu.Game.Tests.Visual.Multiplayer [Resolved] private RulesetStore rulesetStore { get; set; } + private TestRoomContainer roomContainer; + [SetUp] - public new void Setup() => Schedule(() => + public void Setup() => Schedule(() => { - Child = new MatchBeatmapDetailArea + Child = roomContainer = new TestRoomContainer { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(500), - CreateNewItem = createNewItem + Child = new MatchBeatmapDetailArea + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(500), + CreateNewItem = createNewItem + } }; }); private void createNewItem() { - Room.Playlist.Add(new PlaylistItem + roomContainer.Room.Playlist.Add(new PlaylistItem { - ID = Room.Playlist.Count, + ID = roomContainer.Room.Playlist.Count, Beatmap = { Value = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo }, Ruleset = { Value = new OsuRuleset().RulesetInfo }, RequiredMods = diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchHeader.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchHeader.cs index 7cdc6b1a7d..50e6426472 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchHeader.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchHeader.cs @@ -11,42 +11,51 @@ using osu.Game.Users; namespace osu.Game.Tests.Visual.Multiplayer { - public class TestSceneMatchHeader : RoomTestScene + public class TestSceneMatchHeader : OsuTestScene { - public TestSceneMatchHeader() - { - Child = new Header(); - } + private TestRoomContainer roomContainer; [SetUp] - public new void Setup() => Schedule(() => + public void Setup() => Schedule(() => { - Room.Playlist.Add(new PlaylistItem + Child = roomContainer = new TestRoomContainer { - Beatmap = - { - Value = new BeatmapInfo - { - Metadata = new BeatmapMetadata - { - Title = "Title", - Artist = "Artist", - AuthorString = "Author", - }, - Version = "Version", - Ruleset = new OsuRuleset().RulesetInfo - } - }, - RequiredMods = - { - new OsuModDoubleTime(), - new OsuModNoFail(), - new OsuModRelax(), - } - }); - - Room.Name.Value = "A very awesome room"; - Room.Host.Value = new User { Id = 2, Username = "peppy" }; + Child = new Header() + }; }); + + [Test] + public void TestBasicRoom() + { + AddStep("set basic room", () => + { + roomContainer.Room.Playlist.Add(new PlaylistItem + { + Beatmap = + { + Value = new BeatmapInfo + { + Metadata = new BeatmapMetadata + { + Title = "Title", + Artist = "Artist", + AuthorString = "Author", + }, + Version = "Version", + Ruleset = new OsuRuleset().RulesetInfo + } + }, + RequiredMods = + { + new OsuModDoubleTime(), + new OsuModNoFail(), + new OsuModRelax(), + } + }); + + roomContainer.Room.Name.Value = "A very awesome room"; + roomContainer.Room.Host.Value = new User { Id = 2, Username = "peppy" }; + }); + } } } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchLeaderboard.cs index 64eaf0556b..566dc9fc00 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchLeaderboard.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchLeaderboard.cs @@ -2,72 +2,75 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; -using Newtonsoft.Json; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Game.Online.API; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Online.Rooms; using osu.Game.Screens.OnlinePlay.Match.Components; using osu.Game.Users; using osuTK; namespace osu.Game.Tests.Visual.Multiplayer { - public class TestSceneMatchLeaderboard : RoomTestScene + public class TestSceneMatchLeaderboard : OsuTestScene { - protected override bool UseOnlineAPI => true; - - public TestSceneMatchLeaderboard() - { - Add(new MatchLeaderboard - { - Origin = Anchor.Centre, - Anchor = Anchor.Centre, - Size = new Vector2(550f, 450f), - Scope = MatchLeaderboardScope.Overall, - }); - } - [BackgroundDependencyLoader] - private void load(IAPIProvider api) + private void load() { - var req = new GetRoomScoresRequest(); - req.Success += v => { }; - req.Failure += _ => { }; + ((DummyAPIAccess)API).HandleRequest = r => + { + switch (r) + { + case GetRoomLeaderboardRequest leaderboardRequest: + leaderboardRequest.TriggerSuccess(new APILeaderboard + { + Leaderboard = new List + { + new APIUserScoreAggregate + { + UserID = 2, + User = new User { Id = 2, Username = "peppy" }, + TotalScore = 995533, + RoomID = 3, + CompletedBeatmaps = 1, + TotalAttempts = 6, + Accuracy = 0.9851 + }, + new APIUserScoreAggregate + { + UserID = 1040328, + User = new User { Id = 1040328, Username = "smoogipoo" }, + TotalScore = 981100, + RoomID = 3, + CompletedBeatmaps = 1, + TotalAttempts = 9, + Accuracy = 0.937 + } + } + }); + return true; + } - api.Queue(req); + return false; + }; } [SetUp] - public new void Setup() => Schedule(() => + public void Setup() => Schedule(() => { - Room.RoomID.Value = 3; + Child = new TestRoomContainer + { + Room = { RoomID = { Value = 3 } }, + Child = new MatchLeaderboard + { + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + Size = new Vector2(550f, 450f), + Scope = MatchLeaderboardScope.Overall, + } + }; }); - - private class GetRoomScoresRequest : APIRequest> - { - protected override string Target => "rooms/3/leaderboard"; - } - - private class RoomScore - { - [JsonProperty("user")] - public User User { get; set; } - - [JsonProperty("accuracy")] - public double Accuracy { get; set; } - - [JsonProperty("total_score")] - public int TotalScore { get; set; } - - [JsonProperty("pp")] - public double PP { get; set; } - - [JsonProperty("attempts")] - public int TotalAttempts { get; set; } - - [JsonProperty("completed")] - public int CompletedAttempts { get; set; } - } } } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs index 5b059c06f5..2725ef5976 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs @@ -29,7 +29,7 @@ using osu.Game.Screens.Select; namespace osu.Game.Tests.Visual.Multiplayer { - public class TestSceneMultiplayerMatchSongSelect : RoomTestScene + public class TestSceneMultiplayerMatchSongSelect : ScreenTestScene { private BeatmapManager manager; private RulesetStore rulesets; diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerRoomManager.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerRoomManager.cs index c008771fd9..80e36916b1 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerRoomManager.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerRoomManager.cs @@ -12,7 +12,7 @@ using osu.Game.Tests.Beatmaps; namespace osu.Game.Tests.Visual.Multiplayer { [HeadlessTest] - public class TestSceneMultiplayerRoomManager : RoomTestScene + public class TestSceneMultiplayerRoomManager : OsuTestScene { private TestMultiplayerRoomContainer roomContainer; private TestMultiplayerRoomManager roomManager => roomContainer.RoomManager; diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs index 44a79b6598..3055a2c6c7 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs @@ -15,23 +15,25 @@ using osu.Game.Screens.OnlinePlay.Playlists; namespace osu.Game.Tests.Visual.Playlists { - public class TestScenePlaylistsMatchSettingsOverlay : RoomTestScene + public class TestScenePlaylistsMatchSettingsOverlay : OsuTestScene { [Cached(Type = typeof(IRoomManager))] private TestRoomManager roomManager = new TestRoomManager(); + private TestRoomContainer roomContainer; private TestRoomSettings settings; [SetUp] - public new void Setup() => Schedule(() => + public void Setup() => Schedule(() => { - settings = new TestRoomSettings + Child = roomContainer = new TestRoomContainer { - RelativeSizeAxes = Axes.Both, - State = { Value = Visibility.Visible } + Child = settings = new TestRoomSettings + { + RelativeSizeAxes = Axes.Both, + State = { Value = Visibility.Visible } + } }; - - Child = settings; }); [Test] @@ -39,19 +41,19 @@ namespace osu.Game.Tests.Visual.Playlists { AddStep("clear name and beatmap", () => { - Room.Name.Value = ""; - Room.Playlist.Clear(); + roomContainer.Room.Name.Value = ""; + roomContainer.Room.Playlist.Clear(); }); AddAssert("button disabled", () => !settings.ApplyButton.Enabled.Value); - AddStep("set name", () => Room.Name.Value = "Room name"); + AddStep("set name", () => roomContainer.Room.Name.Value = "Room name"); AddAssert("button disabled", () => !settings.ApplyButton.Enabled.Value); - AddStep("set beatmap", () => Room.Playlist.Add(new PlaylistItem { Beatmap = { Value = CreateBeatmap(Ruleset.Value).BeatmapInfo } })); + AddStep("set beatmap", () => roomContainer.Room.Playlist.Add(new PlaylistItem { Beatmap = { Value = CreateBeatmap(Ruleset.Value).BeatmapInfo } })); AddAssert("button enabled", () => settings.ApplyButton.Enabled.Value); - AddStep("clear name", () => Room.Name.Value = ""); + AddStep("clear name", () => roomContainer.Room.Name.Value = ""); AddAssert("button disabled", () => !settings.ApplyButton.Enabled.Value); } @@ -67,7 +69,7 @@ namespace osu.Game.Tests.Visual.Playlists { settings.NameField.Current.Value = expected_name; settings.DurationField.Current.Value = expectedDuration; - Room.Playlist.Add(new PlaylistItem { Beatmap = { Value = CreateBeatmap(Ruleset.Value).BeatmapInfo } }); + roomContainer.Room.Playlist.Add(new PlaylistItem { Beatmap = { Value = CreateBeatmap(Ruleset.Value).BeatmapInfo } }); roomManager.CreateRequested = r => { @@ -88,8 +90,8 @@ namespace osu.Game.Tests.Visual.Playlists AddStep("setup", () => { - Room.Name.Value = "Test Room"; - Room.Playlist.Add(new PlaylistItem { Beatmap = { Value = CreateBeatmap(Ruleset.Value).BeatmapInfo } }); + roomContainer.Room.Name.Value = "Test Room"; + roomContainer.Room.Playlist.Add(new PlaylistItem { Beatmap = { Value = CreateBeatmap(Ruleset.Value).BeatmapInfo } }); fail = true; roomManager.CreateRequested = _ => !fail; diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsParticipantsList.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsParticipantsList.cs index 255f147ec9..083930698f 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsParticipantsList.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsParticipantsList.cs @@ -8,16 +8,21 @@ using osu.Game.Users; namespace osu.Game.Tests.Visual.Playlists { - public class TestScenePlaylistsParticipantsList : RoomTestScene + public class TestScenePlaylistsParticipantsList : OsuTestScene { + private TestRoomContainer roomContainer; + [SetUp] - public new void Setup() => Schedule(() => + public void Setup() => Schedule(() => { - Room.RoomID.Value = 7; + Child = roomContainer = new TestRoomContainer + { + Room = { RoomID = { Value = 7 } } + }; for (int i = 0; i < 50; i++) { - Room.RecentParticipants.Add(new User + roomContainer.Room.RecentParticipants.Add(new User { Username = "peppy", Statistics = new UserStatistics { GlobalRank = 1234 }, @@ -31,7 +36,7 @@ namespace osu.Game.Tests.Visual.Playlists { AddStep("create component", () => { - Child = new ParticipantsDisplay(Direction.Horizontal) + roomContainer.Child = new ParticipantsDisplay(Direction.Horizontal) { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -45,7 +50,7 @@ namespace osu.Game.Tests.Visual.Playlists { AddStep("create component", () => { - Child = new ParticipantsDisplay(Direction.Vertical) + roomContainer.Child = new ParticipantsDisplay(Direction.Vertical) { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game/Tests/Visual/TestRoomContainer.cs b/osu.Game/Tests/Visual/TestRoomContainer.cs new file mode 100644 index 0000000000..c4af89cd51 --- /dev/null +++ b/osu.Game/Tests/Visual/TestRoomContainer.cs @@ -0,0 +1,38 @@ +// 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.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Online.Rooms; + +namespace osu.Game.Tests.Visual +{ + /// + /// Contains a that is resolvable by components in test scenes. + /// + public class TestRoomContainer : Container + { + /// + /// The cached . + /// + public readonly Room Room = new Room(); + + [Cached] + private readonly Bindable roomBindable; + + public TestRoomContainer() + { + RelativeSizeAxes = Axes.Both; + roomBindable = new Bindable(Room); + } + + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) + { + var dependencies = new CachedModelDependencyContainer(base.CreateChildDependencies(parent)); + dependencies.Model.Value = Room; + return dependencies; + } + } +} From a98b5618b84e873adffeed23fdfd2a15a58a907c Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 24 Jun 2021 10:07:38 +0300 Subject: [PATCH 047/168] Convert `RulesetResourcesSkin` to plain `ResourcesSkin` and pass non-null resources --- ...lesetResourcesSkin.cs => ResourcesSkin.cs} | 30 ++++++++----------- .../Skinning/RulesetSkinProvidingContainer.cs | 18 +++++++---- 2 files changed, 25 insertions(+), 23 deletions(-) rename osu.Game/Skinning/{RulesetResourcesSkin.cs => ResourcesSkin.cs} (61%) diff --git a/osu.Game/Skinning/RulesetResourcesSkin.cs b/osu.Game/Skinning/ResourcesSkin.cs similarity index 61% rename from osu.Game/Skinning/RulesetResourcesSkin.cs rename to osu.Game/Skinning/ResourcesSkin.cs index 5cf2eec338..3d17d7cc3d 100644 --- a/osu.Game/Skinning/RulesetResourcesSkin.cs +++ b/osu.Game/Skinning/ResourcesSkin.cs @@ -13,38 +13,32 @@ using osu.Framework.Graphics.Textures; using osu.Framework.IO.Stores; using osu.Framework.Platform; using osu.Game.Audio; -using osu.Game.Rulesets; namespace osu.Game.Skinning { /// - /// An providing the resources of the ruleset for accessibility during lookups. + /// An that uses an underlying with namespaces for resources retrieval. /// - public class RulesetResourcesSkin : ISkin + public class ResourcesSkin : ISkin { - private readonly TextureStore? textures; - private readonly ISampleStore? samples; + private readonly TextureStore textures; + private readonly ISampleStore samples; - public RulesetResourcesSkin(Ruleset ruleset, GameHost host, AudioManager audio) + public ResourcesSkin(IResourceStore resources, GameHost host, AudioManager audio) { - IResourceStore? resources = ruleset.CreateResourceStore(); - - if (resources != null) - { - textures = new TextureStore(host.CreateTextureLoaderStore(new NamespacedResourceStore(resources, @"Textures"))); - samples = audio.GetSampleStore(new NamespacedResourceStore(resources, @"Samples")); - } + textures = new TextureStore(host.CreateTextureLoaderStore(new NamespacedResourceStore(resources, @"Textures"))); + samples = audio.GetSampleStore(new NamespacedResourceStore(resources, @"Samples")); } public Drawable? GetDrawableComponent(ISkinComponent component) => null; - public Texture? GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => textures?.Get(componentName, wrapModeS, wrapModeT); + public Texture? GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => textures.Get(componentName, wrapModeS, wrapModeT); public ISample? GetSample(ISampleInfo sampleInfo) { foreach (var lookup in sampleInfo.LookupNames) { - ISample? sample = samples?.Get(lookup); + ISample? sample = samples.Get(lookup); if (sample != null) return sample; } @@ -56,7 +50,7 @@ namespace osu.Game.Skinning #region Disposal - ~RulesetResourcesSkin() + ~ResourcesSkin() { // required to potentially clean up sample store from audio hierarchy. Dispose(false); @@ -77,8 +71,8 @@ namespace osu.Game.Skinning isDisposed = true; - textures?.Dispose(); - samples?.Dispose(); + textures.Dispose(); + samples.Dispose(); } #endregion diff --git a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs index 83e2d398f9..54bf91523f 100644 --- a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs +++ b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs @@ -6,6 +6,7 @@ using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.IO.Stores; using osu.Framework.Platform; using osu.Game.Beatmaps; using osu.Game.Rulesets; @@ -87,11 +88,18 @@ namespace osu.Game.Skinning } } - var defaultSkinIndex = SkinSources.IndexOf(skinManager.DefaultSkin); - if (defaultSkinIndex >= 0) - SkinSources.Insert(defaultSkinIndex, new RulesetResourcesSkin(Ruleset, host, audio)); - else - SkinSources.Add(new RulesetResourcesSkin(Ruleset, host, audio)); + if (Ruleset.CreateResourceStore() is IResourceStore resources) + { + int defaultSkinIndex = SkinSources.IndexOf(skinManager.DefaultSkin); + + if (defaultSkinIndex >= 0) + SkinSources.Insert(defaultSkinIndex, new ResourcesSkin(resources, host, audio)); + else + { + // Tests may potentially override the SkinManager with another source that doesn't include it in AllSources. + SkinSources.Add(new ResourcesSkin(resources, host, audio)); + } + } } protected ISkin GetLegacyRulesetTransformedSkin(ISkin legacySkin) From 4af119a407c649e364fe917fa63049f52b5cb4a1 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 24 Jun 2021 16:29:06 +0900 Subject: [PATCH 048/168] Re-namespace --- osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomInfo.cs | 1 + .../Visual/Multiplayer/TestSceneMatchBeatmapDetailArea.cs | 1 + osu.Game.Tests/Visual/Multiplayer/TestSceneMatchHeader.cs | 1 + osu.Game.Tests/Visual/Multiplayer/TestSceneMatchLeaderboard.cs | 1 + .../Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs | 1 + .../Visual/Playlists/TestScenePlaylistsParticipantsList.cs | 1 + osu.Game/Tests/Visual/{ => OnlinePlay}/TestRoomContainer.cs | 2 +- 7 files changed, 7 insertions(+), 1 deletion(-) rename osu.Game/Tests/Visual/{ => OnlinePlay}/TestRoomContainer.cs (96%) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomInfo.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomInfo.cs index a8cbd8b7ef..73f7865208 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomInfo.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomInfo.cs @@ -6,6 +6,7 @@ using NUnit.Framework; using osu.Framework.Graphics; using osu.Game.Online.Rooms.RoomStatuses; using osu.Game.Screens.OnlinePlay.Lounge.Components; +using osu.Game.Tests.Visual.OnlinePlay; using osu.Game.Users; namespace osu.Game.Tests.Visual.Multiplayer diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapDetailArea.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapDetailArea.cs index 2a254b1573..d114ae8abf 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapDetailArea.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapDetailArea.cs @@ -11,6 +11,7 @@ using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Screens.OnlinePlay.Components; using osu.Game.Tests.Beatmaps; +using osu.Game.Tests.Visual.OnlinePlay; using osuTK; namespace osu.Game.Tests.Visual.Multiplayer diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchHeader.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchHeader.cs index 50e6426472..25cdfd4a90 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchHeader.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchHeader.cs @@ -7,6 +7,7 @@ using osu.Game.Online.Rooms; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Screens.OnlinePlay.Match.Components; +using osu.Game.Tests.Visual.OnlinePlay; using osu.Game.Users; namespace osu.Game.Tests.Visual.Multiplayer diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchLeaderboard.cs index 566dc9fc00..467df97cb9 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchLeaderboard.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchLeaderboard.cs @@ -9,6 +9,7 @@ using osu.Game.Online.API; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Rooms; using osu.Game.Screens.OnlinePlay.Match.Components; +using osu.Game.Tests.Visual.OnlinePlay; using osu.Game.Users; using osuTK; diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs index 3055a2c6c7..94aed2ecfa 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs @@ -12,6 +12,7 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Online.Rooms; using osu.Game.Screens.OnlinePlay; using osu.Game.Screens.OnlinePlay.Playlists; +using osu.Game.Tests.Visual.OnlinePlay; namespace osu.Game.Tests.Visual.Playlists { diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsParticipantsList.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsParticipantsList.cs index 083930698f..02ddfe4e79 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsParticipantsList.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsParticipantsList.cs @@ -4,6 +4,7 @@ using NUnit.Framework; using osu.Framework.Graphics; using osu.Game.Screens.OnlinePlay.Components; +using osu.Game.Tests.Visual.OnlinePlay; using osu.Game.Users; namespace osu.Game.Tests.Visual.Playlists diff --git a/osu.Game/Tests/Visual/TestRoomContainer.cs b/osu.Game/Tests/Visual/OnlinePlay/TestRoomContainer.cs similarity index 96% rename from osu.Game/Tests/Visual/TestRoomContainer.cs rename to osu.Game/Tests/Visual/OnlinePlay/TestRoomContainer.cs index c4af89cd51..93dc836d23 100644 --- a/osu.Game/Tests/Visual/TestRoomContainer.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/TestRoomContainer.cs @@ -7,7 +7,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Online.Rooms; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.OnlinePlay { /// /// Contains a that is resolvable by components in test scenes. From 9287fae5f764610063e19976986d5b753e39f786 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 24 Jun 2021 16:54:09 +0900 Subject: [PATCH 049/168] Simplify --- osu.Game/Tests/Visual/OnlinePlay/TestRoomContainer.cs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/osu.Game/Tests/Visual/OnlinePlay/TestRoomContainer.cs b/osu.Game/Tests/Visual/OnlinePlay/TestRoomContainer.cs index 93dc836d23..c2abcdfd08 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/TestRoomContainer.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/TestRoomContainer.cs @@ -19,19 +19,18 @@ namespace osu.Game.Tests.Visual.OnlinePlay /// public readonly Room Room = new Room(); - [Cached] - private readonly Bindable roomBindable; - public TestRoomContainer() { RelativeSizeAxes = Axes.Both; - roomBindable = new Bindable(Room); } protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) { - var dependencies = new CachedModelDependencyContainer(base.CreateChildDependencies(parent)); - dependencies.Model.Value = Room; + var dependencies = new DependencyContainer( + new CachedModelDependencyContainer(parent) { Model = { Value = Room } }); + + dependencies.Cache(new Bindable(Room)); + return dependencies; } } From a7b5c3bed124798ed1a9b46ff391bd4df63337cf Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 24 Jun 2021 17:01:28 +0900 Subject: [PATCH 050/168] Add OnlinePlaySubScreenTestScene --- .../TestScenePlaylistsSongSelect.cs | 3 +- .../TestScenePlaylistsRoomSubScreen.cs | 3 +- .../OnlinePlaySubScreenTestScene.cs | 64 +++++++++++++++++++ 3 files changed, 68 insertions(+), 2 deletions(-) create mode 100644 osu.Game/Tests/Visual/OnlinePlay/OnlinePlaySubScreenTestScene.cs diff --git a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs index d95a95ebe5..b958afd183 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs @@ -20,10 +20,11 @@ using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Screens.OnlinePlay.Components; using osu.Game.Screens.OnlinePlay.Playlists; +using osu.Game.Tests.Visual.OnlinePlay; namespace osu.Game.Tests.Visual.Multiplayer { - public class TestScenePlaylistsSongSelect : RoomTestScene + public class TestScenePlaylistsSongSelect : OnlinePlaySubScreenTestScene { [Resolved] private BeatmapManager beatmapManager { get; set; } diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs index a08a91314b..73f9fcd8fd 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs @@ -19,12 +19,13 @@ using osu.Game.Screens.OnlinePlay; using osu.Game.Screens.OnlinePlay.Playlists; using osu.Game.Screens.Play; using osu.Game.Tests.Beatmaps; +using osu.Game.Tests.Visual.OnlinePlay; using osu.Game.Users; using osuTK.Input; namespace osu.Game.Tests.Visual.Playlists { - public class TestScenePlaylistsRoomSubScreen : RoomTestScene + public class TestScenePlaylistsRoomSubScreen : OnlinePlaySubScreenTestScene { [Cached(typeof(IRoomManager))] private readonly TestRoomManager roomManager = new TestRoomManager(); diff --git a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlaySubScreenTestScene.cs b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlaySubScreenTestScene.cs new file mode 100644 index 0000000000..27cb6496ce --- /dev/null +++ b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlaySubScreenTestScene.cs @@ -0,0 +1,64 @@ +// 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 osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Game.Online.Rooms; +using osu.Game.Screens; +using osu.Game.Screens.OnlinePlay; + +namespace osu.Game.Tests.Visual.OnlinePlay +{ + public abstract class OnlinePlaySubScreenTestScene : ScreenTestScene + { + /// + /// The cached . + /// + protected Room Room { get; private set; } + + public override void SetUpSteps() + { + base.SetUpSteps(); + + AddStep("create dependencies", () => LoadScreen(new DependenciesScreen(CreateScreenDependencies))); + } + + /// + /// Creates dependencies for any pushed via . + /// Invoked at the start of every test via . + /// + /// + /// This should be overridden to add any custom dependencies required by subclasses of . + /// + /// The parent dependency container. + /// The resultant dependency container. + protected virtual IReadOnlyDependencyContainer CreateScreenDependencies(IReadOnlyDependencyContainer parent) + { + Room = new Room(); + + var dependencies = new DependencyContainer( + new CachedModelDependencyContainer(parent) { Model = { Value = Room } }); + + dependencies.Cache(new Bindable(Room)); + + return dependencies; + } + + /// + /// A dummy screen used for injecting new dependencies into the hierarchy before any screen is pushed via . + /// + private class DependenciesScreen : OsuScreen + { + private readonly Func createDependenciesFunc; + + public DependenciesScreen(Func createDependenciesFunc) + { + this.createDependenciesFunc = createDependenciesFunc; + } + + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) + => createDependenciesFunc(base.CreateChildDependencies(parent)); + } + } +} From 6922de12c692c7ae6835579d868b1a92100487f6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 24 Jun 2021 17:17:12 +0900 Subject: [PATCH 051/168] Add extra null safety in dispose call --- osu.Game/OsuGameBase.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 7cc34114bf..d0be03f8c1 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -499,7 +499,7 @@ namespace osu.Game contextFactory.FlushConnections(); - if (Host != null) + if (Host?.UpdateThread != null) Host.UpdateThread.ThreadPausing -= onUpdateThreadPausing; } } From 5115299e9aa3fc7119dfa12784c1bee4a034ddb9 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 24 Jun 2021 19:09:31 +0900 Subject: [PATCH 052/168] Merge RoomManagerTestScene into OnlinePlaySubScreenTestScene --- .../Multiplayer/RoomManagerTestScene.cs | 61 ------------ .../Visual/Multiplayer/TestRoomManager.cs | 35 ------- .../TestSceneLoungeRoomsContainer.cs | 17 ++-- .../TestScenePlaylistsSongSelect.cs | 24 ++--- .../TestScenePlaylistsLoungeSubScreen.cs | 6 +- .../TestScenePlaylistsRoomSubScreen.cs | 58 +++-------- .../OnlinePlaySubScreenTestScene.cs | 95 +++++++++++++++++-- .../Visual/OnlinePlay/TestRoomContainer.cs | 2 +- 8 files changed, 129 insertions(+), 169 deletions(-) delete mode 100644 osu.Game.Tests/Visual/Multiplayer/RoomManagerTestScene.cs delete mode 100644 osu.Game.Tests/Visual/Multiplayer/TestRoomManager.cs diff --git a/osu.Game.Tests/Visual/Multiplayer/RoomManagerTestScene.cs b/osu.Game.Tests/Visual/Multiplayer/RoomManagerTestScene.cs deleted file mode 100644 index c665a57452..0000000000 --- a/osu.Game.Tests/Visual/Multiplayer/RoomManagerTestScene.cs +++ /dev/null @@ -1,61 +0,0 @@ -// 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 osu.Framework.Allocation; -using osu.Game.Beatmaps; -using osu.Game.Online.Rooms; -using osu.Game.Rulesets; -using osu.Game.Screens.OnlinePlay; -using osu.Game.Users; - -namespace osu.Game.Tests.Visual.Multiplayer -{ - public abstract class RoomManagerTestScene : RoomTestScene - { - [Cached(Type = typeof(IRoomManager))] - protected TestRoomManager RoomManager { get; } = new TestRoomManager(); - - public override void SetUpSteps() - { - base.SetUpSteps(); - - AddStep("clear rooms", () => RoomManager.Rooms.Clear()); - } - - protected void AddRooms(int count, RulesetInfo ruleset = null) - { - AddStep("add rooms", () => - { - for (int i = 0; i < count; i++) - { - var room = new Room - { - RoomID = { Value = i }, - Name = { Value = $"Room {i}" }, - Host = { Value = new User { Username = "Host" } }, - EndDate = { Value = DateTimeOffset.Now + TimeSpan.FromSeconds(10) }, - Category = { Value = i % 2 == 0 ? RoomCategory.Spotlight : RoomCategory.Normal } - }; - - if (ruleset != null) - { - room.Playlist.Add(new PlaylistItem - { - Ruleset = { Value = ruleset }, - Beatmap = - { - Value = new BeatmapInfo - { - Metadata = new BeatmapMetadata() - } - } - }); - } - - RoomManager.Rooms.Add(room); - } - }); - } - } -} diff --git a/osu.Game.Tests/Visual/Multiplayer/TestRoomManager.cs b/osu.Game.Tests/Visual/Multiplayer/TestRoomManager.cs deleted file mode 100644 index 1785c99784..0000000000 --- a/osu.Game.Tests/Visual/Multiplayer/TestRoomManager.cs +++ /dev/null @@ -1,35 +0,0 @@ -// 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 osu.Framework.Bindables; -using osu.Game.Online.Rooms; -using osu.Game.Screens.OnlinePlay; - -namespace osu.Game.Tests.Visual.Multiplayer -{ - public class TestRoomManager : IRoomManager - { - public event Action RoomsUpdated - { - add { } - remove { } - } - - public readonly BindableList Rooms = new BindableList(); - - public IBindable InitialRoomsReceived { get; } = new Bindable(true); - - IBindableList IRoomManager.Rooms => Rooms; - - public void CreateRoom(Room room, Action onSuccess = null, Action onError = null) => Rooms.Add(room); - - public void JoinRoom(Room room, Action onSuccess = null, Action onError = null) - { - } - - public void PartRoom() - { - } - } -} diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs index 5682fd5c3c..d63d52a463 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs @@ -10,12 +10,13 @@ using osu.Game.Online.Rooms; using osu.Game.Rulesets.Catch; using osu.Game.Rulesets.Osu; using osu.Game.Screens.OnlinePlay.Lounge.Components; +using osu.Game.Tests.Visual.OnlinePlay; using osuTK.Graphics; using osuTK.Input; namespace osu.Game.Tests.Visual.Multiplayer { - public class TestSceneLoungeRoomsContainer : RoomManagerTestScene + public class TestSceneLoungeRoomsContainer : OnlinePlaySubScreenTestScene { private RoomsContainer container; @@ -34,7 +35,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestBasicListChanges() { - AddRooms(3); + AddStep("add rooms", () => RoomManager.AddRooms(3)); AddAssert("has 3 rooms", () => container.Rooms.Count == 3); AddStep("remove first room", () => RoomManager.Rooms.Remove(RoomManager.Rooms.FirstOrDefault())); @@ -51,7 +52,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestKeyboardNavigation() { - AddRooms(3); + AddStep("add rooms", () => RoomManager.AddRooms(3)); AddAssert("no selection", () => checkRoomSelected(null)); @@ -72,7 +73,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestClickDeselection() { - AddRooms(1); + AddStep("add room", () => RoomManager.AddRooms(1)); AddAssert("no selection", () => checkRoomSelected(null)); @@ -91,7 +92,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestStringFiltering() { - AddRooms(4); + AddStep("add rooms", () => RoomManager.AddRooms(4)); AddUntilStep("4 rooms visible", () => container.Rooms.Count(r => r.IsPresent) == 4); @@ -107,8 +108,8 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestRulesetFiltering() { - AddRooms(2, new OsuRuleset().RulesetInfo); - AddRooms(3, new CatchRuleset().RulesetInfo); + AddStep("add rooms", () => RoomManager.AddRooms(2, new OsuRuleset().RulesetInfo)); + AddStep("add rooms", () => RoomManager.AddRooms(3, new CatchRuleset().RulesetInfo)); AddUntilStep("5 rooms visible", () => container.Rooms.Count(r => r.IsPresent) == 5); @@ -121,7 +122,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("3 rooms visible", () => container.Rooms.Count(r => r.IsPresent) == 3); } - private bool checkRoomSelected(Room room) => Room == room; + private bool checkRoomSelected(Room room) => SelectedRoom.Value == room; private void joinRequested(Room room) => room.Status.Value = new JoinedRoomStatus(); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs index b958afd183..4f10877f95 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs @@ -14,6 +14,7 @@ using osu.Framework.Platform; using osu.Framework.Screens; using osu.Framework.Utils; using osu.Game.Beatmaps; +using osu.Game.Online.Rooms; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu; @@ -86,6 +87,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("reset", () => { + SelectedRoom.Value = new Room(); Ruleset.Value = new OsuRuleset().RulesetInfo; Beatmap.SetDefault(); SelectedMods.Value = Array.Empty(); @@ -99,14 +101,14 @@ namespace osu.Game.Tests.Visual.Multiplayer public void TestItemAddedIfEmptyOnStart() { AddStep("finalise selection", () => songSelect.FinaliseSelection()); - AddAssert("playlist has 1 item", () => Room.Playlist.Count == 1); + AddAssert("playlist has 1 item", () => SelectedRoom.Value.Playlist.Count == 1); } [Test] public void TestItemAddedWhenCreateNewItemClicked() { AddStep("create new item", () => songSelect.BeatmapDetails.CreateNewItem()); - AddAssert("playlist has 1 item", () => Room.Playlist.Count == 1); + AddAssert("playlist has 1 item", () => SelectedRoom.Value.Playlist.Count == 1); } [Test] @@ -114,7 +116,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("create new item", () => songSelect.BeatmapDetails.CreateNewItem()); AddStep("finalise selection", () => songSelect.FinaliseSelection()); - AddAssert("playlist has 1 item", () => Room.Playlist.Count == 1); + AddAssert("playlist has 1 item", () => SelectedRoom.Value.Playlist.Count == 1); } [Test] @@ -122,7 +124,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("create new item", () => songSelect.BeatmapDetails.CreateNewItem()); AddStep("create new item", () => songSelect.BeatmapDetails.CreateNewItem()); - AddAssert("playlist has 2 items", () => Room.Playlist.Count == 2); + AddAssert("playlist has 2 items", () => SelectedRoom.Value.Playlist.Count == 2); } [Test] @@ -132,13 +134,13 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("create new item", () => songSelect.BeatmapDetails.CreateNewItem()); AddStep("rearrange", () => { - var item = Room.Playlist[0]; - Room.Playlist.RemoveAt(0); - Room.Playlist.Add(item); + var item = SelectedRoom.Value.Playlist[0]; + SelectedRoom.Value.Playlist.RemoveAt(0); + SelectedRoom.Value.Playlist.Add(item); }); AddStep("create new item", () => songSelect.BeatmapDetails.CreateNewItem()); - AddAssert("new item has id 2", () => Room.Playlist.Last().ID == 2); + AddAssert("new item has id 2", () => SelectedRoom.Value.Playlist.Last().ID == 2); } /// @@ -152,8 +154,8 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("change mod rate", () => ((OsuModDoubleTime)SelectedMods.Value[0]).SpeedChange.Value = 2); AddStep("create item", () => songSelect.BeatmapDetails.CreateNewItem()); - AddAssert("item 1 has rate 1.5", () => Precision.AlmostEquals(1.5, ((OsuModDoubleTime)Room.Playlist.First().RequiredMods[0]).SpeedChange.Value)); - AddAssert("item 2 has rate 2", () => Precision.AlmostEquals(2, ((OsuModDoubleTime)Room.Playlist.Last().RequiredMods[0]).SpeedChange.Value)); + AddAssert("item 1 has rate 1.5", () => Precision.AlmostEquals(1.5, ((OsuModDoubleTime)SelectedRoom.Value.Playlist.First().RequiredMods[0]).SpeedChange.Value)); + AddAssert("item 2 has rate 2", () => Precision.AlmostEquals(2, ((OsuModDoubleTime)SelectedRoom.Value.Playlist.Last().RequiredMods[0]).SpeedChange.Value)); } /// @@ -175,7 +177,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("create item", () => songSelect.BeatmapDetails.CreateNewItem()); AddStep("change stored mod rate", () => mod.SpeedChange.Value = 2); - AddAssert("item has rate 1.5", () => Precision.AlmostEquals(1.5, ((OsuModDoubleTime)Room.Playlist.First().RequiredMods[0]).SpeedChange.Value)); + AddAssert("item has rate 1.5", () => Precision.AlmostEquals(1.5, ((OsuModDoubleTime)SelectedRoom.Value.Playlist.First().RequiredMods[0]).SpeedChange.Value)); } private class TestPlaylistsSongSelect : PlaylistsSongSelect diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsLoungeSubScreen.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsLoungeSubScreen.cs index 618447eae2..62a6f40d65 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsLoungeSubScreen.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsLoungeSubScreen.cs @@ -10,11 +10,11 @@ using osu.Game.Graphics.Containers; using osu.Game.Screens.OnlinePlay.Lounge; using osu.Game.Screens.OnlinePlay.Lounge.Components; using osu.Game.Screens.OnlinePlay.Playlists; -using osu.Game.Tests.Visual.Multiplayer; +using osu.Game.Tests.Visual.OnlinePlay; namespace osu.Game.Tests.Visual.Playlists { - public class TestScenePlaylistsLoungeSubScreen : RoomManagerTestScene + public class TestScenePlaylistsLoungeSubScreen : OnlinePlaySubScreenTestScene { private LoungeSubScreen loungeScreen; @@ -37,7 +37,7 @@ namespace osu.Game.Tests.Visual.Playlists [Test] public void TestScrollSelectedIntoView() { - AddRooms(30); + AddStep("add rooms", () => RoomManager.AddRooms(30)); AddUntilStep("first room is not masked", () => checkRoomVisible(roomsContainer.Rooms.First())); diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs index 73f9fcd8fd..98efdd98ef 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs @@ -15,7 +15,6 @@ using osu.Game.Online.API; using osu.Game.Online.Rooms; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; -using osu.Game.Screens.OnlinePlay; using osu.Game.Screens.OnlinePlay.Playlists; using osu.Game.Screens.Play; using osu.Game.Tests.Beatmaps; @@ -27,9 +26,6 @@ namespace osu.Game.Tests.Visual.Playlists { public class TestScenePlaylistsRoomSubScreen : OnlinePlaySubScreenTestScene { - [Cached(typeof(IRoomManager))] - private readonly TestRoomManager roomManager = new TestRoomManager(); - private BeatmapManager manager; private RulesetStore rulesets; @@ -59,7 +55,8 @@ namespace osu.Game.Tests.Visual.Playlists [SetUpSteps] public void SetupSteps() { - AddStep("load match", () => LoadScreen(match = new TestPlaylistsRoomSubScreen(Room))); + AddStep("set room", () => SelectedRoom.Value = new Room()); + AddStep("load match", () => LoadScreen(match = new TestPlaylistsRoomSubScreen(SelectedRoom.Value))); AddUntilStep("wait for load", () => match.IsCurrentScreen()); } @@ -68,12 +65,12 @@ namespace osu.Game.Tests.Visual.Playlists { AddStep("set room properties", () => { - Room.RoomID.Value = 1; - Room.Name.Value = "my awesome room"; - Room.Host.Value = new User { Id = 2, Username = "peppy" }; - Room.RecentParticipants.Add(Room.Host.Value); - Room.EndDate.Value = DateTimeOffset.Now.AddMinutes(5); - Room.Playlist.Add(new PlaylistItem + SelectedRoom.Value.RoomID.Value = 1; + SelectedRoom.Value.Name.Value = "my awesome room"; + SelectedRoom.Value.Host.Value = new User { Id = 2, Username = "peppy" }; + SelectedRoom.Value.RecentParticipants.Add(SelectedRoom.Value.Host.Value); + SelectedRoom.Value.EndDate.Value = DateTimeOffset.Now.AddMinutes(5); + SelectedRoom.Value.Playlist.Add(new PlaylistItem { Beatmap = { Value = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo }, Ruleset = { Value = new OsuRuleset().RulesetInfo } @@ -89,9 +86,9 @@ namespace osu.Game.Tests.Visual.Playlists { AddStep("set room properties", () => { - Room.Name.Value = "my awesome room"; - Room.Host.Value = new User { Id = 2, Username = "peppy" }; - Room.Playlist.Add(new PlaylistItem + SelectedRoom.Value.Name.Value = "my awesome room"; + SelectedRoom.Value.Host.Value = new User { Id = 2, Username = "peppy" }; + SelectedRoom.Value.Playlist.Add(new PlaylistItem { Beatmap = { Value = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo }, Ruleset = { Value = new OsuRuleset().RulesetInfo } @@ -105,7 +102,7 @@ namespace osu.Game.Tests.Visual.Playlists AddStep("click", () => InputManager.Click(MouseButton.Left)); - AddAssert("first playlist item selected", () => match.SelectedItem.Value == Room.Playlist[0]); + AddAssert("first playlist item selected", () => match.SelectedItem.Value == SelectedRoom.Value.Playlist[0]); } [Test] @@ -123,9 +120,9 @@ namespace osu.Game.Tests.Visual.Playlists AddStep("load room", () => { - Room.Name.Value = "my awesome room"; - Room.Host.Value = new User { Id = 2, Username = "peppy" }; - Room.Playlist.Add(new PlaylistItem + SelectedRoom.Value.Name.Value = "my awesome room"; + SelectedRoom.Value.Host.Value = new User { Id = 2, Username = "peppy" }; + SelectedRoom.Value.Playlist.Add(new PlaylistItem { Beatmap = { Value = importedSet.Beatmaps[0] }, Ruleset = { Value = new OsuRuleset().RulesetInfo } @@ -156,30 +153,5 @@ namespace osu.Game.Tests.Visual.Playlists { } } - - private class TestRoomManager : IRoomManager - { - public event Action RoomsUpdated - { - add => throw new NotImplementedException(); - remove => throw new NotImplementedException(); - } - - public IBindable InitialRoomsReceived { get; } = new Bindable(true); - - public IBindableList Rooms { get; } = new BindableList(); - - public void CreateRoom(Room room, Action onSuccess = null, Action onError = null) - { - room.RoomID.Value = 1; - onSuccess?.Invoke(room); - } - - public void JoinRoom(Room room, Action onSuccess = null, Action onError = null) => onSuccess?.Invoke(room); - - public void PartRoom() - { - } - } } } diff --git a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlaySubScreenTestScene.cs b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlaySubScreenTestScene.cs index 27cb6496ce..177f6635a7 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlaySubScreenTestScene.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlaySubScreenTestScene.cs @@ -2,20 +2,37 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Game.Beatmaps; using osu.Game.Online.Rooms; +using osu.Game.Rulesets; using osu.Game.Screens; using osu.Game.Screens.OnlinePlay; +using osu.Game.Screens.OnlinePlay.Lounge.Components; +using osu.Game.Users; namespace osu.Game.Tests.Visual.OnlinePlay { + /// + /// A providing all the dependencies cached by for testing s. + /// public abstract class OnlinePlaySubScreenTestScene : ScreenTestScene { /// - /// The cached . + /// The cached . /// - protected Room Room { get; private set; } + protected Bindable SelectedRoom { get; private set; } + + /// + /// The cached + /// + protected TestRoomManager RoomManager { get; private set; } + + protected Bindable Filter { get; private set; } + + protected OngoingOperationTracker OngoingOperationTracker { get; private set; } public override void SetUpSteps() { @@ -35,16 +52,80 @@ namespace osu.Game.Tests.Visual.OnlinePlay /// The resultant dependency container. protected virtual IReadOnlyDependencyContainer CreateScreenDependencies(IReadOnlyDependencyContainer parent) { - Room = new Room(); + SelectedRoom = new Bindable(); + RoomManager = new TestRoomManager(); + Filter = new Bindable(new FilterCriteria()); + OngoingOperationTracker = new OngoingOperationTracker(); - var dependencies = new DependencyContainer( - new CachedModelDependencyContainer(parent) { Model = { Value = Room } }); - - dependencies.Cache(new Bindable(Room)); + var dependencies = new DependencyContainer(new CachedModelDependencyContainer(parent) { Model = { BindTarget = SelectedRoom } }); + dependencies.CacheAs(SelectedRoom); + dependencies.CacheAs(RoomManager); + dependencies.CacheAs(Filter); + dependencies.CacheAs(OngoingOperationTracker); return dependencies; } + protected class TestRoomManager : IRoomManager + { + public event Action RoomsUpdated + { + add { } + remove { } + } + + public readonly BindableList Rooms = new BindableList(); + + public IBindable InitialRoomsReceived { get; } = new Bindable(true); + + IBindableList IRoomManager.Rooms => Rooms; + + public void CreateRoom(Room room, Action onSuccess = null, Action onError = null) + { + room.RoomID.Value ??= Rooms.Select(r => r.RoomID.Value).Where(id => id != null).Select(id => id.Value).DefaultIfEmpty().Max() + 1; + Rooms.Add(room); + onSuccess?.Invoke(room); + } + + public void JoinRoom(Room room, Action onSuccess = null, Action onError = null) => onSuccess?.Invoke(room); + + public void PartRoom() + { + } + + public void AddRooms(int count, RulesetInfo ruleset = null) + { + for (int i = 0; i < count; i++) + { + var room = new Room + { + RoomID = { Value = i }, + Name = { Value = $"Room {i}" }, + Host = { Value = new User { Username = "Host" } }, + EndDate = { Value = DateTimeOffset.Now + TimeSpan.FromSeconds(10) }, + Category = { Value = i % 2 == 0 ? RoomCategory.Spotlight : RoomCategory.Normal } + }; + + if (ruleset != null) + { + room.Playlist.Add(new PlaylistItem + { + Ruleset = { Value = ruleset }, + Beatmap = + { + Value = new BeatmapInfo + { + Metadata = new BeatmapMetadata() + } + } + }); + } + + CreateRoom(room); + } + } + } + /// /// A dummy screen used for injecting new dependencies into the hierarchy before any screen is pushed via . /// diff --git a/osu.Game/Tests/Visual/OnlinePlay/TestRoomContainer.cs b/osu.Game/Tests/Visual/OnlinePlay/TestRoomContainer.cs index c2abcdfd08..7ff2e5133e 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/TestRoomContainer.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/TestRoomContainer.cs @@ -10,7 +10,7 @@ using osu.Game.Online.Rooms; namespace osu.Game.Tests.Visual.OnlinePlay { /// - /// Contains a that is resolvable by components in test scenes. + /// Contains a single that is resolvable by components in test scenes. /// public class TestRoomContainer : Container { From a21cf87b5fcee946600bbf22100397094383fbfe Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 24 Jun 2021 19:13:50 +0900 Subject: [PATCH 053/168] Split room manager and allow overrides --- .../TestSceneLoungeRoomsContainer.cs | 5 ++ .../TestScenePlaylistsLoungeSubScreen.cs | 9 +-- .../OnlinePlaySubScreenTestScene.cs | 70 +----------------- .../Visual/OnlinePlay/TestBasicRoomManager.cs | 74 +++++++++++++++++++ 4 files changed, 87 insertions(+), 71 deletions(-) create mode 100644 osu.Game/Tests/Visual/OnlinePlay/TestBasicRoomManager.cs diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs index d63d52a463..22e7acce44 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs @@ -9,6 +9,7 @@ using osu.Game.Graphics; using osu.Game.Online.Rooms; using osu.Game.Rulesets.Catch; using osu.Game.Rulesets.Osu; +using osu.Game.Screens.OnlinePlay; using osu.Game.Screens.OnlinePlay.Lounge.Components; using osu.Game.Tests.Visual.OnlinePlay; using osuTK.Graphics; @@ -18,6 +19,8 @@ namespace osu.Game.Tests.Visual.Multiplayer { public class TestSceneLoungeRoomsContainer : OnlinePlaySubScreenTestScene { + protected new TestBasicRoomManager RoomManager => (TestBasicRoomManager)base.RoomManager; + private RoomsContainer container; [BackgroundDependencyLoader] @@ -32,6 +35,8 @@ namespace osu.Game.Tests.Visual.Multiplayer }; } + protected override IRoomManager CreateRoomManager() => new TestBasicRoomManager(); + [Test] public void TestBasicListChanges() { diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsLoungeSubScreen.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsLoungeSubScreen.cs index 62a6f40d65..34e66b9cb2 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsLoungeSubScreen.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsLoungeSubScreen.cs @@ -3,10 +3,10 @@ using System.Linq; using NUnit.Framework; -using osu.Framework.Allocation; using osu.Framework.Screens; using osu.Framework.Testing; using osu.Game.Graphics.Containers; +using osu.Game.Screens.OnlinePlay; using osu.Game.Screens.OnlinePlay.Lounge; using osu.Game.Screens.OnlinePlay.Lounge.Components; using osu.Game.Screens.OnlinePlay.Playlists; @@ -16,12 +16,11 @@ namespace osu.Game.Tests.Visual.Playlists { public class TestScenePlaylistsLoungeSubScreen : OnlinePlaySubScreenTestScene { + protected new TestBasicRoomManager RoomManager => (TestBasicRoomManager)base.RoomManager; + private LoungeSubScreen loungeScreen; - [BackgroundDependencyLoader] - private void load() - { - } + protected override IRoomManager CreateRoomManager() => new TestBasicRoomManager(); public override void SetUpSteps() { diff --git a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlaySubScreenTestScene.cs b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlaySubScreenTestScene.cs index 177f6635a7..c4a8658d93 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlaySubScreenTestScene.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlaySubScreenTestScene.cs @@ -2,16 +2,12 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; -using osu.Game.Beatmaps; using osu.Game.Online.Rooms; -using osu.Game.Rulesets; using osu.Game.Screens; using osu.Game.Screens.OnlinePlay; using osu.Game.Screens.OnlinePlay.Lounge.Components; -using osu.Game.Users; namespace osu.Game.Tests.Visual.OnlinePlay { @@ -28,7 +24,7 @@ namespace osu.Game.Tests.Visual.OnlinePlay /// /// The cached /// - protected TestRoomManager RoomManager { get; private set; } + protected IRoomManager RoomManager { get; private set; } protected Bindable Filter { get; private set; } @@ -53,78 +49,20 @@ namespace osu.Game.Tests.Visual.OnlinePlay protected virtual IReadOnlyDependencyContainer CreateScreenDependencies(IReadOnlyDependencyContainer parent) { SelectedRoom = new Bindable(); - RoomManager = new TestRoomManager(); + RoomManager = CreateRoomManager(); Filter = new Bindable(new FilterCriteria()); OngoingOperationTracker = new OngoingOperationTracker(); var dependencies = new DependencyContainer(new CachedModelDependencyContainer(parent) { Model = { BindTarget = SelectedRoom } }); dependencies.CacheAs(SelectedRoom); - dependencies.CacheAs(RoomManager); + dependencies.CacheAs(RoomManager); dependencies.CacheAs(Filter); dependencies.CacheAs(OngoingOperationTracker); return dependencies; } - protected class TestRoomManager : IRoomManager - { - public event Action RoomsUpdated - { - add { } - remove { } - } - - public readonly BindableList Rooms = new BindableList(); - - public IBindable InitialRoomsReceived { get; } = new Bindable(true); - - IBindableList IRoomManager.Rooms => Rooms; - - public void CreateRoom(Room room, Action onSuccess = null, Action onError = null) - { - room.RoomID.Value ??= Rooms.Select(r => r.RoomID.Value).Where(id => id != null).Select(id => id.Value).DefaultIfEmpty().Max() + 1; - Rooms.Add(room); - onSuccess?.Invoke(room); - } - - public void JoinRoom(Room room, Action onSuccess = null, Action onError = null) => onSuccess?.Invoke(room); - - public void PartRoom() - { - } - - public void AddRooms(int count, RulesetInfo ruleset = null) - { - for (int i = 0; i < count; i++) - { - var room = new Room - { - RoomID = { Value = i }, - Name = { Value = $"Room {i}" }, - Host = { Value = new User { Username = "Host" } }, - EndDate = { Value = DateTimeOffset.Now + TimeSpan.FromSeconds(10) }, - Category = { Value = i % 2 == 0 ? RoomCategory.Spotlight : RoomCategory.Normal } - }; - - if (ruleset != null) - { - room.Playlist.Add(new PlaylistItem - { - Ruleset = { Value = ruleset }, - Beatmap = - { - Value = new BeatmapInfo - { - Metadata = new BeatmapMetadata() - } - } - }); - } - - CreateRoom(room); - } - } - } + protected virtual IRoomManager CreateRoomManager() => new TestBasicRoomManager(); /// /// A dummy screen used for injecting new dependencies into the hierarchy before any screen is pushed via . diff --git a/osu.Game/Tests/Visual/OnlinePlay/TestBasicRoomManager.cs b/osu.Game/Tests/Visual/OnlinePlay/TestBasicRoomManager.cs new file mode 100644 index 0000000000..fd81072651 --- /dev/null +++ b/osu.Game/Tests/Visual/OnlinePlay/TestBasicRoomManager.cs @@ -0,0 +1,74 @@ +// 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.Linq; +using osu.Framework.Bindables; +using osu.Game.Beatmaps; +using osu.Game.Online.Rooms; +using osu.Game.Rulesets; +using osu.Game.Screens.OnlinePlay; +using osu.Game.Users; + +namespace osu.Game.Tests.Visual.OnlinePlay +{ + public class TestBasicRoomManager : IRoomManager + { + public event Action RoomsUpdated + { + add { } + remove { } + } + + public readonly BindableList Rooms = new BindableList(); + + public IBindable InitialRoomsReceived { get; } = new Bindable(true); + + IBindableList IRoomManager.Rooms => Rooms; + + public void CreateRoom(Room room, Action onSuccess = null, Action onError = null) + { + room.RoomID.Value ??= Rooms.Select(r => r.RoomID.Value).Where(id => id != null).Select(id => id.Value).DefaultIfEmpty().Max() + 1; + Rooms.Add(room); + onSuccess?.Invoke(room); + } + + public void JoinRoom(Room room, Action onSuccess = null, Action onError = null) => onSuccess?.Invoke(room); + + public void PartRoom() + { + } + + public void AddRooms(int count, RulesetInfo ruleset = null) + { + for (int i = 0; i < count; i++) + { + var room = new Room + { + RoomID = { Value = i }, + Name = { Value = $"Room {i}" }, + Host = { Value = new User { Username = "Host" } }, + EndDate = { Value = DateTimeOffset.Now + TimeSpan.FromSeconds(10) }, + Category = { Value = i % 2 == 0 ? RoomCategory.Spotlight : RoomCategory.Normal } + }; + + if (ruleset != null) + { + room.Playlist.Add(new PlaylistItem + { + Ruleset = { Value = ruleset }, + Beatmap = + { + Value = new BeatmapInfo + { + Metadata = new BeatmapMetadata() + } + } + }); + } + + CreateRoom(room); + } + } + } +} From 9cf2c9ddaef526ec9cc3ed8f3b08e8247e1b1765 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 24 Jun 2021 21:02:21 +0900 Subject: [PATCH 054/168] Fix incorrect dependency creation --- osu.Game/Tests/Visual/OnlinePlay/TestRoomContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Tests/Visual/OnlinePlay/TestRoomContainer.cs b/osu.Game/Tests/Visual/OnlinePlay/TestRoomContainer.cs index 7ff2e5133e..d75281e772 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/TestRoomContainer.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/TestRoomContainer.cs @@ -27,7 +27,7 @@ namespace osu.Game.Tests.Visual.OnlinePlay protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) { var dependencies = new DependencyContainer( - new CachedModelDependencyContainer(parent) { Model = { Value = Room } }); + new CachedModelDependencyContainer(base.CreateChildDependencies(parent)) { Model = { Value = Room } }); dependencies.Cache(new Bindable(Room)); From 5820793d1119f82bfeb772a835cfdd220277a356 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 24 Jun 2021 20:44:48 +0900 Subject: [PATCH 055/168] Make TestMultiplayerRoomContainer inherit TestRoomContainer --- .../Tests/Visual/Multiplayer/TestMultiplayerRoomContainer.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomContainer.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomContainer.cs index 1abf4d8f5d..d44b55c3c1 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomContainer.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomContainer.cs @@ -8,10 +8,11 @@ using osu.Framework.Graphics.Containers; using osu.Game.Online.Multiplayer; using osu.Game.Screens.OnlinePlay; using osu.Game.Screens.OnlinePlay.Lounge.Components; +using osu.Game.Tests.Visual.OnlinePlay; namespace osu.Game.Tests.Visual.Multiplayer { - public class TestMultiplayerRoomContainer : Container + public class TestMultiplayerRoomContainer : TestRoomContainer { protected override Container Content => content; private readonly Container content; From 8fba7d2423774f35c5e1182c4dcfe22992f9dad9 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 24 Jun 2021 21:02:04 +0900 Subject: [PATCH 056/168] Remove MultiplayerTestScene inheritance from simple test scenes --- .../TestSceneFreeModSelectOverlay.cs | 11 +- .../TestSceneMultiSpectatorLeaderboard.cs | 112 +++++++++--------- 2 files changed, 66 insertions(+), 57 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs index 26a0301d8a..25de2d0df3 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs @@ -7,14 +7,17 @@ using osu.Game.Screens.OnlinePlay; namespace osu.Game.Tests.Visual.Multiplayer { - public class TestSceneFreeModSelectOverlay : MultiplayerTestScene + public class TestSceneFreeModSelectOverlay : OsuTestScene { [SetUp] - public new void Setup() => Schedule(() => + public void Setup() => Schedule(() => { - Child = new FreeModSelectOverlay + Child = new TestMultiplayerRoomContainer { - State = { Value = Visibility.Visible } + Child = new FreeModSelectOverlay + { + State = { Value = Visibility.Visible } + } }; }); } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs index 5ad35be0ec..9ec85d360f 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs @@ -1,15 +1,18 @@ // 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 System.Threading; using System.Threading.Tasks; +using JetBrains.Annotations; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Testing; +using osu.Framework.Threading; using osu.Framework.Timing; using osu.Game.Database; using osu.Game.Online.Spectator; @@ -21,61 +24,38 @@ using osu.Game.Users; namespace osu.Game.Tests.Visual.Multiplayer { - public class TestSceneMultiSpectatorLeaderboard : MultiplayerTestScene + public class TestSceneMultiSpectatorLeaderboard : OsuTestScene { - [Cached(typeof(SpectatorClient))] - private TestSpectatorClient spectatorClient = new TestSpectatorClient(); + private Dictionary clocks; - [Cached(typeof(UserLookupCache))] - private UserLookupCache lookupCache = new TestUserLookupCache(); - - protected override Container Content => content; - private readonly Container content; - - private readonly Dictionary clocks = new Dictionary - { - { PLAYER_1_ID, new ManualClock() }, - { PLAYER_2_ID, new ManualClock() } - }; - - public TestSceneMultiSpectatorLeaderboard() - { - base.Content.AddRange(new Drawable[] - { - spectatorClient, - lookupCache, - content = new Container { RelativeSizeAxes = Axes.Both } - }); - } + private MultiSpectatorLeaderboard leaderboard; + private TestContainer container; [SetUpSteps] - public new void SetUpSteps() + public void SetUpSteps() { - MultiSpectatorLeaderboard leaderboard = null; - AddStep("reset", () => { - Clear(); - - foreach (var (userId, clock) in clocks) + clocks = new Dictionary { - spectatorClient.EndPlay(userId); - clock.CurrentTime = 0; - } + { MultiplayerTestScene.PLAYER_1_ID, new ManualClock() }, + { MultiplayerTestScene.PLAYER_2_ID, new ManualClock() } + }; + + Child = container = new TestContainer(); + + foreach (var (userId, _) in clocks) + container.SpectatorClient.StartPlay(userId, 0); }); AddStep("create leaderboard", () => { - foreach (var (userId, _) in clocks) - spectatorClient.StartPlay(userId, 0); - Beatmap.Value = CreateWorkingBeatmap(Ruleset.Value); - var playable = Beatmap.Value.GetPlayableBeatmap(Ruleset.Value); var scoreProcessor = new OsuScoreProcessor(); scoreProcessor.ApplyBeatmap(playable); - LoadComponentAsync(leaderboard = new MultiSpectatorLeaderboard(scoreProcessor, clocks.Keys.ToArray()) { Expanded = { Value = true } }, Add); + container.LoadComponentAsync(leaderboard = new MultiSpectatorLeaderboard(scoreProcessor, clocks.Keys.ToArray()) { Expanded = { Value = true } }, container.Add); }); AddUntilStep("wait for load", () => leaderboard.IsLoaded); @@ -96,42 +76,42 @@ namespace osu.Game.Tests.Visual.Multiplayer // For player 2, send frames in sets of 10. for (int i = 0; i < 100; i++) { - spectatorClient.SendFrames(PLAYER_1_ID, i, 1); + container.SpectatorClient.SendFrames(MultiplayerTestScene.PLAYER_1_ID, i, 1); if (i % 10 == 0) - spectatorClient.SendFrames(PLAYER_2_ID, i, 10); + container.SpectatorClient.SendFrames(MultiplayerTestScene.PLAYER_2_ID, i, 10); } }); - assertCombo(PLAYER_1_ID, 1); - assertCombo(PLAYER_2_ID, 10); + assertCombo(MultiplayerTestScene.PLAYER_1_ID, 1); + assertCombo(MultiplayerTestScene.PLAYER_2_ID, 10); // Advance to a point where only user player 1's frame changes. setTime(500); - assertCombo(PLAYER_1_ID, 5); - assertCombo(PLAYER_2_ID, 10); + assertCombo(MultiplayerTestScene.PLAYER_1_ID, 5); + assertCombo(MultiplayerTestScene.PLAYER_2_ID, 10); // Advance to a point where both user's frame changes. setTime(1100); - assertCombo(PLAYER_1_ID, 11); - assertCombo(PLAYER_2_ID, 20); + assertCombo(MultiplayerTestScene.PLAYER_1_ID, 11); + assertCombo(MultiplayerTestScene.PLAYER_2_ID, 20); // Advance user player 2 only to a point where its frame changes. - setTime(PLAYER_2_ID, 2100); - assertCombo(PLAYER_1_ID, 11); - assertCombo(PLAYER_2_ID, 30); + setTime(MultiplayerTestScene.PLAYER_2_ID, 2100); + assertCombo(MultiplayerTestScene.PLAYER_1_ID, 11); + assertCombo(MultiplayerTestScene.PLAYER_2_ID, 30); // Advance both users beyond their last frame setTime(101 * 100); - assertCombo(PLAYER_1_ID, 100); - assertCombo(PLAYER_2_ID, 100); + assertCombo(MultiplayerTestScene.PLAYER_1_ID, 100); + assertCombo(MultiplayerTestScene.PLAYER_2_ID, 100); } [Test] public void TestNoFrames() { - assertCombo(PLAYER_1_ID, 0); - assertCombo(PLAYER_2_ID, 0); + assertCombo(MultiplayerTestScene.PLAYER_1_ID, 0); + assertCombo(MultiplayerTestScene.PLAYER_2_ID, 0); } private void setTime(double time) => AddStep($"set time {time}", () => @@ -146,6 +126,32 @@ namespace osu.Game.Tests.Visual.Multiplayer private void assertCombo(int userId, int expectedCombo) => AddUntilStep($"player {userId} has {expectedCombo} combo", () => this.ChildrenOfType().Single(s => s.User?.Id == userId).Combo.Value == expectedCombo); + private class TestContainer : TestMultiplayerRoomContainer + { + [Cached(typeof(SpectatorClient))] + public readonly TestSpectatorClient SpectatorClient = new TestSpectatorClient(); + + [Cached(typeof(UserLookupCache))] + public readonly UserLookupCache LookupCache = new TestUserLookupCache(); + + protected override Container Content => content; + private readonly Container content; + + public TestContainer() + { + AddRangeInternal(new Drawable[] + { + SpectatorClient, + LookupCache, + content = new Container { RelativeSizeAxes = Axes.Both } + }); + } + + public new Task LoadComponentAsync([NotNull] TLoadable component, Action onLoaded = null, CancellationToken cancellation = default, Scheduler scheduler = null) + where TLoadable : Drawable + => base.LoadComponentAsync(component, onLoaded, cancellation, scheduler); + } + private class TestUserLookupCache : UserLookupCache { protected override Task ComputeValueAsync(int lookup, CancellationToken token = default) From 31c786e1c306e1b0a5161e644b2d12009cc91143 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Fri, 25 Jun 2021 09:51:34 +0800 Subject: [PATCH 057/168] Use `SettingsNumberBox.NumberBox` instead of `OsuNumberBox` --- osu.Game/Rulesets/Mods/SeedSettingsControl.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/osu.Game/Rulesets/Mods/SeedSettingsControl.cs b/osu.Game/Rulesets/Mods/SeedSettingsControl.cs index 5c57717d93..1280197532 100644 --- a/osu.Game/Rulesets/Mods/SeedSettingsControl.cs +++ b/osu.Game/Rulesets/Mods/SeedSettingsControl.cs @@ -5,7 +5,6 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.UserInterface; -using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.Settings; namespace osu.Game.Rulesets.Mods @@ -35,7 +34,7 @@ namespace osu.Game.Rulesets.Mods } } - private readonly OsuNumberBox seedNumberBox; + private readonly SettingsNumberBox.NumberBox seedNumberBox; public SeedControl() { @@ -61,7 +60,7 @@ namespace osu.Game.Rulesets.Mods { new Drawable[] { - seedNumberBox = new OsuNumberBox + seedNumberBox = new SettingsNumberBox.NumberBox { RelativeSizeAxes = Axes.X, CommitOnFocusLost = true From aa5d22d04a4ecf0c0bc315ae7ce3ad1fd7b50b32 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 25 Jun 2021 13:02:19 +0900 Subject: [PATCH 058/168] Remove "test container", make everything go through OnlinePlayTestScene --- .../Multiplayer/TestSceneLoungeRoomInfo.cs | 32 +++--- .../TestSceneLoungeRoomsContainer.cs | 7 +- .../TestSceneMatchBeatmapDetailArea.cs | 25 ++--- .../Multiplayer/TestSceneMatchHeader.cs | 19 ++-- .../Multiplayer/TestSceneMatchLeaderboard.cs | 20 ++-- .../TestSceneMultiplayerMatchFooter.cs | 16 +-- .../TestScenePlaylistsSongSelect.cs | 2 +- .../TestScenePlaylistsLoungeSubScreen.cs | 7 +- .../TestScenePlaylistsMatchSettingsOverlay.cs | 49 +++++---- .../TestScenePlaylistsParticipantsList.cs | 18 ++-- .../TestScenePlaylistsRoomSubScreen.cs | 2 +- .../IMultiplayerRoomTestDependencies.cs | 22 ++++ .../MultiplayerRoomTestDependencies.cs | 23 ++++ .../MultiplayerSubScreenTestScene.cs | 17 +++ .../TestMultiplayerRoomContainer.cs | 3 +- ...RoomManager.cs => BasicTestRoomManager.cs} | 2 +- .../OnlinePlay/IRoomTestDependencies.cs | 38 +++++++ .../OnlinePlaySubScreenTestScene.cs | 83 --------------- .../Visual/OnlinePlay/OnlinePlayTestScene.cs | 100 ++++++++++++++++++ .../Visual/OnlinePlay/RoomTestDependencies.cs | 78 ++++++++++++++ .../Visual/OnlinePlay/TestRoomContainer.cs | 37 ------- 21 files changed, 370 insertions(+), 230 deletions(-) create mode 100644 osu.Game/Tests/Visual/Multiplayer/IMultiplayerRoomTestDependencies.cs create mode 100644 osu.Game/Tests/Visual/Multiplayer/MultiplayerRoomTestDependencies.cs create mode 100644 osu.Game/Tests/Visual/Multiplayer/MultiplayerSubScreenTestScene.cs rename osu.Game/Tests/Visual/OnlinePlay/{TestBasicRoomManager.cs => BasicTestRoomManager.cs} (97%) create mode 100644 osu.Game/Tests/Visual/OnlinePlay/IRoomTestDependencies.cs delete mode 100644 osu.Game/Tests/Visual/OnlinePlay/OnlinePlaySubScreenTestScene.cs create mode 100644 osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs create mode 100644 osu.Game/Tests/Visual/OnlinePlay/RoomTestDependencies.cs delete mode 100644 osu.Game/Tests/Visual/OnlinePlay/TestRoomContainer.cs diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomInfo.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomInfo.cs index 73f7865208..471d0b6c98 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomInfo.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomInfo.cs @@ -4,6 +4,7 @@ using System; using NUnit.Framework; using osu.Framework.Graphics; +using osu.Game.Online.Rooms; using osu.Game.Online.Rooms.RoomStatuses; using osu.Game.Screens.OnlinePlay.Lounge.Components; using osu.Game.Tests.Visual.OnlinePlay; @@ -11,28 +12,25 @@ using osu.Game.Users; namespace osu.Game.Tests.Visual.Multiplayer { - public class TestSceneLoungeRoomInfo : OsuTestScene + public class TestSceneLoungeRoomInfo : OnlinePlayTestScene { - private TestRoomContainer roomContainer; - [SetUp] - public void Setup() => Schedule(() => + public new void Setup() => Schedule(() => { - Child = roomContainer = new TestRoomContainer + SelectedRoom.Value = new Room(); + + Child = new RoomInfo { - Child = new RoomInfo - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Width = 500 - } + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Width = 500 }; }); [Test] public void TestNonSelectedRoom() { - AddStep("set null room", () => roomContainer.Room.RoomID.Value = null); + AddStep("set null room", () => SelectedRoom.Value.RoomID.Value = null); } [Test] @@ -40,11 +38,11 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("set open room", () => { - roomContainer.Room.RoomID.Value = 0; - roomContainer.Room.Name.Value = "Room 0"; - roomContainer.Room.Host.Value = new User { Username = "peppy", Id = 2 }; - roomContainer.Room.EndDate.Value = DateTimeOffset.Now.AddMonths(1); - roomContainer.Room.Status.Value = new RoomStatusOpen(); + SelectedRoom.Value.RoomID.Value = 0; + SelectedRoom.Value.Name.Value = "Room 0"; + SelectedRoom.Value.Host.Value = new User { Username = "peppy", Id = 2 }; + SelectedRoom.Value.EndDate.Value = DateTimeOffset.Now.AddMonths(1); + SelectedRoom.Value.Status.Value = new RoomStatusOpen(); }); } } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs index 22e7acce44..a78cff0650 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs @@ -9,7 +9,6 @@ using osu.Game.Graphics; using osu.Game.Online.Rooms; using osu.Game.Rulesets.Catch; using osu.Game.Rulesets.Osu; -using osu.Game.Screens.OnlinePlay; using osu.Game.Screens.OnlinePlay.Lounge.Components; using osu.Game.Tests.Visual.OnlinePlay; using osuTK.Graphics; @@ -17,9 +16,9 @@ using osuTK.Input; namespace osu.Game.Tests.Visual.Multiplayer { - public class TestSceneLoungeRoomsContainer : OnlinePlaySubScreenTestScene + public class TestSceneLoungeRoomsContainer : OnlinePlayTestScene { - protected new TestBasicRoomManager RoomManager => (TestBasicRoomManager)base.RoomManager; + protected new BasicTestRoomManager RoomManager => (BasicTestRoomManager)base.RoomManager; private RoomsContainer container; @@ -35,8 +34,6 @@ namespace osu.Game.Tests.Visual.Multiplayer }; } - protected override IRoomManager CreateRoomManager() => new TestBasicRoomManager(); - [Test] public void TestBasicListChanges() { diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapDetailArea.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapDetailArea.cs index d114ae8abf..d66603a448 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapDetailArea.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapDetailArea.cs @@ -16,7 +16,7 @@ using osuTK; namespace osu.Game.Tests.Visual.Multiplayer { - public class TestSceneMatchBeatmapDetailArea : OsuTestScene + public class TestSceneMatchBeatmapDetailArea : OnlinePlayTestScene { [Resolved] private BeatmapManager beatmapManager { get; set; } @@ -24,28 +24,25 @@ namespace osu.Game.Tests.Visual.Multiplayer [Resolved] private RulesetStore rulesetStore { get; set; } - private TestRoomContainer roomContainer; - [SetUp] - public void Setup() => Schedule(() => + public new void Setup() => Schedule(() => { - Child = roomContainer = new TestRoomContainer + SelectedRoom.Value = new Room(); + + Child = new MatchBeatmapDetailArea { - Child = new MatchBeatmapDetailArea - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(500), - CreateNewItem = createNewItem - } + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(500), + CreateNewItem = createNewItem }; }); private void createNewItem() { - roomContainer.Room.Playlist.Add(new PlaylistItem + SelectedRoom.Value.Playlist.Add(new PlaylistItem { - ID = roomContainer.Room.Playlist.Count, + ID = SelectedRoom.Value.Playlist.Count, Beatmap = { Value = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo }, Ruleset = { Value = new OsuRuleset().RulesetInfo }, RequiredMods = diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchHeader.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchHeader.cs index 25cdfd4a90..3557bd9127 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchHeader.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchHeader.cs @@ -12,17 +12,14 @@ using osu.Game.Users; namespace osu.Game.Tests.Visual.Multiplayer { - public class TestSceneMatchHeader : OsuTestScene + public class TestSceneMatchHeader : OnlinePlayTestScene { - private TestRoomContainer roomContainer; - [SetUp] - public void Setup() => Schedule(() => + public new void Setup() => Schedule(() => { - Child = roomContainer = new TestRoomContainer - { - Child = new Header() - }; + SelectedRoom.Value = new Room(); + + Child = new Header(); }); [Test] @@ -30,7 +27,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("set basic room", () => { - roomContainer.Room.Playlist.Add(new PlaylistItem + SelectedRoom.Value.Playlist.Add(new PlaylistItem { Beatmap = { @@ -54,8 +51,8 @@ namespace osu.Game.Tests.Visual.Multiplayer } }); - roomContainer.Room.Name.Value = "A very awesome room"; - roomContainer.Room.Host.Value = new User { Id = 2, Username = "peppy" }; + SelectedRoom.Value.Name.Value = "A very awesome room"; + SelectedRoom.Value.Host.Value = new User { Id = 2, Username = "peppy" }; }); } } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchLeaderboard.cs index 467df97cb9..a7a5f3af39 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchLeaderboard.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchLeaderboard.cs @@ -15,7 +15,7 @@ using osuTK; namespace osu.Game.Tests.Visual.Multiplayer { - public class TestSceneMatchLeaderboard : OsuTestScene + public class TestSceneMatchLeaderboard : OnlinePlayTestScene { [BackgroundDependencyLoader] private void load() @@ -59,18 +59,16 @@ namespace osu.Game.Tests.Visual.Multiplayer } [SetUp] - public void Setup() => Schedule(() => + public new void Setup() => Schedule(() => { - Child = new TestRoomContainer + SelectedRoom.Value = new Room { RoomID = { Value = 3 } }; + + Child = new MatchLeaderboard { - Room = { RoomID = { Value = 3 } }, - Child = new MatchLeaderboard - { - Origin = Anchor.Centre, - Anchor = Anchor.Centre, - Size = new Vector2(550f, 450f), - Scope = MatchLeaderboardScope.Overall, - } + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + Size = new Vector2(550f, 450f), + Scope = MatchLeaderboardScope.Overall, }; }); } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchFooter.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchFooter.cs index 6b03b53b4b..afe66d2686 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchFooter.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchFooter.cs @@ -1,27 +1,27 @@ // 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 NUnit.Framework; using osu.Framework.Graphics; using osu.Game.Online.Rooms; using osu.Game.Screens.OnlinePlay.Multiplayer.Match; +using osu.Game.Tests.Visual.OnlinePlay; namespace osu.Game.Tests.Visual.Multiplayer { - public class TestSceneMultiplayerMatchFooter : MultiplayerTestScene + public class TestSceneMultiplayerMatchFooter : OnlinePlayTestScene { - [Cached] - private readonly OnlinePlayBeatmapAvailabilityTracker availablilityTracker = new OnlinePlayBeatmapAvailabilityTracker(); - - [BackgroundDependencyLoader] - private void load() + [SetUp] + public new void Setup() => Schedule(() => { + SelectedRoom.Value = new Room(); + Child = new MultiplayerMatchFooter { Anchor = Anchor.Centre, Origin = Anchor.Centre, Height = 50 }; - } + }); } } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs index 4f10877f95..e4bf9b36ed 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs @@ -25,7 +25,7 @@ using osu.Game.Tests.Visual.OnlinePlay; namespace osu.Game.Tests.Visual.Multiplayer { - public class TestScenePlaylistsSongSelect : OnlinePlaySubScreenTestScene + public class TestScenePlaylistsSongSelect : OnlinePlayTestScene { [Resolved] private BeatmapManager beatmapManager { get; set; } diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsLoungeSubScreen.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsLoungeSubScreen.cs index 34e66b9cb2..b16b61c5c7 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsLoungeSubScreen.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsLoungeSubScreen.cs @@ -6,7 +6,6 @@ using NUnit.Framework; using osu.Framework.Screens; using osu.Framework.Testing; using osu.Game.Graphics.Containers; -using osu.Game.Screens.OnlinePlay; using osu.Game.Screens.OnlinePlay.Lounge; using osu.Game.Screens.OnlinePlay.Lounge.Components; using osu.Game.Screens.OnlinePlay.Playlists; @@ -14,14 +13,12 @@ using osu.Game.Tests.Visual.OnlinePlay; namespace osu.Game.Tests.Visual.Playlists { - public class TestScenePlaylistsLoungeSubScreen : OnlinePlaySubScreenTestScene + public class TestScenePlaylistsLoungeSubScreen : OnlinePlayTestScene { - protected new TestBasicRoomManager RoomManager => (TestBasicRoomManager)base.RoomManager; + protected new BasicTestRoomManager RoomManager => (BasicTestRoomManager)base.RoomManager; private LoungeSubScreen loungeScreen; - protected override IRoomManager CreateRoomManager() => new TestBasicRoomManager(); - public override void SetUpSteps() { base.SetUpSteps(); diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs index 94aed2ecfa..b52b6a6a0e 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs @@ -3,7 +3,6 @@ using System; using NUnit.Framework; -using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -16,24 +15,23 @@ using osu.Game.Tests.Visual.OnlinePlay; namespace osu.Game.Tests.Visual.Playlists { - public class TestScenePlaylistsMatchSettingsOverlay : OsuTestScene + public class TestScenePlaylistsMatchSettingsOverlay : OnlinePlayTestScene { - [Cached(Type = typeof(IRoomManager))] - private TestRoomManager roomManager = new TestRoomManager(); + protected new TestRoomManager RoomManager => (TestRoomManager)base.RoomManager; - private TestRoomContainer roomContainer; private TestRoomSettings settings; + protected override RoomTestDependencies CreateRoomDependencies() => new TestDependencies(); + [SetUp] - public void Setup() => Schedule(() => + public new void Setup() => Schedule(() => { - Child = roomContainer = new TestRoomContainer + SelectedRoom.Value = new Room(); + + Child = settings = new TestRoomSettings { - Child = settings = new TestRoomSettings - { - RelativeSizeAxes = Axes.Both, - State = { Value = Visibility.Visible } - } + RelativeSizeAxes = Axes.Both, + State = { Value = Visibility.Visible } }; }); @@ -42,19 +40,19 @@ namespace osu.Game.Tests.Visual.Playlists { AddStep("clear name and beatmap", () => { - roomContainer.Room.Name.Value = ""; - roomContainer.Room.Playlist.Clear(); + SelectedRoom.Value.Name.Value = ""; + SelectedRoom.Value.Playlist.Clear(); }); AddAssert("button disabled", () => !settings.ApplyButton.Enabled.Value); - AddStep("set name", () => roomContainer.Room.Name.Value = "Room name"); + AddStep("set name", () => SelectedRoom.Value.Name.Value = "Room name"); AddAssert("button disabled", () => !settings.ApplyButton.Enabled.Value); - AddStep("set beatmap", () => roomContainer.Room.Playlist.Add(new PlaylistItem { Beatmap = { Value = CreateBeatmap(Ruleset.Value).BeatmapInfo } })); + AddStep("set beatmap", () => SelectedRoom.Value.Playlist.Add(new PlaylistItem { Beatmap = { Value = CreateBeatmap(Ruleset.Value).BeatmapInfo } })); AddAssert("button enabled", () => settings.ApplyButton.Enabled.Value); - AddStep("clear name", () => roomContainer.Room.Name.Value = ""); + AddStep("clear name", () => SelectedRoom.Value.Name.Value = ""); AddAssert("button disabled", () => !settings.ApplyButton.Enabled.Value); } @@ -70,9 +68,9 @@ namespace osu.Game.Tests.Visual.Playlists { settings.NameField.Current.Value = expected_name; settings.DurationField.Current.Value = expectedDuration; - roomContainer.Room.Playlist.Add(new PlaylistItem { Beatmap = { Value = CreateBeatmap(Ruleset.Value).BeatmapInfo } }); + SelectedRoom.Value.Playlist.Add(new PlaylistItem { Beatmap = { Value = CreateBeatmap(Ruleset.Value).BeatmapInfo } }); - roomManager.CreateRequested = r => + RoomManager.CreateRequested = r => { createdRoom = r; return true; @@ -91,11 +89,11 @@ namespace osu.Game.Tests.Visual.Playlists AddStep("setup", () => { - roomContainer.Room.Name.Value = "Test Room"; - roomContainer.Room.Playlist.Add(new PlaylistItem { Beatmap = { Value = CreateBeatmap(Ruleset.Value).BeatmapInfo } }); + SelectedRoom.Value.Name.Value = "Test Room"; + SelectedRoom.Value.Playlist.Add(new PlaylistItem { Beatmap = { Value = CreateBeatmap(Ruleset.Value).BeatmapInfo } }); fail = true; - roomManager.CreateRequested = _ => !fail; + RoomManager.CreateRequested = _ => !fail; }); AddAssert("error not displayed", () => !settings.ErrorText.IsPresent); @@ -122,7 +120,12 @@ namespace osu.Game.Tests.Visual.Playlists public OsuSpriteText ErrorText => ((MatchSettings)Settings).ErrorText; } - private class TestRoomManager : IRoomManager + private class TestDependencies : RoomTestDependencies + { + protected override IRoomManager CreateRoomManager() => new TestRoomManager(); + } + + protected class TestRoomManager : IRoomManager { public const string FAILED_TEXT = "failed"; diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsParticipantsList.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsParticipantsList.cs index 02ddfe4e79..76a78c0a3c 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsParticipantsList.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsParticipantsList.cs @@ -3,27 +3,23 @@ using NUnit.Framework; using osu.Framework.Graphics; +using osu.Game.Online.Rooms; using osu.Game.Screens.OnlinePlay.Components; using osu.Game.Tests.Visual.OnlinePlay; using osu.Game.Users; namespace osu.Game.Tests.Visual.Playlists { - public class TestScenePlaylistsParticipantsList : OsuTestScene + public class TestScenePlaylistsParticipantsList : OnlinePlayTestScene { - private TestRoomContainer roomContainer; - [SetUp] - public void Setup() => Schedule(() => + public new void Setup() => Schedule(() => { - Child = roomContainer = new TestRoomContainer - { - Room = { RoomID = { Value = 7 } } - }; + SelectedRoom.Value = new Room { RoomID = { Value = 7 } }; for (int i = 0; i < 50; i++) { - roomContainer.Room.RecentParticipants.Add(new User + SelectedRoom.Value.RecentParticipants.Add(new User { Username = "peppy", Statistics = new UserStatistics { GlobalRank = 1234 }, @@ -37,7 +33,7 @@ namespace osu.Game.Tests.Visual.Playlists { AddStep("create component", () => { - roomContainer.Child = new ParticipantsDisplay(Direction.Horizontal) + Child = new ParticipantsDisplay(Direction.Horizontal) { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -51,7 +47,7 @@ namespace osu.Game.Tests.Visual.Playlists { AddStep("create component", () => { - roomContainer.Child = new ParticipantsDisplay(Direction.Vertical) + Child = new ParticipantsDisplay(Direction.Vertical) { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs index 98efdd98ef..a0096c823f 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs @@ -24,7 +24,7 @@ using osuTK.Input; namespace osu.Game.Tests.Visual.Playlists { - public class TestScenePlaylistsRoomSubScreen : OnlinePlaySubScreenTestScene + public class TestScenePlaylistsRoomSubScreen : OnlinePlayTestScene { private BeatmapManager manager; private RulesetStore rulesets; diff --git a/osu.Game/Tests/Visual/Multiplayer/IMultiplayerRoomTestDependencies.cs b/osu.Game/Tests/Visual/Multiplayer/IMultiplayerRoomTestDependencies.cs new file mode 100644 index 0000000000..0b53bdd04e --- /dev/null +++ b/osu.Game/Tests/Visual/Multiplayer/IMultiplayerRoomTestDependencies.cs @@ -0,0 +1,22 @@ +// 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.Multiplayer; +using osu.Game.Screens.OnlinePlay; +using osu.Game.Tests.Visual.OnlinePlay; + +namespace osu.Game.Tests.Visual.Multiplayer +{ + public interface IMultiplayerRoomTestDependencies : IRoomTestDependencies + { + /// + /// The cached . + /// + TestMultiplayerClient Client { get; } + + /// + /// The cached . + /// + new TestMultiplayerRoomManager RoomManager { get; } + } +} diff --git a/osu.Game/Tests/Visual/Multiplayer/MultiplayerRoomTestDependencies.cs b/osu.Game/Tests/Visual/Multiplayer/MultiplayerRoomTestDependencies.cs new file mode 100644 index 0000000000..e3913dd291 --- /dev/null +++ b/osu.Game/Tests/Visual/Multiplayer/MultiplayerRoomTestDependencies.cs @@ -0,0 +1,23 @@ +// 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.Multiplayer; +using osu.Game.Screens.OnlinePlay; +using osu.Game.Tests.Visual.OnlinePlay; + +namespace osu.Game.Tests.Visual.Multiplayer +{ + public class MultiplayerRoomTestDependencies : RoomTestDependencies, IMultiplayerRoomTestDependencies + { + public TestMultiplayerClient Client { get; } + public new TestMultiplayerRoomManager RoomManager => (TestMultiplayerRoomManager)base.RoomManager; + + public MultiplayerRoomTestDependencies() + { + Client = new TestMultiplayerClient(RoomManager); + CacheAs(Client); + } + + protected override IRoomManager CreateRoomManager() => new TestMultiplayerRoomManager(); + } +} diff --git a/osu.Game/Tests/Visual/Multiplayer/MultiplayerSubScreenTestScene.cs b/osu.Game/Tests/Visual/Multiplayer/MultiplayerSubScreenTestScene.cs new file mode 100644 index 0000000000..5c405ee9d3 --- /dev/null +++ b/osu.Game/Tests/Visual/Multiplayer/MultiplayerSubScreenTestScene.cs @@ -0,0 +1,17 @@ +// 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.Tests.Visual.OnlinePlay; + +namespace osu.Game.Tests.Visual.Multiplayer +{ + public class MultiplayerSubScreenTestScene : OnlinePlayTestScene, IMultiplayerRoomTestDependencies + { + public TestMultiplayerClient Client => RoomDependencies.Client; + public new TestMultiplayerRoomManager RoomManager => RoomDependencies.RoomManager; + + protected new MultiplayerRoomTestDependencies RoomDependencies => (MultiplayerRoomTestDependencies)base.RoomDependencies; + + protected override RoomTestDependencies CreateRoomDependencies() => new MultiplayerRoomTestDependencies(); + } +} diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomContainer.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomContainer.cs index d44b55c3c1..1abf4d8f5d 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomContainer.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomContainer.cs @@ -8,11 +8,10 @@ using osu.Framework.Graphics.Containers; using osu.Game.Online.Multiplayer; using osu.Game.Screens.OnlinePlay; using osu.Game.Screens.OnlinePlay.Lounge.Components; -using osu.Game.Tests.Visual.OnlinePlay; namespace osu.Game.Tests.Visual.Multiplayer { - public class TestMultiplayerRoomContainer : TestRoomContainer + public class TestMultiplayerRoomContainer : Container { protected override Container Content => content; private readonly Container content; diff --git a/osu.Game/Tests/Visual/OnlinePlay/TestBasicRoomManager.cs b/osu.Game/Tests/Visual/OnlinePlay/BasicTestRoomManager.cs similarity index 97% rename from osu.Game/Tests/Visual/OnlinePlay/TestBasicRoomManager.cs rename to osu.Game/Tests/Visual/OnlinePlay/BasicTestRoomManager.cs index fd81072651..67beea9117 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/TestBasicRoomManager.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/BasicTestRoomManager.cs @@ -12,7 +12,7 @@ using osu.Game.Users; namespace osu.Game.Tests.Visual.OnlinePlay { - public class TestBasicRoomManager : IRoomManager + public class BasicTestRoomManager : IRoomManager { public event Action RoomsUpdated { diff --git a/osu.Game/Tests/Visual/OnlinePlay/IRoomTestDependencies.cs b/osu.Game/Tests/Visual/OnlinePlay/IRoomTestDependencies.cs new file mode 100644 index 0000000000..848e2aa77f --- /dev/null +++ b/osu.Game/Tests/Visual/OnlinePlay/IRoomTestDependencies.cs @@ -0,0 +1,38 @@ +// 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; +using osu.Game.Screens.OnlinePlay; +using osu.Game.Screens.OnlinePlay.Lounge.Components; + +namespace osu.Game.Tests.Visual.OnlinePlay +{ + public interface IRoomTestDependencies + { + /// + /// The cached . + /// + Bindable SelectedRoom { get; } + + /// + /// The cached + /// + IRoomManager RoomManager { get; } + + /// + /// The cached . + /// + Bindable Filter { get; } + + /// + /// The cached . + /// + OngoingOperationTracker OngoingOperationTracker { get; } + + /// + /// The cached . + /// + OnlinePlayBeatmapAvailabilityTracker AvailabilityTracker { get; } + } +} diff --git a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlaySubScreenTestScene.cs b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlaySubScreenTestScene.cs deleted file mode 100644 index c4a8658d93..0000000000 --- a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlaySubScreenTestScene.cs +++ /dev/null @@ -1,83 +0,0 @@ -// 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 osu.Framework.Allocation; -using osu.Framework.Bindables; -using osu.Game.Online.Rooms; -using osu.Game.Screens; -using osu.Game.Screens.OnlinePlay; -using osu.Game.Screens.OnlinePlay.Lounge.Components; - -namespace osu.Game.Tests.Visual.OnlinePlay -{ - /// - /// A providing all the dependencies cached by for testing s. - /// - public abstract class OnlinePlaySubScreenTestScene : ScreenTestScene - { - /// - /// The cached . - /// - protected Bindable SelectedRoom { get; private set; } - - /// - /// The cached - /// - protected IRoomManager RoomManager { get; private set; } - - protected Bindable Filter { get; private set; } - - protected OngoingOperationTracker OngoingOperationTracker { get; private set; } - - public override void SetUpSteps() - { - base.SetUpSteps(); - - AddStep("create dependencies", () => LoadScreen(new DependenciesScreen(CreateScreenDependencies))); - } - - /// - /// Creates dependencies for any pushed via . - /// Invoked at the start of every test via . - /// - /// - /// This should be overridden to add any custom dependencies required by subclasses of . - /// - /// The parent dependency container. - /// The resultant dependency container. - protected virtual IReadOnlyDependencyContainer CreateScreenDependencies(IReadOnlyDependencyContainer parent) - { - SelectedRoom = new Bindable(); - RoomManager = CreateRoomManager(); - Filter = new Bindable(new FilterCriteria()); - OngoingOperationTracker = new OngoingOperationTracker(); - - var dependencies = new DependencyContainer(new CachedModelDependencyContainer(parent) { Model = { BindTarget = SelectedRoom } }); - dependencies.CacheAs(SelectedRoom); - dependencies.CacheAs(RoomManager); - dependencies.CacheAs(Filter); - dependencies.CacheAs(OngoingOperationTracker); - - return dependencies; - } - - protected virtual IRoomManager CreateRoomManager() => new TestBasicRoomManager(); - - /// - /// A dummy screen used for injecting new dependencies into the hierarchy before any screen is pushed via . - /// - private class DependenciesScreen : OsuScreen - { - private readonly Func createDependenciesFunc; - - public DependenciesScreen(Func createDependenciesFunc) - { - this.createDependenciesFunc = createDependenciesFunc; - } - - protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) - => createDependenciesFunc(base.CreateChildDependencies(parent)); - } - } -} diff --git a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs new file mode 100644 index 0000000000..5e46ea0544 --- /dev/null +++ b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs @@ -0,0 +1,100 @@ +// 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 NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Online.Rooms; +using osu.Game.Screens.OnlinePlay; +using osu.Game.Screens.OnlinePlay.Lounge.Components; + +namespace osu.Game.Tests.Visual.OnlinePlay +{ + /// + /// A providing all the dependencies cached by for testing s. + /// + public abstract class OnlinePlayTestScene : ScreenTestScene, IRoomTestDependencies + { + public Bindable SelectedRoom => RoomDependencies?.SelectedRoom; + public IRoomManager RoomManager => RoomDependencies?.RoomManager; + public Bindable Filter => RoomDependencies?.Filter; + public OngoingOperationTracker OngoingOperationTracker => RoomDependencies?.OngoingOperationTracker; + public OnlinePlayBeatmapAvailabilityTracker AvailabilityTracker => RoomDependencies?.AvailabilityTracker; + + protected RoomTestDependencies RoomDependencies => delegatedDependencies?.RoomDependencies; + private DelegatedRoomDependencyContainer delegatedDependencies; + + protected override Container Content => content; + private readonly Container content; + private readonly Container drawableDependenciesContainer; + + protected OnlinePlayTestScene() + { + base.Content.AddRange(new Drawable[] + { + drawableDependenciesContainer = new Container { RelativeSizeAxes = Axes.Both }, + content = new Container { RelativeSizeAxes = Axes.Both }, + }); + } + + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) + { + delegatedDependencies = new DelegatedRoomDependencyContainer(base.CreateChildDependencies(parent)); + return delegatedDependencies; + } + + [SetUp] + public void Setup() => Schedule(() => + { + // Reset the room dependencies to a fresh state. + drawableDependenciesContainer.Clear(); + delegatedDependencies.RoomDependencies = CreateRoomDependencies(); + drawableDependenciesContainer.AddRange(RoomDependencies.DrawableComponents); + }); + + /// + /// Creates the room dependencies. Called every . + /// + /// + /// Any custom dependencies required for online-play sub-classes should be added here. + /// + protected virtual RoomTestDependencies CreateRoomDependencies() => new RoomTestDependencies(); + + /// + /// A providing a mutable lookup source for room dependencies. + /// + private class DelegatedRoomDependencyContainer : IReadOnlyDependencyContainer + { + /// + /// The room's dependencies. + /// + public RoomTestDependencies RoomDependencies { get; set; } + + private readonly IReadOnlyDependencyContainer parent; + private readonly DependencyContainer injectableDependencies; + + /// + /// Creates a new . + /// + /// The fallback to use when cannot satisfy a dependency. + public DelegatedRoomDependencyContainer(IReadOnlyDependencyContainer parent) + { + this.parent = parent; + injectableDependencies = new DependencyContainer(this); + } + + public object Get(Type type) + => RoomDependencies?.Get(type) ?? parent.Get(type); + + public object Get(Type type, CacheInfo info) + => RoomDependencies?.Get(type, info) ?? parent.Get(type, info); + + public void Inject(T instance) + where T : class + => injectableDependencies.Inject(instance); + } + } +} diff --git a/osu.Game/Tests/Visual/OnlinePlay/RoomTestDependencies.cs b/osu.Game/Tests/Visual/OnlinePlay/RoomTestDependencies.cs new file mode 100644 index 0000000000..9d22f9e286 --- /dev/null +++ b/osu.Game/Tests/Visual/OnlinePlay/RoomTestDependencies.cs @@ -0,0 +1,78 @@ +// 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 osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Game.Online.Rooms; +using osu.Game.Screens.OnlinePlay; +using osu.Game.Screens.OnlinePlay.Lounge.Components; + +namespace osu.Game.Tests.Visual.OnlinePlay +{ + /// + /// Contains dependencies for testing online-play rooms. + /// + public class RoomTestDependencies : IReadOnlyDependencyContainer, IRoomTestDependencies + { + public Bindable SelectedRoom { get; } + public IRoomManager RoomManager { get; } + public Bindable Filter { get; } + public OngoingOperationTracker OngoingOperationTracker { get; } + public OnlinePlayBeatmapAvailabilityTracker AvailabilityTracker { get; } + + /// + /// All cached dependencies which are also components. + /// + public IReadOnlyList DrawableComponents => drawableComponents; + + private readonly List drawableComponents = new List(); + private readonly DependencyContainer dependencies; + + public RoomTestDependencies() + { + SelectedRoom = new Bindable(); + RoomManager = CreateRoomManager(); + Filter = new Bindable(new FilterCriteria()); + OngoingOperationTracker = new OngoingOperationTracker(); + AvailabilityTracker = new OnlinePlayBeatmapAvailabilityTracker(); + + dependencies = new DependencyContainer(new CachedModelDependencyContainer(null) { Model = { BindTarget = SelectedRoom } }); + + CacheAs(SelectedRoom); + CacheAs(RoomManager); + CacheAs(Filter); + CacheAs(OngoingOperationTracker); + CacheAs(AvailabilityTracker); + } + + public object Get(Type type) + => dependencies.Get(type); + + public object Get(Type type, CacheInfo info) + => dependencies.Get(type, info); + + public void Inject(T instance) + where T : class + => dependencies.Inject(instance); + + protected void Cache(object instance) + { + dependencies.Cache(instance); + if (instance is Drawable drawable) + drawableComponents.Add(drawable); + } + + protected void CacheAs(T instance) + where T : class + { + dependencies.CacheAs(instance); + if (instance is Drawable drawable) + drawableComponents.Add(drawable); + } + + protected virtual IRoomManager CreateRoomManager() => new BasicTestRoomManager(); + } +} diff --git a/osu.Game/Tests/Visual/OnlinePlay/TestRoomContainer.cs b/osu.Game/Tests/Visual/OnlinePlay/TestRoomContainer.cs deleted file mode 100644 index d75281e772..0000000000 --- a/osu.Game/Tests/Visual/OnlinePlay/TestRoomContainer.cs +++ /dev/null @@ -1,37 +0,0 @@ -// 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.Bindables; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Online.Rooms; - -namespace osu.Game.Tests.Visual.OnlinePlay -{ - /// - /// Contains a single that is resolvable by components in test scenes. - /// - public class TestRoomContainer : Container - { - /// - /// The cached . - /// - public readonly Room Room = new Room(); - - public TestRoomContainer() - { - RelativeSizeAxes = Axes.Both; - } - - protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) - { - var dependencies = new DependencyContainer( - new CachedModelDependencyContainer(base.CreateChildDependencies(parent)) { Model = { Value = Room } }); - - dependencies.Cache(new Bindable(Room)); - - return dependencies; - } - } -} From 81a812e21690cc4231459765e9a4d8b859da3647 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 25 Jun 2021 15:00:10 +0900 Subject: [PATCH 059/168] Rework MultiplayerTestScene to make use of OnlinePlayTestScene --- .../StatefulMultiplayerClientTest.cs | 8 +- .../TestSceneFreeModSelectOverlay.cs | 11 +- .../TestSceneLoungeRoomsContainer.cs | 11 +- .../TestSceneMultiSpectatorLeaderboard.cs | 85 +++++-------- .../TestSceneMultiSpectatorScreen.cs | 54 ++++---- .../TestSceneMultiplayerMatchSongSelect.cs | 2 +- .../TestSceneMultiplayerMatchSubScreen.cs | 10 +- .../TestSceneMultiplayerReadyButton.cs | 10 +- .../TestSceneMultiplayerRoomManager.cs | 120 +++++++----------- .../TestSceneMultiplayerSpectateButton.cs | 25 +--- .../MultiplayerSubScreenTestScene.cs | 17 --- .../Multiplayer/MultiplayerTestScene.cs | 55 +++----- .../TestMultiplayerRoomContainer.cs | 48 ------- .../Multiplayer/TestMultiplayerRoomManager.cs | 5 +- osu.Game/Tests/Visual/RoomTestScene.cs | 33 ----- 15 files changed, 159 insertions(+), 335 deletions(-) delete mode 100644 osu.Game/Tests/Visual/Multiplayer/MultiplayerSubScreenTestScene.cs delete mode 100644 osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomContainer.cs delete mode 100644 osu.Game/Tests/Visual/RoomTestScene.cs diff --git a/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs b/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs index adc1d6aede..0983b806e2 100644 --- a/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs +++ b/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs @@ -7,6 +7,7 @@ using NUnit.Framework; using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Testing; using osu.Game.Online.Multiplayer; +using osu.Game.Online.Rooms; using osu.Game.Tests.Visual.Multiplayer; using osu.Game.Users; @@ -50,7 +51,10 @@ namespace osu.Game.Tests.NonVisual.Multiplayer AddStep("create room initially in gameplay", () => { - Room.RoomID.Value = null; + var newRoom = new Room(); + newRoom.CopyFrom(SelectedRoom.Value); + + newRoom.RoomID.Value = null; Client.RoomSetupAction = room => { room.State = MultiplayerRoomState.Playing; @@ -61,7 +65,7 @@ namespace osu.Game.Tests.NonVisual.Multiplayer }); }; - RoomManager.CreateRoom(Room); + RoomManager.CreateRoom(newRoom); }); AddUntilStep("wait for room join", () => Client.Room != null); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs index 25de2d0df3..26a0301d8a 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs @@ -7,17 +7,14 @@ using osu.Game.Screens.OnlinePlay; namespace osu.Game.Tests.Visual.Multiplayer { - public class TestSceneFreeModSelectOverlay : OsuTestScene + public class TestSceneFreeModSelectOverlay : MultiplayerTestScene { [SetUp] - public void Setup() => Schedule(() => + public new void Setup() => Schedule(() => { - Child = new TestMultiplayerRoomContainer + Child = new FreeModSelectOverlay { - Child = new FreeModSelectOverlay - { - State = { Value = Visibility.Visible } - } + State = { Value = Visibility.Visible } }; }); } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs index a78cff0650..75cc687ee8 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs @@ -3,7 +3,6 @@ using System.Linq; using NUnit.Framework; -using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Game.Graphics; using osu.Game.Online.Rooms; @@ -22,8 +21,8 @@ namespace osu.Game.Tests.Visual.Multiplayer private RoomsContainer container; - [BackgroundDependencyLoader] - private void load() + [SetUp] + public new void Setup() => Schedule(() => { Child = container = new RoomsContainer { @@ -32,7 +31,7 @@ namespace osu.Game.Tests.Visual.Multiplayer Width = 0.5f, JoinRequested = joinRequested }; - } + }); [Test] public void TestBasicListChanges() @@ -113,14 +112,14 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("add rooms", () => RoomManager.AddRooms(2, new OsuRuleset().RulesetInfo)); AddStep("add rooms", () => RoomManager.AddRooms(3, new CatchRuleset().RulesetInfo)); + // Todo: What even is this case...? + AddStep("set empty filter criteria", () => container.Filter(null)); AddUntilStep("5 rooms visible", () => container.Rooms.Count(r => r.IsPresent) == 5); AddStep("filter osu! rooms", () => container.Filter(new FilterCriteria { Ruleset = new OsuRuleset().RulesetInfo })); - AddUntilStep("2 rooms visible", () => container.Rooms.Count(r => r.IsPresent) == 2); AddStep("filter catch rooms", () => container.Filter(new FilterCriteria { Ruleset = new CatchRuleset().RulesetInfo })); - AddUntilStep("3 rooms visible", () => container.Rooms.Count(r => r.IsPresent) == 3); } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs index 9ec85d360f..54594fbfc8 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs @@ -1,51 +1,48 @@ // 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 System.Threading; using System.Threading.Tasks; -using JetBrains.Annotations; using NUnit.Framework; -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Framework.Testing; -using osu.Framework.Threading; using osu.Framework.Timing; using osu.Game.Database; using osu.Game.Online.Spectator; using osu.Game.Rulesets.Osu.Scoring; using osu.Game.Screens.OnlinePlay.Multiplayer.Spectate; using osu.Game.Screens.Play.HUD; +using osu.Game.Tests.Visual.OnlinePlay; using osu.Game.Tests.Visual.Spectator; using osu.Game.Users; namespace osu.Game.Tests.Visual.Multiplayer { - public class TestSceneMultiSpectatorLeaderboard : OsuTestScene + public class TestSceneMultiSpectatorLeaderboard : MultiplayerTestScene { - private Dictionary clocks; + public TestSpectatorClient SpectatorClient => RoomDependencies?.SpectatorClient; + protected new TestDependencies RoomDependencies => (TestDependencies)base.RoomDependencies; + + private Dictionary clocks; private MultiSpectatorLeaderboard leaderboard; - private TestContainer container; [SetUpSteps] - public void SetUpSteps() + public new void SetUpSteps() { AddStep("reset", () => { + Clear(); + clocks = new Dictionary { - { MultiplayerTestScene.PLAYER_1_ID, new ManualClock() }, - { MultiplayerTestScene.PLAYER_2_ID, new ManualClock() } + { PLAYER_1_ID, new ManualClock() }, + { PLAYER_2_ID, new ManualClock() } }; - Child = container = new TestContainer(); - foreach (var (userId, _) in clocks) - container.SpectatorClient.StartPlay(userId, 0); + SpectatorClient.StartPlay(userId, 0); }); AddStep("create leaderboard", () => @@ -55,7 +52,7 @@ namespace osu.Game.Tests.Visual.Multiplayer var scoreProcessor = new OsuScoreProcessor(); scoreProcessor.ApplyBeatmap(playable); - container.LoadComponentAsync(leaderboard = new MultiSpectatorLeaderboard(scoreProcessor, clocks.Keys.ToArray()) { Expanded = { Value = true } }, container.Add); + LoadComponentAsync(leaderboard = new MultiSpectatorLeaderboard(scoreProcessor, clocks.Keys.ToArray()) { Expanded = { Value = true } }, Add); }); AddUntilStep("wait for load", () => leaderboard.IsLoaded); @@ -76,42 +73,42 @@ namespace osu.Game.Tests.Visual.Multiplayer // For player 2, send frames in sets of 10. for (int i = 0; i < 100; i++) { - container.SpectatorClient.SendFrames(MultiplayerTestScene.PLAYER_1_ID, i, 1); + SpectatorClient.SendFrames(PLAYER_1_ID, i, 1); if (i % 10 == 0) - container.SpectatorClient.SendFrames(MultiplayerTestScene.PLAYER_2_ID, i, 10); + SpectatorClient.SendFrames(PLAYER_2_ID, i, 10); } }); - assertCombo(MultiplayerTestScene.PLAYER_1_ID, 1); - assertCombo(MultiplayerTestScene.PLAYER_2_ID, 10); + assertCombo(PLAYER_1_ID, 1); + assertCombo(PLAYER_2_ID, 10); // Advance to a point where only user player 1's frame changes. setTime(500); - assertCombo(MultiplayerTestScene.PLAYER_1_ID, 5); - assertCombo(MultiplayerTestScene.PLAYER_2_ID, 10); + assertCombo(PLAYER_1_ID, 5); + assertCombo(PLAYER_2_ID, 10); // Advance to a point where both user's frame changes. setTime(1100); - assertCombo(MultiplayerTestScene.PLAYER_1_ID, 11); - assertCombo(MultiplayerTestScene.PLAYER_2_ID, 20); + assertCombo(PLAYER_1_ID, 11); + assertCombo(PLAYER_2_ID, 20); // Advance user player 2 only to a point where its frame changes. - setTime(MultiplayerTestScene.PLAYER_2_ID, 2100); - assertCombo(MultiplayerTestScene.PLAYER_1_ID, 11); - assertCombo(MultiplayerTestScene.PLAYER_2_ID, 30); + setTime(PLAYER_2_ID, 2100); + assertCombo(PLAYER_1_ID, 11); + assertCombo(PLAYER_2_ID, 30); // Advance both users beyond their last frame setTime(101 * 100); - assertCombo(MultiplayerTestScene.PLAYER_1_ID, 100); - assertCombo(MultiplayerTestScene.PLAYER_2_ID, 100); + assertCombo(PLAYER_1_ID, 100); + assertCombo(PLAYER_2_ID, 100); } [Test] public void TestNoFrames() { - assertCombo(MultiplayerTestScene.PLAYER_1_ID, 0); - assertCombo(MultiplayerTestScene.PLAYER_2_ID, 0); + assertCombo(PLAYER_1_ID, 0); + assertCombo(PLAYER_2_ID, 0); } private void setTime(double time) => AddStep($"set time {time}", () => @@ -126,30 +123,18 @@ namespace osu.Game.Tests.Visual.Multiplayer private void assertCombo(int userId, int expectedCombo) => AddUntilStep($"player {userId} has {expectedCombo} combo", () => this.ChildrenOfType().Single(s => s.User?.Id == userId).Combo.Value == expectedCombo); - private class TestContainer : TestMultiplayerRoomContainer - { - [Cached(typeof(SpectatorClient))] - public readonly TestSpectatorClient SpectatorClient = new TestSpectatorClient(); + protected override RoomTestDependencies CreateRoomDependencies() => new TestDependencies(); - [Cached(typeof(UserLookupCache))] + protected class TestDependencies : MultiplayerRoomTestDependencies + { + public readonly TestSpectatorClient SpectatorClient = new TestSpectatorClient(); public readonly UserLookupCache LookupCache = new TestUserLookupCache(); - protected override Container Content => content; - private readonly Container content; - - public TestContainer() + public TestDependencies() { - AddRangeInternal(new Drawable[] - { - SpectatorClient, - LookupCache, - content = new Container { RelativeSizeAxes = Axes.Both } - }); + CacheAs(SpectatorClient); + CacheAs(LookupCache); } - - public new Task LoadComponentAsync([NotNull] TLoadable component, Action onLoaded = null, CancellationToken cancellation = default, Scheduler scheduler = null) - where TLoadable : Drawable - => base.LoadComponentAsync(component, onLoaded, cancellation, scheduler); } private class TestUserLookupCache : UserLookupCache diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs index b91391c409..64f4b4c0d6 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs @@ -15,6 +15,7 @@ using osu.Game.Online.Spectator; using osu.Game.Screens.OnlinePlay.Multiplayer.Spectate; using osu.Game.Screens.Play; using osu.Game.Tests.Beatmaps.IO; +using osu.Game.Tests.Visual.OnlinePlay; using osu.Game.Tests.Visual.Spectator; using osu.Game.Users; @@ -22,11 +23,9 @@ namespace osu.Game.Tests.Visual.Multiplayer { public class TestSceneMultiSpectatorScreen : MultiplayerTestScene { - [Cached(typeof(SpectatorClient))] - private TestSpectatorClient spectatorClient = new TestSpectatorClient(); + public TestSpectatorClient SpectatorClient => RoomDependencies?.SpectatorClient; - [Cached(typeof(UserLookupCache))] - private UserLookupCache lookupCache = new TestUserLookupCache(); + protected new TestDependencies RoomDependencies => (TestDependencies)base.RoomDependencies; [Resolved] private OsuGameBase game { get; set; } @@ -51,25 +50,12 @@ namespace osu.Game.Tests.Visual.Multiplayer importedBeatmapId = importedBeatmap.OnlineBeatmapID ?? -1; } - public override void SetUpSteps() + [SetUp] + public new void Setup() => Schedule(() => { - base.SetUpSteps(); - - AddStep("reset sent frames", () => nextFrame.Clear()); - - AddStep("add streaming client", () => - { - Remove(spectatorClient); - Add(spectatorClient); - }); - - AddStep("finish previous gameplay", () => - { - foreach (var id in playingUserIds) - spectatorClient.EndPlay(id); - playingUserIds.Clear(); - }); - } + nextFrame.Clear(); + playingUserIds.Clear(); + }); [Test] public void TestDelayedStart() @@ -87,11 +73,11 @@ namespace osu.Game.Tests.Visual.Multiplayer loadSpectateScreen(false); AddWaitStep("wait a bit", 10); - AddStep("load player first_player_id", () => spectatorClient.StartPlay(PLAYER_1_ID, importedBeatmapId)); + AddStep("load player first_player_id", () => SpectatorClient.StartPlay(PLAYER_1_ID, importedBeatmapId)); AddUntilStep("one player added", () => spectatorScreen.ChildrenOfType().Count() == 1); AddWaitStep("wait a bit", 10); - AddStep("load player second_player_id", () => spectatorClient.StartPlay(PLAYER_2_ID, importedBeatmapId)); + AddStep("load player second_player_id", () => SpectatorClient.StartPlay(PLAYER_2_ID, importedBeatmapId)); AddUntilStep("two players added", () => spectatorScreen.ChildrenOfType().Count() == 2); } @@ -251,7 +237,7 @@ namespace osu.Game.Tests.Visual.Multiplayer foreach (int id in userIds) { Client.CurrentMatchPlayingUserIds.Add(id); - spectatorClient.StartPlay(id, beatmapId ?? importedBeatmapId); + SpectatorClient.StartPlay(id, beatmapId ?? importedBeatmapId); playingUserIds.Add(id); nextFrame[id] = 0; } @@ -262,7 +248,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("end play", () => { - spectatorClient.EndPlay(userId); + SpectatorClient.EndPlay(userId); playingUserIds.Remove(userId); nextFrame.Remove(userId); }); @@ -276,7 +262,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { foreach (int id in userIds) { - spectatorClient.SendFrames(id, nextFrame[id], count); + SpectatorClient.SendFrames(id, nextFrame[id], count); nextFrame[id] += count; } }); @@ -298,6 +284,20 @@ namespace osu.Game.Tests.Visual.Multiplayer private PlayerArea getInstance(int userId) => spectatorScreen.ChildrenOfType().Single(p => p.UserId == userId); + protected override RoomTestDependencies CreateRoomDependencies() => new TestDependencies(); + + protected class TestDependencies : MultiplayerRoomTestDependencies + { + public readonly TestSpectatorClient SpectatorClient = new TestSpectatorClient(); + public readonly UserLookupCache LookupCache = new TestUserLookupCache(); + + public TestDependencies() + { + CacheAs(SpectatorClient); + CacheAs(LookupCache); + } + } + internal class TestUserLookupCache : UserLookupCache { protected override Task ComputeValueAsync(int lookup, CancellationToken token = default) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs index 2725ef5976..8bcb9cebbc 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs @@ -29,7 +29,7 @@ using osu.Game.Screens.Select; namespace osu.Game.Tests.Visual.Multiplayer { - public class TestSceneMultiplayerMatchSongSelect : ScreenTestScene + public class TestSceneMultiplayerMatchSongSelect : MultiplayerTestScene { private BeatmapManager manager; private RulesetStore rulesets; diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs index e8ebc0c426..955be6ca21 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs @@ -49,13 +49,13 @@ namespace osu.Game.Tests.Visual.Multiplayer [SetUp] public new void Setup() => Schedule(() => { - Room.Name.Value = "Test Room"; + SelectedRoom.Value = new Room { Name = { Value = "Test Room" } }; }); [SetUpSteps] public void SetupSteps() { - AddStep("load match", () => LoadScreen(screen = new MultiplayerMatchSubScreen(Room))); + AddStep("load match", () => LoadScreen(screen = new MultiplayerMatchSubScreen(SelectedRoom.Value))); AddUntilStep("wait for load", () => screen.IsCurrentScreen()); } @@ -66,7 +66,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("set playlist", () => { - Room.Playlist.Add(new PlaylistItem + SelectedRoom.Value.Playlist.Add(new PlaylistItem { Beatmap = { Value = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo }, Ruleset = { Value = new OsuRuleset().RulesetInfo }, @@ -81,7 +81,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("set playlist", () => { - Room.Playlist.Add(new PlaylistItem + SelectedRoom.Value.Playlist.Add(new PlaylistItem { Beatmap = { Value = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo }, Ruleset = { Value = new OsuRuleset().RulesetInfo }, @@ -102,7 +102,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("set playlist", () => { - Room.Playlist.Add(new PlaylistItem + SelectedRoom.Value.Playlist.Add(new PlaylistItem { Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First()).BeatmapInfo }, Ruleset = { Value = new OsuRuleset().RulesetInfo }, diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs index 929cd6ca80..4f2ca34fb0 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs @@ -27,7 +27,6 @@ namespace osu.Game.Tests.Visual.Multiplayer public class TestSceneMultiplayerReadyButton : MultiplayerTestScene { private MultiplayerReadyButton button; - private OnlinePlayBeatmapAvailabilityTracker beatmapTracker; private BeatmapSetInfo importedSet; private readonly Bindable selectedItem = new Bindable(); @@ -43,18 +42,13 @@ namespace osu.Game.Tests.Visual.Multiplayer Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, Beatmap.Default)); beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).Wait(); - - Add(beatmapTracker = new OnlinePlayBeatmapAvailabilityTracker - { - SelectedItem = { BindTarget = selectedItem } - }); - - Dependencies.Cache(beatmapTracker); } [SetUp] public new void Setup() => Schedule(() => { + AvailabilityTracker.SelectedItem.BindTo(selectedItem); + importedSet = beatmaps.GetAllUsableBeatmapSetsEnumerable(IncludedDetails.All).First(); Beatmap.Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First()); selectedItem.Value = new PlaylistItem diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerRoomManager.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerRoomManager.cs index 80e36916b1..302e6998cc 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerRoomManager.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerRoomManager.cs @@ -3,37 +3,38 @@ using System; using NUnit.Framework; -using osu.Framework.Graphics; using osu.Framework.Testing; using osu.Game.Online.Rooms; using osu.Game.Screens.OnlinePlay.Components; using osu.Game.Tests.Beatmaps; +using osu.Game.Tests.Visual.OnlinePlay; namespace osu.Game.Tests.Visual.Multiplayer { [HeadlessTest] - public class TestSceneMultiplayerRoomManager : OsuTestScene + public class TestSceneMultiplayerRoomManager : MultiplayerTestScene { - private TestMultiplayerRoomContainer roomContainer; - private TestMultiplayerRoomManager roomManager => roomContainer.RoomManager; + protected override RoomTestDependencies CreateRoomDependencies() => new TestDependencies(); + + public TestSceneMultiplayerRoomManager() + : base(false) + { + } [Test] public void TestPollsInitially() { AddStep("create room manager with a few rooms", () => { - createRoomManager().With(d => d.OnLoadComplete += _ => - { - roomManager.CreateRoom(createRoom(r => r.Name.Value = "1")); - roomManager.PartRoom(); - roomManager.CreateRoom(createRoom(r => r.Name.Value = "2")); - roomManager.PartRoom(); - roomManager.ClearRooms(); - }); + RoomManager.CreateRoom(createRoom(r => r.Name.Value = "1")); + RoomManager.PartRoom(); + RoomManager.CreateRoom(createRoom(r => r.Name.Value = "2")); + RoomManager.PartRoom(); + RoomManager.ClearRooms(); }); - AddAssert("manager polled for rooms", () => ((RoomManager)roomManager).Rooms.Count == 2); - AddAssert("initial rooms received", () => roomManager.InitialRoomsReceived.Value); + AddAssert("manager polled for rooms", () => ((RoomManager)RoomManager).Rooms.Count == 2); + AddAssert("initial rooms received", () => RoomManager.InitialRoomsReceived.Value); } [Test] @@ -41,19 +42,16 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("create room manager with a few rooms", () => { - createRoomManager().With(d => d.OnLoadComplete += _ => - { - roomManager.CreateRoom(createRoom()); - roomManager.PartRoom(); - roomManager.CreateRoom(createRoom()); - roomManager.PartRoom(); - }); + RoomManager.CreateRoom(createRoom()); + RoomManager.PartRoom(); + RoomManager.CreateRoom(createRoom()); + RoomManager.PartRoom(); }); - AddStep("disconnect", () => roomContainer.Client.Disconnect()); + AddStep("disconnect", () => Client.Disconnect()); - AddAssert("rooms cleared", () => ((RoomManager)roomManager).Rooms.Count == 0); - AddAssert("initial rooms not received", () => !roomManager.InitialRoomsReceived.Value); + AddAssert("rooms cleared", () => ((RoomManager)RoomManager).Rooms.Count == 0); + AddAssert("initial rooms not received", () => !RoomManager.InitialRoomsReceived.Value); } [Test] @@ -61,20 +59,17 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("create room manager with a few rooms", () => { - createRoomManager().With(d => d.OnLoadComplete += _ => - { - roomManager.CreateRoom(createRoom()); - roomManager.PartRoom(); - roomManager.CreateRoom(createRoom()); - roomManager.PartRoom(); - }); + RoomManager.CreateRoom(createRoom()); + RoomManager.PartRoom(); + RoomManager.CreateRoom(createRoom()); + RoomManager.PartRoom(); }); - AddStep("disconnect", () => roomContainer.Client.Disconnect()); - AddStep("connect", () => roomContainer.Client.Connect()); + AddStep("disconnect", () => Client.Disconnect()); + AddStep("connect", () => Client.Connect()); - AddAssert("manager polled for rooms", () => ((RoomManager)roomManager).Rooms.Count == 2); - AddAssert("initial rooms received", () => roomManager.InitialRoomsReceived.Value); + AddAssert("manager polled for rooms", () => ((RoomManager)RoomManager).Rooms.Count == 2); + AddAssert("initial rooms received", () => RoomManager.InitialRoomsReceived.Value); } [Test] @@ -82,15 +77,12 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("create room manager with a room", () => { - createRoomManager().With(d => d.OnLoadComplete += _ => - { - roomManager.CreateRoom(createRoom()); - roomManager.ClearRooms(); - }); + RoomManager.CreateRoom(createRoom()); + RoomManager.ClearRooms(); }); - AddAssert("manager not polled for rooms", () => ((RoomManager)roomManager).Rooms.Count == 0); - AddAssert("initial rooms not received", () => !roomManager.InitialRoomsReceived.Value); + AddAssert("manager not polled for rooms", () => ((RoomManager)RoomManager).Rooms.Count == 0); + AddAssert("initial rooms not received", () => !RoomManager.InitialRoomsReceived.Value); } [Test] @@ -98,13 +90,10 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("create room manager with a room", () => { - createRoomManager().With(d => d.OnLoadComplete += _ => - { - roomManager.CreateRoom(createRoom()); - }); + RoomManager.CreateRoom(createRoom()); }); - AddUntilStep("multiplayer room joined", () => roomContainer.Client.Room != null); + AddUntilStep("multiplayer room joined", () => Client.Room != null); } [Test] @@ -112,14 +101,11 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("create room manager with a room", () => { - createRoomManager().With(d => d.OnLoadComplete += _ => - { - roomManager.CreateRoom(createRoom()); - roomManager.PartRoom(); - }); + RoomManager.CreateRoom(createRoom()); + RoomManager.PartRoom(); }); - AddAssert("multiplayer room parted", () => roomContainer.Client.Room == null); + AddAssert("multiplayer room parted", () => Client.Room == null); } [Test] @@ -127,16 +113,13 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("create room manager with a room", () => { - createRoomManager().With(d => d.OnLoadComplete += _ => - { - var r = createRoom(); - roomManager.CreateRoom(r); - roomManager.PartRoom(); - roomManager.JoinRoom(r); - }); + var r = createRoom(); + RoomManager.CreateRoom(r); + RoomManager.PartRoom(); + RoomManager.JoinRoom(r); }); - AddUntilStep("multiplayer room joined", () => roomContainer.Client.Room != null); + AddUntilStep("multiplayer room joined", () => Client.Room != null); } private Room createRoom(Action initFunc = null) @@ -161,18 +144,13 @@ namespace osu.Game.Tests.Visual.Multiplayer return room; } - private TestMultiplayerRoomManager createRoomManager() + private class TestDependencies : MultiplayerRoomTestDependencies { - Child = roomContainer = new TestMultiplayerRoomContainer + public TestDependencies() { - RoomManager = - { - TimeBetweenListingPolls = { Value = 1 }, - TimeBetweenSelectionPolls = { Value = 1 } - } - }; - - return roomManager; + RoomManager.TimeBetweenListingPolls.Value = 1; + RoomManager.TimeBetweenSelectionPolls.Value = 1; + } } } } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs index d00404102c..070158f552 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs @@ -37,40 +37,19 @@ namespace osu.Game.Tests.Visual.Multiplayer private IDisposable readyClickOperation; - protected override Container Content => content; - private readonly Container content; - - public TestSceneMultiplayerSpectateButton() - { - base.Content.Add(content = new Container - { - RelativeSizeAxes = Axes.Both - }); - } - - protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) - { - var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); - - return dependencies; - } - [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, Beatmap.Default)); - - var beatmapTracker = new OnlinePlayBeatmapAvailabilityTracker { SelectedItem = { BindTarget = selectedItem } }; - base.Content.Add(beatmapTracker); - Dependencies.Cache(beatmapTracker); - beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).Wait(); } [SetUp] public new void Setup() => Schedule(() => { + AvailabilityTracker.SelectedItem.BindTo(selectedItem); + importedSet = beatmaps.GetAllUsableBeatmapSetsEnumerable(IncludedDetails.All).First(); Beatmap.Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First()); selectedItem.Value = new PlaylistItem diff --git a/osu.Game/Tests/Visual/Multiplayer/MultiplayerSubScreenTestScene.cs b/osu.Game/Tests/Visual/Multiplayer/MultiplayerSubScreenTestScene.cs deleted file mode 100644 index 5c405ee9d3..0000000000 --- a/osu.Game/Tests/Visual/Multiplayer/MultiplayerSubScreenTestScene.cs +++ /dev/null @@ -1,17 +0,0 @@ -// 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.Tests.Visual.OnlinePlay; - -namespace osu.Game.Tests.Visual.Multiplayer -{ - public class MultiplayerSubScreenTestScene : OnlinePlayTestScene, IMultiplayerRoomTestDependencies - { - public TestMultiplayerClient Client => RoomDependencies.Client; - public new TestMultiplayerRoomManager RoomManager => RoomDependencies.RoomManager; - - protected new MultiplayerRoomTestDependencies RoomDependencies => (MultiplayerRoomTestDependencies)base.RoomDependencies; - - protected override RoomTestDependencies CreateRoomDependencies() => new MultiplayerRoomTestDependencies(); - } -} diff --git a/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs b/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs index c76d1053b2..69dfd41e04 100644 --- a/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs +++ b/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs @@ -2,66 +2,51 @@ // See the LICENCE file in the repository root for full licence text. using NUnit.Framework; -using osu.Framework.Allocation; -using osu.Framework.Bindables; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Online.Multiplayer; using osu.Game.Online.Rooms; -using osu.Game.Screens.OnlinePlay; -using osu.Game.Screens.OnlinePlay.Lounge.Components; using osu.Game.Tests.Beatmaps; +using osu.Game.Tests.Visual.OnlinePlay; namespace osu.Game.Tests.Visual.Multiplayer { - public abstract class MultiplayerTestScene : RoomTestScene + public abstract class MultiplayerTestScene : OnlinePlayTestScene { public const int PLAYER_1_ID = 55; public const int PLAYER_2_ID = 56; - [Cached(typeof(MultiplayerClient))] - public TestMultiplayerClient Client { get; } + public TestMultiplayerClient Client => RoomDependencies.Client; + public new TestMultiplayerRoomManager RoomManager => RoomDependencies.RoomManager; - [Cached(typeof(IRoomManager))] - public TestMultiplayerRoomManager RoomManager { get; } + protected new MultiplayerRoomTestDependencies RoomDependencies => (MultiplayerRoomTestDependencies)base.RoomDependencies; - [Cached] - public Bindable Filter { get; } - - [Cached] - public OngoingOperationTracker OngoingOperationTracker { get; } - - protected override Container Content => content; - private readonly TestMultiplayerRoomContainer content; + protected override RoomTestDependencies CreateRoomDependencies() => new MultiplayerRoomTestDependencies(); private readonly bool joinRoom; protected MultiplayerTestScene(bool joinRoom = true) { this.joinRoom = joinRoom; - base.Content.Add(content = new TestMultiplayerRoomContainer { RelativeSizeAxes = Axes.Both }); - - Client = content.Client; - RoomManager = content.RoomManager; - Filter = content.Filter; - OngoingOperationTracker = content.OngoingOperationTracker; } [SetUp] public new void Setup() => Schedule(() => { - RoomManager.Schedule(() => RoomManager.PartRoom()); - if (joinRoom) { - Room.Name.Value = "test name"; - Room.Playlist.Add(new PlaylistItem + var room = new Room { - Beatmap = { Value = new TestBeatmap(Ruleset.Value).BeatmapInfo }, - Ruleset = { Value = Ruleset.Value } - }); + Name = { Value = "test name" }, + Playlist = + { + new PlaylistItem + { + Beatmap = { Value = new TestBeatmap(Ruleset.Value).BeatmapInfo }, + Ruleset = { Value = Ruleset.Value } + } + } + }; - RoomManager.Schedule(() => RoomManager.CreateRoom(Room)); + RoomManager.CreateRoom(room); + SelectedRoom.Value = room; } }); @@ -70,7 +55,9 @@ namespace osu.Game.Tests.Visual.Multiplayer base.SetUpSteps(); if (joinRoom) + { AddUntilStep("wait for room join", () => Client.Room != null); + } } } } diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomContainer.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomContainer.cs deleted file mode 100644 index 1abf4d8f5d..0000000000 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomContainer.cs +++ /dev/null @@ -1,48 +0,0 @@ -// 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.Bindables; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Online.Multiplayer; -using osu.Game.Screens.OnlinePlay; -using osu.Game.Screens.OnlinePlay.Lounge.Components; - -namespace osu.Game.Tests.Visual.Multiplayer -{ - public class TestMultiplayerRoomContainer : Container - { - protected override Container Content => content; - private readonly Container content; - - [Cached(typeof(MultiplayerClient))] - public readonly TestMultiplayerClient Client; - - [Cached(typeof(IRoomManager))] - public readonly TestMultiplayerRoomManager RoomManager; - - [Cached] - public readonly Bindable Filter = new Bindable(new FilterCriteria()); - - [Cached] - public readonly OngoingOperationTracker OngoingOperationTracker; - - public TestMultiplayerRoomContainer() - { - RelativeSizeAxes = Axes.Both; - - RoomManager = new TestMultiplayerRoomManager(); - Client = new TestMultiplayerClient(RoomManager); - OngoingOperationTracker = new OngoingOperationTracker(); - - AddRangeInternal(new Drawable[] - { - Client, - RoomManager, - OngoingOperationTracker, - content = new Container { RelativeSizeAxes = Axes.Both } - }); - } - } -} diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomManager.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomManager.cs index 315be510a3..6f4a464d57 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomManager.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomManager.cs @@ -29,10 +29,9 @@ namespace osu.Game.Tests.Visual.Multiplayer public new readonly List Rooms = new List(); - protected override void LoadComplete() + [BackgroundDependencyLoader] + private void load() { - base.LoadComplete(); - int currentScoreId = 0; int currentRoomId = 0; int currentPlaylistItemId = 0; diff --git a/osu.Game/Tests/Visual/RoomTestScene.cs b/osu.Game/Tests/Visual/RoomTestScene.cs deleted file mode 100644 index aaf5c7624f..0000000000 --- a/osu.Game/Tests/Visual/RoomTestScene.cs +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using NUnit.Framework; -using osu.Framework.Allocation; -using osu.Framework.Bindables; -using osu.Game.Online.Rooms; - -namespace osu.Game.Tests.Visual -{ - public abstract class RoomTestScene : ScreenTestScene - { - [Cached] - private readonly Bindable currentRoom = new Bindable(); - - protected Room Room => currentRoom.Value; - - private CachedModelDependencyContainer dependencies; - - protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) - { - dependencies = new CachedModelDependencyContainer(base.CreateChildDependencies(parent)); - dependencies.Model.BindTo(currentRoom); - return dependencies; - } - - [SetUp] - public void Setup() => Schedule(() => - { - currentRoom.Value = new Room(); - }); - } -} From 57ae87956a54e7ac3365abde44338a04f5d33a45 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 25 Jun 2021 15:27:40 +0900 Subject: [PATCH 060/168] Update execution state change blocking logic in line with framework changes --- osu.Game/OsuGameBase.cs | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index d0be03f8c1..dec738e5b3 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -24,6 +24,7 @@ using osu.Framework.Graphics.Performance; using osu.Framework.Graphics.Textures; using osu.Framework.Input; using osu.Framework.Logging; +using osu.Framework.Threading; using osu.Game.Audio; using osu.Game.Database; using osu.Game.Input; @@ -156,6 +157,8 @@ namespace osu.Game private readonly BindableNumber globalTrackVolumeAdjust = new BindableNumber(GLOBAL_TRACK_VOLUME_ADJUST); + private IBindable updateThreadState; + public OsuGameBase() { UseDevelopmentServer = DebugUtils.IsDebugBuild; @@ -183,7 +186,8 @@ namespace osu.Game dependencies.Cache(realmFactory = new RealmContextFactory(Storage)); - Host.UpdateThread.ThreadPausing += onUpdateThreadPausing; + updateThreadState = Host.UpdateThread.State.GetBoundCopy(); + updateThreadState.BindValueChanged(updateThreadStateChanged); AddInternal(realmFactory); @@ -359,10 +363,21 @@ namespace osu.Game Ruleset.BindValueChanged(onRulesetChanged); } - private void onUpdateThreadPausing() + private IDisposable blocking; + + private void updateThreadStateChanged(ValueChangedEvent state) { - var blocking = realmFactory.BlockAllOperations(); - Schedule(() => blocking.Dispose()); + switch (state.NewValue) + { + case GameThreadState.Running: + blocking.Dispose(); + blocking = null; + break; + + case GameThreadState.Paused: + blocking = realmFactory.BlockAllOperations(); + break; + } } protected override void LoadComplete() @@ -498,9 +513,6 @@ namespace osu.Game LocalConfig?.Dispose(); contextFactory.FlushConnections(); - - if (Host?.UpdateThread != null) - Host.UpdateThread.ThreadPausing -= onUpdateThreadPausing; } } } From d6ab08c95821927001a863ad2db40199fc8656a3 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 25 Jun 2021 15:30:23 +0900 Subject: [PATCH 061/168] Remove manual dependencies in TestSceneMultiplayerGameplayLeaderboard --- ...TestSceneMultiplayerGameplayLeaderboard.cs | 51 ++++++++++--------- 1 file changed, 26 insertions(+), 25 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs index af2f6fa5fe..9c30151f05 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs @@ -7,7 +7,6 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Framework.Testing; using osu.Framework.Utils; using osu.Game.Configuration; @@ -20,6 +19,7 @@ using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osu.Game.Screens.Play.HUD; using osu.Game.Tests.Visual.Online; +using osu.Game.Tests.Visual.OnlinePlay; using osu.Game.Tests.Visual.Spectator; namespace osu.Game.Tests.Visual.Multiplayer @@ -28,28 +28,15 @@ namespace osu.Game.Tests.Visual.Multiplayer { private const int users = 16; - [Cached(typeof(SpectatorClient))] - private TestMultiplayerSpectatorClient spectatorClient = new TestMultiplayerSpectatorClient(); + public TestMultiplayerSpectatorClient SpectatorClient => RoomDependencies?.SpectatorClient; - [Cached(typeof(UserLookupCache))] - private UserLookupCache lookupCache = new TestSceneCurrentlyPlayingDisplay.TestUserLookupCache(); + public UserLookupCache LookupCache => RoomDependencies?.LookupCache; + + protected new TestDependencies RoomDependencies => (TestDependencies)base.RoomDependencies; private MultiplayerGameplayLeaderboard leaderboard; - - protected override Container Content { get; } = new Container { RelativeSizeAxes = Axes.Both }; - private OsuConfigManager config; - public TestSceneMultiplayerGameplayLeaderboard() - { - base.Content.Children = new Drawable[] - { - spectatorClient, - lookupCache, - Content - }; - } - [BackgroundDependencyLoader] private void load() { @@ -59,7 +46,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [SetUpSteps] public override void SetUpSteps() { - AddStep("set local user", () => ((DummyAPIAccess)API).LocalUser.Value = lookupCache.GetUserAsync(1).Result); + AddStep("set local user", () => ((DummyAPIAccess)API).LocalUser.Value = LookupCache.GetUserAsync(1).Result); AddStep("create leaderboard", () => { @@ -71,12 +58,12 @@ namespace osu.Game.Tests.Visual.Multiplayer var playable = Beatmap.Value.GetPlayableBeatmap(Ruleset.Value); for (int i = 0; i < users; i++) - spectatorClient.StartPlay(i, Beatmap.Value.BeatmapInfo.OnlineBeatmapID ?? 0); + SpectatorClient.StartPlay(i, Beatmap.Value.BeatmapInfo.OnlineBeatmapID ?? 0); - spectatorClient.Schedule(() => + SpectatorClient.Schedule(() => { Client.CurrentMatchPlayingUserIds.Clear(); - Client.CurrentMatchPlayingUserIds.AddRange(spectatorClient.PlayingUsers); + Client.CurrentMatchPlayingUserIds.AddRange(SpectatorClient.PlayingUsers); }); Children = new Drawable[] @@ -86,7 +73,7 @@ namespace osu.Game.Tests.Visual.Multiplayer scoreProcessor.ApplyBeatmap(playable); - LoadComponentAsync(leaderboard = new MultiplayerGameplayLeaderboard(scoreProcessor, spectatorClient.PlayingUsers.ToArray()) + LoadComponentAsync(leaderboard = new MultiplayerGameplayLeaderboard(scoreProcessor, SpectatorClient.PlayingUsers.ToArray()) { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -100,7 +87,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestScoreUpdates() { - AddRepeatStep("update state", () => spectatorClient.RandomlyUpdateState(), 100); + AddRepeatStep("update state", () => SpectatorClient.RandomlyUpdateState(), 100); AddToggleStep("switch compact mode", expanded => leaderboard.Expanded.Value = expanded); } @@ -113,11 +100,25 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestChangeScoringMode() { - AddRepeatStep("update state", () => spectatorClient.RandomlyUpdateState(), 5); + AddRepeatStep("update state", () => SpectatorClient.RandomlyUpdateState(), 5); AddStep("change to classic", () => config.SetValue(OsuSetting.ScoreDisplayMode, ScoringMode.Classic)); AddStep("change to standardised", () => config.SetValue(OsuSetting.ScoreDisplayMode, ScoringMode.Standardised)); } + protected override RoomTestDependencies CreateRoomDependencies() => new TestDependencies(); + + protected class TestDependencies : MultiplayerRoomTestDependencies + { + public readonly TestMultiplayerSpectatorClient SpectatorClient = new TestMultiplayerSpectatorClient(); + public readonly UserLookupCache LookupCache = new TestSceneCurrentlyPlayingDisplay.TestUserLookupCache(); + + public TestDependencies() + { + CacheAs(SpectatorClient); + CacheAs(LookupCache); + } + } + public class TestMultiplayerSpectatorClient : TestSpectatorClient { private readonly Dictionary lastHeaders = new Dictionary(); From 8241fee4a8aab8853d11853ca0757e08e13a3664 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 25 Jun 2021 09:22:34 +0300 Subject: [PATCH 062/168] Add failing test case --- .../TestSceneRulesetSkinProvidingContainer.cs | 100 ++++++++++++++++++ .../Testing/TestSceneRulesetDependencies.cs | 2 +- 2 files changed, 101 insertions(+), 1 deletion(-) create mode 100644 osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs diff --git a/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs b/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs new file mode 100644 index 0000000000..dd78f28351 --- /dev/null +++ b/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs @@ -0,0 +1,100 @@ +// 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.Linq; +using JetBrains.Annotations; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Audio.Sample; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.OpenGL.Textures; +using osu.Framework.Graphics.Textures; +using osu.Game.Audio; +using osu.Game.Beatmaps; +using osu.Game.Rulesets; +using osu.Game.Skinning; +using osu.Game.Tests.Testing; +using osu.Game.Tests.Visual; + +namespace osu.Game.Tests.Rulesets +{ + public class TestSceneRulesetSkinProvidingContainer : OsuTestScene + { + [Resolved] + private SkinManager skins { get; set; } + + private SkinRequester requester; + + protected override Ruleset CreateRuleset() => new TestRuleset(); + + [Test] + public void TestEarlyAddedSkinRequester() + { + ISample transformerSampleOnBdl = null; + + // need a legacy skin to plug the TestRuleset's legacy transformer, which is required for testing this. + AddStep("set legacy skin", () => skins.CurrentSkinInfo.Value = DefaultLegacySkin.Info); + + AddStep("setup provider", () => + { + var rulesetSkinProvider = new RulesetSkinProvidingContainer(Ruleset.Value.CreateInstance(), Beatmap.Value.Beatmap, Beatmap.Value.Skin); + + rulesetSkinProvider.Add(requester = new SkinRequester()); + + requester.OnBdl += () => transformerSampleOnBdl = requester.GetSample(new SampleInfo(TestLegacySkinTransformer.VIRTUAL_SAMPLE_NAME)); + + Child = rulesetSkinProvider; + }); + + AddAssert("requester got correct initial sample", () => transformerSampleOnBdl != null); + } + + private class SkinRequester : Drawable, ISkin + { + private ISkinSource skin; + + public event Action OnBdl; + + [BackgroundDependencyLoader] + private void load(ISkinSource skin) + { + this.skin = skin; + + OnBdl?.Invoke(); + } + + public Drawable GetDrawableComponent(ISkinComponent component) => skin.GetDrawableComponent(component); + + public Texture GetTexture(string componentName, WrapMode wrapModeS = default, WrapMode wrapModeT = default) => skin.GetTexture(componentName); + + public ISample GetSample(ISampleInfo sampleInfo) => skin.GetSample(sampleInfo); + + public IBindable GetConfig(TLookup lookup) => skin.GetConfig(lookup); + } + + private class TestRuleset : TestSceneRulesetDependencies.TestRuleset + { + public override ISkin CreateLegacySkinProvider(ISkin skin, IBeatmap beatmap) => new TestLegacySkinTransformer(skin); + } + + private class TestLegacySkinTransformer : LegacySkinTransformer + { + public const string VIRTUAL_SAMPLE_NAME = "virtual-test-sample"; + + public TestLegacySkinTransformer([NotNull] ISkin skin) + : base(skin) + { + } + + public override ISample GetSample(ISampleInfo sampleInfo) + { + if (sampleInfo.LookupNames.Single() == VIRTUAL_SAMPLE_NAME) + return new SampleVirtual(); + + return base.GetSample(sampleInfo); + } + } + } +} diff --git a/osu.Game.Tests/Testing/TestSceneRulesetDependencies.cs b/osu.Game.Tests/Testing/TestSceneRulesetDependencies.cs index 97087e31ab..fb50da32f3 100644 --- a/osu.Game.Tests/Testing/TestSceneRulesetDependencies.cs +++ b/osu.Game.Tests/Testing/TestSceneRulesetDependencies.cs @@ -52,7 +52,7 @@ namespace osu.Game.Tests.Testing Dependencies.Get() != null); } - private class TestRuleset : Ruleset + public class TestRuleset : Ruleset { public override string Description => string.Empty; public override string ShortName => string.Empty; From f07008a0a284023e50f63e7de3c2392d2de0e626 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 25 Jun 2021 09:55:29 +0300 Subject: [PATCH 063/168] Fix `RulesetSkinProvidingContainer` potentially late in setting up skin sources --- .../Skinning/RulesetSkinProvidingContainer.cs | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs index c48aeca99a..abf5cb040a 100644 --- a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs +++ b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs @@ -42,27 +42,30 @@ namespace osu.Game.Skinning }; } - [Resolved] - private ISkinSource skinSource { get; set; } + private ISkinSource parentSource; - [BackgroundDependencyLoader] - private void load() + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) { - UpdateSkins(); - skinSource.SourceChanged += OnSourceChanged; + parentSource = parent.Get(); + + UpdateSkinSources(); + + parentSource.SourceChanged += OnSourceChanged; + + return base.CreateChildDependencies(parent); } protected override void OnSourceChanged() { - UpdateSkins(); + UpdateSkinSources(); base.OnSourceChanged(); } - protected virtual void UpdateSkins() + protected virtual void UpdateSkinSources() { SkinSources.Clear(); - foreach (var skin in skinSource.AllSources) + foreach (var skin in parentSource.AllSources) { switch (skin) { @@ -93,8 +96,8 @@ namespace osu.Game.Skinning { base.Dispose(isDisposing); - if (skinSource != null) - skinSource.SourceChanged -= OnSourceChanged; + if (parentSource != null) + parentSource.SourceChanged -= OnSourceChanged; } } } From 06e357647abce9f383d0dee2c66bfc85440a41e7 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 25 Jun 2021 10:40:07 +0300 Subject: [PATCH 064/168] OnBdl -> OnLoadAsync --- .../Rulesets/TestSceneRulesetSkinProvidingContainer.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs b/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs index dd78f28351..3072c466e0 100644 --- a/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs +++ b/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs @@ -32,7 +32,7 @@ namespace osu.Game.Tests.Rulesets [Test] public void TestEarlyAddedSkinRequester() { - ISample transformerSampleOnBdl = null; + ISample transformerSampleOnLoad = null; // need a legacy skin to plug the TestRuleset's legacy transformer, which is required for testing this. AddStep("set legacy skin", () => skins.CurrentSkinInfo.Value = DefaultLegacySkin.Info); @@ -43,26 +43,26 @@ namespace osu.Game.Tests.Rulesets rulesetSkinProvider.Add(requester = new SkinRequester()); - requester.OnBdl += () => transformerSampleOnBdl = requester.GetSample(new SampleInfo(TestLegacySkinTransformer.VIRTUAL_SAMPLE_NAME)); + requester.OnLoadAsync += () => transformerSampleOnLoad = requester.GetSample(new SampleInfo(TestLegacySkinTransformer.VIRTUAL_SAMPLE_NAME)); Child = rulesetSkinProvider; }); - AddAssert("requester got correct initial sample", () => transformerSampleOnBdl != null); + AddAssert("requester got correct initial sample", () => transformerSampleOnLoad != null); } private class SkinRequester : Drawable, ISkin { private ISkinSource skin; - public event Action OnBdl; + public event Action OnLoadAsync; [BackgroundDependencyLoader] private void load(ISkinSource skin) { this.skin = skin; - OnBdl?.Invoke(); + OnLoadAsync?.Invoke(); } public Drawable GetDrawableComponent(ISkinComponent component) => skin.GetDrawableComponent(component); From 8d7705dc923b04ea037803260fc40fbc02f55933 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 25 Jun 2021 10:55:23 +0300 Subject: [PATCH 065/168] Test using a simple `GetTexture` lookup instead Presumes that `RulesetSkinProvidingContainer` doesn't allow falling back to parent skins, whatsoever. --- .../TestSceneRulesetSkinProvidingContainer.cs | 37 +++---------------- 1 file changed, 5 insertions(+), 32 deletions(-) diff --git a/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs b/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs index 3072c466e0..50b75ea0c5 100644 --- a/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs +++ b/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs @@ -2,8 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Linq; -using JetBrains.Annotations; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio.Sample; @@ -12,7 +10,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.OpenGL.Textures; using osu.Framework.Graphics.Textures; using osu.Game.Audio; -using osu.Game.Beatmaps; using osu.Game.Rulesets; using osu.Game.Skinning; using osu.Game.Tests.Testing; @@ -27,15 +24,14 @@ namespace osu.Game.Tests.Rulesets private SkinRequester requester; - protected override Ruleset CreateRuleset() => new TestRuleset(); + protected override Ruleset CreateRuleset() => new TestSceneRulesetDependencies.TestRuleset(); [Test] public void TestEarlyAddedSkinRequester() { - ISample transformerSampleOnLoad = null; + Texture textureOnLoad = null; - // need a legacy skin to plug the TestRuleset's legacy transformer, which is required for testing this. - AddStep("set legacy skin", () => skins.CurrentSkinInfo.Value = DefaultLegacySkin.Info); + AddStep("set skin", () => skins.CurrentSkinInfo.Value = DefaultLegacySkin.Info); AddStep("setup provider", () => { @@ -43,12 +39,12 @@ namespace osu.Game.Tests.Rulesets rulesetSkinProvider.Add(requester = new SkinRequester()); - requester.OnLoadAsync += () => transformerSampleOnLoad = requester.GetSample(new SampleInfo(TestLegacySkinTransformer.VIRTUAL_SAMPLE_NAME)); + requester.OnLoadAsync += () => textureOnLoad = requester.GetTexture("hitcircle"); Child = rulesetSkinProvider; }); - AddAssert("requester got correct initial sample", () => transformerSampleOnLoad != null); + AddAssert("requester got correct initial texture", () => textureOnLoad != null); } private class SkinRequester : Drawable, ISkin @@ -73,28 +69,5 @@ namespace osu.Game.Tests.Rulesets public IBindable GetConfig(TLookup lookup) => skin.GetConfig(lookup); } - - private class TestRuleset : TestSceneRulesetDependencies.TestRuleset - { - public override ISkin CreateLegacySkinProvider(ISkin skin, IBeatmap beatmap) => new TestLegacySkinTransformer(skin); - } - - private class TestLegacySkinTransformer : LegacySkinTransformer - { - public const string VIRTUAL_SAMPLE_NAME = "virtual-test-sample"; - - public TestLegacySkinTransformer([NotNull] ISkin skin) - : base(skin) - { - } - - public override ISample GetSample(ISampleInfo sampleInfo) - { - if (sampleInfo.LookupNames.Single() == VIRTUAL_SAMPLE_NAME) - return new SampleVirtual(); - - return base.GetSample(sampleInfo); - } - } } } From 13ed52a990cc4f1b53b3a1f1b37694859b5427cf Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 25 Jun 2021 11:16:26 +0300 Subject: [PATCH 066/168] Fix weird license misindent No idea how the hell that happened... R# silent about it, of course. --- .../Rulesets/TestSceneRulesetSkinProvidingContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs b/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs index 50b75ea0c5..b6800e40e4 100644 --- a/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs +++ b/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs @@ -1,5 +1,5 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. - // See the LICENCE file in the repository root for full licence text. +// See the LICENCE file in the repository root for full licence text. using System; using NUnit.Framework; From 7aefbe3da16c3abecb1cfa172f4aa18a0bb329a7 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 25 Jun 2021 17:37:02 +0900 Subject: [PATCH 067/168] Move UserLookupCache inside dependencies --- .../Visual/Gameplay/TestSceneSpectator.cs | 11 -------- .../TestSceneMultiSpectatorLeaderboard.cs | 18 ------------- .../TestSceneMultiSpectatorScreen.cs | 18 ------------- ...TestSceneMultiplayerGameplayLeaderboard.cs | 25 +++++++------------ ...ies.cs => IMultiplayerTestDependencies.cs} | 8 +++++- .../MultiplayerRoomTestDependencies.cs | 7 +++++- .../Multiplayer/MultiplayerTestScene.cs | 3 ++- ...cies.cs => IOnlinePlayTestDependencies.cs} | 2 +- .../Visual/OnlinePlay/OnlinePlayTestScene.cs | 2 +- .../Visual/OnlinePlay/RoomTestDependencies.cs | 2 +- osu.Game/Tests/Visual/TestUserLookupCache.cs | 19 ++++++++++++++ 11 files changed, 46 insertions(+), 69 deletions(-) rename osu.Game/Tests/Visual/Multiplayer/{IMultiplayerRoomTestDependencies.cs => IMultiplayerTestDependencies.cs} (71%) rename osu.Game/Tests/Visual/OnlinePlay/{IRoomTestDependencies.cs => IOnlinePlayTestDependencies.cs} (95%) create mode 100644 osu.Game/Tests/Visual/TestUserLookupCache.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs index 6eeb3596a8..7f3056771d 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs @@ -2,8 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System.Linq; -using System.Threading; -using System.Threading.Tasks; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -232,14 +230,5 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("load screen", () => LoadScreen(spectatorScreen = new SoloSpectator(streamingUser))); AddUntilStep("wait for screen load", () => spectatorScreen.LoadState == LoadState.Loaded); } - - internal class TestUserLookupCache : UserLookupCache - { - protected override Task ComputeValueAsync(int lookup, CancellationToken token = default) => Task.FromResult(new User - { - Id = lookup, - Username = $"User {lookup}" - }); - } } } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs index 54594fbfc8..7f586009a1 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs @@ -3,19 +3,15 @@ using System.Collections.Generic; using System.Linq; -using System.Threading; -using System.Threading.Tasks; using NUnit.Framework; using osu.Framework.Testing; using osu.Framework.Timing; -using osu.Game.Database; using osu.Game.Online.Spectator; using osu.Game.Rulesets.Osu.Scoring; using osu.Game.Screens.OnlinePlay.Multiplayer.Spectate; using osu.Game.Screens.Play.HUD; using osu.Game.Tests.Visual.OnlinePlay; using osu.Game.Tests.Visual.Spectator; -using osu.Game.Users; namespace osu.Game.Tests.Visual.Multiplayer { @@ -128,24 +124,10 @@ namespace osu.Game.Tests.Visual.Multiplayer protected class TestDependencies : MultiplayerRoomTestDependencies { public readonly TestSpectatorClient SpectatorClient = new TestSpectatorClient(); - public readonly UserLookupCache LookupCache = new TestUserLookupCache(); public TestDependencies() { CacheAs(SpectatorClient); - CacheAs(LookupCache); - } - } - - private class TestUserLookupCache : UserLookupCache - { - protected override Task ComputeValueAsync(int lookup, CancellationToken token = default) - { - return Task.FromResult(new User - { - Id = lookup, - Username = $"User {lookup}" - }); } } } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs index 64f4b4c0d6..e6634a598e 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs @@ -3,21 +3,17 @@ using System.Collections.Generic; using System.Linq; -using System.Threading; -using System.Threading.Tasks; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Testing; using osu.Game.Beatmaps; -using osu.Game.Database; using osu.Game.Online.Spectator; using osu.Game.Screens.OnlinePlay.Multiplayer.Spectate; using osu.Game.Screens.Play; using osu.Game.Tests.Beatmaps.IO; using osu.Game.Tests.Visual.OnlinePlay; using osu.Game.Tests.Visual.Spectator; -using osu.Game.Users; namespace osu.Game.Tests.Visual.Multiplayer { @@ -289,24 +285,10 @@ namespace osu.Game.Tests.Visual.Multiplayer protected class TestDependencies : MultiplayerRoomTestDependencies { public readonly TestSpectatorClient SpectatorClient = new TestSpectatorClient(); - public readonly UserLookupCache LookupCache = new TestUserLookupCache(); public TestDependencies() { CacheAs(SpectatorClient); - CacheAs(LookupCache); - } - } - - internal class TestUserLookupCache : UserLookupCache - { - protected override Task ComputeValueAsync(int lookup, CancellationToken token = default) - { - return Task.FromResult(new User - { - Id = lookup, - Username = $"User {lookup}" - }); } } } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs index 9c30151f05..ae0938ae37 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs @@ -6,11 +6,11 @@ using System.Collections.Generic; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Testing; using osu.Framework.Utils; using osu.Game.Configuration; -using osu.Game.Database; using osu.Game.Online.API; using osu.Game.Online.Spectator; using osu.Game.Replays.Legacy; @@ -18,7 +18,6 @@ using osu.Game.Rulesets.Osu.Scoring; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osu.Game.Screens.Play.HUD; -using osu.Game.Tests.Visual.Online; using osu.Game.Tests.Visual.OnlinePlay; using osu.Game.Tests.Visual.Spectator; @@ -26,12 +25,10 @@ namespace osu.Game.Tests.Visual.Multiplayer { public class TestSceneMultiplayerGameplayLeaderboard : MultiplayerTestScene { - private const int users = 16; + private static IEnumerable users => Enumerable.Range(0, 16); public TestMultiplayerSpectatorClient SpectatorClient => RoomDependencies?.SpectatorClient; - public UserLookupCache LookupCache => RoomDependencies?.LookupCache; - protected new TestDependencies RoomDependencies => (TestDependencies)base.RoomDependencies; private MultiplayerGameplayLeaderboard leaderboard; @@ -57,14 +54,11 @@ namespace osu.Game.Tests.Visual.Multiplayer var playable = Beatmap.Value.GetPlayableBeatmap(Ruleset.Value); - for (int i = 0; i < users; i++) - SpectatorClient.StartPlay(i, Beatmap.Value.BeatmapInfo.OnlineBeatmapID ?? 0); + foreach (var user in users) + SpectatorClient.StartPlay(user, Beatmap.Value.BeatmapInfo.OnlineBeatmapID ?? 0); - SpectatorClient.Schedule(() => - { - Client.CurrentMatchPlayingUserIds.Clear(); - Client.CurrentMatchPlayingUserIds.AddRange(SpectatorClient.PlayingUsers); - }); + // Todo: This is REALLY bad. + Client.CurrentMatchPlayingUserIds.AddRange(users); Children = new Drawable[] { @@ -73,7 +67,7 @@ namespace osu.Game.Tests.Visual.Multiplayer scoreProcessor.ApplyBeatmap(playable); - LoadComponentAsync(leaderboard = new MultiplayerGameplayLeaderboard(scoreProcessor, SpectatorClient.PlayingUsers.ToArray()) + LoadComponentAsync(leaderboard = new MultiplayerGameplayLeaderboard(scoreProcessor, users.ToArray()) { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -94,7 +88,8 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestUserQuit() { - AddRepeatStep("mark user quit", () => Client.CurrentMatchPlayingUserIds.RemoveAt(0), users); + foreach (var user in users) + AddStep($"mark user {user} quit", () => Client.RemoveUser(LookupCache.GetUserAsync(user).Result.AsNonNull())); } [Test] @@ -110,12 +105,10 @@ namespace osu.Game.Tests.Visual.Multiplayer protected class TestDependencies : MultiplayerRoomTestDependencies { public readonly TestMultiplayerSpectatorClient SpectatorClient = new TestMultiplayerSpectatorClient(); - public readonly UserLookupCache LookupCache = new TestSceneCurrentlyPlayingDisplay.TestUserLookupCache(); public TestDependencies() { CacheAs(SpectatorClient); - CacheAs(LookupCache); } } diff --git a/osu.Game/Tests/Visual/Multiplayer/IMultiplayerRoomTestDependencies.cs b/osu.Game/Tests/Visual/Multiplayer/IMultiplayerTestDependencies.cs similarity index 71% rename from osu.Game/Tests/Visual/Multiplayer/IMultiplayerRoomTestDependencies.cs rename to osu.Game/Tests/Visual/Multiplayer/IMultiplayerTestDependencies.cs index 0b53bdd04e..331c4dcba6 100644 --- a/osu.Game/Tests/Visual/Multiplayer/IMultiplayerRoomTestDependencies.cs +++ b/osu.Game/Tests/Visual/Multiplayer/IMultiplayerTestDependencies.cs @@ -1,13 +1,14 @@ // 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.Database; using osu.Game.Online.Multiplayer; using osu.Game.Screens.OnlinePlay; using osu.Game.Tests.Visual.OnlinePlay; namespace osu.Game.Tests.Visual.Multiplayer { - public interface IMultiplayerRoomTestDependencies : IRoomTestDependencies + public interface IMultiplayerTestDependencies : IOnlinePlayTestDependencies { /// /// The cached . @@ -18,5 +19,10 @@ namespace osu.Game.Tests.Visual.Multiplayer /// The cached . /// new TestMultiplayerRoomManager RoomManager { get; } + + /// + /// The cached . + /// + TestUserLookupCache LookupCache { get; } } } diff --git a/osu.Game/Tests/Visual/Multiplayer/MultiplayerRoomTestDependencies.cs b/osu.Game/Tests/Visual/Multiplayer/MultiplayerRoomTestDependencies.cs index e3913dd291..369563c5d9 100644 --- a/osu.Game/Tests/Visual/Multiplayer/MultiplayerRoomTestDependencies.cs +++ b/osu.Game/Tests/Visual/Multiplayer/MultiplayerRoomTestDependencies.cs @@ -1,21 +1,26 @@ // 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.Database; using osu.Game.Online.Multiplayer; using osu.Game.Screens.OnlinePlay; using osu.Game.Tests.Visual.OnlinePlay; namespace osu.Game.Tests.Visual.Multiplayer { - public class MultiplayerRoomTestDependencies : RoomTestDependencies, IMultiplayerRoomTestDependencies + public class MultiplayerRoomTestDependencies : RoomTestDependencies, IMultiplayerTestDependencies { public TestMultiplayerClient Client { get; } + public TestUserLookupCache LookupCache { get; } public new TestMultiplayerRoomManager RoomManager => (TestMultiplayerRoomManager)base.RoomManager; public MultiplayerRoomTestDependencies() { Client = new TestMultiplayerClient(RoomManager); + LookupCache = new TestUserLookupCache(); + CacheAs(Client); + CacheAs(LookupCache); } protected override IRoomManager CreateRoomManager() => new TestMultiplayerRoomManager(); diff --git a/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs b/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs index 69dfd41e04..5c717bfb2f 100644 --- a/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs +++ b/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs @@ -8,13 +8,14 @@ using osu.Game.Tests.Visual.OnlinePlay; namespace osu.Game.Tests.Visual.Multiplayer { - public abstract class MultiplayerTestScene : OnlinePlayTestScene + public abstract class MultiplayerTestScene : OnlinePlayTestScene, IMultiplayerTestDependencies { public const int PLAYER_1_ID = 55; public const int PLAYER_2_ID = 56; public TestMultiplayerClient Client => RoomDependencies.Client; public new TestMultiplayerRoomManager RoomManager => RoomDependencies.RoomManager; + public TestUserLookupCache LookupCache => RoomDependencies?.LookupCache; protected new MultiplayerRoomTestDependencies RoomDependencies => (MultiplayerRoomTestDependencies)base.RoomDependencies; diff --git a/osu.Game/Tests/Visual/OnlinePlay/IRoomTestDependencies.cs b/osu.Game/Tests/Visual/OnlinePlay/IOnlinePlayTestDependencies.cs similarity index 95% rename from osu.Game/Tests/Visual/OnlinePlay/IRoomTestDependencies.cs rename to osu.Game/Tests/Visual/OnlinePlay/IOnlinePlayTestDependencies.cs index 848e2aa77f..a4e0368adc 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/IRoomTestDependencies.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/IOnlinePlayTestDependencies.cs @@ -8,7 +8,7 @@ using osu.Game.Screens.OnlinePlay.Lounge.Components; namespace osu.Game.Tests.Visual.OnlinePlay { - public interface IRoomTestDependencies + public interface IOnlinePlayTestDependencies { /// /// The cached . diff --git a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs index 5e46ea0544..39ce219092 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs @@ -16,7 +16,7 @@ namespace osu.Game.Tests.Visual.OnlinePlay /// /// A providing all the dependencies cached by for testing s. /// - public abstract class OnlinePlayTestScene : ScreenTestScene, IRoomTestDependencies + public abstract class OnlinePlayTestScene : ScreenTestScene, IOnlinePlayTestDependencies { public Bindable SelectedRoom => RoomDependencies?.SelectedRoom; public IRoomManager RoomManager => RoomDependencies?.RoomManager; diff --git a/osu.Game/Tests/Visual/OnlinePlay/RoomTestDependencies.cs b/osu.Game/Tests/Visual/OnlinePlay/RoomTestDependencies.cs index 9d22f9e286..b833a9400f 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/RoomTestDependencies.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/RoomTestDependencies.cs @@ -15,7 +15,7 @@ namespace osu.Game.Tests.Visual.OnlinePlay /// /// Contains dependencies for testing online-play rooms. /// - public class RoomTestDependencies : IReadOnlyDependencyContainer, IRoomTestDependencies + public class RoomTestDependencies : IReadOnlyDependencyContainer, IOnlinePlayTestDependencies { public Bindable SelectedRoom { get; } public IRoomManager RoomManager { get; } diff --git a/osu.Game/Tests/Visual/TestUserLookupCache.cs b/osu.Game/Tests/Visual/TestUserLookupCache.cs new file mode 100644 index 0000000000..d2941b5bd5 --- /dev/null +++ b/osu.Game/Tests/Visual/TestUserLookupCache.cs @@ -0,0 +1,19 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Threading; +using System.Threading.Tasks; +using osu.Game.Database; +using osu.Game.Users; + +namespace osu.Game.Tests.Visual +{ + public class TestUserLookupCache : UserLookupCache + { + protected override Task ComputeValueAsync(int lookup, CancellationToken token = default) => Task.FromResult(new User + { + Id = lookup, + Username = $"User {lookup}" + }); + } +} From c0d2b41d4c54fa861b9945dd9b26795b064478da Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 25 Jun 2021 17:55:16 +0900 Subject: [PATCH 068/168] Move SpectatorClient into multiplayer dependencies --- .../TestSceneMultiSpectatorLeaderboard.cs | 19 ------------------- .../TestSceneMultiSpectatorScreen.cs | 19 ------------------- ...TestSceneMultiplayerGameplayLeaderboard.cs | 11 ++--------- .../TestSceneMultiplayerRoomManager.cs | 1 + .../IMultiplayerTestDependencies.cs | 6 ++++++ .../MultiplayerRoomTestDependencies.cs | 8 ++++++++ .../Multiplayer/MultiplayerTestScene.cs | 2 ++ 7 files changed, 19 insertions(+), 47 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs index 7f586009a1..2537198503 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs @@ -6,21 +6,14 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Testing; using osu.Framework.Timing; -using osu.Game.Online.Spectator; using osu.Game.Rulesets.Osu.Scoring; using osu.Game.Screens.OnlinePlay.Multiplayer.Spectate; using osu.Game.Screens.Play.HUD; -using osu.Game.Tests.Visual.OnlinePlay; -using osu.Game.Tests.Visual.Spectator; namespace osu.Game.Tests.Visual.Multiplayer { public class TestSceneMultiSpectatorLeaderboard : MultiplayerTestScene { - public TestSpectatorClient SpectatorClient => RoomDependencies?.SpectatorClient; - - protected new TestDependencies RoomDependencies => (TestDependencies)base.RoomDependencies; - private Dictionary clocks; private MultiSpectatorLeaderboard leaderboard; @@ -118,17 +111,5 @@ namespace osu.Game.Tests.Visual.Multiplayer private void assertCombo(int userId, int expectedCombo) => AddUntilStep($"player {userId} has {expectedCombo} combo", () => this.ChildrenOfType().Single(s => s.User?.Id == userId).Combo.Value == expectedCombo); - - protected override RoomTestDependencies CreateRoomDependencies() => new TestDependencies(); - - protected class TestDependencies : MultiplayerRoomTestDependencies - { - public readonly TestSpectatorClient SpectatorClient = new TestSpectatorClient(); - - public TestDependencies() - { - CacheAs(SpectatorClient); - } - } } } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs index e6634a598e..873f8ca35b 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs @@ -8,21 +8,14 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Testing; using osu.Game.Beatmaps; -using osu.Game.Online.Spectator; using osu.Game.Screens.OnlinePlay.Multiplayer.Spectate; using osu.Game.Screens.Play; using osu.Game.Tests.Beatmaps.IO; -using osu.Game.Tests.Visual.OnlinePlay; -using osu.Game.Tests.Visual.Spectator; namespace osu.Game.Tests.Visual.Multiplayer { public class TestSceneMultiSpectatorScreen : MultiplayerTestScene { - public TestSpectatorClient SpectatorClient => RoomDependencies?.SpectatorClient; - - protected new TestDependencies RoomDependencies => (TestDependencies)base.RoomDependencies; - [Resolved] private OsuGameBase game { get; set; } @@ -279,17 +272,5 @@ namespace osu.Game.Tests.Visual.Multiplayer private Player getPlayer(int userId) => getInstance(userId).ChildrenOfType().Single(); private PlayerArea getInstance(int userId) => spectatorScreen.ChildrenOfType().Single(p => p.UserId == userId); - - protected override RoomTestDependencies CreateRoomDependencies() => new TestDependencies(); - - protected class TestDependencies : MultiplayerRoomTestDependencies - { - public readonly TestSpectatorClient SpectatorClient = new TestSpectatorClient(); - - public TestDependencies() - { - CacheAs(SpectatorClient); - } - } } } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs index ae0938ae37..b7ccc17397 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs @@ -27,9 +27,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { private static IEnumerable users => Enumerable.Range(0, 16); - public TestMultiplayerSpectatorClient SpectatorClient => RoomDependencies?.SpectatorClient; - - protected new TestDependencies RoomDependencies => (TestDependencies)base.RoomDependencies; + public new TestMultiplayerSpectatorClient SpectatorClient => (TestMultiplayerSpectatorClient)RoomDependencies?.SpectatorClient; private MultiplayerGameplayLeaderboard leaderboard; private OsuConfigManager config; @@ -104,12 +102,7 @@ namespace osu.Game.Tests.Visual.Multiplayer protected class TestDependencies : MultiplayerRoomTestDependencies { - public readonly TestMultiplayerSpectatorClient SpectatorClient = new TestMultiplayerSpectatorClient(); - - public TestDependencies() - { - CacheAs(SpectatorClient); - } + protected override TestSpectatorClient CreateSpectatorClient() => new TestMultiplayerSpectatorClient(); } public class TestMultiplayerSpectatorClient : TestSpectatorClient diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerRoomManager.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerRoomManager.cs index 302e6998cc..225a9ff703 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerRoomManager.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerRoomManager.cs @@ -148,6 +148,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { public TestDependencies() { + // Need to set these values as early as possible. RoomManager.TimeBetweenListingPolls.Value = 1; RoomManager.TimeBetweenSelectionPolls.Value = 1; } diff --git a/osu.Game/Tests/Visual/Multiplayer/IMultiplayerTestDependencies.cs b/osu.Game/Tests/Visual/Multiplayer/IMultiplayerTestDependencies.cs index 331c4dcba6..c5f9e85003 100644 --- a/osu.Game/Tests/Visual/Multiplayer/IMultiplayerTestDependencies.cs +++ b/osu.Game/Tests/Visual/Multiplayer/IMultiplayerTestDependencies.cs @@ -5,6 +5,7 @@ using osu.Game.Database; using osu.Game.Online.Multiplayer; using osu.Game.Screens.OnlinePlay; using osu.Game.Tests.Visual.OnlinePlay; +using osu.Game.Tests.Visual.Spectator; namespace osu.Game.Tests.Visual.Multiplayer { @@ -24,5 +25,10 @@ namespace osu.Game.Tests.Visual.Multiplayer /// The cached . /// TestUserLookupCache LookupCache { get; } + + /// + /// The cached . + /// + TestSpectatorClient SpectatorClient { get; } } } diff --git a/osu.Game/Tests/Visual/Multiplayer/MultiplayerRoomTestDependencies.cs b/osu.Game/Tests/Visual/Multiplayer/MultiplayerRoomTestDependencies.cs index 369563c5d9..a3f7156848 100644 --- a/osu.Game/Tests/Visual/Multiplayer/MultiplayerRoomTestDependencies.cs +++ b/osu.Game/Tests/Visual/Multiplayer/MultiplayerRoomTestDependencies.cs @@ -3,8 +3,10 @@ using osu.Game.Database; using osu.Game.Online.Multiplayer; +using osu.Game.Online.Spectator; using osu.Game.Screens.OnlinePlay; using osu.Game.Tests.Visual.OnlinePlay; +using osu.Game.Tests.Visual.Spectator; namespace osu.Game.Tests.Visual.Multiplayer { @@ -12,17 +14,23 @@ namespace osu.Game.Tests.Visual.Multiplayer { public TestMultiplayerClient Client { get; } public TestUserLookupCache LookupCache { get; } + public TestSpectatorClient SpectatorClient { get; } + public new TestMultiplayerRoomManager RoomManager => (TestMultiplayerRoomManager)base.RoomManager; public MultiplayerRoomTestDependencies() { Client = new TestMultiplayerClient(RoomManager); LookupCache = new TestUserLookupCache(); + SpectatorClient = CreateSpectatorClient(); CacheAs(Client); CacheAs(LookupCache); + CacheAs(SpectatorClient); } protected override IRoomManager CreateRoomManager() => new TestMultiplayerRoomManager(); + + protected virtual TestSpectatorClient CreateSpectatorClient() => new TestSpectatorClient(); } } diff --git a/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs b/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs index 5c717bfb2f..19e7536286 100644 --- a/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs +++ b/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs @@ -5,6 +5,7 @@ using NUnit.Framework; using osu.Game.Online.Rooms; using osu.Game.Tests.Beatmaps; using osu.Game.Tests.Visual.OnlinePlay; +using osu.Game.Tests.Visual.Spectator; namespace osu.Game.Tests.Visual.Multiplayer { @@ -16,6 +17,7 @@ namespace osu.Game.Tests.Visual.Multiplayer public TestMultiplayerClient Client => RoomDependencies.Client; public new TestMultiplayerRoomManager RoomManager => RoomDependencies.RoomManager; public TestUserLookupCache LookupCache => RoomDependencies?.LookupCache; + public TestSpectatorClient SpectatorClient => RoomDependencies?.SpectatorClient; protected new MultiplayerRoomTestDependencies RoomDependencies => (MultiplayerRoomTestDependencies)base.RoomDependencies; From 04279e7f5cbfa0a90982f4cfb333be8f0e99f4b0 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 25 Jun 2021 18:02:53 +0900 Subject: [PATCH 069/168] Add some xmldocs --- .../Visual/Multiplayer/IMultiplayerTestDependencies.cs | 3 +++ .../Multiplayer/MultiplayerRoomTestDependencies.cs | 4 +++- .../Tests/Visual/Multiplayer/MultiplayerTestScene.cs | 9 +++++---- .../Tests/Visual/Multiplayer/TestMultiplayerClient.cs | 3 +++ .../Visual/Multiplayer/TestMultiplayerRoomManager.cs | 4 ++++ osu.Game/Tests/Visual/OnlinePlay/BasicTestRoomManager.cs | 4 ++++ .../Visual/OnlinePlay/IOnlinePlayTestDependencies.cs | 3 +++ osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs | 2 +- osu.Game/Tests/Visual/OnlinePlay/RoomTestDependencies.cs | 2 +- 9 files changed, 27 insertions(+), 7 deletions(-) diff --git a/osu.Game/Tests/Visual/Multiplayer/IMultiplayerTestDependencies.cs b/osu.Game/Tests/Visual/Multiplayer/IMultiplayerTestDependencies.cs index c5f9e85003..46ad5a5a1c 100644 --- a/osu.Game/Tests/Visual/Multiplayer/IMultiplayerTestDependencies.cs +++ b/osu.Game/Tests/Visual/Multiplayer/IMultiplayerTestDependencies.cs @@ -9,6 +9,9 @@ using osu.Game.Tests.Visual.Spectator; namespace osu.Game.Tests.Visual.Multiplayer { + /// + /// Interface that defines the dependencies required for multiplayer test scenes. + /// public interface IMultiplayerTestDependencies : IOnlinePlayTestDependencies { /// diff --git a/osu.Game/Tests/Visual/Multiplayer/MultiplayerRoomTestDependencies.cs b/osu.Game/Tests/Visual/Multiplayer/MultiplayerRoomTestDependencies.cs index a3f7156848..4c5afb9b58 100644 --- a/osu.Game/Tests/Visual/Multiplayer/MultiplayerRoomTestDependencies.cs +++ b/osu.Game/Tests/Visual/Multiplayer/MultiplayerRoomTestDependencies.cs @@ -10,12 +10,14 @@ using osu.Game.Tests.Visual.Spectator; namespace osu.Game.Tests.Visual.Multiplayer { + /// + /// Contains the basic dependencies of multiplayer test scenes. + /// public class MultiplayerRoomTestDependencies : RoomTestDependencies, IMultiplayerTestDependencies { public TestMultiplayerClient Client { get; } public TestUserLookupCache LookupCache { get; } public TestSpectatorClient SpectatorClient { get; } - public new TestMultiplayerRoomManager RoomManager => (TestMultiplayerRoomManager)base.RoomManager; public MultiplayerRoomTestDependencies() diff --git a/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs b/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs index 19e7536286..6a9ae4c772 100644 --- a/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs +++ b/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs @@ -9,6 +9,9 @@ using osu.Game.Tests.Visual.Spectator; namespace osu.Game.Tests.Visual.Multiplayer { + /// + /// The base test scene for all multiplayer components and screens. + /// public abstract class MultiplayerTestScene : OnlinePlayTestScene, IMultiplayerTestDependencies { public const int PLAYER_1_ID = 55; @@ -21,8 +24,6 @@ namespace osu.Game.Tests.Visual.Multiplayer protected new MultiplayerRoomTestDependencies RoomDependencies => (MultiplayerRoomTestDependencies)base.RoomDependencies; - protected override RoomTestDependencies CreateRoomDependencies() => new MultiplayerRoomTestDependencies(); - private readonly bool joinRoom; protected MultiplayerTestScene(bool joinRoom = true) @@ -58,9 +59,9 @@ namespace osu.Game.Tests.Visual.Multiplayer base.SetUpSteps(); if (joinRoom) - { AddUntilStep("wait for room join", () => Client.Room != null); - } } + + protected override RoomTestDependencies CreateRoomDependencies() => new MultiplayerRoomTestDependencies(); } } diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index b12bd8091d..b0c8d6d19b 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -20,6 +20,9 @@ using osu.Game.Users; namespace osu.Game.Tests.Visual.Multiplayer { + /// + /// A for use in multiplayer test scenes. Should generally not be used by itself outside of a . + /// public class TestMultiplayerClient : MultiplayerClient { public override IBindable IsConnected => isConnected; diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomManager.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomManager.cs index 6f4a464d57..5d66cdba02 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomManager.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomManager.cs @@ -11,11 +11,15 @@ using osu.Game.Online.API.Requests; using osu.Game.Online.Rooms; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; +using osu.Game.Screens.OnlinePlay.Components; using osu.Game.Screens.OnlinePlay.Lounge.Components; using osu.Game.Screens.OnlinePlay.Multiplayer; namespace osu.Game.Tests.Visual.Multiplayer { + /// + /// A for use in multiplayer test scenes. Should generally not be used by itself outside of a . + /// public class TestMultiplayerRoomManager : MultiplayerRoomManager { [Resolved] diff --git a/osu.Game/Tests/Visual/OnlinePlay/BasicTestRoomManager.cs b/osu.Game/Tests/Visual/OnlinePlay/BasicTestRoomManager.cs index 67beea9117..81b93fe5b5 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/BasicTestRoomManager.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/BasicTestRoomManager.cs @@ -8,10 +8,14 @@ using osu.Game.Beatmaps; using osu.Game.Online.Rooms; using osu.Game.Rulesets; using osu.Game.Screens.OnlinePlay; +using osu.Game.Screens.OnlinePlay.Components; using osu.Game.Users; namespace osu.Game.Tests.Visual.OnlinePlay { + /// + /// A very simple for use in online-play test scenes. + /// public class BasicTestRoomManager : IRoomManager { public event Action RoomsUpdated diff --git a/osu.Game/Tests/Visual/OnlinePlay/IOnlinePlayTestDependencies.cs b/osu.Game/Tests/Visual/OnlinePlay/IOnlinePlayTestDependencies.cs index a4e0368adc..bc5d524bc4 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/IOnlinePlayTestDependencies.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/IOnlinePlayTestDependencies.cs @@ -8,6 +8,9 @@ using osu.Game.Screens.OnlinePlay.Lounge.Components; namespace osu.Game.Tests.Visual.OnlinePlay { + /// + /// Interface that defines the dependencies required for online-play test scenes. + /// public interface IOnlinePlayTestDependencies { /// diff --git a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs index 39ce219092..6c1339fd85 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs @@ -14,7 +14,7 @@ using osu.Game.Screens.OnlinePlay.Lounge.Components; namespace osu.Game.Tests.Visual.OnlinePlay { /// - /// A providing all the dependencies cached by for testing s. + /// A base test scene for all online-play components and screens. /// public abstract class OnlinePlayTestScene : ScreenTestScene, IOnlinePlayTestDependencies { diff --git a/osu.Game/Tests/Visual/OnlinePlay/RoomTestDependencies.cs b/osu.Game/Tests/Visual/OnlinePlay/RoomTestDependencies.cs index b833a9400f..7b198c128b 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/RoomTestDependencies.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/RoomTestDependencies.cs @@ -13,7 +13,7 @@ using osu.Game.Screens.OnlinePlay.Lounge.Components; namespace osu.Game.Tests.Visual.OnlinePlay { /// - /// Contains dependencies for testing online-play rooms. + /// Contains the basic dependencies of online-play test scenes. /// public class RoomTestDependencies : IReadOnlyDependencyContainer, IOnlinePlayTestDependencies { From a27a647ae72a7610caa442af2c293db494ccf9ae Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 25 Jun 2021 18:07:47 +0900 Subject: [PATCH 070/168] Rename RoomDependencies -> OnlinePlayDependencies --- ...TestSceneMultiplayerGameplayLeaderboard.cs | 4 +- .../TestSceneMultiplayerRoomManager.cs | 2 +- .../TestScenePlaylistsMatchSettingsOverlay.cs | 4 +- .../MultiplayerRoomTestDependencies.cs | 2 +- .../Multiplayer/MultiplayerTestScene.cs | 12 ++--- .../Visual/OnlinePlay/BasicTestRoomManager.cs | 2 +- .../OnlinePlay/IOnlinePlayTestDependencies.cs | 2 +- ...ncies.cs => OnlinePlayTestDependencies.cs} | 6 +-- .../Visual/OnlinePlay/OnlinePlayTestScene.cs | 50 ++++++++++--------- 9 files changed, 44 insertions(+), 40 deletions(-) rename osu.Game/Tests/Visual/OnlinePlay/{RoomTestDependencies.cs => OnlinePlayTestDependencies.cs} (92%) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs index b7ccc17397..2938e813b2 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs @@ -27,7 +27,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { private static IEnumerable users => Enumerable.Range(0, 16); - public new TestMultiplayerSpectatorClient SpectatorClient => (TestMultiplayerSpectatorClient)RoomDependencies?.SpectatorClient; + public new TestMultiplayerSpectatorClient SpectatorClient => (TestMultiplayerSpectatorClient)OnlinePlayDependencies?.SpectatorClient; private MultiplayerGameplayLeaderboard leaderboard; private OsuConfigManager config; @@ -98,7 +98,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("change to standardised", () => config.SetValue(OsuSetting.ScoreDisplayMode, ScoringMode.Standardised)); } - protected override RoomTestDependencies CreateRoomDependencies() => new TestDependencies(); + protected override OnlinePlayTestDependencies CreateOnlinePlayDependencies() => new TestDependencies(); protected class TestDependencies : MultiplayerRoomTestDependencies { diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerRoomManager.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerRoomManager.cs index 225a9ff703..c750bbed4b 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerRoomManager.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerRoomManager.cs @@ -14,7 +14,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [HeadlessTest] public class TestSceneMultiplayerRoomManager : MultiplayerTestScene { - protected override RoomTestDependencies CreateRoomDependencies() => new TestDependencies(); + protected override OnlinePlayTestDependencies CreateOnlinePlayDependencies() => new TestDependencies(); public TestSceneMultiplayerRoomManager() : base(false) diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs index b52b6a6a0e..fd59ebad30 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs @@ -21,7 +21,7 @@ namespace osu.Game.Tests.Visual.Playlists private TestRoomSettings settings; - protected override RoomTestDependencies CreateRoomDependencies() => new TestDependencies(); + protected override OnlinePlayTestDependencies CreateOnlinePlayDependencies() => new TestDependencies(); [SetUp] public new void Setup() => Schedule(() => @@ -120,7 +120,7 @@ namespace osu.Game.Tests.Visual.Playlists public OsuSpriteText ErrorText => ((MatchSettings)Settings).ErrorText; } - private class TestDependencies : RoomTestDependencies + private class TestDependencies : OnlinePlayTestDependencies { protected override IRoomManager CreateRoomManager() => new TestRoomManager(); } diff --git a/osu.Game/Tests/Visual/Multiplayer/MultiplayerRoomTestDependencies.cs b/osu.Game/Tests/Visual/Multiplayer/MultiplayerRoomTestDependencies.cs index 4c5afb9b58..e7885890b4 100644 --- a/osu.Game/Tests/Visual/Multiplayer/MultiplayerRoomTestDependencies.cs +++ b/osu.Game/Tests/Visual/Multiplayer/MultiplayerRoomTestDependencies.cs @@ -13,7 +13,7 @@ namespace osu.Game.Tests.Visual.Multiplayer /// /// Contains the basic dependencies of multiplayer test scenes. /// - public class MultiplayerRoomTestDependencies : RoomTestDependencies, IMultiplayerTestDependencies + public class MultiplayerRoomTestDependencies : OnlinePlayTestDependencies, IMultiplayerTestDependencies { public TestMultiplayerClient Client { get; } public TestUserLookupCache LookupCache { get; } diff --git a/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs b/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs index 6a9ae4c772..90ffc83ccd 100644 --- a/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs +++ b/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs @@ -17,12 +17,12 @@ namespace osu.Game.Tests.Visual.Multiplayer public const int PLAYER_1_ID = 55; public const int PLAYER_2_ID = 56; - public TestMultiplayerClient Client => RoomDependencies.Client; - public new TestMultiplayerRoomManager RoomManager => RoomDependencies.RoomManager; - public TestUserLookupCache LookupCache => RoomDependencies?.LookupCache; - public TestSpectatorClient SpectatorClient => RoomDependencies?.SpectatorClient; + public TestMultiplayerClient Client => OnlinePlayDependencies.Client; + public new TestMultiplayerRoomManager RoomManager => OnlinePlayDependencies.RoomManager; + public TestUserLookupCache LookupCache => OnlinePlayDependencies?.LookupCache; + public TestSpectatorClient SpectatorClient => OnlinePlayDependencies?.SpectatorClient; - protected new MultiplayerRoomTestDependencies RoomDependencies => (MultiplayerRoomTestDependencies)base.RoomDependencies; + protected new MultiplayerRoomTestDependencies OnlinePlayDependencies => (MultiplayerRoomTestDependencies)base.OnlinePlayDependencies; private readonly bool joinRoom; @@ -62,6 +62,6 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("wait for room join", () => Client.Room != null); } - protected override RoomTestDependencies CreateRoomDependencies() => new MultiplayerRoomTestDependencies(); + protected override OnlinePlayTestDependencies CreateOnlinePlayDependencies() => new MultiplayerRoomTestDependencies(); } } diff --git a/osu.Game/Tests/Visual/OnlinePlay/BasicTestRoomManager.cs b/osu.Game/Tests/Visual/OnlinePlay/BasicTestRoomManager.cs index 81b93fe5b5..813e617ac5 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/BasicTestRoomManager.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/BasicTestRoomManager.cs @@ -14,7 +14,7 @@ using osu.Game.Users; namespace osu.Game.Tests.Visual.OnlinePlay { /// - /// A very simple for use in online-play test scenes. + /// A very simple for use in online play test scenes. /// public class BasicTestRoomManager : IRoomManager { diff --git a/osu.Game/Tests/Visual/OnlinePlay/IOnlinePlayTestDependencies.cs b/osu.Game/Tests/Visual/OnlinePlay/IOnlinePlayTestDependencies.cs index bc5d524bc4..8c262e718a 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/IOnlinePlayTestDependencies.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/IOnlinePlayTestDependencies.cs @@ -9,7 +9,7 @@ using osu.Game.Screens.OnlinePlay.Lounge.Components; namespace osu.Game.Tests.Visual.OnlinePlay { /// - /// Interface that defines the dependencies required for online-play test scenes. + /// Interface that defines the dependencies required for online play test scenes. /// public interface IOnlinePlayTestDependencies { diff --git a/osu.Game/Tests/Visual/OnlinePlay/RoomTestDependencies.cs b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestDependencies.cs similarity index 92% rename from osu.Game/Tests/Visual/OnlinePlay/RoomTestDependencies.cs rename to osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestDependencies.cs index 7b198c128b..e45aadfd84 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/RoomTestDependencies.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestDependencies.cs @@ -13,9 +13,9 @@ using osu.Game.Screens.OnlinePlay.Lounge.Components; namespace osu.Game.Tests.Visual.OnlinePlay { /// - /// Contains the basic dependencies of online-play test scenes. + /// Contains the basic dependencies of online play test scenes. /// - public class RoomTestDependencies : IReadOnlyDependencyContainer, IOnlinePlayTestDependencies + public class OnlinePlayTestDependencies : IReadOnlyDependencyContainer, IOnlinePlayTestDependencies { public Bindable SelectedRoom { get; } public IRoomManager RoomManager { get; } @@ -31,7 +31,7 @@ namespace osu.Game.Tests.Visual.OnlinePlay private readonly List drawableComponents = new List(); private readonly DependencyContainer dependencies; - public RoomTestDependencies() + public OnlinePlayTestDependencies() { SelectedRoom = new Bindable(); RoomManager = CreateRoomManager(); diff --git a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs index 6c1339fd85..e1c21ce377 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs @@ -14,18 +14,22 @@ using osu.Game.Screens.OnlinePlay.Lounge.Components; namespace osu.Game.Tests.Visual.OnlinePlay { /// - /// A base test scene for all online-play components and screens. + /// A base test scene for all online play components and screens. /// public abstract class OnlinePlayTestScene : ScreenTestScene, IOnlinePlayTestDependencies { - public Bindable SelectedRoom => RoomDependencies?.SelectedRoom; - public IRoomManager RoomManager => RoomDependencies?.RoomManager; - public Bindable Filter => RoomDependencies?.Filter; - public OngoingOperationTracker OngoingOperationTracker => RoomDependencies?.OngoingOperationTracker; - public OnlinePlayBeatmapAvailabilityTracker AvailabilityTracker => RoomDependencies?.AvailabilityTracker; + public Bindable SelectedRoom => OnlinePlayDependencies?.SelectedRoom; + public IRoomManager RoomManager => OnlinePlayDependencies?.RoomManager; + public Bindable Filter => OnlinePlayDependencies?.Filter; + public OngoingOperationTracker OngoingOperationTracker => OnlinePlayDependencies?.OngoingOperationTracker; + public OnlinePlayBeatmapAvailabilityTracker AvailabilityTracker => OnlinePlayDependencies?.AvailabilityTracker; - protected RoomTestDependencies RoomDependencies => delegatedDependencies?.RoomDependencies; - private DelegatedRoomDependencyContainer delegatedDependencies; + /// + /// All dependencies required for online play components and screens. + /// + protected OnlinePlayTestDependencies OnlinePlayDependencies => dependencies?.OnlinePlayDependencies; + + private DelegatedDependencyContainer dependencies; protected override Container Content => content; private readonly Container content; @@ -42,8 +46,8 @@ namespace osu.Game.Tests.Visual.OnlinePlay protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) { - delegatedDependencies = new DelegatedRoomDependencyContainer(base.CreateChildDependencies(parent)); - return delegatedDependencies; + dependencies = new DelegatedDependencyContainer(base.CreateChildDependencies(parent)); + return dependencies; } [SetUp] @@ -51,46 +55,46 @@ namespace osu.Game.Tests.Visual.OnlinePlay { // Reset the room dependencies to a fresh state. drawableDependenciesContainer.Clear(); - delegatedDependencies.RoomDependencies = CreateRoomDependencies(); - drawableDependenciesContainer.AddRange(RoomDependencies.DrawableComponents); + dependencies.OnlinePlayDependencies = CreateOnlinePlayDependencies(); + drawableDependenciesContainer.AddRange(OnlinePlayDependencies.DrawableComponents); }); /// /// Creates the room dependencies. Called every . /// /// - /// Any custom dependencies required for online-play sub-classes should be added here. + /// Any custom dependencies required for online play sub-classes should be added here. /// - protected virtual RoomTestDependencies CreateRoomDependencies() => new RoomTestDependencies(); + protected virtual OnlinePlayTestDependencies CreateOnlinePlayDependencies() => new OnlinePlayTestDependencies(); /// - /// A providing a mutable lookup source for room dependencies. + /// A providing a mutable lookup source for online play dependencies. /// - private class DelegatedRoomDependencyContainer : IReadOnlyDependencyContainer + private class DelegatedDependencyContainer : IReadOnlyDependencyContainer { /// - /// The room's dependencies. + /// The online play dependencies. /// - public RoomTestDependencies RoomDependencies { get; set; } + public OnlinePlayTestDependencies OnlinePlayDependencies { get; set; } private readonly IReadOnlyDependencyContainer parent; private readonly DependencyContainer injectableDependencies; /// - /// Creates a new . + /// Creates a new . /// - /// The fallback to use when cannot satisfy a dependency. - public DelegatedRoomDependencyContainer(IReadOnlyDependencyContainer parent) + /// The fallback to use when cannot satisfy a dependency. + public DelegatedDependencyContainer(IReadOnlyDependencyContainer parent) { this.parent = parent; injectableDependencies = new DependencyContainer(this); } public object Get(Type type) - => RoomDependencies?.Get(type) ?? parent.Get(type); + => OnlinePlayDependencies?.Get(type) ?? parent.Get(type); public object Get(Type type, CacheInfo info) - => RoomDependencies?.Get(type, info) ?? parent.Get(type, info); + => OnlinePlayDependencies?.Get(type, info) ?? parent.Get(type, info); public void Inject(T instance) where T : class From ff5e590d323ce868cce4eeaed71280645e6ae729 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 25 Jun 2021 12:00:46 +0300 Subject: [PATCH 071/168] Add local source for testing --- .../TestSceneRulesetSkinProvidingContainer.cs | 23 ++++++++++++++----- 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs b/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs index b6800e40e4..b058cc3694 100644 --- a/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs +++ b/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs @@ -19,11 +19,11 @@ namespace osu.Game.Tests.Rulesets { public class TestSceneRulesetSkinProvidingContainer : OsuTestScene { - [Resolved] - private SkinManager skins { get; set; } - private SkinRequester requester; + [Cached(typeof(ISkin))] + private readonly TestSkinProvider testSkin = new TestSkinProvider(); + protected override Ruleset CreateRuleset() => new TestSceneRulesetDependencies.TestRuleset(); [Test] @@ -31,15 +31,13 @@ namespace osu.Game.Tests.Rulesets { Texture textureOnLoad = null; - AddStep("set skin", () => skins.CurrentSkinInfo.Value = DefaultLegacySkin.Info); - AddStep("setup provider", () => { var rulesetSkinProvider = new RulesetSkinProvidingContainer(Ruleset.Value.CreateInstance(), Beatmap.Value.Beatmap, Beatmap.Value.Skin); rulesetSkinProvider.Add(requester = new SkinRequester()); - requester.OnLoadAsync += () => textureOnLoad = requester.GetTexture("hitcircle"); + requester.OnLoadAsync += () => textureOnLoad = requester.GetTexture(TestSkinProvider.TEXTURE_NAME); Child = rulesetSkinProvider; }); @@ -69,5 +67,18 @@ namespace osu.Game.Tests.Rulesets public IBindable GetConfig(TLookup lookup) => skin.GetConfig(lookup); } + + private class TestSkinProvider : ISkin + { + public const string TEXTURE_NAME = "some-texture"; + + public Drawable GetDrawableComponent(ISkinComponent component) => throw new NotImplementedException(); + + public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => componentName == TEXTURE_NAME ? Texture.WhitePixel : null; + + public ISample GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException(); + + public IBindable GetConfig(TLookup lookup) => throw new NotImplementedException(); + } } } From 04cc390c419f73da5bc074fe0ef168f594e51a7f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 25 Jun 2021 19:16:54 +0900 Subject: [PATCH 072/168] Fix TestSceneMultiplayer resolving the wrong client --- .../Multiplayer/TestSceneMultiplayer.cs | 41 ++++++++++++------- 1 file changed, 27 insertions(+), 14 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs index 599dfb082b..c93640e7b5 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs @@ -18,6 +18,7 @@ using osu.Game.Overlays.Mods; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Mods; +using osu.Game.Screens; using osu.Game.Screens.OnlinePlay.Components; using osu.Game.Screens.OnlinePlay.Match.Components; using osu.Game.Screens.OnlinePlay.Multiplayer; @@ -30,14 +31,13 @@ namespace osu.Game.Tests.Visual.Multiplayer { public class TestSceneMultiplayer : ScreenTestScene { - private TestMultiplayer multiplayerScreen; - private BeatmapManager beatmaps; private RulesetStore rulesets; private BeatmapSetInfo importedSet; - private TestMultiplayerClient client => multiplayerScreen.Client; - private Room room => client.APIRoom; + private DependenciesScreen dependenciesScreen; + private TestMultiplayer multiplayerScreen; + private TestMultiplayerClient client; public TestSceneMultiplayer() { @@ -229,30 +229,43 @@ namespace osu.Game.Tests.Visual.Multiplayer private void loadMultiplayer() { - AddStep("show", () => + AddStep("create multiplayer screen", () => multiplayerScreen = new TestMultiplayer()); + + AddStep("load dependencies", () => { - multiplayerScreen = new TestMultiplayer(); + client = new TestMultiplayerClient(multiplayerScreen.RoomManager); - // Needs to be added at a higher level since the multiplayer screen becomes non-current. - Child = multiplayerScreen.Client; + // The screen gets suspended so it stops receiving updates. + Child = client; - LoadScreen(multiplayerScreen); + LoadScreen(dependenciesScreen = new DependenciesScreen(client)); }); - AddUntilStep("wait for loaded", () => multiplayerScreen.IsLoaded); + AddUntilStep("wait for dependencies to load", () => dependenciesScreen.IsLoaded); + + AddStep("load multiplayer", () => LoadScreen(multiplayerScreen)); + AddUntilStep("wait for multiplayer to load", () => multiplayerScreen.IsLoaded); } - private class TestMultiplayer : Screens.OnlinePlay.Multiplayer.Multiplayer + /// + /// Used for the sole purpose of adding as a resolvable dependency. + /// + private class DependenciesScreen : OsuScreen { [Cached(typeof(MultiplayerClient))] public readonly TestMultiplayerClient Client; - public TestMultiplayer() + public DependenciesScreen(TestMultiplayerClient client) { - Client = new TestMultiplayerClient((TestMultiplayerRoomManager)RoomManager); + Client = client; } + } - protected override RoomManager CreateRoomManager() => new TestMultiplayerRoomManager(); + private class TestMultiplayer : Screens.OnlinePlay.Multiplayer.Multiplayer + { + public new TestMultiplayerRoomManager RoomManager { get; private set; } + + protected override RoomManager CreateRoomManager() => RoomManager = new TestMultiplayerRoomManager(); } } } From 84c9ede966af09a0b4b1337ce7750d01b14d7c03 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 25 Jun 2021 13:17:11 +0300 Subject: [PATCH 073/168] Fix incorrect pushed changes This should've been in the original commit, but for some reason got deleted out. --- .../TestSceneRulesetSkinProvidingContainer.cs | 23 +++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs b/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs index b058cc3694..0dde0a8194 100644 --- a/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs +++ b/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio.Sample; @@ -21,11 +22,15 @@ namespace osu.Game.Tests.Rulesets { private SkinRequester requester; - [Cached(typeof(ISkin))] - private readonly TestSkinProvider testSkin = new TestSkinProvider(); - protected override Ruleset CreateRuleset() => new TestSceneRulesetDependencies.TestRuleset(); + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) + { + var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); + dependencies.CacheAs(new TestSkinProvider()); + return dependencies; + } + [Test] public void TestEarlyAddedSkinRequester() { @@ -68,7 +73,7 @@ namespace osu.Game.Tests.Rulesets public IBindable GetConfig(TLookup lookup) => skin.GetConfig(lookup); } - private class TestSkinProvider : ISkin + private class TestSkinProvider : ISkinSource { public const string TEXTURE_NAME = "some-texture"; @@ -79,6 +84,16 @@ namespace osu.Game.Tests.Rulesets public ISample GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException(); public IBindable GetConfig(TLookup lookup) => throw new NotImplementedException(); + + public event Action SourceChanged + { + add { } + remove { } + } + + public ISkin FindProvider(Func lookupFunction) => lookupFunction(this) ? this : null; + + public IEnumerable AllSources => new[] { this }; } } } From 57f2b4f812870bb1d9804ee7d8c079c106bd84d9 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 25 Jun 2021 20:09:03 +0900 Subject: [PATCH 074/168] Fix incorrect parent class --- .../Visual/Multiplayer/TestSceneMultiplayerMatchFooter.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchFooter.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchFooter.cs index afe66d2686..4e08ffef17 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchFooter.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchFooter.cs @@ -5,11 +5,10 @@ using NUnit.Framework; using osu.Framework.Graphics; using osu.Game.Online.Rooms; using osu.Game.Screens.OnlinePlay.Multiplayer.Match; -using osu.Game.Tests.Visual.OnlinePlay; namespace osu.Game.Tests.Visual.Multiplayer { - public class TestSceneMultiplayerMatchFooter : OnlinePlayTestScene + public class TestSceneMultiplayerMatchFooter : MultiplayerTestScene { [SetUp] public new void Setup() => Schedule(() => From 1ed61b9b98757b1c2b63a1127c3569f0d728624a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 25 Jun 2021 20:11:38 +0900 Subject: [PATCH 075/168] Fix up dependencies class names --- .../Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs | 4 ++-- .../Visual/Multiplayer/TestSceneMultiplayerRoomManager.cs | 4 ++-- .../Playlists/TestScenePlaylistsMatchSettingsOverlay.cs | 4 ++-- osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs | 4 ++-- ...tDependencies.cs => MultiplayerTestSceneDependencies.cs} | 4 ++-- osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs | 6 +++--- ...stDependencies.cs => OnlinePlayTestSceneDependencies.cs} | 4 ++-- 7 files changed, 15 insertions(+), 15 deletions(-) rename osu.Game/Tests/Visual/Multiplayer/{MultiplayerRoomTestDependencies.cs => MultiplayerTestSceneDependencies.cs} (89%) rename osu.Game/Tests/Visual/OnlinePlay/{OnlinePlayTestDependencies.cs => OnlinePlayTestSceneDependencies.cs} (94%) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs index 2938e813b2..0e368b59dd 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs @@ -98,9 +98,9 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("change to standardised", () => config.SetValue(OsuSetting.ScoreDisplayMode, ScoringMode.Standardised)); } - protected override OnlinePlayTestDependencies CreateOnlinePlayDependencies() => new TestDependencies(); + protected override OnlinePlayTestSceneDependencies CreateOnlinePlayDependencies() => new TestDependencies(); - protected class TestDependencies : MultiplayerRoomTestDependencies + protected class TestDependencies : MultiplayerTestSceneDependencies { protected override TestSpectatorClient CreateSpectatorClient() => new TestMultiplayerSpectatorClient(); } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerRoomManager.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerRoomManager.cs index c750bbed4b..b17427a30b 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerRoomManager.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerRoomManager.cs @@ -14,7 +14,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [HeadlessTest] public class TestSceneMultiplayerRoomManager : MultiplayerTestScene { - protected override OnlinePlayTestDependencies CreateOnlinePlayDependencies() => new TestDependencies(); + protected override OnlinePlayTestSceneDependencies CreateOnlinePlayDependencies() => new TestDependencies(); public TestSceneMultiplayerRoomManager() : base(false) @@ -144,7 +144,7 @@ namespace osu.Game.Tests.Visual.Multiplayer return room; } - private class TestDependencies : MultiplayerRoomTestDependencies + private class TestDependencies : MultiplayerTestSceneDependencies { public TestDependencies() { diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs index fd59ebad30..a320cb240f 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs @@ -21,7 +21,7 @@ namespace osu.Game.Tests.Visual.Playlists private TestRoomSettings settings; - protected override OnlinePlayTestDependencies CreateOnlinePlayDependencies() => new TestDependencies(); + protected override OnlinePlayTestSceneDependencies CreateOnlinePlayDependencies() => new TestDependencies(); [SetUp] public new void Setup() => Schedule(() => @@ -120,7 +120,7 @@ namespace osu.Game.Tests.Visual.Playlists public OsuSpriteText ErrorText => ((MatchSettings)Settings).ErrorText; } - private class TestDependencies : OnlinePlayTestDependencies + private class TestDependencies : OnlinePlayTestSceneDependencies { protected override IRoomManager CreateRoomManager() => new TestRoomManager(); } diff --git a/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs b/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs index 90ffc83ccd..da01010dbf 100644 --- a/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs +++ b/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs @@ -22,7 +22,7 @@ namespace osu.Game.Tests.Visual.Multiplayer public TestUserLookupCache LookupCache => OnlinePlayDependencies?.LookupCache; public TestSpectatorClient SpectatorClient => OnlinePlayDependencies?.SpectatorClient; - protected new MultiplayerRoomTestDependencies OnlinePlayDependencies => (MultiplayerRoomTestDependencies)base.OnlinePlayDependencies; + protected new MultiplayerTestSceneDependencies OnlinePlayDependencies => (MultiplayerTestSceneDependencies)base.OnlinePlayDependencies; private readonly bool joinRoom; @@ -62,6 +62,6 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("wait for room join", () => Client.Room != null); } - protected override OnlinePlayTestDependencies CreateOnlinePlayDependencies() => new MultiplayerRoomTestDependencies(); + protected override OnlinePlayTestSceneDependencies CreateOnlinePlayDependencies() => new MultiplayerTestSceneDependencies(); } } diff --git a/osu.Game/Tests/Visual/Multiplayer/MultiplayerRoomTestDependencies.cs b/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestSceneDependencies.cs similarity index 89% rename from osu.Game/Tests/Visual/Multiplayer/MultiplayerRoomTestDependencies.cs rename to osu.Game/Tests/Visual/Multiplayer/MultiplayerTestSceneDependencies.cs index e7885890b4..8fd21b5e4a 100644 --- a/osu.Game/Tests/Visual/Multiplayer/MultiplayerRoomTestDependencies.cs +++ b/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestSceneDependencies.cs @@ -13,14 +13,14 @@ namespace osu.Game.Tests.Visual.Multiplayer /// /// Contains the basic dependencies of multiplayer test scenes. /// - public class MultiplayerRoomTestDependencies : OnlinePlayTestDependencies, IMultiplayerTestDependencies + public class MultiplayerTestSceneDependencies : OnlinePlayTestSceneDependencies, IMultiplayerTestDependencies { public TestMultiplayerClient Client { get; } public TestUserLookupCache LookupCache { get; } public TestSpectatorClient SpectatorClient { get; } public new TestMultiplayerRoomManager RoomManager => (TestMultiplayerRoomManager)base.RoomManager; - public MultiplayerRoomTestDependencies() + public MultiplayerTestSceneDependencies() { Client = new TestMultiplayerClient(RoomManager); LookupCache = new TestUserLookupCache(); diff --git a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs index e1c21ce377..4920ca2058 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs @@ -27,7 +27,7 @@ namespace osu.Game.Tests.Visual.OnlinePlay /// /// All dependencies required for online play components and screens. /// - protected OnlinePlayTestDependencies OnlinePlayDependencies => dependencies?.OnlinePlayDependencies; + protected OnlinePlayTestSceneDependencies OnlinePlayDependencies => dependencies?.OnlinePlayDependencies; private DelegatedDependencyContainer dependencies; @@ -65,7 +65,7 @@ namespace osu.Game.Tests.Visual.OnlinePlay /// /// Any custom dependencies required for online play sub-classes should be added here. /// - protected virtual OnlinePlayTestDependencies CreateOnlinePlayDependencies() => new OnlinePlayTestDependencies(); + protected virtual OnlinePlayTestSceneDependencies CreateOnlinePlayDependencies() => new OnlinePlayTestSceneDependencies(); /// /// A providing a mutable lookup source for online play dependencies. @@ -75,7 +75,7 @@ namespace osu.Game.Tests.Visual.OnlinePlay /// /// The online play dependencies. /// - public OnlinePlayTestDependencies OnlinePlayDependencies { get; set; } + public OnlinePlayTestSceneDependencies OnlinePlayDependencies { get; set; } private readonly IReadOnlyDependencyContainer parent; private readonly DependencyContainer injectableDependencies; diff --git a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestDependencies.cs b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestSceneDependencies.cs similarity index 94% rename from osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestDependencies.cs rename to osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestSceneDependencies.cs index e45aadfd84..b73a982fb6 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestDependencies.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestSceneDependencies.cs @@ -15,7 +15,7 @@ namespace osu.Game.Tests.Visual.OnlinePlay /// /// Contains the basic dependencies of online play test scenes. /// - public class OnlinePlayTestDependencies : IReadOnlyDependencyContainer, IOnlinePlayTestDependencies + public class OnlinePlayTestSceneDependencies : IReadOnlyDependencyContainer, IOnlinePlayTestDependencies { public Bindable SelectedRoom { get; } public IRoomManager RoomManager { get; } @@ -31,7 +31,7 @@ namespace osu.Game.Tests.Visual.OnlinePlay private readonly List drawableComponents = new List(); private readonly DependencyContainer dependencies; - public OnlinePlayTestDependencies() + public OnlinePlayTestSceneDependencies() { SelectedRoom = new Bindable(); RoomManager = CreateRoomManager(); From c93c615f5e704d248f82fecb1f6fdda06e248aed Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 25 Jun 2021 20:15:30 +0900 Subject: [PATCH 076/168] Also fix up interface names --- ...TestDependencies.cs => IMultiplayerTestSceneDependencies.cs} | 2 +- osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs | 2 +- .../Visual/Multiplayer/MultiplayerTestSceneDependencies.cs | 2 +- ...yTestDependencies.cs => IOnlinePlayTestSceneDependencies.cs} | 2 +- osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs | 2 +- .../Tests/Visual/OnlinePlay/OnlinePlayTestSceneDependencies.cs | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) rename osu.Game/Tests/Visual/Multiplayer/{IMultiplayerTestDependencies.cs => IMultiplayerTestSceneDependencies.cs} (92%) rename osu.Game/Tests/Visual/OnlinePlay/{IOnlinePlayTestDependencies.cs => IOnlinePlayTestSceneDependencies.cs} (95%) diff --git a/osu.Game/Tests/Visual/Multiplayer/IMultiplayerTestDependencies.cs b/osu.Game/Tests/Visual/Multiplayer/IMultiplayerTestSceneDependencies.cs similarity index 92% rename from osu.Game/Tests/Visual/Multiplayer/IMultiplayerTestDependencies.cs rename to osu.Game/Tests/Visual/Multiplayer/IMultiplayerTestSceneDependencies.cs index 46ad5a5a1c..204c189591 100644 --- a/osu.Game/Tests/Visual/Multiplayer/IMultiplayerTestDependencies.cs +++ b/osu.Game/Tests/Visual/Multiplayer/IMultiplayerTestSceneDependencies.cs @@ -12,7 +12,7 @@ namespace osu.Game.Tests.Visual.Multiplayer /// /// Interface that defines the dependencies required for multiplayer test scenes. /// - public interface IMultiplayerTestDependencies : IOnlinePlayTestDependencies + public interface IMultiplayerTestSceneDependencies : IOnlinePlayTestSceneDependencies { /// /// The cached . diff --git a/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs b/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs index da01010dbf..b7d3793ab1 100644 --- a/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs +++ b/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs @@ -12,7 +12,7 @@ namespace osu.Game.Tests.Visual.Multiplayer /// /// The base test scene for all multiplayer components and screens. /// - public abstract class MultiplayerTestScene : OnlinePlayTestScene, IMultiplayerTestDependencies + public abstract class MultiplayerTestScene : OnlinePlayTestScene, IMultiplayerTestSceneDependencies { public const int PLAYER_1_ID = 55; public const int PLAYER_2_ID = 56; diff --git a/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestSceneDependencies.cs b/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestSceneDependencies.cs index 8fd21b5e4a..a2b0b066a7 100644 --- a/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestSceneDependencies.cs +++ b/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestSceneDependencies.cs @@ -13,7 +13,7 @@ namespace osu.Game.Tests.Visual.Multiplayer /// /// Contains the basic dependencies of multiplayer test scenes. /// - public class MultiplayerTestSceneDependencies : OnlinePlayTestSceneDependencies, IMultiplayerTestDependencies + public class MultiplayerTestSceneDependencies : OnlinePlayTestSceneDependencies, IMultiplayerTestSceneDependencies { public TestMultiplayerClient Client { get; } public TestUserLookupCache LookupCache { get; } diff --git a/osu.Game/Tests/Visual/OnlinePlay/IOnlinePlayTestDependencies.cs b/osu.Game/Tests/Visual/OnlinePlay/IOnlinePlayTestSceneDependencies.cs similarity index 95% rename from osu.Game/Tests/Visual/OnlinePlay/IOnlinePlayTestDependencies.cs rename to osu.Game/Tests/Visual/OnlinePlay/IOnlinePlayTestSceneDependencies.cs index 8c262e718a..6e1e831d9b 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/IOnlinePlayTestDependencies.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/IOnlinePlayTestSceneDependencies.cs @@ -11,7 +11,7 @@ namespace osu.Game.Tests.Visual.OnlinePlay /// /// Interface that defines the dependencies required for online play test scenes. /// - public interface IOnlinePlayTestDependencies + public interface IOnlinePlayTestSceneDependencies { /// /// The cached . diff --git a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs index 4920ca2058..997c910dd4 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs @@ -16,7 +16,7 @@ namespace osu.Game.Tests.Visual.OnlinePlay /// /// A base test scene for all online play components and screens. /// - public abstract class OnlinePlayTestScene : ScreenTestScene, IOnlinePlayTestDependencies + public abstract class OnlinePlayTestScene : ScreenTestScene, IOnlinePlayTestSceneDependencies { public Bindable SelectedRoom => OnlinePlayDependencies?.SelectedRoom; public IRoomManager RoomManager => OnlinePlayDependencies?.RoomManager; diff --git a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestSceneDependencies.cs b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestSceneDependencies.cs index b73a982fb6..ddbbfe501b 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestSceneDependencies.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestSceneDependencies.cs @@ -15,7 +15,7 @@ namespace osu.Game.Tests.Visual.OnlinePlay /// /// Contains the basic dependencies of online play test scenes. /// - public class OnlinePlayTestSceneDependencies : IReadOnlyDependencyContainer, IOnlinePlayTestDependencies + public class OnlinePlayTestSceneDependencies : IReadOnlyDependencyContainer, IOnlinePlayTestSceneDependencies { public Bindable SelectedRoom { get; } public IRoomManager RoomManager { get; } From 3e5ae7ea58090d9b30d31cbb52ccb2dfd68fe45a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 25 Jun 2021 20:44:00 +0900 Subject: [PATCH 077/168] Don't join room in participants test --- .../Multiplayer/TestSceneMultiplayerParticipantsList.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs index 7f8f04b718..e94750c695 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs @@ -22,6 +22,11 @@ namespace osu.Game.Tests.Visual.Multiplayer { public class TestSceneMultiplayerParticipantsList : MultiplayerTestScene { + public TestSceneMultiplayerParticipantsList() + : base(false) + { + } + [SetUp] public new void Setup() => Schedule(createNewParticipantsList); From d035633f9514d78b7dfc88a1f567220169eb061b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 25 Jun 2021 20:56:13 +0900 Subject: [PATCH 078/168] Load participants list after joining room --- .../TestSceneMultiplayerParticipantsList.cs | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs index e94750c695..6526f7eea7 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs @@ -22,14 +22,12 @@ namespace osu.Game.Tests.Visual.Multiplayer { public class TestSceneMultiplayerParticipantsList : MultiplayerTestScene { - public TestSceneMultiplayerParticipantsList() - : base(false) + [SetUpSteps] + public void SetupSteps() { + createNewParticipantsList(); } - [SetUp] - public new void Setup() => Schedule(createNewParticipantsList); - [Test] public void TestAddUser() { @@ -93,7 +91,7 @@ namespace osu.Game.Tests.Visual.Multiplayer public void TestCorrectInitialState() { AddStep("set to downloading map", () => Client.ChangeBeatmapAvailability(BeatmapAvailability.Downloading(0))); - AddStep("recreate list", createNewParticipantsList); + createNewParticipantsList(); checkProgressBarVisibility(true); } @@ -238,7 +236,17 @@ namespace osu.Game.Tests.Visual.Multiplayer private void createNewParticipantsList() { - Child = new ParticipantsList { Anchor = Anchor.Centre, Origin = Anchor.Centre, RelativeSizeAxes = Axes.Y, Size = new Vector2(380, 0.7f) }; + ParticipantsList participantsList = null; + + AddStep("create new list", () => Child = participantsList = new ParticipantsList + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Y, + Size = new Vector2(380, 0.7f) + }); + + AddUntilStep("wait for list to load", () => participantsList.IsLoaded); } private void checkProgressBarVisibility(bool visible) => From 50c27d26357f2646548c126c7201398a23ee489e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 25 Jun 2021 19:10:04 +0200 Subject: [PATCH 079/168] Update usages of `IHasTooltip` in line with framework localisation changes --- osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs | 3 ++- .../Sliders/Components/PathControlPointPiece.cs | 3 ++- osu.Game.Tests/Visual/UserInterface/TestSceneOsuIcon.cs | 3 ++- osu.Game/Configuration/SettingSourceAttribute.cs | 2 +- osu.Game/Graphics/Containers/Markdown/OsuMarkdownImage.cs | 3 ++- osu.Game/Graphics/Containers/OsuClickableContainer.cs | 3 ++- osu.Game/Graphics/Cursor/OsuTooltipContainer.cs | 3 ++- osu.Game/Graphics/UserInterface/ExternalLinkButton.cs | 3 ++- osu.Game/Graphics/UserInterface/OsuPasswordTextBox.cs | 3 ++- osu.Game/Graphics/UserInterface/OsuSliderBar.cs | 3 ++- osu.Game/Online/Leaderboards/LeaderboardScore.cs | 5 +++-- osu.Game/Overlays/BeatmapSet/BasicStats.cs | 2 +- osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs | 3 ++- .../Overlays/BeatmapSet/Buttons/HeaderDownloadButton.cs | 3 ++- osu.Game/Overlays/Comments/DrawableComment.cs | 5 +++-- osu.Game/Overlays/Mods/ModButton.cs | 3 ++- osu.Game/Overlays/OverlayPanelDisplayStyleControl.cs | 3 ++- .../Overlays/Profile/Header/Components/DrawableBadge.cs | 3 ++- .../Profile/Header/Components/ExpandDetailsButton.cs | 3 ++- .../Overlays/Profile/Header/Components/FollowersButton.cs | 3 ++- osu.Game/Overlays/Profile/Header/Components/LevelBadge.cs | 3 ++- .../Profile/Header/Components/LevelProgressBar.cs | 3 ++- .../Profile/Header/Components/MappingSubscribersButton.cs | 3 ++- .../Profile/Header/Components/MessageUserButton.cs | 3 ++- .../Profile/Header/Components/OverlinedTotalPlayTime.cs | 3 ++- .../Overlays/Profile/Header/Components/SupporterIcon.cs | 3 ++- .../Sections/Historical/DrawableMostPlayedBeatmap.cs | 2 +- osu.Game/Overlays/RestoreDefaultValueButton.cs | 3 ++- .../Overlays/Settings/Sections/Audio/OffsetSettings.cs | 3 ++- .../Overlays/Settings/Sections/Graphics/LayoutSettings.cs | 2 +- .../Overlays/Settings/Sections/Input/MouseSettings.cs | 3 ++- osu.Game/Overlays/Settings/Sections/SizeSlider.cs | 3 ++- .../Settings/Sections/UserInterface/GeneralSettings.cs | 3 ++- .../Settings/Sections/UserInterface/SongSelectSettings.cs | 5 +++-- osu.Game/Overlays/Settings/SettingsButton.cs | 8 +++++--- osu.Game/Overlays/Settings/SettingsItem.cs | 4 ++-- osu.Game/Rulesets/UI/ModIcon.cs | 3 ++- .../Screens/Edit/Compose/Components/SelectionBoxButton.cs | 3 ++- .../Screens/OnlinePlay/Components/DrawableGameType.cs | 3 ++- osu.Game/Screens/Select/BeatmapInfoWedge.cs | 2 +- osu.Game/Users/Drawables/ClickableAvatar.cs | 7 ++++--- osu.Game/Users/Drawables/DrawableFlag.cs | 3 ++- 42 files changed, 87 insertions(+), 50 deletions(-) diff --git a/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs b/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs index 1c89d9cd00..f89750a96e 100644 --- a/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs +++ b/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs @@ -3,6 +3,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Localisation; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.Settings; using osu.Game.Rulesets.Mania.Configuration; @@ -47,7 +48,7 @@ namespace osu.Game.Rulesets.Mania private class TimeSlider : OsuSliderBar { - public override string TooltipText => Current.Value.ToString("N0") + "ms"; + public override LocalisableString TooltipText => Current.Value.ToString(@"N0") + "ms"; } } } diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs index 48e4db11ca..5b476526c9 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs @@ -13,6 +13,7 @@ using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; +using osu.Framework.Localisation; using osu.Framework.Utils; using osu.Game.Graphics; using osu.Game.Rulesets.Edit; @@ -283,6 +284,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components } } - public string TooltipText => ControlPoint.Type.Value.ToString() ?? string.Empty; + public LocalisableString TooltipText => ControlPoint.Type.Value.ToString() ?? string.Empty; } } diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneOsuIcon.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneOsuIcon.cs index c5374d50ab..096bccae9e 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneOsuIcon.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneOsuIcon.cs @@ -10,6 +10,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; +using osu.Framework.Localisation; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osuTK; @@ -59,7 +60,7 @@ namespace osu.Game.Tests.Visual.UserInterface private class Icon : Container, IHasTooltip { - public string TooltipText { get; } + public LocalisableString TooltipText { get; } public SpriteIcon SpriteIcon { get; } diff --git a/osu.Game/Configuration/SettingSourceAttribute.cs b/osu.Game/Configuration/SettingSourceAttribute.cs index 55636495df..f373e59417 100644 --- a/osu.Game/Configuration/SettingSourceAttribute.cs +++ b/osu.Game/Configuration/SettingSourceAttribute.cs @@ -31,7 +31,7 @@ namespace osu.Game.Configuration { public LocalisableString Label { get; } - public string Description { get; } + public LocalisableString Description { get; } public int? OrderPosition { get; } diff --git a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownImage.cs b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownImage.cs index 75c73af0ce..ce8a9c8f9f 100644 --- a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownImage.cs +++ b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownImage.cs @@ -4,12 +4,13 @@ using Markdig.Syntax.Inlines; using osu.Framework.Graphics.Containers.Markdown; using osu.Framework.Graphics.Cursor; +using osu.Framework.Localisation; namespace osu.Game.Graphics.Containers.Markdown { public class OsuMarkdownImage : MarkdownImage, IHasTooltip { - public string TooltipText { get; } + public LocalisableString TooltipText { get; } public OsuMarkdownImage(LinkInline linkInline) : base(linkInline.Url) diff --git a/osu.Game/Graphics/Containers/OsuClickableContainer.cs b/osu.Game/Graphics/Containers/OsuClickableContainer.cs index 60ded8952d..0bc3c876e1 100644 --- a/osu.Game/Graphics/Containers/OsuClickableContainer.cs +++ b/osu.Game/Graphics/Containers/OsuClickableContainer.cs @@ -5,6 +5,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; +using osu.Framework.Localisation; using osu.Game.Graphics.UserInterface; namespace osu.Game.Graphics.Containers @@ -24,7 +25,7 @@ namespace osu.Game.Graphics.Containers this.sampleSet = sampleSet; } - public virtual string TooltipText { get; set; } + public virtual LocalisableString TooltipText { get; set; } [BackgroundDependencyLoader] private void load() diff --git a/osu.Game/Graphics/Cursor/OsuTooltipContainer.cs b/osu.Game/Graphics/Cursor/OsuTooltipContainer.cs index 57f39bb8c7..81dca99ddd 100644 --- a/osu.Game/Graphics/Cursor/OsuTooltipContainer.cs +++ b/osu.Game/Graphics/Cursor/OsuTooltipContainer.cs @@ -9,6 +9,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; +using osu.Framework.Localisation; using osu.Game.Graphics.Sprites; namespace osu.Game.Graphics.Cursor @@ -32,7 +33,7 @@ namespace osu.Game.Graphics.Cursor public override bool SetContent(object content) { - if (!(content is string contentString)) + if (!(content is LocalisableString contentString)) return false; if (contentString == text.Text) return true; diff --git a/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs b/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs index 5a1eb53fe1..6ad88eaaba 100644 --- a/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs +++ b/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs @@ -7,6 +7,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; +using osu.Framework.Localisation; using osu.Framework.Platform; using osuTK; using osuTK.Graphics; @@ -58,6 +59,6 @@ namespace osu.Game.Graphics.UserInterface return true; } - public string TooltipText => "view in browser"; + public LocalisableString TooltipText => "view in browser"; } } diff --git a/osu.Game/Graphics/UserInterface/OsuPasswordTextBox.cs b/osu.Game/Graphics/UserInterface/OsuPasswordTextBox.cs index ac6f5ceb1b..8e82f4a7c1 100644 --- a/osu.Game/Graphics/UserInterface/OsuPasswordTextBox.cs +++ b/osu.Game/Graphics/UserInterface/OsuPasswordTextBox.cs @@ -12,6 +12,7 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Input; using osu.Framework.Input.Events; +using osu.Framework.Localisation; using osu.Framework.Platform; namespace osu.Game.Graphics.UserInterface @@ -105,7 +106,7 @@ namespace osu.Game.Graphics.UserInterface private class CapsWarning : SpriteIcon, IHasTooltip { - public string TooltipText => @"caps lock is active"; + public LocalisableString TooltipText => "caps lock is active"; public CapsWarning() { diff --git a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs index f58962f8e1..ae16169123 100644 --- a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs +++ b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs @@ -14,6 +14,7 @@ using osu.Framework.Graphics.UserInterface; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; +using osu.Framework.Localisation; namespace osu.Game.Graphics.UserInterface { @@ -34,7 +35,7 @@ namespace osu.Game.Graphics.UserInterface private readonly Box rightBox; private readonly Container nubContainer; - public virtual string TooltipText { get; private set; } + public virtual LocalisableString TooltipText { get; private set; } /// /// Whether to format the tooltip as a percentage or the actual value. diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index 795540b65d..e35d3d6461 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -13,6 +13,7 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; +using osu.Framework.Localisation; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; @@ -295,7 +296,7 @@ namespace osu.Game.Online.Leaderboards public override bool Contains(Vector2 screenSpacePos) => content.Contains(screenSpacePos); - public string TooltipText { get; } + public LocalisableString TooltipText { get; } public ScoreComponentLabel(LeaderboardScoreStatistic statistic) { @@ -365,7 +366,7 @@ namespace osu.Game.Online.Leaderboards }; } - public string TooltipText { get; } + public LocalisableString TooltipText { get; } } public class LeaderboardScoreStatistic diff --git a/osu.Game/Overlays/BeatmapSet/BasicStats.cs b/osu.Game/Overlays/BeatmapSet/BasicStats.cs index cf74c0d4d3..b81c60a5b9 100644 --- a/osu.Game/Overlays/BeatmapSet/BasicStats.cs +++ b/osu.Game/Overlays/BeatmapSet/BasicStats.cs @@ -95,7 +95,7 @@ namespace osu.Game.Overlays.BeatmapSet { private readonly OsuSpriteText value; - public string TooltipText { get; } + public LocalisableString TooltipText { get; } public LocalisableString Value { diff --git a/osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs b/osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs index 7ad6906cea..bb87e7151b 100644 --- a/osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs +++ b/osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs @@ -7,6 +7,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Sprites; +using osu.Framework.Localisation; using osu.Game.Beatmaps; using osu.Game.Graphics.UserInterface; using osu.Game.Online.API; @@ -28,7 +29,7 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons private readonly IBindable localUser = new Bindable(); - public string TooltipText + public LocalisableString TooltipText { get { diff --git a/osu.Game/Overlays/BeatmapSet/Buttons/HeaderDownloadButton.cs b/osu.Game/Overlays/BeatmapSet/Buttons/HeaderDownloadButton.cs index 6d27342049..cef623e59b 100644 --- a/osu.Game/Overlays/BeatmapSet/Buttons/HeaderDownloadButton.cs +++ b/osu.Game/Overlays/BeatmapSet/Buttons/HeaderDownloadButton.cs @@ -7,6 +7,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Sprites; +using osu.Framework.Localisation; using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Containers; @@ -26,7 +27,7 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons private readonly bool noVideo; - public string TooltipText => button.Enabled.Value ? "download this beatmap" : "login to download"; + public LocalisableString TooltipText => button.Enabled.Value ? "download this beatmap" : "login to download"; private readonly IBindable localUser = new Bindable(); diff --git a/osu.Game/Overlays/Comments/DrawableComment.cs b/osu.Game/Overlays/Comments/DrawableComment.cs index 7c47ac655f..d94f8c4b8b 100644 --- a/osu.Game/Overlays/Comments/DrawableComment.cs +++ b/osu.Game/Overlays/Comments/DrawableComment.cs @@ -20,6 +20,7 @@ using System; using osu.Framework.Graphics.Shapes; using osu.Framework.Extensions.IEnumerableExtensions; using System.Collections.Specialized; +using osu.Framework.Localisation; using osu.Game.Overlays.Comments.Buttons; namespace osu.Game.Overlays.Comments @@ -395,7 +396,7 @@ namespace osu.Game.Overlays.Comments private class ParentUsername : FillFlowContainer, IHasTooltip { - public string TooltipText => getParentMessage(); + public LocalisableString TooltipText => getParentMessage(); private readonly Comment parentComment; @@ -427,7 +428,7 @@ namespace osu.Game.Overlays.Comments if (parentComment == null) return string.Empty; - return parentComment.HasMessage ? parentComment.Message : parentComment.IsDeleted ? @"deleted" : string.Empty; + return parentComment.HasMessage ? parentComment.Message : parentComment.IsDeleted ? "deleted" : string.Empty; } } } diff --git a/osu.Game/Overlays/Mods/ModButton.cs b/osu.Game/Overlays/Mods/ModButton.cs index 70424101fd..d0bd24496a 100644 --- a/osu.Game/Overlays/Mods/ModButton.cs +++ b/osu.Game/Overlays/Mods/ModButton.cs @@ -14,6 +14,7 @@ using System; using System.Linq; using osu.Framework.Graphics.Cursor; using osu.Framework.Input.Events; +using osu.Framework.Localisation; using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; @@ -34,7 +35,7 @@ namespace osu.Game.Overlays.Mods /// public Action SelectionChanged; - public string TooltipText => (SelectedMod?.Description ?? Mods.FirstOrDefault()?.Description) ?? string.Empty; + public LocalisableString TooltipText => (SelectedMod?.Description ?? Mods.FirstOrDefault()?.Description) ?? string.Empty; private const Easing mod_switch_easing = Easing.InOutSine; private const double mod_switch_duration = 120; diff --git a/osu.Game/Overlays/OverlayPanelDisplayStyleControl.cs b/osu.Game/Overlays/OverlayPanelDisplayStyleControl.cs index 87b9d89d4d..0ece96b56c 100644 --- a/osu.Game/Overlays/OverlayPanelDisplayStyleControl.cs +++ b/osu.Game/Overlays/OverlayPanelDisplayStyleControl.cs @@ -11,6 +11,7 @@ using osu.Game.Graphics.UserInterface; using osu.Framework.Allocation; using osuTK.Graphics; using osu.Framework.Graphics.Cursor; +using osu.Framework.Localisation; namespace osu.Game.Overlays { @@ -56,7 +57,7 @@ namespace osu.Game.Overlays [Resolved] private OverlayColourProvider colourProvider { get; set; } - public string TooltipText => $@"{Value} view"; + public LocalisableString TooltipText => $@"{Value} view"; private readonly SpriteIcon icon; diff --git a/osu.Game/Overlays/Profile/Header/Components/DrawableBadge.cs b/osu.Game/Overlays/Profile/Header/Components/DrawableBadge.cs index 7eed4d3b6b..74f3ed846b 100644 --- a/osu.Game/Overlays/Profile/Header/Components/DrawableBadge.cs +++ b/osu.Game/Overlays/Profile/Header/Components/DrawableBadge.cs @@ -7,6 +7,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; +using osu.Framework.Localisation; using osu.Game.Users; using osuTK; @@ -42,6 +43,6 @@ namespace osu.Game.Overlays.Profile.Header.Components InternalChild.FadeInFromZero(200); } - public string TooltipText => badge.Description; + public LocalisableString TooltipText => badge.Description; } } diff --git a/osu.Game/Overlays/Profile/Header/Components/ExpandDetailsButton.cs b/osu.Game/Overlays/Profile/Header/Components/ExpandDetailsButton.cs index 29e13e4f51..527c70685f 100644 --- a/osu.Game/Overlays/Profile/Header/Components/ExpandDetailsButton.cs +++ b/osu.Game/Overlays/Profile/Header/Components/ExpandDetailsButton.cs @@ -6,6 +6,7 @@ using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; +using osu.Framework.Localisation; using osuTK; namespace osu.Game.Overlays.Profile.Header.Components @@ -14,7 +15,7 @@ namespace osu.Game.Overlays.Profile.Header.Components { public readonly BindableBool DetailsVisible = new BindableBool(); - public override string TooltipText => DetailsVisible.Value ? "collapse" : "expand"; + public override LocalisableString TooltipText => DetailsVisible.Value ? "collapse" : "expand"; private SpriteIcon icon; diff --git a/osu.Game/Overlays/Profile/Header/Components/FollowersButton.cs b/osu.Game/Overlays/Profile/Header/Components/FollowersButton.cs index bd8aa7b3bd..db94762efd 100644 --- a/osu.Game/Overlays/Profile/Header/Components/FollowersButton.cs +++ b/osu.Game/Overlays/Profile/Header/Components/FollowersButton.cs @@ -4,6 +4,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics.Sprites; +using osu.Framework.Localisation; using osu.Game.Users; namespace osu.Game.Overlays.Profile.Header.Components @@ -12,7 +13,7 @@ namespace osu.Game.Overlays.Profile.Header.Components { public readonly Bindable User = new Bindable(); - public override string TooltipText => "followers"; + public override LocalisableString TooltipText => "followers"; protected override IconUsage Icon => FontAwesome.Solid.User; diff --git a/osu.Game/Overlays/Profile/Header/Components/LevelBadge.cs b/osu.Game/Overlays/Profile/Header/Components/LevelBadge.cs index 29471375b5..a0b8ef0f11 100644 --- a/osu.Game/Overlays/Profile/Header/Components/LevelBadge.cs +++ b/osu.Game/Overlays/Profile/Header/Components/LevelBadge.cs @@ -8,6 +8,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; +using osu.Framework.Localisation; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Users; @@ -18,7 +19,7 @@ namespace osu.Game.Overlays.Profile.Header.Components { public readonly Bindable User = new Bindable(); - public string TooltipText { get; } + public LocalisableString TooltipText { get; } private OsuSpriteText levelText; diff --git a/osu.Game/Overlays/Profile/Header/Components/LevelProgressBar.cs b/osu.Game/Overlays/Profile/Header/Components/LevelProgressBar.cs index c97df3bc4d..528b05a80a 100644 --- a/osu.Game/Overlays/Profile/Header/Components/LevelProgressBar.cs +++ b/osu.Game/Overlays/Profile/Header/Components/LevelProgressBar.cs @@ -6,6 +6,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; +using osu.Framework.Localisation; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; @@ -18,7 +19,7 @@ namespace osu.Game.Overlays.Profile.Header.Components { public readonly Bindable User = new Bindable(); - public string TooltipText { get; } + public LocalisableString TooltipText { get; } private Bar levelProgressBar; private OsuSpriteText levelProgressText; diff --git a/osu.Game/Overlays/Profile/Header/Components/MappingSubscribersButton.cs b/osu.Game/Overlays/Profile/Header/Components/MappingSubscribersButton.cs index b4d7c9a05c..ae3d024fbf 100644 --- a/osu.Game/Overlays/Profile/Header/Components/MappingSubscribersButton.cs +++ b/osu.Game/Overlays/Profile/Header/Components/MappingSubscribersButton.cs @@ -4,6 +4,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics.Sprites; +using osu.Framework.Localisation; using osu.Game.Users; namespace osu.Game.Overlays.Profile.Header.Components @@ -12,7 +13,7 @@ namespace osu.Game.Overlays.Profile.Header.Components { public readonly Bindable User = new Bindable(); - public override string TooltipText => "mapping subscribers"; + public override LocalisableString TooltipText => "mapping subscribers"; protected override IconUsage Icon => FontAwesome.Solid.Bell; diff --git a/osu.Game/Overlays/Profile/Header/Components/MessageUserButton.cs b/osu.Game/Overlays/Profile/Header/Components/MessageUserButton.cs index 228765ee1a..4c2cc768ce 100644 --- a/osu.Game/Overlays/Profile/Header/Components/MessageUserButton.cs +++ b/osu.Game/Overlays/Profile/Header/Components/MessageUserButton.cs @@ -5,6 +5,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; +using osu.Framework.Localisation; using osu.Game.Online.API; using osu.Game.Online.Chat; using osu.Game.Users; @@ -16,7 +17,7 @@ namespace osu.Game.Overlays.Profile.Header.Components { public readonly Bindable User = new Bindable(); - public override string TooltipText => "send message"; + public override LocalisableString TooltipText => "send message"; [Resolved(CanBeNull = true)] private ChannelManager channelManager { get; set; } diff --git a/osu.Game/Overlays/Profile/Header/Components/OverlinedTotalPlayTime.cs b/osu.Game/Overlays/Profile/Header/Components/OverlinedTotalPlayTime.cs index be96840217..aa7cb8636a 100644 --- a/osu.Game/Overlays/Profile/Header/Components/OverlinedTotalPlayTime.cs +++ b/osu.Game/Overlays/Profile/Header/Components/OverlinedTotalPlayTime.cs @@ -6,6 +6,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; +using osu.Framework.Localisation; using osu.Game.Users; namespace osu.Game.Overlays.Profile.Header.Components @@ -14,7 +15,7 @@ namespace osu.Game.Overlays.Profile.Header.Components { public readonly Bindable User = new Bindable(); - public string TooltipText { get; set; } + public LocalisableString TooltipText { get; set; } private OverlinedInfoContainer info; diff --git a/osu.Game/Overlays/Profile/Header/Components/SupporterIcon.cs b/osu.Game/Overlays/Profile/Header/Components/SupporterIcon.cs index d581e2750c..9a43997030 100644 --- a/osu.Game/Overlays/Profile/Header/Components/SupporterIcon.cs +++ b/osu.Game/Overlays/Profile/Header/Components/SupporterIcon.cs @@ -8,6 +8,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; +using osu.Framework.Localisation; using osu.Game.Graphics; namespace osu.Game.Overlays.Profile.Header.Components @@ -18,7 +19,7 @@ namespace osu.Game.Overlays.Profile.Header.Components private readonly FillFlowContainer iconContainer; private readonly CircularContainer content; - public string TooltipText => "osu!supporter"; + public LocalisableString TooltipText => "osu!supporter"; public int SupportLevel { diff --git a/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedBeatmap.cs b/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedBeatmap.cs index 6d6ff32aac..6f1869966a 100644 --- a/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedBeatmap.cs +++ b/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedBeatmap.cs @@ -143,7 +143,7 @@ namespace osu.Game.Overlays.Profile.Sections.Historical private class PlayCountText : CompositeDrawable, IHasTooltip { - public string TooltipText => "times played"; + public LocalisableString TooltipText => "times played"; public PlayCountText(int playCount) { diff --git a/osu.Game/Overlays/RestoreDefaultValueButton.cs b/osu.Game/Overlays/RestoreDefaultValueButton.cs index 213ad2ba68..fe36f6ba6d 100644 --- a/osu.Game/Overlays/RestoreDefaultValueButton.cs +++ b/osu.Game/Overlays/RestoreDefaultValueButton.cs @@ -10,6 +10,7 @@ using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; +using osu.Framework.Localisation; using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; @@ -76,7 +77,7 @@ namespace osu.Game.Overlays UpdateState(); } - public string TooltipText => "revert to default"; + public LocalisableString TooltipText => "revert to default"; protected override bool OnHover(HoverEvent e) { diff --git a/osu.Game/Overlays/Settings/Sections/Audio/OffsetSettings.cs b/osu.Game/Overlays/Settings/Sections/Audio/OffsetSettings.cs index c9a81b955b..1ae297f2a9 100644 --- a/osu.Game/Overlays/Settings/Sections/Audio/OffsetSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Audio/OffsetSettings.cs @@ -3,6 +3,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Localisation; using osu.Game.Configuration; using osu.Game.Graphics.UserInterface; @@ -32,7 +33,7 @@ namespace osu.Game.Overlays.Settings.Sections.Audio private class OffsetSlider : OsuSliderBar { - public override string TooltipText => Current.Value.ToString(@"0ms"); + public override LocalisableString TooltipText => Current.Value.ToString(@"0ms"); } } } diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs index 937bcc8abf..669753d2cb 100644 --- a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs @@ -233,7 +233,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics private class UIScaleSlider : OsuSliderBar { - public override string TooltipText => base.TooltipText + "x"; + public override LocalisableString TooltipText => base.TooltipText + "x"; } private class ResolutionSettingsDropdown : SettingsDropdown diff --git a/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs index fb908a7669..e87572e2ca 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs @@ -6,6 +6,7 @@ using osu.Framework.Bindables; using osu.Framework.Configuration; using osu.Framework.Graphics; using osu.Framework.Input.Handlers.Mouse; +using osu.Framework.Localisation; using osu.Game.Configuration; using osu.Game.Graphics.UserInterface; using osu.Game.Input; @@ -116,7 +117,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input private class SensitivitySlider : OsuSliderBar { - public override string TooltipText => Current.Disabled ? "enable high precision mouse to adjust sensitivity" : $"{base.TooltipText}x"; + public override LocalisableString TooltipText => Current.Disabled ? "enable high precision mouse to adjust sensitivity" : $"{base.TooltipText}x"; } } } diff --git a/osu.Game/Overlays/Settings/Sections/SizeSlider.cs b/osu.Game/Overlays/Settings/Sections/SizeSlider.cs index 101d8f43f7..8aeb440be1 100644 --- a/osu.Game/Overlays/Settings/Sections/SizeSlider.cs +++ b/osu.Game/Overlays/Settings/Sections/SizeSlider.cs @@ -1,6 +1,7 @@ // 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.Localisation; using osu.Game.Graphics.UserInterface; namespace osu.Game.Overlays.Settings.Sections @@ -10,6 +11,6 @@ namespace osu.Game.Overlays.Settings.Sections /// internal class SizeSlider : OsuSliderBar { - public override string TooltipText => Current.Value.ToString(@"0.##x"); + public override LocalisableString TooltipText => Current.Value.ToString(@"0.##x"); } } diff --git a/osu.Game/Overlays/Settings/Sections/UserInterface/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/UserInterface/GeneralSettings.cs index 19adfc5dd9..a6eb008623 100644 --- a/osu.Game/Overlays/Settings/Sections/UserInterface/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/UserInterface/GeneralSettings.cs @@ -3,6 +3,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Localisation; using osu.Game.Configuration; using osu.Game.Graphics.UserInterface; @@ -44,7 +45,7 @@ namespace osu.Game.Overlays.Settings.Sections.UserInterface private class TimeSlider : OsuSliderBar { - public override string TooltipText => Current.Value.ToString("N0") + "ms"; + public override LocalisableString TooltipText => Current.Value.ToString(@"N0") + "ms"; } } } diff --git a/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs b/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs index c73a783d37..2470c0a6c5 100644 --- a/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs @@ -5,6 +5,7 @@ using System; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Localisation; using osu.Game.Configuration; using osu.Game.Graphics.UserInterface; @@ -62,12 +63,12 @@ namespace osu.Game.Overlays.Settings.Sections.UserInterface private class MaximumStarsSlider : StarsSlider { - public override string TooltipText => Current.IsDefault ? "no limit" : base.TooltipText; + public override LocalisableString TooltipText => Current.IsDefault ? "no limit" : base.TooltipText; } private class StarsSlider : OsuSliderBar { - public override string TooltipText => Current.Value.ToString(@"0.## stars"); + public override LocalisableString TooltipText => Current.Value.ToString(@"0.## stars"); } } } diff --git a/osu.Game/Overlays/Settings/SettingsButton.cs b/osu.Game/Overlays/Settings/SettingsButton.cs index 088d69c031..87b1aa0e46 100644 --- a/osu.Game/Overlays/Settings/SettingsButton.cs +++ b/osu.Game/Overlays/Settings/SettingsButton.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Graphics; using osu.Framework.Graphics.Cursor; +using osu.Framework.Localisation; using osu.Game.Graphics.UserInterface; namespace osu.Game.Overlays.Settings @@ -17,14 +18,15 @@ namespace osu.Game.Overlays.Settings Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS, Right = SettingsPanel.CONTENT_MARGINS }; } - public string TooltipText { get; set; } + public LocalisableString TooltipText { get; set; } public override IEnumerable FilterTerms { get { - if (TooltipText != null) - return base.FilterTerms.Append(TooltipText); + if (TooltipText != default) + // TODO: this won't work as intended once the tooltip text is translated. + return base.FilterTerms.Append(TooltipText.ToString()); return base.FilterTerms; } diff --git a/osu.Game/Overlays/Settings/SettingsItem.cs b/osu.Game/Overlays/Settings/SettingsItem.cs index 807916e7f6..15a0a42d31 100644 --- a/osu.Game/Overlays/Settings/SettingsItem.cs +++ b/osu.Game/Overlays/Settings/SettingsItem.cs @@ -35,7 +35,7 @@ namespace osu.Game.Overlays.Settings public bool ShowsDefaultIndicator = true; - public string TooltipText { get; set; } + public LocalisableString TooltipText { get; set; } [Resolved] private OsuColour colours { get; set; } @@ -142,4 +142,4 @@ namespace osu.Game.Overlays.Settings labelText.Alpha = controlWithCurrent.Current.Disabled ? 0.3f : 1; } } -} \ No newline at end of file +} diff --git a/osu.Game/Rulesets/UI/ModIcon.cs b/osu.Game/Rulesets/UI/ModIcon.cs index cae5da3d16..725cfa9c26 100644 --- a/osu.Game/Rulesets/UI/ModIcon.cs +++ b/osu.Game/Rulesets/UI/ModIcon.cs @@ -13,6 +13,7 @@ using osu.Game.Graphics.Sprites; using osu.Game.Rulesets.Mods; using osuTK; using osu.Framework.Bindables; +using osu.Framework.Localisation; namespace osu.Game.Rulesets.UI { @@ -29,7 +30,7 @@ namespace osu.Game.Rulesets.UI private const float size = 80; - public virtual string TooltipText => showTooltip ? mod.IconTooltip : null; + public virtual LocalisableString TooltipText => showTooltip ? mod.IconTooltip : null; private Mod mod; private readonly bool showTooltip; diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxButton.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxButton.cs index 3b1dae6c3d..3ac40fda0f 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxButton.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxButton.cs @@ -7,6 +7,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; +using osu.Framework.Localisation; using osuTK; using osuTK.Graphics; @@ -58,6 +59,6 @@ namespace osu.Game.Screens.Edit.Compose.Components icon.FadeColour(!IsHeld && IsHovered ? Color4.White : Color4.Black, TRANSFORM_DURATION, Easing.OutQuint); } - public string TooltipText { get; } + public LocalisableString TooltipText { get; } } } diff --git a/osu.Game/Screens/OnlinePlay/Components/DrawableGameType.cs b/osu.Game/Screens/OnlinePlay/Components/DrawableGameType.cs index c4dc2a2b8f..ae1ca1b967 100644 --- a/osu.Game/Screens/OnlinePlay/Components/DrawableGameType.cs +++ b/osu.Game/Screens/OnlinePlay/Components/DrawableGameType.cs @@ -7,6 +7,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; +using osu.Framework.Localisation; using osu.Game.Graphics; using osu.Game.Online.Rooms; @@ -16,7 +17,7 @@ namespace osu.Game.Screens.OnlinePlay.Components { private readonly GameType type; - public string TooltipText => type.Name; + public LocalisableString TooltipText => type.Name; public DrawableGameType(GameType type) { diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs index e1cf0cef4e..4a35202df2 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs @@ -431,7 +431,7 @@ namespace osu.Game.Screens.Select public class InfoLabel : Container, IHasTooltip { - public string TooltipText { get; } + public LocalisableString TooltipText { get; } public InfoLabel(BeatmapStatistic statistic) { diff --git a/osu.Game/Users/Drawables/ClickableAvatar.cs b/osu.Game/Users/Drawables/ClickableAvatar.cs index c3bf740108..f73489ac61 100644 --- a/osu.Game/Users/Drawables/ClickableAvatar.cs +++ b/osu.Game/Users/Drawables/ClickableAvatar.cs @@ -6,6 +6,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Textures; using osu.Framework.Input.Events; +using osu.Framework.Localisation; using osu.Game.Graphics.Containers; namespace osu.Game.Users.Drawables @@ -68,11 +69,11 @@ namespace osu.Game.Users.Drawables private class ClickableArea : OsuClickableContainer { - private string tooltip = default_tooltip_text; + private LocalisableString tooltip = default_tooltip_text; - public override string TooltipText + public override LocalisableString TooltipText { - get => Enabled.Value ? tooltip : null; + get => Enabled.Value ? tooltip : default; set => tooltip = value; } diff --git a/osu.Game/Users/Drawables/DrawableFlag.cs b/osu.Game/Users/Drawables/DrawableFlag.cs index 1d648e46b6..aea40a01ae 100644 --- a/osu.Game/Users/Drawables/DrawableFlag.cs +++ b/osu.Game/Users/Drawables/DrawableFlag.cs @@ -6,6 +6,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; +using osu.Framework.Localisation; namespace osu.Game.Users.Drawables { @@ -13,7 +14,7 @@ namespace osu.Game.Users.Drawables { private readonly Country country; - public string TooltipText => country?.FullName; + public LocalisableString TooltipText => country?.FullName; public DrawableFlag(Country country) { From 3b822cd5cf45af3cd21bbfed9471b4f3b60a07c5 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Sat, 26 Jun 2021 11:19:14 +0800 Subject: [PATCH 080/168] Refactor `SeedSettingsControl` and related controls --- .../Overlays/Settings/OutlinedNumberBox.cs | 10 ++++ osu.Game/Overlays/Settings/OutlinedTextBox.cs | 49 +++++++++++++++++++ .../Overlays/Settings/SettingsNumberBox.cs | 8 +-- osu.Game/Overlays/Settings/SettingsTextBox.cs | 47 +----------------- osu.Game/Rulesets/Mods/SeedSettingsControl.cs | 33 +++---------- 5 files changed, 70 insertions(+), 77 deletions(-) create mode 100644 osu.Game/Overlays/Settings/OutlinedNumberBox.cs create mode 100644 osu.Game/Overlays/Settings/OutlinedTextBox.cs diff --git a/osu.Game/Overlays/Settings/OutlinedNumberBox.cs b/osu.Game/Overlays/Settings/OutlinedNumberBox.cs new file mode 100644 index 0000000000..6fcadc09e1 --- /dev/null +++ b/osu.Game/Overlays/Settings/OutlinedNumberBox.cs @@ -0,0 +1,10 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +namespace osu.Game.Overlays.Settings +{ + public class OutlinedNumberBox : OutlinedTextBox + { + protected override bool CanAddCharacter(char character) => char.IsNumber(character); + } +} diff --git a/osu.Game/Overlays/Settings/OutlinedTextBox.cs b/osu.Game/Overlays/Settings/OutlinedTextBox.cs new file mode 100644 index 0000000000..93eaf74b77 --- /dev/null +++ b/osu.Game/Overlays/Settings/OutlinedTextBox.cs @@ -0,0 +1,49 @@ +// 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.Color4Extensions; +using osu.Framework.Input.Events; +using osu.Game.Graphics; +using osu.Game.Graphics.UserInterface; +using osuTK.Graphics; + +namespace osu.Game.Overlays.Settings +{ + public class OutlinedTextBox : OsuTextBox + { + private const float border_thickness = 3; + + private Color4 borderColourFocused; + private Color4 borderColourUnfocused; + + [BackgroundDependencyLoader] + private void load(OsuColour colour) + { + borderColourUnfocused = colour.Gray4.Opacity(0.5f); + borderColourFocused = BorderColour; + + updateBorder(); + } + + protected override void OnFocus(FocusEvent e) + { + base.OnFocus(e); + + updateBorder(); + } + + protected override void OnFocusLost(FocusLostEvent e) + { + base.OnFocusLost(e); + + updateBorder(); + } + + private void updateBorder() + { + BorderThickness = border_thickness; + BorderColour = HasFocus ? borderColourFocused : borderColourUnfocused; + } + } +} diff --git a/osu.Game/Overlays/Settings/SettingsNumberBox.cs b/osu.Game/Overlays/Settings/SettingsNumberBox.cs index ca9a8e9c08..d4d1fc8610 100644 --- a/osu.Game/Overlays/Settings/SettingsNumberBox.cs +++ b/osu.Game/Overlays/Settings/SettingsNumberBox.cs @@ -7,15 +7,11 @@ namespace osu.Game.Overlays.Settings { public class SettingsNumberBox : SettingsItem { - protected override Drawable CreateControl() => new NumberBox + protected override Drawable CreateControl() => new OutlinedNumberBox { Margin = new MarginPadding { Top = 5 }, RelativeSizeAxes = Axes.X, + CommitOnFocusLost = true }; - - public class NumberBox : SettingsTextBox.TextBox - { - protected override bool CanAddCharacter(char character) => char.IsNumber(character); - } } } diff --git a/osu.Game/Overlays/Settings/SettingsTextBox.cs b/osu.Game/Overlays/Settings/SettingsTextBox.cs index 25424e85a1..d28dbf1068 100644 --- a/osu.Game/Overlays/Settings/SettingsTextBox.cs +++ b/osu.Game/Overlays/Settings/SettingsTextBox.cs @@ -1,60 +1,17 @@ // 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.Color4Extensions; using osu.Framework.Graphics; -using osu.Framework.Input.Events; -using osu.Game.Graphics; -using osu.Game.Graphics.UserInterface; -using osuTK.Graphics; namespace osu.Game.Overlays.Settings { public class SettingsTextBox : SettingsItem { - protected override Drawable CreateControl() => new TextBox + protected override Drawable CreateControl() => new OutlinedTextBox { Margin = new MarginPadding { Top = 5 }, RelativeSizeAxes = Axes.X, - CommitOnFocusLost = true, + CommitOnFocusLost = true }; - - public class TextBox : OsuTextBox - { - private const float border_thickness = 3; - - private Color4 borderColourFocused; - private Color4 borderColourUnfocused; - - [BackgroundDependencyLoader] - private void load(OsuColour colour) - { - borderColourUnfocused = colour.Gray4.Opacity(0.5f); - borderColourFocused = BorderColour; - - updateBorder(); - } - - protected override void OnFocus(FocusEvent e) - { - base.OnFocus(e); - - updateBorder(); - } - - protected override void OnFocusLost(FocusLostEvent e) - { - base.OnFocusLost(e); - - updateBorder(); - } - - private void updateBorder() - { - BorderThickness = border_thickness; - BorderColour = HasFocus ? borderColourFocused : borderColourUnfocused; - } - } } } diff --git a/osu.Game/Rulesets/Mods/SeedSettingsControl.cs b/osu.Game/Rulesets/Mods/SeedSettingsControl.cs index 1280197532..1eaf31874b 100644 --- a/osu.Game/Rulesets/Mods/SeedSettingsControl.cs +++ b/osu.Game/Rulesets/Mods/SeedSettingsControl.cs @@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Mods } } - private readonly SettingsNumberBox.NumberBox seedNumberBox; + private readonly OutlinedNumberBox seedNumberBox; public SeedControl() { @@ -42,31 +42,11 @@ namespace osu.Game.Rulesets.Mods InternalChildren = new[] { - new GridContainer + seedNumberBox = new OutlinedNumberBox { + Margin = new MarginPadding { Top = 5 }, RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - ColumnDimensions = new[] - { - new Dimension(), - new Dimension(GridSizeMode.Absolute, 2), - new Dimension(GridSizeMode.Relative, 0.25f) - }, - RowDimensions = new[] - { - new Dimension(GridSizeMode.AutoSize) - }, - Content = new[] - { - new Drawable[] - { - seedNumberBox = new SettingsNumberBox.NumberBox - { - RelativeSizeAxes = Axes.X, - CommitOnFocusLost = true - } - } - } + CommitOnFocusLost = true } }; @@ -83,8 +63,9 @@ namespace osu.Game.Rulesets.Mods protected override void Update() { - if (current.Value == null) - seedNumberBox.Text = current.Current.Value.ToString(); + base.Update(); + if (Current.Value == null) + seedNumberBox.Current.Value = ""; } } } From e5eea503dbdfebee5f2911354ded244885a5f5d1 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 26 Jun 2021 12:21:49 +0300 Subject: [PATCH 081/168] Remove finalizer logic from `ResourcesSkin` --- osu.Game/Skinning/ResourcesSkin.cs | 25 +------------------------ 1 file changed, 1 insertion(+), 24 deletions(-) diff --git a/osu.Game/Skinning/ResourcesSkin.cs b/osu.Game/Skinning/ResourcesSkin.cs index 3d17d7cc3d..90020495c3 100644 --- a/osu.Game/Skinning/ResourcesSkin.cs +++ b/osu.Game/Skinning/ResourcesSkin.cs @@ -19,7 +19,7 @@ namespace osu.Game.Skinning /// /// An that uses an underlying with namespaces for resources retrieval. /// - public class ResourcesSkin : ISkin + public class ResourcesSkin : ISkin, IDisposable { private readonly TextureStore textures; private readonly ISampleStore samples; @@ -48,33 +48,10 @@ namespace osu.Game.Skinning public IBindable? GetConfig(TLookup lookup) => null; - #region Disposal - - ~ResourcesSkin() - { - // required to potentially clean up sample store from audio hierarchy. - Dispose(false); - } - public void Dispose() { - Dispose(true); - GC.SuppressFinalize(this); - } - - private bool isDisposed; - - protected virtual void Dispose(bool isDisposing) - { - if (isDisposed) - return; - - isDisposed = true; - textures.Dispose(); samples.Dispose(); } - - #endregion } } From e8e9fdd5331dec8d6522c23dcb7b03827b4ef666 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 26 Jun 2021 12:23:05 +0300 Subject: [PATCH 082/168] Dispose `ResourcesSkin` before clearing skin sources --- osu.Game/Skinning/RulesetSkinProvidingContainer.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs index 54bf91523f..8a807eff21 100644 --- a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs +++ b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Linq; using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Audio; @@ -72,6 +73,9 @@ namespace osu.Game.Skinning protected virtual void UpdateSkins() { + foreach (var resourcesSkin in SkinSources.OfType()) + resourcesSkin.Dispose(); + SkinSources.Clear(); foreach (var skin in skinSource.AllSources) From 8e1bcc4d6bd751a5112451f3f9c0ed06a8937714 Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Sat, 26 Jun 2021 21:02:31 +0700 Subject: [PATCH 083/168] add overall difficulty in filter criteria --- osu.Game/Screens/Select/FilterCriteria.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/Select/FilterCriteria.cs b/osu.Game/Screens/Select/FilterCriteria.cs index 208048380a..b9e912df8e 100644 --- a/osu.Game/Screens/Select/FilterCriteria.cs +++ b/osu.Game/Screens/Select/FilterCriteria.cs @@ -24,6 +24,7 @@ namespace osu.Game.Screens.Select public OptionalRange ApproachRate; public OptionalRange DrainRate; public OptionalRange CircleSize; + public OptionalRange OverallDifficulty; public OptionalRange Length; public OptionalRange BPM; public OptionalRange BeatDivisor; From 4df4afe533aed92ee7c8d7f22d14b1047007283b Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Sat, 26 Jun 2021 21:02:57 +0700 Subject: [PATCH 084/168] add test for overall difficulty filter query --- .../NonVisual/Filtering/FilterQueryParserTest.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs index 9bd262a569..a55bdd2df8 100644 --- a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs +++ b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs @@ -90,6 +90,20 @@ namespace osu.Game.Tests.NonVisual.Filtering Assert.Less(filterCriteria.DrainRate.Min, 6.1f); } + [Test] + public void TestApplyOverallDifficultyQueries() + { + const string query = "od>4 easy od<8"; + var filterCriteria = new FilterCriteria(); + FilterQueryParser.ApplyQueries(filterCriteria, query); + Assert.AreEqual("easy", filterCriteria.SearchText.Trim()); + Assert.AreEqual(1, filterCriteria.SearchTerms.Length); + Assert.Greater(filterCriteria.OverallDifficulty.Min, 4.0); + Assert.Less(filterCriteria.OverallDifficulty.Min, 4.1); + Assert.Greater(filterCriteria.OverallDifficulty.Max, 7.9); + Assert.Less(filterCriteria.OverallDifficulty.Max, 8.0); + } + [Test] public void TestApplyBPMQueries() { From 2b1d3c8e9c9ce4eb5e5790032d64da556a0f7c6f Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Sat, 26 Jun 2021 21:05:01 +0700 Subject: [PATCH 085/168] add od filter in search filter --- osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs | 1 + osu.Game/Screens/Select/FilterQueryParser.cs | 3 +++ 2 files changed, 4 insertions(+) diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs index 521b90202d..f95ddfee41 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs @@ -42,6 +42,7 @@ namespace osu.Game.Screens.Select.Carousel match &= !criteria.ApproachRate.HasFilter || criteria.ApproachRate.IsInRange(Beatmap.BaseDifficulty.ApproachRate); match &= !criteria.DrainRate.HasFilter || criteria.DrainRate.IsInRange(Beatmap.BaseDifficulty.DrainRate); match &= !criteria.CircleSize.HasFilter || criteria.CircleSize.IsInRange(Beatmap.BaseDifficulty.CircleSize); + match &= !criteria.OverallDifficulty.HasFilter || criteria.OverallDifficulty.IsInRange(Beatmap.BaseDifficulty.OverallDifficulty); match &= !criteria.Length.HasFilter || criteria.Length.IsInRange(Beatmap.Length); match &= !criteria.BPM.HasFilter || criteria.BPM.IsInRange(Beatmap.BPM); diff --git a/osu.Game/Screens/Select/FilterQueryParser.cs b/osu.Game/Screens/Select/FilterQueryParser.cs index db2803d29a..72d10019b2 100644 --- a/osu.Game/Screens/Select/FilterQueryParser.cs +++ b/osu.Game/Screens/Select/FilterQueryParser.cs @@ -51,6 +51,9 @@ namespace osu.Game.Screens.Select case "cs": return TryUpdateCriteriaRange(ref criteria.CircleSize, op, value); + case "od": + return TryUpdateCriteriaRange(ref criteria.OverallDifficulty, op, value); + case "bpm": return TryUpdateCriteriaRange(ref criteria.BPM, op, value, 0.01d / 2); From d8117fa73032af9b130c768fd1b1c0a694268580 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Sat, 26 Jun 2021 19:20:34 +0200 Subject: [PATCH 086/168] Add muted objects check --- osu.Game/Rulesets/Edit/BeatmapVerifier.cs | 1 + .../Rulesets/Edit/Checks/CheckMutedObjects.cs | 133 ++++++++++++++++++ 2 files changed, 134 insertions(+) create mode 100644 osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.cs diff --git a/osu.Game/Rulesets/Edit/BeatmapVerifier.cs b/osu.Game/Rulesets/Edit/BeatmapVerifier.cs index d208c7fe07..462a87af85 100644 --- a/osu.Game/Rulesets/Edit/BeatmapVerifier.cs +++ b/osu.Game/Rulesets/Edit/BeatmapVerifier.cs @@ -22,6 +22,7 @@ namespace osu.Game.Rulesets.Edit // Audio new CheckAudioPresence(), new CheckAudioQuality(), + new CheckMutedObjects(), // Compose new CheckUnsnappedObjects(), diff --git a/osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.cs b/osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.cs new file mode 100644 index 0000000000..cbe7c7fbab --- /dev/null +++ b/osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.cs @@ -0,0 +1,133 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Utils; +using osu.Game.Rulesets.Edit.Checks.Components; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; + +namespace osu.Game.Rulesets.Edit.Checks +{ + public class CheckMutedObjects : ICheck + { + /// + /// Volume percentages lower than this are typically inaudible. + /// + private const int muted_threshold = 5; + + /// + /// Volume percentages lower than this can sometimes be inaudible depending on sample used and music volume. + /// + private const int low_volume_threshold = 20; + + public CheckMetadata Metadata { get; } = new CheckMetadata(CheckCategory.Audio, "Low volume hitobjects"); + + public IEnumerable PossibleTemplates => new IssueTemplate[] + { + new IssueTemplateMutedActive(this), + new IssueTemplateLowVolumeActive(this), + new IssueTemplateMutedPassive(this) + }; + + public IEnumerable Run(BeatmapVerifierContext context) + { + foreach (var hitObject in context.Beatmap.HitObjects) + { + // Worth keeping in mind: The samples of an object always play at its end time. + // Objects like spinners have no sound at its start because of this, while hold notes have nested objects to accomplish this. + foreach (var nestedHitObject in hitObject.NestedHitObjects) + { + foreach (var issue in getVolumeIssues(hitObject, nestedHitObject)) + yield return issue; + } + + foreach (var issue in getVolumeIssues(hitObject)) + yield return issue; + } + } + + private IEnumerable getVolumeIssues(HitObject hitObject, HitObject sampledHitObject = null) + { + sampledHitObject ??= hitObject; + if (!sampledHitObject.Samples.Any()) + yield break; + + // Samples that allow themselves to be overridden by control points have a volume of 0. + int maxVolume = sampledHitObject.Samples.Max(sample => sample.Volume > 0 ? sample.Volume : sampledHitObject.SampleControlPoint.SampleVolume); + double samplePlayTime = sampledHitObject.GetEndTime(); + + bool head = Precision.AlmostEquals(samplePlayTime, hitObject.StartTime, 1f); + bool tail = Precision.AlmostEquals(samplePlayTime, hitObject.GetEndTime(), 1f); + bool repeat = false; + + if (hitObject is IHasRepeats hasRepeats && !head && !tail) + { + double spanDuration = hasRepeats.Duration / hasRepeats.SpanCount(); + repeat = Precision.AlmostEquals((samplePlayTime - hitObject.StartTime) % spanDuration, 0f, 1f); + } + + // We only care about samples played on the edges of objects, not ones like spinnerspin or slidertick. + if (!head && !tail && !repeat) + yield break; + + string postfix = null; + if (hitObject is IHasDuration) + postfix = head ? "head" : tail ? "tail" : "repeat"; + + if (maxVolume <= muted_threshold) + { + if (head) + yield return new IssueTemplateMutedActive(this).Create(hitObject, maxVolume / 100f, sampledHitObject.GetEndTime(), postfix); + else + yield return new IssueTemplateMutedPassive(this).Create(hitObject, maxVolume / 100f, sampledHitObject.GetEndTime(), postfix); + } + else if (maxVolume <= low_volume_threshold && head) + { + yield return new IssueTemplateLowVolumeActive(this).Create(hitObject, maxVolume / 100f, sampledHitObject.GetEndTime(), postfix); + } + } + + public abstract class IssueTemplateMuted : IssueTemplate + { + protected IssueTemplateMuted(ICheck check, IssueType type, string unformattedMessage) + : base(check, type, unformattedMessage) + { + } + + public Issue Create(HitObject hitobject, double volume, double time, string postfix = "") + { + string objectName = hitobject.GetType().Name; + if (!string.IsNullOrEmpty(postfix)) + objectName += " " + postfix; + + return new Issue(hitobject, this, objectName, volume) { Time = time }; + } + } + + public class IssueTemplateMutedActive : IssueTemplateMuted + { + public IssueTemplateMutedActive(ICheck check) + : base(check, IssueType.Problem, "{0} has a volume of {1:0%}. Clickable objects must have clearly audible feedback.") + { + } + } + + public class IssueTemplateLowVolumeActive : IssueTemplateMuted + { + public IssueTemplateLowVolumeActive(ICheck check) + : base(check, IssueType.Warning, "{0} has a volume of {1:0%}, ensure this is audible.") + { + } + } + + public class IssueTemplateMutedPassive : IssueTemplateMuted + { + public IssueTemplateMutedPassive(ICheck check) + : base(check, IssueType.Negligible, "{0} has a volume of {1:0%}, ensure there is no distinct sound here in the song if inaudible.") + { + } + } + } +} From 4b436b774dcb3c35d145410fb56edc12e2c66ebb Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Sat, 26 Jun 2021 19:20:46 +0200 Subject: [PATCH 087/168] Add few hitsounds check --- osu.Game/Rulesets/Edit/BeatmapVerifier.cs | 1 + .../Rulesets/Edit/Checks/CheckFewHitsounds.cs | 161 ++++++++++++++++++ 2 files changed, 162 insertions(+) create mode 100644 osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs diff --git a/osu.Game/Rulesets/Edit/BeatmapVerifier.cs b/osu.Game/Rulesets/Edit/BeatmapVerifier.cs index 462a87af85..706eec226c 100644 --- a/osu.Game/Rulesets/Edit/BeatmapVerifier.cs +++ b/osu.Game/Rulesets/Edit/BeatmapVerifier.cs @@ -23,6 +23,7 @@ namespace osu.Game.Rulesets.Edit new CheckAudioPresence(), new CheckAudioQuality(), new CheckMutedObjects(), + new CheckFewHitsounds(), // Compose new CheckUnsnappedObjects(), diff --git a/osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs b/osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs new file mode 100644 index 0000000000..07ca470e62 --- /dev/null +++ b/osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs @@ -0,0 +1,161 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using System.Linq; +using osu.Game.Audio; +using osu.Game.Rulesets.Edit.Checks.Components; +using osu.Game.Rulesets.Objects; + +namespace osu.Game.Rulesets.Edit.Checks +{ + public class CheckFewHitsounds : ICheck + { + /// + /// 2 measures (4/4) of 120 BPM, typically makes up a few patterns in the map. + /// This is almost always ok, but can still be useful for the mapper to make sure hitsounding coverage is good. + /// + private const int negligible_threshold_time = 4000; + + /// + /// 4 measures (4/4) of 120 BPM, typically makes up a large portion of a section in the song. + /// This is ok if the section is a quiet intro, for example. + /// + private const int warning_threshold_time = 8000; + + /// + /// 12 measures (4/4) of 120 BPM, typically makes up multiple sections in the song. + /// + private const int problem_threshold_time = 24000; + + // Should pass at least this many objects without hitsounds to be considered an issue (should work for Easy diffs too). + private const int warning_threshold_objects = 4; + private const int problem_threshold_objects = 16; + + private static readonly string[] hitsound_types = { HitSampleInfo.HIT_CLAP, HitSampleInfo.HIT_WHISTLE, HitSampleInfo.HIT_FINISH }; + + public CheckMetadata Metadata { get; } = new CheckMetadata(CheckCategory.Audio, "Few or no hitsounds"); + + public IEnumerable PossibleTemplates => new IssueTemplate[] + { + new IssueTemplateLongPeriodProblem(this), + new IssueTemplateLongPeriodWarning(this), + new IssueTemplateNoHitsounds(this) + }; + + private bool hasHitsounds; + private int objectsWithoutHitsounds; + private double lastHitsoundTime; + + public IEnumerable Run(BeatmapVerifierContext context) + { + if (!context.Beatmap.HitObjects.Any()) + yield break; + + hasHitsounds = false; + objectsWithoutHitsounds = 0; + lastHitsoundTime = context.Beatmap.HitObjects.First().StartTime; + + var hitObjectCount = context.Beatmap.HitObjects.Count; + + for (int i = 0; i < hitObjectCount; ++i) + { + var hitObject = context.Beatmap.HitObjects[i]; + + // Samples play on the end of objects. Some objects have nested objects to accomplish playing them elsewhere (e.g. slider head/repeat). + foreach (var nestedHitObject in hitObject.NestedHitObjects) + { + foreach (var issue in applyHitsoundUpdate(nestedHitObject)) + yield return issue; + } + + // This is used to perform an update at the end so that the period after the last hitsounded object can be an issue. + bool isLastObject = i == hitObjectCount - 1; + + foreach (var issue in applyHitsoundUpdate(hitObject, isLastObject)) + yield return issue; + } + + if (!hasHitsounds) + yield return new IssueTemplateNoHitsounds(this).Create(); + } + + private IEnumerable applyHitsoundUpdate(HitObject hitObject, bool isLastObject = false) + { + var time = hitObject.GetEndTime(); + + // Only generating issues on hitsounded or last objects ensures we get one issue per long period. + // If there are no hitsounds we let the "No hitsounds" template take precedence. + if (hasHitsound(hitObject) || isLastObject && hasHitsounds) + { + var timeWithoutHitsounds = time - lastHitsoundTime; + + if (timeWithoutHitsounds > problem_threshold_time && objectsWithoutHitsounds > problem_threshold_objects) + yield return new IssueTemplateLongPeriodProblem(this).Create(lastHitsoundTime, timeWithoutHitsounds); + else if (timeWithoutHitsounds > warning_threshold_time && objectsWithoutHitsounds > warning_threshold_objects) + yield return new IssueTemplateLongPeriodWarning(this).Create(lastHitsoundTime, timeWithoutHitsounds); + else if (timeWithoutHitsounds > negligible_threshold_time && objectsWithoutHitsounds > warning_threshold_objects) + yield return new IssueTemplateLongPeriodNegligible(this).Create(lastHitsoundTime, timeWithoutHitsounds); + } + + if (hasHitsound(hitObject)) + { + hasHitsounds = true; + objectsWithoutHitsounds = 0; + lastHitsoundTime = time; + } + else if (couldHaveHitsound(hitObject)) + ++objectsWithoutHitsounds; + } + + private bool hasHitsound(HitObject hitObject) => hitObject.Samples.Any(isHitsound); + private bool couldHaveHitsound(HitObject hitObject) => hitObject.Samples.Any(isHitnormal); + + private bool isHitsound(HitSampleInfo sample) => hitsound_types.Any(sample.Name.Contains); + private bool isHitnormal(HitSampleInfo sample) => sample.Name.Contains(HitSampleInfo.HIT_NORMAL); + + public abstract class IssueTemplateLongPeriod : IssueTemplate + { + protected IssueTemplateLongPeriod(ICheck check, IssueType type) + : base(check, type, "Long period without hitsounds ({0:F1} seconds).") + { + } + + public Issue Create(double time, double duration) => new Issue(this, duration / 1000f) { Time = time }; + } + + public class IssueTemplateLongPeriodProblem : IssueTemplateLongPeriod + { + public IssueTemplateLongPeriodProblem(ICheck check) + : base(check, IssueType.Problem) + { + } + } + + public class IssueTemplateLongPeriodWarning : IssueTemplateLongPeriod + { + public IssueTemplateLongPeriodWarning(ICheck check) + : base(check, IssueType.Warning) + { + } + } + + public class IssueTemplateLongPeriodNegligible : IssueTemplateLongPeriod + { + public IssueTemplateLongPeriodNegligible(ICheck check) + : base(check, IssueType.Negligible) + { + } + } + + public class IssueTemplateNoHitsounds : IssueTemplate + { + public IssueTemplateNoHitsounds(ICheck check) + : base(check, IssueType.Problem, "There are no hitsounds.") + { + } + + public Issue Create() => new Issue(this); + } + } +} From 7b9569a1176d7a982ef6e9212574a9385c9d665b Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Sat, 26 Jun 2021 19:21:01 +0200 Subject: [PATCH 088/168] Add muted object check tests --- .../Editing/Checks/CheckMutedObjectsTest.cs | 317 ++++++++++++++++++ 1 file changed, 317 insertions(+) create mode 100644 osu.Game.Tests/Editing/Checks/CheckMutedObjectsTest.cs diff --git a/osu.Game.Tests/Editing/Checks/CheckMutedObjectsTest.cs b/osu.Game.Tests/Editing/Checks/CheckMutedObjectsTest.cs new file mode 100644 index 0000000000..4bfe62a64d --- /dev/null +++ b/osu.Game.Tests/Editing/Checks/CheckMutedObjectsTest.cs @@ -0,0 +1,317 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using NUnit.Framework; +using osu.Game.Audio; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Edit.Checks; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Tests.Beatmaps; + +namespace osu.Game.Tests.Editing.Checks +{ + [TestFixture] + public class CheckMutedObjectsTest + { + private CheckMutedObjects check; + private ControlPointInfo cpi; + + private const int volume_regular = 50; + private const int volume_low = 15; + private const int volume_muted = 5; + + private sealed class MockNestableHitObject : HitObject, IHasDuration + { + private readonly IEnumerable toBeNested; + + public MockNestableHitObject(IEnumerable toBeNested, double startTime, double endTime) + { + this.toBeNested = toBeNested; + StartTime = startTime; + EndTime = endTime; + } + + protected override void CreateNestedHitObjects(CancellationToken cancellationToken) + { + foreach (var hitObject in toBeNested) + AddNested(hitObject); + } + + public double EndTime { get; } + + public double Duration + { + get => EndTime - StartTime; + set => throw new System.NotImplementedException(); + } + } + + [SetUp] + public void Setup() + { + check = new CheckMutedObjects(); + + cpi = new ControlPointInfo(); + cpi.Add(0, new SampleControlPoint { SampleVolume = volume_regular }); + cpi.Add(1000, new SampleControlPoint { SampleVolume = volume_low }); + cpi.Add(2000, new SampleControlPoint { SampleVolume = volume_muted }); + } + + [Test] + public void TestNormalControlPointVolume() + { + var hitcircle = new HitCircle + { + StartTime = 0, + Samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) } + }; + hitcircle.ApplyDefaults(cpi, new BeatmapDifficulty()); + + assertOk(new List { hitcircle }); + } + + [Test] + public void TestLowControlPointVolume() + { + var hitcircle = new HitCircle + { + StartTime = 1000, + Samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) } + }; + hitcircle.ApplyDefaults(cpi, new BeatmapDifficulty()); + + assertLowVolume(new List { hitcircle }); + } + + [Test] + public void TestMutedControlPointVolume() + { + var hitcircle = new HitCircle + { + StartTime = 2000, + Samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) } + }; + hitcircle.ApplyDefaults(cpi, new BeatmapDifficulty()); + + assertMuted(new List { hitcircle }); + } + + [Test] + public void TestNormalSampleVolume() + { + // The sample volume should take precedence over the control point volume. + var hitcircle = new HitCircle + { + StartTime = 2000, + Samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL, volume: volume_regular) } + }; + hitcircle.ApplyDefaults(cpi, new BeatmapDifficulty()); + + assertOk(new List { hitcircle }); + } + + [Test] + public void TestLowSampleVolume() + { + var hitcircle = new HitCircle + { + StartTime = 2000, + Samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL, volume: volume_low) } + }; + hitcircle.ApplyDefaults(cpi, new BeatmapDifficulty()); + + assertLowVolume(new List { hitcircle }); + } + + [Test] + public void TestMutedSampleVolume() + { + var hitcircle = new HitCircle + { + StartTime = 0, + Samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL, volume: volume_muted) } + }; + hitcircle.ApplyDefaults(cpi, new BeatmapDifficulty()); + + assertMuted(new List { hitcircle }); + } + + [Test] + public void TestNormalSampleVolumeSlider() + { + var sliderHead = new SliderHeadCircle + { + StartTime = 0, + Samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) } + }; + sliderHead.ApplyDefaults(cpi, new BeatmapDifficulty()); + + var sliderTick = new SliderTick + { + StartTime = 250, + Samples = new List { new HitSampleInfo("slidertick", volume: volume_muted) } // Should be fine. + }; + sliderTick.ApplyDefaults(cpi, new BeatmapDifficulty()); + + var slider = new MockNestableHitObject(new List { sliderHead, sliderTick, }, startTime: 0, endTime: 500) + { + Samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) } + }; + slider.ApplyDefaults(cpi, new BeatmapDifficulty()); + + assertOk(new List { slider }); + } + + [Test] + public void TestMutedSampleVolumeSliderHead() + { + var sliderHead = new SliderHeadCircle + { + StartTime = 0, + Samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL, volume: volume_muted) } + }; + sliderHead.ApplyDefaults(cpi, new BeatmapDifficulty()); + + var sliderTick = new SliderTick + { + StartTime = 250, + Samples = new List { new HitSampleInfo("slidertick") } + }; + sliderTick.ApplyDefaults(cpi, new BeatmapDifficulty()); + + var slider = new MockNestableHitObject(new List { sliderHead, sliderTick, }, startTime: 0, endTime: 500) + { + Samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) } // Applies to the tail. + }; + slider.ApplyDefaults(cpi, new BeatmapDifficulty()); + + assertMuted(new List { slider }); + } + + [Test] + public void TestMutedSampleVolumeSliderTail() + { + var sliderHead = new SliderHeadCircle + { + StartTime = 0, + Samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) } + }; + sliderHead.ApplyDefaults(cpi, new BeatmapDifficulty()); + + var sliderTick = new SliderTick + { + StartTime = 250, + Samples = new List { new HitSampleInfo("slidertick") } + }; + sliderTick.ApplyDefaults(cpi, new BeatmapDifficulty()); + + var slider = new MockNestableHitObject(new List { sliderHead, sliderTick, }, startTime: 0, endTime: 2500) + { + Samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL, volume: volume_muted) } // Applies to the tail. + }; + slider.ApplyDefaults(cpi, new BeatmapDifficulty()); + + assertMutedPassive(new List { slider }); + } + + [Test] + public void TestMutedControlPointVolumeSliderHead() + { + var sliderHead = new SliderHeadCircle + { + StartTime = 2000, + Samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) } + }; + sliderHead.ApplyDefaults(cpi, new BeatmapDifficulty()); + + var sliderTick = new SliderTick + { + StartTime = 2250, + Samples = new List { new HitSampleInfo("slidertick") } + }; + sliderTick.ApplyDefaults(cpi, new BeatmapDifficulty()); + + var slider = new MockNestableHitObject(new List { sliderHead, sliderTick, }, startTime: 2000, endTime: 2500) + { + Samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL, volume: volume_regular) } + }; + slider.ApplyDefaults(cpi, new BeatmapDifficulty()); + + assertMuted(new List { slider }); + } + + [Test] + public void TestMutedControlPointVolumeSliderTail() + { + var sliderHead = new SliderHeadCircle + { + StartTime = 0, + Samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) } + }; + sliderHead.ApplyDefaults(cpi, new BeatmapDifficulty()); + + var sliderTick = new SliderTick + { + StartTime = 250, + Samples = new List { new HitSampleInfo("slidertick") } + }; + sliderTick.ApplyDefaults(cpi, new BeatmapDifficulty()); + + // Ends after the 5% control point. + var slider = new MockNestableHitObject(new List { sliderHead, sliderTick, }, startTime: 0, endTime: 2500) + { + Samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) } + }; + slider.ApplyDefaults(cpi, new BeatmapDifficulty()); + + assertMutedPassive(new List { slider }); + } + + private void assertOk(List hitObjects) + { + Assert.That(check.Run(getContext(hitObjects)), Is.Empty); + } + + private void assertLowVolume(List hitObjects, int count = 1) + { + var issues = check.Run(getContext(hitObjects)).ToList(); + + Assert.That(issues, Has.Count.EqualTo(count)); + Assert.That(issues.All(issue => issue.Template is CheckMutedObjects.IssueTemplateLowVolumeActive)); + } + + private void assertMuted(List hitObjects, int count = 1) + { + var issues = check.Run(getContext(hitObjects)).ToList(); + + Assert.That(issues, Has.Count.EqualTo(count)); + Assert.That(issues.All(issue => issue.Template is CheckMutedObjects.IssueTemplateMutedActive)); + } + + private void assertMutedPassive(List hitObjects) + { + var issues = check.Run(getContext(hitObjects)).ToList(); + + Assert.That(issues, Has.Count.EqualTo(1)); + Assert.That(issues.Any(issue => issue.Template is CheckMutedObjects.IssueTemplateMutedPassive)); + } + + private BeatmapVerifierContext getContext(List hitObjects) + { + var beatmap = new Beatmap + { + ControlPointInfo = cpi, + HitObjects = hitObjects + }; + + return new BeatmapVerifierContext(beatmap, new TestWorkingBeatmap(beatmap)); + } + } +} From a5abc664f30b25c249cb39e50ee67067eccebfb7 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Sat, 26 Jun 2021 19:21:15 +0200 Subject: [PATCH 089/168] Add few hitsounds check tests --- .../Editing/Checks/CheckFewHitsoundsTest.cs | 166 ++++++++++++++++++ 1 file changed, 166 insertions(+) create mode 100644 osu.Game.Tests/Editing/Checks/CheckFewHitsoundsTest.cs diff --git a/osu.Game.Tests/Editing/Checks/CheckFewHitsoundsTest.cs b/osu.Game.Tests/Editing/Checks/CheckFewHitsoundsTest.cs new file mode 100644 index 0000000000..8ae8cd8163 --- /dev/null +++ b/osu.Game.Tests/Editing/Checks/CheckFewHitsoundsTest.cs @@ -0,0 +1,166 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using osu.Game.Audio; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Edit.Checks; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Tests.Beatmaps; + +namespace osu.Game.Tests.Editing.Checks +{ + [TestFixture] + public class CheckFewHitsoundsTest + { + private CheckFewHitsounds check; + + [SetUp] + public void Setup() + { + check = new CheckFewHitsounds(); + } + + [Test] + public void TestHitsounded() + { + var hitObjects = new List(); + + for (int i = 0; i < 16; ++i) + { + var samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) }; + + if ((i + 1) % 2 == 0) + samples.Add(new HitSampleInfo(HitSampleInfo.HIT_CLAP)); + if ((i + 1) % 3 == 0) + samples.Add(new HitSampleInfo(HitSampleInfo.HIT_WHISTLE)); + if ((i + 1) % 4 == 0) + samples.Add(new HitSampleInfo(HitSampleInfo.HIT_FINISH)); + + hitObjects.Add(new HitCircle { StartTime = 1000 * i, Samples = samples }); + } + + assertOk(hitObjects); + } + + [Test] + public void TestLightlyHitsounded() + { + var hitObjects = new List(); + + for (int i = 0; i < 30; ++i) + { + var samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) }; + + if (i % 8 == 0) + samples.Add(new HitSampleInfo(HitSampleInfo.HIT_WHISTLE)); + + hitObjects.Add(new HitCircle { StartTime = 1000 * i, Samples = samples }); + } + + assertLongPeriodNegligible(hitObjects, count: 3); + } + + [Test] + public void TestRarelyHitsounded() + { + var hitObjects = new List(); + + for (int i = 0; i < 30; ++i) + { + var samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) }; + + if (i == 0 || i == 15) + samples.Add(new HitSampleInfo(HitSampleInfo.HIT_WHISTLE)); + + hitObjects.Add(new HitCircle { StartTime = 1000 * i, Samples = samples }); + } + + // Should prompt one warning between 1st and 11th, and another between 11th and 20th. + assertLongPeriodWarning(hitObjects, count: 2); + } + + [Test] + public void TestExtremelyRarelyHitsounded() + { + var hitObjects = new List(); + + for (int i = 0; i < 80; ++i) + { + var samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) }; + + if (i == 40) + samples.Add(new HitSampleInfo(HitSampleInfo.HIT_WHISTLE)); + + hitObjects.Add(new HitCircle { StartTime = 1000 * i, Samples = samples }); + } + + // Should prompt one problem between 1st and 40th, and another between 40th and 80th. + assertLongPeriodProblem(hitObjects, count: 2); + } + + [Test] + public void TestNotHitsounded() + { + var hitObjects = new List(); + + for (int i = 0; i < 20; ++i) + { + var samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) }; + + hitObjects.Add(new HitCircle { StartTime = 1000 * i, Samples = samples }); + } + + // Should prompt one problem between 1st and 40th, and another between 40th and 80th. + assertNoHitsounds(hitObjects); + } + + private void assertOk(List hitObjects) + { + Assert.That(check.Run(getContext(hitObjects)), Is.Empty); + } + + private void assertLongPeriodProblem(List hitObjects, int count = 1) + { + var issues = check.Run(getContext(hitObjects)).ToList(); + + Assert.That(issues, Has.Count.EqualTo(count)); + Assert.That(issues.All(issue => issue.Template is CheckFewHitsounds.IssueTemplateLongPeriodProblem)); + } + + private void assertLongPeriodWarning(List hitObjects, int count = 1) + { + var issues = check.Run(getContext(hitObjects)).ToList(); + + Assert.That(issues, Has.Count.EqualTo(count)); + Assert.That(issues.All(issue => issue.Template is CheckFewHitsounds.IssueTemplateLongPeriodWarning)); + } + + private void assertLongPeriodNegligible(List hitObjects, int count = 1) + { + var issues = check.Run(getContext(hitObjects)).ToList(); + + Assert.That(issues, Has.Count.EqualTo(count)); + Assert.That(issues.All(issue => issue.Template is CheckFewHitsounds.IssueTemplateLongPeriodNegligible)); + } + + private void assertNoHitsounds(List hitObjects) + { + var issues = check.Run(getContext(hitObjects)).ToList(); + + Assert.That(issues, Has.Count.EqualTo(1)); + Assert.That(issues.Any(issue => issue.Template is CheckFewHitsounds.IssueTemplateNoHitsounds)); + } + + private BeatmapVerifierContext getContext(List hitObjects) + { + var beatmap = new Beatmap { HitObjects = hitObjects }; + + return new BeatmapVerifierContext(beatmap, new TestWorkingBeatmap(beatmap)); + } + } +} From 82b64f5589a950b43afc9dc9d6b0e02c548e285c Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Sat, 26 Jun 2021 19:57:12 +0200 Subject: [PATCH 090/168] Add hitsounded with break test --- .../Editing/Checks/CheckFewHitsoundsTest.cs | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/osu.Game.Tests/Editing/Checks/CheckFewHitsoundsTest.cs b/osu.Game.Tests/Editing/Checks/CheckFewHitsoundsTest.cs index 8ae8cd8163..8edc6d096e 100644 --- a/osu.Game.Tests/Editing/Checks/CheckFewHitsoundsTest.cs +++ b/osu.Game.Tests/Editing/Checks/CheckFewHitsoundsTest.cs @@ -47,6 +47,31 @@ namespace osu.Game.Tests.Editing.Checks assertOk(hitObjects); } + [Test] + public void TestHitsoundedWithBreak() + { + var hitObjects = new List(); + + for (int i = 0; i < 32; ++i) + { + var samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) }; + + if ((i + 1) % 2 == 0) + samples.Add(new HitSampleInfo(HitSampleInfo.HIT_CLAP)); + if ((i + 1) % 3 == 0) + samples.Add(new HitSampleInfo(HitSampleInfo.HIT_WHISTLE)); + if ((i + 1) % 4 == 0) + samples.Add(new HitSampleInfo(HitSampleInfo.HIT_FINISH)); + // Leaves a gap in which no hitsounds exist or can be added, and so shouldn't be an issue. + if (i > 8 && i < 24) + continue; + + hitObjects.Add(new HitCircle { StartTime = 1000 * i, Samples = samples }); + } + + assertOk(hitObjects); + } + [Test] public void TestLightlyHitsounded() { From 51888d0d5a3fbbcf8fbd4787ffa58b9f3d7b56e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 26 Jun 2021 19:27:34 +0200 Subject: [PATCH 091/168] Rename test methods --- .../Visual/Online/TestSceneBeatmapListingOverlay.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs index cd382c2bb2..16122e496f 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs @@ -72,7 +72,7 @@ namespace osu.Game.Tests.Visual.Online } [Test] - public void TestNonSupportUseSupporterOnlyFiltersPlaceholderNoBeatmaps() + public void TestUserWithoutSupporterUsesSupporterOnlyFiltersWithoutResults() { AddStep("fetch for 0 beatmaps", () => fetchFor()); AddStep("set dummy as non-supporter", () => ((DummyAPIAccess)API).LocalUser.Value.IsSupporter = false); @@ -102,7 +102,7 @@ namespace osu.Game.Tests.Visual.Online } [Test] - public void TestSupportUseSupporterOnlyFiltersPlaceholderNoBeatmaps() + public void TestUserWithSupporterUsesSupporterOnlyFiltersWithoutResults() { AddStep("fetch for 0 beatmaps", () => fetchFor()); AddStep("set dummy as supporter", () => ((DummyAPIAccess)API).LocalUser.Value.IsSupporter = true); @@ -132,7 +132,7 @@ namespace osu.Game.Tests.Visual.Online } [Test] - public void TestNonSupporterUseSupporterOnlyFiltersPlaceholderOneBeatmap() + public void TestUserWithoutSupporterUsesSupporterOnlyFiltersWithResults() { AddStep("fetch for 1 beatmap", () => fetchFor(CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet)); AddStep("set dummy as non-supporter", () => ((DummyAPIAccess)API).LocalUser.Value.IsSupporter = false); @@ -162,7 +162,7 @@ namespace osu.Game.Tests.Visual.Online } [Test] - public void TestSupporterUseSupporterOnlyFiltersPlaceholderOneBeatmap() + public void TestUserWithSupporterUsesSupporterOnlyFiltersWithResults() { AddStep("fetch for 1 beatmap", () => fetchFor(CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet)); AddStep("set dummy as supporter", () => ((DummyAPIAccess)API).LocalUser.Value.IsSupporter = true); From b7c4fe2052a38f30aa1789f8c8b9103660b51b31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 26 Jun 2021 20:24:12 +0200 Subject: [PATCH 092/168] Rewrite test helpers to also handle clearing filters --- .../Online/TestSceneBeatmapListingOverlay.cs | 100 +++++++++--------- 1 file changed, 51 insertions(+), 49 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs index 16122e496f..66e7248207 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs @@ -1,6 +1,7 @@ // 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; @@ -14,6 +15,7 @@ using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays; using osu.Game.Overlays.BeatmapListing; using osu.Game.Rulesets; +using osu.Game.Scoring; using osu.Game.Users; namespace osu.Game.Tests.Visual.Online @@ -77,27 +79,27 @@ namespace osu.Game.Tests.Visual.Online AddStep("fetch for 0 beatmaps", () => fetchFor()); AddStep("set dummy as non-supporter", () => ((DummyAPIAccess)API).LocalUser.Value.IsSupporter = false); - // test non-supporter on Rank Achieved filter - toggleRankFilter(Scoring.ScoreRank.XH); + // only Rank Achieved filter + setRankAchievedFilter(new[] { ScoreRank.XH }); supporterRequiredPlaceholderShown(); - AddStep("Clear Rank Achieved filter", () => searchControl.Ranks.Clear()); + setRankAchievedFilter(Array.Empty()); notFoundPlaceholderShown(); - // test non-supporter on Played filter - toggleSupporterOnlyPlayedFilter(SearchPlayed.Played); + // only Played filter + setPlayedFilter(SearchPlayed.Played); supporterRequiredPlaceholderShown(); - AddStep("Set Played filter to Any", () => searchControl.Played.Value = SearchPlayed.Any); + setPlayedFilter(SearchPlayed.Any); notFoundPlaceholderShown(); - // test non-supporter on both Rank Achieved and Played filter - toggleRankFilter(Scoring.ScoreRank.XH); - toggleSupporterOnlyPlayedFilter(SearchPlayed.Played); + // both RankAchieved and Played filters + setRankAchievedFilter(new[] { ScoreRank.XH }); + setPlayedFilter(SearchPlayed.Played); supporterRequiredPlaceholderShown(); - AddStep("Clear Rank Achieved filter", () => searchControl.Ranks.Clear()); - AddStep("Set Played filter to Any", () => searchControl.Played.Value = SearchPlayed.Any); + setRankAchievedFilter(Array.Empty()); + setPlayedFilter(SearchPlayed.Any); notFoundPlaceholderShown(); } @@ -107,27 +109,27 @@ namespace osu.Game.Tests.Visual.Online AddStep("fetch for 0 beatmaps", () => fetchFor()); AddStep("set dummy as supporter", () => ((DummyAPIAccess)API).LocalUser.Value.IsSupporter = true); - // test supporter on Rank Achieved filter - toggleRankFilter(Scoring.ScoreRank.XH); + // only Rank Achieved filter + setRankAchievedFilter(new[] { ScoreRank.XH }); notFoundPlaceholderShown(); - AddStep("Clear Rank Achieved filter", () => searchControl.Ranks.Clear()); + setRankAchievedFilter(Array.Empty()); notFoundPlaceholderShown(); - // test supporter on Played filter - toggleSupporterOnlyPlayedFilter(SearchPlayed.Played); + // only Played filter + setPlayedFilter(SearchPlayed.Played); notFoundPlaceholderShown(); - AddStep("Set Played filter to Any", () => searchControl.Played.Value = SearchPlayed.Any); + setPlayedFilter(SearchPlayed.Any); notFoundPlaceholderShown(); - // test supporter on both Rank Achieved and Played filter - toggleRankFilter(Scoring.ScoreRank.XH); - toggleSupporterOnlyPlayedFilter(SearchPlayed.Played); + // both Rank Achieved and Played filters + setRankAchievedFilter(new[] { ScoreRank.XH }); + setPlayedFilter(SearchPlayed.Played); notFoundPlaceholderShown(); - AddStep("Clear Rank Achieved filter", () => searchControl.Ranks.Clear()); - AddStep("Set Played filter to Any", () => searchControl.Played.Value = SearchPlayed.Any); + setRankAchievedFilter(Array.Empty()); + setPlayedFilter(SearchPlayed.Any); notFoundPlaceholderShown(); } @@ -137,27 +139,27 @@ namespace osu.Game.Tests.Visual.Online AddStep("fetch for 1 beatmap", () => fetchFor(CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet)); AddStep("set dummy as non-supporter", () => ((DummyAPIAccess)API).LocalUser.Value.IsSupporter = false); - // test non-supporter on Rank Achieved filter - toggleRankFilter(Scoring.ScoreRank.XH); + // only Rank Achieved filter + setRankAchievedFilter(new[] { ScoreRank.XH }); supporterRequiredPlaceholderShown(); - AddStep("Clear Rank Achieved filter", () => searchControl.Ranks.Clear()); + setRankAchievedFilter(Array.Empty()); noPlaceholderShown(); - // test non-supporter on Played filter - toggleSupporterOnlyPlayedFilter(SearchPlayed.Played); + // only Played filter + setPlayedFilter(SearchPlayed.Played); supporterRequiredPlaceholderShown(); - AddStep("Set Played filter to Any", () => searchControl.Played.Value = SearchPlayed.Any); + setPlayedFilter(SearchPlayed.Any); noPlaceholderShown(); - // test non-supporter on both Rank Achieved and Played filter - toggleRankFilter(Scoring.ScoreRank.XH); - toggleSupporterOnlyPlayedFilter(SearchPlayed.Played); + // both Rank Achieved and Played filters + setRankAchievedFilter(new[] { ScoreRank.XH }); + setPlayedFilter(SearchPlayed.Played); supporterRequiredPlaceholderShown(); - AddStep("Clear Rank Achieved filter", () => searchControl.Ranks.Clear()); - AddStep("Set Played filter to Any", () => searchControl.Played.Value = SearchPlayed.Any); + setRankAchievedFilter(Array.Empty()); + setPlayedFilter(SearchPlayed.Any); noPlaceholderShown(); } @@ -167,27 +169,27 @@ namespace osu.Game.Tests.Visual.Online AddStep("fetch for 1 beatmap", () => fetchFor(CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet)); AddStep("set dummy as supporter", () => ((DummyAPIAccess)API).LocalUser.Value.IsSupporter = true); - // test supporter on Rank Achieved filter - toggleRankFilter(Scoring.ScoreRank.XH); + // only Rank Achieved filter + setRankAchievedFilter(new[] { ScoreRank.XH }); noPlaceholderShown(); - AddStep("Clear Rank Achieved filter", () => searchControl.Ranks.Clear()); + setRankAchievedFilter(Array.Empty()); noPlaceholderShown(); - // test supporter on Played filter - toggleSupporterOnlyPlayedFilter(SearchPlayed.Played); + // only Played filter + setPlayedFilter(SearchPlayed.Played); noPlaceholderShown(); - AddStep("Set Played filter to Any", () => searchControl.Played.Value = SearchPlayed.Any); + setPlayedFilter(SearchPlayed.Any); noPlaceholderShown(); - // test supporter on both Rank Achieved and Played filter - toggleRankFilter(Scoring.ScoreRank.XH); - toggleSupporterOnlyPlayedFilter(SearchPlayed.Played); + // both Rank Achieved and Played filters + setRankAchievedFilter(new[] { ScoreRank.XH }); + setPlayedFilter(SearchPlayed.Played); noPlaceholderShown(); - AddStep("Set Played filter to Any", () => searchControl.Played.Value = SearchPlayed.Any); - AddStep("Clear Rank Achieved filter", () => searchControl.Ranks.Clear()); + setRankAchievedFilter(Array.Empty()); + setPlayedFilter(SearchPlayed.Any); noPlaceholderShown(); } @@ -200,18 +202,18 @@ namespace osu.Game.Tests.Visual.Online searchControl.Query.TriggerChange(); } - private void toggleRankFilter(Scoring.ScoreRank rank) + private void setRankAchievedFilter(ScoreRank[] ranks) { - AddStep("toggle Rank Achieved filter", () => + AddStep($"set Rank Achieved filter to [{string.Join(',', ranks)}]", () => { searchControl.Ranks.Clear(); - searchControl.Ranks.Add(rank); + searchControl.Ranks.AddRange(ranks); }); } - private void toggleSupporterOnlyPlayedFilter(SearchPlayed played) + private void setPlayedFilter(SearchPlayed played) { - AddStep("toggle Played filter", () => searchControl.Played.Value = played); + AddStep($"set Played filter to {played}", () => searchControl.Played.Value = played); } private void supporterRequiredPlaceholderShown() From 709e555566a80bcc9a7623c6721c99c645da2e5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 26 Jun 2021 20:27:15 +0200 Subject: [PATCH 093/168] Rename test steps for legibility --- .../Visual/Online/TestSceneBeatmapListingOverlay.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs index 66e7248207..5bfb676f81 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs @@ -218,17 +218,19 @@ namespace osu.Game.Tests.Visual.Online private void supporterRequiredPlaceholderShown() { - AddUntilStep("supporter-placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); + AddUntilStep("\"supporter required\" placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); } private void notFoundPlaceholderShown() { - AddUntilStep("not-found-placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); + AddUntilStep("\"no maps found\" placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); } private void noPlaceholderShown() { - AddUntilStep("no placeholder shown", () => !overlay.ChildrenOfType().Any() && !overlay.ChildrenOfType().Any()); + AddUntilStep("no placeholder shown", () => + !overlay.ChildrenOfType().Any() + && !overlay.ChildrenOfType().Any()); } private class TestAPIBeatmapSet : APIBeatmapSet From b56dd7ff25db96dbcd73c1e0f651987bda726e05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 26 Jun 2021 20:31:26 +0200 Subject: [PATCH 094/168] Fix naming and xmldocs in new beatmap search result structures --- .../BeatmapListingFilterControl.cs | 43 +++++++++++++------ osu.Game/Overlays/BeatmapListingOverlay.cs | 4 +- 2 files changed, 32 insertions(+), 15 deletions(-) diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs index b6a0846407..d80ef075e9 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs @@ -26,8 +26,6 @@ namespace osu.Game.Overlays.BeatmapListing { /// /// Fired when a search finishes. - /// SearchFinished.Type = ResultsReturned when results returned. Contains only new items in the case of pagination. - /// SearchFinished.Type = SupporterOnlyFilter when a non-supporter user applied supporter-only filters. /// public Action SearchFinished; @@ -216,7 +214,7 @@ namespace osu.Game.Overlays.BeatmapListing lastResponse = response; getSetsRequest = null; - // check if an non-supporter user used supporter-only filters + // check if a non-supporter used supporter-only filters if (!api.LocalUser.Value.IsSupporter) { List filters = new List(); @@ -229,7 +227,7 @@ namespace osu.Game.Overlays.BeatmapListing if (filters.Any()) { - SearchFinished?.Invoke(SearchResult.SupporterOnlyFilter(filters)); + SearchFinished?.Invoke(SearchResult.SupporterOnlyFilters(filters)); return; } } @@ -260,21 +258,40 @@ namespace osu.Game.Overlays.BeatmapListing base.Dispose(isDisposing); } + /// + /// Indicates the type of result of a user-requested beatmap search. + /// public enum SearchResultType { - // returned with Results + /// + /// Actual results have been returned from API. + /// ResultsReturned, - // non-supporter user applied supporter-only filters - SupporterOnlyFilter + + /// + /// The user is not a supporter, but used supporter-only search filters. + /// + SupporterOnlyFilters } - // Results only valid when Type == ResultsReturned - // Filters only valid when Type == SupporterOnlyFilter + /// + /// Describes the result of a user-requested beatmap search. + /// public struct SearchResult { public SearchResultType Type { get; private set; } + + /// + /// Contains the beatmap sets returned from API. + /// Valid for read if and only if is . + /// public List Results { get; private set; } - public List Filters { get; private set; } + + /// + /// Contains the names of supporter-only filters requested by the user. + /// Valid for read if and only if is . + /// + public List SupporterOnlyFiltersUsed { get; private set; } public static SearchResult ResultsReturned(List results) => new SearchResult { @@ -282,10 +299,10 @@ namespace osu.Game.Overlays.BeatmapListing Results = results }; - public static SearchResult SupporterOnlyFilter(List filters) => new SearchResult + public static SearchResult SupporterOnlyFilters(List filters) => new SearchResult { - Type = SearchResultType.SupporterOnlyFilter, - Filters = filters + Type = SearchResultType.SupporterOnlyFilters, + SupporterOnlyFiltersUsed = filters }; } } diff --git a/osu.Game/Overlays/BeatmapListingOverlay.cs b/osu.Game/Overlays/BeatmapListingOverlay.cs index c2ba3d5bc0..5489f0277f 100644 --- a/osu.Game/Overlays/BeatmapListingOverlay.cs +++ b/osu.Game/Overlays/BeatmapListingOverlay.cs @@ -123,9 +123,9 @@ namespace osu.Game.Overlays private void onSearchFinished(BeatmapListingFilterControl.SearchResult searchResult) { // non-supporter user used supporter-only filters - if (searchResult.Type == BeatmapListingFilterControl.SearchResultType.SupporterOnlyFilter) + if (searchResult.Type == BeatmapListingFilterControl.SearchResultType.SupporterOnlyFilters) { - supporterRequiredContent.UpdateText(searchResult.Filters); + supporterRequiredContent.UpdateText(searchResult.SupporterOnlyFiltersUsed); addContentToPlaceholder(supporterRequiredContent); return; } From 9061ab0a278e058704db7dd4c2ffa6ea476463d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 26 Jun 2021 20:40:54 +0200 Subject: [PATCH 095/168] Update/reword comments in listing overlay --- osu.Game/Overlays/BeatmapListingOverlay.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/BeatmapListingOverlay.cs b/osu.Game/Overlays/BeatmapListingOverlay.cs index 5489f0277f..460b4ba4c9 100644 --- a/osu.Game/Overlays/BeatmapListingOverlay.cs +++ b/osu.Game/Overlays/BeatmapListingOverlay.cs @@ -122,7 +122,6 @@ namespace osu.Game.Overlays private void onSearchFinished(BeatmapListingFilterControl.SearchResult searchResult) { - // non-supporter user used supporter-only filters if (searchResult.Type == BeatmapListingFilterControl.SearchResultType.SupporterOnlyFilters) { supporterRequiredContent.UpdateText(searchResult.SupporterOnlyFiltersUsed); @@ -185,7 +184,7 @@ namespace osu.Game.Overlays if (lastContent == notFoundContent || lastContent == supporterRequiredContent) { - // the placeholder may be used multiple times, so don't expire/dispose it. + // the placeholders may be used multiple times, so don't expire/dispose them. transform.Schedule(() => panelTarget.Remove(lastContent)); } else @@ -253,7 +252,8 @@ namespace osu.Game.Overlays } } - // using string literals as there's no proper processing for LocalizeStrings yet + // TODO: localisation requires Text/LinkFlowContainer support for localising strings with links inside + // (https://github.com/ppy/osu-framework/issues/4530) public class SupporterRequiredDrawable : CompositeDrawable { private LinkFlowContainer supporterRequiredText; From 51147405c59e7c01fd035efa55f8ac68fadd755d Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Sat, 26 Jun 2021 20:44:39 +0200 Subject: [PATCH 096/168] Make || and && priority explicit --- osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs b/osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs index 07ca470e62..f9897ea20c 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs @@ -86,7 +86,7 @@ namespace osu.Game.Rulesets.Edit.Checks // Only generating issues on hitsounded or last objects ensures we get one issue per long period. // If there are no hitsounds we let the "No hitsounds" template take precedence. - if (hasHitsound(hitObject) || isLastObject && hasHitsounds) + if (hasHitsound(hitObject) || (isLastObject && hasHitsounds)) { var timeWithoutHitsounds = time - lastHitsoundTime; From f78cc9397eff96d67f4b198ffd758934ba8b09c3 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Sat, 26 Jun 2021 20:45:31 +0200 Subject: [PATCH 097/168] Factor out edge type logic --- .../Rulesets/Edit/Checks/CheckMutedObjects.cs | 46 ++++++++++++------- 1 file changed, 30 insertions(+), 16 deletions(-) diff --git a/osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.cs b/osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.cs index cbe7c7fbab..23d89a2f44 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.cs @@ -22,6 +22,14 @@ namespace osu.Game.Rulesets.Edit.Checks /// private const int low_volume_threshold = 20; + private enum EdgeType + { + Head, + Repeat, + Tail, + None + } + public CheckMetadata Metadata { get; } = new CheckMetadata(CheckCategory.Audio, "Low volume hitobjects"); public IEnumerable PossibleTemplates => new IssueTemplate[] @@ -58,37 +66,43 @@ namespace osu.Game.Rulesets.Edit.Checks int maxVolume = sampledHitObject.Samples.Max(sample => sample.Volume > 0 ? sample.Volume : sampledHitObject.SampleControlPoint.SampleVolume); double samplePlayTime = sampledHitObject.GetEndTime(); - bool head = Precision.AlmostEquals(samplePlayTime, hitObject.StartTime, 1f); - bool tail = Precision.AlmostEquals(samplePlayTime, hitObject.GetEndTime(), 1f); - bool repeat = false; - - if (hitObject is IHasRepeats hasRepeats && !head && !tail) - { - double spanDuration = hasRepeats.Duration / hasRepeats.SpanCount(); - repeat = Precision.AlmostEquals((samplePlayTime - hitObject.StartTime) % spanDuration, 0f, 1f); - } - + EdgeType edgeType = getEdgeAtTime(hitObject, samplePlayTime); // We only care about samples played on the edges of objects, not ones like spinnerspin or slidertick. - if (!head && !tail && !repeat) + if (edgeType == EdgeType.None) yield break; - string postfix = null; - if (hitObject is IHasDuration) - postfix = head ? "head" : tail ? "tail" : "repeat"; + string postfix = hitObject is IHasDuration ? edgeType.ToString().ToLower() : null; if (maxVolume <= muted_threshold) { - if (head) + if (edgeType == EdgeType.Head) yield return new IssueTemplateMutedActive(this).Create(hitObject, maxVolume / 100f, sampledHitObject.GetEndTime(), postfix); else yield return new IssueTemplateMutedPassive(this).Create(hitObject, maxVolume / 100f, sampledHitObject.GetEndTime(), postfix); } - else if (maxVolume <= low_volume_threshold && head) + else if (maxVolume <= low_volume_threshold && edgeType == EdgeType.Head) { yield return new IssueTemplateLowVolumeActive(this).Create(hitObject, maxVolume / 100f, sampledHitObject.GetEndTime(), postfix); } } + private EdgeType getEdgeAtTime(HitObject hitObject, double time) + { + if (Precision.AlmostEquals(time, hitObject.StartTime, 1f)) + return EdgeType.Head; + if (Precision.AlmostEquals(time, hitObject.GetEndTime(), 1f)) + return EdgeType.Tail; + + if (hitObject is IHasRepeats hasRepeats) + { + double spanDuration = hasRepeats.Duration / hasRepeats.SpanCount(); + if (Precision.AlmostEquals((time - hitObject.StartTime) % spanDuration, 0f, 1f)) + return EdgeType.Repeat; + } + + return EdgeType.None; + } + public abstract class IssueTemplateMuted : IssueTemplate { protected IssueTemplateMuted(ICheck check, IssueType type, string unformattedMessage) From 5642d321b70f55550b4bc992c0ccf992514cf5b9 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Sat, 26 Jun 2021 23:43:08 +0200 Subject: [PATCH 098/168] Fix comments in few hitsounds check tests --- osu.Game.Tests/Editing/Checks/CheckFewHitsoundsTest.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Editing/Checks/CheckFewHitsoundsTest.cs b/osu.Game.Tests/Editing/Checks/CheckFewHitsoundsTest.cs index 8edc6d096e..7561e94d2e 100644 --- a/osu.Game.Tests/Editing/Checks/CheckFewHitsoundsTest.cs +++ b/osu.Game.Tests/Editing/Checks/CheckFewHitsoundsTest.cs @@ -105,7 +105,7 @@ namespace osu.Game.Tests.Editing.Checks hitObjects.Add(new HitCircle { StartTime = 1000 * i, Samples = samples }); } - // Should prompt one warning between 1st and 11th, and another between 11th and 20th. + // Should prompt one warning between 1st and 16th, and another between 16th and 31st. assertLongPeriodWarning(hitObjects, count: 2); } @@ -124,7 +124,7 @@ namespace osu.Game.Tests.Editing.Checks hitObjects.Add(new HitCircle { StartTime = 1000 * i, Samples = samples }); } - // Should prompt one problem between 1st and 40th, and another between 40th and 80th. + // Should prompt one problem between 1st and 41st, and another between 41st and 81st. assertLongPeriodProblem(hitObjects, count: 2); } @@ -140,7 +140,6 @@ namespace osu.Game.Tests.Editing.Checks hitObjects.Add(new HitCircle { StartTime = 1000 * i, Samples = samples }); } - // Should prompt one problem between 1st and 40th, and another between 40th and 80th. assertNoHitsounds(hitObjects); } From 191308434215634a96f88dc3501bacee9a2da354 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Sat, 26 Jun 2021 23:48:28 +0200 Subject: [PATCH 099/168] Use `HitSampleInfo.AllAdditions` instead of new list --- osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs b/osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs index f9897ea20c..acdac83141 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs @@ -32,8 +32,6 @@ namespace osu.Game.Rulesets.Edit.Checks private const int warning_threshold_objects = 4; private const int problem_threshold_objects = 16; - private static readonly string[] hitsound_types = { HitSampleInfo.HIT_CLAP, HitSampleInfo.HIT_WHISTLE, HitSampleInfo.HIT_FINISH }; - public CheckMetadata Metadata { get; } = new CheckMetadata(CheckCategory.Audio, "Few or no hitsounds"); public IEnumerable PossibleTemplates => new IssueTemplate[] @@ -111,7 +109,7 @@ namespace osu.Game.Rulesets.Edit.Checks private bool hasHitsound(HitObject hitObject) => hitObject.Samples.Any(isHitsound); private bool couldHaveHitsound(HitObject hitObject) => hitObject.Samples.Any(isHitnormal); - private bool isHitsound(HitSampleInfo sample) => hitsound_types.Any(sample.Name.Contains); + private bool isHitsound(HitSampleInfo sample) => HitSampleInfo.AllAdditions.Any(sample.Name.Contains); private bool isHitnormal(HitSampleInfo sample) => sample.Name.Contains(HitSampleInfo.HIT_NORMAL); public abstract class IssueTemplateLongPeriod : IssueTemplate From d29e6f46953624bbfac6dbea4f5c0929a0071ab6 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Sat, 26 Jun 2021 23:49:06 +0200 Subject: [PATCH 100/168] Add negligible template to `PossibleTemplates` --- osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs b/osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs index acdac83141..1de3652a39 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs @@ -38,6 +38,7 @@ namespace osu.Game.Rulesets.Edit.Checks { new IssueTemplateLongPeriodProblem(this), new IssueTemplateLongPeriodWarning(this), + new IssueTemplateLongPeriodNegligible(this), new IssueTemplateNoHitsounds(this) }; From 5bc08ebadb95ff4c215a9f7214dfde7d66a11031 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Sat, 26 Jun 2021 23:49:25 +0200 Subject: [PATCH 101/168] Rename `hasHitsounds` -> `mapHasHitsounds` --- osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs b/osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs index 1de3652a39..8d8243938a 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs @@ -42,7 +42,7 @@ namespace osu.Game.Rulesets.Edit.Checks new IssueTemplateNoHitsounds(this) }; - private bool hasHitsounds; + private bool mapHasHitsounds; private int objectsWithoutHitsounds; private double lastHitsoundTime; @@ -51,7 +51,7 @@ namespace osu.Game.Rulesets.Edit.Checks if (!context.Beatmap.HitObjects.Any()) yield break; - hasHitsounds = false; + mapHasHitsounds = false; objectsWithoutHitsounds = 0; lastHitsoundTime = context.Beatmap.HitObjects.First().StartTime; @@ -75,7 +75,7 @@ namespace osu.Game.Rulesets.Edit.Checks yield return issue; } - if (!hasHitsounds) + if (!mapHasHitsounds) yield return new IssueTemplateNoHitsounds(this).Create(); } @@ -85,7 +85,7 @@ namespace osu.Game.Rulesets.Edit.Checks // Only generating issues on hitsounded or last objects ensures we get one issue per long period. // If there are no hitsounds we let the "No hitsounds" template take precedence. - if (hasHitsound(hitObject) || (isLastObject && hasHitsounds)) + if (hasHitsound(hitObject) || (isLastObject && mapHasHitsounds)) { var timeWithoutHitsounds = time - lastHitsoundTime; @@ -99,7 +99,7 @@ namespace osu.Game.Rulesets.Edit.Checks if (hasHitsound(hitObject)) { - hasHitsounds = true; + mapHasHitsounds = true; objectsWithoutHitsounds = 0; lastHitsoundTime = time; } From 4796b1b2089f6e5c64449bbb01e81182231491fd Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Sun, 27 Jun 2021 00:04:30 +0200 Subject: [PATCH 102/168] Use local variables for `hasHitsound` & `couldHaveHitsound` --- osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs b/osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs index 8d8243938a..1fca6b26a4 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs @@ -82,10 +82,12 @@ namespace osu.Game.Rulesets.Edit.Checks private IEnumerable applyHitsoundUpdate(HitObject hitObject, bool isLastObject = false) { var time = hitObject.GetEndTime(); + bool hasHitsound = hitObject.Samples.Any(isHitsound); + bool couldHaveHitsound = hitObject.Samples.Any(isHitnormal); // Only generating issues on hitsounded or last objects ensures we get one issue per long period. // If there are no hitsounds we let the "No hitsounds" template take precedence. - if (hasHitsound(hitObject) || (isLastObject && mapHasHitsounds)) + if (hasHitsound || (isLastObject && mapHasHitsounds)) { var timeWithoutHitsounds = time - lastHitsoundTime; @@ -97,19 +99,16 @@ namespace osu.Game.Rulesets.Edit.Checks yield return new IssueTemplateLongPeriodNegligible(this).Create(lastHitsoundTime, timeWithoutHitsounds); } - if (hasHitsound(hitObject)) + if (hasHitsound) { mapHasHitsounds = true; objectsWithoutHitsounds = 0; lastHitsoundTime = time; } - else if (couldHaveHitsound(hitObject)) + else if (couldHaveHitsound) ++objectsWithoutHitsounds; } - private bool hasHitsound(HitObject hitObject) => hitObject.Samples.Any(isHitsound); - private bool couldHaveHitsound(HitObject hitObject) => hitObject.Samples.Any(isHitnormal); - private bool isHitsound(HitSampleInfo sample) => HitSampleInfo.AllAdditions.Any(sample.Name.Contains); private bool isHitnormal(HitSampleInfo sample) => sample.Name.Contains(HitSampleInfo.HIT_NORMAL); From 0c0fd291d9381d718668e0588d6fb5dee60e91c7 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Sun, 27 Jun 2021 01:25:03 +0200 Subject: [PATCH 103/168] Order hitobjects by endtime --- .../Rulesets/Edit/Checks/CheckFewHitsounds.cs | 23 +++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs b/osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs index 1fca6b26a4..5185ba6c99 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs @@ -55,18 +55,23 @@ namespace osu.Game.Rulesets.Edit.Checks objectsWithoutHitsounds = 0; lastHitsoundTime = context.Beatmap.HitObjects.First().StartTime; - var hitObjectCount = context.Beatmap.HitObjects.Count; + var hitObjectsIncludingNested = new List(); + + foreach (var hitObject in context.Beatmap.HitObjects) + { + // Samples play on the end of objects. Some objects have nested objects to accomplish playing them elsewhere (e.g. slider head/repeat). + foreach (var nestedHitObject in hitObject.NestedHitObjects) + hitObjectsIncludingNested.Add(nestedHitObject); + + hitObjectsIncludingNested.Add(hitObject); + } + + var hitObjectsByEndTime = hitObjectsIncludingNested.OrderBy(o => o.GetEndTime()).ToList(); + var hitObjectCount = hitObjectsByEndTime.Count; for (int i = 0; i < hitObjectCount; ++i) { - var hitObject = context.Beatmap.HitObjects[i]; - - // Samples play on the end of objects. Some objects have nested objects to accomplish playing them elsewhere (e.g. slider head/repeat). - foreach (var nestedHitObject in hitObject.NestedHitObjects) - { - foreach (var issue in applyHitsoundUpdate(nestedHitObject)) - yield return issue; - } + var hitObject = hitObjectsByEndTime[i]; // This is used to perform an update at the end so that the period after the last hitsounded object can be an issue. bool isLastObject = i == hitObjectCount - 1; From 2cd7eda3c4d62e7b7898c7a60de3d78ea5447b5f Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Sun, 27 Jun 2021 02:30:12 +0200 Subject: [PATCH 104/168] Add "or equal to" to volume threshold xmldocs --- osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.cs b/osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.cs index 23d89a2f44..2ac1efc636 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.cs @@ -13,12 +13,12 @@ namespace osu.Game.Rulesets.Edit.Checks public class CheckMutedObjects : ICheck { /// - /// Volume percentages lower than this are typically inaudible. + /// Volume percentages lower than or equal to this are typically inaudible. /// private const int muted_threshold = 5; /// - /// Volume percentages lower than this can sometimes be inaudible depending on sample used and music volume. + /// Volume percentages lower than or equal to this can sometimes be inaudible depending on sample used and music volume. /// private const int low_volume_threshold = 20; From 4cfa0ae5ec79bf2e1922132f23515baa374f2b4e Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Sun, 27 Jun 2021 03:26:35 +0200 Subject: [PATCH 105/168] Improve precision for repeat edges --- osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.cs b/osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.cs index 2ac1efc636..c743b5693e 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.cs @@ -1,6 +1,7 @@ // 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 osu.Framework.Utils; @@ -96,7 +97,9 @@ namespace osu.Game.Rulesets.Edit.Checks if (hitObject is IHasRepeats hasRepeats) { double spanDuration = hasRepeats.Duration / hasRepeats.SpanCount(); - if (Precision.AlmostEquals((time - hitObject.StartTime) % spanDuration, 0f, 1f)) + double spans = (time - hitObject.StartTime) / spanDuration; + + if (Precision.AlmostEquals(spans, Math.Ceiling(spans)) || Precision.AlmostEquals(spans, Math.Floor(spans))) return EdgeType.Repeat; } From d1f852d102489ae8ae69069dc5ae1416ea0927da Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 27 Jun 2021 13:06:20 +0900 Subject: [PATCH 106/168] Make `Populate` abstract to avoid unnecessary base call async complexity --- osu.Game/Database/ArchiveModelManager.cs | 2 +- osu.Game/Scoring/ScoreManager.cs | 4 ++++ osu.Game/Skinning/SkinManager.cs | 6 +++--- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index 8efd451857..c1a4a6e18a 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -727,7 +727,7 @@ namespace osu.Game.Database /// The model to populate. /// The archive to use as a reference for population. May be null. /// An optional cancellation token. - protected virtual Task Populate(TModel model, [CanBeNull] ArchiveReader archive, CancellationToken cancellationToken = default) => Task.CompletedTask; + protected abstract Task Populate(TModel model, [CanBeNull] ArchiveReader archive, CancellationToken cancellationToken = default); /// /// Perform any final actions before the import to database executes. diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index 9d3b952ada..d5bea0affc 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -7,6 +7,7 @@ using System.IO; using System.Linq; using System.Linq.Expressions; using System.Threading; +using System.Threading.Tasks; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore; using osu.Framework.Bindables; @@ -72,6 +73,9 @@ namespace osu.Game.Scoring } } + protected override Task Populate(ScoreInfo model, ArchiveReader archive, CancellationToken cancellationToken = default) + => Task.CompletedTask; + protected override void ExportModelTo(ScoreInfo model, Stream outputStream) { var file = model.Files.SingleOrDefault(); diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index 4cde4cd2b8..645c943d09 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -142,16 +142,16 @@ namespace osu.Game.Skinning return base.ComputeHash(item, reader); } - protected override async Task Populate(SkinInfo model, ArchiveReader archive, CancellationToken cancellationToken = default) + protected override Task Populate(SkinInfo model, ArchiveReader archive, CancellationToken cancellationToken = default) { - await base.Populate(model, archive, cancellationToken).ConfigureAwait(false); - var instance = GetSkin(model); model.InstantiationInfo ??= instance.GetType().GetInvariantInstantiationInfo(); if (model.Name?.Contains(".osk", StringComparison.OrdinalIgnoreCase) == true) populateMetadata(model, instance); + + return Task.CompletedTask; } private void populateMetadata(SkinInfo item, Skin instance) From 46f8100f4371b64e2bda9b484493597b4a868bf5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 27 Jun 2021 13:06:47 +0900 Subject: [PATCH 107/168] Remove overly verbose logging during beatmap imports --- osu.Game/Beatmaps/BeatmapManager.cs | 2 -- osu.Game/Beatmaps/BeatmapManager_BeatmapOnlineLookupQueue.cs | 1 - 2 files changed, 3 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 00af06703d..86c8fb611f 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -191,8 +191,6 @@ namespace osu.Game.Beatmaps { var beatmapIds = beatmapSet.Beatmaps.Where(b => b.OnlineBeatmapID.HasValue).Select(b => b.OnlineBeatmapID).ToList(); - LogForModel(beatmapSet, $"Validating online IDs for {beatmapSet.Beatmaps.Count} beatmaps..."); - // ensure all IDs are unique if (beatmapIds.GroupBy(b => b).Any(g => g.Count() > 1)) { diff --git a/osu.Game/Beatmaps/BeatmapManager_BeatmapOnlineLookupQueue.cs b/osu.Game/Beatmaps/BeatmapManager_BeatmapOnlineLookupQueue.cs index 5dff4fe282..7824205257 100644 --- a/osu.Game/Beatmaps/BeatmapManager_BeatmapOnlineLookupQueue.cs +++ b/osu.Game/Beatmaps/BeatmapManager_BeatmapOnlineLookupQueue.cs @@ -48,7 +48,6 @@ namespace osu.Game.Beatmaps public Task UpdateAsync(BeatmapSetInfo beatmapSet, CancellationToken cancellationToken) { - LogForModel(beatmapSet, "Performing online lookups..."); return Task.WhenAll(beatmapSet.Beatmaps.Select(b => UpdateAsync(beatmapSet, b, cancellationToken)).ToArray()); } From 15af28d2a0af88ac7f167341d1d438121ccf3a62 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 27 Jun 2021 14:48:51 +0900 Subject: [PATCH 108/168] Remove comparison of online beatmap IDs during dedupe checks --- .../Beatmaps/IO/ImportBeatmapTest.cs | 35 ------------------- osu.Game/Beatmaps/BeatmapManager.cs | 12 ------- osu.Game/Database/ArchiveModelManager.cs | 4 +-- 3 files changed, 2 insertions(+), 49 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs index 0d117f8755..2087bdf144 100644 --- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs @@ -439,41 +439,6 @@ namespace osu.Game.Tests.Beatmaps.IO } } - [TestCase(true)] - [TestCase(false)] - public async Task TestImportThenDeleteThenImportWithOnlineIDMismatch(bool set) - { - // unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. - using (HeadlessGameHost host = new CleanRunHeadlessGameHost($"{nameof(ImportBeatmapTest)}-{set}")) - { - try - { - var osu = LoadOsuIntoHost(host); - - var imported = await LoadOszIntoOsu(osu); - - if (set) - imported.OnlineBeatmapSetID = 1234; - else - imported.Beatmaps.First().OnlineBeatmapID = 1234; - - osu.Dependencies.Get().Update(imported); - - deleteBeatmapSet(imported, osu); - - var importedSecondTime = await LoadOszIntoOsu(osu); - - // check the newly "imported" beatmap has been reimported due to mismatch (even though hashes matched) - Assert.IsTrue(imported.ID != importedSecondTime.ID); - Assert.IsTrue(imported.Beatmaps.First().ID != importedSecondTime.Beatmaps.First().ID); - } - finally - { - host.Exit(); - } - } - } - [Test] public async Task TestImportWithDuplicateBeatmapIDs() { diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 00af06703d..221774b018 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -319,18 +319,6 @@ namespace osu.Game.Beatmaps /// The first result for the provided query, or null if no results were found. public BeatmapSetInfo QueryBeatmapSet(Expression> query) => beatmaps.ConsumableItems.AsNoTracking().FirstOrDefault(query); - protected override bool CanReuseExisting(BeatmapSetInfo existing, BeatmapSetInfo import) - { - if (!base.CanReuseExisting(existing, import)) - return false; - - var existingIds = existing.Beatmaps.Select(b => b.OnlineBeatmapID).OrderBy(i => i); - var importIds = import.Beatmaps.Select(b => b.OnlineBeatmapID).OrderBy(i => i); - - // force re-import if we are not in a sane state. - return existing.OnlineBeatmapSetID == import.OnlineBeatmapSetID && existingIds.SequenceEqual(importIds); - } - /// /// Returns a list of all usable s. /// diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index 8efd451857..29c83b4699 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -377,7 +377,7 @@ namespace osu.Game.Database if (existing != null) { - if (CanReuseExisting(existing, item)) + if (canReuseExisting(existing, item)) { Undelete(existing); LogForModel(item, $"Found existing {HumanisedModelName} for {item} (ID {existing.ID}) – skipping import."); @@ -751,7 +751,7 @@ namespace osu.Game.Database /// The existing model. /// The newly imported model. /// Whether the existing model should be restored and used. Returning false will delete the existing and force a re-import. - protected virtual bool CanReuseExisting(TModel existing, TModel import) => + private bool canReuseExisting(TModel existing, TModel import) => // for the best or worst, we copy and import files of a new import before checking whether // it is a duplicate. so to check if anything has changed, we can just compare all FileInfo IDs. getIDs(existing.Files).SequenceEqual(getIDs(import.Files)) && From e493685c14acd80e430092e62700bcc75e59f618 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 27 Jun 2021 16:34:40 +0900 Subject: [PATCH 109/168] Add optimised existing check earlier in import process --- osu.Game/Database/ArchiveModelManager.cs | 61 +++++++++++++++++++----- 1 file changed, 50 insertions(+), 11 deletions(-) diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index 29c83b4699..80faa64953 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -317,7 +317,11 @@ namespace osu.Game.Database /// protected virtual string ComputeHash(TModel item, ArchiveReader reader = null) { - // for now, concatenate all .osu files in the set to create a unique hash. + if (reader != null) + // fast hashing for cases where the item's files may not be populated. + return computeHashFast(reader); + + // for now, concatenate all hashable files in the set to create a unique hash. MemoryStream hashable = new MemoryStream(); foreach (TFileModel file in item.Files.Where(f => HashableFileTypes.Any(ext => f.Filename.EndsWith(ext, StringComparison.OrdinalIgnoreCase))).OrderBy(f => f.Filename)) @@ -329,12 +333,25 @@ namespace osu.Game.Database if (hashable.Length > 0) return hashable.ComputeSHA2Hash(); - if (reader != null) - return reader.Name.ComputeSHA2Hash(); - return item.Hash; } + private string computeHashFast(ArchiveReader reader) + { + MemoryStream hashable = new MemoryStream(); + + foreach (var file in reader.Filenames.Where(f => HashableFileTypes.Any(ext => f.EndsWith(ext, StringComparison.OrdinalIgnoreCase))).OrderBy(f => f)) + { + using (Stream s = reader.GetStream(file)) + s.CopyTo(hashable); + } + + if (hashable.Length > 0) + return hashable.ComputeSHA2Hash(); + + return reader.Name.ComputeSHA2Hash(); + } + /// /// Silently import an item from a . /// @@ -348,6 +365,21 @@ namespace osu.Game.Database delayEvents(); + if (archive != null) + { + // fast bail to improve large import performance. + item.Hash = computeHashFast(archive); + + var fastExisting = CheckForExisting(item); + + if (fastExisting != null) + { + // bare minimum comparisons + if (getFilenames(fastExisting.Files).SequenceEqual(getShortenedFilenames(archive).Select(p => p.shortened))) + return fastExisting; + } + } + void rollback() { if (!Delete(item)) @@ -644,18 +676,14 @@ namespace osu.Game.Database { var fileInfos = new List(); - string prefix = reader.Filenames.GetCommonPrefix(); - if (!(prefix.EndsWith('/') || prefix.EndsWith('\\'))) - prefix = string.Empty; - // import files to manager - foreach (string file in reader.Filenames) + foreach (var filenames in getShortenedFilenames(reader)) { - using (Stream s = reader.GetStream(file)) + using (Stream s = reader.GetStream(filenames.original)) { fileInfos.Add(new TFileModel { - Filename = file.Substring(prefix.Length).ToStandardisedPath(), + Filename = filenames.shortened, FileInfo = files.Add(s) }); } @@ -664,6 +692,17 @@ namespace osu.Game.Database return fileInfos; } + private IEnumerable<(string original, string shortened)> getShortenedFilenames(ArchiveReader reader) + { + string prefix = reader.Filenames.GetCommonPrefix(); + if (!(prefix.EndsWith('/') || prefix.EndsWith('\\'))) + prefix = string.Empty; + + // import files to manager + foreach (string file in reader.Filenames) + yield return (file, file.Substring(prefix.Length).ToStandardisedPath()); + } + #region osu-stable import /// From 44f875b802f8d958d58cb34b065474fcc954b5ca Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 27 Jun 2021 16:35:13 +0900 Subject: [PATCH 110/168] Bypass optimised existing check in `SkinManager` (due to custom hashing function) --- osu.Game/Database/ArchiveModelManager.cs | 8 +++++++- osu.Game/Skinning/SkinManager.cs | 2 ++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index 80faa64953..22e5486909 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -309,6 +309,12 @@ namespace osu.Game.Database Logger.Log($"{prefix} {message}", LoggingTarget.Database); } + /// + /// Whether the implementation overrides with a custom implementation. + /// Custom has implementations must bypass the early exit in the import flow (see usage). + /// + protected virtual bool HasCustomHashFunction => false; + /// /// Create a SHA-2 hash from the provided archive based on file content of all files matching . /// @@ -365,7 +371,7 @@ namespace osu.Game.Database delayEvents(); - if (archive != null) + if (archive != null && !HasCustomHashFunction) { // fast bail to improve large import performance. item.Hash = computeHashFast(archive); diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index 4cde4cd2b8..43cf6b6874 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -125,6 +125,8 @@ namespace osu.Game.Skinning private const string unknown_creator_string = "Unknown"; + protected override bool HasCustomHashFunction => true; + protected override string ComputeHash(SkinInfo item, ArchiveReader reader = null) { // we need to populate early to create a hash based off skin.ini contents From 9120321731baac949045283dd0057ab1e8a723bb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 27 Jun 2021 16:48:42 +0900 Subject: [PATCH 111/168] Add comments mentioning shortcomings and avoid potential double check --- osu.Game/Database/ArchiveModelManager.cs | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index 22e5486909..ed5b54f446 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -371,18 +371,25 @@ namespace osu.Game.Database delayEvents(); + bool checkedExisting = false; + TModel existing = null; + if (archive != null && !HasCustomHashFunction) { - // fast bail to improve large import performance. + // this is a fast bail condition to improve large import performance. item.Hash = computeHashFast(archive); - var fastExisting = CheckForExisting(item); + checkedExisting = true; + existing = CheckForExisting(item); - if (fastExisting != null) + if (existing != null) { // bare minimum comparisons - if (getFilenames(fastExisting.Files).SequenceEqual(getShortenedFilenames(archive).Select(p => p.shortened))) - return fastExisting; + // + // note that this should really be checking filesizes on disk (of existing files) for some degree of sanity. + // or alternatively doing a faster hash check. either of these require database changes and reprocessing of existing files. + if (getFilenames(existing.Files).SequenceEqual(getShortenedFilenames(archive).Select(p => p.shortened).OrderBy(f => f))) + return existing; } } @@ -411,7 +418,8 @@ namespace osu.Game.Database { if (!write.IsTransactionLeader) throw new InvalidOperationException($"Ensure there is no parent transaction so errors can correctly be handled by {this}"); - var existing = CheckForExisting(item); + if (!checkedExisting) + existing = CheckForExisting(item); if (existing != null) { From f2164049526e8c94e40ad4db5ec96edecdc257d9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 27 Jun 2021 20:22:48 +0900 Subject: [PATCH 112/168] Fix missing undelete call on using existing --- osu.Game/Database/ArchiveModelManager.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index ed5b54f446..7ea6fe067c 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -389,7 +389,10 @@ namespace osu.Game.Database // note that this should really be checking filesizes on disk (of existing files) for some degree of sanity. // or alternatively doing a faster hash check. either of these require database changes and reprocessing of existing files. if (getFilenames(existing.Files).SequenceEqual(getShortenedFilenames(archive).Select(p => p.shortened).OrderBy(f => f))) + { + Undelete(existing); return existing; + } } } From cd9aa38d3debcc69730ef7e89f5330d463b2c634 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 27 Jun 2021 20:24:16 +0900 Subject: [PATCH 113/168] Add back ignore cases for intentionally broken tests --- osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs index 2087bdf144..79c85f6c61 100644 --- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs @@ -192,6 +192,7 @@ namespace osu.Game.Tests.Beatmaps.IO } [Test] + [Ignore("intentionally broken by import optimisations")] public async Task TestImportThenImportWithChangedFile() { using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest))) @@ -294,6 +295,7 @@ namespace osu.Game.Tests.Beatmaps.IO } [Test] + [Ignore("intentionally broken by import optimisations")] public async Task TestImportCorruptThenImport() { // unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. From f470b7095d9e6aafa201a9abf1ba098093ede571 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 27 Jun 2021 20:36:01 +0900 Subject: [PATCH 114/168] Move private method down in class --- osu.Game/Database/ArchiveModelManager.cs | 32 ++++++++++++------------ 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index 7ea6fe067c..9b89037334 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -342,22 +342,6 @@ namespace osu.Game.Database return item.Hash; } - private string computeHashFast(ArchiveReader reader) - { - MemoryStream hashable = new MemoryStream(); - - foreach (var file in reader.Filenames.Where(f => HashableFileTypes.Any(ext => f.EndsWith(ext, StringComparison.OrdinalIgnoreCase))).OrderBy(f => f)) - { - using (Stream s = reader.GetStream(file)) - s.CopyTo(hashable); - } - - if (hashable.Length > 0) - return hashable.ComputeSHA2Hash(); - - return reader.Name.ComputeSHA2Hash(); - } - /// /// Silently import an item from a . /// @@ -686,6 +670,22 @@ namespace osu.Game.Database } } + private string computeHashFast(ArchiveReader reader) + { + MemoryStream hashable = new MemoryStream(); + + foreach (var file in reader.Filenames.Where(f => HashableFileTypes.Any(ext => f.EndsWith(ext, StringComparison.OrdinalIgnoreCase))).OrderBy(f => f)) + { + using (Stream s = reader.GetStream(file)) + s.CopyTo(hashable); + } + + if (hashable.Length > 0) + return hashable.ComputeSHA2Hash(); + + return reader.Name.ComputeSHA2Hash(); + } + /// /// Create all required s for the provided archive, adding them to the global file store. /// From e755dcc34dffbaa8b6415fa9adf5fe61580cf940 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 27 Jun 2021 20:37:12 +0900 Subject: [PATCH 115/168] Add log method for new flow --- osu.Game/Database/ArchiveModelManager.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index 9b89037334..e1202ed95f 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -374,6 +374,7 @@ namespace osu.Game.Database // or alternatively doing a faster hash check. either of these require database changes and reprocessing of existing files. if (getFilenames(existing.Files).SequenceEqual(getShortenedFilenames(archive).Select(p => p.shortened).OrderBy(f => f))) { + LogForModel(item, $"Found existing (optimised) {HumanisedModelName} for {item} (ID {existing.ID}) – skipping import."); Undelete(existing); return existing; } From c2ceb83bbb20c53edff6c4973fb45e48a984cad2 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Sun, 27 Jun 2021 15:16:40 +0200 Subject: [PATCH 116/168] Move `MockNestedHitObject` to own class --- .../Editing/Checks/CheckMutedObjectsTest.cs | 28 --------------- .../Editing/Checks/MockNestableHitObject.cs | 36 +++++++++++++++++++ 2 files changed, 36 insertions(+), 28 deletions(-) create mode 100644 osu.Game.Tests/Editing/Checks/MockNestableHitObject.cs diff --git a/osu.Game.Tests/Editing/Checks/CheckMutedObjectsTest.cs b/osu.Game.Tests/Editing/Checks/CheckMutedObjectsTest.cs index 4bfe62a64d..41a8f72305 100644 --- a/osu.Game.Tests/Editing/Checks/CheckMutedObjectsTest.cs +++ b/osu.Game.Tests/Editing/Checks/CheckMutedObjectsTest.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Linq; -using System.Threading; using NUnit.Framework; using osu.Game.Audio; using osu.Game.Beatmaps; @@ -11,7 +10,6 @@ using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Checks; using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Tests.Beatmaps; @@ -27,32 +25,6 @@ namespace osu.Game.Tests.Editing.Checks private const int volume_low = 15; private const int volume_muted = 5; - private sealed class MockNestableHitObject : HitObject, IHasDuration - { - private readonly IEnumerable toBeNested; - - public MockNestableHitObject(IEnumerable toBeNested, double startTime, double endTime) - { - this.toBeNested = toBeNested; - StartTime = startTime; - EndTime = endTime; - } - - protected override void CreateNestedHitObjects(CancellationToken cancellationToken) - { - foreach (var hitObject in toBeNested) - AddNested(hitObject); - } - - public double EndTime { get; } - - public double Duration - { - get => EndTime - StartTime; - set => throw new System.NotImplementedException(); - } - } - [SetUp] public void Setup() { diff --git a/osu.Game.Tests/Editing/Checks/MockNestableHitObject.cs b/osu.Game.Tests/Editing/Checks/MockNestableHitObject.cs new file mode 100644 index 0000000000..29938839d3 --- /dev/null +++ b/osu.Game.Tests/Editing/Checks/MockNestableHitObject.cs @@ -0,0 +1,36 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using System.Threading; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; + +namespace osu.Game.Tests.Editing.Checks +{ + public sealed class MockNestableHitObject : HitObject, IHasDuration + { + private readonly IEnumerable toBeNested; + + public MockNestableHitObject(IEnumerable toBeNested, double startTime, double endTime) + { + this.toBeNested = toBeNested; + StartTime = startTime; + EndTime = endTime; + } + + protected override void CreateNestedHitObjects(CancellationToken cancellationToken) + { + foreach (var hitObject in toBeNested) + AddNested(hitObject); + } + + public double EndTime { get; } + + public double Duration + { + get => EndTime - StartTime; + set => throw new System.NotImplementedException(); + } + } +} From 1d5bff166043e0b7b41c98603c65af3e411ec115 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Sun, 27 Jun 2021 15:26:52 +0200 Subject: [PATCH 117/168] Add concurrent hitobjects test for few hitsounds check See https://github.com/ppy/osu/pull/13669#discussion_r659314980 --- .../Editing/Checks/CheckFewHitsoundsTest.cs | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/osu.Game.Tests/Editing/Checks/CheckFewHitsoundsTest.cs b/osu.Game.Tests/Editing/Checks/CheckFewHitsoundsTest.cs index 7561e94d2e..d681c3fd3d 100644 --- a/osu.Game.Tests/Editing/Checks/CheckFewHitsoundsTest.cs +++ b/osu.Game.Tests/Editing/Checks/CheckFewHitsoundsTest.cs @@ -6,6 +6,7 @@ using System.Linq; using NUnit.Framework; using osu.Game.Audio; using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Checks; using osu.Game.Rulesets.Objects; @@ -143,6 +144,35 @@ namespace osu.Game.Tests.Editing.Checks assertNoHitsounds(hitObjects); } + [Test] + public void TestConcurrentObjects() + { + var hitObjects = new List(); + + var notHitsounded = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) }; + var hitsounded = new List + { + new HitSampleInfo(HitSampleInfo.HIT_NORMAL), + new HitSampleInfo(HitSampleInfo.HIT_FINISH) + }; + + var ticks = new List(); + for (int i = 1; i < 10; ++i) + ticks.Add(new SliderTick { StartTime = 5000 * i, Samples = hitsounded }); + + var nested = new MockNestableHitObject(ticks.ToList(), 0, 50000) + { + Samples = notHitsounded + }; + nested.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); + hitObjects.Add(nested); + + for (int i = 1; i <= 6; ++i) + hitObjects.Add(new HitCircle { StartTime = 10000 * i, Samples = notHitsounded }); + + assertOk(hitObjects); + } + private void assertOk(List hitObjects) { Assert.That(check.Run(getContext(hitObjects)), Is.Empty); From a4a5325b73e8e6108137aacd40dbdf93c074f980 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Sun, 27 Jun 2021 15:39:31 +0200 Subject: [PATCH 118/168] Improve acceptable difference for repeat edges Likelihood that `spanDuration` is greater than E+7 is quite low in any realistic case, so this should work fine. --- osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.cs b/osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.cs index c743b5693e..0559a8b0cd 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.cs @@ -98,9 +98,13 @@ namespace osu.Game.Rulesets.Edit.Checks { double spanDuration = hasRepeats.Duration / hasRepeats.SpanCount(); double spans = (time - hitObject.StartTime) / spanDuration; + double acceptableDifference = 1 / spanDuration; // 1 ms of acceptable difference, as with head/tail above. - if (Precision.AlmostEquals(spans, Math.Ceiling(spans)) || Precision.AlmostEquals(spans, Math.Floor(spans))) + if (Precision.AlmostEquals(spans, Math.Ceiling(spans), acceptableDifference) || + Precision.AlmostEquals(spans, Math.Floor(spans), acceptableDifference)) + { return EdgeType.Repeat; + } } return EdgeType.None; From 9f9e96ce9ee797024585af8515338f67bc0175d6 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Sun, 27 Jun 2021 15:40:09 +0200 Subject: [PATCH 119/168] Add check for `spanDuration` <= 0 prior to division --- osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.cs b/osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.cs index 0559a8b0cd..a4ff921b7e 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.cs @@ -97,6 +97,10 @@ namespace osu.Game.Rulesets.Edit.Checks if (hitObject is IHasRepeats hasRepeats) { double spanDuration = hasRepeats.Duration / hasRepeats.SpanCount(); + if (spanDuration <= 0) + // Prevents undefined behaviour in cases like where zero/negative-length sliders/hold notes exist. + return EdgeType.None; + double spans = (time - hitObject.StartTime) / spanDuration; double acceptableDifference = 1 / spanDuration; // 1 ms of acceptable difference, as with head/tail above. From 1dbac76da5c4366bab40f1962faa40e0322d0c5b Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Sun, 27 Jun 2021 15:57:41 +0200 Subject: [PATCH 120/168] Use local variables for common sample lists --- .../Editing/Checks/CheckFewHitsoundsTest.cs | 37 +++++++------------ 1 file changed, 13 insertions(+), 24 deletions(-) diff --git a/osu.Game.Tests/Editing/Checks/CheckFewHitsoundsTest.cs b/osu.Game.Tests/Editing/Checks/CheckFewHitsoundsTest.cs index d681c3fd3d..21e274c2c0 100644 --- a/osu.Game.Tests/Editing/Checks/CheckFewHitsoundsTest.cs +++ b/osu.Game.Tests/Editing/Checks/CheckFewHitsoundsTest.cs @@ -20,10 +20,19 @@ namespace osu.Game.Tests.Editing.Checks { private CheckFewHitsounds check; + private List notHitsounded; + private List hitsounded; + [SetUp] public void Setup() { check = new CheckFewHitsounds(); + notHitsounded = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) }; + hitsounded = new List + { + new HitSampleInfo(HitSampleInfo.HIT_NORMAL), + new HitSampleInfo(HitSampleInfo.HIT_FINISH) + }; } [Test] @@ -80,10 +89,7 @@ namespace osu.Game.Tests.Editing.Checks for (int i = 0; i < 30; ++i) { - var samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) }; - - if (i % 8 == 0) - samples.Add(new HitSampleInfo(HitSampleInfo.HIT_WHISTLE)); + var samples = i % 8 == 0 ? hitsounded : notHitsounded; hitObjects.Add(new HitCircle { StartTime = 1000 * i, Samples = samples }); } @@ -98,10 +104,7 @@ namespace osu.Game.Tests.Editing.Checks for (int i = 0; i < 30; ++i) { - var samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) }; - - if (i == 0 || i == 15) - samples.Add(new HitSampleInfo(HitSampleInfo.HIT_WHISTLE)); + var samples = (i == 0 || i == 15) ? hitsounded : notHitsounded; hitObjects.Add(new HitCircle { StartTime = 1000 * i, Samples = samples }); } @@ -117,10 +120,7 @@ namespace osu.Game.Tests.Editing.Checks for (int i = 0; i < 80; ++i) { - var samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) }; - - if (i == 40) - samples.Add(new HitSampleInfo(HitSampleInfo.HIT_WHISTLE)); + var samples = i == 40 ? hitsounded : notHitsounded; hitObjects.Add(new HitCircle { StartTime = 1000 * i, Samples = samples }); } @@ -135,11 +135,7 @@ namespace osu.Game.Tests.Editing.Checks var hitObjects = new List(); for (int i = 0; i < 20; ++i) - { - var samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) }; - - hitObjects.Add(new HitCircle { StartTime = 1000 * i, Samples = samples }); - } + hitObjects.Add(new HitCircle { StartTime = 1000 * i, Samples = notHitsounded }); assertNoHitsounds(hitObjects); } @@ -149,13 +145,6 @@ namespace osu.Game.Tests.Editing.Checks { var hitObjects = new List(); - var notHitsounded = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) }; - var hitsounded = new List - { - new HitSampleInfo(HitSampleInfo.HIT_NORMAL), - new HitSampleInfo(HitSampleInfo.HIT_FINISH) - }; - var ticks = new List(); for (int i = 1; i < 10; ++i) ticks.Add(new SliderTick { StartTime = 5000 * i, Samples = hitsounded }); From b58644106c188d09802dd8d1e455633193651f5d Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Sun, 27 Jun 2021 15:58:00 +0200 Subject: [PATCH 121/168] Add nested hitobject tests for few hitsounds check --- .../Editing/Checks/CheckFewHitsoundsTest.cs | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/osu.Game.Tests/Editing/Checks/CheckFewHitsoundsTest.cs b/osu.Game.Tests/Editing/Checks/CheckFewHitsoundsTest.cs index 21e274c2c0..cf5b3a42a4 100644 --- a/osu.Game.Tests/Editing/Checks/CheckFewHitsoundsTest.cs +++ b/osu.Game.Tests/Editing/Checks/CheckFewHitsoundsTest.cs @@ -140,6 +140,38 @@ namespace osu.Game.Tests.Editing.Checks assertNoHitsounds(hitObjects); } + [Test] + public void TestNestedObjectsHitsounded() + { + var ticks = new List(); + for (int i = 1; i < 16; ++i) + ticks.Add(new SliderTick { StartTime = 1000 * i, Samples = hitsounded }); + + var nested = new MockNestableHitObject(ticks.ToList(), 0, 16000) + { + Samples = hitsounded + }; + nested.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); + + assertOk(new List { nested }); + } + + [Test] + public void TestNestedObjectsRarelyHitsounded() + { + var ticks = new List(); + for (int i = 1; i < 16; ++i) + ticks.Add(new SliderTick { StartTime = 1000 * i, Samples = i == 0 ? hitsounded : notHitsounded }); + + var nested = new MockNestableHitObject(ticks.ToList(), 0, 16000) + { + Samples = hitsounded + }; + nested.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); + + assertLongPeriodWarning(new List { nested }); + } + [Test] public void TestConcurrentObjects() { From 9a96cd4a1df625322e644fc9b708676fbe62cd04 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 28 Jun 2021 09:54:18 +0900 Subject: [PATCH 122/168] Revert "Remove comparison of online beatmap IDs during dedupe checks" This reverts commit 15af28d2a0af88ac7f167341d1d438121ccf3a62. --- .../Beatmaps/IO/ImportBeatmapTest.cs | 35 +++++++++++++++++++ osu.Game/Beatmaps/BeatmapManager.cs | 12 +++++++ osu.Game/Database/ArchiveModelManager.cs | 4 +-- 3 files changed, 49 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs index 79c85f6c61..990e75df81 100644 --- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs @@ -441,6 +441,41 @@ namespace osu.Game.Tests.Beatmaps.IO } } + [TestCase(true)] + [TestCase(false)] + public async Task TestImportThenDeleteThenImportWithOnlineIDMismatch(bool set) + { + // unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. + using (HeadlessGameHost host = new CleanRunHeadlessGameHost($"{nameof(ImportBeatmapTest)}-{set}")) + { + try + { + var osu = LoadOsuIntoHost(host); + + var imported = await LoadOszIntoOsu(osu); + + if (set) + imported.OnlineBeatmapSetID = 1234; + else + imported.Beatmaps.First().OnlineBeatmapID = 1234; + + osu.Dependencies.Get().Update(imported); + + deleteBeatmapSet(imported, osu); + + var importedSecondTime = await LoadOszIntoOsu(osu); + + // check the newly "imported" beatmap has been reimported due to mismatch (even though hashes matched) + Assert.IsTrue(imported.ID != importedSecondTime.ID); + Assert.IsTrue(imported.Beatmaps.First().ID != importedSecondTime.Beatmaps.First().ID); + } + finally + { + host.Exit(); + } + } + } + [Test] public async Task TestImportWithDuplicateBeatmapIDs() { diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 221774b018..00af06703d 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -319,6 +319,18 @@ namespace osu.Game.Beatmaps /// The first result for the provided query, or null if no results were found. public BeatmapSetInfo QueryBeatmapSet(Expression> query) => beatmaps.ConsumableItems.AsNoTracking().FirstOrDefault(query); + protected override bool CanReuseExisting(BeatmapSetInfo existing, BeatmapSetInfo import) + { + if (!base.CanReuseExisting(existing, import)) + return false; + + var existingIds = existing.Beatmaps.Select(b => b.OnlineBeatmapID).OrderBy(i => i); + var importIds = import.Beatmaps.Select(b => b.OnlineBeatmapID).OrderBy(i => i); + + // force re-import if we are not in a sane state. + return existing.OnlineBeatmapSetID == import.OnlineBeatmapSetID && existingIds.SequenceEqual(importIds); + } + /// /// Returns a list of all usable s. /// diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index e1202ed95f..84473f57fe 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -411,7 +411,7 @@ namespace osu.Game.Database if (existing != null) { - if (canReuseExisting(existing, item)) + if (CanReuseExisting(existing, item)) { Undelete(existing); LogForModel(item, $"Found existing {HumanisedModelName} for {item} (ID {existing.ID}) – skipping import."); @@ -808,7 +808,7 @@ namespace osu.Game.Database /// The existing model. /// The newly imported model. /// Whether the existing model should be restored and used. Returning false will delete the existing and force a re-import. - private bool canReuseExisting(TModel existing, TModel import) => + protected virtual bool CanReuseExisting(TModel existing, TModel import) => // for the best or worst, we copy and import files of a new import before checking whether // it is a duplicate. so to check if anything has changed, we can just compare all FileInfo IDs. getIDs(existing.Files).SequenceEqual(getIDs(import.Files)) && From 90b87cbb9eaba15ade0585a881ded8978192d871 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 28 Jun 2021 10:11:27 +0900 Subject: [PATCH 123/168] Add back unidirectional online id check --- osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs | 10 +++------- osu.Game/Beatmaps/BeatmapManager.cs | 8 ++++++++ osu.Game/Database/ArchiveModelManager.cs | 12 +++++++++++- 3 files changed, 22 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs index 990e75df81..e87b82cd2a 100644 --- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs @@ -441,9 +441,7 @@ namespace osu.Game.Tests.Beatmaps.IO } } - [TestCase(true)] - [TestCase(false)] - public async Task TestImportThenDeleteThenImportWithOnlineIDMismatch(bool set) + public async Task TestImportThenDeleteThenImportWithOnlineIDsMissing() { // unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. using (HeadlessGameHost host = new CleanRunHeadlessGameHost($"{nameof(ImportBeatmapTest)}-{set}")) @@ -454,10 +452,8 @@ namespace osu.Game.Tests.Beatmaps.IO var imported = await LoadOszIntoOsu(osu); - if (set) - imported.OnlineBeatmapSetID = 1234; - else - imported.Beatmaps.First().OnlineBeatmapID = 1234; + foreach (var b in imported.Beatmaps) + b.OnlineBeatmapID = null; osu.Dependencies.Get().Update(imported); diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 00af06703d..f854a5fecb 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -319,6 +319,14 @@ namespace osu.Game.Beatmaps /// The first result for the provided query, or null if no results were found. public BeatmapSetInfo QueryBeatmapSet(Expression> query) => beatmaps.ConsumableItems.AsNoTracking().FirstOrDefault(query); + protected override bool CanSkipImport(BeatmapSetInfo existing, BeatmapSetInfo import) + { + if (!base.CanReuseExisting(existing, import)) + return false; + + return existing.Beatmaps.Any(b => b.OnlineBeatmapID != null); + } + protected override bool CanReuseExisting(BeatmapSetInfo existing, BeatmapSetInfo import) { if (!base.CanReuseExisting(existing, import)) diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index 84473f57fe..6d8b671fd8 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -372,7 +372,8 @@ namespace osu.Game.Database // // note that this should really be checking filesizes on disk (of existing files) for some degree of sanity. // or alternatively doing a faster hash check. either of these require database changes and reprocessing of existing files. - if (getFilenames(existing.Files).SequenceEqual(getShortenedFilenames(archive).Select(p => p.shortened).OrderBy(f => f))) + if (CanSkipImport(existing, item) && + getFilenames(existing.Files).SequenceEqual(getShortenedFilenames(archive).Select(p => p.shortened).OrderBy(f => f))) { LogForModel(item, $"Found existing (optimised) {HumanisedModelName} for {item} (ID {existing.ID}) – skipping import."); Undelete(existing); @@ -801,6 +802,15 @@ namespace osu.Game.Database /// An existing model which matches the criteria to skip importing, else null. protected TModel CheckForExisting(TModel model) => model.Hash == null ? null : ModelStore.ConsumableItems.FirstOrDefault(b => b.Hash == model.Hash); + /// + /// Whether inport can be skipped after finding an existing import early in the process. + /// Only valid when is not overridden. + /// + /// The existing model. + /// The newly imported model. + /// Whether to skip this import completely. + protected virtual bool CanSkipImport(TModel existing, TModel import) => true; + /// /// After an existing is found during an import process, the default behaviour is to use/restore the existing /// item and skip the import. This method allows changing that behaviour. From 128f08ccbadaf5a2b027f88b651c8ace8a7696f9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 28 Jun 2021 10:42:28 +0900 Subject: [PATCH 124/168] Fix test oversights --- osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs index e87b82cd2a..02b1d8bcfc 100644 --- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs @@ -441,10 +441,11 @@ namespace osu.Game.Tests.Beatmaps.IO } } + [Test] public async Task TestImportThenDeleteThenImportWithOnlineIDsMissing() { // unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. - using (HeadlessGameHost host = new CleanRunHeadlessGameHost($"{nameof(ImportBeatmapTest)}-{set}")) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost($"{nameof(ImportBeatmapTest)}")) { try { From 4a557e73a781b4d71f79f797ccfe876c90b9d89c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 28 Jun 2021 10:42:42 +0900 Subject: [PATCH 125/168] Add logging to help understand existing case skips better --- osu.Game/Database/ArchiveModelManager.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index 6d8b671fd8..91ffe2966a 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -379,6 +379,8 @@ namespace osu.Game.Database Undelete(existing); return existing; } + + LogForModel(item, $"Found existing (optimised) but failed pre-check."); } } @@ -422,6 +424,7 @@ namespace osu.Game.Database return existing; } + LogForModel(item, $"Found existing but failed re-use check."); Delete(existing); ModelStore.PurgeDeletable(s => s.ID == existing.ID); } From 0cceef8da50264bbfa0c539e183acd04ee1d6e68 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Mon, 28 Jun 2021 11:00:07 +0800 Subject: [PATCH 126/168] Moved the `string` to `int?` conversion logic into `SettingsNumberBox` --- .../Screens/Editors/RoundEditorScreen.cs | 12 ++-- .../Screens/Editors/SeedingEditorScreen.cs | 14 ++-- .../Screens/Editors/TeamEditorScreen.cs | 12 ++-- .../Overlays/Settings/SettingsNumberBox.cs | 59 +++++++++++++-- osu.Game/Rulesets/Mods/ModRandom.cs | 3 +- osu.Game/Rulesets/Mods/SeedSettingsControl.cs | 72 ------------------- 6 files changed, 72 insertions(+), 100 deletions(-) delete mode 100644 osu.Game/Rulesets/Mods/SeedSettingsControl.cs diff --git a/osu.Game.Tournament/Screens/Editors/RoundEditorScreen.cs b/osu.Game.Tournament/Screens/Editors/RoundEditorScreen.cs index 069ddfa4db..e91eed420e 100644 --- a/osu.Game.Tournament/Screens/Editors/RoundEditorScreen.cs +++ b/osu.Game.Tournament/Screens/Editors/RoundEditorScreen.cs @@ -147,7 +147,7 @@ namespace osu.Game.Tournament.Screens.Editors [Resolved] protected IAPIProvider API { get; private set; } - private readonly Bindable beatmapId = new Bindable(); + private readonly Bindable beatmapId = new Bindable(); private readonly Bindable mods = new Bindable(); @@ -220,14 +220,12 @@ namespace osu.Game.Tournament.Screens.Editors [BackgroundDependencyLoader] private void load(RulesetStore rulesets) { - beatmapId.Value = Model.ID.ToString(); - beatmapId.BindValueChanged(idString => + beatmapId.Value = Model.ID; + beatmapId.BindValueChanged(idInt => { - int.TryParse(idString.NewValue, out var parsed); + Model.ID = idInt.NewValue ?? 0; - Model.ID = parsed; - - if (idString.NewValue != idString.OldValue) + if (idInt.NewValue != idInt.OldValue) Model.BeatmapInfo = null; if (Model.BeatmapInfo != null) diff --git a/osu.Game.Tournament/Screens/Editors/SeedingEditorScreen.cs b/osu.Game.Tournament/Screens/Editors/SeedingEditorScreen.cs index 7bd8d3f6a0..4b50d74dc8 100644 --- a/osu.Game.Tournament/Screens/Editors/SeedingEditorScreen.cs +++ b/osu.Game.Tournament/Screens/Editors/SeedingEditorScreen.cs @@ -147,7 +147,7 @@ namespace osu.Game.Tournament.Screens.Editors [Resolved] protected IAPIProvider API { get; private set; } - private readonly Bindable beatmapId = new Bindable(); + private readonly Bindable beatmapId = new Bindable(); private readonly Bindable score = new Bindable(); @@ -228,16 +228,12 @@ namespace osu.Game.Tournament.Screens.Editors [BackgroundDependencyLoader] private void load(RulesetStore rulesets) { - beatmapId.Value = Model.ID.ToString(); - beatmapId.BindValueChanged(idString => + beatmapId.Value = Model.ID; + beatmapId.BindValueChanged(idInt => { - int parsed; + Model.ID = idInt.NewValue ?? 0; - int.TryParse(idString.NewValue, out parsed); - - Model.ID = parsed; - - if (idString.NewValue != idString.OldValue) + if (idInt.NewValue != idInt.OldValue) Model.BeatmapInfo = null; if (Model.BeatmapInfo != null) diff --git a/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs b/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs index aa1be143ea..22ec2d3398 100644 --- a/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs +++ b/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs @@ -214,7 +214,7 @@ namespace osu.Game.Tournament.Screens.Editors [Resolved] private TournamentGameBase game { get; set; } - private readonly Bindable userId = new Bindable(); + private readonly Bindable userId = new Bindable(); private readonly Container drawableContainer; @@ -278,14 +278,12 @@ namespace osu.Game.Tournament.Screens.Editors [BackgroundDependencyLoader] private void load() { - userId.Value = user.Id.ToString(); - userId.BindValueChanged(idString => + userId.Value = user.Id; + userId.BindValueChanged(idInt => { - int.TryParse(idString.NewValue, out var parsed); + user.Id = idInt.NewValue ?? 0; - user.Id = parsed; - - if (idString.NewValue != idString.OldValue) + if (idInt.NewValue != idInt.OldValue) user.Username = string.Empty; if (!string.IsNullOrEmpty(user.Username)) diff --git a/osu.Game/Overlays/Settings/SettingsNumberBox.cs b/osu.Game/Overlays/Settings/SettingsNumberBox.cs index d4d1fc8610..bc86f2c6dc 100644 --- a/osu.Game/Overlays/Settings/SettingsNumberBox.cs +++ b/osu.Game/Overlays/Settings/SettingsNumberBox.cs @@ -1,17 +1,68 @@ // 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.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.UserInterface; namespace osu.Game.Overlays.Settings { - public class SettingsNumberBox : SettingsItem + public class SettingsNumberBox : SettingsItem { - protected override Drawable CreateControl() => new OutlinedNumberBox + protected override Drawable CreateControl() => new NumberControl { - Margin = new MarginPadding { Top = 5 }, RelativeSizeAxes = Axes.X, - CommitOnFocusLost = true + Margin = new MarginPadding { Top = 5 } }; + + private sealed class NumberControl : CompositeDrawable, IHasCurrentValue + { + private readonly BindableWithCurrent current = new BindableWithCurrent(); + + private readonly OutlinedNumberBox numberBox; + + public Bindable Current + { + get => current; + set + { + current.Current = value; + numberBox.Text = value.Value.ToString(); + } + } + + public NumberControl() + { + AutoSizeAxes = Axes.Y; + + InternalChildren = new[] + { + numberBox = new OutlinedNumberBox + { + Margin = new MarginPadding { Top = 5 }, + RelativeSizeAxes = Axes.X, + CommitOnFocusLost = true + } + }; + + numberBox.Current.BindValueChanged(e => + { + int? value = null; + + if (int.TryParse(e.NewValue, out var intVal)) + value = intVal; + + current.Value = value; + }); + } + + protected override void Update() + { + base.Update(); + if (Current.Value == null) + numberBox.Current.Value = ""; + } + } } } diff --git a/osu.Game/Rulesets/Mods/ModRandom.cs b/osu.Game/Rulesets/Mods/ModRandom.cs index 61297c162d..1f7742b075 100644 --- a/osu.Game/Rulesets/Mods/ModRandom.cs +++ b/osu.Game/Rulesets/Mods/ModRandom.cs @@ -5,6 +5,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics.Sprites; using osu.Game.Configuration; using osu.Game.Graphics; +using osu.Game.Overlays.Settings; namespace osu.Game.Rulesets.Mods { @@ -16,7 +17,7 @@ namespace osu.Game.Rulesets.Mods public override IconUsage? Icon => OsuIcon.Dice; public override double ScoreMultiplier => 1; - [SettingSource("Seed", "Use a custom seed instead of a random one", SettingControlType = typeof(SeedSettingsControl))] + [SettingSource("Seed", "Use a custom seed instead of a random one", SettingControlType = typeof(SettingsNumberBox))] public Bindable Seed { get; } = new Bindable { Default = null, diff --git a/osu.Game/Rulesets/Mods/SeedSettingsControl.cs b/osu.Game/Rulesets/Mods/SeedSettingsControl.cs deleted file mode 100644 index 1eaf31874b..0000000000 --- a/osu.Game/Rulesets/Mods/SeedSettingsControl.cs +++ /dev/null @@ -1,72 +0,0 @@ -// 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.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.UserInterface; -using osu.Game.Overlays.Settings; - -namespace osu.Game.Rulesets.Mods -{ - /// - /// A settings control for use by mods which have a customisable seed value. - /// - public class SeedSettingsControl : SettingsItem - { - protected override Drawable CreateControl() => new SeedControl - { - RelativeSizeAxes = Axes.X, - Margin = new MarginPadding { Top = 5 } - }; - - private sealed class SeedControl : CompositeDrawable, IHasCurrentValue - { - private readonly BindableWithCurrent current = new BindableWithCurrent(); - - public Bindable Current - { - get => current; - set - { - current.Current = value; - seedNumberBox.Text = value.Value.ToString(); - } - } - - private readonly OutlinedNumberBox seedNumberBox; - - public SeedControl() - { - AutoSizeAxes = Axes.Y; - - InternalChildren = new[] - { - seedNumberBox = new OutlinedNumberBox - { - Margin = new MarginPadding { Top = 5 }, - RelativeSizeAxes = Axes.X, - CommitOnFocusLost = true - } - }; - - seedNumberBox.Current.BindValueChanged(e => - { - int? value = null; - - if (int.TryParse(e.NewValue, out var intVal)) - value = intVal; - - current.Value = value; - }); - } - - protected override void Update() - { - base.Update(); - if (Current.Value == null) - seedNumberBox.Current.Value = ""; - } - } - } -} From 4d6002ab889e449313f2fe6a8d6718377640a9f4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 28 Jun 2021 12:13:11 +0900 Subject: [PATCH 127/168] Remove redundant string interpolation (and mark all local logging strings as verbatim) --- osu.Game/Database/ArchiveModelManager.cs | 30 ++++++++++++------------ 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index 91ffe2966a..5446c3b851 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -78,7 +78,7 @@ namespace osu.Game.Database private readonly Bindable> itemRemoved = new Bindable>(); - public virtual IEnumerable HandledExtensions => new[] { ".zip" }; + public virtual IEnumerable HandledExtensions => new[] { @".zip" }; protected readonly FileStore Files; @@ -99,7 +99,7 @@ namespace osu.Game.Database ModelStore.ItemUpdated += item => handleEvent(() => itemUpdated.Value = new WeakReference(item)); ModelStore.ItemRemoved += item => handleEvent(() => itemRemoved.Value = new WeakReference(item)); - exportStorage = storage.GetStorageForDirectory("exports"); + exportStorage = storage.GetStorageForDirectory(@"exports"); Files = new FileStore(contextFactory, storage); @@ -282,7 +282,7 @@ namespace osu.Game.Database } catch (Exception e) { - LogForModel(model, $"Model creation of {archive.Name} failed.", e); + LogForModel(model, @$"Model creation of {archive.Name} failed.", e); return null; } @@ -375,12 +375,12 @@ namespace osu.Game.Database if (CanSkipImport(existing, item) && getFilenames(existing.Files).SequenceEqual(getShortenedFilenames(archive).Select(p => p.shortened).OrderBy(f => f))) { - LogForModel(item, $"Found existing (optimised) {HumanisedModelName} for {item} (ID {existing.ID}) – skipping import."); + LogForModel(item, @$"Found existing (optimised) {HumanisedModelName} for {item} (ID {existing.ID}) – skipping import."); Undelete(existing); return existing; } - LogForModel(item, $"Found existing (optimised) but failed pre-check."); + LogForModel(item, @"Found existing (optimised) but failed pre-check."); } } @@ -389,14 +389,14 @@ namespace osu.Game.Database if (!Delete(item)) { // We may have not yet added the model to the underlying table, but should still clean up files. - LogForModel(item, "Dereferencing files for incomplete import."); + LogForModel(item, @"Dereferencing files for incomplete import."); Files.Dereference(item.Files.Select(f => f.FileInfo).ToArray()); } } try { - LogForModel(item, "Beginning import..."); + LogForModel(item, @"Beginning import..."); item.Files = archive != null ? createFileInfos(archive, Files) : new List(); item.Hash = ComputeHash(item, archive); @@ -407,7 +407,7 @@ namespace osu.Game.Database { try { - if (!write.IsTransactionLeader) throw new InvalidOperationException($"Ensure there is no parent transaction so errors can correctly be handled by {this}"); + if (!write.IsTransactionLeader) throw new InvalidOperationException(@$"Ensure there is no parent transaction so errors can correctly be handled by {this}"); if (!checkedExisting) existing = CheckForExisting(item); @@ -417,14 +417,14 @@ namespace osu.Game.Database if (CanReuseExisting(existing, item)) { Undelete(existing); - LogForModel(item, $"Found existing {HumanisedModelName} for {item} (ID {existing.ID}) – skipping import."); + LogForModel(item, @$"Found existing {HumanisedModelName} for {item} (ID {existing.ID}) – skipping import."); // existing item will be used; rollback new import and exit early. rollback(); flushEvents(true); return existing; } - LogForModel(item, $"Found existing but failed re-use check."); + LogForModel(item, @"Found existing but failed re-use check."); Delete(existing); ModelStore.PurgeDeletable(s => s.ID == existing.ID); } @@ -441,12 +441,12 @@ namespace osu.Game.Database } } - LogForModel(item, "Import successfully completed!"); + LogForModel(item, @"Import successfully completed!"); } catch (Exception e) { if (!(e is TaskCanceledException)) - LogForModel(item, "Database import or population failed and has been rolled back.", e); + LogForModel(item, @"Database import or population failed and has been rolled back.", e); rollback(); flushEvents(false); @@ -466,7 +466,7 @@ namespace osu.Game.Database var retrievedItem = ModelStore.ConsumableItems.FirstOrDefault(s => s.ID == item.ID); if (retrievedItem == null) - throw new ArgumentException("Specified model could not be found", nameof(item)); + throw new ArgumentException(@"Specified model could not be found", nameof(item)); using (var outputStream = exportStorage.GetStream($"{getValidFilename(item.ToString())}{HandledExtensions.First()}", FileAccess.Write, FileMode.Create)) ExportModelTo(retrievedItem, outputStream); @@ -757,7 +757,7 @@ namespace osu.Game.Database { string fullPath = storage.GetFullPath(ImportFromStablePath); - Logger.Log($"Folder \"{fullPath}\" not available in the target osu!stable installation to import {HumanisedModelName}s.", LoggingTarget.Information, LogLevel.Error); + Logger.Log(@$"Folder ""{fullPath}"" not available in the target osu!stable installation to import {HumanisedModelName}s.", LoggingTarget.Information, LogLevel.Error); return Task.CompletedTask; } @@ -841,7 +841,7 @@ namespace osu.Game.Database private DbSet queryModel() => ContextFactory.Get().Set(); - protected virtual string HumanisedModelName => $"{typeof(TModel).Name.Replace("Info", "").ToLower()}"; + protected virtual string HumanisedModelName => $"{typeof(TModel).Name.Replace(@"Info", "").ToLower()}"; #region Event handling / delaying From 3d19364a7121702bb9b6c61068e82fb7223129af Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Mon, 28 Jun 2021 11:20:00 +0800 Subject: [PATCH 128/168] Use `BindValueChanged` instead of setting the value in property setter --- .../Overlays/Settings/SettingsNumberBox.cs | 22 +++++++------------ 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/osu.Game/Overlays/Settings/SettingsNumberBox.cs b/osu.Game/Overlays/Settings/SettingsNumberBox.cs index bc86f2c6dc..de3e5a6bb0 100644 --- a/osu.Game/Overlays/Settings/SettingsNumberBox.cs +++ b/osu.Game/Overlays/Settings/SettingsNumberBox.cs @@ -20,22 +20,18 @@ namespace osu.Game.Overlays.Settings { private readonly BindableWithCurrent current = new BindableWithCurrent(); - private readonly OutlinedNumberBox numberBox; - public Bindable Current { - get => current; - set - { - current.Current = value; - numberBox.Text = value.Value.ToString(); - } + get => current.Current; + set => current.Current = value; } public NumberControl() { AutoSizeAxes = Axes.Y; + OutlinedNumberBox numberBox; + InternalChildren = new[] { numberBox = new OutlinedNumberBox @@ -55,13 +51,11 @@ namespace osu.Game.Overlays.Settings current.Value = value; }); - } - protected override void Update() - { - base.Update(); - if (Current.Value == null) - numberBox.Current.Value = ""; + Current.BindValueChanged(e => + { + numberBox.Current.Value = e.NewValue?.ToString(); + }); } } } From a3946a1265fbee6968daf04dbf563b95e06330b8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 28 Jun 2021 14:07:21 +0900 Subject: [PATCH 129/168] Fix typo in newly added xmldoc Co-authored-by: Salman Ahmed --- osu.Game/Database/ArchiveModelManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index 5446c3b851..3798c0c6ae 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -311,7 +311,7 @@ namespace osu.Game.Database /// /// Whether the implementation overrides with a custom implementation. - /// Custom has implementations must bypass the early exit in the import flow (see usage). + /// Custom hash implementations must bypass the early exit in the import flow (see usage). /// protected virtual bool HasCustomHashFunction => false; From e387feb1d6261b491246e014de199e921a290103 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 28 Jun 2021 14:39:55 +0900 Subject: [PATCH 130/168] Add inline comment mentioning why `CreateChildDependencies` is being used in this instance --- osu.Game/Skinning/RulesetSkinProvidingContainer.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs index abf5cb040a..cb8b0fb3c8 100644 --- a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs +++ b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs @@ -47,11 +47,11 @@ namespace osu.Game.Skinning protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) { parentSource = parent.Get(); - - UpdateSkinSources(); - parentSource.SourceChanged += OnSourceChanged; + // ensure sources are populated and ready for use before childrens' asynchronous load flow. + UpdateSkinSources(); + return base.CreateChildDependencies(parent); } From 73bd88cb318ae1c8b4690a8398587bb87f83b3ee Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 28 Jun 2021 14:44:52 +0900 Subject: [PATCH 131/168] Simplify caching in test --- .../Rulesets/TestSceneRulesetSkinProvidingContainer.cs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs b/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs index 0dde0a8194..25619de323 100644 --- a/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs +++ b/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs @@ -24,12 +24,8 @@ namespace osu.Game.Tests.Rulesets protected override Ruleset CreateRuleset() => new TestSceneRulesetDependencies.TestRuleset(); - protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) - { - var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); - dependencies.CacheAs(new TestSkinProvider()); - return dependencies; - } + [Cached(typeof(ISkinSource))] + private readonly ISkinSource testSource = new TestSkinProvider(); [Test] public void TestEarlyAddedSkinRequester() From f777741ca788c3ea1a83830cd5db7286f943e67f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 28 Jun 2021 14:51:27 +0900 Subject: [PATCH 132/168] Simplify instantiation --- osu.Game/Skinning/RulesetSkinProvidingContainer.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs index 8a807eff21..a1db4ba6d1 100644 --- a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs +++ b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs @@ -96,12 +96,14 @@ namespace osu.Game.Skinning { int defaultSkinIndex = SkinSources.IndexOf(skinManager.DefaultSkin); + var rulesetResources = new ResourcesSkin(resources, host, audio); + if (defaultSkinIndex >= 0) - SkinSources.Insert(defaultSkinIndex, new ResourcesSkin(resources, host, audio)); + SkinSources.Insert(defaultSkinIndex, rulesetResources); else { // Tests may potentially override the SkinManager with another source that doesn't include it in AllSources. - SkinSources.Add(new ResourcesSkin(resources, host, audio)); + SkinSources.Add(rulesetResources); } } } From c281e43cd8beb91f50e0853579fab33699718502 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 28 Jun 2021 15:04:14 +0900 Subject: [PATCH 133/168] Remove `Dispose()` special case and add explicit exception to make debugging issues non-deadlock --- osu.Game/Database/RealmContextFactory.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index 4d81f8676f..2f70d8af22 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -97,6 +97,9 @@ namespace osu.Game.Database { try { + if (IsDisposed) + throw new InvalidOperationException(@"Attempted to retrieve a context after the factor has already been disposed."); + blockingLock.Wait(); contexts_created.Value++; @@ -128,11 +131,8 @@ namespace osu.Game.Database { base.Dispose(isDisposing); - // In the standard case, operations will already be blocked by the Update thread "pausing" from GameHost exit. - // This avoids waiting (forever) on an already entered semaphore. - if (context != null || active_usages.Value > 0) - BlockAllOperations(); - + // intentionally block all operations indefinitely. this ensures that nothing can start consuming a new context after disposal. + BlockAllOperations(); blockingLock?.Dispose(); } From 7dd566dc46639a28e86fda9bd0ba13030aaf74ee Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 28 Jun 2021 15:08:49 +0900 Subject: [PATCH 134/168] Add null check for safety MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- osu.Game/OsuGameBase.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index dec738e5b3..bf1b449292 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -370,7 +370,7 @@ namespace osu.Game switch (state.NewValue) { case GameThreadState.Running: - blocking.Dispose(); + blocking?.Dispose(); blocking = null; break; From 692f24437ef5ec3afd2eed87143da90bd621c5fd Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 28 Jun 2021 09:11:28 +0300 Subject: [PATCH 135/168] Maintain ruleset resources skin across multiple source changes --- .../Skinning/RulesetSkinProvidingContainer.cs | 36 +++++++------------ 1 file changed, 13 insertions(+), 23 deletions(-) diff --git a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs index a1db4ba6d1..076f8b03dc 100644 --- a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs +++ b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs @@ -46,21 +46,20 @@ namespace osu.Game.Skinning }; } - [Resolved] - private GameHost host { get; set; } - - [Resolved] - private AudioManager audio { get; set; } - [Resolved] private SkinManager skinManager { get; set; } [Resolved] private ISkinSource skinSource { get; set; } + private ResourcesSkin rulesetResourcesSkin; + [BackgroundDependencyLoader] - private void load() + private void load(GameHost host, AudioManager audio) { + if (Ruleset.CreateResourceStore() is IResourceStore resources) + rulesetResourcesSkin = new ResourcesSkin(resources, host, audio); + UpdateSkins(); skinSource.SourceChanged += OnSourceChanged; } @@ -73,9 +72,6 @@ namespace osu.Game.Skinning protected virtual void UpdateSkins() { - foreach (var resourcesSkin in SkinSources.OfType()) - resourcesSkin.Dispose(); - SkinSources.Clear(); foreach (var skin in skinSource.AllSources) @@ -92,20 +88,12 @@ namespace osu.Game.Skinning } } - if (Ruleset.CreateResourceStore() is IResourceStore resources) - { - int defaultSkinIndex = SkinSources.IndexOf(skinManager.DefaultSkin); + int defaultSkinIndex = SkinSources.IndexOf(skinManager.DefaultSkin); - var rulesetResources = new ResourcesSkin(resources, host, audio); - - if (defaultSkinIndex >= 0) - SkinSources.Insert(defaultSkinIndex, rulesetResources); - else - { - // Tests may potentially override the SkinManager with another source that doesn't include it in AllSources. - SkinSources.Add(rulesetResources); - } - } + if (defaultSkinIndex >= 0) + SkinSources.Insert(defaultSkinIndex, rulesetResourcesSkin); + else + SkinSources.Add(rulesetResourcesSkin); } protected ISkin GetLegacyRulesetTransformedSkin(ISkin legacySkin) @@ -126,6 +114,8 @@ namespace osu.Game.Skinning if (skinSource != null) skinSource.SourceChanged -= OnSourceChanged; + + rulesetResourcesSkin?.Dispose(); } } } From f598de4cdb0069109abc42485349b2026ea323f8 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 28 Jun 2021 09:18:29 +0300 Subject: [PATCH 136/168] ResourcesSkin -> ResourceStoreBackedSkin --- .../{ResourcesSkin.cs => ResourceStoreBackedSkin.cs} | 4 ++-- osu.Game/Skinning/RulesetSkinProvidingContainer.cs | 5 ++--- 2 files changed, 4 insertions(+), 5 deletions(-) rename osu.Game/Skinning/{ResourcesSkin.cs => ResourceStoreBackedSkin.cs} (91%) diff --git a/osu.Game/Skinning/ResourcesSkin.cs b/osu.Game/Skinning/ResourceStoreBackedSkin.cs similarity index 91% rename from osu.Game/Skinning/ResourcesSkin.cs rename to osu.Game/Skinning/ResourceStoreBackedSkin.cs index 90020495c3..f041b82cf4 100644 --- a/osu.Game/Skinning/ResourcesSkin.cs +++ b/osu.Game/Skinning/ResourceStoreBackedSkin.cs @@ -19,12 +19,12 @@ namespace osu.Game.Skinning /// /// An that uses an underlying with namespaces for resources retrieval. /// - public class ResourcesSkin : ISkin, IDisposable + public class ResourceStoreBackedSkin : ISkin, IDisposable { private readonly TextureStore textures; private readonly ISampleStore samples; - public ResourcesSkin(IResourceStore resources, GameHost host, AudioManager audio) + public ResourceStoreBackedSkin(IResourceStore resources, GameHost host, AudioManager audio) { textures = new TextureStore(host.CreateTextureLoaderStore(new NamespacedResourceStore(resources, @"Textures"))); samples = audio.GetSampleStore(new NamespacedResourceStore(resources, @"Samples")); diff --git a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs index 076f8b03dc..d35e9c1ff3 100644 --- a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs +++ b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.Linq; using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Audio; @@ -52,13 +51,13 @@ namespace osu.Game.Skinning [Resolved] private ISkinSource skinSource { get; set; } - private ResourcesSkin rulesetResourcesSkin; + private ResourceStoreBackedSkin rulesetResourcesSkin; [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { if (Ruleset.CreateResourceStore() is IResourceStore resources) - rulesetResourcesSkin = new ResourcesSkin(resources, host, audio); + rulesetResourcesSkin = new ResourceStoreBackedSkin(resources, host, audio); UpdateSkins(); skinSource.SourceChanged += OnSourceChanged; From 88c6143aaeedc656e9fe81d2ea2efa7ce8069209 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 28 Jun 2021 15:23:19 +0900 Subject: [PATCH 137/168] Rename variables in line with standards --- osu.Game.Tournament/Screens/Editors/RoundEditorScreen.cs | 6 +++--- osu.Game.Tournament/Screens/Editors/SeedingEditorScreen.cs | 6 +++--- osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/osu.Game.Tournament/Screens/Editors/RoundEditorScreen.cs b/osu.Game.Tournament/Screens/Editors/RoundEditorScreen.cs index e91eed420e..27ad6650d1 100644 --- a/osu.Game.Tournament/Screens/Editors/RoundEditorScreen.cs +++ b/osu.Game.Tournament/Screens/Editors/RoundEditorScreen.cs @@ -221,11 +221,11 @@ namespace osu.Game.Tournament.Screens.Editors private void load(RulesetStore rulesets) { beatmapId.Value = Model.ID; - beatmapId.BindValueChanged(idInt => + beatmapId.BindValueChanged(id => { - Model.ID = idInt.NewValue ?? 0; + Model.ID = id.NewValue ?? 0; - if (idInt.NewValue != idInt.OldValue) + if (id.NewValue != id.OldValue) Model.BeatmapInfo = null; if (Model.BeatmapInfo != null) diff --git a/osu.Game.Tournament/Screens/Editors/SeedingEditorScreen.cs b/osu.Game.Tournament/Screens/Editors/SeedingEditorScreen.cs index 4b50d74dc8..6418bf97da 100644 --- a/osu.Game.Tournament/Screens/Editors/SeedingEditorScreen.cs +++ b/osu.Game.Tournament/Screens/Editors/SeedingEditorScreen.cs @@ -229,11 +229,11 @@ namespace osu.Game.Tournament.Screens.Editors private void load(RulesetStore rulesets) { beatmapId.Value = Model.ID; - beatmapId.BindValueChanged(idInt => + beatmapId.BindValueChanged(id => { - Model.ID = idInt.NewValue ?? 0; + Model.ID = id.NewValue ?? 0; - if (idInt.NewValue != idInt.OldValue) + if (id.NewValue != id.OldValue) Model.BeatmapInfo = null; if (Model.BeatmapInfo != null) diff --git a/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs b/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs index 22ec2d3398..0d2e64f300 100644 --- a/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs +++ b/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs @@ -279,11 +279,11 @@ namespace osu.Game.Tournament.Screens.Editors private void load() { userId.Value = user.Id; - userId.BindValueChanged(idInt => + userId.BindValueChanged(id => { - user.Id = idInt.NewValue ?? 0; + user.Id = id.NewValue ?? 0; - if (idInt.NewValue != idInt.OldValue) + if (id.NewValue != id.OldValue) user.Username = string.Empty; if (!string.IsNullOrEmpty(user.Username)) From eeb56970ef036f6dedebe72e57d218410d5b7b88 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 28 Jun 2021 15:24:55 +0900 Subject: [PATCH 138/168] Make `OutlinedNumberBox` private and nested again --- osu.Game/Overlays/Settings/OutlinedNumberBox.cs | 10 ---------- osu.Game/Overlays/Settings/SettingsNumberBox.cs | 5 +++++ 2 files changed, 5 insertions(+), 10 deletions(-) delete mode 100644 osu.Game/Overlays/Settings/OutlinedNumberBox.cs diff --git a/osu.Game/Overlays/Settings/OutlinedNumberBox.cs b/osu.Game/Overlays/Settings/OutlinedNumberBox.cs deleted file mode 100644 index 6fcadc09e1..0000000000 --- a/osu.Game/Overlays/Settings/OutlinedNumberBox.cs +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -namespace osu.Game.Overlays.Settings -{ - public class OutlinedNumberBox : OutlinedTextBox - { - protected override bool CanAddCharacter(char character) => char.IsNumber(character); - } -} diff --git a/osu.Game/Overlays/Settings/SettingsNumberBox.cs b/osu.Game/Overlays/Settings/SettingsNumberBox.cs index de3e5a6bb0..2fbe522479 100644 --- a/osu.Game/Overlays/Settings/SettingsNumberBox.cs +++ b/osu.Game/Overlays/Settings/SettingsNumberBox.cs @@ -58,5 +58,10 @@ namespace osu.Game.Overlays.Settings }); } } + + private class OutlinedNumberBox : OutlinedTextBox + { + protected override bool CanAddCharacter(char character) => char.IsNumber(character); + } } } From e4780d4b06f9eb7b2b420114853f9b99b93481c7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 28 Jun 2021 15:29:47 +0900 Subject: [PATCH 139/168] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 3c4380e355..c845d7f276 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,7 +52,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index f91620bd25..f047859dbb 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -36,7 +36,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/osu.iOS.props b/osu.iOS.props index 22c4340ba2..304047ad12 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -93,7 +93,7 @@ - + From 779a1b322ca177632e6ba76e50cb431c40cf2600 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 28 Jun 2021 09:38:42 +0300 Subject: [PATCH 140/168] Add comment explaining insertion of ruleset skin before default skin Co-authored-by: Dean Herbert --- osu.Game/Skinning/RulesetSkinProvidingContainer.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs index d35e9c1ff3..5f0ca27170 100644 --- a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs +++ b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs @@ -89,6 +89,9 @@ namespace osu.Game.Skinning int defaultSkinIndex = SkinSources.IndexOf(skinManager.DefaultSkin); + // Ruleset resources should be given the ability to override game-wide defaults + // This is achieved by placing them before an instance of DefaultSkin. + // Note that DefaultSkin may not be present in some test scenes. if (defaultSkinIndex >= 0) SkinSources.Insert(defaultSkinIndex, rulesetResourcesSkin); else From 842f033522efc6b90ee88ff8d8684897e09d09c4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 28 Jun 2021 16:11:55 +0900 Subject: [PATCH 141/168] Remove no longer necessary exception --- osu.Game/Database/RealmContextFactory.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index 2f70d8af22..461444870e 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -44,9 +44,6 @@ namespace osu.Game.Database { get { - if (IsDisposed) - throw new InvalidOperationException($"Attempted to access {nameof(Context)} on a disposed context factory"); - if (context == null) { context = createContext(); From 90f0bc87f5002b0479ec01508584c637d042358f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 28 Jun 2021 16:12:21 +0900 Subject: [PATCH 142/168] Add safety against double disposal --- osu.Game/Database/RealmContextFactory.cs | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index 461444870e..0b0263a16b 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -126,22 +126,28 @@ namespace osu.Game.Database protected override void Dispose(bool isDisposing) { - base.Dispose(isDisposing); + if (!IsDisposed) + { + // intentionally block all operations indefinitely. this ensures that nothing can start consuming a new context after disposal. + BlockAllOperations(); + blockingLock?.Dispose(); + } - // intentionally block all operations indefinitely. this ensures that nothing can start consuming a new context after disposal. - BlockAllOperations(); - blockingLock?.Dispose(); + base.Dispose(isDisposing); } public IDisposable BlockAllOperations() { + if (IsDisposed) + throw new InvalidOperationException(@"Attempted to block operations after already disposed."); + blockingLock.Wait(); flushContexts(); return new InvokeOnDisposal(this, endBlockingSection); - } - private static void endBlockingSection(RealmContextFactory factory) => factory.blockingLock.Release(); + static void endBlockingSection(RealmContextFactory factory) => factory.blockingLock.Release(); + } private void flushContexts() { From f78cedd0e1d799aaec1562bcb1d1677c72bb5536 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 28 Jun 2021 16:14:14 +0900 Subject: [PATCH 143/168] Reorder methods and add xmldoc for `BlockAllOperations` --- osu.Game/Database/RealmContextFactory.cs | 65 ++++++++++++++---------- 1 file changed, 37 insertions(+), 28 deletions(-) diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index 0b0263a16b..6214f4ca81 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -76,10 +76,26 @@ namespace osu.Game.Database return new RealmWriteUsage(createContext(), writeComplete); } - private void writeComplete() + /// + /// Flush any active contexts and block any further writes. + /// + /// + /// This should be used in places we need to ensure no ongoing reads/writes are occurring with realm. + /// ie. to move the realm backing file to a new location. + /// + /// An which should be disposed to end the blocking section. + /// Thrown if this context is already disposed. + public IDisposable BlockAllOperations() { - Monitor.Exit(writeLock); - pending_writes.Value--; + if (IsDisposed) + throw new InvalidOperationException(@"Attempted to block operations after already disposed."); + + blockingLock.Wait(); + flushContexts(); + + return new InvokeOnDisposal(this, endBlockingSection); + + static void endBlockingSection(RealmContextFactory factory) => factory.blockingLock.Release(); } protected override void Update() @@ -113,6 +129,12 @@ namespace osu.Game.Database } } + private void writeComplete() + { + Monitor.Exit(writeLock); + pending_writes.Value--; + } + private void onMigration(Migration migration, ulong lastSchemaVersion) { switch (lastSchemaVersion) @@ -124,31 +146,6 @@ namespace osu.Game.Database } } - protected override void Dispose(bool isDisposing) - { - if (!IsDisposed) - { - // intentionally block all operations indefinitely. this ensures that nothing can start consuming a new context after disposal. - BlockAllOperations(); - blockingLock?.Dispose(); - } - - base.Dispose(isDisposing); - } - - public IDisposable BlockAllOperations() - { - if (IsDisposed) - throw new InvalidOperationException(@"Attempted to block operations after already disposed."); - - blockingLock.Wait(); - flushContexts(); - - return new InvokeOnDisposal(this, endBlockingSection); - - static void endBlockingSection(RealmContextFactory factory) => factory.blockingLock.Release(); - } - private void flushContexts() { var previousContext = context; @@ -161,6 +158,18 @@ namespace osu.Game.Database previousContext?.Dispose(); } + protected override void Dispose(bool isDisposing) + { + if (!IsDisposed) + { + // intentionally block all operations indefinitely. this ensures that nothing can start consuming a new context after disposal. + BlockAllOperations(); + blockingLock?.Dispose(); + } + + base.Dispose(isDisposing); + } + /// /// A usage of realm from an arbitrary thread. /// From d4ea73d727ec94ed118e3279bc8752758b9a73bc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 28 Jun 2021 16:17:09 +0900 Subject: [PATCH 144/168] Simplify disposal exceptions --- osu.Game/Database/RealmContextFactory.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index 6214f4ca81..71617b258d 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -84,11 +84,10 @@ namespace osu.Game.Database /// ie. to move the realm backing file to a new location. /// /// An which should be disposed to end the blocking section. - /// Thrown if this context is already disposed. public IDisposable BlockAllOperations() { if (IsDisposed) - throw new InvalidOperationException(@"Attempted to block operations after already disposed."); + throw new ObjectDisposedException(nameof(RealmContextFactory)); blockingLock.Wait(); flushContexts(); @@ -111,7 +110,7 @@ namespace osu.Game.Database try { if (IsDisposed) - throw new InvalidOperationException(@"Attempted to retrieve a context after the factor has already been disposed."); + throw new ObjectDisposedException(nameof(RealmContextFactory)); blockingLock.Wait(); From 035fe2ad4919e67be14d4fc2323d0daa10753fa2 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 28 Jun 2021 11:29:43 +0300 Subject: [PATCH 145/168] Mark ruleset skin provider test scene as headless --- .../Rulesets/TestSceneRulesetSkinProvidingContainer.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs b/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs index e060c8578a..28ad7ed6a7 100644 --- a/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs +++ b/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs @@ -10,6 +10,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.OpenGL.Textures; using osu.Framework.Graphics.Textures; +using osu.Framework.Testing; using osu.Game.Audio; using osu.Game.Rulesets; using osu.Game.Skinning; @@ -18,6 +19,7 @@ using osu.Game.Tests.Visual; namespace osu.Game.Tests.Rulesets { + [HeadlessTest] public class TestSceneRulesetSkinProvidingContainer : OsuTestScene { private SkinRequester requester; From b5c3c9d55099a47d775d51ce5e64ed4d554a3244 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 28 Jun 2021 17:37:58 +0900 Subject: [PATCH 146/168] Disable realm analytics submission --- FodyWeavers.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FodyWeavers.xml b/FodyWeavers.xml index cc07b89533..ea490e3297 100644 --- a/FodyWeavers.xml +++ b/FodyWeavers.xml @@ -1,3 +1,3 @@  - + \ No newline at end of file From 7197998a104d0e25db060cf6adbbb4f14870bf64 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 28 Jun 2021 12:43:12 +0300 Subject: [PATCH 147/168] Remove resolution to `SkinManager` and use pattern matching instead --- osu.Game/Skinning/RulesetSkinProvidingContainer.cs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs index c3d01218d7..26646d87fe 100644 --- a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs +++ b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Linq; using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Audio; @@ -45,15 +46,12 @@ namespace osu.Game.Skinning }; } - private SkinManager skinManager; private ISkinSource parentSource; private ResourceStoreBackedSkin rulesetResourcesSkin; protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) { - skinManager = parent.Get(); - parentSource = parent.Get(); parentSource.SourceChanged += OnSourceChanged; @@ -90,13 +88,13 @@ namespace osu.Game.Skinning } } - int defaultSkinIndex = SkinSources.IndexOf(skinManager.DefaultSkin); + int lastDefaultSkinIndex = SkinSources.IndexOf(SkinSources.OfType().Last()); // Ruleset resources should be given the ability to override game-wide defaults - // This is achieved by placing them before an instance of DefaultSkin. + // This is achieved by placing them before the last instance of DefaultSkin. // Note that DefaultSkin may not be present in some test scenes. - if (defaultSkinIndex >= 0) - SkinSources.Insert(defaultSkinIndex, rulesetResourcesSkin); + if (lastDefaultSkinIndex >= 0) + SkinSources.Insert(lastDefaultSkinIndex, rulesetResourcesSkin); else SkinSources.Add(rulesetResourcesSkin); } From 66fc95c1118f74fa9ce78d154862b375428afa9e Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 28 Jun 2021 12:43:55 +0300 Subject: [PATCH 148/168] Use `LastOrDefault` instead --- osu.Game/Skinning/RulesetSkinProvidingContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs index 26646d87fe..19efc66814 100644 --- a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs +++ b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs @@ -88,7 +88,7 @@ namespace osu.Game.Skinning } } - int lastDefaultSkinIndex = SkinSources.IndexOf(SkinSources.OfType().Last()); + int lastDefaultSkinIndex = SkinSources.IndexOf(SkinSources.OfType().LastOrDefault()); // Ruleset resources should be given the ability to override game-wide defaults // This is achieved by placing them before the last instance of DefaultSkin. From e4ca6a4266ce8624e189e9409128b97bd02b44bf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 29 Jun 2021 01:55:09 +0900 Subject: [PATCH 149/168] Serialise and send ruleset ID as part of score submission --- osu.Game/Scoring/ScoreInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index 3944c1d3de..4fd1d00fef 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -46,7 +46,7 @@ namespace osu.Game.Scoring [JsonIgnore] public int Combo { get; set; } // Todo: Shouldn't exist in here - [JsonIgnore] + [JsonProperty("ruleset_id")] public int RulesetID { get; set; } [JsonProperty("passed")] From a3b1e1d5fc477f54de3e359f96ea4f3ab5a90cda Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 29 Jun 2021 15:18:40 +0900 Subject: [PATCH 150/168] Check for null ruleset in FilterCriteria --- osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs index 8e59dc8579..5f135a3e90 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs @@ -93,7 +93,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components { bool matchingFilter = true; - matchingFilter &= r.Room.Playlist.Count == 0 || r.Room.Playlist.Any(i => i.Ruleset.Value.Equals(criteria.Ruleset)); + matchingFilter &= r.Room.Playlist.Count == 0 || criteria.Ruleset == null || r.Room.Playlist.Any(i => i.Ruleset.Value.Equals(criteria.Ruleset)); if (!string.IsNullOrEmpty(criteria.SearchString)) matchingFilter &= r.FilterTerms.Any(term => term.Contains(criteria.SearchString, StringComparison.InvariantCultureIgnoreCase)); From 06beeee4d8004e6d7608761f3de0ab73a5b9ee07 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 29 Jun 2021 15:39:01 +0900 Subject: [PATCH 151/168] Cleanup match header test --- .../Multiplayer/TestSceneMatchHeader.cs | 66 +++++++++---------- 1 file changed, 31 insertions(+), 35 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchHeader.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchHeader.cs index 3557bd9127..71ba5db481 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchHeader.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchHeader.cs @@ -17,43 +17,39 @@ namespace osu.Game.Tests.Visual.Multiplayer [SetUp] public new void Setup() => Schedule(() => { - SelectedRoom.Value = new Room(); + SelectedRoom.Value = new Room + { + Name = { Value = "A very awesome room" }, + Host = { Value = new User { Id = 2, Username = "peppy" } }, + Playlist = + { + new PlaylistItem + { + Beatmap = + { + Value = new BeatmapInfo + { + Metadata = new BeatmapMetadata + { + Title = "Title", + Artist = "Artist", + AuthorString = "Author", + }, + Version = "Version", + Ruleset = new OsuRuleset().RulesetInfo + } + }, + RequiredMods = + { + new OsuModDoubleTime(), + new OsuModNoFail(), + new OsuModRelax(), + } + } + } + }; Child = new Header(); }); - - [Test] - public void TestBasicRoom() - { - AddStep("set basic room", () => - { - SelectedRoom.Value.Playlist.Add(new PlaylistItem - { - Beatmap = - { - Value = new BeatmapInfo - { - Metadata = new BeatmapMetadata - { - Title = "Title", - Artist = "Artist", - AuthorString = "Author", - }, - Version = "Version", - Ruleset = new OsuRuleset().RulesetInfo - } - }, - RequiredMods = - { - new OsuModDoubleTime(), - new OsuModNoFail(), - new OsuModRelax(), - } - }); - - SelectedRoom.Value.Name.Value = "A very awesome room"; - SelectedRoom.Value.Host.Value = new User { Id = 2, Username = "peppy" }; - }); - } } } From 9132c42f875576a4d7c9e74cccb1ecc1a9592826 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 29 Jun 2021 15:58:07 +0900 Subject: [PATCH 152/168] Fix actions posted to the wrong channel --- osu.Game/Online/Chat/ChannelManager.cs | 4 ++-- osu.Game/Online/Chat/NowPlayingCommand.cs | 9 ++++++++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/osu.Game/Online/Chat/ChannelManager.cs b/osu.Game/Online/Chat/ChannelManager.cs index 8507887357..3136a3960d 100644 --- a/osu.Game/Online/Chat/ChannelManager.cs +++ b/osu.Game/Online/Chat/ChannelManager.cs @@ -225,7 +225,7 @@ namespace osu.Game.Online.Chat switch (command) { case "np": - AddInternal(new NowPlayingCommand()); + AddInternal(new NowPlayingCommand(target)); break; case "me": @@ -235,7 +235,7 @@ namespace osu.Game.Online.Chat break; } - PostMessage(content, true); + PostMessage(content, true, target); break; case "join": diff --git a/osu.Game/Online/Chat/NowPlayingCommand.cs b/osu.Game/Online/Chat/NowPlayingCommand.cs index 926709694b..f522d8a236 100644 --- a/osu.Game/Online/Chat/NowPlayingCommand.cs +++ b/osu.Game/Online/Chat/NowPlayingCommand.cs @@ -21,6 +21,13 @@ namespace osu.Game.Online.Chat [Resolved] private Bindable currentBeatmap { get; set; } + private readonly Channel target; + + public NowPlayingCommand(Channel target) + { + this.target = target; + } + protected override void LoadComplete() { base.LoadComplete(); @@ -48,7 +55,7 @@ namespace osu.Game.Online.Chat var beatmapString = beatmap.OnlineBeatmapID.HasValue ? $"[{api.WebsiteRootUrl}/b/{beatmap.OnlineBeatmapID} {beatmap}]" : beatmap.ToString(); - channelManager.PostMessage($"is {verb} {beatmapString}", true); + channelManager.PostMessage($"is {verb} {beatmapString}", true, target); Expire(); } } From 7a86686f40836dd58939ebbd00c0e7107f7c099c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 29 Jun 2021 16:30:40 +0900 Subject: [PATCH 153/168] Make nullable --- osu.Game/Online/Chat/NowPlayingCommand.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/Chat/NowPlayingCommand.cs b/osu.Game/Online/Chat/NowPlayingCommand.cs index f522d8a236..7756591e03 100644 --- a/osu.Game/Online/Chat/NowPlayingCommand.cs +++ b/osu.Game/Online/Chat/NowPlayingCommand.cs @@ -23,7 +23,11 @@ namespace osu.Game.Online.Chat private readonly Channel target; - public NowPlayingCommand(Channel target) + /// + /// Creates a new to post the currently-playing beatmap to a parenting . + /// + /// The target channel to post to. If null, the currently-selected channel will be posted to. + public NowPlayingCommand(Channel target = null) { this.target = target; } From ca0eaab8e21b743ad5bdc14ec3878e7658d5ca97 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 29 Jun 2021 16:30:46 +0900 Subject: [PATCH 154/168] Add test --- .../Chat/TestSceneChannelManager.cs | 104 ++++++++++++++++++ .../Online/API/Requests/PostMessageRequest.cs | 10 +- 2 files changed, 109 insertions(+), 5 deletions(-) create mode 100644 osu.Game.Tests/Chat/TestSceneChannelManager.cs diff --git a/osu.Game.Tests/Chat/TestSceneChannelManager.cs b/osu.Game.Tests/Chat/TestSceneChannelManager.cs new file mode 100644 index 0000000000..b81c39933d --- /dev/null +++ b/osu.Game.Tests/Chat/TestSceneChannelManager.cs @@ -0,0 +1,104 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics.Containers; +using osu.Framework.Testing; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests; +using osu.Game.Online.Chat; +using osu.Game.Tests.Visual; +using osu.Game.Users; + +namespace osu.Game.Tests.Chat +{ + [HeadlessTest] + public class TestSceneChannelManager : OsuTestScene + { + private ChannelManager channelManager; + private int currentMessageId; + + [SetUp] + public void Setup() => Schedule(() => + { + var container = new ChannelManagerContainer(); + Child = container; + channelManager = container.ChannelManager; + }); + + [SetUpSteps] + public void SetUpSteps() + { + AddStep("register request handling", () => + { + currentMessageId = 0; + + ((DummyAPIAccess)API).HandleRequest = req => + { + switch (req) + { + case JoinChannelRequest _: + return true; + + case PostMessageRequest postMessage: + postMessage.TriggerSuccess(new Message(++currentMessageId) + { + IsAction = postMessage.Message.IsAction, + ChannelId = postMessage.Message.ChannelId, + Content = postMessage.Message.Content, + Links = postMessage.Message.Links, + Timestamp = postMessage.Message.Timestamp, + Sender = postMessage.Message.Sender + }); + + return true; + } + + return false; + }; + }); + } + + [Test] + public void TestCommandsPostedToCorrectChannelWhenNotCurrent() + { + Channel channel1 = null; + Channel channel2 = null; + + AddStep("join 2 rooms", () => + { + channelManager.JoinChannel(channel1 = createChannel(1, ChannelType.Public)); + channelManager.JoinChannel(channel2 = createChannel(2, ChannelType.Public)); + }); + + AddStep("select channel 1", () => channelManager.CurrentChannel.Value = channel1); + + AddStep("post /me command to channel 2", () => channelManager.PostCommand("me dances", channel2)); + AddAssert("/me command received by channel 2", () => channel2.Messages.Last().Content == "dances"); + + AddStep("post /np command to channel 2", () => channelManager.PostCommand("np", channel2)); + AddAssert("/np command received by channel 2", () => channel2.Messages.Last().Content.Contains("is listening to")); + } + + private Channel createChannel(int id, ChannelType type) => new Channel(new User()) + { + Id = id, + Name = $"Channel {id}", + Topic = $"Topic of channel {id} with type {type}", + Type = type, + }; + + private class ChannelManagerContainer : CompositeDrawable + { + [Cached] + public ChannelManager ChannelManager { get; } = new ChannelManager(); + + public ChannelManagerContainer() + { + InternalChild = ChannelManager; + } + } + } +} diff --git a/osu.Game/Online/API/Requests/PostMessageRequest.cs b/osu.Game/Online/API/Requests/PostMessageRequest.cs index 84ab873acf..5d508a4cdf 100644 --- a/osu.Game/Online/API/Requests/PostMessageRequest.cs +++ b/osu.Game/Online/API/Requests/PostMessageRequest.cs @@ -9,11 +9,11 @@ namespace osu.Game.Online.API.Requests { public class PostMessageRequest : APIRequest { - private readonly Message message; + public readonly Message Message; public PostMessageRequest(Message message) { - this.message = message; + Message = message; } protected override WebRequest CreateWebRequest() @@ -21,12 +21,12 @@ namespace osu.Game.Online.API.Requests var req = base.CreateWebRequest(); req.Method = HttpMethod.Post; - req.AddParameter(@"is_action", message.IsAction.ToString().ToLowerInvariant()); - req.AddParameter(@"message", message.Content); + req.AddParameter(@"is_action", Message.IsAction.ToString().ToLowerInvariant()); + req.AddParameter(@"message", Message.Content); return req; } - protected override string Target => $@"chat/channels/{message.ChannelId}/messages"; + protected override string Target => $@"chat/channels/{Message.ChannelId}/messages"; } } From 8bcb4d13fb66a5c49101c9d10b424ca0bd3f6fed Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 29 Jun 2021 17:21:09 +0900 Subject: [PATCH 155/168] Fix multiple tests eating host exceptions --- osu.Game.Tests/ImportTest.cs | 3 ++- .../NonVisual/CustomTourneyDirectoryTest.cs | 3 ++- osu.Game.Tournament.Tests/NonVisual/IPCLocationTest.cs | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/ImportTest.cs b/osu.Game.Tests/ImportTest.cs index ea351e0d45..e888f51e98 100644 --- a/osu.Game.Tests/ImportTest.cs +++ b/osu.Game.Tests/ImportTest.cs @@ -17,7 +17,8 @@ namespace osu.Game.Tests protected virtual TestOsuGameBase LoadOsuIntoHost(GameHost host, bool withBeatmap = false) { var osu = new TestOsuGameBase(withBeatmap); - Task.Run(() => host.Run(osu)); + Task.Run(() => host.Run(osu)) + .ContinueWith(t => Assert.Fail($"Host threw exception {t.Exception}"), TaskContinuationOptions.OnlyOnFaulted); waitForOrAssert(() => osu.IsLoaded, @"osu! failed to start in a reasonable amount of time"); diff --git a/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs b/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs index 46c3b8bc3b..61f8511e3c 100644 --- a/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs +++ b/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs @@ -149,7 +149,8 @@ namespace osu.Game.Tournament.Tests.NonVisual private TournamentGameBase loadOsu(GameHost host) { var osu = new TournamentGameBase(); - Task.Run(() => host.Run(osu)); + Task.Run(() => host.Run(osu)) + .ContinueWith(t => Assert.Fail($"Host threw exception {t.Exception}"), TaskContinuationOptions.OnlyOnFaulted); waitForOrAssert(() => osu.IsLoaded, @"osu! failed to start in a reasonable amount of time"); return osu; } diff --git a/osu.Game.Tournament.Tests/NonVisual/IPCLocationTest.cs b/osu.Game.Tournament.Tests/NonVisual/IPCLocationTest.cs index 4c5f5a7a1a..e4eb5a36fb 100644 --- a/osu.Game.Tournament.Tests/NonVisual/IPCLocationTest.cs +++ b/osu.Game.Tournament.Tests/NonVisual/IPCLocationTest.cs @@ -55,7 +55,8 @@ namespace osu.Game.Tournament.Tests.NonVisual private TournamentGameBase loadOsu(GameHost host) { var osu = new TournamentGameBase(); - Task.Run(() => host.Run(osu)); + Task.Run(() => host.Run(osu)) + .ContinueWith(t => Assert.Fail($"Host threw exception {t.Exception}"), TaskContinuationOptions.OnlyOnFaulted); waitForOrAssert(() => osu.IsLoaded, @"osu! failed to start in a reasonable amount of time"); return osu; } From bfdbe3c3fe534fc3b0060f4253635065f30b074b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 29 Jun 2021 17:29:25 +0900 Subject: [PATCH 156/168] Fix clocks getting added a second time --- .../Multiplayer/Spectate/CatchUpSyncManager.cs | 7 ++++++- .../Spectate/MultiSpectatorScreen.cs | 17 ++++++++--------- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/CatchUpSyncManager.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/CatchUpSyncManager.cs index 781123f5bb..94278a47b6 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/CatchUpSyncManager.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/CatchUpSyncManager.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -54,7 +55,11 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate MasterClock = master; } - public void AddPlayerClock(ISpectatorPlayerClock clock) => playerClocks.Add(clock); + public void AddPlayerClock(ISpectatorPlayerClock clock) + { + Debug.Assert(!playerClocks.Contains(clock)); + playerClocks.Add(clock); + } public void RemovePlayerClock(ISpectatorPlayerClock clock) => playerClocks.Remove(clock); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs index 013e5551cf..e709cba0ee 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs @@ -101,7 +101,13 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate Expanded = { Value = true }, Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, - }, leaderboardContainer.Add); + }, l => + { + foreach (var instance in instances) + leaderboard.AddClock(instance.UserId, instance.GameplayClock); + + leaderboardContainer.Add(leaderboard); + }); syncManager.ReadyToStart += onReadyToStart; syncManager.MasterState.BindValueChanged(onMasterStateChanged, true); @@ -166,14 +172,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate } protected override void StartGameplay(int userId, GameplayState gameplayState) - { - var instance = instances.Single(i => i.UserId == userId); - - instance.LoadScore(gameplayState.Score); - - syncManager.AddPlayerClock(instance.GameplayClock); - leaderboard.AddClock(instance.UserId, instance.GameplayClock); - } + => instances.Single(i => i.UserId == userId).LoadScore(gameplayState.Score); protected override void EndGameplay(int userId) { From 1b2d00f796735f1fa94234cd5ff91778079087f1 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 29 Jun 2021 20:13:39 +0900 Subject: [PATCH 157/168] Trigger successes --- osu.Game.Tests/Chat/TestSceneChannelManager.cs | 3 ++- osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Chat/TestSceneChannelManager.cs b/osu.Game.Tests/Chat/TestSceneChannelManager.cs index b81c39933d..0ec21a4c7b 100644 --- a/osu.Game.Tests/Chat/TestSceneChannelManager.cs +++ b/osu.Game.Tests/Chat/TestSceneChannelManager.cs @@ -39,7 +39,8 @@ namespace osu.Game.Tests.Chat { switch (req) { - case JoinChannelRequest _: + case JoinChannelRequest joinChannel: + joinChannel.TriggerSuccess(); return true; case PostMessageRequest postMessage: diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs index 3971146ff8..a1549dfbce 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs @@ -82,7 +82,8 @@ namespace osu.Game.Tests.Visual.Online { switch (req) { - case JoinChannelRequest _: + case JoinChannelRequest joinChannel: + joinChannel.TriggerSuccess(); return true; } From 9acc5e38bb1574fe0ff9226cd8ff4221a3e29d49 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 29 Jun 2021 20:16:57 +0900 Subject: [PATCH 158/168] Add basic logging for osu! storage migration When looking into the test failure at https://github.com/ppy/osu/runs/2940065457, it became apparent that we are not showing the migration process anywhere in logs. It's the cause of many issues, and we would want to see this in CI and user logs when occurring. --- osu.Game/OsuGameBase.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index bf1b449292..7954eafdca 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -422,11 +422,15 @@ namespace osu.Game public void Migrate(string path) { + Logger.Log($@"Migrating osu! data from ""{Storage.GetFullPath(string.Empty)}"" to ""{path}""..."); + using (realmFactory.BlockAllOperations()) { contextFactory.FlushConnections(); (Storage as OsuStorage)?.Migrate(Host.GetStorage(path)); } + + Logger.Log(@"Migration complete!"); } protected override UserInputManager CreateUserInputManager() => new OsuUserInputManager(); From 2f1203085b4fc015dbda854510f274564ad18b21 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 29 Jun 2021 20:21:31 +0900 Subject: [PATCH 159/168] Also add logging of realm block/flush operations --- osu.Game/Database/RealmContextFactory.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index 71617b258d..fb5e2faff8 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -89,12 +89,18 @@ namespace osu.Game.Database if (IsDisposed) throw new ObjectDisposedException(nameof(RealmContextFactory)); + Logger.Log(@"Blocking realm operations.", LoggingTarget.Database); + blockingLock.Wait(); flushContexts(); return new InvokeOnDisposal(this, endBlockingSection); - static void endBlockingSection(RealmContextFactory factory) => factory.blockingLock.Release(); + static void endBlockingSection(RealmContextFactory factory) + { + factory.blockingLock.Release(); + Logger.Log(@"Restoring realm operations.", LoggingTarget.Database); + } } protected override void Update() @@ -147,6 +153,8 @@ namespace osu.Game.Database private void flushContexts() { + Logger.Log(@"Flushing realm contexts...", LoggingTarget.Database); + var previousContext = context; context = null; @@ -155,6 +163,8 @@ namespace osu.Game.Database Thread.Sleep(50); previousContext?.Dispose(); + + Logger.Log(@"Realm contexts flushed.", LoggingTarget.Database); } protected override void Dispose(bool isDisposing) From 6dd3c6fe9307b5d71c850ef8dbe6d1733d2e8643 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 29 Jun 2021 22:45:18 +0900 Subject: [PATCH 160/168] Make MultiSpectatorScreen and tests more resillient to timing --- .../Multiplayer/TestSceneMultiSpectatorScreen.cs | 12 +++++++++--- .../Multiplayer/Spectate/MultiSpectatorScreen.cs | 16 +++++++++++----- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs index a308e854c6..8c63fb8924 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs @@ -82,8 +82,8 @@ namespace osu.Game.Tests.Visual.Multiplayer start(new[] { PLAYER_1_ID, PLAYER_2_ID }); loadSpectateScreen(); - sendFrames(PLAYER_1_ID, 20); - sendFrames(PLAYER_2_ID, 10); + sendFrames(PLAYER_1_ID, 40); + sendFrames(PLAYER_2_ID, 20); checkPaused(PLAYER_2_ID, true); checkPausedInstant(PLAYER_1_ID, false); @@ -196,6 +196,7 @@ namespace osu.Game.Tests.Visual.Multiplayer sendFrames(PLAYER_1_ID, 10); sendFrames(PLAYER_2_ID, 20); + checkPaused(PLAYER_1_ID, false); assertMuted(PLAYER_1_ID, false); assertMuted(PLAYER_2_ID, true); @@ -297,7 +298,12 @@ namespace osu.Game.Tests.Visual.Multiplayer => AddUntilStep($"{userId} is {(state ? "paused" : "playing")}", () => getPlayer(userId).ChildrenOfType().First().GameplayClock.IsRunning != state); private void checkPausedInstant(int userId, bool state) - => AddAssert($"{userId} is {(state ? "paused" : "playing")}", () => getPlayer(userId).ChildrenOfType().First().GameplayClock.IsRunning != state); + { + checkPaused(userId, state); + + // Todo: The following should work, but is broken because SpectatorScreen retrieves the WorkingBeatmap via the BeatmapManager, bypassing the test scene clock and running real-time. + // AddAssert($"{userId} is {(state ? "paused" : "playing")}", () => getPlayer(userId).ChildrenOfType().First().GameplayClock.IsRunning != state); + } private void assertMuted(int userId, bool muted) => AddAssert($"{userId} {(muted ? "is" : "is not")} muted", () => getInstance(userId).Mute == muted); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs index e709cba0ee..2a2759e0dd 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs @@ -43,6 +43,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate private PlayerGrid grid; private MultiSpectatorLeaderboard leaderboard; private PlayerArea currentAudioSource; + private bool canStartMasterClock; /// /// Creates a new . @@ -108,17 +109,17 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate leaderboardContainer.Add(leaderboard); }); - - syncManager.ReadyToStart += onReadyToStart; - syncManager.MasterState.BindValueChanged(onMasterStateChanged, true); } protected override void LoadComplete() { base.LoadComplete(); - masterClockContainer.Stop(); masterClockContainer.Reset(); + masterClockContainer.Stop(); + + syncManager.ReadyToStart += onReadyToStart; + syncManager.MasterState.BindValueChanged(onMasterStateChanged, true); } protected override void Update() @@ -151,6 +152,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate masterClockContainer.Seek(startTime); masterClockContainer.Start(); + + // Although the clock has been started, this flag is set to allow for later synchronisation state changes to also be able to start it. + canStartMasterClock = true; } private void onMasterStateChanged(ValueChangedEvent state) @@ -158,7 +162,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate switch (state.NewValue) { case MasterClockState.Synchronised: - masterClockContainer.Start(); + if (canStartMasterClock) + masterClockContainer.Start(); + break; case MasterClockState.TooFarAhead: From 331b7237ab5f175067a886668d2ef3de34baf664 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 29 Jun 2021 22:53:31 +0900 Subject: [PATCH 161/168] Attempt to fix one more intermittent test failure --- .../Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs index 8c63fb8924..783db49d36 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs @@ -197,8 +197,7 @@ namespace osu.Game.Tests.Visual.Multiplayer sendFrames(PLAYER_1_ID, 10); sendFrames(PLAYER_2_ID, 20); checkPaused(PLAYER_1_ID, false); - assertMuted(PLAYER_1_ID, false); - assertMuted(PLAYER_2_ID, true); + assertOneNotMuted(); checkPaused(PLAYER_1_ID, true); assertMuted(PLAYER_1_ID, true); @@ -305,6 +304,8 @@ namespace osu.Game.Tests.Visual.Multiplayer // AddAssert($"{userId} is {(state ? "paused" : "playing")}", () => getPlayer(userId).ChildrenOfType().First().GameplayClock.IsRunning != state); } + private void assertOneNotMuted() => AddAssert("one player not muted", () => spectatorScreen.ChildrenOfType().Count(p => !p.Mute) == 1); + private void assertMuted(int userId, bool muted) => AddAssert($"{userId} {(muted ? "is" : "is not")} muted", () => getInstance(userId).Mute == muted); From e9158ccc41828b1a3fc220d5f15e8df03bca09d3 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 29 Jun 2021 23:23:21 +0900 Subject: [PATCH 162/168] Fix gameplay tests incorrectly seeking via MusicController --- osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs | 2 +- osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs index e111bb1054..1d500dcc14 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs @@ -195,7 +195,7 @@ namespace osu.Game.Rulesets.Osu.Tests private void addSeekStep(double time) { - AddStep($"seek to {time}", () => MusicController.SeekTo(time)); + AddStep($"seek to {time}", () => Player.GameplayClockContainer.Seek(time)); AddUntilStep("wait for seek to finish", () => Precision.AlmostEquals(time, Player.DrawableRuleset.FrameStableClock.CurrentTime, 100)); } diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs index 8ff21057b5..9da583a073 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs @@ -217,7 +217,7 @@ namespace osu.Game.Rulesets.Osu.Tests private void addSeekStep(double time) { - AddStep($"seek to {time}", () => MusicController.SeekTo(time)); + AddStep($"seek to {time}", () => Player.GameplayClockContainer.Seek(time)); AddUntilStep("wait for seek to finish", () => Precision.AlmostEquals(time, Player.DrawableRuleset.FrameStableClock.CurrentTime, 100)); } From 43375774487498cf412be541ce8d17c3169ee6b7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 30 Jun 2021 14:31:27 +0900 Subject: [PATCH 163/168] Remove unused private methods --- .../Multiplayer/TestSceneMultiSpectatorScreen.cs | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs index 873f8ca35b..ea8f7813fd 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs @@ -217,8 +217,6 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("wait for screen load", () => spectatorScreen.LoadState == LoadState.Loaded && (!waitForPlayerLoad || spectatorScreen.AllPlayersLoaded)); } - private void start(int userId, int? beatmapId = null) => start(new[] { userId }, beatmapId); - private void start(int[] userIds, int? beatmapId = null) { AddStep("start play", () => @@ -233,16 +231,6 @@ namespace osu.Game.Tests.Visual.Multiplayer }); } - private void finish(int userId) - { - AddStep("end play", () => - { - SpectatorClient.EndPlay(userId); - playingUserIds.Remove(userId); - nextFrame.Remove(userId); - }); - } - private void sendFrames(int userId, int count = 10) => sendFrames(new[] { userId }, count); private void sendFrames(int[] userIds, int count = 10) From 57a21dfb1ca75d4024060ccfc054a2132237907d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 30 Jun 2021 16:11:18 +0900 Subject: [PATCH 164/168] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index c845d7f276..481ddc118f 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,7 +52,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index f047859dbb..589afb86be 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -36,7 +36,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/osu.iOS.props b/osu.iOS.props index 304047ad12..a8bf0e4ab2 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -93,7 +93,7 @@ - + From e23614556eaefc9a579f7a42e6cf8f5f8f4c2dc6 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 30 Jun 2021 19:43:34 +0900 Subject: [PATCH 165/168] Fix slider testscene failures --- .../TestSceneSliderSnaking.cs | 102 ++++++++++-------- 1 file changed, 59 insertions(+), 43 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs index 1d500dcc14..744291ae09 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs @@ -37,11 +37,13 @@ namespace osu.Game.Rulesets.Osu.Tests private readonly BindableBool snakingIn = new BindableBool(); private readonly BindableBool snakingOut = new BindableBool(); + private IBeatmap beatmap; + private const double duration_of_span = 3605; private const double fade_in_modifier = -1200; protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null) - => new ClockBackedTestWorkingBeatmap(beatmap, storyboard, new FramedClock(new ManualClock { Rate = 1 }), audioManager); + => new ClockBackedTestWorkingBeatmap(this.beatmap = beatmap, storyboard, new FramedClock(new ManualClock { Rate = 1 }), audioManager); [BackgroundDependencyLoader] private void load(RulesetConfigCache configCache) @@ -51,8 +53,16 @@ namespace osu.Game.Rulesets.Osu.Tests config.BindWith(OsuRulesetSetting.SnakingOutSliders, snakingOut); } + private Slider slider; private DrawableSlider drawableSlider; + [SetUp] + public void Setup() => Schedule(() => + { + slider = null; + drawableSlider = null; + }); + [SetUpSteps] public override void SetUpSteps() { @@ -67,21 +77,19 @@ namespace osu.Game.Rulesets.Osu.Tests base.SetUpSteps(); AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning); - double startTime = hitObjects[sliderIndex].StartTime; - addSeekStep(startTime); - retrieveDrawableSlider((Slider)hitObjects[sliderIndex]); + retrieveSlider(sliderIndex); setSnaking(true); - ensureSnakingIn(startTime + fade_in_modifier); + addEnsureSnakingInSteps(() => slider.StartTime + fade_in_modifier); for (int i = 0; i < sliderIndex; i++) { // non-final repeats should not snake out - ensureNoSnakingOut(startTime, i); + addEnsureNoSnakingOutStep(() => slider.StartTime, i); } // final repeat should snake out - ensureSnakingOut(startTime, sliderIndex); + addEnsureSnakingOutSteps(() => slider.StartTime, sliderIndex); } [TestCase(0)] @@ -93,17 +101,15 @@ namespace osu.Game.Rulesets.Osu.Tests base.SetUpSteps(); AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning); - double startTime = hitObjects[sliderIndex].StartTime; - addSeekStep(startTime); - retrieveDrawableSlider((Slider)hitObjects[sliderIndex]); + retrieveSlider(sliderIndex); setSnaking(false); - ensureNoSnakingIn(startTime + fade_in_modifier); + addEnsureNoSnakingInSteps(() => slider.StartTime + fade_in_modifier); for (int i = 0; i <= sliderIndex; i++) { // no snaking out ever, including final repeat - ensureNoSnakingOut(startTime, i); + addEnsureNoSnakingOutStep(() => slider.StartTime, i); } } @@ -116,7 +122,7 @@ namespace osu.Game.Rulesets.Osu.Tests // repeat might have a chance to update its position depending on where in the frame its hit, // so some leniency is allowed here instead of checking strict equality - checkPositionChange(16600, sliderRepeat, positionAlmostSame); + addCheckPositionChangeSteps(() => 16600, getSliderRepeat, positionAlmostSame); } [Test] @@ -126,38 +132,46 @@ namespace osu.Game.Rulesets.Osu.Tests setSnaking(true); base.SetUpSteps(); - checkPositionChange(16600, sliderRepeat, positionDecreased); + addCheckPositionChangeSteps(() => 16600, getSliderRepeat, positionDecreased); } - private void retrieveDrawableSlider(Slider slider) => AddUntilStep($"retrieve slider @ {slider.StartTime}", () => - (drawableSlider = (DrawableSlider)Player.DrawableRuleset.Playfield.AllHitObjects.SingleOrDefault(d => d.HitObject == slider)) != null); - - private void ensureSnakingIn(double startTime) => checkPositionChange(startTime, sliderEnd, positionIncreased); - private void ensureNoSnakingIn(double startTime) => checkPositionChange(startTime, sliderEnd, positionRemainsSame); - - private void ensureSnakingOut(double startTime, int repeatIndex) + private void retrieveSlider(int index) { - var repeatTime = timeAtRepeat(startTime, repeatIndex); + AddStep("retrieve slider at index", () => slider = (Slider)beatmap.HitObjects[index]); + addSeekStep(() => slider); + retrieveDrawableSlider(() => slider); + } + private void retrieveDrawableSlider(Func getSliderFunc) + { + AddUntilStep("retrieve drawable slider", () => + (drawableSlider = (DrawableSlider)Player.DrawableRuleset.Playfield.AllHitObjects.SingleOrDefault(d => d.HitObject == getSliderFunc())) != null); + } + + private void addEnsureSnakingInSteps(Func startTime) => addCheckPositionChangeSteps(startTime, getSliderEnd, positionIncreased); + private void addEnsureNoSnakingInSteps(Func startTime) => addCheckPositionChangeSteps(startTime, getSliderEnd, positionRemainsSame); + + private void addEnsureSnakingOutSteps(Func startTime, int repeatIndex) + { if (repeatIndex % 2 == 0) - checkPositionChange(repeatTime, sliderStart, positionIncreased); + addCheckPositionChangeSteps(timeAtRepeat(startTime, repeatIndex), getSliderStart, positionIncreased); else - checkPositionChange(repeatTime, sliderEnd, positionDecreased); + addCheckPositionChangeSteps(timeAtRepeat(startTime, repeatIndex), getSliderEnd, positionDecreased); } - private void ensureNoSnakingOut(double startTime, int repeatIndex) => - checkPositionChange(timeAtRepeat(startTime, repeatIndex), positionAtRepeat(repeatIndex), positionRemainsSame); + private void addEnsureNoSnakingOutStep(Func startTime, int repeatIndex) + => addCheckPositionChangeSteps(timeAtRepeat(startTime, repeatIndex), positionAtRepeat(repeatIndex), positionRemainsSame); - private double timeAtRepeat(double startTime, int repeatIndex) => startTime + 100 + duration_of_span * repeatIndex; - private Func positionAtRepeat(int repeatIndex) => repeatIndex % 2 == 0 ? (Func)sliderStart : sliderEnd; + private Func timeAtRepeat(Func startTime, int repeatIndex) => () => startTime() + 100 + duration_of_span * repeatIndex; + private Func positionAtRepeat(int repeatIndex) => repeatIndex % 2 == 0 ? (Func)getSliderStart : getSliderEnd; - private List sliderCurve => ((PlaySliderBody)drawableSlider.Body.Drawable).CurrentCurve; - private Vector2 sliderStart() => sliderCurve.First(); - private Vector2 sliderEnd() => sliderCurve.Last(); + private List getSliderCurve() => ((PlaySliderBody)drawableSlider.Body.Drawable).CurrentCurve; + private Vector2 getSliderStart() => getSliderCurve().First(); + private Vector2 getSliderEnd() => getSliderCurve().Last(); - private Vector2 sliderRepeat() + private Vector2 getSliderRepeat() { - var drawable = Player.DrawableRuleset.Playfield.AllHitObjects.SingleOrDefault(d => d.HitObject == hitObjects[1]); + var drawable = Player.DrawableRuleset.Playfield.AllHitObjects.SingleOrDefault(d => d.HitObject == beatmap.HitObjects[1]); var repeat = drawable.ChildrenOfType>().First().Children.First(); return repeat.Position; } @@ -167,7 +181,7 @@ namespace osu.Game.Rulesets.Osu.Tests private bool positionDecreased(Vector2 previous, Vector2 current) => current.X < previous.X && current.Y < previous.Y; private bool positionAlmostSame(Vector2 previous, Vector2 current) => Precision.AlmostEquals(previous, current, 1); - private void checkPositionChange(double startTime, Func positionToCheck, Func positionAssertion) + private void addCheckPositionChangeSteps(Func startTime, Func positionToCheck, Func positionAssertion) { Vector2 previousPosition = Vector2.Zero; @@ -176,7 +190,7 @@ namespace osu.Game.Rulesets.Osu.Tests addSeekStep(startTime); AddStep($"save {positionDescription} position", () => previousPosition = positionToCheck.Invoke()); - addSeekStep(startTime + 100); + addSeekStep(() => startTime() + 100); AddAssert($"{positionDescription} {assertionDescription}", () => { var currentPosition = positionToCheck.Invoke(); @@ -193,19 +207,21 @@ namespace osu.Game.Rulesets.Osu.Tests }); } - private void addSeekStep(double time) + private void addSeekStep(Func slider) { - AddStep($"seek to {time}", () => Player.GameplayClockContainer.Seek(time)); - - AddUntilStep("wait for seek to finish", () => Precision.AlmostEquals(time, Player.DrawableRuleset.FrameStableClock.CurrentTime, 100)); + AddStep("seek to slider", () => Player.GameplayClockContainer.Seek(slider().StartTime)); + AddUntilStep("wait for seek to finish", () => Precision.AlmostEquals(slider().StartTime, Player.DrawableRuleset.FrameStableClock.CurrentTime, 100)); } - protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new Beatmap + private void addSeekStep(Func time) { - HitObjects = hitObjects - }; + AddStep("seek to time", () => Player.GameplayClockContainer.Seek(time())); + AddUntilStep("wait for seek to finish", () => Precision.AlmostEquals(time(), Player.DrawableRuleset.FrameStableClock.CurrentTime, 100)); + } - private readonly List hitObjects = new List + protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new Beatmap { HitObjects = createHitObjects() }; + + private List createHitObjects() => new List { new Slider { From b94d88be5078457279ac09df1086950a335ee8b7 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 30 Jun 2021 19:49:51 +0900 Subject: [PATCH 166/168] Make method static to better define its usage --- osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs index 744291ae09..7dafecfced 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs @@ -221,7 +221,7 @@ namespace osu.Game.Rulesets.Osu.Tests protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new Beatmap { HitObjects = createHitObjects() }; - private List createHitObjects() => new List + private static List createHitObjects() => new List { new Slider { From 2c1f788f2de20f495baf0e2631e54163824a408c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 30 Jun 2021 19:52:25 +0900 Subject: [PATCH 167/168] Merge methods --- osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs index 7dafecfced..3252e6d912 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs @@ -139,13 +139,8 @@ namespace osu.Game.Rulesets.Osu.Tests { AddStep("retrieve slider at index", () => slider = (Slider)beatmap.HitObjects[index]); addSeekStep(() => slider); - retrieveDrawableSlider(() => slider); - } - - private void retrieveDrawableSlider(Func getSliderFunc) - { AddUntilStep("retrieve drawable slider", () => - (drawableSlider = (DrawableSlider)Player.DrawableRuleset.Playfield.AllHitObjects.SingleOrDefault(d => d.HitObject == getSliderFunc())) != null); + (drawableSlider = (DrawableSlider)Player.DrawableRuleset.Playfield.AllHitObjects.SingleOrDefault(d => d.HitObject == slider)) != null); } private void addEnsureSnakingInSteps(Func startTime) => addCheckPositionChangeSteps(startTime, getSliderEnd, positionIncreased); From aa7405afa1f8c467c0534cd20101e7e00fa752ab Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 30 Jun 2021 20:16:57 +0900 Subject: [PATCH 168/168] Increase number of sent frames to prevent timing issues --- .../Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs index 5112029873..b8db4067fb 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs @@ -168,7 +168,7 @@ namespace osu.Game.Tests.Visual.Multiplayer // Send initial frames for both players. A few more for player 1. sendFrames(PLAYER_1_ID, 1000); - sendFrames(PLAYER_2_ID, 10); + sendFrames(PLAYER_2_ID, 30); checkPausedInstant(PLAYER_1_ID, false); checkPausedInstant(PLAYER_2_ID, false);