From 0633f3bcfe23513d671c0d5dc7bf7efcb603ba44 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 22 Nov 2021 16:35:58 +0900 Subject: [PATCH 01/87] Add owner id to playlist items --- osu.Game/Online/Multiplayer/MultiplayerClient.cs | 1 + osu.Game/Online/Rooms/PlaylistItem.cs | 3 +++ 2 files changed, 4 insertions(+) diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index ad3c1f6781..df16fb3042 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -720,6 +720,7 @@ namespace osu.Game.Online.Multiplayer var playlistItem = new PlaylistItem { ID = item.ID, + OwnerID = item.OwnerID, Beatmap = { Value = beatmap }, Ruleset = { Value = ruleset }, Expired = item.Expired diff --git a/osu.Game/Online/Rooms/PlaylistItem.cs b/osu.Game/Online/Rooms/PlaylistItem.cs index c889dc514b..a1480865b8 100644 --- a/osu.Game/Online/Rooms/PlaylistItem.cs +++ b/osu.Game/Online/Rooms/PlaylistItem.cs @@ -18,6 +18,9 @@ namespace osu.Game.Online.Rooms [JsonProperty("id")] public long ID { get; set; } + [JsonProperty("owner_id")] + public int OwnerID { get; set; } + [JsonProperty("beatmap_id")] public int BeatmapID { get; set; } From e5dcfc311390a22b0b1fd6ad880980e90bc292ca Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Sun, 28 Nov 2021 14:03:21 +0900 Subject: [PATCH 02/87] Use console IPC --- osu.Desktop/Program.cs | 53 +++++++++++++++++++-- osu.Game/Beatmaps/FlatFileWorkingBeatmap.cs | 52 ++++++++++++++++++++ 2 files changed, 100 insertions(+), 5 deletions(-) create mode 100644 osu.Game/Beatmaps/FlatFileWorkingBeatmap.cs diff --git a/osu.Desktop/Program.cs b/osu.Desktop/Program.cs index 898f7d5105..6dd6849d78 100644 --- a/osu.Desktop/Program.cs +++ b/osu.Desktop/Program.cs @@ -3,13 +3,22 @@ using System; using System.IO; +using System.Linq; using System.Threading; using System.Threading.Tasks; using osu.Framework; using osu.Framework.Development; using osu.Framework.Logging; using osu.Framework.Platform; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.Legacy; using osu.Game.IPC; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Catch; +using osu.Game.Rulesets.Mania; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Taiko; using osu.Game.Tournament; namespace osu.Desktop @@ -19,7 +28,7 @@ namespace osu.Desktop private const string base_game_name = @"osu"; [STAThread] - public static int Main(string[] args) + public static void Main(string[] args) { // Back up the cwd before DesktopGameHost changes it string cwd = Environment.CurrentDirectory; @@ -49,6 +58,34 @@ namespace osu.Desktop gameName = $"{base_game_name}-{clientID}"; break; + + case "--osu-stable-difficulty-stream": + while (true) + { + try + { + string beatmapFile = Console.ReadLine() ?? string.Empty; + int rulesetId = int.Parse(Console.ReadLine() ?? string.Empty); + LegacyMods legacyMods = (LegacyMods)int.Parse(Console.ReadLine() ?? string.Empty); + + Ruleset ruleset = rulesetId switch + { + 0 => new OsuRuleset(), + 1 => new TaikoRuleset(), + 2 => new CatchRuleset(), + 3 => new ManiaRuleset(), + _ => throw new ArgumentException("Invalid ruleset id") + }; + + Mod[] mods = ruleset.ConvertFromLegacyMods(legacyMods).ToArray(); + WorkingBeatmap beatmap = new FlatFileWorkingBeatmap(beatmapFile, _ => ruleset); + Console.WriteLine(ruleset.CreateDifficultyCalculator(beatmap).Calculate(mods).StarRating); + } + catch + { + Console.WriteLine(0); + } + } } } @@ -69,14 +106,14 @@ namespace osu.Desktop throw new TimeoutException(@"IPC took too long to send"); } - return 0; + return; } // we want to allow multiple instances to be started when in debug. if (!DebugUtils.IsDebugBuild) { Logger.Log(@"osu! does not support multiple running instances.", LoggingTarget.Runtime, LogLevel.Error); - return 0; + return; } } @@ -84,8 +121,6 @@ namespace osu.Desktop host.Run(new TournamentGame()); else host.Run(new OsuGameDesktop(args)); - - return 0; } } @@ -107,4 +142,12 @@ namespace osu.Desktop return continueExecution; } } + + // Note: Keep in osu.Desktop namespace, or update osu!stable also. + public class DifficultyCalculationMessage + { + public string BeatmapFile { get; set; } + public int RulesetId { get; set; } + public int Mods { get; set; } + } } diff --git a/osu.Game/Beatmaps/FlatFileWorkingBeatmap.cs b/osu.Game/Beatmaps/FlatFileWorkingBeatmap.cs new file mode 100644 index 0000000000..8c915e2872 --- /dev/null +++ b/osu.Game/Beatmaps/FlatFileWorkingBeatmap.cs @@ -0,0 +1,52 @@ +// 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.IO; +using osu.Framework.Audio.Track; +using osu.Framework.Graphics.Textures; +using osu.Game.Beatmaps.Formats; +using osu.Game.IO; +using osu.Game.Rulesets; +using osu.Game.Skinning; + +namespace osu.Game.Beatmaps +{ + /// + /// A which can be constructed directly from a .osu file, providing an implementation for + /// . + /// + public class FlatFileWorkingBeatmap : WorkingBeatmap + { + private readonly Beatmap beatmap; + + public FlatFileWorkingBeatmap(string file, Func rulesetProvider, int? beatmapId = null) + : this(readFromFile(file), rulesetProvider, beatmapId) + { + } + + private FlatFileWorkingBeatmap(Beatmap beatmap, Func rulesetProvider, int? beatmapId = null) + : base(beatmap.BeatmapInfo, null) + { + this.beatmap = beatmap; + + beatmap.BeatmapInfo.Ruleset = rulesetProvider(beatmap.BeatmapInfo.RulesetID).RulesetInfo; + + if (beatmapId.HasValue) + beatmap.BeatmapInfo.OnlineID = beatmapId; + } + + private static Beatmap readFromFile(string filename) + { + using (var stream = File.OpenRead(filename)) + using (var reader = new LineBufferedReader(stream)) + return Decoder.GetDecoder(reader).Decode(reader); + } + + protected override IBeatmap GetBeatmap() => beatmap; + protected override Texture GetBackground() => throw new NotImplementedException(); + protected override Track GetBeatmapTrack() => throw new NotImplementedException(); + protected internal override ISkin GetSkin() => throw new NotImplementedException(); + public override Stream GetStream(string storagePath) => throw new NotImplementedException(); + } +} From ef247806429270a6e69fb0a25bcb8d81ae124ba7 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Sun, 28 Nov 2021 18:00:06 +0900 Subject: [PATCH 03/87] Use IPC via TCP --- .../LegacyIpcDifficultyCalculationRequest.cs | 18 ++++ .../LegacyIpcDifficultyCalculationResponse.cs | 16 ++++ osu.Desktop/LegacyIpc/LegacyIpcMessage.cs | 40 +++++++++ osu.Desktop/LegacyIpc/LegacyTcpIpcProvider.cs | 65 ++++++++++++++ osu.Desktop/Program.cs | 86 ++++++++++++------- 5 files changed, 193 insertions(+), 32 deletions(-) create mode 100644 osu.Desktop/LegacyIpc/LegacyIpcDifficultyCalculationRequest.cs create mode 100644 osu.Desktop/LegacyIpc/LegacyIpcDifficultyCalculationResponse.cs create mode 100644 osu.Desktop/LegacyIpc/LegacyIpcMessage.cs create mode 100644 osu.Desktop/LegacyIpc/LegacyTcpIpcProvider.cs diff --git a/osu.Desktop/LegacyIpc/LegacyIpcDifficultyCalculationRequest.cs b/osu.Desktop/LegacyIpc/LegacyIpcDifficultyCalculationRequest.cs new file mode 100644 index 0000000000..d6ef390a8f --- /dev/null +++ b/osu.Desktop/LegacyIpc/LegacyIpcDifficultyCalculationRequest.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.Desktop.LegacyIpc +{ + /// + /// A difficulty calculation request from the legacy client. + /// + /// + /// Synchronise any changes with osu!stable. + /// + public class LegacyIpcDifficultyCalculationRequest + { + public string BeatmapFile { get; set; } + public int RulesetId { get; set; } + public int Mods { get; set; } + } +} diff --git a/osu.Desktop/LegacyIpc/LegacyIpcDifficultyCalculationResponse.cs b/osu.Desktop/LegacyIpc/LegacyIpcDifficultyCalculationResponse.cs new file mode 100644 index 0000000000..7b9fae5797 --- /dev/null +++ b/osu.Desktop/LegacyIpc/LegacyIpcDifficultyCalculationResponse.cs @@ -0,0 +1,16 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +namespace osu.Desktop.LegacyIpc +{ + /// + /// A difficulty calculation response returned to the legacy client. + /// + /// + /// Synchronise any changes with osu!stable. + /// + public class LegacyIpcDifficultyCalculationResponse + { + public double StarRating { get; set; } + } +} diff --git a/osu.Desktop/LegacyIpc/LegacyIpcMessage.cs b/osu.Desktop/LegacyIpc/LegacyIpcMessage.cs new file mode 100644 index 0000000000..6fefae4509 --- /dev/null +++ b/osu.Desktop/LegacyIpc/LegacyIpcMessage.cs @@ -0,0 +1,40 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Platform; + +namespace osu.Desktop.LegacyIpc +{ + /// + /// An that can be used to communicate to and from legacy clients. + /// + /// + /// Synchronise any changes with osu-stable. + /// + public class LegacyIpcMessage : IpcMessage + { + public LegacyIpcMessage() + { + // Types/assemblies are not inter-compatible, so always serialise/deserialise into objects. + base.Type = typeof(object).FullName; + } + + public new string Type => base.Type; // Hide setter. + + public new object Value + { + get => base.Value; + set => base.Value = new Data + { + MessageType = value.GetType().Name, + MessageData = value + }; + } + + public class Data + { + public string MessageType { get; set; } + public object MessageData { get; set; } + } + } +} diff --git a/osu.Desktop/LegacyIpc/LegacyTcpIpcProvider.cs b/osu.Desktop/LegacyIpc/LegacyTcpIpcProvider.cs new file mode 100644 index 0000000000..4fca40f22f --- /dev/null +++ b/osu.Desktop/LegacyIpc/LegacyTcpIpcProvider.cs @@ -0,0 +1,65 @@ +// 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.Threading.Tasks; +using Newtonsoft.Json.Linq; +using osu.Framework.Platform; + +namespace osu.Desktop.LegacyIpc +{ + public class LegacyTcpIpcProvider : TcpIpcProvider + { + public new Func MessageReceived; + + public LegacyTcpIpcProvider() + { + base.MessageReceived += msg => + { + try + { + var legacyData = ((JObject)msg.Value).ToObject(); + object value = parseObject((JObject)legacyData.MessageData, legacyData.MessageType); + + object result = MessageReceived?.Invoke(value); + return result != null ? new LegacyIpcMessage { Value = result } : null; + } + catch (Exception ex) + { + Console.WriteLine(ex); + } + + return null; + }; + } + + public Task SendMessageAsync(object message) => base.SendMessageAsync(new LegacyIpcMessage { Value = message }); + + public async Task SendMessageWithResponseAsync(object message) + { + var result = await base.SendMessageWithResponseAsync(new LegacyIpcMessage { Value = message }).ConfigureAwait(false); + + var legacyData = ((JObject)result.Value).ToObject(); + return (T)parseObject((JObject)legacyData.MessageData, legacyData.MessageType); + } + + public new Task SendMessageAsync(IpcMessage message) => throw new InvalidOperationException("Use typed overloads."); + + public new Task SendMessageWithResponseAsync(IpcMessage message) => throw new InvalidOperationException("Use typed overloads."); + + private object parseObject(JObject value, string type) + { + switch (type) + { + case nameof(LegacyIpcDifficultyCalculationRequest): + return value.ToObject(); + + case nameof(LegacyIpcDifficultyCalculationResponse): + return value.ToObject(); + + default: + throw new ArgumentException($"Unknown type: {type}"); + } + } + } +} diff --git a/osu.Desktop/Program.cs b/osu.Desktop/Program.cs index 6dd6849d78..cb9204f518 100644 --- a/osu.Desktop/Program.cs +++ b/osu.Desktop/Program.cs @@ -6,6 +6,7 @@ using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; +using osu.Desktop.LegacyIpc; using osu.Framework; using osu.Framework.Development; using osu.Framework.Logging; @@ -27,6 +28,8 @@ namespace osu.Desktop { private const string base_game_name = @"osu"; + private static LegacyTcpIpcProvider legacyIpcProvider; + [STAThread] public static void Main(string[] args) { @@ -59,33 +62,26 @@ namespace osu.Desktop gameName = $"{base_game_name}-{clientID}"; break; - case "--osu-stable-difficulty-stream": - while (true) + case "--legacy-ipc-server": + using (legacyIpcProvider = new LegacyTcpIpcProvider()) { - try - { - string beatmapFile = Console.ReadLine() ?? string.Empty; - int rulesetId = int.Parse(Console.ReadLine() ?? string.Empty); - LegacyMods legacyMods = (LegacyMods)int.Parse(Console.ReadLine() ?? string.Empty); - - Ruleset ruleset = rulesetId switch - { - 0 => new OsuRuleset(), - 1 => new TaikoRuleset(), - 2 => new CatchRuleset(), - 3 => new ManiaRuleset(), - _ => throw new ArgumentException("Invalid ruleset id") - }; - - Mod[] mods = ruleset.ConvertFromLegacyMods(legacyMods).ToArray(); - WorkingBeatmap beatmap = new FlatFileWorkingBeatmap(beatmapFile, _ => ruleset); - Console.WriteLine(ruleset.CreateDifficultyCalculator(beatmap).Calculate(mods).StarRating); - } - catch - { - Console.WriteLine(0); - } + legacyIpcProvider.MessageReceived += onLegacyIpcMessageReceived; + legacyIpcProvider.Bind(); + legacyIpcProvider.StartAsync().Wait(); } + + return; + + case "--legacy-ipc-client": + using (legacyIpcProvider = new LegacyTcpIpcProvider()) + { + Console.WriteLine(legacyIpcProvider.SendMessageWithResponseAsync(new LegacyIpcDifficultyCalculationRequest + { + BeatmapFile = "/home/smgi/Downloads/osu_files/129891.osu", + }).Result.StarRating); + } + + return; } } @@ -141,13 +137,39 @@ namespace osu.Desktop return continueExecution; } - } - // Note: Keep in osu.Desktop namespace, or update osu!stable also. - public class DifficultyCalculationMessage - { - public string BeatmapFile { get; set; } - public int RulesetId { get; set; } - public int Mods { get; set; } + private static object onLegacyIpcMessageReceived(object message) + { + switch (message) + { + case LegacyIpcDifficultyCalculationRequest req: + try + { + Ruleset ruleset = req.RulesetId switch + { + 0 => new OsuRuleset(), + 1 => new TaikoRuleset(), + 2 => new CatchRuleset(), + 3 => new ManiaRuleset(), + _ => throw new ArgumentException("Invalid ruleset id") + }; + + Mod[] mods = ruleset.ConvertFromLegacyMods((LegacyMods)req.Mods).ToArray(); + WorkingBeatmap beatmap = new FlatFileWorkingBeatmap(req.BeatmapFile, _ => ruleset); + + return new LegacyIpcDifficultyCalculationResponse + { + StarRating = ruleset.CreateDifficultyCalculator(beatmap).Calculate(mods).StarRating + }; + } + catch + { + return new LegacyIpcDifficultyCalculationResponse(); + } + } + + Console.WriteLine("Type not matched."); + return null; + } } } From 5711c428caa9af871aa2e8e2509f16896470eabb Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Sun, 28 Nov 2021 21:15:21 +0900 Subject: [PATCH 04/87] Increment IPC port --- osu.Desktop/LegacyIpc/LegacyTcpIpcProvider.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Desktop/LegacyIpc/LegacyTcpIpcProvider.cs b/osu.Desktop/LegacyIpc/LegacyTcpIpcProvider.cs index 4fca40f22f..5d5a34e850 100644 --- a/osu.Desktop/LegacyIpc/LegacyTcpIpcProvider.cs +++ b/osu.Desktop/LegacyIpc/LegacyTcpIpcProvider.cs @@ -13,6 +13,7 @@ namespace osu.Desktop.LegacyIpc public new Func MessageReceived; public LegacyTcpIpcProvider() + : base(45357) { base.MessageReceived += msg => { From f506cb35bc4781b404b0f039f5978a11514d80f8 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Sun, 28 Nov 2021 21:15:29 +0900 Subject: [PATCH 05/87] Bind legacy IPC on startup --- osu.Desktop/Program.cs | 29 ++++++++--------------------- 1 file changed, 8 insertions(+), 21 deletions(-) diff --git a/osu.Desktop/Program.cs b/osu.Desktop/Program.cs index cb9204f518..7762aa9010 100644 --- a/osu.Desktop/Program.cs +++ b/osu.Desktop/Program.cs @@ -61,27 +61,6 @@ namespace osu.Desktop gameName = $"{base_game_name}-{clientID}"; break; - - case "--legacy-ipc-server": - using (legacyIpcProvider = new LegacyTcpIpcProvider()) - { - legacyIpcProvider.MessageReceived += onLegacyIpcMessageReceived; - legacyIpcProvider.Bind(); - legacyIpcProvider.StartAsync().Wait(); - } - - return; - - case "--legacy-ipc-client": - using (legacyIpcProvider = new LegacyTcpIpcProvider()) - { - Console.WriteLine(legacyIpcProvider.SendMessageWithResponseAsync(new LegacyIpcDifficultyCalculationRequest - { - BeatmapFile = "/home/smgi/Downloads/osu_files/129891.osu", - }).Result.StarRating); - } - - return; } } @@ -113,6 +92,14 @@ namespace osu.Desktop } } + if (host.IsPrimaryInstance) + { + var legacyIpc = new LegacyTcpIpcProvider(); + legacyIpc.MessageReceived += onLegacyIpcMessageReceived; + legacyIpc.Bind(); + legacyIpc.StartAsync(); + } + if (tournamentClient) host.Run(new TournamentGame()); else From 36fffbd9178585aa3c497918a220f9df767c75b6 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Sun, 28 Nov 2021 21:31:22 +0900 Subject: [PATCH 06/87] Refactoring --- osu.Desktop/LegacyIpc/LegacyIpcMessage.cs | 1 - osu.Desktop/LegacyIpc/LegacyTcpIpcProvider.cs | 33 ++++++++----------- osu.Desktop/Program.cs | 2 -- 3 files changed, 14 insertions(+), 22 deletions(-) diff --git a/osu.Desktop/LegacyIpc/LegacyIpcMessage.cs b/osu.Desktop/LegacyIpc/LegacyIpcMessage.cs index 6fefae4509..2e421068dc 100644 --- a/osu.Desktop/LegacyIpc/LegacyIpcMessage.cs +++ b/osu.Desktop/LegacyIpc/LegacyIpcMessage.cs @@ -23,7 +23,6 @@ namespace osu.Desktop.LegacyIpc public new object Value { - get => base.Value; set => base.Value = new Data { MessageType = value.GetType().Name, diff --git a/osu.Desktop/LegacyIpc/LegacyTcpIpcProvider.cs b/osu.Desktop/LegacyIpc/LegacyTcpIpcProvider.cs index 5d5a34e850..43d9dd741c 100644 --- a/osu.Desktop/LegacyIpc/LegacyTcpIpcProvider.cs +++ b/osu.Desktop/LegacyIpc/LegacyTcpIpcProvider.cs @@ -2,14 +2,22 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Threading.Tasks; using Newtonsoft.Json.Linq; +using osu.Framework.Logging; using osu.Framework.Platform; namespace osu.Desktop.LegacyIpc { + /// + /// Provides IPC to legacy osu! clients. + /// public class LegacyTcpIpcProvider : TcpIpcProvider { + private static readonly Logger logger = Logger.GetLogger("ipc"); + + /// + /// Invoked when a message is received from a legacy client. + /// public new Func MessageReceived; public LegacyTcpIpcProvider() @@ -19,35 +27,22 @@ namespace osu.Desktop.LegacyIpc { try { + logger.Add($"Processing incoming IPC message: {msg.Value}"); + var legacyData = ((JObject)msg.Value).ToObject(); - object value = parseObject((JObject)legacyData.MessageData, legacyData.MessageType); + object value = parseObject((JObject)legacyData!.MessageData, legacyData.MessageType); object result = MessageReceived?.Invoke(value); return result != null ? new LegacyIpcMessage { Value = result } : null; } catch (Exception ex) { - Console.WriteLine(ex); + logger.Add("Processing IPC message failed!", exception: ex); + return null; } - - return null; }; } - public Task SendMessageAsync(object message) => base.SendMessageAsync(new LegacyIpcMessage { Value = message }); - - public async Task SendMessageWithResponseAsync(object message) - { - var result = await base.SendMessageWithResponseAsync(new LegacyIpcMessage { Value = message }).ConfigureAwait(false); - - var legacyData = ((JObject)result.Value).ToObject(); - return (T)parseObject((JObject)legacyData.MessageData, legacyData.MessageType); - } - - public new Task SendMessageAsync(IpcMessage message) => throw new InvalidOperationException("Use typed overloads."); - - public new Task SendMessageWithResponseAsync(IpcMessage message) => throw new InvalidOperationException("Use typed overloads."); - private object parseObject(JObject value, string type) { switch (type) diff --git a/osu.Desktop/Program.cs b/osu.Desktop/Program.cs index 7762aa9010..c41a6a4f5d 100644 --- a/osu.Desktop/Program.cs +++ b/osu.Desktop/Program.cs @@ -28,8 +28,6 @@ namespace osu.Desktop { private const string base_game_name = @"osu"; - private static LegacyTcpIpcProvider legacyIpcProvider; - [STAThread] public static void Main(string[] args) { From 27ba3c6d1a8c18bb69aeac5e38d97966dcba56ed Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Sun, 28 Nov 2021 22:16:21 +0900 Subject: [PATCH 07/87] Add back removed getter Seems to somehow be required. --- osu.Desktop/LegacyIpc/LegacyIpcMessage.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Desktop/LegacyIpc/LegacyIpcMessage.cs b/osu.Desktop/LegacyIpc/LegacyIpcMessage.cs index 2e421068dc..6fefae4509 100644 --- a/osu.Desktop/LegacyIpc/LegacyIpcMessage.cs +++ b/osu.Desktop/LegacyIpc/LegacyIpcMessage.cs @@ -23,6 +23,7 @@ namespace osu.Desktop.LegacyIpc public new object Value { + get => base.Value; set => base.Value = new Data { MessageType = value.GetType().Name, From 18a0a791fd9f56c08a81b622ea0f196fef00097a Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Sun, 28 Nov 2021 22:24:42 +0900 Subject: [PATCH 08/87] Refactor --- osu.Desktop/LegacyIpc/LegacyTcpIpcProvider.cs | 5 +++-- osu.Desktop/Program.cs | 18 ++++++++++++++---- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/osu.Desktop/LegacyIpc/LegacyTcpIpcProvider.cs b/osu.Desktop/LegacyIpc/LegacyTcpIpcProvider.cs index 43d9dd741c..7855f9c7ce 100644 --- a/osu.Desktop/LegacyIpc/LegacyTcpIpcProvider.cs +++ b/osu.Desktop/LegacyIpc/LegacyTcpIpcProvider.cs @@ -13,7 +13,7 @@ namespace osu.Desktop.LegacyIpc /// public class LegacyTcpIpcProvider : TcpIpcProvider { - private static readonly Logger logger = Logger.GetLogger("ipc"); + private static readonly Logger logger = Logger.GetLogger("legacy-ipc"); /// /// Invoked when a message is received from a legacy client. @@ -27,7 +27,8 @@ namespace osu.Desktop.LegacyIpc { try { - logger.Add($"Processing incoming IPC message: {msg.Value}"); + logger.Add($"Processing legacy IPC message..."); + logger.Add($"\t{msg.Value}", LogLevel.Debug); var legacyData = ((JObject)msg.Value).ToObject(); object value = parseObject((JObject)legacyData!.MessageData, legacyData.MessageType); diff --git a/osu.Desktop/Program.cs b/osu.Desktop/Program.cs index c41a6a4f5d..9542ccd8dc 100644 --- a/osu.Desktop/Program.cs +++ b/osu.Desktop/Program.cs @@ -28,6 +28,8 @@ namespace osu.Desktop { private const string base_game_name = @"osu"; + private static LegacyTcpIpcProvider legacyIpc; + [STAThread] public static void Main(string[] args) { @@ -92,10 +94,18 @@ namespace osu.Desktop if (host.IsPrimaryInstance) { - var legacyIpc = new LegacyTcpIpcProvider(); - legacyIpc.MessageReceived += onLegacyIpcMessageReceived; - legacyIpc.Bind(); - legacyIpc.StartAsync(); + try + { + Logger.Log("Starting legacy IPC provider..."); + legacyIpc = new LegacyTcpIpcProvider(); + legacyIpc.MessageReceived += onLegacyIpcMessageReceived; + legacyIpc.Bind(); + legacyIpc.StartAsync(); + } + catch (Exception ex) + { + Logger.Error(ex, "Failed to start legacy IPC provider"); + } } if (tournamentClient) From fc3eb08452d620fb0b5c5158be30313f117c1ca5 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Sun, 28 Nov 2021 22:27:59 +0900 Subject: [PATCH 09/87] Output raw message on failure --- osu.Desktop/LegacyIpc/LegacyTcpIpcProvider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Desktop/LegacyIpc/LegacyTcpIpcProvider.cs b/osu.Desktop/LegacyIpc/LegacyTcpIpcProvider.cs index 7855f9c7ce..4fb500fa9c 100644 --- a/osu.Desktop/LegacyIpc/LegacyTcpIpcProvider.cs +++ b/osu.Desktop/LegacyIpc/LegacyTcpIpcProvider.cs @@ -38,7 +38,7 @@ namespace osu.Desktop.LegacyIpc } catch (Exception ex) { - logger.Add("Processing IPC message failed!", exception: ex); + logger.Add($"Processing IPC message failed: {msg.Value}", exception: ex); return null; } }; From 0d147b4ad9f7a7bc308eef9508034b45b499add5 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Sun, 28 Nov 2021 22:59:49 +0900 Subject: [PATCH 10/87] Return null IPC response for archive imports --- osu.Game/IPC/ArchiveImportIPCChannel.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/IPC/ArchiveImportIPCChannel.cs b/osu.Game/IPC/ArchiveImportIPCChannel.cs index d9d0e4c0ea..f381aad39a 100644 --- a/osu.Game/IPC/ArchiveImportIPCChannel.cs +++ b/osu.Game/IPC/ArchiveImportIPCChannel.cs @@ -18,6 +18,7 @@ namespace osu.Game.IPC : base(host) { this.importer = importer; + MessageReceived += msg => { Debug.Assert(importer != null); @@ -25,6 +26,8 @@ namespace osu.Game.IPC { if (t.Exception != null) throw t.Exception; }, TaskContinuationOptions.OnlyOnFaulted); + + return null; }; } From 4ee2063683f7fb14a9e93a0f36e1800e6f20b514 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Sun, 28 Nov 2021 23:02:57 +0900 Subject: [PATCH 11/87] Move event handlign internal to LegacyTcpIpcProvider --- osu.Desktop/LegacyIpc/LegacyTcpIpcProvider.cs | 45 ++++++++++++++++++- osu.Desktop/Program.cs | 44 ------------------ 2 files changed, 44 insertions(+), 45 deletions(-) diff --git a/osu.Desktop/LegacyIpc/LegacyTcpIpcProvider.cs b/osu.Desktop/LegacyIpc/LegacyTcpIpcProvider.cs index 4fb500fa9c..273e17d24b 100644 --- a/osu.Desktop/LegacyIpc/LegacyTcpIpcProvider.cs +++ b/osu.Desktop/LegacyIpc/LegacyTcpIpcProvider.cs @@ -2,9 +2,18 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Linq; using Newtonsoft.Json.Linq; using osu.Framework.Logging; using osu.Framework.Platform; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.Legacy; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Catch; +using osu.Game.Rulesets.Mania; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Taiko; namespace osu.Desktop.LegacyIpc { @@ -33,7 +42,7 @@ namespace osu.Desktop.LegacyIpc var legacyData = ((JObject)msg.Value).ToObject(); object value = parseObject((JObject)legacyData!.MessageData, legacyData.MessageType); - object result = MessageReceived?.Invoke(value); + object result = onLegacyIpcMessageReceived(value); return result != null ? new LegacyIpcMessage { Value = result } : null; } catch (Exception ex) @@ -58,5 +67,39 @@ namespace osu.Desktop.LegacyIpc throw new ArgumentException($"Unknown type: {type}"); } } + + private object onLegacyIpcMessageReceived(object message) + { + switch (message) + { + case LegacyIpcDifficultyCalculationRequest req: + try + { + Ruleset ruleset = req.RulesetId switch + { + 0 => new OsuRuleset(), + 1 => new TaikoRuleset(), + 2 => new CatchRuleset(), + 3 => new ManiaRuleset(), + _ => throw new ArgumentException("Invalid ruleset id") + }; + + Mod[] mods = ruleset.ConvertFromLegacyMods((LegacyMods)req.Mods).ToArray(); + WorkingBeatmap beatmap = new FlatFileWorkingBeatmap(req.BeatmapFile, _ => ruleset); + + return new LegacyIpcDifficultyCalculationResponse + { + StarRating = ruleset.CreateDifficultyCalculator(beatmap).Calculate(mods).StarRating + }; + } + catch + { + return new LegacyIpcDifficultyCalculationResponse(); + } + } + + Console.WriteLine("Type not matched."); + return null; + } } } diff --git a/osu.Desktop/Program.cs b/osu.Desktop/Program.cs index 9542ccd8dc..a9e3575a49 100644 --- a/osu.Desktop/Program.cs +++ b/osu.Desktop/Program.cs @@ -3,7 +3,6 @@ using System; using System.IO; -using System.Linq; using System.Threading; using System.Threading.Tasks; using osu.Desktop.LegacyIpc; @@ -11,15 +10,7 @@ using osu.Framework; using osu.Framework.Development; using osu.Framework.Logging; using osu.Framework.Platform; -using osu.Game.Beatmaps; -using osu.Game.Beatmaps.Legacy; using osu.Game.IPC; -using osu.Game.Rulesets; -using osu.Game.Rulesets.Catch; -using osu.Game.Rulesets.Mania; -using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Osu; -using osu.Game.Rulesets.Taiko; using osu.Game.Tournament; namespace osu.Desktop @@ -98,7 +89,6 @@ namespace osu.Desktop { Logger.Log("Starting legacy IPC provider..."); legacyIpc = new LegacyTcpIpcProvider(); - legacyIpc.MessageReceived += onLegacyIpcMessageReceived; legacyIpc.Bind(); legacyIpc.StartAsync(); } @@ -132,39 +122,5 @@ namespace osu.Desktop return continueExecution; } - - private static object onLegacyIpcMessageReceived(object message) - { - switch (message) - { - case LegacyIpcDifficultyCalculationRequest req: - try - { - Ruleset ruleset = req.RulesetId switch - { - 0 => new OsuRuleset(), - 1 => new TaikoRuleset(), - 2 => new CatchRuleset(), - 3 => new ManiaRuleset(), - _ => throw new ArgumentException("Invalid ruleset id") - }; - - Mod[] mods = ruleset.ConvertFromLegacyMods((LegacyMods)req.Mods).ToArray(); - WorkingBeatmap beatmap = new FlatFileWorkingBeatmap(req.BeatmapFile, _ => ruleset); - - return new LegacyIpcDifficultyCalculationResponse - { - StarRating = ruleset.CreateDifficultyCalculator(beatmap).Calculate(mods).StarRating - }; - } - catch - { - return new LegacyIpcDifficultyCalculationResponse(); - } - } - - Console.WriteLine("Type not matched."); - return null; - } } } From 7224f6bac54127ad9c4559b02fb92eeb9d20277a Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 1 Dec 2021 20:00:31 +0900 Subject: [PATCH 12/87] Fix testable online IDs starting at 0 --- osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs b/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs index abcf31c007..a4586dea12 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs @@ -25,9 +25,9 @@ namespace osu.Game.Tests.Visual.OnlinePlay private readonly List serverSideRooms = new List(); - private int currentRoomId; - private int currentPlaylistItemId; - private int currentScoreId; + private int currentRoomId = 1; + private int currentPlaylistItemId = 1; + private int currentScoreId = 1; /// /// Handles an API request, while also updating the local state to match From c38537a51ab1a0e313ee0ed3b73d990c726ff214 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 22 Nov 2021 18:39:50 +0900 Subject: [PATCH 13/87] Initial implementation of MultiplayerPlaylist --- .../TestSceneMultiplayerPlaylist.cs | 255 ++++++++++++++++++ .../Online/Multiplayer/MultiplayerClient.cs | 9 +- .../OnlinePlay/DrawableRoomPlaylist.cs | 23 +- .../Multiplayer/MultiplayerRoomComposite.cs | 34 +++ 4 files changed, 297 insertions(+), 24 deletions(-) create mode 100644 osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs new file mode 100644 index 0000000000..ee5cb7f32c --- /dev/null +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs @@ -0,0 +1,255 @@ +// 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.Diagnostics; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Platform; +using osu.Framework.Testing; +using osu.Game.Beatmaps; +using osu.Game.Database; +using osu.Game.Online.Multiplayer; +using osu.Game.Online.Rooms; +using osu.Game.Rulesets; +using osu.Game.Screens.OnlinePlay; +using osu.Game.Screens.OnlinePlay.Multiplayer; +using osu.Game.Tests.Resources; +using osuTK; + +namespace osu.Game.Tests.Visual.Multiplayer +{ + public class TestSceneMultiplayerPlaylist : MultiplayerTestScene + { + private BeatmapManager beatmaps; + private RulesetStore rulesets; + private BeatmapSetInfo importedSet; + private BeatmapInfo importedBeatmap; + + [Cached(typeof(UserLookupCache))] + private UserLookupCache lookupCache = new TestUserLookupCache(); + + [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)); + } + + [SetUp] + public new void Setup() => Schedule(() => + { + Child = new MultiplayerPlaylist + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Size = new Vector2(0.4f, 0.8f) + }; + }); + + [SetUpSteps] + public new void SetUpSteps() + { + AddStep("import beatmap", () => + { + beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).Wait(); + importedSet = beatmaps.GetAllUsableBeatmapSetsEnumerable(IncludedDetails.All).First(); + importedBeatmap = importedSet.Beatmaps.First(b => b.RulesetID == 0); + }); + } + + [Test] + public void DoTest() + { + AddStep("change to round robin mode", () => Client.ChangeSettings(new MultiplayerRoomSettings { QueueMode = QueueMode.AllPlayersRoundRobin })); + AddStep("add playlist item for user 1", () => Client.AddPlaylistItem(new MultiplayerPlaylistItem + { + BeatmapID = importedBeatmap.OnlineID!.Value + })); + } + + public class MultiplayerPlaylist : MultiplayerRoomComposite + { + private QueueList queueList; + private DrawableRoomPlaylist historyList; + private bool firstPopulation = true; + + [BackgroundDependencyLoader] + private void load() + { + InternalChild = new GridContainer + { + RelativeSizeAxes = Axes.Both, + Content = new[] + { + new Drawable[] + { + queueList = new QueueList(false, false, true) + { + RelativeSizeAxes = Axes.Both + }, + historyList = new DrawableRoomPlaylist(false, false, true) + { + RelativeSizeAxes = Axes.Both + } + } + } + }; + } + + protected override void OnRoomUpdated() + { + base.OnRoomUpdated(); + + if (Room == null) + return; + + if (!firstPopulation) return; + + foreach (var item in Room.Playlist) + PlaylistItemAdded(item); + + firstPopulation = false; + } + + protected override void PlaylistItemAdded(MultiplayerPlaylistItem item) + { + base.PlaylistItemAdded(item); + + if (item.Expired) + historyList.Items.Add(getPlaylistItem(item)); + else + queueList.Items.Add(getPlaylistItem(item)); + } + + protected override void PlaylistItemRemoved(long item) + { + base.PlaylistItemRemoved(item); + + queueList.Items.RemoveAll(i => i.ID == item); + } + + protected override void PlaylistItemChanged(MultiplayerPlaylistItem item) + { + base.PlaylistItemChanged(item); + + PlaylistItemRemoved(item.ID); + PlaylistItemAdded(item); + } + + private PlaylistItem getPlaylistItem(MultiplayerPlaylistItem item) => Playlist.Single(i => i.ID == item.ID); + } + + public class QueueList : DrawableRoomPlaylist + { + public readonly IBindable QueueMode = new Bindable(); + + public QueueList(bool allowEdit, bool allowSelection, bool reverse = false) + : base(allowEdit, allowSelection, reverse) + { + } + + protected override FillFlowContainer> CreateListFillFlowContainer() => new QueueFillFlowContainer + { + QueueMode = { BindTarget = QueueMode }, + Spacing = new Vector2(0, 2) + }; + + private class QueueFillFlowContainer : FillFlowContainer> + { + public readonly IBindable QueueMode = new Bindable(); + + protected override void LoadComplete() + { + base.LoadComplete(); + QueueMode.BindValueChanged(_ => InvalidateLayout()); + } + + public override IEnumerable FlowingChildren + { + get + { + switch (QueueMode.Value) + { + default: + return AliveInternalChildren.Where(d => d.IsPresent) + .OfType>() + .OrderBy(item => item.Model.ID); + + case Game.Online.Multiplayer.QueueMode.AllPlayersRoundRobin: + // TODO: THIS IS SO INEFFICIENT, can it be done any better? + + // Group all items by their owners. + var groups = AliveInternalChildren.Where(d => d.IsPresent) + .OfType>() + .GroupBy(item => item.Model.OwnerID) + .Select(g => g.ToArray()) + .ToArray(); + + if (groups.Length == 0) + return Enumerable.Empty(); + + // Find the initial picking order for the groups. The group with the smallest 'weight' picks first. + int[] groupWeights = new int[groups.Length]; + + for (int i = 0; i < groups.Length; i++) + { + groupWeights[i] = groups[i].Count(item => item.Model.Expired); + groups[i] = groups[i].Where(item => !item.Model.Expired).ToArray(); + } + + var result = new List(); + + // Simulate the playlist by picking in order from the smallest-weighted room each time until no longer able to. + while (true) + { + var candidateGroup = groups + // Map each group to an index. + .Select((items, index) => new { index, items }) + // Order groups by their weights. + .OrderBy(group => groupWeights[group.index]) + // Select the first group with remaining items (null is set from previous iterations). + .FirstOrDefault(group => group.items.Any(i => i != null)); + + // Iteration ends when all groups have been exhausted of items. + if (candidateGroup == null) + break; + + // Find the index of the first non-null (i.e. unused) item in the group. + int candidateItemIndex = 0; + RearrangeableListItem candidateItem = null; + + for (int i = 0; i < candidateGroup.items.Length; i++) + { + if (candidateGroup.items[i] != null) + { + candidateItemIndex = i; + candidateItem = candidateGroup.items[i]; + } + } + + // The item is guaranteed to not be expired, since we've previously removed all expired items. + Debug.Assert(candidateItem?.Model.Expired == false); + + // Add the item to the result set. + result.Add(candidateItem); + + // Update the group for the next iteration. + candidateGroup.items[candidateItemIndex] = null; + groupWeights[candidateGroup.index]++; + } + + return result; + } + } + } + } + } + } +} diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index df16fb3042..fb716c9814 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -33,11 +33,13 @@ namespace osu.Game.Online.Multiplayer public event Action? RoomUpdated; public event Action? UserJoined; - public event Action? UserLeft; - public event Action? UserKicked; + public event Action? ItemAdded; + public event Action? ItemRemoved; + public event Action? ItemChanged; + /// /// Invoked when the multiplayer server requests the current beatmap to be loaded into play. /// @@ -619,6 +621,7 @@ namespace osu.Game.Online.Multiplayer Room.Playlist.Add(item); APIRoom.Playlist.Add(playlistItem); + ItemAdded?.Invoke(item); RoomUpdated?.Invoke(); }); } @@ -638,6 +641,7 @@ namespace osu.Game.Online.Multiplayer Room.Playlist.Remove(Room.Playlist.Single(existing => existing.ID == playlistItemId)); APIRoom.Playlist.RemoveAll(existing => existing.ID == playlistItemId); + ItemRemoved?.Invoke(playlistItemId); RoomUpdated?.Invoke(); }); @@ -668,6 +672,7 @@ namespace osu.Game.Online.Multiplayer if (CurrentMatchPlayingItem.Value?.ID == playlistItem.ID) CurrentMatchPlayingItem.Value = playlistItem; + ItemChanged?.Invoke(item); RoomUpdated?.Invoke(); }); } diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs index f5522cd25d..dc04d9a77b 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs @@ -1,9 +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.Collections.Generic; using System.Collections.Specialized; -using System.Linq; using osu.Framework.Bindables; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; @@ -25,8 +23,6 @@ namespace osu.Game.Screens.OnlinePlay { this.allowEdit = allowEdit; this.allowSelection = allowSelection; - - ((ReversibleFillFlowContainer)ListContainer).Reverse = reverse; } protected override void LoadComplete() @@ -51,7 +47,7 @@ namespace osu.Game.Screens.OnlinePlay d.ScrollbarVisible = false; }); - protected override FillFlowContainer> CreateListFillFlowContainer() => new ReversibleFillFlowContainer + protected override FillFlowContainer> CreateListFillFlowContainer() => new FillFlowContainer> { Spacing = new Vector2(0, 2) }; @@ -74,22 +70,5 @@ namespace osu.Game.Screens.OnlinePlay Items.Remove(item); } - - private class ReversibleFillFlowContainer : FillFlowContainer> - { - private bool reverse; - - public bool Reverse - { - get => reverse; - set - { - reverse = value; - Invalidate(); - } - } - - public override IEnumerable FlowingChildren => Reverse ? base.FlowingChildren.OrderBy(d => -GetLayoutPosition(d)) : base.FlowingChildren; - } } } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerRoomComposite.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerRoomComposite.cs index a380ddef25..f2cac708e4 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerRoomComposite.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerRoomComposite.cs @@ -4,6 +4,7 @@ using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Game.Online.Multiplayer; +using osu.Game.Online.Rooms; namespace osu.Game.Screens.OnlinePlay.Multiplayer { @@ -23,6 +24,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer Client.UserLeft += invokeUserLeft; Client.UserKicked += invokeUserKicked; Client.UserJoined += invokeUserJoined; + Client.ItemAdded += invokeItemAdded; + Client.ItemRemoved += invokeItemRemoved; + Client.ItemChanged += invokeItemChanged; OnRoomUpdated(); } @@ -31,6 +35,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer private void invokeUserJoined(MultiplayerRoomUser user) => Scheduler.AddOnce(UserJoined, user); private void invokeUserKicked(MultiplayerRoomUser user) => Scheduler.AddOnce(UserKicked, user); private void invokeUserLeft(MultiplayerRoomUser user) => Scheduler.AddOnce(UserLeft, user); + private void invokeItemAdded(MultiplayerPlaylistItem item) => Scheduler.AddOnce(PlaylistItemAdded, item); + private void invokeItemRemoved(long item) => Scheduler.AddOnce(PlaylistItemRemoved, item); + private void invokeItemChanged(MultiplayerPlaylistItem item) => Scheduler.AddOnce(PlaylistItemChanged, item); /// /// Invoked when a user has joined the room. @@ -56,6 +63,30 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer { } + /// + /// Invoked when a playlist item is added to the room. + /// + /// The added playlist item. + protected virtual void PlaylistItemAdded(MultiplayerPlaylistItem item) + { + } + + /// + /// Invoked when a playlist item is removed from the room. + /// + /// The ID of the removed playlist item. + protected virtual void PlaylistItemRemoved(long item) + { + } + + /// + /// Invoked when a playlist item is changed in the room. + /// + /// The new playlist item, with an existing item's ID. + protected virtual void PlaylistItemChanged(MultiplayerPlaylistItem item) + { + } + /// /// Invoked when any change occurs to the multiplayer room. /// @@ -71,6 +102,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer Client.UserLeft -= invokeUserLeft; Client.UserKicked -= invokeUserKicked; Client.UserJoined -= invokeUserJoined; + Client.ItemAdded -= invokeItemAdded; + Client.ItemRemoved -= invokeItemRemoved; + Client.ItemChanged -= invokeItemChanged; } base.Dispose(isDisposing); From 0cb35e8b1852d0c6f76a14db8382c84b25597ec9 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 26 Nov 2021 19:24:49 +0900 Subject: [PATCH 14/87] Separate out QueueList --- .../TestSceneMultiplayerPlaylist.cs | 110 +--------------- .../Multiplayer/Match/Playlist/QueueList.cs | 121 ++++++++++++++++++ 2 files changed, 122 insertions(+), 109 deletions(-) create mode 100644 osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/QueueList.cs diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs index ee5cb7f32c..3244e9420d 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs @@ -1,13 +1,10 @@ // 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.Diagnostics; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Platform; @@ -19,6 +16,7 @@ using osu.Game.Online.Rooms; using osu.Game.Rulesets; using osu.Game.Screens.OnlinePlay; using osu.Game.Screens.OnlinePlay.Multiplayer; +using osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist; using osu.Game.Tests.Resources; using osuTK; @@ -145,111 +143,5 @@ namespace osu.Game.Tests.Visual.Multiplayer private PlaylistItem getPlaylistItem(MultiplayerPlaylistItem item) => Playlist.Single(i => i.ID == item.ID); } - - public class QueueList : DrawableRoomPlaylist - { - public readonly IBindable QueueMode = new Bindable(); - - public QueueList(bool allowEdit, bool allowSelection, bool reverse = false) - : base(allowEdit, allowSelection, reverse) - { - } - - protected override FillFlowContainer> CreateListFillFlowContainer() => new QueueFillFlowContainer - { - QueueMode = { BindTarget = QueueMode }, - Spacing = new Vector2(0, 2) - }; - - private class QueueFillFlowContainer : FillFlowContainer> - { - public readonly IBindable QueueMode = new Bindable(); - - protected override void LoadComplete() - { - base.LoadComplete(); - QueueMode.BindValueChanged(_ => InvalidateLayout()); - } - - public override IEnumerable FlowingChildren - { - get - { - switch (QueueMode.Value) - { - default: - return AliveInternalChildren.Where(d => d.IsPresent) - .OfType>() - .OrderBy(item => item.Model.ID); - - case Game.Online.Multiplayer.QueueMode.AllPlayersRoundRobin: - // TODO: THIS IS SO INEFFICIENT, can it be done any better? - - // Group all items by their owners. - var groups = AliveInternalChildren.Where(d => d.IsPresent) - .OfType>() - .GroupBy(item => item.Model.OwnerID) - .Select(g => g.ToArray()) - .ToArray(); - - if (groups.Length == 0) - return Enumerable.Empty(); - - // Find the initial picking order for the groups. The group with the smallest 'weight' picks first. - int[] groupWeights = new int[groups.Length]; - - for (int i = 0; i < groups.Length; i++) - { - groupWeights[i] = groups[i].Count(item => item.Model.Expired); - groups[i] = groups[i].Where(item => !item.Model.Expired).ToArray(); - } - - var result = new List(); - - // Simulate the playlist by picking in order from the smallest-weighted room each time until no longer able to. - while (true) - { - var candidateGroup = groups - // Map each group to an index. - .Select((items, index) => new { index, items }) - // Order groups by their weights. - .OrderBy(group => groupWeights[group.index]) - // Select the first group with remaining items (null is set from previous iterations). - .FirstOrDefault(group => group.items.Any(i => i != null)); - - // Iteration ends when all groups have been exhausted of items. - if (candidateGroup == null) - break; - - // Find the index of the first non-null (i.e. unused) item in the group. - int candidateItemIndex = 0; - RearrangeableListItem candidateItem = null; - - for (int i = 0; i < candidateGroup.items.Length; i++) - { - if (candidateGroup.items[i] != null) - { - candidateItemIndex = i; - candidateItem = candidateGroup.items[i]; - } - } - - // The item is guaranteed to not be expired, since we've previously removed all expired items. - Debug.Assert(candidateItem?.Model.Expired == false); - - // Add the item to the result set. - result.Add(candidateItem); - - // Update the group for the next iteration. - candidateGroup.items[candidateItemIndex] = null; - groupWeights[candidateGroup.index]++; - } - - return result; - } - } - } - } - } } } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/QueueList.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/QueueList.cs new file mode 100644 index 0000000000..5b691fdfda --- /dev/null +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/QueueList.cs @@ -0,0 +1,121 @@ +// 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.Diagnostics; +using System.Linq; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Online.Multiplayer; +using osu.Game.Online.Rooms; +using osuTK; + +namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist +{ + public class QueueList : DrawableRoomPlaylist + { + public readonly IBindable QueueMode = new Bindable(); + + public QueueList(bool allowEdit, bool allowSelection, bool reverse = false) + : base(allowEdit, allowSelection, reverse) + { + } + + protected override FillFlowContainer> CreateListFillFlowContainer() => new QueueFillFlowContainer + { + QueueMode = { BindTarget = QueueMode }, + Spacing = new Vector2(0, 2) + }; + + private class QueueFillFlowContainer : FillFlowContainer> + { + public readonly IBindable QueueMode = new Bindable(); + + protected override void LoadComplete() + { + base.LoadComplete(); + QueueMode.BindValueChanged(_ => InvalidateLayout()); + } + + public override IEnumerable FlowingChildren + { + get + { + switch (QueueMode.Value) + { + default: + return AliveInternalChildren.Where(d => d.IsPresent) + .OfType>() + .OrderBy(item => item.Model.ID); + + case Game.Online.Multiplayer.QueueMode.AllPlayersRoundRobin: + // TODO: THIS IS SO INEFFICIENT, can it be done any better? + + // Group all items by their owners. + var groups = AliveInternalChildren.Where(d => d.IsPresent) + .OfType>() + .GroupBy(item => item.Model.OwnerID) + .Select(g => g.ToArray()) + .ToArray(); + + if (groups.Length == 0) + return Enumerable.Empty(); + + // Find the initial picking order for the groups. The group with the smallest 'weight' picks first. + int[] groupWeights = new int[groups.Length]; + + for (int i = 0; i < groups.Length; i++) + { + groupWeights[i] = groups[i].Count(item => item.Model.Expired); + groups[i] = groups[i].Where(item => !item.Model.Expired).ToArray(); + } + + var result = new List(); + + // Simulate the playlist by picking in order from the smallest-weighted room each time until no longer able to. + while (true) + { + var candidateGroup = groups + // Map each group to an index. + .Select((items, index) => new { index, items }) + // Order groups by their weights. + .OrderBy(group => groupWeights[group.index]) + // Select the first group with remaining items (null is set from previous iterations). + .FirstOrDefault(group => group.items.Any(i => i != null)); + + // Iteration ends when all groups have been exhausted of items. + if (candidateGroup == null) + break; + + // Find the index of the first non-null (i.e. unused) item in the group. + int candidateItemIndex = 0; + RearrangeableListItem candidateItem = null; + + for (int i = 0; i < candidateGroup.items.Length; i++) + { + if (candidateGroup.items[i] != null) + { + candidateItemIndex = i; + candidateItem = candidateGroup.items[i]; + } + } + + // The item is guaranteed to not be expired, since we've previously removed all expired items. + Debug.Assert(candidateItem?.Model.Expired == false); + + // Add the item to the result set. + result.Add(candidateItem); + + // Update the group for the next iteration. + candidateGroup.items[candidateItemIndex] = null; + groupWeights[candidateGroup.index]++; + } + + return result; + } + } + } + } + } +} From 68bb49fc1e795c86879ebcebaa6b599402531dd0 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 26 Nov 2021 20:02:30 +0900 Subject: [PATCH 15/87] Add QueueList tests --- .../TestSceneMultiplayerQueueList.cs | 153 ++++++++++++++++++ .../Multiplayer/Match/Playlist/QueueList.cs | 2 +- 2 files changed, 154 insertions(+), 1 deletion(-) create mode 100644 osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs new file mode 100644 index 0000000000..b08b76a7ac --- /dev/null +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs @@ -0,0 +1,153 @@ +// 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.Graphics; +using osu.Framework.Testing; +using osu.Game.Online.Multiplayer; +using osu.Game.Online.Rooms; +using osu.Game.Rulesets.Osu; +using osu.Game.Screens.OnlinePlay; +using osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist; +using osu.Game.Tests.Beatmaps; + +namespace osu.Game.Tests.Visual.Multiplayer +{ + public class TestSceneMultiplayerQueueList : OsuTestScene + { + private QueueList list; + private int currentItemId; + + [SetUp] + public void Setup() => Schedule(() => + { + Child = list = new QueueList(false, false) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Width = 0.4f, + Height = 0.6f + }; + }); + + [Test] + public void TestItemsAddedToEndInHostOnlyMode() + { + changeQueueModeStep(QueueMode.HostOnly); + + // User 1. + + PlaylistItem item1 = addItemStep(1); + assertPositionStep(item1, 0); + + PlaylistItem item2 = addItemStep(1); + assertPositionStep(item2, 1); + + // User 2. + + PlaylistItem item3 = addItemStep(2); + assertPositionStep(item3, 2); + } + + [Test] + public void TestItemsAddedToEndInAllPlayersMode() + { + changeQueueModeStep(QueueMode.AllPlayers); + + // User 1. + + PlaylistItem item1 = addItemStep(1); + assertPositionStep(item1, 0); + + PlaylistItem item2 = addItemStep(1); + assertPositionStep(item2, 1); + + // User 2. + + PlaylistItem item3 = addItemStep(2); + assertPositionStep(item3, 2); + } + + [Test] + public void TestItemsInsertedInCorrectPositionInRoundRobinMode() + { + changeQueueModeStep(QueueMode.AllPlayersRoundRobin); + + // User 1. + + PlaylistItem item1 = addItemStep(1); + assertPositionStep(item1, 0); + + PlaylistItem item2 = addItemStep(1); + assertPositionStep(item2, 1); + + // User 2. + + PlaylistItem item3 = addItemStep(2); + assertPositionStep(item3, 1); + assertPositionStep(item2, 2); + + PlaylistItem item4 = addItemStep(2); + assertPositionStep(item4, 3); + + PlaylistItem item5 = addItemStep(2); + assertPositionStep(item5, 4); + + // User 1. + + PlaylistItem item6 = addItemStep(1); + assertPositionStep(item6, 4); + assertPositionStep(item5, 5); + + // User 3. + + PlaylistItem item7 = addItemStep(3); + assertPositionStep(item7, 2); + + PlaylistItem item8 = addItemStep(3); + assertPositionStep(item8, 5); + + PlaylistItem item9 = addItemStep(3); + assertPositionStep(item9, 8); + } + + /// + /// Adds a step to create a new playlist item. + /// + /// The item owner. + /// The playlist item's ID. + private PlaylistItem addItemStep(int ownerId) + { + var item = new PlaylistItem + { + ID = ++currentItemId, + OwnerID = ownerId, + Beatmap = { Value = new TestBeatmap(new OsuRuleset().RulesetInfo, false).BeatmapInfo } + }; + + AddStep($"add {{ item: {item.ID}, user: {ownerId} }}", () => list.Items.Add(item)); + + return item; + } + + /// + /// Asserts the position of a given playlist item in the visual layout of the list. + /// + /// The playlist item. + /// The index at which the item should appear visually. The item with index 0 is at the top of the list. + private void assertPositionStep(PlaylistItem item, int visualIndex) + { + AddUntilStep($"item {item.ID} has pos = {visualIndex}", () => + { + return this.ChildrenOfType() + .OrderBy(drawable => drawable.Position.Y) + .TakeWhile(drawable => drawable.Item.ID != item.ID) + .Count() == visualIndex; + }); + } + + private void changeQueueModeStep(QueueMode newMode) => AddStep($"change queue mode to {newMode}", () => list.QueueMode.Value = newMode); + } +} diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/QueueList.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/QueueList.cs index 5b691fdfda..971196865b 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/QueueList.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/QueueList.cs @@ -15,7 +15,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist { public class QueueList : DrawableRoomPlaylist { - public readonly IBindable QueueMode = new Bindable(); + public readonly Bindable QueueMode = new Bindable(); public QueueList(bool allowEdit, bool allowSelection, bool reverse = false) : base(allowEdit, allowSelection, reverse) From 9806c75743387baa015630b611f4355451fa2ae2 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 26 Nov 2021 21:13:21 +0900 Subject: [PATCH 16/87] Implement better round robin algorithm --- .../Multiplayer/Match/Playlist/QueueList.cs | 77 ++++++------------- 1 file changed, 23 insertions(+), 54 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/QueueList.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/QueueList.cs index 971196865b..6d4ea5c7fa 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/QueueList.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/QueueList.cs @@ -1,8 +1,8 @@ // 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.Diagnostics; using System.Linq; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -50,66 +50,35 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist .OrderBy(item => item.Model.ID); case Game.Online.Multiplayer.QueueMode.AllPlayersRoundRobin: - // TODO: THIS IS SO INEFFICIENT, can it be done any better? + RearrangeableListItem[] allItems = AliveInternalChildren + .Where(d => d.IsPresent) + .OfType>() + .OrderBy(item => item.Model.ID) + .ToArray(); - // Group all items by their owners. - var groups = AliveInternalChildren.Where(d => d.IsPresent) - .OfType>() - .GroupBy(item => item.Model.OwnerID) - .Select(g => g.ToArray()) - .ToArray(); - - if (groups.Length == 0) + int totalCount = allItems.Length; + if (totalCount == 0) return Enumerable.Empty(); - // Find the initial picking order for the groups. The group with the smallest 'weight' picks first. - int[] groupWeights = new int[groups.Length]; + Dictionary perUserCounts = allItems + .Select(item => item.Model.OwnerID) + .Distinct() + .ToDictionary(u => u, _ => 0); - for (int i = 0; i < groups.Length; i++) + List result = new List(); + + while (totalCount-- > 0) { - groupWeights[i] = groups[i].Count(item => item.Model.Expired); - groups[i] = groups[i].Where(item => !item.Model.Expired).ToArray(); - } + var candidateItem = allItems + .Where(item => item != null) + .OrderBy(item => perUserCounts[item.Model.OwnerID]) + .First(); - var result = new List(); + int itemIndex = Array.IndexOf(allItems, candidateItem); - // Simulate the playlist by picking in order from the smallest-weighted room each time until no longer able to. - while (true) - { - var candidateGroup = groups - // Map each group to an index. - .Select((items, index) => new { index, items }) - // Order groups by their weights. - .OrderBy(group => groupWeights[group.index]) - // Select the first group with remaining items (null is set from previous iterations). - .FirstOrDefault(group => group.items.Any(i => i != null)); - - // Iteration ends when all groups have been exhausted of items. - if (candidateGroup == null) - break; - - // Find the index of the first non-null (i.e. unused) item in the group. - int candidateItemIndex = 0; - RearrangeableListItem candidateItem = null; - - for (int i = 0; i < candidateGroup.items.Length; i++) - { - if (candidateGroup.items[i] != null) - { - candidateItemIndex = i; - candidateItem = candidateGroup.items[i]; - } - } - - // The item is guaranteed to not be expired, since we've previously removed all expired items. - Debug.Assert(candidateItem?.Model.Expired == false); - - // Add the item to the result set. - result.Add(candidateItem); - - // Update the group for the next iteration. - candidateGroup.items[candidateItemIndex] = null; - groupWeights[candidateGroup.index]++; + result.Add(allItems[itemIndex]); + perUserCounts[candidateItem.Model.OwnerID]++; + allItems[itemIndex] = null; } return result; From 48a181af1f002df2ead3c7c1764497acd3c26a5f Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 30 Nov 2021 19:09:45 +0900 Subject: [PATCH 17/87] Fix test button display --- .../Visual/Multiplayer/TestSceneMultiplayerQueueList.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs index b08b76a7ac..f52683df8e 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs @@ -32,6 +32,13 @@ namespace osu.Game.Tests.Visual.Multiplayer }; }); + [SetUpSteps] + public void SetUpSteps() + { + // Not scheduled since this should affect buttons added by the current test. + currentItemId = 0; + } + [Test] public void TestItemsAddedToEndInHostOnlyMode() { From 7e800659aa1fec2acac5b70e48f30efba4bbf260 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 30 Nov 2021 19:09:52 +0900 Subject: [PATCH 18/87] Fix incorrect assertion --- .../Visual/Multiplayer/TestSceneMultiplayerQueueList.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs index f52683df8e..c88b8bf4d5 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs @@ -104,9 +104,10 @@ namespace osu.Game.Tests.Visual.Multiplayer // User 1. + // This item is added to the end rather than injected between item4 and item5, since both users have an equal number + // of added items at this point and this user was the last of the two to add an item. PlaylistItem item6 = addItemStep(1); - assertPositionStep(item6, 4); - assertPositionStep(item5, 5); + assertPositionStep(item6, 5); // User 3. From 6b198ce11260e8a1884de26d27b67f0e9b21f2a3 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 30 Nov 2021 20:12:18 +0900 Subject: [PATCH 19/87] Document simulation --- .../OnlinePlay/Multiplayer/Match/Playlist/QueueList.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/QueueList.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/QueueList.cs index 6d4ea5c7fa..a783e66ee6 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/QueueList.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/QueueList.cs @@ -67,6 +67,10 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist List result = new List(); + // Run a simulation... + // In every iteration, pick the first available item from the user with the lowest number of items in the queue to add to the result set. + // If multiple users have the same number of items in the queue, then the item with the lowest ID is chosen. + // Todo: This could probably be more efficient, likely at the cost of increased complexity. while (totalCount-- > 0) { var candidateItem = allItems From 89d22824c354dedbc19d8312ccb6f7fa5402a98c Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 30 Nov 2021 20:54:20 +0900 Subject: [PATCH 20/87] Fix incorrect comment --- .../Visual/Multiplayer/TestSceneMultiplayerQueueList.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs index c88b8bf4d5..d6b53819c9 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs @@ -35,7 +35,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [SetUpSteps] public void SetUpSteps() { - // Not scheduled since this should affect buttons added by the current test. + // Not inside a step since this is used to affect steps added by the current test. currentItemId = 0; } From 01108016a7f45c7e9d47a1cc77d46128846ffd9f Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 30 Nov 2021 21:13:38 +0900 Subject: [PATCH 21/87] Add reorder test --- .../TestSceneMultiplayerQueueList.cs | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs index d6b53819c9..83c611e325 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.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.Collections.Generic; using System.Linq; using NUnit.Framework; using osu.Framework.Graphics; @@ -121,6 +122,28 @@ namespace osu.Game.Tests.Visual.Multiplayer assertPositionStep(item9, 8); } + [Test] + public void TestItemsReorderedWhenQueueModeChanged() + { + changeQueueModeStep(QueueMode.AllPlayers); + + var items = new List(); + + for (int i = 0; i < 8; i++) + items.Add(addItemStep(i <= 3 ? 1 : 2)); + + for (int i = 0; i < 8; i++) + assertPositionStep(items[i], i); + + changeQueueModeStep(QueueMode.AllPlayersRoundRobin); + + for (int i = 0; i < 4; i++) + { + assertPositionStep(items[i], i * 2); // Items by user 1. + assertPositionStep(items[i + 4], i * 2 + 1); // Items by user 2. + } + } + /// /// Adds a step to create a new playlist item. /// From bfd2dc28c896e109926f8111bb7891e7ccf1234b Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 1 Dec 2021 16:56:32 +0900 Subject: [PATCH 22/87] Rename QueueList -> MultiplayerQueueList --- .../Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs | 8 ++++---- .../Visual/Multiplayer/TestSceneMultiplayerQueueList.cs | 4 ++-- .../Playlist/{QueueList.cs => MultiplayerQueueList.cs} | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) rename osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/{QueueList.cs => MultiplayerQueueList.cs} (96%) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs index 3244e9420d..977a6f0115 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs @@ -74,7 +74,7 @@ namespace osu.Game.Tests.Visual.Multiplayer public class MultiplayerPlaylist : MultiplayerRoomComposite { - private QueueList queueList; + private MultiplayerQueueList multiplayerQueueList; private DrawableRoomPlaylist historyList; private bool firstPopulation = true; @@ -88,7 +88,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { new Drawable[] { - queueList = new QueueList(false, false, true) + multiplayerQueueList = new MultiplayerQueueList(false, false, true) { RelativeSizeAxes = Axes.Both }, @@ -123,14 +123,14 @@ namespace osu.Game.Tests.Visual.Multiplayer if (item.Expired) historyList.Items.Add(getPlaylistItem(item)); else - queueList.Items.Add(getPlaylistItem(item)); + multiplayerQueueList.Items.Add(getPlaylistItem(item)); } protected override void PlaylistItemRemoved(long item) { base.PlaylistItemRemoved(item); - queueList.Items.RemoveAll(i => i.ID == item); + multiplayerQueueList.Items.RemoveAll(i => i.ID == item); } protected override void PlaylistItemChanged(MultiplayerPlaylistItem item) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs index 83c611e325..2186d2e594 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs @@ -17,13 +17,13 @@ namespace osu.Game.Tests.Visual.Multiplayer { public class TestSceneMultiplayerQueueList : OsuTestScene { - private QueueList list; + private MultiplayerQueueList list; private int currentItemId; [SetUp] public void Setup() => Schedule(() => { - Child = list = new QueueList(false, false) + Child = list = new MultiplayerQueueList(false, false) { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/QueueList.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerQueueList.cs similarity index 96% rename from osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/QueueList.cs rename to osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerQueueList.cs index a783e66ee6..de3ad7b824 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/QueueList.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerQueueList.cs @@ -13,11 +13,11 @@ using osuTK; namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist { - public class QueueList : DrawableRoomPlaylist + public class MultiplayerQueueList : DrawableRoomPlaylist { public readonly Bindable QueueMode = new Bindable(); - public QueueList(bool allowEdit, bool allowSelection, bool reverse = false) + public MultiplayerQueueList(bool allowEdit, bool allowSelection, bool reverse = false) : base(allowEdit, allowSelection, reverse) { } From e0ca1af9b896565bf41f2bb8d75d4ae0413b59c4 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 1 Dec 2021 16:58:48 +0900 Subject: [PATCH 23/87] Remove ctor params --- .../Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs | 2 +- .../Visual/Multiplayer/TestSceneMultiplayerQueueList.cs | 2 +- .../Multiplayer/Match/Playlist/MultiplayerQueueList.cs | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs index 977a6f0115..b9f147e44a 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs @@ -88,7 +88,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { new Drawable[] { - multiplayerQueueList = new MultiplayerQueueList(false, false, true) + multiplayerQueueList = new MultiplayerQueueList { RelativeSizeAxes = Axes.Both }, diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs index 2186d2e594..2e24d54b59 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs @@ -23,7 +23,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [SetUp] public void Setup() => Schedule(() => { - Child = list = new MultiplayerQueueList(false, false) + Child = list = new MultiplayerQueueList { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerQueueList.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerQueueList.cs index de3ad7b824..802603d7e1 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerQueueList.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerQueueList.cs @@ -17,8 +17,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist { public readonly Bindable QueueMode = new Bindable(); - public MultiplayerQueueList(bool allowEdit, bool allowSelection, bool reverse = false) - : base(allowEdit, allowSelection, reverse) + public MultiplayerQueueList() + : base(false, false, false, true) { } From fc8c8685b87dd958a8cdeb0d1abba6157a120926 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 1 Dec 2021 18:44:46 +0900 Subject: [PATCH 24/87] Add playlist queue tests --- .../TestSceneMultiplayerPlaylist.cs | 145 +++++++++++++++--- .../Match/Playlist/MultiplayerHistoryList.cs | 30 ++++ .../Multiplayer/TestMultiplayerClient.cs | 4 +- 3 files changed, 158 insertions(+), 21 deletions(-) create mode 100644 osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerHistoryList.cs diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs index b9f147e44a..136625682a 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs @@ -10,7 +10,6 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Platform; using osu.Framework.Testing; using osu.Game.Beatmaps; -using osu.Game.Database; using osu.Game.Online.Multiplayer; using osu.Game.Online.Rooms; using osu.Game.Rulesets; @@ -29,9 +28,6 @@ namespace osu.Game.Tests.Visual.Multiplayer private BeatmapSetInfo importedSet; private BeatmapInfo importedBeatmap; - [Cached(typeof(UserLookupCache))] - private UserLookupCache lookupCache = new TestUserLookupCache(); - [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { @@ -60,22 +56,132 @@ namespace osu.Game.Tests.Visual.Multiplayer importedSet = beatmaps.GetAllUsableBeatmapSetsEnumerable(IncludedDetails.All).First(); importedBeatmap = importedSet.Beatmaps.First(b => b.RulesetID == 0); }); + + AddStep("change to all players mode", () => Client.ChangeSettings(new MultiplayerRoomSettings { QueueMode = QueueMode.AllPlayers })); } [Test] - public void DoTest() + public void TestNonExpiredItemsAddedToQueueList() { - AddStep("change to round robin mode", () => Client.ChangeSettings(new MultiplayerRoomSettings { QueueMode = QueueMode.AllPlayersRoundRobin })); - AddStep("add playlist item for user 1", () => Client.AddPlaylistItem(new MultiplayerPlaylistItem - { - BeatmapID = importedBeatmap.OnlineID!.Value - })); + assertItemInQueueListStep(1, 0); + + addItemStep(); + assertItemInQueueListStep(2, 1); + + addItemStep(); + assertItemInQueueListStep(3, 2); + } + + [Test] + public void TestExpiredItemsAddedToHistoryList() + { + assertItemInQueueListStep(1, 0); + + addItemStep(true); + assertItemInHistoryListStep(2, 0); + + addItemStep(true); + assertItemInHistoryListStep(3, 0); + assertItemInHistoryListStep(2, 1); + + // Initial item is still in the queue. + assertItemInQueueListStep(1, 0); + } + + [Test] + public void TestExpiredItemsMoveToQueueList() + { + addItemStep(); + addItemStep(); + + AddStep("finish current item", () => Client.FinishCurrentItem()); + + assertItemInHistoryListStep(1, 0); + assertItemInQueueListStep(2, 0); + assertItemInQueueListStep(3, 1); + + AddStep("finish current item", () => Client.FinishCurrentItem()); + + assertItemInHistoryListStep(2, 0); + assertItemInHistoryListStep(1, 1); + assertItemInQueueListStep(3, 0); + + AddStep("finish current item", () => Client.FinishCurrentItem()); + + assertItemInHistoryListStep(3, 0); + assertItemInHistoryListStep(2, 1); + assertItemInHistoryListStep(1, 2); + } + + [Test] + public void TestJoinRoomWithMixedItemsAddedInCorrectLists() + { + AddStep("leave room", () => RoomManager.PartRoom()); + AddUntilStep("wait for room part", () => Client.Room == null); + } + + /// + /// Adds a step to create a new playlist item. + /// + private void addItemStep(bool expired = false) => AddStep("add item", () => Client.AddPlaylistItem(new MultiplayerPlaylistItem(new PlaylistItem + { + Beatmap = { Value = importedBeatmap }, + BeatmapID = importedBeatmap.OnlineID ?? -1, + Expired = expired + }))); + + /// + /// Asserts the position of a given playlist item in the queue list. + /// + /// The item id. + /// The index at which the item should appear visually. The item with index 0 is at the top of the list. + private void assertItemInQueueListStep(int playlistItemId, int visualIndex) => AddUntilStep($"{playlistItemId} in queue at pos = {visualIndex}", () => + { + return !inHistoryList(playlistItemId) + && this.ChildrenOfType() + .Single() + .ChildrenOfType() + .OrderBy(drawable => drawable.Position.Y) + .TakeWhile(drawable => drawable.Item.ID != playlistItemId) + .Count() == visualIndex; + }); + + /// + /// Asserts the position of a given playlist item in the history list. + /// + /// The item id. + /// The index at which the item should appear visually. The item with index 0 is at the top of the list. + private void assertItemInHistoryListStep(int playlistItemId, int visualIndex) => AddUntilStep($"{playlistItemId} in history at pos = {visualIndex}", () => + { + return !inQueueList(playlistItemId) + && this.ChildrenOfType() + .Single() + .ChildrenOfType() + .OrderBy(drawable => drawable.Position.Y) + .TakeWhile(drawable => drawable.Item.ID != playlistItemId) + .Count() == visualIndex; + }); + + private bool inQueueList(int playlistItemId) + { + return this.ChildrenOfType() + .Single() + .ChildrenOfType() + .Any(i => i.Item.ID == playlistItemId); + } + + private bool inHistoryList(int playlistItemId) + { + return this.ChildrenOfType() + .Single() + .ChildrenOfType() + .Any(i => i.Item.ID == playlistItemId); } public class MultiplayerPlaylist : MultiplayerRoomComposite { - private MultiplayerQueueList multiplayerQueueList; - private DrawableRoomPlaylist historyList; + private MultiplayerQueueList queueList; + private MultiplayerHistoryList historyList; private bool firstPopulation = true; [BackgroundDependencyLoader] @@ -88,11 +194,11 @@ namespace osu.Game.Tests.Visual.Multiplayer { new Drawable[] { - multiplayerQueueList = new MultiplayerQueueList + queueList = new MultiplayerQueueList { RelativeSizeAxes = Axes.Both }, - historyList = new DrawableRoomPlaylist(false, false, true) + historyList = new MultiplayerHistoryList { RelativeSizeAxes = Axes.Both } @@ -120,17 +226,20 @@ namespace osu.Game.Tests.Visual.Multiplayer { base.PlaylistItemAdded(item); + var apiItem = Playlist.Single(i => i.ID == item.ID); + if (item.Expired) - historyList.Items.Add(getPlaylistItem(item)); + historyList.Items.Add(apiItem); else - multiplayerQueueList.Items.Add(getPlaylistItem(item)); + queueList.Items.Add(apiItem); } protected override void PlaylistItemRemoved(long item) { base.PlaylistItemRemoved(item); - multiplayerQueueList.Items.RemoveAll(i => i.ID == item); + queueList.Items.RemoveAll(i => i.ID == item); + historyList.Items.RemoveAll(i => i.ID == item); } protected override void PlaylistItemChanged(MultiplayerPlaylistItem item) @@ -140,8 +249,6 @@ namespace osu.Game.Tests.Visual.Multiplayer PlaylistItemRemoved(item.ID); PlaylistItemAdded(item); } - - private PlaylistItem getPlaylistItem(MultiplayerPlaylistItem item) => Playlist.Single(i => i.ID == item.ID); } } } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerHistoryList.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerHistoryList.cs new file mode 100644 index 0000000000..76bb364f3a --- /dev/null +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerHistoryList.cs @@ -0,0 +1,30 @@ +// 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.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Online.Rooms; +using osuTK; + +namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist +{ + public class MultiplayerHistoryList : DrawableRoomPlaylist + { + public MultiplayerHistoryList() + : base(false, false, false, true) + { + } + + protected override FillFlowContainer> CreateListFillFlowContainer() => new HistoryFillFlowContainer + { + Spacing = new Vector2(0, 2) + }; + + private class HistoryFillFlowContainer : FillFlowContainer> + { + public override IEnumerable FlowingChildren => base.FlowingChildren.Reverse(); + } + } +} diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index 2d77e17513..b746f7e667 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -145,7 +145,7 @@ namespace osu.Game.Tests.Visual.Multiplayer ((IMultiplayerClient)this).ResultsReady(); - finishCurrentItem().Wait(); + FinishCurrentItem().Wait(); } break; @@ -390,7 +390,7 @@ namespace osu.Game.Tests.Visual.Multiplayer await updateCurrentItem(Room).ConfigureAwait(false); } - private async Task finishCurrentItem() + public async Task FinishCurrentItem() { Debug.Assert(Room != null); Debug.Assert(APIRoom != null); From a4cd22d5a97295bb2de4b53026ae33afd1a40e56 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 1 Dec 2021 18:57:29 +0900 Subject: [PATCH 25/87] Clear lists on room leave --- .../Multiplayer/TestSceneMultiplayerPlaylist.cs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs index 136625682a..74284e21f1 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs @@ -113,6 +113,19 @@ namespace osu.Game.Tests.Visual.Multiplayer assertItemInHistoryListStep(1, 2); } + [Test] + public void TestListsClearedWhenRoomLeft() + { + addItemStep(); + AddStep("finish current item", () => Client.FinishCurrentItem()); + + AddStep("leave room", () => RoomManager.PartRoom()); + AddUntilStep("wait for room part", () => Client.Room == null); + + AddUntilStep("item 0 not in lists", () => !inHistoryList(0) && !inQueueList(0)); + AddUntilStep("item 1 not in lists", () => !inHistoryList(0) && !inQueueList(0)); + } + [Test] public void TestJoinRoomWithMixedItemsAddedInCorrectLists() { @@ -212,7 +225,11 @@ namespace osu.Game.Tests.Visual.Multiplayer base.OnRoomUpdated(); if (Room == null) + { + historyList.Items.Clear(); + queueList.Items.Clear(); return; + } if (!firstPopulation) return; From 0b3cc47a51645d67892ac1182a4a9d3b8141eb6b Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 1 Dec 2021 19:02:17 +0900 Subject: [PATCH 26/87] Fix list not repopulating on new room --- .../TestSceneMultiplayerPlaylist.cs | 40 ++++++++++++++++--- 1 file changed, 35 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs index 74284e21f1..df34e3b6af 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs @@ -16,6 +16,7 @@ using osu.Game.Rulesets; using osu.Game.Screens.OnlinePlay; using osu.Game.Screens.OnlinePlay.Multiplayer; using osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist; +using osu.Game.Tests.Beatmaps; using osu.Game.Tests.Resources; using osuTK; @@ -131,6 +132,33 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("leave room", () => RoomManager.PartRoom()); AddUntilStep("wait for room part", () => Client.Room == null); + + AddStep("join room with items", () => + { + RoomManager.CreateRoom(new Room + { + Name = { Value = "test name" }, + Playlist = + { + new PlaylistItem + { + Beatmap = { Value = new TestBeatmap(Ruleset.Value).BeatmapInfo }, + Ruleset = { Value = Ruleset.Value } + }, + new PlaylistItem + { + Beatmap = { Value = new TestBeatmap(Ruleset.Value).BeatmapInfo }, + Ruleset = { Value = Ruleset.Value }, + Expired = true + } + } + }); + }); + + AddUntilStep("wait for room join", () => RoomJoined); + + assertItemInQueueListStep(1, 0); + assertItemInHistoryListStep(2, 0); } /// @@ -228,15 +256,17 @@ namespace osu.Game.Tests.Visual.Multiplayer { historyList.Items.Clear(); queueList.Items.Clear(); + firstPopulation = true; return; } - if (!firstPopulation) return; + if (firstPopulation) + { + foreach (var item in Room.Playlist) + PlaylistItemAdded(item); - foreach (var item in Room.Playlist) - PlaylistItemAdded(item); - - firstPopulation = false; + firstPopulation = false; + } } protected override void PlaylistItemAdded(MultiplayerPlaylistItem item) From 11c137cf83523a76904a6f2eb539f80b07a30874 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 1 Dec 2021 19:22:21 +0900 Subject: [PATCH 27/87] Ignore test --- .../Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs index df34e3b6af..3117db8900 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs @@ -127,6 +127,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("item 1 not in lists", () => !inHistoryList(0) && !inQueueList(0)); } + [Ignore("Expired items are initially removed from the room.")] [Test] public void TestJoinRoomWithMixedItemsAddedInCorrectLists() { From 95050d6597e559879b6103899979e7529f00d936 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 1 Dec 2021 19:23:35 +0900 Subject: [PATCH 28/87] Extract class to file --- .../TestSceneMultiplayerPlaylist.cs | 81 ----------------- .../Match/Playlist/MultiplayerPlaylist.cs | 90 +++++++++++++++++++ 2 files changed, 90 insertions(+), 81 deletions(-) create mode 100644 osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerPlaylist.cs diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs index 3117db8900..b21dbbd462 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs @@ -6,7 +6,6 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Framework.Platform; using osu.Framework.Testing; using osu.Game.Beatmaps; @@ -14,7 +13,6 @@ using osu.Game.Online.Multiplayer; using osu.Game.Online.Rooms; using osu.Game.Rulesets; using osu.Game.Screens.OnlinePlay; -using osu.Game.Screens.OnlinePlay.Multiplayer; using osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist; using osu.Game.Tests.Beatmaps; using osu.Game.Tests.Resources; @@ -219,84 +217,5 @@ namespace osu.Game.Tests.Visual.Multiplayer .ChildrenOfType() .Any(i => i.Item.ID == playlistItemId); } - - public class MultiplayerPlaylist : MultiplayerRoomComposite - { - private MultiplayerQueueList queueList; - private MultiplayerHistoryList historyList; - private bool firstPopulation = true; - - [BackgroundDependencyLoader] - private void load() - { - InternalChild = new GridContainer - { - RelativeSizeAxes = Axes.Both, - Content = new[] - { - new Drawable[] - { - queueList = new MultiplayerQueueList - { - RelativeSizeAxes = Axes.Both - }, - historyList = new MultiplayerHistoryList - { - RelativeSizeAxes = Axes.Both - } - } - } - }; - } - - protected override void OnRoomUpdated() - { - base.OnRoomUpdated(); - - if (Room == null) - { - historyList.Items.Clear(); - queueList.Items.Clear(); - firstPopulation = true; - return; - } - - if (firstPopulation) - { - foreach (var item in Room.Playlist) - PlaylistItemAdded(item); - - firstPopulation = false; - } - } - - protected override void PlaylistItemAdded(MultiplayerPlaylistItem item) - { - base.PlaylistItemAdded(item); - - var apiItem = Playlist.Single(i => i.ID == item.ID); - - if (item.Expired) - historyList.Items.Add(apiItem); - else - queueList.Items.Add(apiItem); - } - - protected override void PlaylistItemRemoved(long item) - { - base.PlaylistItemRemoved(item); - - queueList.Items.RemoveAll(i => i.ID == item); - historyList.Items.RemoveAll(i => i.ID == item); - } - - protected override void PlaylistItemChanged(MultiplayerPlaylistItem item) - { - base.PlaylistItemChanged(item); - - PlaylistItemRemoved(item.ID); - PlaylistItemAdded(item); - } - } } } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerPlaylist.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerPlaylist.cs new file mode 100644 index 0000000000..f853333b08 --- /dev/null +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerPlaylist.cs @@ -0,0 +1,90 @@ +// 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 osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Online.Rooms; + +namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist +{ + public class MultiplayerPlaylist : MultiplayerRoomComposite + { + private MultiplayerQueueList queueList; + private MultiplayerHistoryList historyList; + private bool firstPopulation = true; + + [BackgroundDependencyLoader] + private void load() + { + InternalChild = new GridContainer + { + RelativeSizeAxes = Axes.Both, + Content = new[] + { + new Drawable[] + { + queueList = new MultiplayerQueueList + { + RelativeSizeAxes = Axes.Both + }, + historyList = new MultiplayerHistoryList + { + RelativeSizeAxes = Axes.Both + } + } + } + }; + } + + protected override void OnRoomUpdated() + { + base.OnRoomUpdated(); + + if (Room == null) + { + historyList.Items.Clear(); + queueList.Items.Clear(); + firstPopulation = true; + return; + } + + if (firstPopulation) + { + foreach (var item in Room.Playlist) + PlaylistItemAdded(item); + + firstPopulation = false; + } + } + + protected override void PlaylistItemAdded(MultiplayerPlaylistItem item) + { + base.PlaylistItemAdded(item); + + var apiItem = Playlist.Single(i => i.ID == item.ID); + + if (item.Expired) + historyList.Items.Add(apiItem); + else + queueList.Items.Add(apiItem); + } + + protected override void PlaylistItemRemoved(long item) + { + base.PlaylistItemRemoved(item); + + queueList.Items.RemoveAll(i => i.ID == item); + historyList.Items.RemoveAll(i => i.ID == item); + } + + protected override void PlaylistItemChanged(MultiplayerPlaylistItem item) + { + base.PlaylistItemChanged(item); + + PlaylistItemRemoved(item.ID); + PlaylistItemAdded(item); + } + } +} From 7847ce6253e5c3bc8ba43b838852273d65f8bc68 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 1 Dec 2021 19:44:29 +0900 Subject: [PATCH 29/87] Redesign with tab control --- .../Match/Playlist/MultiplayerPlaylist.cs | 39 ++++++++++++++++--- 1 file changed, 33 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerPlaylist.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerPlaylist.cs index f853333b08..a44e086814 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerPlaylist.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerPlaylist.cs @@ -3,8 +3,11 @@ using System.Linq; using osu.Framework.Allocation; +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.Online.Rooms; namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist @@ -18,24 +21,42 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist [BackgroundDependencyLoader] private void load() { - InternalChild = new GridContainer + TabControl displayModeTabControl; + + InternalChildren = new Drawable[] { - RelativeSizeAxes = Axes.Both, - Content = new[] + displayModeTabControl = new OsuTabControl { - new Drawable[] + RelativeSizeAxes = Axes.X, + Height = 25 + }, + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Top = 27 }, + Masking = true, + Children = new Drawable[] { queueList = new MultiplayerQueueList { - RelativeSizeAxes = Axes.Both + RelativeSizeAxes = Axes.Both, }, historyList = new MultiplayerHistoryList { - RelativeSizeAxes = Axes.Both + RelativeSizeAxes = Axes.Both, + Alpha = 0, } } } }; + + displayModeTabControl.Current.BindValueChanged(onDisplayModeChanged, true); + } + + private void onDisplayModeChanged(ValueChangedEvent mode) + { + historyList.FadeTo(mode.NewValue == DisplayMode.History ? 1 : 0, 100); + queueList.FadeTo(mode.NewValue == DisplayMode.Queue ? 1 : 0, 100); } protected override void OnRoomUpdated() @@ -86,5 +107,11 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist PlaylistItemRemoved(item.ID); PlaylistItemAdded(item); } + + private enum DisplayMode + { + Queue, + History, + } } } From 1152c4e8e9bb91b6dc5209645cc9f2da69ac5eb0 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 1 Dec 2021 19:54:39 +0900 Subject: [PATCH 30/87] Fix tests --- .../TestSceneMultiplayerPlaylist.cs | 57 +++++++++++-------- .../Match/Playlist/MultiplayerPlaylist.cs | 31 +++++----- .../MultiplayerPlaylistDisplayMode.cs | 11 ++++ 3 files changed, 60 insertions(+), 39 deletions(-) create mode 100644 osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerPlaylistDisplayMode.cs diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs index b21dbbd462..ae885685f7 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs @@ -22,6 +22,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { public class TestSceneMultiplayerPlaylist : MultiplayerTestScene { + private MultiplayerPlaylist list; private BeatmapManager beatmaps; private RulesetStore rulesets; private BeatmapSetInfo importedSet; @@ -37,7 +38,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [SetUp] public new void Setup() => Schedule(() => { - Child = new MultiplayerPlaylist + Child = list = new MultiplayerPlaylist { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -175,47 +176,57 @@ namespace osu.Game.Tests.Visual.Multiplayer /// /// The item id. /// The index at which the item should appear visually. The item with index 0 is at the top of the list. - private void assertItemInQueueListStep(int playlistItemId, int visualIndex) => AddUntilStep($"{playlistItemId} in queue at pos = {visualIndex}", () => + private void assertItemInQueueListStep(int playlistItemId, int visualIndex) { - return !inHistoryList(playlistItemId) - && this.ChildrenOfType() - .Single() - .ChildrenOfType() - .OrderBy(drawable => drawable.Position.Y) - .TakeWhile(drawable => drawable.Item.ID != playlistItemId) - .Count() == visualIndex; - }); + changeDisplayModeStep(MultiplayerPlaylistDisplayMode.Queue); + + AddUntilStep($"{playlistItemId} in queue at pos = {visualIndex}", () => + { + return !inHistoryList(playlistItemId) + && this.ChildrenOfType() + .Single() + .ChildrenOfType() + .OrderBy(drawable => drawable.Position.Y) + .TakeWhile(drawable => drawable.Item.ID != playlistItemId) + .Count() == visualIndex; + }); + } /// /// Asserts the position of a given playlist item in the history list. /// /// The item id. /// The index at which the item should appear visually. The item with index 0 is at the top of the list. - private void assertItemInHistoryListStep(int playlistItemId, int visualIndex) => AddUntilStep($"{playlistItemId} in history at pos = {visualIndex}", () => + private void assertItemInHistoryListStep(int playlistItemId, int visualIndex) { - return !inQueueList(playlistItemId) - && this.ChildrenOfType() - .Single() - .ChildrenOfType() - .OrderBy(drawable => drawable.Position.Y) - .TakeWhile(drawable => drawable.Item.ID != playlistItemId) - .Count() == visualIndex; - }); + changeDisplayModeStep(MultiplayerPlaylistDisplayMode.History); + + AddUntilStep($"{playlistItemId} in history at pos = {visualIndex}", () => + { + return !inQueueList(playlistItemId) + && this.ChildrenOfType() + .Single() + .ChildrenOfType() + .OrderBy(drawable => drawable.Position.Y) + .TakeWhile(drawable => drawable.Item.ID != playlistItemId) + .Count() == visualIndex; + }); + } + + private void changeDisplayModeStep(MultiplayerPlaylistDisplayMode mode) => AddStep($"change list to {mode}", () => list.DisplayMode.Value = mode); private bool inQueueList(int playlistItemId) { return this.ChildrenOfType() .Single() - .ChildrenOfType() - .Any(i => i.Item.ID == playlistItemId); + .Items.Any(i => i.ID == playlistItemId); } private bool inHistoryList(int playlistItemId) { return this.ChildrenOfType() .Single() - .ChildrenOfType() - .Any(i => i.Item.ID == playlistItemId); + .Items.Any(i => i.ID == playlistItemId); } } } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerPlaylist.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerPlaylist.cs index a44e086814..aebe70f95a 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerPlaylist.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerPlaylist.cs @@ -6,7 +6,6 @@ using osu.Framework.Allocation; 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.Online.Rooms; @@ -14,6 +13,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist { public class MultiplayerPlaylist : MultiplayerRoomComposite { + public readonly Bindable DisplayMode = new Bindable(); + private MultiplayerQueueList queueList; private MultiplayerHistoryList historyList; private bool firstPopulation = true; @@ -21,14 +22,13 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist [BackgroundDependencyLoader] private void load() { - TabControl displayModeTabControl; - InternalChildren = new Drawable[] { - displayModeTabControl = new OsuTabControl + new OsuTabControl { RelativeSizeAxes = Axes.X, - Height = 25 + Height = 25, + Current = { BindTarget = DisplayMode } }, new Container { @@ -49,14 +49,19 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist } } }; - - displayModeTabControl.Current.BindValueChanged(onDisplayModeChanged, true); } - private void onDisplayModeChanged(ValueChangedEvent mode) + protected override void LoadComplete() { - historyList.FadeTo(mode.NewValue == DisplayMode.History ? 1 : 0, 100); - queueList.FadeTo(mode.NewValue == DisplayMode.Queue ? 1 : 0, 100); + base.LoadComplete(); + + DisplayMode.BindValueChanged(onDisplayModeChanged, true); + } + + private void onDisplayModeChanged(ValueChangedEvent mode) + { + historyList.FadeTo(mode.NewValue == MultiplayerPlaylistDisplayMode.History ? 1 : 0, 100); + queueList.FadeTo(mode.NewValue == MultiplayerPlaylistDisplayMode.Queue ? 1 : 0, 100); } protected override void OnRoomUpdated() @@ -107,11 +112,5 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist PlaylistItemRemoved(item.ID); PlaylistItemAdded(item); } - - private enum DisplayMode - { - Queue, - History, - } } } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerPlaylistDisplayMode.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerPlaylistDisplayMode.cs new file mode 100644 index 0000000000..af1fac1c79 --- /dev/null +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerPlaylistDisplayMode.cs @@ -0,0 +1,11 @@ +// 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.Match.Playlist +{ + public enum MultiplayerPlaylistDisplayMode + { + Queue, + History, + } +} From c3dfe10a8a637d472500dc67e9d4c1cd5b7bc17d Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 1 Dec 2021 19:54:48 +0900 Subject: [PATCH 31/87] Add new list to match subscreen --- .../Multiplayer/MultiplayerMatchSubScreen.cs | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index 077e9cef93..16017a1f0e 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -26,6 +26,7 @@ using osu.Game.Screens.OnlinePlay.Components; using osu.Game.Screens.OnlinePlay.Match; using osu.Game.Screens.OnlinePlay.Match.Components; using osu.Game.Screens.OnlinePlay.Multiplayer.Match; +using osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist; using osu.Game.Screens.OnlinePlay.Multiplayer.Participants; using osu.Game.Screens.OnlinePlay.Multiplayer.Spectate; using osu.Game.Screens.Play; @@ -56,8 +57,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer [CanBeNull] private IDisposable readyClickOperation; - private DrawableRoomPlaylist playlist; - public MultiplayerMatchSubScreen(Room room) : base(room) { @@ -74,9 +73,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer BeatmapAvailability.BindValueChanged(updateBeatmapAvailability, true); UserMods.BindValueChanged(onUserModsChanged); - playlist.Items.BindTo(Room.Playlist); - playlist.SelectedItem.BindTo(SelectedItem); - client.LoadRequested += onLoadRequested; client.RoomUpdated += onRoomUpdated; @@ -153,10 +149,10 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer null, new Drawable[] { - playlist = new DrawableRoomPlaylist(false, false, true, true) + new MultiplayerPlaylist { - RelativeSizeAxes = Axes.Both, - }, + RelativeSizeAxes = Axes.Both + } }, new[] { From d70355237df75dd4b8e374fed92e4f3ab7b7bd7a Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 1 Dec 2021 19:55:53 +0900 Subject: [PATCH 32/87] Fix selected item not bound --- .../Multiplayer/Match/Playlist/MultiplayerPlaylist.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerPlaylist.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerPlaylist.cs index aebe70f95a..8ab6531300 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerPlaylist.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerPlaylist.cs @@ -40,11 +40,13 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist queueList = new MultiplayerQueueList { RelativeSizeAxes = Axes.Both, + SelectedItem = { BindTarget = SelectedItem } }, historyList = new MultiplayerHistoryList { RelativeSizeAxes = Axes.Both, Alpha = 0, + SelectedItem = { BindTarget = SelectedItem } } } } From 93a7726f4a39410dfcc38cf14a0ebd9126acca75 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 1 Dec 2021 20:24:14 +0900 Subject: [PATCH 33/87] Remove now-unused parameter --- osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs | 2 +- .../Multiplayer/Match/Playlist/MultiplayerHistoryList.cs | 2 +- .../Multiplayer/Match/Playlist/MultiplayerQueueList.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs index 3592a51b4a..14c5bd5240 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs @@ -20,7 +20,7 @@ namespace osu.Game.Screens.OnlinePlay private readonly bool allowSelection; private readonly bool showItemOwner; - public DrawableRoomPlaylist(bool allowEdit, bool allowSelection, bool reverse = false, bool showItemOwner = false) + public DrawableRoomPlaylist(bool allowEdit, bool allowSelection, bool showItemOwner = false) { this.allowEdit = allowEdit; this.allowSelection = allowSelection; diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerHistoryList.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerHistoryList.cs index 76bb364f3a..63b2925c41 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerHistoryList.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerHistoryList.cs @@ -13,7 +13,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist public class MultiplayerHistoryList : DrawableRoomPlaylist { public MultiplayerHistoryList() - : base(false, false, false, true) + : base(false, false, true) { } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerQueueList.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerQueueList.cs index 802603d7e1..7292e2edca 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerQueueList.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerQueueList.cs @@ -18,7 +18,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist public readonly Bindable QueueMode = new Bindable(); public MultiplayerQueueList() - : base(false, false, false, true) + : base(false, false, true) { } From e2f289eeffa1d97378aa9800ba4641b31d61bd34 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 1 Dec 2021 20:27:47 +0900 Subject: [PATCH 34/87] Xmldocs --- .../Multiplayer/Match/Playlist/MultiplayerHistoryList.cs | 3 +++ .../Multiplayer/Match/Playlist/MultiplayerPlaylist.cs | 5 ++++- .../Match/Playlist/MultiplayerPlaylistDisplayMode.cs | 3 +++ .../Multiplayer/Match/Playlist/MultiplayerQueueList.cs | 3 +++ 4 files changed, 13 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerHistoryList.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerHistoryList.cs index 63b2925c41..76088180c4 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerHistoryList.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerHistoryList.cs @@ -10,6 +10,9 @@ using osuTK; namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist { + /// + /// A historically-ordered list of s. + /// public class MultiplayerHistoryList : DrawableRoomPlaylist { public MultiplayerHistoryList() diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerPlaylist.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerPlaylist.cs index 8ab6531300..38199532e2 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerPlaylist.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerPlaylist.cs @@ -7,10 +7,14 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics.UserInterface; +using osu.Game.Online.Multiplayer; using osu.Game.Online.Rooms; namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist { + /// + /// The multiplayer playlist, containing lists to show the items from a in both gameplay-order and historical-order. + /// public class MultiplayerPlaylist : MultiplayerRoomComposite { public readonly Bindable DisplayMode = new Bindable(); @@ -56,7 +60,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist protected override void LoadComplete() { base.LoadComplete(); - DisplayMode.BindValueChanged(onDisplayModeChanged, true); } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerPlaylistDisplayMode.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerPlaylistDisplayMode.cs index af1fac1c79..cc3dca6a34 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerPlaylistDisplayMode.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerPlaylistDisplayMode.cs @@ -3,6 +3,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist { + /// + /// The type of list displayed in a . + /// public enum MultiplayerPlaylistDisplayMode { Queue, diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerQueueList.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerQueueList.cs index 7292e2edca..c1bc60becc 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerQueueList.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerQueueList.cs @@ -13,6 +13,9 @@ using osuTK; namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist { + /// + /// A gameplay-ordered list of s. + /// public class MultiplayerQueueList : DrawableRoomPlaylist { public readonly Bindable QueueMode = new Bindable(); From ad35f3434b210de9de114b88516cf192311fe3e3 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 1 Dec 2021 20:53:29 +0900 Subject: [PATCH 35/87] Fix queue list not considering expired items --- .../TestSceneMultiplayerQueueList.cs | 8 ++-- .../Match/Playlist/MultiplayerQueueList.cs | 37 ++++++++++++------- 2 files changed, 28 insertions(+), 17 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs index 2e24d54b59..efb4a395f4 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs @@ -15,13 +15,13 @@ using osu.Game.Tests.Beatmaps; namespace osu.Game.Tests.Visual.Multiplayer { - public class TestSceneMultiplayerQueueList : OsuTestScene + public class TestSceneMultiplayerQueueList : MultiplayerTestScene { private MultiplayerQueueList list; private int currentItemId; [SetUp] - public void Setup() => Schedule(() => + public new void Setup() => Schedule(() => { Child = list = new MultiplayerQueueList { @@ -34,8 +34,10 @@ namespace osu.Game.Tests.Visual.Multiplayer }); [SetUpSteps] - public void SetUpSteps() + public override void SetUpSteps() { + base.SetUpSteps(); + // Not inside a step since this is used to affect steps added by the current test. currentItemId = 0; } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerQueueList.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerQueueList.cs index c1bc60becc..9184cf6aba 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerQueueList.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerQueueList.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -35,6 +36,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist { public readonly IBindable QueueMode = new Bindable(); + [Resolved(typeof(Room), nameof(Room.Playlist))] + private BindableList roomPlaylist { get; set; } + protected override void LoadComplete() { base.LoadComplete(); @@ -53,20 +57,25 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist .OrderBy(item => item.Model.ID); case Game.Online.Multiplayer.QueueMode.AllPlayersRoundRobin: - RearrangeableListItem[] allItems = AliveInternalChildren - .Where(d => d.IsPresent) - .OfType>() - .OrderBy(item => item.Model.ID) - .ToArray(); + RearrangeableListItem[] items = AliveInternalChildren + .Where(d => d.IsPresent) + .OfType>() + .OrderBy(item => item.Model.ID) + .ToArray(); - int totalCount = allItems.Length; + int totalCount = items.Length; if (totalCount == 0) return Enumerable.Empty(); - Dictionary perUserCounts = allItems - .Select(item => item.Model.OwnerID) - .Distinct() - .ToDictionary(u => u, _ => 0); + // Count of "expired" items per user. + Dictionary perUserCounts = roomPlaylist + .Where(item => item.Expired) + .GroupBy(item => item.OwnerID) + .ToDictionary(group => group.Key, group => group.Count()); + + // Fill the count dictionary with zeroes for all users with no currently expired items. + foreach (var item in items) + perUserCounts.TryAdd(item.Model.OwnerID, 0); List result = new List(); @@ -76,16 +85,16 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist // Todo: This could probably be more efficient, likely at the cost of increased complexity. while (totalCount-- > 0) { - var candidateItem = allItems + var candidateItem = items .Where(item => item != null) .OrderBy(item => perUserCounts[item.Model.OwnerID]) .First(); - int itemIndex = Array.IndexOf(allItems, candidateItem); + int itemIndex = Array.IndexOf(items, candidateItem); - result.Add(allItems[itemIndex]); + result.Add(items[itemIndex]); perUserCounts[candidateItem.Model.OwnerID]++; - allItems[itemIndex] = null; + items[itemIndex] = null; } return result; From e87b0003fb01c89a70586dcf7ce16bda22b1f685 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 1 Dec 2021 20:54:10 +0900 Subject: [PATCH 36/87] Fix queue mode not being bound to in all cases --- .../Multiplayer/TestSceneMultiplayerQueueList.cs | 2 +- .../Match/Playlist/MultiplayerQueueList.cs | 12 +++++------- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs index efb4a395f4..9e002a7f71 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs @@ -181,6 +181,6 @@ namespace osu.Game.Tests.Visual.Multiplayer }); } - private void changeQueueModeStep(QueueMode newMode) => AddStep($"change queue mode to {newMode}", () => list.QueueMode.Value = newMode); + private void changeQueueModeStep(QueueMode newMode) => AddStep($"change queue mode to {newMode}", () => SelectedRoom.Value.QueueMode.Value = newMode); } } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerQueueList.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerQueueList.cs index 9184cf6aba..614e7133fc 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerQueueList.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerQueueList.cs @@ -19,8 +19,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist /// public class MultiplayerQueueList : DrawableRoomPlaylist { - public readonly Bindable QueueMode = new Bindable(); - public MultiplayerQueueList() : base(false, false, true) { @@ -28,13 +26,13 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist protected override FillFlowContainer> CreateListFillFlowContainer() => new QueueFillFlowContainer { - QueueMode = { BindTarget = QueueMode }, Spacing = new Vector2(0, 2) }; private class QueueFillFlowContainer : FillFlowContainer> { - public readonly IBindable QueueMode = new Bindable(); + [Resolved(typeof(Room), nameof(Room.QueueMode))] + private Bindable queueMode { get; set; } [Resolved(typeof(Room), nameof(Room.Playlist))] private BindableList roomPlaylist { get; set; } @@ -42,21 +40,21 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist protected override void LoadComplete() { base.LoadComplete(); - QueueMode.BindValueChanged(_ => InvalidateLayout()); + queueMode.BindValueChanged(_ => InvalidateLayout()); } public override IEnumerable FlowingChildren { get { - switch (QueueMode.Value) + switch (queueMode.Value) { default: return AliveInternalChildren.Where(d => d.IsPresent) .OfType>() .OrderBy(item => item.Model.ID); - case Game.Online.Multiplayer.QueueMode.AllPlayersRoundRobin: + case QueueMode.AllPlayersRoundRobin: RearrangeableListItem[] items = AliveInternalChildren .Where(d => d.IsPresent) .OfType>() From f9b4e6f004f8d8e12dfb295e055ae6fb8c07983b Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 1 Dec 2021 21:00:00 +0900 Subject: [PATCH 37/87] Add test considering expired items --- .../TestSceneMultiplayerQueueList.cs | 36 +++++++++++++++++-- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs index 9e002a7f71..bd513ddf64 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs @@ -146,21 +146,51 @@ namespace osu.Game.Tests.Visual.Multiplayer } } + [Test] + public void TestPreviouslyExpiredItemsConsideredInRoundRobinMode() + { + changeQueueModeStep(QueueMode.AllPlayersRoundRobin); + + // User 1. + + addItemStep(1, true); + addItemStep(1, true); + PlaylistItem item3 = addItemStep(1); + PlaylistItem item4 = addItemStep(1); + + // User2. + + PlaylistItem item5 = addItemStep(2); + PlaylistItem item6 = addItemStep(2); + + assertPositionStep(item5, 0); + assertPositionStep(item6, 1); + assertPositionStep(item3, 2); + assertPositionStep(item4, 3); + } + /// /// Adds a step to create a new playlist item. /// /// The item owner. + /// Whether the item should be added in an expired state. /// The playlist item's ID. - private PlaylistItem addItemStep(int ownerId) + private PlaylistItem addItemStep(int ownerId, bool expired = false) { var item = new PlaylistItem { ID = ++currentItemId, OwnerID = ownerId, - Beatmap = { Value = new TestBeatmap(new OsuRuleset().RulesetInfo, false).BeatmapInfo } + Beatmap = { Value = new TestBeatmap(new OsuRuleset().RulesetInfo, false).BeatmapInfo }, + Expired = expired }; - AddStep($"add {{ item: {item.ID}, user: {ownerId} }}", () => list.Items.Add(item)); + AddStep($"add {{ item: {item.ID}, user: {ownerId} }}", () => + { + SelectedRoom.Value.Playlist.Add(item); + if (!expired) + list.Items.Add(item); + }); return item; } From e5e2ae8ab4f682addd7b8ad3cce7d7d61a39f422 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 1 Dec 2021 21:36:25 +0900 Subject: [PATCH 38/87] Fix dangling line post-rebase --- osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs index 14c5bd5240..f2d31c8e67 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs @@ -25,8 +25,6 @@ namespace osu.Game.Screens.OnlinePlay this.allowEdit = allowEdit; this.allowSelection = allowSelection; this.showItemOwner = showItemOwner; - - ((ReversibleFillFlowContainer)ListContainer).Reverse = reverse; } protected override void LoadComplete() From 0adfb75cf36decb00fec3ac4189d38513b366408 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 2 Dec 2021 14:53:06 +0900 Subject: [PATCH 39/87] Combine similarly named `StatefulMultiplayerClient` tests --- .../StatefulMultiplayerClientTest.cs | 21 +++++++++++ .../StatefulMultiplayerClientTest.cs | 35 ------------------- 2 files changed, 21 insertions(+), 35 deletions(-) delete mode 100644 osu.Game.Tests/OnlinePlay/StatefulMultiplayerClientTest.cs diff --git a/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs b/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs index 840ff20a83..42305ccd81 100644 --- a/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs +++ b/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs @@ -16,6 +16,27 @@ namespace osu.Game.Tests.NonVisual.Multiplayer [HeadlessTest] public class StatefulMultiplayerClientTest : MultiplayerTestScene { + [Test] + public void TestUserAddedOnJoin() + { + var user = new APIUser { Id = 33 }; + + AddRepeatStep("add user multiple times", () => Client.AddUser(user), 3); + AddAssert("room has 2 users", () => Client.Room?.Users.Count == 2); + } + + [Test] + public void TestUserRemovedOnLeave() + { + var user = new APIUser { Id = 44 }; + + AddStep("add user", () => Client.AddUser(user)); + AddAssert("room has 2 users", () => Client.Room?.Users.Count == 2); + + AddRepeatStep("remove user multiple times", () => Client.RemoveUser(user), 3); + AddAssert("room has 1 user", () => Client.Room?.Users.Count == 1); + } + [Test] public void TestPlayingUserTracking() { diff --git a/osu.Game.Tests/OnlinePlay/StatefulMultiplayerClientTest.cs b/osu.Game.Tests/OnlinePlay/StatefulMultiplayerClientTest.cs deleted file mode 100644 index 5a621ecf84..0000000000 --- a/osu.Game.Tests/OnlinePlay/StatefulMultiplayerClientTest.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 NUnit.Framework; -using osu.Framework.Testing; -using osu.Game.Online.API.Requests.Responses; -using osu.Game.Tests.Visual.Multiplayer; - -namespace osu.Game.Tests.OnlinePlay -{ - [HeadlessTest] - public class StatefulMultiplayerClientTest : MultiplayerTestScene - { - [Test] - public void TestUserAddedOnJoin() - { - var user = new APIUser { Id = 33 }; - - AddRepeatStep("add user multiple times", () => Client.AddUser(user), 3); - AddAssert("room has 2 users", () => Client.Room?.Users.Count == 2); - } - - [Test] - public void TestUserRemovedOnLeave() - { - var user = new APIUser { Id = 44 }; - - AddStep("add user", () => Client.AddUser(user)); - AddAssert("room has 2 users", () => Client.Room?.Users.Count == 2); - - AddRepeatStep("remove user multiple times", () => Client.RemoveUser(user), 3); - AddAssert("room has 1 user", () => Client.Room?.Users.Count == 1); - } - } -} From 5976982b12c158282a67b97844965dc98a989d39 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 2 Dec 2021 16:45:26 +0900 Subject: [PATCH 40/87] Add missing xmldoc for `MultiplayerClient` events --- .../Online/Multiplayer/MultiplayerClient.cs | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index 784d365520..8ac2de9902 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -32,12 +32,34 @@ namespace osu.Game.Online.Multiplayer /// public event Action? RoomUpdated; + /// + /// Invoked when a new user joins the room. + /// public event Action? UserJoined; + + /// + /// Invoked when a user leaves the room of their own accord. + /// public event Action? UserLeft; + + /// + /// Invoked when a user was kicked from the room forcefully. + /// public event Action? UserKicked; + /// + /// Invoked when a new item is added to the playlist. + /// public event Action? ItemAdded; + + /// + /// Invoked when a playlist item is removed from the playlist. The provided long is the playlist's item ID. + /// public event Action? ItemRemoved; + + /// + /// Invoked when a playlist item's details change. + /// public event Action? ItemChanged; /// From 512818648f0f0fd2efe4df15e9231b20d15c96d4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 2 Dec 2021 16:56:30 +0900 Subject: [PATCH 41/87] Add some more breathing room between tab control and queue content --- .../Multiplayer/Match/Playlist/MultiplayerPlaylist.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerPlaylist.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerPlaylist.cs index 38199532e2..2c50c88de8 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerPlaylist.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerPlaylist.cs @@ -26,18 +26,20 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist [BackgroundDependencyLoader] private void load() { + const float tab_control_height = 25; + InternalChildren = new Drawable[] { new OsuTabControl { RelativeSizeAxes = Axes.X, - Height = 25, + Height = tab_control_height, Current = { BindTarget = DisplayMode } }, new Container { RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Top = 27 }, + Padding = new MarginPadding { Top = tab_control_height + 5 }, Masking = true, Children = new Drawable[] { From ba8af303ccc0e2e9053f22034be7baa4b86dbdc7 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 2 Dec 2021 19:15:27 +0900 Subject: [PATCH 42/87] Add GameplayOrder to MultiplayerPlaylistItem --- osu.Game/Online/Multiplayer/MultiplayerClient.cs | 3 ++- osu.Game/Online/Rooms/MultiplayerPlaylistItem.cs | 10 ++++++++++ osu.Game/Online/Rooms/PlaylistItem.cs | 3 +++ 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index 8ac2de9902..0ffb81d986 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -741,7 +741,8 @@ namespace osu.Game.Online.Multiplayer OwnerID = item.OwnerID, Beatmap = { Value = apiBeatmap }, Ruleset = { Value = ruleset }, - Expired = item.Expired + Expired = item.Expired, + GameplayOrder = item.GameplayOrder }; playlistItem.RequiredMods.AddRange(item.RequiredMods.Select(m => m.ToMod(rulesetInstance))); diff --git a/osu.Game/Online/Rooms/MultiplayerPlaylistItem.cs b/osu.Game/Online/Rooms/MultiplayerPlaylistItem.cs index 6ca0b822f3..5094ee510f 100644 --- a/osu.Game/Online/Rooms/MultiplayerPlaylistItem.cs +++ b/osu.Game/Online/Rooms/MultiplayerPlaylistItem.cs @@ -39,6 +39,15 @@ namespace osu.Game.Online.Rooms [Key(7)] public bool Expired { get; set; } + /// + /// The order in which this will be played, starting from 0 and increasing for items which will be played later. + /// + /// + /// Undefined value for items which are expired. + /// + [Key(8)] + public int GameplayOrder { get; set; } + public MultiplayerPlaylistItem() { } @@ -52,6 +61,7 @@ namespace osu.Game.Online.Rooms RequiredMods = item.RequiredMods.Select(m => new APIMod(m)).ToArray(); AllowedMods = item.AllowedMods.Select(m => new APIMod(m)).ToArray(); Expired = item.Expired; + GameplayOrder = item.GameplayOrder; } } } diff --git a/osu.Game/Online/Rooms/PlaylistItem.cs b/osu.Game/Online/Rooms/PlaylistItem.cs index a1480865b8..4c7f9e139a 100644 --- a/osu.Game/Online/Rooms/PlaylistItem.cs +++ b/osu.Game/Online/Rooms/PlaylistItem.cs @@ -33,6 +33,9 @@ namespace osu.Game.Online.Rooms [JsonProperty("expired")] public bool Expired { get; set; } + [JsonProperty("gameplay_order")] + public int GameplayOrder { get; set; } + [JsonIgnore] public IBindable Valid => valid; From 9760a2b08723f326e349bad1d799d7e99f596a23 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 2 Dec 2021 21:13:20 +0900 Subject: [PATCH 43/87] Update MultiplayerQueueList to take advantage of GameplayOrder --- .../Match/Playlist/MultiplayerQueueList.cs | 64 +------------------ 1 file changed, 2 insertions(+), 62 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerQueueList.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerQueueList.cs index 614e7133fc..58f35b940a 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerQueueList.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerQueueList.cs @@ -1,14 +1,12 @@ // 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.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 osuTK; @@ -31,74 +29,16 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist private class QueueFillFlowContainer : FillFlowContainer> { - [Resolved(typeof(Room), nameof(Room.QueueMode))] - private Bindable queueMode { get; set; } - [Resolved(typeof(Room), nameof(Room.Playlist))] private BindableList roomPlaylist { get; set; } protected override void LoadComplete() { base.LoadComplete(); - queueMode.BindValueChanged(_ => InvalidateLayout()); + roomPlaylist.BindCollectionChanged((_, __) => InvalidateLayout()); } - public override IEnumerable FlowingChildren - { - get - { - switch (queueMode.Value) - { - default: - return AliveInternalChildren.Where(d => d.IsPresent) - .OfType>() - .OrderBy(item => item.Model.ID); - - case QueueMode.AllPlayersRoundRobin: - RearrangeableListItem[] items = AliveInternalChildren - .Where(d => d.IsPresent) - .OfType>() - .OrderBy(item => item.Model.ID) - .ToArray(); - - int totalCount = items.Length; - if (totalCount == 0) - return Enumerable.Empty(); - - // Count of "expired" items per user. - Dictionary perUserCounts = roomPlaylist - .Where(item => item.Expired) - .GroupBy(item => item.OwnerID) - .ToDictionary(group => group.Key, group => group.Count()); - - // Fill the count dictionary with zeroes for all users with no currently expired items. - foreach (var item in items) - perUserCounts.TryAdd(item.Model.OwnerID, 0); - - List result = new List(); - - // Run a simulation... - // In every iteration, pick the first available item from the user with the lowest number of items in the queue to add to the result set. - // If multiple users have the same number of items in the queue, then the item with the lowest ID is chosen. - // Todo: This could probably be more efficient, likely at the cost of increased complexity. - while (totalCount-- > 0) - { - var candidateItem = items - .Where(item => item != null) - .OrderBy(item => perUserCounts[item.Model.OwnerID]) - .First(); - - int itemIndex = Array.IndexOf(items, candidateItem); - - result.Add(items[itemIndex]); - perUserCounts[candidateItem.Model.OwnerID]++; - items[itemIndex] = null; - } - - return result; - } - } - } + public override IEnumerable FlowingChildren => base.FlowingChildren.OfType>().OrderBy(item => item.Model.GameplayOrder); } } } From 933fd49cff4fb0c9d66535cfab6aa7166d7f097e Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 2 Dec 2021 22:11:28 +0900 Subject: [PATCH 44/87] Fix missed callbacks due to AddOnce() schedules --- .../OnlinePlay/Multiplayer/MultiplayerRoomComposite.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerRoomComposite.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerRoomComposite.cs index f2cac708e4..2f75f09a9f 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerRoomComposite.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerRoomComposite.cs @@ -35,9 +35,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer private void invokeUserJoined(MultiplayerRoomUser user) => Scheduler.AddOnce(UserJoined, user); private void invokeUserKicked(MultiplayerRoomUser user) => Scheduler.AddOnce(UserKicked, user); private void invokeUserLeft(MultiplayerRoomUser user) => Scheduler.AddOnce(UserLeft, user); - private void invokeItemAdded(MultiplayerPlaylistItem item) => Scheduler.AddOnce(PlaylistItemAdded, item); - private void invokeItemRemoved(long item) => Scheduler.AddOnce(PlaylistItemRemoved, item); - private void invokeItemChanged(MultiplayerPlaylistItem item) => Scheduler.AddOnce(PlaylistItemChanged, item); + private void invokeItemAdded(MultiplayerPlaylistItem item) => Schedule(() => PlaylistItemAdded(item)); + private void invokeItemRemoved(long item) => Schedule(() => PlaylistItemRemoved(item)); + private void invokeItemChanged(MultiplayerPlaylistItem item) => Schedule(() => PlaylistItemChanged(item)); /// /// Invoked when a user has joined the room. From 806ca5d4de4f9dac36c1e3a00ff9cc1a0331c13e Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 2 Dec 2021 22:32:41 +0900 Subject: [PATCH 45/87] Update TestMultiplayerClient implementation to match server --- .../Multiplayer/TestMultiplayerClient.cs | 102 +++++++++++++----- 1 file changed, 77 insertions(+), 25 deletions(-) diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index 82a8212a94..2831c94429 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -45,7 +45,7 @@ namespace osu.Game.Tests.Visual.Multiplayer /// /// Guaranteed up-to-date playlist. /// - private readonly List serverSidePlaylist = new List(); + private List serverSidePlaylist = new List(); private MultiplayerPlaylistItem? currentItem => Room?.Playlist[currentIndex]; private int currentIndex; @@ -189,6 +189,7 @@ namespace osu.Game.Tests.Visual.Multiplayer Host = localUser }; + await updatePlaylistOrder(room).ConfigureAwait(false); await updateCurrentItem(room, false).ConfigureAwait(false); RoomSetupAction?.Invoke(room); @@ -308,12 +309,14 @@ namespace osu.Game.Tests.Visual.Multiplayer if (Room.Settings.QueueMode == QueueMode.HostOnly && Room.Host?.UserID != LocalUser?.UserID) throw new InvalidOperationException("Local user is not the room host."); + item.OwnerID = userId; + switch (Room.Settings.QueueMode) { case QueueMode.HostOnly: // In host-only mode, the current item is re-used. item.ID = currentItem.ID; - item.OwnerID = currentItem.OwnerID; + item.GameplayOrder = currentItem.GameplayOrder; serverSidePlaylist[currentIndex] = item; await ((IMultiplayerClient)this).PlaylistItemChanged(item).ConfigureAwait(false); @@ -323,12 +326,9 @@ namespace osu.Game.Tests.Visual.Multiplayer break; default: - item.ID = serverSidePlaylist.Last().ID + 1; - item.OwnerID = userId; - - serverSidePlaylist.Add(item); - await ((IMultiplayerClient)this).PlaylistItemAdded(item).ConfigureAwait(false); + await addItem(item).ConfigureAwait(false); + // The current item can change as a result of an item being added. For example, if all items earlier in the queue were expired. await updateCurrentItem(Room).ConfigureAwait(false); break; } @@ -385,7 +385,7 @@ namespace osu.Game.Tests.Visual.Multiplayer if (newMode == QueueMode.HostOnly && serverSidePlaylist.All(item => item.Expired)) await duplicateCurrentItem().ConfigureAwait(false); - // When changing modes, items could have been added (above) or the queueing order could have changed. + await updatePlaylistOrder(Room).ConfigureAwait(false); await updateCurrentItem(Room).ConfigureAwait(false); } @@ -408,47 +408,99 @@ namespace osu.Game.Tests.Visual.Multiplayer private async Task duplicateCurrentItem() { - Debug.Assert(Room != null); - Debug.Assert(APIRoom != null); Debug.Assert(currentItem != null); - var newItem = new MultiplayerPlaylistItem + await addItem(new MultiplayerPlaylistItem { - ID = serverSidePlaylist.Last().ID + 1, BeatmapID = currentItem.BeatmapID, BeatmapChecksum = currentItem.BeatmapChecksum, RulesetID = currentItem.RulesetID, RequiredMods = currentItem.RequiredMods, AllowedMods = currentItem.AllowedMods - }; + }).ConfigureAwait(false); + } - serverSidePlaylist.Add(newItem); - await ((IMultiplayerClient)this).PlaylistItemAdded(newItem).ConfigureAwait(false); + private async Task addItem(MultiplayerPlaylistItem item) + { + Debug.Assert(Room != null); + + // Add the item to the list first in order to compute gameplay order. + serverSidePlaylist.Add(item); + await updatePlaylistOrder(Room).ConfigureAwait(false); + + item.ID = serverSidePlaylist[^2].ID + 1; + await ((IMultiplayerClient)this).PlaylistItemAdded(item).ConfigureAwait(false); } private async Task updateCurrentItem(MultiplayerRoom room, bool notify = true) { - MultiplayerPlaylistItem newItem; + // The playlist is already in correct gameplay order, so pick the next non-expired item or default to the last item. + MultiplayerPlaylistItem nextItem = serverSidePlaylist.FirstOrDefault(i => !i.Expired) ?? room.Playlist.Last(); + currentIndex = serverSidePlaylist.IndexOf(nextItem); + + long lastItem = room.Settings.PlaylistItemId; + room.Settings.PlaylistItemId = nextItem.ID; + + if (notify && nextItem.ID != lastItem) + await ((IMultiplayerClient)this).SettingsChanged(room.Settings).ConfigureAwait(false); + } + + private async Task updatePlaylistOrder(MultiplayerRoom room) + { + List orderedItems; switch (room.Settings.QueueMode) { default: - // Pick the single non-expired playlist item. - newItem = serverSidePlaylist.FirstOrDefault(i => !i.Expired) ?? serverSidePlaylist.Last(); + orderedItems = serverSidePlaylist.OrderBy(item => item.ID == 0 ? int.MaxValue : item.ID).ToList(); break; case QueueMode.AllPlayersRoundRobin: - // Group playlist items by (user_id -> count_expired), and select the first available playlist item from a user that has available beatmaps where count_expired is the lowest. - throw new NotImplementedException(); + // Todo: This could probably be more efficient, likely at the cost of increased complexity. + // Number of "expired" or "used" items per player. + Dictionary perUserCounts = serverSidePlaylist + .GroupBy(item => item.OwnerID) + .ToDictionary(group => group.Key, group => group.Count(item => item.Expired)); + + // We'll run a simulation over all items which are not expired ("unprocessed"). Expired items will not have their ordering updated. + List processedItems = serverSidePlaylist.Where(item => item.Expired).ToList(); + List unprocessedItems = serverSidePlaylist.Where(item => !item.Expired).ToList(); + + // In every iteration of the simulation, pick the first available item from the user with the lowest number of items in the queue to add to the result set. + // If multiple users have the same number of items in the queue, then the item with the lowest ID is chosen. + while (unprocessedItems.Count > 0) + { + MultiplayerPlaylistItem candidateItem = unprocessedItems + .OrderBy(item => perUserCounts[item.OwnerID]) + .ThenBy(item => item.ID == 0 ? int.MaxValue : item.ID) + .First(); + + unprocessedItems.Remove(candidateItem); + processedItems.Add(candidateItem); + + perUserCounts[candidateItem.OwnerID]++; + } + + orderedItems = processedItems; + break; } - currentIndex = serverSidePlaylist.IndexOf(newItem); + for (int i = 0; i < orderedItems.Count; i++) + { + // Items which are already ordered correct don't need to be updated. + if (orderedItems[i].GameplayOrder == i) + continue; - long lastItem = room.Settings.PlaylistItemId; - room.Settings.PlaylistItemId = newItem.ID; + orderedItems[i].GameplayOrder = i; - if (notify && newItem.ID != lastItem) - await ((IMultiplayerClient)this).SettingsChanged(room.Settings).ConfigureAwait(false); + // Items which have an ID of 0 are not in the database, so avoid propagating database/hub events for them. + if (orderedItems[i].ID <= 0) + continue; + + await ((IMultiplayerClient)this).PlaylistItemChanged(orderedItems[i]).ConfigureAwait(false); + } + + serverSidePlaylist = orderedItems; } } } From 10932dd2824024418a24794c502f98f813cd4b3b Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 2 Dec 2021 22:37:09 +0900 Subject: [PATCH 46/87] Remove now unnecessary test --- .../TestSceneMultiplayerQueueList.cs | 216 ------------------ 1 file changed, 216 deletions(-) delete mode 100644 osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs deleted file mode 100644 index bd513ddf64..0000000000 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs +++ /dev/null @@ -1,216 +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.Collections.Generic; -using System.Linq; -using NUnit.Framework; -using osu.Framework.Graphics; -using osu.Framework.Testing; -using osu.Game.Online.Multiplayer; -using osu.Game.Online.Rooms; -using osu.Game.Rulesets.Osu; -using osu.Game.Screens.OnlinePlay; -using osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist; -using osu.Game.Tests.Beatmaps; - -namespace osu.Game.Tests.Visual.Multiplayer -{ - public class TestSceneMultiplayerQueueList : MultiplayerTestScene - { - private MultiplayerQueueList list; - private int currentItemId; - - [SetUp] - public new void Setup() => Schedule(() => - { - Child = list = new MultiplayerQueueList - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - Width = 0.4f, - Height = 0.6f - }; - }); - - [SetUpSteps] - public override void SetUpSteps() - { - base.SetUpSteps(); - - // Not inside a step since this is used to affect steps added by the current test. - currentItemId = 0; - } - - [Test] - public void TestItemsAddedToEndInHostOnlyMode() - { - changeQueueModeStep(QueueMode.HostOnly); - - // User 1. - - PlaylistItem item1 = addItemStep(1); - assertPositionStep(item1, 0); - - PlaylistItem item2 = addItemStep(1); - assertPositionStep(item2, 1); - - // User 2. - - PlaylistItem item3 = addItemStep(2); - assertPositionStep(item3, 2); - } - - [Test] - public void TestItemsAddedToEndInAllPlayersMode() - { - changeQueueModeStep(QueueMode.AllPlayers); - - // User 1. - - PlaylistItem item1 = addItemStep(1); - assertPositionStep(item1, 0); - - PlaylistItem item2 = addItemStep(1); - assertPositionStep(item2, 1); - - // User 2. - - PlaylistItem item3 = addItemStep(2); - assertPositionStep(item3, 2); - } - - [Test] - public void TestItemsInsertedInCorrectPositionInRoundRobinMode() - { - changeQueueModeStep(QueueMode.AllPlayersRoundRobin); - - // User 1. - - PlaylistItem item1 = addItemStep(1); - assertPositionStep(item1, 0); - - PlaylistItem item2 = addItemStep(1); - assertPositionStep(item2, 1); - - // User 2. - - PlaylistItem item3 = addItemStep(2); - assertPositionStep(item3, 1); - assertPositionStep(item2, 2); - - PlaylistItem item4 = addItemStep(2); - assertPositionStep(item4, 3); - - PlaylistItem item5 = addItemStep(2); - assertPositionStep(item5, 4); - - // User 1. - - // This item is added to the end rather than injected between item4 and item5, since both users have an equal number - // of added items at this point and this user was the last of the two to add an item. - PlaylistItem item6 = addItemStep(1); - assertPositionStep(item6, 5); - - // User 3. - - PlaylistItem item7 = addItemStep(3); - assertPositionStep(item7, 2); - - PlaylistItem item8 = addItemStep(3); - assertPositionStep(item8, 5); - - PlaylistItem item9 = addItemStep(3); - assertPositionStep(item9, 8); - } - - [Test] - public void TestItemsReorderedWhenQueueModeChanged() - { - changeQueueModeStep(QueueMode.AllPlayers); - - var items = new List(); - - for (int i = 0; i < 8; i++) - items.Add(addItemStep(i <= 3 ? 1 : 2)); - - for (int i = 0; i < 8; i++) - assertPositionStep(items[i], i); - - changeQueueModeStep(QueueMode.AllPlayersRoundRobin); - - for (int i = 0; i < 4; i++) - { - assertPositionStep(items[i], i * 2); // Items by user 1. - assertPositionStep(items[i + 4], i * 2 + 1); // Items by user 2. - } - } - - [Test] - public void TestPreviouslyExpiredItemsConsideredInRoundRobinMode() - { - changeQueueModeStep(QueueMode.AllPlayersRoundRobin); - - // User 1. - - addItemStep(1, true); - addItemStep(1, true); - PlaylistItem item3 = addItemStep(1); - PlaylistItem item4 = addItemStep(1); - - // User2. - - PlaylistItem item5 = addItemStep(2); - PlaylistItem item6 = addItemStep(2); - - assertPositionStep(item5, 0); - assertPositionStep(item6, 1); - assertPositionStep(item3, 2); - assertPositionStep(item4, 3); - } - - /// - /// Adds a step to create a new playlist item. - /// - /// The item owner. - /// Whether the item should be added in an expired state. - /// The playlist item's ID. - private PlaylistItem addItemStep(int ownerId, bool expired = false) - { - var item = new PlaylistItem - { - ID = ++currentItemId, - OwnerID = ownerId, - Beatmap = { Value = new TestBeatmap(new OsuRuleset().RulesetInfo, false).BeatmapInfo }, - Expired = expired - }; - - AddStep($"add {{ item: {item.ID}, user: {ownerId} }}", () => - { - SelectedRoom.Value.Playlist.Add(item); - if (!expired) - list.Items.Add(item); - }); - - return item; - } - - /// - /// Asserts the position of a given playlist item in the visual layout of the list. - /// - /// The playlist item. - /// The index at which the item should appear visually. The item with index 0 is at the top of the list. - private void assertPositionStep(PlaylistItem item, int visualIndex) - { - AddUntilStep($"item {item.ID} has pos = {visualIndex}", () => - { - return this.ChildrenOfType() - .OrderBy(drawable => drawable.Position.Y) - .TakeWhile(drawable => drawable.Item.ID != item.ID) - .Count() == visualIndex; - }); - } - - private void changeQueueModeStep(QueueMode newMode) => AddStep($"change queue mode to {newMode}", () => SelectedRoom.Value.QueueMode.Value = newMode); - } -} From abf7735b8403efb9e76e80b7d30ba49f4de8367d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 3 Dec 2021 14:18:03 +0900 Subject: [PATCH 47/87] 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 eff0eed278..cd2710c3ba 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 7cc8893d8d..a4a4661822 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 9c21f76617..c095d3ef74 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -60,7 +60,7 @@ - + @@ -83,7 +83,7 @@ - + From 614256697442c2c543d0120d1271be1264dfb858 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 3 Dec 2021 14:26:53 +0900 Subject: [PATCH 48/87] Update resources --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index cd2710c3ba..17b5cb67e9 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -51,7 +51,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index a4a4661822..53a3337c9d 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -37,7 +37,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index c095d3ef74..f3dc163a67 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -61,7 +61,7 @@ - + From 9803e63e6f0c9a2a14f6427c9faafb629c5ef2ac Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 3 Dec 2021 14:30:15 +0900 Subject: [PATCH 49/87] Update IPC usage to return `null` --- osu.Game/IPC/ArchiveImportIPCChannel.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/IPC/ArchiveImportIPCChannel.cs b/osu.Game/IPC/ArchiveImportIPCChannel.cs index d9d0e4c0ea..3ef1dc051d 100644 --- a/osu.Game/IPC/ArchiveImportIPCChannel.cs +++ b/osu.Game/IPC/ArchiveImportIPCChannel.cs @@ -25,6 +25,8 @@ namespace osu.Game.IPC { if (t.Exception != null) throw t.Exception; }, TaskContinuationOptions.OnlyOnFaulted); + + return null; }; } From 1d2d1bfcf3497c35a5e06b2eb3d322518d56ece2 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 3 Dec 2021 14:38:14 +0900 Subject: [PATCH 50/87] Add UpdatedAt to MultiplayerPlaylistItem --- osu.Game/Online/Rooms/MultiplayerPlaylistItem.cs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/osu.Game/Online/Rooms/MultiplayerPlaylistItem.cs b/osu.Game/Online/Rooms/MultiplayerPlaylistItem.cs index 5094ee510f..ec98de2b43 100644 --- a/osu.Game/Online/Rooms/MultiplayerPlaylistItem.cs +++ b/osu.Game/Online/Rooms/MultiplayerPlaylistItem.cs @@ -42,12 +42,16 @@ namespace osu.Game.Online.Rooms /// /// The order in which this will be played, starting from 0 and increasing for items which will be played later. /// - /// - /// Undefined value for items which are expired. - /// [Key(8)] public int GameplayOrder { get; set; } + /// + /// The date when this was last updated. + /// Not serialised to/from the client. + /// + [IgnoreMember] + public DateTimeOffset UpdatedAt { get; set; } + public MultiplayerPlaylistItem() { } From b75a5b778e0d22206dd17f0afce694825b39214f Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 3 Dec 2021 15:05:52 +0900 Subject: [PATCH 51/87] Update history list to also sort by gameplay order --- .../Multiplayer/Match/Playlist/MultiplayerHistoryList.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerHistoryList.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerHistoryList.cs index 76088180c4..25f658446a 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerHistoryList.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerHistoryList.cs @@ -27,7 +27,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist private class HistoryFillFlowContainer : FillFlowContainer> { - public override IEnumerable FlowingChildren => base.FlowingChildren.Reverse(); + public override IEnumerable FlowingChildren => base.FlowingChildren.OfType>().OrderByDescending(item => item.Model.GameplayOrder); } } } From 0a1304b92a00200c318828d995b63e1660ce1a18 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 3 Dec 2021 15:45:13 +0900 Subject: [PATCH 52/87] Remove gameplay_order, use existing playlist_order --- osu.Game/Online/Multiplayer/MultiplayerClient.cs | 2 +- osu.Game/Online/Rooms/MultiplayerPlaylistItem.cs | 4 ++-- osu.Game/Online/Rooms/PlaylistItem.cs | 4 ++-- .../Multiplayer/Match/Playlist/MultiplayerHistoryList.cs | 2 +- .../Multiplayer/Match/Playlist/MultiplayerQueueList.cs | 2 +- osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs | 6 +++--- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index 0ffb81d986..ae53ef2e52 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -742,7 +742,7 @@ namespace osu.Game.Online.Multiplayer Beatmap = { Value = apiBeatmap }, Ruleset = { Value = ruleset }, Expired = item.Expired, - GameplayOrder = item.GameplayOrder + PlaylistOrder = item.PlaylistOrder }; playlistItem.RequiredMods.AddRange(item.RequiredMods.Select(m => m.ToMod(rulesetInstance))); diff --git a/osu.Game/Online/Rooms/MultiplayerPlaylistItem.cs b/osu.Game/Online/Rooms/MultiplayerPlaylistItem.cs index ec98de2b43..c84b5b9039 100644 --- a/osu.Game/Online/Rooms/MultiplayerPlaylistItem.cs +++ b/osu.Game/Online/Rooms/MultiplayerPlaylistItem.cs @@ -43,7 +43,7 @@ namespace osu.Game.Online.Rooms /// The order in which this will be played, starting from 0 and increasing for items which will be played later. /// [Key(8)] - public int GameplayOrder { get; set; } + public ushort PlaylistOrder { get; set; } /// /// The date when this was last updated. @@ -65,7 +65,7 @@ namespace osu.Game.Online.Rooms RequiredMods = item.RequiredMods.Select(m => new APIMod(m)).ToArray(); AllowedMods = item.AllowedMods.Select(m => new APIMod(m)).ToArray(); Expired = item.Expired; - GameplayOrder = item.GameplayOrder; + PlaylistOrder = item.PlaylistOrder ?? 0; } } } diff --git a/osu.Game/Online/Rooms/PlaylistItem.cs b/osu.Game/Online/Rooms/PlaylistItem.cs index 4c7f9e139a..beefb17d54 100644 --- a/osu.Game/Online/Rooms/PlaylistItem.cs +++ b/osu.Game/Online/Rooms/PlaylistItem.cs @@ -33,8 +33,8 @@ namespace osu.Game.Online.Rooms [JsonProperty("expired")] public bool Expired { get; set; } - [JsonProperty("gameplay_order")] - public int GameplayOrder { get; set; } + [JsonProperty("playlist_order")] + public ushort? PlaylistOrder { get; set; } [JsonIgnore] public IBindable Valid => valid; diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerHistoryList.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerHistoryList.cs index 25f658446a..ed41a6fc78 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerHistoryList.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerHistoryList.cs @@ -27,7 +27,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist private class HistoryFillFlowContainer : FillFlowContainer> { - public override IEnumerable FlowingChildren => base.FlowingChildren.OfType>().OrderByDescending(item => item.Model.GameplayOrder); + public override IEnumerable FlowingChildren => base.FlowingChildren.OfType>().OrderByDescending(item => item.Model.PlaylistOrder); } } } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerQueueList.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerQueueList.cs index 58f35b940a..1b1b66273f 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerQueueList.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerQueueList.cs @@ -38,7 +38,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist roomPlaylist.BindCollectionChanged((_, __) => InvalidateLayout()); } - public override IEnumerable FlowingChildren => base.FlowingChildren.OfType>().OrderBy(item => item.Model.GameplayOrder); + public override IEnumerable FlowingChildren => base.FlowingChildren.OfType>().OrderBy(item => item.Model.PlaylistOrder); } } } diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index 2831c94429..b47b86a2d8 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -316,7 +316,7 @@ namespace osu.Game.Tests.Visual.Multiplayer case QueueMode.HostOnly: // In host-only mode, the current item is re-used. item.ID = currentItem.ID; - item.GameplayOrder = currentItem.GameplayOrder; + item.PlaylistOrder = currentItem.PlaylistOrder; serverSidePlaylist[currentIndex] = item; await ((IMultiplayerClient)this).PlaylistItemChanged(item).ConfigureAwait(false); @@ -488,10 +488,10 @@ namespace osu.Game.Tests.Visual.Multiplayer for (int i = 0; i < orderedItems.Count; i++) { // Items which are already ordered correct don't need to be updated. - if (orderedItems[i].GameplayOrder == i) + if (orderedItems[i].PlaylistOrder == i) continue; - orderedItems[i].GameplayOrder = i; + orderedItems[i].PlaylistOrder = (ushort)i; // Items which have an ID of 0 are not in the database, so avoid propagating database/hub events for them. if (orderedItems[i].ID <= 0) From 2262b7b331efdd1ecfaa8572687e03dacd175d8f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 3 Dec 2021 15:35:06 +0900 Subject: [PATCH 53/87] Adjust logging to avoid using tabs --- osu.Desktop/LegacyIpc/LegacyTcpIpcProvider.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Desktop/LegacyIpc/LegacyTcpIpcProvider.cs b/osu.Desktop/LegacyIpc/LegacyTcpIpcProvider.cs index 273e17d24b..a48a6fc631 100644 --- a/osu.Desktop/LegacyIpc/LegacyTcpIpcProvider.cs +++ b/osu.Desktop/LegacyIpc/LegacyTcpIpcProvider.cs @@ -36,8 +36,8 @@ namespace osu.Desktop.LegacyIpc { try { - logger.Add($"Processing legacy IPC message..."); - logger.Add($"\t{msg.Value}", LogLevel.Debug); + logger.Add("Processing legacy IPC message..."); + logger.Add($" {msg.Value}", LogLevel.Debug); var legacyData = ((JObject)msg.Value).ToObject(); object value = parseObject((JObject)legacyData!.MessageData, legacyData.MessageType); @@ -64,7 +64,7 @@ namespace osu.Desktop.LegacyIpc return value.ToObject(); default: - throw new ArgumentException($"Unknown type: {type}"); + throw new ArgumentException($"Unsupported object type {type}"); } } From 33992e11e09e0d8b8dc2ca851c15167abb3b0ed5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 3 Dec 2021 15:40:53 +0900 Subject: [PATCH 54/87] Split out ruleset lookup code --- osu.Desktop/LegacyIpc/LegacyTcpIpcProvider.cs | 30 ++++++++++++++----- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/osu.Desktop/LegacyIpc/LegacyTcpIpcProvider.cs b/osu.Desktop/LegacyIpc/LegacyTcpIpcProvider.cs index a48a6fc631..b7f8baf219 100644 --- a/osu.Desktop/LegacyIpc/LegacyTcpIpcProvider.cs +++ b/osu.Desktop/LegacyIpc/LegacyTcpIpcProvider.cs @@ -75,14 +75,7 @@ namespace osu.Desktop.LegacyIpc case LegacyIpcDifficultyCalculationRequest req: try { - Ruleset ruleset = req.RulesetId switch - { - 0 => new OsuRuleset(), - 1 => new TaikoRuleset(), - 2 => new CatchRuleset(), - 3 => new ManiaRuleset(), - _ => throw new ArgumentException("Invalid ruleset id") - }; + var ruleset = getLegacyRulesetFromID(req.RulesetId); Mod[] mods = ruleset.ConvertFromLegacyMods((LegacyMods)req.Mods).ToArray(); WorkingBeatmap beatmap = new FlatFileWorkingBeatmap(req.BeatmapFile, _ => ruleset); @@ -101,5 +94,26 @@ namespace osu.Desktop.LegacyIpc Console.WriteLine("Type not matched."); return null; } + + private static Ruleset getLegacyRulesetFromID(int rulesetId) + { + switch (rulesetId) + { + case 0: + return new OsuRuleset(); + + case 1: + return new TaikoRuleset(); + + case 2: + return new CatchRuleset(); + + case 3: + return new ManiaRuleset(); + + default: + throw new ArgumentException("Invalid ruleset id"); + } + } } } From 79d723172a30c44b432f852fb8d29e0742f521cd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 3 Dec 2021 15:48:40 +0900 Subject: [PATCH 55/87] Remove `Console.WriteLine` usage --- osu.Desktop/LegacyIpc/LegacyTcpIpcProvider.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Desktop/LegacyIpc/LegacyTcpIpcProvider.cs b/osu.Desktop/LegacyIpc/LegacyTcpIpcProvider.cs index b7f8baf219..8ac946d4a4 100644 --- a/osu.Desktop/LegacyIpc/LegacyTcpIpcProvider.cs +++ b/osu.Desktop/LegacyIpc/LegacyTcpIpcProvider.cs @@ -89,10 +89,10 @@ namespace osu.Desktop.LegacyIpc { return new LegacyIpcDifficultyCalculationResponse(); } - } - Console.WriteLine("Type not matched."); - return null; + default: + throw new ArgumentException($"Unsupported message type {message}"); + } } private static Ruleset getLegacyRulesetFromID(int rulesetId) From f9ad3075261edfc9c590beda29a8f8323c18b197 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 3 Dec 2021 15:49:01 +0900 Subject: [PATCH 56/87] Apply nullable --- osu.Desktop/LegacyIpc/LegacyTcpIpcProvider.cs | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/osu.Desktop/LegacyIpc/LegacyTcpIpcProvider.cs b/osu.Desktop/LegacyIpc/LegacyTcpIpcProvider.cs index 8ac946d4a4..2c9eb76dfc 100644 --- a/osu.Desktop/LegacyIpc/LegacyTcpIpcProvider.cs +++ b/osu.Desktop/LegacyIpc/LegacyTcpIpcProvider.cs @@ -15,6 +15,8 @@ using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Taiko; +#nullable enable + namespace osu.Desktop.LegacyIpc { /// @@ -27,7 +29,7 @@ namespace osu.Desktop.LegacyIpc /// /// Invoked when a message is received from a legacy client. /// - public new Func MessageReceived; + public new Func? MessageReceived; public LegacyTcpIpcProvider() : base(45357) @@ -42,8 +44,10 @@ namespace osu.Desktop.LegacyIpc var legacyData = ((JObject)msg.Value).ToObject(); object value = parseObject((JObject)legacyData!.MessageData, legacyData.MessageType); - object result = onLegacyIpcMessageReceived(value); - return result != null ? new LegacyIpcMessage { Value = result } : null; + return new LegacyIpcMessage + { + Value = onLegacyIpcMessageReceived(value) + }; } catch (Exception ex) { @@ -58,10 +62,12 @@ namespace osu.Desktop.LegacyIpc switch (type) { case nameof(LegacyIpcDifficultyCalculationRequest): - return value.ToObject(); + return value.ToObject() + ?? throw new InvalidOperationException($"Failed to parse request {value}"); case nameof(LegacyIpcDifficultyCalculationResponse): - return value.ToObject(); + return value.ToObject() + ?? throw new InvalidOperationException($"Failed to parse request {value}"); default: throw new ArgumentException($"Unsupported object type {type}"); From ba05a0a3834b6d02a07ef2f94e69a23b02584d14 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 3 Dec 2021 16:04:11 +0900 Subject: [PATCH 57/87] Centralise specification of bracket.json filename --- .../NonVisual/CustomTourneyDirectoryTest.cs | 4 ++-- osu.Game.Tournament/IO/TournamentStorage.cs | 2 +- osu.Game.Tournament/TournamentGame.cs | 2 +- osu.Game.Tournament/TournamentGameBase.cs | 8 ++++---- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs b/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs index d149ec145b..3619aae7e0 100644 --- a/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs +++ b/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs @@ -96,7 +96,7 @@ namespace osu.Game.Tournament.Tests.NonVisual Directory.CreateDirectory(flagsPath); // Define testing files corresponding to the specific file migrations that are needed - string bracketFile = Path.Combine(osuRoot, "bracket.json"); + string bracketFile = Path.Combine(osuRoot, TournamentGameBase.BRACKET_FILENAME); string drawingsConfig = Path.Combine(osuRoot, "drawings.ini"); string drawingsFile = Path.Combine(osuRoot, "drawings.txt"); @@ -133,7 +133,7 @@ namespace osu.Game.Tournament.Tests.NonVisual Assert.That(storage.GetFullPath("."), Is.EqualTo(migratedPath)); - Assert.True(storage.Exists("bracket.json")); + Assert.True(storage.Exists(TournamentGameBase.BRACKET_FILENAME)); Assert.True(storage.Exists("drawings.txt")); Assert.True(storage.Exists("drawings_results.txt")); diff --git a/osu.Game.Tournament/IO/TournamentStorage.cs b/osu.Game.Tournament/IO/TournamentStorage.cs index 02cf567837..347d368a04 100644 --- a/osu.Game.Tournament/IO/TournamentStorage.cs +++ b/osu.Game.Tournament/IO/TournamentStorage.cs @@ -86,7 +86,7 @@ namespace osu.Game.Tournament.IO DeleteRecursive(source); } - moveFileIfExists("bracket.json", destination); + moveFileIfExists(TournamentGameBase.BRACKET_FILENAME, destination); moveFileIfExists("drawings.txt", destination); moveFileIfExists("drawings_results.txt", destination); moveFileIfExists("drawings.ini", destination); diff --git a/osu.Game.Tournament/TournamentGame.cs b/osu.Game.Tournament/TournamentGame.cs index f03f815b83..5d613894d4 100644 --- a/osu.Game.Tournament/TournamentGame.cs +++ b/osu.Game.Tournament/TournamentGame.cs @@ -71,7 +71,7 @@ namespace osu.Game.Tournament loadingSpinner.Expire(); Logger.Error(t.Exception, "Couldn't load bracket with error"); - Add(new WarningBox("Your bracket.json file could not be parsed. Please check runtime.log for more details.")); + Add(new WarningBox($"Your {BRACKET_FILENAME} file could not be parsed. Please check runtime.log for more details.")); }); return; diff --git a/osu.Game.Tournament/TournamentGameBase.cs b/osu.Game.Tournament/TournamentGameBase.cs index d2f146c4c2..9db007f3ee 100644 --- a/osu.Game.Tournament/TournamentGameBase.cs +++ b/osu.Game.Tournament/TournamentGameBase.cs @@ -26,7 +26,7 @@ namespace osu.Game.Tournament [Cached(typeof(TournamentGameBase))] public class TournamentGameBase : OsuGameBase { - private const string bracket_filename = "bracket.json"; + public const string BRACKET_FILENAME = @"bracket.json"; private LadderInfo ladder; private TournamentStorage storage; private DependencyContainer dependencies; @@ -71,9 +71,9 @@ namespace osu.Game.Tournament { try { - if (storage.Exists(bracket_filename)) + if (storage.Exists(BRACKET_FILENAME)) { - using (Stream stream = storage.GetStream(bracket_filename, FileAccess.Read, FileMode.Open)) + using (Stream stream = storage.GetStream(BRACKET_FILENAME, FileAccess.Read, FileMode.Open)) using (var sr = new StreamReader(stream)) ladder = JsonConvert.DeserializeObject(sr.ReadToEnd(), new JsonPointConverter()); } @@ -309,7 +309,7 @@ namespace osu.Game.Tournament Converters = new JsonConverter[] { new JsonPointConverter() } }); - using (var stream = storage.GetStream(bracket_filename, FileAccess.Write, FileMode.Create)) + using (var stream = storage.GetStream(BRACKET_FILENAME, FileAccess.Write, FileMode.Create)) using (var sw = new StreamWriter(stream)) sw.Write(serialisedLadder); } From 5158736839ee93732531e2f596e2db22c0076bbc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 3 Dec 2021 16:06:38 +0900 Subject: [PATCH 58/87] Avoid saving bracket if parsing failed, at all costs --- osu.Game.Tournament/TournamentGameBase.cs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tournament/TournamentGameBase.cs b/osu.Game.Tournament/TournamentGameBase.cs index 9db007f3ee..d08322a3e8 100644 --- a/osu.Game.Tournament/TournamentGameBase.cs +++ b/osu.Game.Tournament/TournamentGameBase.cs @@ -11,6 +11,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Textures; using osu.Framework.Input; using osu.Framework.IO.Stores; +using osu.Framework.Logging; using osu.Framework.Platform; using osu.Game.Graphics; using osu.Game.Online.API.Requests; @@ -32,9 +33,9 @@ namespace osu.Game.Tournament private DependencyContainer dependencies; private FileBasedIPC ipc; - protected Task BracketLoadTask => taskCompletionSource.Task; + protected Task BracketLoadTask => bracketLoadTaskCompletionSource.Task; - private readonly TaskCompletionSource taskCompletionSource = new TaskCompletionSource(); + private readonly TaskCompletionSource bracketLoadTaskCompletionSource = new TaskCompletionSource(); protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) { @@ -144,7 +145,7 @@ namespace osu.Game.Tournament } catch (Exception e) { - taskCompletionSource.SetException(e); + bracketLoadTaskCompletionSource.SetException(e); return; } @@ -156,7 +157,7 @@ namespace osu.Game.Tournament dependencies.CacheAs(ipc = new FileBasedIPC()); Add(ipc); - taskCompletionSource.SetResult(true); + bracketLoadTaskCompletionSource.SetResult(true); initialisationText.Expire(); }); @@ -292,6 +293,12 @@ namespace osu.Game.Tournament protected virtual void SaveChanges() { + if (!bracketLoadTaskCompletionSource.Task.IsCompletedSuccessfully) + { + Logger.Log("Inhibiting bracket save as bracket parsing failed"); + return; + } + foreach (var r in ladder.Rounds) r.Matches = ladder.Matches.Where(p => p.Round.Value == r).Select(p => p.ID).ToList(); From dad5b06e8402205a67e6b4d5b899e2de642e6aef Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 3 Dec 2021 16:23:39 +0900 Subject: [PATCH 59/87] Avoid sending empty parameters in `GetBeatmapRequest` --- osu.Game/Online/API/Requests/GetBeatmapRequest.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/osu.Game/Online/API/Requests/GetBeatmapRequest.cs b/osu.Game/Online/API/Requests/GetBeatmapRequest.cs index 6cd45a41df..671f543422 100644 --- a/osu.Game/Online/API/Requests/GetBeatmapRequest.cs +++ b/osu.Game/Online/API/Requests/GetBeatmapRequest.cs @@ -26,9 +26,12 @@ namespace osu.Game.Online.API.Requests { var request = base.CreateWebRequest(); - request.AddParameter(@"id", beatmapInfo.OnlineID.ToString()); - request.AddParameter(@"checksum", beatmapInfo.MD5Hash); - request.AddParameter(@"filename", filename); + if (beatmapInfo.OnlineID > 0) + request.AddParameter(@"id", beatmapInfo.OnlineID.ToString()); + if (!string.IsNullOrEmpty(beatmapInfo.MD5Hash)) + request.AddParameter(@"checksum", beatmapInfo.MD5Hash); + if (!string.IsNullOrEmpty(filename)) + request.AddParameter(@"filename", filename); return request; } From 487a71312e3cebbea75902dbae6146ee0ef79ee3 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 3 Dec 2021 16:40:20 +0900 Subject: [PATCH 60/87] Split out code so base methods aren't called --- .../Match/Playlist/MultiplayerPlaylist.cs | 32 ++++++++++++------- 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerPlaylist.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerPlaylist.cs index 2c50c88de8..c3245b550f 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerPlaylist.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerPlaylist.cs @@ -86,7 +86,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist if (firstPopulation) { foreach (var item in Room.Playlist) - PlaylistItemAdded(item); + addItemToLists(item); firstPopulation = false; } @@ -95,7 +95,25 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist protected override void PlaylistItemAdded(MultiplayerPlaylistItem item) { base.PlaylistItemAdded(item); + addItemToLists(item); + } + protected override void PlaylistItemRemoved(long item) + { + base.PlaylistItemRemoved(item); + removeItemFromLists(item); + } + + protected override void PlaylistItemChanged(MultiplayerPlaylistItem item) + { + base.PlaylistItemChanged(item); + + removeItemFromLists(item.ID); + addItemToLists(item); + } + + private void addItemToLists(MultiplayerPlaylistItem item) + { var apiItem = Playlist.Single(i => i.ID == item.ID); if (item.Expired) @@ -104,20 +122,10 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist queueList.Items.Add(apiItem); } - protected override void PlaylistItemRemoved(long item) + private void removeItemFromLists(long item) { - base.PlaylistItemRemoved(item); - queueList.Items.RemoveAll(i => i.ID == item); historyList.Items.RemoveAll(i => i.ID == item); } - - protected override void PlaylistItemChanged(MultiplayerPlaylistItem item) - { - base.PlaylistItemChanged(item); - - PlaylistItemRemoved(item.ID); - PlaylistItemAdded(item); - } } } From 9d6fe558c2d554e40203e963eceb72c619a7e5c7 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 3 Dec 2021 16:48:54 +0900 Subject: [PATCH 61/87] Update TestMultiplayerClient with expired item ordering --- .../Multiplayer/TestMultiplayerClient.cs | 57 ++++++++++++------- 1 file changed, 38 insertions(+), 19 deletions(-) diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index b47b86a2d8..f2d5323386 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -45,11 +45,13 @@ namespace osu.Game.Tests.Visual.Multiplayer /// /// Guaranteed up-to-date playlist. /// - private List serverSidePlaylist = new List(); + private readonly List serverSidePlaylist = new List(); private MultiplayerPlaylistItem? currentItem => Room?.Playlist[currentIndex]; private int currentIndex; + private long lastPlaylistItemId; + public TestMultiplayerClient(TestMultiplayerRoomManager roomManager) { this.roomManager = roomManager; @@ -169,6 +171,7 @@ namespace osu.Game.Tests.Visual.Multiplayer serverSidePlaylist.Clear(); serverSidePlaylist.AddRange(apiRoom.Playlist.Select(item => new MultiplayerPlaylistItem(item))); + lastPlaylistItemId = serverSidePlaylist.Max(item => item.ID); var localUser = new MultiplayerRoomUser(api.LocalUser.Value.Id) { @@ -397,10 +400,13 @@ namespace osu.Game.Tests.Visual.Multiplayer // Expire the current playlist item. currentItem.Expired = true; + currentItem.UpdatedAt = DateTimeOffset.Now; + await ((IMultiplayerClient)this).PlaylistItemChanged(currentItem).ConfigureAwait(false); + await updatePlaylistOrder(Room).ConfigureAwait(false); // In host-only mode, a duplicate playlist item will be used for the next round. - if (Room.Settings.QueueMode == QueueMode.HostOnly) + if (Room.Settings.QueueMode == QueueMode.HostOnly && serverSidePlaylist.All(item => item.Expired)) await duplicateCurrentItem().ConfigureAwait(false); await updateCurrentItem(Room).ConfigureAwait(false); @@ -424,11 +430,14 @@ namespace osu.Game.Tests.Visual.Multiplayer { Debug.Assert(Room != null); + // Some tests can add items in already-expired states. + item.UpdatedAt = DateTimeOffset.Now; + // Add the item to the list first in order to compute gameplay order. serverSidePlaylist.Add(item); await updatePlaylistOrder(Room).ConfigureAwait(false); - item.ID = serverSidePlaylist[^2].ID + 1; + item.ID = ++lastPlaylistItemId; await ((IMultiplayerClient)this).PlaylistItemAdded(item).ConfigureAwait(false); } @@ -447,15 +456,17 @@ namespace osu.Game.Tests.Visual.Multiplayer private async Task updatePlaylistOrder(MultiplayerRoom room) { - List orderedItems; + List orderedActiveItems; switch (room.Settings.QueueMode) { default: - orderedItems = serverSidePlaylist.OrderBy(item => item.ID == 0 ? int.MaxValue : item.ID).ToList(); + orderedActiveItems = serverSidePlaylist.Where(item => !item.Expired).OrderBy(item => item.ID == 0 ? int.MaxValue : item.ID).ToList(); break; case QueueMode.AllPlayersRoundRobin: + orderedActiveItems = new List(); + // Todo: This could probably be more efficient, likely at the cost of increased complexity. // Number of "expired" or "used" items per player. Dictionary perUserCounts = serverSidePlaylist @@ -463,7 +474,6 @@ namespace osu.Game.Tests.Visual.Multiplayer .ToDictionary(group => group.Key, group => group.Count(item => item.Expired)); // We'll run a simulation over all items which are not expired ("unprocessed"). Expired items will not have their ordering updated. - List processedItems = serverSidePlaylist.Where(item => item.Expired).ToList(); List unprocessedItems = serverSidePlaylist.Where(item => !item.Expired).ToList(); // In every iteration of the simulation, pick the first available item from the user with the lowest number of items in the queue to add to the result set. @@ -476,31 +486,40 @@ namespace osu.Game.Tests.Visual.Multiplayer .First(); unprocessedItems.Remove(candidateItem); - processedItems.Add(candidateItem); + orderedActiveItems.Add(candidateItem); perUserCounts[candidateItem.OwnerID]++; } - orderedItems = processedItems; break; } - for (int i = 0; i < orderedItems.Count; i++) - { - // Items which are already ordered correct don't need to be updated. - if (orderedItems[i].PlaylistOrder == i) - continue; + // For expired items, it's important that they're ordered in ascending order such that the last updated item is the last in the list. + // This is so that the updated_at database column doesn't get refreshed as a result of change in ordering. + List orderedExpiredItems = serverSidePlaylist.Where(item => item.Expired).OrderBy(item => item.UpdatedAt).ToList(); + for (int i = 0; i < orderedExpiredItems.Count; i++) + await setOrder(orderedExpiredItems[i], (ushort)i).ConfigureAwait(false); - orderedItems[i].PlaylistOrder = (ushort)i; + for (int i = 0; i < orderedActiveItems.Count; i++) + await setOrder(orderedActiveItems[i], (ushort)i).ConfigureAwait(false); + + serverSidePlaylist.Clear(); + serverSidePlaylist.AddRange(orderedExpiredItems); + serverSidePlaylist.AddRange(orderedActiveItems); + + async Task setOrder(MultiplayerPlaylistItem item, ushort order) + { + if (item.PlaylistOrder == order) + return; + + item.PlaylistOrder = order; // Items which have an ID of 0 are not in the database, so avoid propagating database/hub events for them. - if (orderedItems[i].ID <= 0) - continue; + if (item.ID <= 0) + return; - await ((IMultiplayerClient)this).PlaylistItemChanged(orderedItems[i]).ConfigureAwait(false); + await ((IMultiplayerClient)this).PlaylistItemChanged(item).ConfigureAwait(false); } - - serverSidePlaylist = orderedItems; } } } From 2927b235dec25bc6c27cac9786e1d0736ccea593 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 3 Dec 2021 17:18:02 +0900 Subject: [PATCH 62/87] Add test coverage of mouse wheel scroll adjusting volume --- .../TestSceneMouseWheelVolumeAdjust.cs | 99 +++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 osu.Game.Tests/Visual/Navigation/TestSceneMouseWheelVolumeAdjust.cs diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneMouseWheelVolumeAdjust.cs b/osu.Game.Tests/Visual/Navigation/TestSceneMouseWheelVolumeAdjust.cs new file mode 100644 index 0000000000..9e684e4f10 --- /dev/null +++ b/osu.Game.Tests/Visual/Navigation/TestSceneMouseWheelVolumeAdjust.cs @@ -0,0 +1,99 @@ +// 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.Game.Configuration; +using osu.Game.Screens.Play; +using osu.Game.Tests.Beatmaps.IO; +using osuTK.Input; + +namespace osu.Game.Tests.Visual.Navigation +{ + public class TestSceneMouseWheelVolumeAdjust : OsuGameTestScene + { + public override void SetUpSteps() + { + base.SetUpSteps(); + + // Headless tests are always at minimum volume. This covers interactive tests, matching that initial value. + AddStep("Set volume to min", () => Game.Audio.Volume.Value = 0); + AddAssert("Volume is min", () => Game.Audio.AggregateVolume.Value == 0); + AddStep("Move mouse to centre", () => InputManager.MoveMouseTo(Game.ScreenSpaceDrawQuad.Centre)); + } + + [Test] + public void TestAdjustVolumeFromMainMenu() + { + // First scroll makes volume controls appear, second adjusts volume. + AddRepeatStep("Adjust volume using mouse wheel", () => InputManager.ScrollVerticalBy(5), 2); + AddUntilStep("Volume is above zero", () => Game.Audio.AggregateVolume.Value > 0); + } + + [Test] + public void TestAdjustVolumeFromPlayerWheelEnabled() + { + loadToPlayerNonBreakTime(); + + // First scroll makes volume controls appear, second adjusts volume. + AddRepeatStep("Adjust volume using mouse wheel", () => InputManager.ScrollVerticalBy(5), 2); + AddAssert("Volume is above zero", () => Game.Audio.Volume.Value > 0); + } + + [Test] + public void TestAdjustVolumeFromPlayerWheelDisabled() + { + AddStep("disable wheel volume adjust", () => Game.LocalConfig.SetValue(OsuSetting.MouseDisableWheel, true)); + + loadToPlayerNonBreakTime(); + + // First scroll makes volume controls appear, second adjusts volume. + AddRepeatStep("Adjust volume using mouse wheel", () => InputManager.ScrollVerticalBy(5), 2); + AddAssert("Volume is still zero", () => Game.Audio.Volume.Value == 0); + } + + [Test] + public void TestAdjustVolumeFromPlayerWheelDisabledHoldingAlt() + { + AddStep("disable wheel volume adjust", () => Game.LocalConfig.SetValue(OsuSetting.MouseDisableWheel, true)); + + loadToPlayerNonBreakTime(); + + // First scroll makes volume controls appear, second adjusts volume. + AddRepeatStep("Adjust volume using mouse wheel holding alt", () => + { + InputManager.PressKey(Key.AltLeft); + InputManager.ScrollVerticalBy(5); + InputManager.ReleaseKey(Key.AltLeft); + }, 2); + + AddAssert("Volume is above zero", () => Game.Audio.Volume.Value > 0); + } + + private void loadToPlayerNonBreakTime() + { + Player player = null; + Screens.Select.SongSelect songSelect = null; + PushAndConfirm(() => songSelect = new TestSceneScreenNavigation.TestPlaySongSelect()); + AddUntilStep("wait for song select", () => songSelect.BeatmapSetsLoaded); + + AddStep("import beatmap", () => ImportBeatmapTest.LoadOszIntoOsu(Game, virtualTrack: true).Wait()); + AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault); + AddStep("press enter", () => InputManager.Key(Key.Enter)); + + AddUntilStep("wait for player", () => + { + // dismiss any notifications that may appear (ie. muted notification). + clickMouseInCentre(); + return (player = Game.ScreenStack.CurrentScreen as Player) != null; + }); + + AddUntilStep("wait for play time active", () => !player.IsBreakTime.Value); + } + + private void clickMouseInCentre() + { + InputManager.MoveMouseTo(Game.ScreenSpaceDrawQuad.Centre); + InputManager.Click(MouseButton.Left); + } + } +} From aaa46960b3967562034481e9773f6d2d2af51893 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 3 Dec 2021 16:56:14 +0900 Subject: [PATCH 63/87] Reword mouse wheel disable setting to better explain its purpose --- osu.Game/Localisation/MouseSettingsStrings.cs | 9 +++++++-- .../Overlays/Settings/Sections/Input/MouseSettings.cs | 3 ++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/osu.Game/Localisation/MouseSettingsStrings.cs b/osu.Game/Localisation/MouseSettingsStrings.cs index 5e894c4e0b..fd7225ad2e 100644 --- a/osu.Game/Localisation/MouseSettingsStrings.cs +++ b/osu.Game/Localisation/MouseSettingsStrings.cs @@ -35,9 +35,14 @@ namespace osu.Game.Localisation public static LocalisableString ConfineMouseMode => new TranslatableString(getKey(@"confine_mouse_mode"), @"Confine mouse cursor to window"); /// - /// "Disable mouse wheel during gameplay" + /// "Disable mouse wheel adjusting volume during gameplay" /// - public static LocalisableString DisableMouseWheel => new TranslatableString(getKey(@"disable_mouse_wheel"), @"Disable mouse wheel during gameplay"); + public static LocalisableString DisableMouseWheelVolumeAdjust => new TranslatableString(getKey(@"disable_mouse_wheel_volume_adjust"), @"Disable mouse wheel adjusting volume during gameplay"); + + /// + /// "Volume can still be adjusted using the mouse wheel by holding "Alt"" + /// + public static LocalisableString DisableMouseWheelVolumeAdjustTooltip => new TranslatableString(getKey(@"disable_mouse_wheel_volume_adjust_tooltip"), @"Volume can still be adjusted using the mouse wheel by holding ""Alt"""); /// /// "Disable mouse buttons during gameplay" diff --git a/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs index 0334167759..4235dc0a05 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs @@ -67,7 +67,8 @@ namespace osu.Game.Overlays.Settings.Sections.Input }, new SettingsCheckbox { - LabelText = MouseSettingsStrings.DisableMouseWheel, + LabelText = MouseSettingsStrings.DisableMouseWheelVolumeAdjust, + TooltipText = MouseSettingsStrings.DisableMouseWheelVolumeAdjustTooltip, Current = osuConfig.GetBindable(OsuSetting.MouseDisableWheel) }, new SettingsCheckbox From 6b7367240354715ade59470e1488e90ba3772cc7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 3 Dec 2021 16:56:34 +0900 Subject: [PATCH 64/87] Stop `Player` from blocking volume adjust when `Alt` it held Similar case to what we already have in `OsuScrollContainer`, so there is precedent for handling this locally in this fashion. --- osu.Game/Screens/Play/Player.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 4d574dea99..a0e9428cff 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -768,7 +768,15 @@ namespace osu.Game.Screens.Play Scheduler.Add(resultsDisplayDelegate); } - protected override bool OnScroll(ScrollEvent e) => mouseWheelDisabled.Value && !GameplayClockContainer.IsPaused.Value; + protected override bool OnScroll(ScrollEvent e) + { + // During pause, allow global volume adjust regardless of settings. + if (GameplayClockContainer.IsPaused.Value) + return false; + + // Block global volume adjust if the user has asked for it (special case when holding "Alt"). + return mouseWheelDisabled.Value && !e.AltPressed; + } #region Fail Logic From 675ecb603f7500dd3b92547b703e4e14b985be3b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 3 Dec 2021 17:50:07 +0900 Subject: [PATCH 65/87] Add `IRulesetStore` to allow for transitional usage in upcoming manager classes --- osu.Game.Tests/Database/RulesetStoreTests.cs | 6 ++-- osu.Game/Rulesets/IRulesetStore.cs | 31 ++++++++++++++++++++ osu.Game/Rulesets/RulesetStore.cs | 10 ++++++- osu.Game/Stores/RealmRulesetStore.cs | 18 ++++++++---- 4 files changed, 56 insertions(+), 9 deletions(-) create mode 100644 osu.Game/Rulesets/IRulesetStore.cs diff --git a/osu.Game.Tests/Database/RulesetStoreTests.cs b/osu.Game.Tests/Database/RulesetStoreTests.cs index f4e0838be1..b82dab1874 100644 --- a/osu.Game.Tests/Database/RulesetStoreTests.cs +++ b/osu.Game.Tests/Database/RulesetStoreTests.cs @@ -45,9 +45,9 @@ namespace osu.Game.Tests.Database { var rulesets = new RealmRulesetStore(realmFactory, storage); - Assert.IsTrue((rulesets.AvailableRulesets.First() as RealmRuleset)?.IsManaged == false); - Assert.IsTrue((rulesets.GetRuleset(0) as RealmRuleset)?.IsManaged == false); - Assert.IsTrue((rulesets.GetRuleset("mania") as RealmRuleset)?.IsManaged == false); + Assert.IsTrue(rulesets.AvailableRulesets.First().IsManaged == false); + Assert.IsTrue(rulesets.GetRuleset(0)?.IsManaged == false); + Assert.IsTrue(rulesets.GetRuleset("mania")?.IsManaged == false); }); } } diff --git a/osu.Game/Rulesets/IRulesetStore.cs b/osu.Game/Rulesets/IRulesetStore.cs new file mode 100644 index 0000000000..08d907810b --- /dev/null +++ b/osu.Game/Rulesets/IRulesetStore.cs @@ -0,0 +1,31 @@ +// 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; + +#nullable enable + +namespace osu.Game.Rulesets +{ + public interface IRulesetStore + { + /// + /// Retrieve a ruleset using a known ID. + /// + /// The ruleset's internal ID. + /// A ruleset, if available, else null. + IRulesetInfo? GetRuleset(int id); + + /// + /// Retrieve a ruleset using a known short name. + /// + /// The ruleset's short name. + /// A ruleset, if available, else null. + IRulesetInfo? GetRuleset(string shortName); + + /// + /// All available rulesets. + /// + IEnumerable AvailableRulesets { get; } + } +} diff --git a/osu.Game/Rulesets/RulesetStore.cs b/osu.Game/Rulesets/RulesetStore.cs index 6dd036c0e6..5cc6a75f43 100644 --- a/osu.Game/Rulesets/RulesetStore.cs +++ b/osu.Game/Rulesets/RulesetStore.cs @@ -13,7 +13,7 @@ using osu.Game.Database; namespace osu.Game.Rulesets { - public class RulesetStore : DatabaseBackedStore, IDisposable + public class RulesetStore : DatabaseBackedStore, IRulesetStore, IDisposable { private const string ruleset_library_prefix = "osu.Game.Rulesets"; @@ -236,5 +236,13 @@ namespace osu.Game.Rulesets { AppDomain.CurrentDomain.AssemblyResolve -= resolveRulesetDependencyAssembly; } + + #region Implementation of IRulesetStore + + IRulesetInfo IRulesetStore.GetRuleset(int id) => GetRuleset(id); + IRulesetInfo IRulesetStore.GetRuleset(string shortName) => GetRuleset(shortName); + IEnumerable IRulesetStore.AvailableRulesets => AvailableRulesets; + + #endregion } } diff --git a/osu.Game/Stores/RealmRulesetStore.cs b/osu.Game/Stores/RealmRulesetStore.cs index 0119aec9a4..93b6d29e7d 100644 --- a/osu.Game/Stores/RealmRulesetStore.cs +++ b/osu.Game/Stores/RealmRulesetStore.cs @@ -18,7 +18,7 @@ using osu.Game.Rulesets; namespace osu.Game.Stores { - public class RealmRulesetStore : IDisposable + public class RealmRulesetStore : IRulesetStore, IDisposable { private readonly RealmContextFactory realmFactory; @@ -29,9 +29,9 @@ namespace osu.Game.Stores /// /// All available rulesets. /// - public IEnumerable AvailableRulesets => availableRulesets; + public IEnumerable AvailableRulesets => availableRulesets; - private readonly List availableRulesets = new List(); + private readonly List availableRulesets = new List(); public RealmRulesetStore(RealmContextFactory realmFactory, Storage? storage = null) { @@ -64,14 +64,14 @@ namespace osu.Game.Stores /// /// The ruleset's internal ID. /// A ruleset, if available, else null. - public IRulesetInfo? GetRuleset(int id) => AvailableRulesets.FirstOrDefault(r => r.OnlineID == id); + public RealmRuleset? GetRuleset(int id) => AvailableRulesets.FirstOrDefault(r => r.OnlineID == id); /// /// Retrieve a ruleset using a known short name. /// /// The ruleset's short name. /// A ruleset, if available, else null. - public IRulesetInfo? GetRuleset(string shortName) => AvailableRulesets.FirstOrDefault(r => r.ShortName == shortName); + public RealmRuleset? GetRuleset(string shortName) => AvailableRulesets.FirstOrDefault(r => r.ShortName == shortName); private Assembly? resolveRulesetDependencyAssembly(object? sender, ResolveEventArgs args) { @@ -258,5 +258,13 @@ namespace osu.Game.Stores { AppDomain.CurrentDomain.AssemblyResolve -= resolveRulesetDependencyAssembly; } + + #region Implementation of IRulesetStore + + IRulesetInfo? IRulesetStore.GetRuleset(int id) => GetRuleset(id); + IRulesetInfo? IRulesetStore.GetRuleset(string shortName) => GetRuleset(shortName); + IEnumerable IRulesetStore.AvailableRulesets => AvailableRulesets; + + #endregion } } From 15db1372aafa23a0b431fdf54465f1b7fdf55d18 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 3 Dec 2021 18:01:29 +0900 Subject: [PATCH 66/87] Add missing equality implementations on `IRulesetInfo` --- osu.Game/Models/RealmRuleset.cs | 2 ++ osu.Game/Rulesets/IRulesetInfo.cs | 3 ++- osu.Game/Rulesets/RulesetInfo.cs | 2 ++ 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Models/RealmRuleset.cs b/osu.Game/Models/RealmRuleset.cs index 9a7488fda2..b959d0b4dc 100644 --- a/osu.Game/Models/RealmRuleset.cs +++ b/osu.Game/Models/RealmRuleset.cs @@ -50,6 +50,8 @@ namespace osu.Game.Models public bool Equals(RealmRuleset? other) => other != null && OnlineID == other.OnlineID && Available == other.Available && Name == other.Name && InstantiationInfo == other.InstantiationInfo; + public bool Equals(IRulesetInfo? other) => other is RealmRuleset b && Equals(b); + public override string ToString() => Name; public RealmRuleset Clone() => new RealmRuleset diff --git a/osu.Game/Rulesets/IRulesetInfo.cs b/osu.Game/Rulesets/IRulesetInfo.cs index 4e529a73fb..6599e0d59d 100644 --- a/osu.Game/Rulesets/IRulesetInfo.cs +++ b/osu.Game/Rulesets/IRulesetInfo.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.Game.Database; #nullable enable @@ -10,7 +11,7 @@ namespace osu.Game.Rulesets /// /// A representation of a ruleset's metadata. /// - public interface IRulesetInfo : IHasOnlineID + public interface IRulesetInfo : IHasOnlineID, IEquatable { /// /// The user-exposed name of this ruleset. diff --git a/osu.Game/Rulesets/RulesetInfo.cs b/osu.Game/Rulesets/RulesetInfo.cs index 4a146c05bf..d018cc4194 100644 --- a/osu.Game/Rulesets/RulesetInfo.cs +++ b/osu.Game/Rulesets/RulesetInfo.cs @@ -49,6 +49,8 @@ namespace osu.Game.Rulesets public override bool Equals(object obj) => obj is RulesetInfo rulesetInfo && Equals(rulesetInfo); + public bool Equals(IRulesetInfo other) => other is RulesetInfo b && Equals(b); + [SuppressMessage("ReSharper", "NonReadonlyMemberInGetHashCode")] public override int GetHashCode() { From e75e209053adc92ba696110b87b5691a1aa0b9c0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 3 Dec 2021 18:14:44 +0900 Subject: [PATCH 67/87] Cache and consume `IRulesetStore` where feasible --- osu.Game.Tests/Visual/Menus/TestSceneToolbar.cs | 2 +- .../Visual/Online/TestSceneBeatmapRulesetSelector.cs | 2 +- .../Visual/Online/TestSceneBeatmapSetOverlay.cs | 2 +- osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs | 2 +- .../Ranking/TestSceneContractedPanelMiddleContent.cs | 2 +- .../Components/TestSceneTournamentModDisplay.cs | 2 +- osu.Game.Tournament/Components/TournamentModIcon.cs | 2 +- osu.Game.Tournament/IPC/FileBasedIPC.cs | 2 +- osu.Game/Beatmaps/DifficultyRecommender.cs | 6 +++--- osu.Game/Beatmaps/Drawables/DifficultyIcon.cs | 2 +- osu.Game/Online/API/Requests/GetUserRequest.cs | 6 +++--- osu.Game/Online/Multiplayer/MultiplayerClient.cs | 5 ++++- osu.Game/Online/Rooms/PlaylistItem.cs | 5 ++++- osu.Game/OsuGameBase.cs | 1 + osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs | 9 +++++++-- .../Profile/Sections/Recent/DrawableRecentActivity.cs | 2 +- osu.Game/Screens/OnlinePlay/Components/RoomManager.cs | 2 +- .../Multiplayer/Participants/ParticipantPanel.cs | 4 ++-- osu.Game/Users/UserActivity.cs | 10 +++++----- 19 files changed, 40 insertions(+), 28 deletions(-) diff --git a/osu.Game.Tests/Visual/Menus/TestSceneToolbar.cs b/osu.Game.Tests/Visual/Menus/TestSceneToolbar.cs index 57d60cea9e..c65595d82e 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneToolbar.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneToolbar.cs @@ -20,7 +20,7 @@ namespace osu.Game.Tests.Visual.Menus private TestToolbar toolbar; [Resolved] - private RulesetStore rulesets { get; set; } + private IRulesetStore rulesets { get; set; } [SetUp] public void SetUp() => Schedule(() => diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapRulesetSelector.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapRulesetSelector.cs index 90f3eb64e4..63741451f3 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapRulesetSelector.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapRulesetSelector.cs @@ -25,7 +25,7 @@ namespace osu.Game.Tests.Visual.Online } [Resolved] - private RulesetStore rulesets { get; set; } + private IRulesetStore rulesets { get; set; } [Test] public void TestMultipleRulesetsBeatmapSet() diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs index 3314e291e8..f87cca80b0 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs @@ -30,7 +30,7 @@ namespace osu.Game.Tests.Visual.Online } [Resolved] - private RulesetStore rulesets { get; set; } + private IRulesetStore rulesets { get; set; } [Test] public void TestLoading() diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs b/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs index 19e06beaad..52d5eb2c65 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs @@ -24,7 +24,7 @@ namespace osu.Game.Tests.Visual.Online private TestUserListPanel evast; [Resolved] - private RulesetStore rulesetStore { get; set; } + private IRulesetStore rulesetStore { get; set; } [SetUp] public void SetUp() => Schedule(() => diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneContractedPanelMiddleContent.cs b/osu.Game.Tests/Visual/Ranking/TestSceneContractedPanelMiddleContent.cs index acacdf8644..9ed135e8b8 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneContractedPanelMiddleContent.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneContractedPanelMiddleContent.cs @@ -21,7 +21,7 @@ namespace osu.Game.Tests.Visual.Ranking public class TestSceneContractedPanelMiddleContent : OsuTestScene { [Resolved] - private RulesetStore rulesetStore { get; set; } + private IRulesetStore rulesetStore { get; set; } [Test] public void TestShowPanel() diff --git a/osu.Game.Tournament.Tests/Components/TestSceneTournamentModDisplay.cs b/osu.Game.Tournament.Tests/Components/TestSceneTournamentModDisplay.cs index 3cd13df0d3..9feef36a02 100644 --- a/osu.Game.Tournament.Tests/Components/TestSceneTournamentModDisplay.cs +++ b/osu.Game.Tournament.Tests/Components/TestSceneTournamentModDisplay.cs @@ -19,7 +19,7 @@ namespace osu.Game.Tournament.Tests.Components private IAPIProvider api { get; set; } [Resolved] - private RulesetStore rulesets { get; set; } + private IRulesetStore rulesets { get; set; } private FillFlowContainer fillFlow; diff --git a/osu.Game.Tournament/Components/TournamentModIcon.cs b/osu.Game.Tournament/Components/TournamentModIcon.cs index 0fde263bc8..ed8a36c220 100644 --- a/osu.Game.Tournament/Components/TournamentModIcon.cs +++ b/osu.Game.Tournament/Components/TournamentModIcon.cs @@ -21,7 +21,7 @@ namespace osu.Game.Tournament.Components private readonly string modAcronym; [Resolved] - private RulesetStore rulesets { get; set; } + private IRulesetStore rulesets { get; set; } public TournamentModIcon(string modAcronym) { diff --git a/osu.Game.Tournament/IPC/FileBasedIPC.cs b/osu.Game.Tournament/IPC/FileBasedIPC.cs index a57f9fd691..5278d538d2 100644 --- a/osu.Game.Tournament/IPC/FileBasedIPC.cs +++ b/osu.Game.Tournament/IPC/FileBasedIPC.cs @@ -28,7 +28,7 @@ namespace osu.Game.Tournament.IPC protected IAPIProvider API { get; private set; } [Resolved] - protected RulesetStore Rulesets { get; private set; } + protected IRulesetStore Rulesets { get; private set; } [Resolved] private GameHost host { get; set; } diff --git a/osu.Game/Beatmaps/DifficultyRecommender.cs b/osu.Game/Beatmaps/DifficultyRecommender.cs index 8b00d0f7f2..3949e84f4a 100644 --- a/osu.Game/Beatmaps/DifficultyRecommender.cs +++ b/osu.Game/Beatmaps/DifficultyRecommender.cs @@ -25,7 +25,7 @@ namespace osu.Game.Beatmaps private IAPIProvider api { get; set; } [Resolved] - private RulesetStore rulesets { get; set; } + private IRulesetStore rulesets { get; set; } [Resolved] private Bindable ruleset { get; set; } @@ -35,7 +35,7 @@ namespace osu.Game.Beatmaps /// private int? requestedUserId; - private readonly Dictionary recommendedDifficultyMapping = new Dictionary(); + private readonly Dictionary recommendedDifficultyMapping = new Dictionary(); private readonly IBindable apiState = new Bindable(); @@ -101,7 +101,7 @@ namespace osu.Game.Beatmaps /// Rulesets ordered descending by their respective recommended difficulties. /// The currently selected ruleset will always be first. /// - private IEnumerable orderedRulesets + private IEnumerable orderedRulesets { get { diff --git a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs index 6e573cc2a0..82be0559a7 100644 --- a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs +++ b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs @@ -86,7 +86,7 @@ namespace osu.Game.Beatmaps.Drawables } [Resolved] - private RulesetStore rulesets { get; set; } + private IRulesetStore rulesets { get; set; } [BackgroundDependencyLoader] private void load(OsuColour colours) diff --git a/osu.Game/Online/API/Requests/GetUserRequest.cs b/osu.Game/Online/API/Requests/GetUserRequest.cs index e32451fc2f..28da5222f9 100644 --- a/osu.Game/Online/API/Requests/GetUserRequest.cs +++ b/osu.Game/Online/API/Requests/GetUserRequest.cs @@ -9,7 +9,7 @@ namespace osu.Game.Online.API.Requests public class GetUserRequest : APIRequest { public readonly string Lookup; - public readonly RulesetInfo Ruleset; + public readonly IRulesetInfo Ruleset; private readonly LookupType lookupType; /// @@ -24,7 +24,7 @@ namespace osu.Game.Online.API.Requests /// /// The user to get. /// The ruleset to get the user's info for. - public GetUserRequest(long? userId = null, RulesetInfo ruleset = null) + public GetUserRequest(long? userId = null, IRulesetInfo ruleset = null) { Lookup = userId.ToString(); lookupType = LookupType.Id; @@ -36,7 +36,7 @@ namespace osu.Game.Online.API.Requests /// /// The user to get. /// The ruleset to get the user's info for. - public GetUserRequest(string username = null, RulesetInfo ruleset = null) + public GetUserRequest(string username = null, IRulesetInfo ruleset = null) { Lookup = username; lookupType = LookupType.Username; diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index 0822c29376..19ee6a3af3 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -94,7 +94,7 @@ namespace osu.Game.Online.Multiplayer protected IAPIProvider API { get; private set; } = null!; [Resolved] - protected RulesetStore Rulesets { get; private set; } = null!; + protected IRulesetStore Rulesets { get; private set; } = null!; [Resolved] private UserLookupCache userLookupCache { get; set; } = null!; @@ -706,6 +706,9 @@ namespace osu.Game.Online.Multiplayer var apiBeatmap = await GetAPIBeatmap(item.BeatmapID).ConfigureAwait(false); var ruleset = Rulesets.GetRuleset(item.RulesetID); + + Debug.Assert(ruleset != null); + var rulesetInstance = ruleset.CreateInstance(); var playlistItem = new PlaylistItem diff --git a/osu.Game/Online/Rooms/PlaylistItem.cs b/osu.Game/Online/Rooms/PlaylistItem.cs index a1480865b8..3eae03c702 100644 --- a/osu.Game/Online/Rooms/PlaylistItem.cs +++ b/osu.Game/Online/Rooms/PlaylistItem.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Diagnostics; using System.Linq; using Newtonsoft.Json; using osu.Framework.Bindables; @@ -79,11 +80,13 @@ namespace osu.Game.Online.Rooms public void MarkInvalid() => valid.Value = false; - public void MapObjects(RulesetStore rulesets) + public void MapObjects(IRulesetStore rulesets) { Beatmap.Value ??= apiBeatmap; Ruleset.Value ??= rulesets.GetRuleset(RulesetID); + Debug.Assert(Ruleset.Value != null); + Ruleset rulesetInstance = Ruleset.Value.CreateInstance(); if (allowedModsBacking != null) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 34344f8022..e852e3955b 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -196,6 +196,7 @@ namespace osu.Game runMigrations(); dependencies.Cache(RulesetStore = new RulesetStore(contextFactory, Storage)); + dependencies.CacheAs(RulesetStore); dependencies.Cache(realmFactory = new RealmContextFactory(Storage, "client", contextFactory)); diff --git a/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs b/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs index a9723c9c62..25aed4c980 100644 --- a/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs +++ b/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs @@ -47,7 +47,7 @@ namespace osu.Game.Overlays.BeatmapSet } [Resolved] - private RulesetStore rulesets { get; set; } + private IRulesetStore rulesets { get; set; } private void onRulesetChanged(ValueChangedEvent ruleset) { @@ -57,8 +57,13 @@ namespace osu.Game.Overlays.BeatmapSet if (ruleset.NewValue == null) return; + var rulesetInstance = rulesets.GetRuleset(ruleset.NewValue.OnlineID)?.CreateInstance(); + + if (rulesetInstance == null) + return; + modsContainer.Add(new ModButton(new ModNoMod())); - modsContainer.AddRange(rulesets.GetRuleset(ruleset.NewValue.OnlineID).CreateInstance().AllMods.Where(m => m.UserPlayable).Select(m => new ModButton(m))); + modsContainer.AddRange(rulesetInstance.AllMods.Where(m => m.UserPlayable).Select(m => new ModButton(m))); modsContainer.ForEach(button => { diff --git a/osu.Game/Overlays/Profile/Sections/Recent/DrawableRecentActivity.cs b/osu.Game/Overlays/Profile/Sections/Recent/DrawableRecentActivity.cs index cb8dae0bbc..7a27c6e4e1 100644 --- a/osu.Game/Overlays/Profile/Sections/Recent/DrawableRecentActivity.cs +++ b/osu.Game/Overlays/Profile/Sections/Recent/DrawableRecentActivity.cs @@ -25,7 +25,7 @@ namespace osu.Game.Overlays.Profile.Sections.Recent private IAPIProvider api { get; set; } [Resolved] - private RulesetStore rulesets { get; set; } + private IRulesetStore rulesets { get; set; } private readonly APIRecentActivity activity; diff --git a/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs b/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs index ddfdab18f7..1391422f9f 100644 --- a/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs +++ b/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs @@ -28,7 +28,7 @@ namespace osu.Game.Screens.OnlinePlay.Components private readonly Bindable joinedRoom = new Bindable(); [Resolved] - private RulesetStore rulesets { get; set; } + private IRulesetStore rulesets { get; set; } [Resolved] private BeatmapManager beatmaps { get; set; } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs index 3152f50d3d..a68309dae5 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs @@ -35,7 +35,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants private IAPIProvider api { get; set; } [Resolved] - private RulesetStore rulesets { get; set; } + private IRulesetStore rulesets { get; set; } private SpriteIcon crown; @@ -185,7 +185,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants const double fade_time = 50; // Todo: Should use the room's selected item to determine ruleset. - var ruleset = rulesets.GetRuleset(0).CreateInstance(); + var ruleset = rulesets.GetRuleset(0)?.CreateInstance(); int? currentModeRank = User.User?.RulesetsStatistics?.GetValueOrDefault(ruleset.ShortName)?.GlobalRank; userRankText.Text = currentModeRank != null ? $"#{currentModeRank.Value:N0}" : string.Empty; diff --git a/osu.Game/Users/UserActivity.cs b/osu.Game/Users/UserActivity.cs index 0874685f49..516aa80652 100644 --- a/osu.Game/Users/UserActivity.cs +++ b/osu.Game/Users/UserActivity.cs @@ -29,9 +29,9 @@ namespace osu.Game.Users { public IBeatmapInfo BeatmapInfo { get; } - public RulesetInfo Ruleset { get; } + public IRulesetInfo Ruleset { get; } - protected InGame(IBeatmapInfo beatmapInfo, RulesetInfo ruleset) + protected InGame(IBeatmapInfo beatmapInfo, IRulesetInfo ruleset) { BeatmapInfo = beatmapInfo; Ruleset = ruleset; @@ -42,7 +42,7 @@ namespace osu.Game.Users public class InMultiplayerGame : InGame { - public InMultiplayerGame(IBeatmapInfo beatmapInfo, RulesetInfo ruleset) + public InMultiplayerGame(IBeatmapInfo beatmapInfo, IRulesetInfo ruleset) : base(beatmapInfo, ruleset) { } @@ -52,7 +52,7 @@ namespace osu.Game.Users public class InPlaylistGame : InGame { - public InPlaylistGame(IBeatmapInfo beatmapInfo, RulesetInfo ruleset) + public InPlaylistGame(IBeatmapInfo beatmapInfo, IRulesetInfo ruleset) : base(beatmapInfo, ruleset) { } @@ -60,7 +60,7 @@ namespace osu.Game.Users public class InSoloGame : InGame { - public InSoloGame(IBeatmapInfo beatmapInfo, RulesetInfo ruleset) + public InSoloGame(IBeatmapInfo beatmapInfo, IRulesetInfo ruleset) : base(beatmapInfo, ruleset) { } From 2acf46154af93674dcb178e117dae20116243198 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 3 Dec 2021 18:16:29 +0900 Subject: [PATCH 68/87] Remove many unused resolutions of `RulesetStore` --- .../TestSceneMatchBeatmapDetailArea.cs | 9 -------- .../TestSceneTournamentBeatmapPanel.cs | 10 +------- .../Leaderboards/UserTopScoreContainer.cs | 5 ---- .../BeatmapListingFilterControl.cs | 4 ---- osu.Game/Overlays/BeatmapSetOverlay.cs | 5 ---- .../Sections/PaginatedProfileSubsection.cs | 20 +++++++--------- .../Overlays/Rankings/SpotlightsLayout.cs | 23 ++++++++----------- osu.Game/Screens/Play/SoloSpectator.cs | 5 ---- osu.Game/Screens/Select/BeatmapDetails.cs | 4 ---- 9 files changed, 19 insertions(+), 66 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapDetailArea.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapDetailArea.cs index d66603a448..1d61a5d496 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapDetailArea.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapDetailArea.cs @@ -2,11 +2,8 @@ // See the LICENCE file in the repository root for full licence text. using NUnit.Framework; -using osu.Framework.Allocation; using osu.Framework.Graphics; -using osu.Game.Beatmaps; using osu.Game.Online.Rooms; -using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Screens.OnlinePlay.Components; @@ -18,12 +15,6 @@ namespace osu.Game.Tests.Visual.Multiplayer { public class TestSceneMatchBeatmapDetailArea : OnlinePlayTestScene { - [Resolved] - private BeatmapManager beatmapManager { get; set; } - - [Resolved] - private RulesetStore rulesetStore { get; set; } - [SetUp] public new void Setup() => Schedule(() => { diff --git a/osu.Game.Tournament.Tests/Components/TestSceneTournamentBeatmapPanel.cs b/osu.Game.Tournament.Tests/Components/TestSceneTournamentBeatmapPanel.cs index 8139387a96..7132655535 100644 --- a/osu.Game.Tournament.Tests/Components/TestSceneTournamentBeatmapPanel.cs +++ b/osu.Game.Tournament.Tests/Components/TestSceneTournamentBeatmapPanel.cs @@ -3,28 +3,20 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; -using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests.Responses; -using osu.Game.Rulesets; using osu.Game.Tournament.Components; namespace osu.Game.Tournament.Tests.Components { public class TestSceneTournamentBeatmapPanel : TournamentTestScene { - [Resolved] - private IAPIProvider api { get; set; } - - [Resolved] - private RulesetStore rulesets { get; set; } - [BackgroundDependencyLoader] private void load() { var req = new GetBeatmapRequest(new APIBeatmap { OnlineID = 1091460 }); req.Success += success; - api.Queue(req); + API.Queue(req); } private void success(APIBeatmap beatmap) diff --git a/osu.Game/Online/Leaderboards/UserTopScoreContainer.cs b/osu.Game/Online/Leaderboards/UserTopScoreContainer.cs index ab4210251e..3db497bd6a 100644 --- a/osu.Game/Online/Leaderboards/UserTopScoreContainer.cs +++ b/osu.Game/Online/Leaderboards/UserTopScoreContainer.cs @@ -3,13 +3,11 @@ using System; using System.Threading; -using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; -using osu.Game.Rulesets; using osuTK; namespace osu.Game.Online.Leaderboards @@ -25,9 +23,6 @@ namespace osu.Game.Online.Leaderboards protected override bool StartHidden => true; - [Resolved] - private RulesetStore rulesets { get; set; } - public UserTopScoreContainer(Func createScoreDelegate) { this.createScoreDelegate = createScoreDelegate; diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs index 38f2bdb34f..f5b4785264 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs @@ -15,7 +15,6 @@ using osu.Framework.Threading; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests.Responses; -using osu.Game.Rulesets; using osu.Game.Resources.Localisation.Web; using osuTK; using osuTK.Graphics; @@ -61,9 +60,6 @@ namespace osu.Game.Overlays.BeatmapListing [Resolved] private IAPIProvider api { get; set; } - [Resolved] - private RulesetStore rulesets { get; set; } - public BeatmapListingFilterControl() { RelativeSizeAxes = Axes.X; diff --git a/osu.Game/Overlays/BeatmapSetOverlay.cs b/osu.Game/Overlays/BeatmapSetOverlay.cs index fa5a7c66d0..b9d3854066 100644 --- a/osu.Game/Overlays/BeatmapSetOverlay.cs +++ b/osu.Game/Overlays/BeatmapSetOverlay.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System.Linq; -using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -12,7 +11,6 @@ using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays.BeatmapSet; using osu.Game.Overlays.BeatmapSet.Scores; using osu.Game.Overlays.Comments; -using osu.Game.Rulesets; using osuTK; using osuTK.Graphics; @@ -24,9 +22,6 @@ namespace osu.Game.Overlays public const float Y_PADDING = 25; public const float RIGHT_WIDTH = 275; - [Resolved] - private RulesetStore rulesets { get; set; } - private readonly Bindable beatmapSet = new Bindable(); // receive input outside our bounds so we can trigger a close event on ourselves. diff --git a/osu.Game/Overlays/Profile/Sections/PaginatedProfileSubsection.cs b/osu.Game/Overlays/Profile/Sections/PaginatedProfileSubsection.cs index affe9ecb0c..130ae44273 100644 --- a/osu.Game/Overlays/Profile/Sections/PaginatedProfileSubsection.cs +++ b/osu.Game/Overlays/Profile/Sections/PaginatedProfileSubsection.cs @@ -1,21 +1,20 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osuTK; +using System.Collections.Generic; +using System.Linq; +using System.Threading; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Game.Online.API; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using osu.Game.Graphics.UserInterface; -using osu.Game.Rulesets; -using osu.Game.Graphics.Sprites; -using osu.Game.Graphics; using osu.Framework.Localisation; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Game.Online.API; using osu.Game.Online.API.Requests.Responses; +using osuTK; namespace osu.Game.Overlays.Profile.Sections { @@ -24,9 +23,6 @@ namespace osu.Game.Overlays.Profile.Sections [Resolved] private IAPIProvider api { get; set; } - [Resolved] - protected RulesetStore Rulesets { get; private set; } - protected int VisiblePages; protected int ItemsPerPage; diff --git a/osu.Game/Overlays/Rankings/SpotlightsLayout.cs b/osu.Game/Overlays/Rankings/SpotlightsLayout.cs index cc553ad361..a37f762532 100644 --- a/osu.Game/Overlays/Rankings/SpotlightsLayout.cs +++ b/osu.Game/Overlays/Rankings/SpotlightsLayout.cs @@ -1,21 +1,21 @@ // 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.Graphics; -using osu.Framework.Bindables; -using osu.Game.Rulesets; -using osu.Framework.Graphics.Containers; -using osu.Game.Online.API.Requests.Responses; -using osuTK; -using osu.Framework.Allocation; -using osu.Game.Online.API; -using osu.Game.Online.API.Requests; -using osu.Game.Overlays.Rankings.Tables; using System.Linq; using System.Threading; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps.Drawables.Cards; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Overlays.Rankings.Tables; +using osu.Game.Rulesets; +using osuTK; namespace osu.Game.Overlays.Rankings { @@ -29,9 +29,6 @@ namespace osu.Game.Overlays.Rankings [Resolved] private IAPIProvider api { get; set; } - [Resolved] - private RulesetStore rulesets { get; set; } - private CancellationTokenSource cancellationToken; private GetSpotlightRankingsRequest getRankingsRequest; private GetSpotlightsRequest spotlightsRequest; diff --git a/osu.Game/Screens/Play/SoloSpectator.cs b/osu.Game/Screens/Play/SoloSpectator.cs index 45601999a0..7fea44b3ea 100644 --- a/osu.Game/Screens/Play/SoloSpectator.cs +++ b/osu.Game/Screens/Play/SoloSpectator.cs @@ -23,12 +23,10 @@ using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Spectator; using osu.Game.Overlays; using osu.Game.Overlays.Settings; -using osu.Game.Rulesets; using osu.Game.Screens.OnlinePlay.Match.Components; using osu.Game.Screens.Spectate; using osu.Game.Users; using osuTK; -using APIUser = osu.Game.Online.API.Requests.Responses.APIUser; namespace osu.Game.Screens.Play { @@ -44,9 +42,6 @@ namespace osu.Game.Screens.Play [Resolved] private PreviewTrackManager previewTrackManager { get; set; } - [Resolved] - private RulesetStore rulesets { get; set; } - [Resolved] private BeatmapManager beatmaps { get; set; } diff --git a/osu.Game/Screens/Select/BeatmapDetails.cs b/osu.Game/Screens/Select/BeatmapDetails.cs index 7543c89f17..bbe0a37d8e 100644 --- a/osu.Game/Screens/Select/BeatmapDetails.cs +++ b/osu.Game/Screens/Select/BeatmapDetails.cs @@ -16,7 +16,6 @@ using osu.Game.Online; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Overlays.BeatmapSet; -using osu.Game.Rulesets; using osu.Game.Screens.Select.Details; using osuTK; using osuTK.Graphics; @@ -38,9 +37,6 @@ namespace osu.Game.Screens.Select [Resolved] private IAPIProvider api { get; set; } - [Resolved] - private RulesetStore rulesets { get; set; } - private IBeatmapInfo beatmapInfo; private APIFailTimes failTimes; From 1eed2436e6d76b94312541b01e1fd7b0baf8632e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 3 Dec 2021 18:37:45 +0900 Subject: [PATCH 69/87] Clean up unused resolved properties --- osu.Game.Rulesets.Mania/Edit/ManiaBeatSnapGrid.cs | 7 ------- osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs | 4 ---- osu.Game.Rulesets.Mania/UI/PoolableHitExplosion.cs | 3 --- osu.Game.Rulesets.Taiko/UI/InputDrum.cs | 4 ---- .../Beatmaps/TestSceneBeatmapDifficultyCache.cs | 3 --- osu.Game.Tests/Input/ConfineMouseTrackerTest.cs | 3 --- osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs | 5 ----- .../Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs | 4 ---- .../Multiplayer/TestSceneMatchBeatmapDetailArea.cs | 9 --------- .../Visual/Multiplayer/TestScenePlaylistsSongSelect.cs | 3 --- osu.Game.Tests/Visual/Navigation/TestSceneOsuGame.cs | 3 --- osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs | 4 ---- .../Visual/Online/TestSceneUserProfileOverlay.cs | 5 ----- .../Ranking/TestSceneContractedPanelMiddleContent.cs | 4 ---- .../TestSceneUpdateableBeatmapBackgroundSprite.cs | 3 --- .../Components/TestSceneTournamentBeatmapPanel.cs | 4 ---- .../Screens/Editors/SeedingEditorScreen.cs | 6 ------ .../Screens/Setup/StablePathSelectScreen.cs | 3 --- osu.Game/Audio/PreviewTrackManager.cs | 3 --- osu.Game/Collections/CollectionFilterDropdown.cs | 4 ---- osu.Game/Collections/CollectionManager.cs | 3 --- osu.Game/Online/Leaderboards/LeaderboardScore.cs | 3 --- osu.Game/Online/Leaderboards/UserTopScoreContainer.cs | 5 ----- .../BeatmapListing/BeatmapListingFilterControl.cs | 4 ---- osu.Game/Overlays/BeatmapSetOverlay.cs | 5 ----- osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs | 3 --- osu.Game/Overlays/Rankings/SpotlightsLayout.cs | 3 --- osu.Game/Overlays/Wiki/Markdown/WikiMarkdownContainer.cs | 5 ----- osu.Game/Rulesets/UI/Playfield.cs | 4 ---- .../Timelines/Summary/Parts/GroupVisualisation.cs | 5 ----- .../Screens/Edit/Compose/Components/Timeline/Timeline.cs | 3 --- .../Components/Timeline/TimelineControlPointGroup.cs | 5 ----- .../Components/Timeline/TimelineHitObjectBlueprint.cs | 3 --- osu.Game/Screens/Edit/Editor.cs | 3 --- osu.Game/Screens/Edit/EditorRoundedScreen.cs | 4 ---- osu.Game/Screens/Edit/EditorTable.cs | 3 --- .../Screens/Edit/Setup/FileChooserLabelledTextBox.cs | 4 ---- osu.Game/Screens/Edit/Timing/ControlPointTable.cs | 3 --- osu.Game/Screens/Edit/Verify/IssueList.cs | 3 --- osu.Game/Screens/Menu/StorageErrorDialog.cs | 3 --- osu.Game/Screens/OnlinePlay/Components/RoomManager.cs | 4 ---- .../OnlinePlay/Components/SelectionPollingComponent.cs | 4 ---- .../Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs | 3 --- .../OnlinePlay/Lounge/Components/RoomsContainer.cs | 3 --- .../Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs | 8 -------- .../Multiplayer/Match/MultiplayerReadyButton.cs | 4 ---- .../Multiplayer/Spectate/MultiSpectatorScreen.cs | 3 --- osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs | 9 --------- .../OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs | 4 ---- .../Screens/OnlinePlay/Playlists/PlaylistsSongSelect.cs | 5 ----- osu.Game/Screens/Play/HUD/DefaultAccuracyCounter.cs | 3 --- osu.Game/Screens/Play/HUD/DefaultComboCounter.cs | 3 --- osu.Game/Screens/Play/HUD/DefaultScoreCounter.cs | 3 --- osu.Game/Screens/Play/HUD/LegacyComboCounter.cs | 3 --- osu.Game/Screens/Play/SoloSpectator.cs | 4 ---- osu.Game/Screens/Ranking/ScorePanelList.cs | 4 ---- osu.Game/Screens/Select/BeatmapDetails.cs | 4 ---- .../Screens/Select/Leaderboards/BeatmapLeaderboard.cs | 3 --- osu.Game/Skinning/Editor/SkinEditorOverlay.cs | 5 ----- osu.Game/Skinning/LegacyAccuracyCounter.cs | 5 ----- osu.Game/Skinning/PoolableSkinnableSample.cs | 4 ---- osu.Game/Skinning/SkinnableSound.cs | 4 ---- osu.Game/Updater/NoActionUpdateManager.cs | 4 ---- 63 files changed, 254 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaBeatSnapGrid.cs b/osu.Game.Rulesets.Mania/Edit/ManiaBeatSnapGrid.cs index 9d1f5429a1..1aa20f4737 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaBeatSnapGrid.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaBeatSnapGrid.cs @@ -9,7 +9,6 @@ using osu.Framework.Bindables; using osu.Framework.Caching; using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; -using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Mania.UI; @@ -46,12 +45,6 @@ namespace osu.Game.Rulesets.Mania.Edit [Resolved] private EditorBeatmap beatmap { get; set; } - [Resolved] - private IScrollingInfo scrollingInfo { get; set; } - - [Resolved] - private Bindable working { get; set; } - [Resolved] private OsuColour colours { get; set; } diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs b/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs index dc858fb54f..9fe1eb7932 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs @@ -7,16 +7,12 @@ using osu.Framework.Allocation; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Screens.Edit.Compose.Components; namespace osu.Game.Rulesets.Mania.Edit { public class ManiaSelectionHandler : EditorSelectionHandler { - [Resolved] - private IScrollingInfo scrollingInfo { get; set; } - [Resolved] private HitObjectComposer composer { get; set; } diff --git a/osu.Game.Rulesets.Mania/UI/PoolableHitExplosion.cs b/osu.Game.Rulesets.Mania/UI/PoolableHitExplosion.cs index 90d3c6c4c7..9f4963b022 100644 --- a/osu.Game.Rulesets.Mania/UI/PoolableHitExplosion.cs +++ b/osu.Game.Rulesets.Mania/UI/PoolableHitExplosion.cs @@ -15,9 +15,6 @@ namespace osu.Game.Rulesets.Mania.UI public JudgementResult Result { get; private set; } - [Resolved] - private Column column { get; set; } - private SkinnableDrawable skinnableExplosion; public PoolableHitExplosion() diff --git a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs index 861b800038..16be20f7f3 100644 --- a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs +++ b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs @@ -12,7 +12,6 @@ using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Rulesets.UI; -using osu.Game.Screens.Play; using osu.Game.Skinning; using osuTK; @@ -149,9 +148,6 @@ namespace osu.Game.Rulesets.Taiko.UI centreHit.Colour = colours.Pink; } - [Resolved(canBeNull: true)] - private GameplayClock gameplayClock { get; set; } - public bool OnPressed(KeyBindingPressEvent e) { Drawable target = null; diff --git a/osu.Game.Tests/Beatmaps/TestSceneBeatmapDifficultyCache.cs b/osu.Game.Tests/Beatmaps/TestSceneBeatmapDifficultyCache.cs index 2a60a7b96d..3a82cbc785 100644 --- a/osu.Game.Tests/Beatmaps/TestSceneBeatmapDifficultyCache.cs +++ b/osu.Game.Tests/Beatmaps/TestSceneBeatmapDifficultyCache.cs @@ -25,9 +25,6 @@ namespace osu.Game.Tests.Beatmaps private BeatmapSetInfo importedSet; - [Resolved] - private BeatmapManager beatmaps { get; set; } - private TestBeatmapDifficultyCache difficultyCache; private IBindable starDifficultyBindable; diff --git a/osu.Game.Tests/Input/ConfineMouseTrackerTest.cs b/osu.Game.Tests/Input/ConfineMouseTrackerTest.cs index b612899d79..28937b2120 100644 --- a/osu.Game.Tests/Input/ConfineMouseTrackerTest.cs +++ b/osu.Game.Tests/Input/ConfineMouseTrackerTest.cs @@ -18,9 +18,6 @@ namespace osu.Game.Tests.Input [Resolved] private FrameworkConfigManager frameworkConfigManager { get; set; } - [Resolved] - private OsuConfigManager osuConfigManager { get; set; } - [TestCase(WindowMode.Windowed)] [TestCase(WindowMode.Borderless)] public void TestDisableConfining(WindowMode windowMode) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs index a0b27755b7..a0602e21b9 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs @@ -2,12 +2,10 @@ // See the LICENCE file in the repository root for full licence text. using NUnit.Framework; -using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Testing; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; -using osu.Game.Skinning; using osu.Game.Skinning.Editor; namespace osu.Game.Tests.Visual.Gameplay @@ -16,9 +14,6 @@ namespace osu.Game.Tests.Visual.Gameplay { private SkinEditor skinEditor; - [Resolved] - private SkinManager skinManager { get; set; } - protected override bool Autoplay => true; [SetUpSteps] diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs index 723e35ed55..3074a91dc6 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs @@ -10,7 +10,6 @@ using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Testing; -using osu.Game.Configuration; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu; @@ -36,9 +35,6 @@ namespace osu.Game.Tests.Visual.Gameplay private Drawable hideTarget => hudOverlay.KeyCounter; private FillFlowContainer keyCounterFlow => hudOverlay.KeyCounter.ChildrenOfType>().First(); - [Resolved] - private OsuConfigManager config { get; set; } - [Test] public void TestComboCounterIncrementing() { diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapDetailArea.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapDetailArea.cs index d66603a448..1d61a5d496 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapDetailArea.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapDetailArea.cs @@ -2,11 +2,8 @@ // See the LICENCE file in the repository root for full licence text. using NUnit.Framework; -using osu.Framework.Allocation; using osu.Framework.Graphics; -using osu.Game.Beatmaps; using osu.Game.Online.Rooms; -using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Screens.OnlinePlay.Components; @@ -18,12 +15,6 @@ namespace osu.Game.Tests.Visual.Multiplayer { public class TestSceneMatchBeatmapDetailArea : OnlinePlayTestScene { - [Resolved] - private BeatmapManager beatmapManager { get; set; } - - [Resolved] - private RulesetStore rulesetStore { get; set; } - [SetUp] public new void Setup() => Schedule(() => { diff --git a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs index 35c66e8cda..5aac228f4b 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs @@ -24,9 +24,6 @@ namespace osu.Game.Tests.Visual.Multiplayer { public class TestScenePlaylistsSongSelect : OnlinePlayTestScene { - [Resolved] - private BeatmapManager beatmapManager { get; set; } - private BeatmapManager manager; private RulesetStore rulesets; diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneOsuGame.cs b/osu.Game.Tests/Visual/Navigation/TestSceneOsuGame.cs index 2706ff5ceb..4d1e279090 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneOsuGame.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneOsuGame.cs @@ -83,9 +83,6 @@ namespace osu.Game.Tests.Visual.Navigation [Resolved] private OsuGameBase gameBase { get; set; } - [Resolved] - private GameHost host { get; set; } - [Test] public void TestNullRulesetHandled() { diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs index 7028ecf39f..9c65b2dc51 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs @@ -13,7 +13,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input; -using osu.Framework.Platform; using osu.Framework.Testing; using osu.Game.Graphics.UserInterface; using osu.Game.Online.API; @@ -47,9 +46,6 @@ namespace osu.Game.Tests.Visual.Online [CanBeNull] private Func> onGetMessages; - [Resolved] - private GameHost host { get; set; } - public TestSceneChatOverlay() { channels = Enumerable.Range(1, 10) diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs index ce8136199f..1c92bb1e38 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs @@ -4,8 +4,6 @@ using System; using System.Linq; using NUnit.Framework; -using osu.Framework.Allocation; -using osu.Game.Online.API; using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays; using osu.Game.Overlays.Profile; @@ -20,9 +18,6 @@ namespace osu.Game.Tests.Visual.Online private readonly TestUserProfileOverlay profile; - [Resolved] - private IAPIProvider api { get; set; } - public static readonly APIUser TEST_USER = new APIUser { Username = @"Somebody", diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneContractedPanelMiddleContent.cs b/osu.Game.Tests/Visual/Ranking/TestSceneContractedPanelMiddleContent.cs index acacdf8644..f246560c82 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneContractedPanelMiddleContent.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneContractedPanelMiddleContent.cs @@ -9,7 +9,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Beatmaps; -using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; using osu.Game.Scoring; using osu.Game.Screens.Ranking; @@ -20,9 +19,6 @@ namespace osu.Game.Tests.Visual.Ranking { public class TestSceneContractedPanelMiddleContent : OsuTestScene { - [Resolved] - private RulesetStore rulesetStore { get; set; } - [Test] public void TestShowPanel() { diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneUpdateableBeatmapBackgroundSprite.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneUpdateableBeatmapBackgroundSprite.cs index d30f1e8889..3fa9b8b877 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneUpdateableBeatmapBackgroundSprite.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneUpdateableBeatmapBackgroundSprite.cs @@ -26,9 +26,6 @@ namespace osu.Game.Tests.Visual.UserInterface private BeatmapSetInfo testBeatmap; private IAPIProvider api; - [Resolved] - private BeatmapManager beatmaps { get; set; } - [BackgroundDependencyLoader] private void load(OsuGameBase osu, IAPIProvider api, RulesetStore rulesets) { diff --git a/osu.Game.Tournament.Tests/Components/TestSceneTournamentBeatmapPanel.cs b/osu.Game.Tournament.Tests/Components/TestSceneTournamentBeatmapPanel.cs index 8139387a96..a96c5123e0 100644 --- a/osu.Game.Tournament.Tests/Components/TestSceneTournamentBeatmapPanel.cs +++ b/osu.Game.Tournament.Tests/Components/TestSceneTournamentBeatmapPanel.cs @@ -6,7 +6,6 @@ using osu.Framework.Graphics; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests.Responses; -using osu.Game.Rulesets; using osu.Game.Tournament.Components; namespace osu.Game.Tournament.Tests.Components @@ -16,9 +15,6 @@ namespace osu.Game.Tournament.Tests.Components [Resolved] private IAPIProvider api { get; set; } - [Resolved] - private RulesetStore rulesets { get; set; } - [BackgroundDependencyLoader] private void load() { diff --git a/osu.Game.Tournament/Screens/Editors/SeedingEditorScreen.cs b/osu.Game.Tournament/Screens/Editors/SeedingEditorScreen.cs index 9abf1d3adb..5d2fddffd9 100644 --- a/osu.Game.Tournament/Screens/Editors/SeedingEditorScreen.cs +++ b/osu.Game.Tournament/Screens/Editors/SeedingEditorScreen.cs @@ -25,9 +25,6 @@ namespace osu.Game.Tournament.Screens.Editors protected override BindableList Storage => team.SeedingResults; - [Resolved(canBeNull: true)] - private TournamentSceneManager sceneManager { get; set; } - public SeedingEditorScreen(TournamentTeam team, TournamentScreen parentScreen) : base(parentScreen) { @@ -38,9 +35,6 @@ namespace osu.Game.Tournament.Screens.Editors { public SeedingResult Model { get; } - [Resolved] - private LadderInfo ladderInfo { get; set; } - public SeedingResultRow(TournamentTeam team, SeedingResult round) { Model = round; diff --git a/osu.Game.Tournament/Screens/Setup/StablePathSelectScreen.cs b/osu.Game.Tournament/Screens/Setup/StablePathSelectScreen.cs index 8e9b32231f..5a1ceecd01 100644 --- a/osu.Game.Tournament/Screens/Setup/StablePathSelectScreen.cs +++ b/osu.Game.Tournament/Screens/Setup/StablePathSelectScreen.cs @@ -21,9 +21,6 @@ namespace osu.Game.Tournament.Screens.Setup { public class StablePathSelectScreen : TournamentScreen { - [Resolved] - private GameHost host { get; set; } - [Resolved(canBeNull: true)] private TournamentSceneManager sceneManager { get; set; } diff --git a/osu.Game/Audio/PreviewTrackManager.cs b/osu.Game/Audio/PreviewTrackManager.cs index e631d35180..6d56d152f1 100644 --- a/osu.Game/Audio/PreviewTrackManager.cs +++ b/osu.Game/Audio/PreviewTrackManager.cs @@ -18,9 +18,6 @@ namespace osu.Game.Audio private readonly BindableDouble muteBindable = new BindableDouble(); - [Resolved] - private AudioManager audio { get; set; } - private ITrackStore trackStore; protected TrackManagerPreviewTrack CurrentTrack; diff --git a/osu.Game/Collections/CollectionFilterDropdown.cs b/osu.Game/Collections/CollectionFilterDropdown.cs index ad23874b2e..77bda00107 100644 --- a/osu.Game/Collections/CollectionFilterDropdown.cs +++ b/osu.Game/Collections/CollectionFilterDropdown.cs @@ -13,7 +13,6 @@ using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; using osu.Framework.Localisation; using osu.Game.Beatmaps; -using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osuTK; @@ -193,9 +192,6 @@ namespace osu.Game.Collections [NotNull] protected new CollectionFilterMenuItem Item => ((DropdownMenuItem)base.Item).Value; - [Resolved] - private OsuColour colours { get; set; } - [Resolved] private IBindable beatmap { get; set; } diff --git a/osu.Game/Collections/CollectionManager.cs b/osu.Game/Collections/CollectionManager.cs index 9ff92032b7..c4f991094c 100644 --- a/osu.Game/Collections/CollectionManager.cs +++ b/osu.Game/Collections/CollectionManager.cs @@ -40,9 +40,6 @@ namespace osu.Game.Collections public readonly BindableList Collections = new BindableList(); - [Resolved] - private GameHost host { get; set; } - [Resolved] private BeatmapManager beatmaps { get; set; } diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index e01c7c9e49..644c2e2a99 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -65,9 +65,6 @@ namespace osu.Game.Online.Leaderboards [Resolved(CanBeNull = true)] private SongSelect songSelect { get; set; } - [Resolved] - private ScoreManager scoreManager { get; set; } - [Resolved] private Storage storage { get; set; } diff --git a/osu.Game/Online/Leaderboards/UserTopScoreContainer.cs b/osu.Game/Online/Leaderboards/UserTopScoreContainer.cs index ab4210251e..3db497bd6a 100644 --- a/osu.Game/Online/Leaderboards/UserTopScoreContainer.cs +++ b/osu.Game/Online/Leaderboards/UserTopScoreContainer.cs @@ -3,13 +3,11 @@ using System; using System.Threading; -using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; -using osu.Game.Rulesets; using osuTK; namespace osu.Game.Online.Leaderboards @@ -25,9 +23,6 @@ namespace osu.Game.Online.Leaderboards protected override bool StartHidden => true; - [Resolved] - private RulesetStore rulesets { get; set; } - public UserTopScoreContainer(Func createScoreDelegate) { this.createScoreDelegate = createScoreDelegate; diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs index 38f2bdb34f..f5b4785264 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs @@ -15,7 +15,6 @@ using osu.Framework.Threading; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests.Responses; -using osu.Game.Rulesets; using osu.Game.Resources.Localisation.Web; using osuTK; using osuTK.Graphics; @@ -61,9 +60,6 @@ namespace osu.Game.Overlays.BeatmapListing [Resolved] private IAPIProvider api { get; set; } - [Resolved] - private RulesetStore rulesets { get; set; } - public BeatmapListingFilterControl() { RelativeSizeAxes = Axes.X; diff --git a/osu.Game/Overlays/BeatmapSetOverlay.cs b/osu.Game/Overlays/BeatmapSetOverlay.cs index fa5a7c66d0..b9d3854066 100644 --- a/osu.Game/Overlays/BeatmapSetOverlay.cs +++ b/osu.Game/Overlays/BeatmapSetOverlay.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System.Linq; -using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -12,7 +11,6 @@ using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays.BeatmapSet; using osu.Game.Overlays.BeatmapSet.Scores; using osu.Game.Overlays.Comments; -using osu.Game.Rulesets; using osuTK; using osuTK.Graphics; @@ -24,9 +22,6 @@ namespace osu.Game.Overlays public const float Y_PADDING = 25; public const float RIGHT_WIDTH = 275; - [Resolved] - private RulesetStore rulesets { get; set; } - private readonly Bindable beatmapSet = new Bindable(); // receive input outside our bounds so we can trigger a close event on ourselves. diff --git a/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs b/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs index 269ed81bb5..0844975906 100644 --- a/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs +++ b/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs @@ -43,9 +43,6 @@ namespace osu.Game.Overlays.Dashboard }; } - [Resolved] - private IAPIProvider api { get; set; } - [Resolved] private UserLookupCache users { get; set; } diff --git a/osu.Game/Overlays/Rankings/SpotlightsLayout.cs b/osu.Game/Overlays/Rankings/SpotlightsLayout.cs index cc553ad361..dbfcfea414 100644 --- a/osu.Game/Overlays/Rankings/SpotlightsLayout.cs +++ b/osu.Game/Overlays/Rankings/SpotlightsLayout.cs @@ -29,9 +29,6 @@ namespace osu.Game.Overlays.Rankings [Resolved] private IAPIProvider api { get; set; } - [Resolved] - private RulesetStore rulesets { get; set; } - private CancellationTokenSource cancellationToken; private GetSpotlightRankingsRequest getRankingsRequest; private GetSpotlightsRequest spotlightsRequest; diff --git a/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownContainer.cs b/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownContainer.cs index 6f0b433acb..789ed457a4 100644 --- a/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownContainer.cs +++ b/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownContainer.cs @@ -5,19 +5,14 @@ using System.Linq; using Markdig.Extensions.Yaml; using Markdig.Syntax; using Markdig.Syntax.Inlines; -using osu.Framework.Allocation; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers.Markdown; using osu.Game.Graphics.Containers.Markdown; -using osu.Game.Online.API; namespace osu.Game.Overlays.Wiki.Markdown { public class WikiMarkdownContainer : OsuMarkdownContainer { - [Resolved] - private IAPIProvider api { get; set; } - public string CurrentPath { set => DocumentUrl = value; diff --git a/osu.Game/Rulesets/UI/Playfield.cs b/osu.Game/Rulesets/UI/Playfield.cs index 52aecb27de..d0bbf859af 100644 --- a/osu.Game/Rulesets/UI/Playfield.cs +++ b/osu.Game/Rulesets/UI/Playfield.cs @@ -19,7 +19,6 @@ using osu.Game.Rulesets.Objects; using osu.Game.Skinning; using osuTK; using System.Diagnostics; -using osu.Framework.Audio.Sample; namespace osu.Game.Rulesets.UI { @@ -88,9 +87,6 @@ namespace osu.Game.Rulesets.UI [Resolved(CanBeNull = true)] private IReadOnlyList mods { get; set; } - [Resolved] - private ISampleStore sampleStore { get; set; } - /// /// Creates a new . /// diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/GroupVisualisation.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/GroupVisualisation.cs index 4629f9b540..f0e643f805 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/GroupVisualisation.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/GroupVisualisation.cs @@ -1,20 +1,15 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Graphics; namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts { public class GroupVisualisation : CompositeDrawable { - [Resolved] - private OsuColour colours { get; set; } - public readonly ControlPointGroup Group; private readonly IBindableList controlPoints = new BindableList(); diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs index b8fa05e7eb..265f56534f 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs @@ -279,9 +279,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline editorClock.Start(); } - [Resolved] - private EditorBeatmap beatmap { get; set; } - [Resolved] private IBeatSnapProvider beatSnapProvider { get; set; } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineControlPointGroup.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineControlPointGroup.cs index 2b2e66fb18..9610f6424c 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineControlPointGroup.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineControlPointGroup.cs @@ -1,12 +1,10 @@ // 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.Beatmaps.ControlPoints; -using osu.Game.Graphics; namespace osu.Game.Screens.Edit.Compose.Components.Timeline { @@ -16,9 +14,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private readonly IBindableList controlPoints = new BindableList(); - [Resolved] - private OsuColour colours { get; set; } - public TimelineControlPointGroup(ControlPointGroup group) { Group = group; diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs index 80aa6972b1..1839b0507d 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs @@ -184,9 +184,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private SamplePointPiece sampleOverrideDisplay; private DifficultyPointPiece difficultyOverrideDisplay; - [Resolved] - private EditorBeatmap beatmap { get; set; } - private DifficultyControlPoint difficultyControlPoint; private SampleControlPoint sampleControlPoint; diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index ac71298f36..48489c60ab 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -109,9 +109,6 @@ namespace osu.Game.Screens.Edit [Resolved] private IAPIProvider api { get; set; } - [Resolved] - private MusicController music { get; set; } - [Cached] public readonly EditorClipboard Clipboard = new EditorClipboard(); diff --git a/osu.Game/Screens/Edit/EditorRoundedScreen.cs b/osu.Game/Screens/Edit/EditorRoundedScreen.cs index 7f7b3abc2a..62f40f0325 100644 --- a/osu.Game/Screens/Edit/EditorRoundedScreen.cs +++ b/osu.Game/Screens/Edit/EditorRoundedScreen.cs @@ -5,7 +5,6 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Game.Graphics; using osu.Game.Overlays; namespace osu.Game.Screens.Edit @@ -14,9 +13,6 @@ namespace osu.Game.Screens.Edit { public const int HORIZONTAL_PADDING = 100; - [Resolved] - private OsuColour colours { get; set; } - private Container roundedContent; protected override Container Content => roundedContent; diff --git a/osu.Game/Screens/Edit/EditorTable.cs b/osu.Game/Screens/Edit/EditorTable.cs index ab8bd6a3bc..a67a060134 100644 --- a/osu.Game/Screens/Edit/EditorTable.cs +++ b/osu.Game/Screens/Edit/EditorTable.cs @@ -62,9 +62,6 @@ namespace osu.Game.Screens.Edit private readonly Box hoveredBackground; - [Resolved] - private EditorClock clock { get; set; } - public RowBackground(object item) { Item = item; diff --git a/osu.Game/Screens/Edit/Setup/FileChooserLabelledTextBox.cs b/osu.Game/Screens/Edit/Setup/FileChooserLabelledTextBox.cs index f833bc49f7..d1e35ae20d 100644 --- a/osu.Game/Screens/Edit/Setup/FileChooserLabelledTextBox.cs +++ b/osu.Game/Screens/Edit/Setup/FileChooserLabelledTextBox.cs @@ -15,7 +15,6 @@ using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; using osu.Game.Database; -using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterfaceV2; using osuTK; @@ -36,9 +35,6 @@ namespace osu.Game.Screens.Edit.Setup [Resolved] private OsuGameBase game { get; set; } - [Resolved] - private SectionsContainer sectionsContainer { get; set; } - public FileChooserLabelledTextBox(params string[] handledExtensions) { this.handledExtensions = handledExtensions; diff --git a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs index 7a98cf63c3..1e6899e05f 100644 --- a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs +++ b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs @@ -132,9 +132,6 @@ namespace osu.Game.Screens.Edit.Timing controlPoints.BindTo(group.ControlPoints); } - [Resolved] - private OsuColour colours { get; set; } - [BackgroundDependencyLoader] private void load() { diff --git a/osu.Game/Screens/Edit/Verify/IssueList.cs b/osu.Game/Screens/Edit/Verify/IssueList.cs index fd238feeac..cadcdebc6e 100644 --- a/osu.Game/Screens/Edit/Verify/IssueList.cs +++ b/osu.Game/Screens/Edit/Verify/IssueList.cs @@ -23,9 +23,6 @@ namespace osu.Game.Screens.Edit.Verify { private IssueTable table; - [Resolved] - private EditorClock clock { get; set; } - [Resolved] private IBindable workingBeatmap { get; set; } diff --git a/osu.Game/Screens/Menu/StorageErrorDialog.cs b/osu.Game/Screens/Menu/StorageErrorDialog.cs index dcaad4013a..250623ec68 100644 --- a/osu.Game/Screens/Menu/StorageErrorDialog.cs +++ b/osu.Game/Screens/Menu/StorageErrorDialog.cs @@ -15,9 +15,6 @@ namespace osu.Game.Screens.Menu [Resolved] private DialogOverlay dialogOverlay { get; set; } - [Resolved] - private OsuGameBase osuGame { get; set; } - public StorageErrorDialog(OsuStorage storage, OsuStorageError error) { HeaderText = "osu! storage error"; diff --git a/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs b/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs index ddfdab18f7..47055c9c36 100644 --- a/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs +++ b/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs @@ -9,7 +9,6 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Logging; -using osu.Game.Beatmaps; using osu.Game.Online.API; using osu.Game.Online.Rooms; using osu.Game.Rulesets; @@ -30,9 +29,6 @@ namespace osu.Game.Screens.OnlinePlay.Components [Resolved] private RulesetStore rulesets { get; set; } - [Resolved] - private BeatmapManager beatmaps { get; set; } - [Resolved] private IAPIProvider api { get; set; } diff --git a/osu.Game/Screens/OnlinePlay/Components/SelectionPollingComponent.cs b/osu.Game/Screens/OnlinePlay/Components/SelectionPollingComponent.cs index b9d2bdf23e..22842fbb9e 100644 --- a/osu.Game/Screens/OnlinePlay/Components/SelectionPollingComponent.cs +++ b/osu.Game/Screens/OnlinePlay/Components/SelectionPollingComponent.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System.Threading.Tasks; -using osu.Framework.Allocation; using osu.Game.Online.Rooms; namespace osu.Game.Screens.OnlinePlay.Components @@ -12,9 +11,6 @@ namespace osu.Game.Screens.OnlinePlay.Components /// public class SelectionPollingComponent : RoomPollingComponent { - [Resolved] - private IRoomManager roomManager { get; set; } - private readonly Room room; public SelectionPollingComponent(Room room) diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs index 9920883078..0502c4abe6 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs @@ -30,9 +30,6 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components public readonly Room Room; - [Resolved] - private BeatmapManager beatmaps { get; set; } - protected Container ButtonsContainer { get; private set; } private readonly Bindable roomType = new Bindable(); diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs index 54c762b8ce..f4d7823fcc 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs @@ -33,9 +33,6 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components [Resolved] private IRoomManager roomManager { get; set; } - [Resolved(CanBeNull = true)] - private LoungeSubScreen loungeSubScreen { get; set; } - // handle deselection public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs index 34edc1ccd1..39d60a0b05 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs @@ -12,7 +12,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Screens; -using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; @@ -20,7 +19,6 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Online.Multiplayer; using osu.Game.Online.Rooms; using osu.Game.Overlays; -using osu.Game.Rulesets; using osu.Game.Screens.OnlinePlay.Match.Components; using osuTK; @@ -84,12 +82,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match [Resolved] private MultiplayerClient client { get; set; } - [Resolved] - private Bindable beatmap { get; set; } - - [Resolved] - private Bindable ruleset { get; set; } - [Resolved] private OngoingOperationTracker ongoingOperationTracker { get; set; } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs index ce988e377f..874113d859 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs @@ -11,7 +11,6 @@ using osu.Framework.Graphics; using osu.Framework.Threading; using osu.Game.Graphics; using osu.Game.Graphics.Backgrounds; -using osu.Game.Online.API; using osu.Game.Online.Multiplayer; using osu.Game.Screens.OnlinePlay.Components; using osuTK; @@ -25,9 +24,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match set => button.Action = value; } - [Resolved] - private IAPIProvider api { get; set; } - [Resolved] private OsuColour colours { get; set; } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs index 57d0d2c198..9ac64add9a 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs @@ -36,9 +36,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate [Resolved] private OsuColour colours { get; set; } - [Resolved] - private SpectatorClient spectatorClient { get; set; } - [Resolved] private MultiplayerClient multiplayerClient { get; set; } diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs b/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs index a18e4b45cf..19153521cd 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs @@ -40,18 +40,9 @@ namespace osu.Game.Screens.OnlinePlay [Cached] private readonly OngoingOperationTracker ongoingOperationTracker = new OngoingOperationTracker(); - [Resolved(CanBeNull = true)] - private MusicController music { get; set; } - - [Resolved] - private OsuGameBase game { get; set; } - [Resolved] protected IAPIProvider API { get; private set; } - [Resolved(CanBeNull = true)] - private OsuLogo logo { get; set; } - protected OnlinePlayScreen() { Anchor = Anchor.Centre; diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs index 6d2a426e70..7e045802f7 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs @@ -11,7 +11,6 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Logging; using osu.Framework.Screens; using osu.Game.Input; -using osu.Game.Online.API; using osu.Game.Online.Rooms; using osu.Game.Screens.OnlinePlay.Components; using osu.Game.Screens.OnlinePlay.Match; @@ -29,9 +28,6 @@ namespace osu.Game.Screens.OnlinePlay.Playlists public override string ShortTitle => "playlist"; - [Resolved] - private IAPIProvider api { get; set; } - private readonly IBindable isIdle = new BindableBool(); private MatchLeaderboard leaderboard; diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsSongSelect.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsSongSelect.cs index 03c95ec060..0fd76f7e25 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsSongSelect.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsSongSelect.cs @@ -2,9 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.Linq; -using osu.Framework.Allocation; using osu.Framework.Screens; -using osu.Game.Beatmaps; using osu.Game.Online.Rooms; using osu.Game.Screens.OnlinePlay.Components; using osu.Game.Screens.Select; @@ -13,9 +11,6 @@ namespace osu.Game.Screens.OnlinePlay.Playlists { public class PlaylistsSongSelect : OnlinePlaySongSelect { - [Resolved] - private BeatmapManager beatmaps { get; set; } - public PlaylistsSongSelect(Room room) : base(room) { diff --git a/osu.Game/Screens/Play/HUD/DefaultAccuracyCounter.cs b/osu.Game/Screens/Play/HUD/DefaultAccuracyCounter.cs index 324e5d43b5..06b53e8426 100644 --- a/osu.Game/Screens/Play/HUD/DefaultAccuracyCounter.cs +++ b/osu.Game/Screens/Play/HUD/DefaultAccuracyCounter.cs @@ -9,9 +9,6 @@ namespace osu.Game.Screens.Play.HUD { public class DefaultAccuracyCounter : GameplayAccuracyCounter, ISkinnableDrawable { - [Resolved(canBeNull: true)] - private HUDOverlay hud { get; set; } - public bool UsesFixedAnchor { get; set; } [BackgroundDependencyLoader] diff --git a/osu.Game/Screens/Play/HUD/DefaultComboCounter.cs b/osu.Game/Screens/Play/HUD/DefaultComboCounter.cs index 6d87211ddc..52f86d2bc3 100644 --- a/osu.Game/Screens/Play/HUD/DefaultComboCounter.cs +++ b/osu.Game/Screens/Play/HUD/DefaultComboCounter.cs @@ -15,9 +15,6 @@ namespace osu.Game.Screens.Play.HUD { public class DefaultComboCounter : RollingCounter, ISkinnableDrawable { - [Resolved(canBeNull: true)] - private HUDOverlay hud { get; set; } - public bool UsesFixedAnchor { get; set; } public DefaultComboCounter() diff --git a/osu.Game/Screens/Play/HUD/DefaultScoreCounter.cs b/osu.Game/Screens/Play/HUD/DefaultScoreCounter.cs index 87b19e8433..6af89404e0 100644 --- a/osu.Game/Screens/Play/HUD/DefaultScoreCounter.cs +++ b/osu.Game/Screens/Play/HUD/DefaultScoreCounter.cs @@ -16,9 +16,6 @@ namespace osu.Game.Screens.Play.HUD Origin = Anchor.TopCentre; } - [Resolved(canBeNull: true)] - private HUDOverlay hud { get; set; } - public bool UsesFixedAnchor { get; set; } [BackgroundDependencyLoader] diff --git a/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs b/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs index 5c5b66d496..4859f1b977 100644 --- a/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs +++ b/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs @@ -40,9 +40,6 @@ namespace osu.Game.Screens.Play.HUD private bool isRolling; - [Resolved] - private ISkinSource skin { get; set; } - private readonly Container counterContainer; /// diff --git a/osu.Game/Screens/Play/SoloSpectator.cs b/osu.Game/Screens/Play/SoloSpectator.cs index 45601999a0..b48c787752 100644 --- a/osu.Game/Screens/Play/SoloSpectator.cs +++ b/osu.Game/Screens/Play/SoloSpectator.cs @@ -23,7 +23,6 @@ using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Spectator; using osu.Game.Overlays; using osu.Game.Overlays.Settings; -using osu.Game.Rulesets; using osu.Game.Screens.OnlinePlay.Match.Components; using osu.Game.Screens.Spectate; using osu.Game.Users; @@ -44,9 +43,6 @@ namespace osu.Game.Screens.Play [Resolved] private PreviewTrackManager previewTrackManager { get; set; } - [Resolved] - private RulesetStore rulesets { get; set; } - [Resolved] private BeatmapManager beatmaps { get; set; } diff --git a/osu.Game/Screens/Ranking/ScorePanelList.cs b/osu.Game/Screens/Ranking/ScorePanelList.cs index d5b8a4c8ea..22be91b974 100644 --- a/osu.Game/Screens/Ranking/ScorePanelList.cs +++ b/osu.Game/Screens/Ranking/ScorePanelList.cs @@ -13,7 +13,6 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Events; -using osu.Game.Beatmaps; using osu.Game.Graphics.Containers; using osu.Game.Scoring; using osuTK; @@ -70,9 +69,6 @@ namespace osu.Game.Screens.Ranking [Resolved] private ScoreManager scoreManager { get; set; } - [Resolved] - private BeatmapDifficultyCache difficultyCache { get; set; } - private readonly CancellationTokenSource loadCancellationSource = new CancellationTokenSource(); private readonly Flow flow; private readonly Scroll scroll; diff --git a/osu.Game/Screens/Select/BeatmapDetails.cs b/osu.Game/Screens/Select/BeatmapDetails.cs index 7543c89f17..bbe0a37d8e 100644 --- a/osu.Game/Screens/Select/BeatmapDetails.cs +++ b/osu.Game/Screens/Select/BeatmapDetails.cs @@ -16,7 +16,6 @@ using osu.Game.Online; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Overlays.BeatmapSet; -using osu.Game.Rulesets; using osu.Game.Screens.Select.Details; using osuTK; using osuTK.Graphics; @@ -38,9 +37,6 @@ namespace osu.Game.Screens.Select [Resolved] private IAPIProvider api { get; set; } - [Resolved] - private RulesetStore rulesets { get; set; } - private IBeatmapInfo beatmapInfo; private APIFailTimes failTimes; diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index 1fd6d8c921..872e630ba0 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -64,9 +64,6 @@ namespace osu.Game.Screens.Select.Leaderboards [Resolved] private ScoreManager scoreManager { get; set; } - [Resolved] - private BeatmapDifficultyCache difficultyCache { get; set; } - [Resolved] private IBindable ruleset { get; set; } diff --git a/osu.Game/Skinning/Editor/SkinEditorOverlay.cs b/osu.Game/Skinning/Editor/SkinEditorOverlay.cs index d27122aea8..340c6ed931 100644 --- a/osu.Game/Skinning/Editor/SkinEditorOverlay.cs +++ b/osu.Game/Skinning/Editor/SkinEditorOverlay.cs @@ -3,13 +3,11 @@ using System.Diagnostics; using JetBrains.Annotations; -using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; -using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Input.Bindings; @@ -28,9 +26,6 @@ namespace osu.Game.Skinning.Editor public const float VISIBLE_TARGET_SCALE = 0.8f; - [Resolved] - private OsuColour colours { get; set; } - public SkinEditorOverlay(ScalingContainer target) { this.target = target; diff --git a/osu.Game/Skinning/LegacyAccuracyCounter.cs b/osu.Game/Skinning/LegacyAccuracyCounter.cs index fd5a9500d9..bdcb85456a 100644 --- a/osu.Game/Skinning/LegacyAccuracyCounter.cs +++ b/osu.Game/Skinning/LegacyAccuracyCounter.cs @@ -1,10 +1,8 @@ // 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.Graphics; using osu.Game.Graphics.Sprites; -using osu.Game.Screens.Play; using osu.Game.Screens.Play.HUD; using osuTK; @@ -23,9 +21,6 @@ namespace osu.Game.Skinning Margin = new MarginPadding(10); } - [Resolved(canBeNull: true)] - private HUDOverlay hud { get; set; } - protected sealed override OsuSpriteText CreateSpriteText() => new LegacySpriteText(LegacyFont.Score) { Anchor = Anchor.TopRight, diff --git a/osu.Game/Skinning/PoolableSkinnableSample.cs b/osu.Game/Skinning/PoolableSkinnableSample.cs index 3fcca74fb8..5db4f00b46 100644 --- a/osu.Game/Skinning/PoolableSkinnableSample.cs +++ b/osu.Game/Skinning/PoolableSkinnableSample.cs @@ -4,7 +4,6 @@ using System; using System.Linq; using JetBrains.Annotations; -using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; using osu.Framework.Bindables; @@ -30,9 +29,6 @@ namespace osu.Game.Skinning private ISampleInfo sampleInfo; private SampleChannel activeChannel; - [Resolved] - private ISampleStore sampleStore { get; set; } - /// /// Creates a new with no applied . /// An can be applied later via . diff --git a/osu.Game/Skinning/SkinnableSound.cs b/osu.Game/Skinning/SkinnableSound.cs index f935adf7a5..c9e55c09aa 100644 --- a/osu.Game/Skinning/SkinnableSound.cs +++ b/osu.Game/Skinning/SkinnableSound.cs @@ -7,7 +7,6 @@ using System.Linq; using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Audio; -using osu.Framework.Audio.Sample; using osu.Framework.Bindables; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; @@ -43,9 +42,6 @@ namespace osu.Game.Skinning private readonly AudioContainer samplesContainer; - [Resolved] - private ISampleStore sampleStore { get; set; } - [Resolved(CanBeNull = true)] private IPooledSampleProvider samplePool { get; set; } diff --git a/osu.Game/Updater/NoActionUpdateManager.cs b/osu.Game/Updater/NoActionUpdateManager.cs index 641263ed0f..8f9c4c6f16 100644 --- a/osu.Game/Updater/NoActionUpdateManager.cs +++ b/osu.Game/Updater/NoActionUpdateManager.cs @@ -5,7 +5,6 @@ using System.Linq; using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Graphics.Sprites; -using osu.Framework.Platform; using osu.Game.Online.API; using osu.Game.Overlays.Notifications; @@ -19,9 +18,6 @@ namespace osu.Game.Updater { private string version; - [Resolved] - private GameHost host { get; set; } - [BackgroundDependencyLoader] private void load(OsuGameBase game) { From d5803e541bdfcc1b0f6ed703bdb0773b766b5624 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 3 Dec 2021 20:05:25 +0900 Subject: [PATCH 70/87] Give playlist items a PlayedAt date --- .../TestSceneMultiplayerPlaylist.cs | 4 ++- .../Online/Multiplayer/MultiplayerClient.cs | 3 +- .../Online/Rooms/MultiplayerPlaylistItem.cs | 8 ++--- osu.Game/Online/Rooms/PlaylistItem.cs | 3 ++ .../Match/Playlist/MultiplayerHistoryList.cs | 2 +- .../Multiplayer/TestMultiplayerClient.cs | 36 +++++++------------ 6 files changed, 26 insertions(+), 30 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs index ae885685f7..674ee0f186 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.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.Linq; using NUnit.Framework; using osu.Framework.Allocation; @@ -168,7 +169,8 @@ namespace osu.Game.Tests.Visual.Multiplayer { Beatmap = { Value = importedBeatmap }, BeatmapID = importedBeatmap.OnlineID ?? -1, - Expired = expired + Expired = expired, + PlayedAt = DateTimeOffset.Now }))); /// diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index ae53ef2e52..555558eb6d 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -742,7 +742,8 @@ namespace osu.Game.Online.Multiplayer Beatmap = { Value = apiBeatmap }, Ruleset = { Value = ruleset }, Expired = item.Expired, - PlaylistOrder = item.PlaylistOrder + PlaylistOrder = item.PlaylistOrder, + PlayedAt = item.PlayedAt }; playlistItem.RequiredMods.AddRange(item.RequiredMods.Select(m => m.ToMod(rulesetInstance))); diff --git a/osu.Game/Online/Rooms/MultiplayerPlaylistItem.cs b/osu.Game/Online/Rooms/MultiplayerPlaylistItem.cs index c84b5b9039..1c33b79531 100644 --- a/osu.Game/Online/Rooms/MultiplayerPlaylistItem.cs +++ b/osu.Game/Online/Rooms/MultiplayerPlaylistItem.cs @@ -46,11 +46,10 @@ namespace osu.Game.Online.Rooms public ushort PlaylistOrder { get; set; } /// - /// The date when this was last updated. - /// Not serialised to/from the client. + /// The date when this was played. /// - [IgnoreMember] - public DateTimeOffset UpdatedAt { get; set; } + [Key(9)] + public DateTimeOffset? PlayedAt { get; set; } public MultiplayerPlaylistItem() { @@ -66,6 +65,7 @@ namespace osu.Game.Online.Rooms AllowedMods = item.AllowedMods.Select(m => new APIMod(m)).ToArray(); Expired = item.Expired; PlaylistOrder = item.PlaylistOrder ?? 0; + PlayedAt = item.PlayedAt; } } } diff --git a/osu.Game/Online/Rooms/PlaylistItem.cs b/osu.Game/Online/Rooms/PlaylistItem.cs index beefb17d54..4d67864048 100644 --- a/osu.Game/Online/Rooms/PlaylistItem.cs +++ b/osu.Game/Online/Rooms/PlaylistItem.cs @@ -36,6 +36,9 @@ namespace osu.Game.Online.Rooms [JsonProperty("playlist_order")] public ushort? PlaylistOrder { get; set; } + [JsonProperty("played_at")] + public DateTimeOffset? PlayedAt { get; set; } + [JsonIgnore] public IBindable Valid => valid; diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerHistoryList.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerHistoryList.cs index ed41a6fc78..d708b39898 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerHistoryList.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerHistoryList.cs @@ -27,7 +27,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist private class HistoryFillFlowContainer : FillFlowContainer> { - public override IEnumerable FlowingChildren => base.FlowingChildren.OfType>().OrderByDescending(item => item.Model.PlaylistOrder); + public override IEnumerable FlowingChildren => base.FlowingChildren.OfType>().OrderByDescending(item => item.Model.PlayedAt); } } } diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index f2d5323386..024b091bfb 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -400,7 +400,7 @@ namespace osu.Game.Tests.Visual.Multiplayer // Expire the current playlist item. currentItem.Expired = true; - currentItem.UpdatedAt = DateTimeOffset.Now; + currentItem.PlayedAt = DateTimeOffset.Now; await ((IMultiplayerClient)this).PlaylistItemChanged(currentItem).ConfigureAwait(false); await updatePlaylistOrder(Room).ConfigureAwait(false); @@ -430,9 +430,6 @@ namespace osu.Game.Tests.Visual.Multiplayer { Debug.Assert(Room != null); - // Some tests can add items in already-expired states. - item.UpdatedAt = DateTimeOffset.Now; - // Add the item to the list first in order to compute gameplay order. serverSidePlaylist.Add(item); await updatePlaylistOrder(Room).ConfigureAwait(false); @@ -443,8 +440,12 @@ namespace osu.Game.Tests.Visual.Multiplayer private async Task updateCurrentItem(MultiplayerRoom room, bool notify = true) { - // The playlist is already in correct gameplay order, so pick the next non-expired item or default to the last item. - MultiplayerPlaylistItem nextItem = serverSidePlaylist.FirstOrDefault(i => !i.Expired) ?? room.Playlist.Last(); + MultiplayerPlaylistItem nextItem = serverSidePlaylist + .Where(i => !i.Expired) + .OrderBy(i => i.PlaylistOrder) + .FirstOrDefault() + ?? room.Playlist.Last(); + currentIndex = serverSidePlaylist.IndexOf(nextItem); long lastItem = room.Settings.PlaylistItemId; @@ -494,29 +495,18 @@ namespace osu.Game.Tests.Visual.Multiplayer break; } - // For expired items, it's important that they're ordered in ascending order such that the last updated item is the last in the list. - // This is so that the updated_at database column doesn't get refreshed as a result of change in ordering. - List orderedExpiredItems = serverSidePlaylist.Where(item => item.Expired).OrderBy(item => item.UpdatedAt).ToList(); - for (int i = 0; i < orderedExpiredItems.Count; i++) - await setOrder(orderedExpiredItems[i], (ushort)i).ConfigureAwait(false); - for (int i = 0; i < orderedActiveItems.Count; i++) - await setOrder(orderedActiveItems[i], (ushort)i).ConfigureAwait(false); - - serverSidePlaylist.Clear(); - serverSidePlaylist.AddRange(orderedExpiredItems); - serverSidePlaylist.AddRange(orderedActiveItems); - - async Task setOrder(MultiplayerPlaylistItem item, ushort order) { - if (item.PlaylistOrder == order) - return; + var item = orderedActiveItems[i]; - item.PlaylistOrder = order; + if (item.PlaylistOrder == i) + continue; + + item.PlaylistOrder = (ushort)i; // Items which have an ID of 0 are not in the database, so avoid propagating database/hub events for them. if (item.ID <= 0) - return; + continue; await ((IMultiplayerClient)this).PlaylistItemChanged(item).ConfigureAwait(false); } From 9acc0556a4fbe260a5cea97f9b823cdd2aa893df Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 3 Dec 2021 20:35:47 +0900 Subject: [PATCH 71/87] Remove unused event --- osu.Desktop/LegacyIpc/LegacyTcpIpcProvider.cs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/osu.Desktop/LegacyIpc/LegacyTcpIpcProvider.cs b/osu.Desktop/LegacyIpc/LegacyTcpIpcProvider.cs index 2c9eb76dfc..e42a10430a 100644 --- a/osu.Desktop/LegacyIpc/LegacyTcpIpcProvider.cs +++ b/osu.Desktop/LegacyIpc/LegacyTcpIpcProvider.cs @@ -26,15 +26,10 @@ namespace osu.Desktop.LegacyIpc { private static readonly Logger logger = Logger.GetLogger("legacy-ipc"); - /// - /// Invoked when a message is received from a legacy client. - /// - public new Func? MessageReceived; - public LegacyTcpIpcProvider() : base(45357) { - base.MessageReceived += msg => + MessageReceived += msg => { try { From 34b0e374d855cacde830c91b7251f74d499d1a1d Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 3 Dec 2021 21:29:20 +0900 Subject: [PATCH 72/87] Add serialisation/deserialisation explanation --- osu.Desktop/LegacyIpc/LegacyIpcMessage.cs | 13 +++++++++++++ osu.Desktop/LegacyIpc/LegacyTcpIpcProvider.cs | 1 + 2 files changed, 14 insertions(+) diff --git a/osu.Desktop/LegacyIpc/LegacyIpcMessage.cs b/osu.Desktop/LegacyIpc/LegacyIpcMessage.cs index 6fefae4509..0fa60e2068 100644 --- a/osu.Desktop/LegacyIpc/LegacyIpcMessage.cs +++ b/osu.Desktop/LegacyIpc/LegacyIpcMessage.cs @@ -2,11 +2,24 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Platform; +using Newtonsoft.Json.Linq; namespace osu.Desktop.LegacyIpc { /// /// An that can be used to communicate to and from legacy clients. + /// + /// In order to deserialise types at either end, types must be serialised as their , + /// however this cannot be done since osu!stable and osu!lazer live in two different assemblies. + ///
+ /// To get around this, this class exists which serialises a payload () as an type, + /// which can be deserialised at either end because it is part of the core library (mscorlib / System.Private.CorLib). + /// The payload contains the data to be sent over the IPC channel. + ///
+ /// At either end, Json.NET deserialises the payload into a which is manually converted back into the expected type, + /// which then further contains another representing the data sent over the IPC channel whose type can likewise be lazily matched through + /// . + ///
///
/// /// Synchronise any changes with osu-stable. diff --git a/osu.Desktop/LegacyIpc/LegacyTcpIpcProvider.cs b/osu.Desktop/LegacyIpc/LegacyTcpIpcProvider.cs index e42a10430a..97a4c57bf0 100644 --- a/osu.Desktop/LegacyIpc/LegacyTcpIpcProvider.cs +++ b/osu.Desktop/LegacyIpc/LegacyTcpIpcProvider.cs @@ -36,6 +36,7 @@ namespace osu.Desktop.LegacyIpc logger.Add("Processing legacy IPC message..."); logger.Add($" {msg.Value}", LogLevel.Debug); + // See explanation in LegacyIpcMessage for why this is done this way. var legacyData = ((JObject)msg.Value).ToObject(); object value = parseObject((JObject)legacyData!.MessageData, legacyData.MessageType); From 85d3b70d8ce096011172daf8a4dede7100aa75a7 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Sat, 4 Dec 2021 22:34:37 +0900 Subject: [PATCH 73/87] Update test multiplayer client to match server-side --- .../Multiplayer/TestMultiplayerClient.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index 024b091bfb..f5f5eebe0f 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -431,6 +431,7 @@ namespace osu.Game.Tests.Visual.Multiplayer Debug.Assert(Room != null); // Add the item to the list first in order to compute gameplay order. + item.ID = long.MaxValue; serverSidePlaylist.Add(item); await updatePlaylistOrder(Room).ConfigureAwait(false); @@ -440,11 +441,9 @@ namespace osu.Game.Tests.Visual.Multiplayer private async Task updateCurrentItem(MultiplayerRoom room, bool notify = true) { - MultiplayerPlaylistItem nextItem = serverSidePlaylist - .Where(i => !i.Expired) - .OrderBy(i => i.PlaylistOrder) - .FirstOrDefault() - ?? room.Playlist.Last(); + // Pick the next non-expired playlist item by playlist order, or default to the most-recently-expired item. + MultiplayerPlaylistItem nextItem = serverSidePlaylist.Where(i => !i.Expired).OrderBy(i => i.PlaylistOrder).FirstOrDefault() + ?? serverSidePlaylist.OrderByDescending(i => i.PlayedAt).First(); currentIndex = serverSidePlaylist.IndexOf(nextItem); @@ -462,7 +461,7 @@ namespace osu.Game.Tests.Visual.Multiplayer switch (room.Settings.QueueMode) { default: - orderedActiveItems = serverSidePlaylist.Where(item => !item.Expired).OrderBy(item => item.ID == 0 ? int.MaxValue : item.ID).ToList(); + orderedActiveItems = serverSidePlaylist.Where(item => !item.Expired).OrderBy(item => item.ID).ToList(); break; case QueueMode.AllPlayersRoundRobin: @@ -483,7 +482,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { MultiplayerPlaylistItem candidateItem = unprocessedItems .OrderBy(item => perUserCounts[item.OwnerID]) - .ThenBy(item => item.ID == 0 ? int.MaxValue : item.ID) + .ThenBy(item => item.ID) .First(); unprocessedItems.Remove(candidateItem); @@ -504,8 +503,9 @@ namespace osu.Game.Tests.Visual.Multiplayer item.PlaylistOrder = (ushort)i; - // Items which have an ID of 0 are not in the database, so avoid propagating database/hub events for them. - if (item.ID <= 0) + // Items which have an "infinite" ID are not yet in the database, so avoid propagating database/hub events for them. + // See addItem() for when this occurs. + if (item.ID == long.MaxValue) continue; await ((IMultiplayerClient)this).PlaylistItemChanged(item).ConfigureAwait(false); From 16fd7f5a287cf921445f51ae7a587b6ccebe1327 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 4 Dec 2021 14:42:01 +0100 Subject: [PATCH 74/87] Simplify slightly redundant assertions --- osu.Game.Tests/Database/RulesetStoreTests.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Database/RulesetStoreTests.cs b/osu.Game.Tests/Database/RulesetStoreTests.cs index b82dab1874..cc7e8a0c97 100644 --- a/osu.Game.Tests/Database/RulesetStoreTests.cs +++ b/osu.Game.Tests/Database/RulesetStoreTests.cs @@ -45,9 +45,9 @@ namespace osu.Game.Tests.Database { var rulesets = new RealmRulesetStore(realmFactory, storage); - Assert.IsTrue(rulesets.AvailableRulesets.First().IsManaged == false); - Assert.IsTrue(rulesets.GetRuleset(0)?.IsManaged == false); - Assert.IsTrue(rulesets.GetRuleset("mania")?.IsManaged == false); + Assert.IsFalse(rulesets.AvailableRulesets.First().IsManaged); + Assert.IsFalse(rulesets.GetRuleset(0)?.IsManaged); + Assert.IsFalse(rulesets.GetRuleset("mania")?.IsManaged); }); } } From 53a6ef22ceb9122faff7a0b538dbf654246b0da1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 4 Dec 2021 14:55:35 +0100 Subject: [PATCH 75/87] Add null check to resolve inspection --- .../OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs index a68309dae5..8fbaebadfe 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs @@ -187,7 +187,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants // Todo: Should use the room's selected item to determine ruleset. var ruleset = rulesets.GetRuleset(0)?.CreateInstance(); - int? currentModeRank = User.User?.RulesetsStatistics?.GetValueOrDefault(ruleset.ShortName)?.GlobalRank; + int? currentModeRank = ruleset != null ? User.User?.RulesetsStatistics?.GetValueOrDefault(ruleset.ShortName)?.GlobalRank : null; userRankText.Text = currentModeRank != null ? $"#{currentModeRank.Value:N0}" : string.Empty; userStateDisplay.UpdateStatus(User.State, User.BeatmapAvailability); From 054543f58fd8a0ec7641047635d1b8c9d6ff821c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 4 Dec 2021 15:33:02 +0100 Subject: [PATCH 76/87] Revert tournament beatmap panel test change with comment --- .../Components/TestSceneTournamentBeatmapPanel.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tournament.Tests/Components/TestSceneTournamentBeatmapPanel.cs b/osu.Game.Tournament.Tests/Components/TestSceneTournamentBeatmapPanel.cs index 7132655535..b678f69b8f 100644 --- a/osu.Game.Tournament.Tests/Components/TestSceneTournamentBeatmapPanel.cs +++ b/osu.Game.Tournament.Tests/Components/TestSceneTournamentBeatmapPanel.cs @@ -3,20 +3,29 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests.Responses; +using osu.Game.Tests.Visual; using osu.Game.Tournament.Components; namespace osu.Game.Tournament.Tests.Components { public class TestSceneTournamentBeatmapPanel : TournamentTestScene { + /// + /// Warning: the below API instance is actually the online API, rather than the dummy API provided by the test. + /// It cannot be trivially replaced because setting to causes to no longer be usable. + /// + [Resolved] + private IAPIProvider api { get; set; } + [BackgroundDependencyLoader] private void load() { var req = new GetBeatmapRequest(new APIBeatmap { OnlineID = 1091460 }); req.Success += success; - API.Queue(req); + api.Queue(req); } private void success(APIBeatmap beatmap) From ea6766d940f6e4bf2a1510d375bb09f6d6215023 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 4 Dec 2021 16:47:53 +0100 Subject: [PATCH 77/87] Add failing test case --- .../Formats/LegacyScoreDecoderTest.cs | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs index 6e5a546e87..a73ae9dcdb 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs @@ -2,14 +2,20 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using System.Globalization; +using System.IO; using System.Linq; using NUnit.Framework; using osu.Framework.Utils; using osu.Game.Beatmaps; +using osu.Game.Replays; using osu.Game.Rulesets; using osu.Game.Rulesets.Catch; using osu.Game.Rulesets.Mania; using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Osu.Replays; +using osu.Game.Rulesets.Osu.UI; +using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Taiko; using osu.Game.Scoring; @@ -21,6 +27,14 @@ namespace osu.Game.Tests.Beatmaps.Formats [TestFixture] public class LegacyScoreDecoderTest { + private CultureInfo originalCulture; + + [SetUp] + public void SetUp() + { + originalCulture = CultureInfo.CurrentCulture; + } + [Test] public void TestDecodeManiaReplay() { @@ -44,6 +58,59 @@ namespace osu.Game.Tests.Beatmaps.Formats } } + [Test] + public void TestCultureInvariance() + { + var ruleset = new OsuRuleset().RulesetInfo; + var scoreInfo = new TestScoreInfo(ruleset); + var beatmap = new TestBeatmap(ruleset); + var score = new Score + { + ScoreInfo = scoreInfo, + Replay = new Replay + { + Frames = new List + { + new OsuReplayFrame(2000, OsuPlayfield.BASE_SIZE / 2, OsuAction.LeftButton) + } + } + }; + + // the "se" culture is used here, as it encodes the negative number sign as U+2212 MINUS SIGN, + // rather than the classic ASCII U+002D HYPHEN-MINUS. + CultureInfo.CurrentCulture = new CultureInfo("se"); + + var encodeStream = new MemoryStream(); + + var encoder = new LegacyScoreEncoder(score, beatmap); + encoder.Encode(encodeStream); + + var decodeStream = new MemoryStream(encodeStream.GetBuffer()); + + var decoder = new TestLegacyScoreDecoder(); + var decodedAfterEncode = decoder.Parse(decodeStream); + + Assert.Multiple(() => + { + Assert.That(decodedAfterEncode, Is.Not.Null); + + Assert.That(decodedAfterEncode.ScoreInfo.User.Username, Is.EqualTo(scoreInfo.User.Username)); + Assert.That(decodedAfterEncode.ScoreInfo.BeatmapInfoID, Is.EqualTo(scoreInfo.BeatmapInfoID)); + Assert.That(decodedAfterEncode.ScoreInfo.Ruleset, Is.EqualTo(scoreInfo.Ruleset)); + Assert.That(decodedAfterEncode.ScoreInfo.TotalScore, Is.EqualTo(scoreInfo.TotalScore)); + Assert.That(decodedAfterEncode.ScoreInfo.MaxCombo, Is.EqualTo(scoreInfo.MaxCombo)); + Assert.That(decodedAfterEncode.ScoreInfo.Date, Is.EqualTo(scoreInfo.Date)); + + Assert.That(decodedAfterEncode.Replay.Frames.Count, Is.EqualTo(1)); + }); + } + + [TearDown] + public void TearDown() + { + CultureInfo.CurrentCulture = originalCulture; + } + private class TestLegacyScoreDecoder : LegacyScoreDecoder { private static readonly Dictionary rulesets = new Ruleset[] From f051720fa14256b2f7ad46d9f7aacf40ae157a1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 4 Dec 2021 17:02:39 +0100 Subject: [PATCH 78/87] Fix score encoder being dependent on current culture As it turns out, on some cultures, the "negative integer" sign is not encoded using the U+002D HYPHEN-MINUS codepoint. For instance, Swedish uses U+2212 MINUS SIGN instead. This was confusing the legacy decoder, since it is correctly depending on the serialisation being culture-independent. To fix, ensure that the special "end replay" frame, as well as the replay MD5 hash, are generated in a culture-invariant manner. Thankfully the replay MD5 hash is currently being discarded in `LegacyScoreDecoder`, so it changing in future scores should not have any negative effect on lazer operation. --- osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs index 7b8cacb35b..3d67aa9558 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs @@ -46,7 +46,7 @@ namespace osu.Game.Scoring.Legacy sw.Write(LATEST_VERSION); sw.Write(score.ScoreInfo.BeatmapInfo.MD5Hash); sw.Write(score.ScoreInfo.UserString); - sw.Write($"lazer-{score.ScoreInfo.UserString}-{score.ScoreInfo.Date}".ComputeMD5Hash()); + sw.Write(FormattableString.Invariant($"lazer-{score.ScoreInfo.UserString}-{score.ScoreInfo.Date}").ComputeMD5Hash()); sw.Write((ushort)(score.ScoreInfo.GetCount300() ?? 0)); sw.Write((ushort)(score.ScoreInfo.GetCount100() ?? 0)); sw.Write((ushort)(score.ScoreInfo.GetCount50() ?? 0)); @@ -110,7 +110,9 @@ namespace osu.Game.Scoring.Legacy } } - replayData.AppendFormat(@"{0}|{1}|{2}|{3},", -12345, 0, 0, 0); + // Warning: this is purposefully hardcoded as a string rather than interpolating, as in some cultures the minus sign is not encoded as the standard ASCII U+00C2 codepoint, + // which then would break decoding. + replayData.Append(@"-12345|0|0|0"); return replayData.ToString(); } } From e1897f9998065b499e758503ecff3d11cbaae880 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 5 Dec 2021 01:38:37 +0900 Subject: [PATCH 79/87] Don't debounce `MultiplayerRoomComposite` events This avoids accidental usage which could result in data being lost or ignored (as only the last `user` in a single frame would arrive). This was added specifically to debounce sample playback, but given that it's only debouncing on a single frame (hardly noticeable) I'm not going to add back support for that yet. It should be handled by sample playback concurrency or something more local to the usage. --- .../OnlinePlay/Multiplayer/MultiplayerRoomComposite.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerRoomComposite.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerRoomComposite.cs index 2f75f09a9f..7d2fe44c4e 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerRoomComposite.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerRoomComposite.cs @@ -32,9 +32,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer } private void invokeOnRoomUpdated() => Scheduler.AddOnce(OnRoomUpdated); - private void invokeUserJoined(MultiplayerRoomUser user) => Scheduler.AddOnce(UserJoined, user); - private void invokeUserKicked(MultiplayerRoomUser user) => Scheduler.AddOnce(UserKicked, user); - private void invokeUserLeft(MultiplayerRoomUser user) => Scheduler.AddOnce(UserLeft, user); + private void invokeUserJoined(MultiplayerRoomUser user) => Scheduler.Add(() => UserJoined(user)); + private void invokeUserKicked(MultiplayerRoomUser user) => Scheduler.Add(() => UserKicked(user)); + private void invokeUserLeft(MultiplayerRoomUser user) => Scheduler.Add(() => UserLeft(user)); private void invokeItemAdded(MultiplayerPlaylistItem item) => Schedule(() => PlaylistItemAdded(item)); private void invokeItemRemoved(long item) => Schedule(() => PlaylistItemRemoved(item)); private void invokeItemChanged(MultiplayerPlaylistItem item) => Schedule(() => PlaylistItemChanged(item)); From 81215b9f0e7b70c2cfbd31bf0345d64e92fa4fc4 Mon Sep 17 00:00:00 2001 From: ColdVolcano <16726733+ColdVolcano@users.noreply.github.com> Date: Sat, 4 Dec 2021 22:31:55 -0600 Subject: [PATCH 80/87] Use correct effect points when EarlyActivationMilliseconds is not zero --- osu.Game/Graphics/Containers/BeatSyncedContainer.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs index 6e4901ab1a..2024d18570 100644 --- a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs +++ b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs @@ -111,7 +111,7 @@ namespace osu.Game.Graphics.Containers if (clock == null) return; - double currentTrackTime = clock.CurrentTime; + double currentTrackTime = clock.CurrentTime + EarlyActivationMilliseconds; if (Beatmap.Value.TrackLoaded && Beatmap.Value.BeatmapLoaded) { @@ -132,13 +132,11 @@ namespace osu.Game.Graphics.Containers { // this may be the case where the beat syncing clock has been paused. // we still want to show an idle animation, so use this container's time instead. - currentTrackTime = Clock.CurrentTime; + currentTrackTime = Clock.CurrentTime + EarlyActivationMilliseconds; timingPoint = TimingControlPoint.DEFAULT; effectPoint = EffectControlPoint.DEFAULT; } - currentTrackTime += EarlyActivationMilliseconds; - double beatLength = timingPoint.BeatLength / Divisor; while (beatLength < MinimumBeatLength) From 86c908c657db4771840435887c69964cdbfeb9d3 Mon Sep 17 00:00:00 2001 From: ColdVolcano <16726733+ColdVolcano@users.noreply.github.com> Date: Sun, 5 Dec 2021 03:53:36 -0600 Subject: [PATCH 81/87] Add test coverage --- .../TestSceneBeatSyncedContainer.cs | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs index e5bcc08924..ede89c6096 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs @@ -135,6 +135,35 @@ namespace osu.Game.Tests.Visual.UserInterface AddAssert("bpm is default", () => lastBpm != null && Precision.AlmostEquals(lastBpm.Value, 60)); } + [TestCase(true)] + [TestCase(false)] + public void TestEarlyActivationEffectPoint(bool earlyActivating) + { + double earlyActivationMilliseconds = earlyActivating ? 100 : 0; + ControlPoint actualEffectPoint = null; + + AddStep($"set early activation to {earlyActivationMilliseconds}", () => beatContainer.EarlyActivationMilliseconds = earlyActivationMilliseconds); + + AddStep("seek before kiai effect point", () => + { + ControlPoint expectedEffectPoint = Beatmap.Value.Beatmap.ControlPointInfo.EffectPoints.First(ep => ep.KiaiMode); + actualEffectPoint = null; + beatContainer.AllowMistimedEventFiring = false; + + beatContainer.NewBeat = (i, timingControlPoint, effectControlPoint, channelAmplitudes) => + { + if (Precision.AlmostEquals(gameplayClockContainer.CurrentTime + earlyActivationMilliseconds, expectedEffectPoint.Time, BeatSyncedContainer.MISTIMED_ALLOWANCE)) + actualEffectPoint = effectControlPoint; + }; + + gameplayClockContainer.Seek(expectedEffectPoint.Time - earlyActivationMilliseconds); + }); + + AddUntilStep("wait for effect point", () => actualEffectPoint != null); + + AddAssert("effect has kiai", () => actualEffectPoint != null && ((EffectControlPoint)actualEffectPoint).KiaiMode); + } + private class TestBeatSyncedContainer : BeatSyncedContainer { private const int flash_layer_height = 150; @@ -145,6 +174,12 @@ namespace osu.Game.Tests.Visual.UserInterface set => base.AllowMistimedEventFiring = value; } + public new double EarlyActivationMilliseconds + { + get => base.EarlyActivationMilliseconds; + set => base.EarlyActivationMilliseconds = value; + } + private readonly InfoString timingPointCount; private readonly InfoString currentTimingPoint; private readonly InfoString beatCount; From ca1f96d2c2ef22d4917a3a60364e949ac546082f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 6 Dec 2021 13:03:11 +0900 Subject: [PATCH 82/87] Reword xmldoc of `MultiplayerPlaylistItem.PlaylistOrder` to better match actual behaviour --- osu.Game/Online/Rooms/MultiplayerPlaylistItem.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/Rooms/MultiplayerPlaylistItem.cs b/osu.Game/Online/Rooms/MultiplayerPlaylistItem.cs index 1c33b79531..cee6d8fe41 100644 --- a/osu.Game/Online/Rooms/MultiplayerPlaylistItem.cs +++ b/osu.Game/Online/Rooms/MultiplayerPlaylistItem.cs @@ -40,8 +40,12 @@ namespace osu.Game.Online.Rooms public bool Expired { get; set; } /// - /// The order in which this will be played, starting from 0 and increasing for items which will be played later. + /// The order in which this will be played relative to others. + /// Playlist items should be played in increasing order (lower values are played first). /// + /// + /// This is only valid for items which are not . The value for expired items is undefined and should not be used. + /// [Key(8)] public ushort PlaylistOrder { get; set; } From a76cfbea2163ed0869d507ccb9865d5ee55c9cbd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 6 Dec 2021 14:03:24 +0900 Subject: [PATCH 83/87] Add test coverage of incorrect beatmap being used in multiplayer when match started from song select --- .../Multiplayer/TestSceneMultiplayer.cs | 38 +++++++++++++++++++ .../Multiplayer/MultiplayerMatchSubScreen.cs | 14 ++++--- 2 files changed, 47 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs index 4521a7fa0f..843b7a0c51 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs @@ -397,6 +397,44 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("user state is idle", () => client.LocalUser?.State == MultiplayerUserState.Idle); } + [Test] + public void TestPlayStartsWithCorrectBeatmapWhileAtSongSelect() + { + createRoom(() => new Room + { + Name = { Value = "Test Room" }, + Playlist = + { + new PlaylistItem + { + Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo }, + Ruleset = { Value = new OsuRuleset().RulesetInfo }, + } + } + }); + + AddStep("Enter song select", () => + { + var currentSubScreen = ((Screens.OnlinePlay.Multiplayer.Multiplayer)multiplayerScreenStack.CurrentScreen).CurrentSubScreen; + + ((MultiplayerMatchSubScreen)currentSubScreen).SelectBeatmap(); + }); + + AddUntilStep("wait for song select", () => this.ChildrenOfType().FirstOrDefault()?.IsLoaded == true); + + AddAssert("Beatmap matches current item", () => Beatmap.Value.BeatmapInfo.OnlineID == client.Room?.Playlist.First().BeatmapID); + + AddStep("Select next beatmap", () => InputManager.Key(Key.Down)); + + AddUntilStep("Beatmap doesn't match current item", () => Beatmap.Value.BeatmapInfo.OnlineID != client.Room?.Playlist.First().BeatmapID); + + AddStep("start match externally", () => client.StartMatch()); + + AddUntilStep("play started", () => multiplayerScreenStack.CurrentScreen is Player); + + AddAssert("Beatmap matches current item", () => Beatmap.Value.BeatmapInfo.OnlineID == client.Room?.Playlist.First().BeatmapID); + } + [Test] public void TestLocalPlayDoesNotStartWhileSpectatingWithNoBeatmap() { diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index 16017a1f0e..3a25bd7b06 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -138,11 +138,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer { RelativeSizeAxes = Axes.X, Height = 40, - Action = () => - { - if (this.IsCurrentScreen()) - this.Push(new MultiplayerMatchSongSelect(Room)); - }, + Action = SelectBeatmap, Alpha = 0 }, }, @@ -224,6 +220,14 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer } }; + internal void SelectBeatmap() + { + if (!this.IsCurrentScreen()) + return; + + this.Push(new MultiplayerMatchSongSelect(Room)); + } + protected override Drawable CreateFooter() => new MultiplayerMatchFooter { OnReadyClick = onReadyClick, From 0ea7a6908422342c130c35141e0e35aa20e564a8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 6 Dec 2021 14:03:51 +0900 Subject: [PATCH 84/87] Ensure user is returned to the `RoomSubScreen` before gameplay is started This covers the scenario where a user may be at the song select screen while another user (the room host) starts the match. This was only made possible with the new queue modes, so is quite a recent regression. --- osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs index 7c5ed3f5cc..184ac2c563 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs @@ -319,6 +319,16 @@ namespace osu.Game.Screens.OnlinePlay.Match protected void StartPlay() { + // User may be at song select or otherwise when the host starts gameplay. + // Ensure that they first return to this screen, else global bindables (beatmap etc.) may be in a bad state. + if (!this.IsCurrentScreen()) + { + this.MakeCurrent(); + + Schedule(StartPlay); + return; + } + sampleStart?.Play(); // fallback is to allow this class to operate when there is no parent OnlineScreen (testing purposes). From 0fa1a96e9dc508f16c27a0ad7b128c0f8899ccc6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 6 Dec 2021 15:03:17 +0900 Subject: [PATCH 85/87] Wait for beatmap sets to finish loading to avoid test failures Co-authored-by: Dan Balasescu --- osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs index 843b7a0c51..2411f39ae3 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs @@ -420,7 +420,7 @@ namespace osu.Game.Tests.Visual.Multiplayer ((MultiplayerMatchSubScreen)currentSubScreen).SelectBeatmap(); }); - AddUntilStep("wait for song select", () => this.ChildrenOfType().FirstOrDefault()?.IsLoaded == true); + AddUntilStep("wait for song select", () => this.ChildrenOfType().FirstOrDefault()?.BeatmapSetsLoaded == true); AddAssert("Beatmap matches current item", () => Beatmap.Value.BeatmapInfo.OnlineID == client.Room?.Playlist.First().BeatmapID); From 5be74af8fe58a1a4c859b4af73007e96d4fdd061 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 6 Dec 2021 15:09:06 +0900 Subject: [PATCH 86/87] Update addItem() implementation --- .../Tests/Visual/Multiplayer/TestMultiplayerClient.cs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index f5f5eebe0f..740693ef76 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -430,13 +430,12 @@ namespace osu.Game.Tests.Visual.Multiplayer { Debug.Assert(Room != null); - // Add the item to the list first in order to compute gameplay order. - item.ID = long.MaxValue; - serverSidePlaylist.Add(item); - await updatePlaylistOrder(Room).ConfigureAwait(false); - item.ID = ++lastPlaylistItemId; + + serverSidePlaylist.Add(item); await ((IMultiplayerClient)this).PlaylistItemAdded(item).ConfigureAwait(false); + + await updatePlaylistOrder(Room).ConfigureAwait(false); } private async Task updateCurrentItem(MultiplayerRoom room, bool notify = true) From fae41b21821fa8740004612972385b48d2719be5 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 6 Dec 2021 15:17:28 +0900 Subject: [PATCH 87/87] Remove one more piece of code --- osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index 740693ef76..2510ddb432 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -502,11 +502,6 @@ namespace osu.Game.Tests.Visual.Multiplayer item.PlaylistOrder = (ushort)i; - // Items which have an "infinite" ID are not yet in the database, so avoid propagating database/hub events for them. - // See addItem() for when this occurs. - if (item.ID == long.MaxValue) - continue; - await ((IMultiplayerClient)this).PlaylistItemChanged(item).ConfigureAwait(false); } }