From 0cb35e8b1852d0c6f76a14db8382c84b25597ec9 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 26 Nov 2021 19:24:49 +0900 Subject: [PATCH] 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; + } + } + } + } + } +}