From 34e889e66e3bfeabd22ea5eb6550b5e338c15bce Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Oct 2020 18:37:19 +0900 Subject: [PATCH] Don't watch every user in normal gameplay (but allow so in test) --- .../Visual/Gameplay/TestSceneSpectator.cs | 20 ++++- osu.Game/Online/Spectator/ISpectatorClient.cs | 6 +- osu.Game/Online/Spectator/ISpectatorServer.cs | 4 +- osu.Game/Online/Spectator/SpectatorState.cs | 1 - .../Spectator/SpectatorStreamingClient.cs | 85 ++++++++++++------- 5 files changed, 78 insertions(+), 38 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs index 665df5f9c7..2ec82ad5fb 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs @@ -2,8 +2,10 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using System.Collections.Specialized; using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -34,7 +36,7 @@ namespace osu.Game.Tests.Visual.Gameplay private Replay replay; - private TestReplayRecorder recorder; + private IBindableList users; [Resolved] private SpectatorStreamingClient streamingClient { get; set; } @@ -44,7 +46,19 @@ namespace osu.Game.Tests.Visual.Gameplay { replay = new Replay(); - streamingClient.OnNewFrames += frames => + users = streamingClient.PlayingUsers.GetBoundCopy(); + users.BindCollectionChanged((obj, args) => + { + switch (args.Action) + { + case NotifyCollectionChangedAction.Add: + foreach (int user in args.NewItems) + streamingClient.WatchUser(user); + break; + } + }, true); + + streamingClient.OnNewFrames += (userId, frames) => { foreach (var legacyFrame in frames.Frames) { @@ -63,7 +77,7 @@ namespace osu.Game.Tests.Visual.Gameplay { recordingManager = new TestRulesetInputManager(new TestSceneModSettings.TestRulesetInfo(), 0, SimultaneousBindingMode.Unique) { - Recorder = recorder = new TestReplayRecorder + Recorder = new TestReplayRecorder { ScreenSpaceToGamefield = pos => recordingManager.ToLocalSpace(pos), }, diff --git a/osu.Game/Online/Spectator/ISpectatorClient.cs b/osu.Game/Online/Spectator/ISpectatorClient.cs index dcff6e6c1c..18c9d61561 100644 --- a/osu.Game/Online/Spectator/ISpectatorClient.cs +++ b/osu.Game/Online/Spectator/ISpectatorClient.cs @@ -12,20 +12,20 @@ namespace osu.Game.Online.Spectator /// /// The user. /// The state of gameplay. - Task UserBeganPlaying(string userId, SpectatorState state); + Task UserBeganPlaying(int userId, SpectatorState state); /// /// Signals that a user has finished a play session. /// /// The user. /// The state of gameplay. - Task UserFinishedPlaying(string userId, SpectatorState state); + Task UserFinishedPlaying(int userId, SpectatorState state); /// /// Called when new frames are available for a subscribed user's play session. /// /// The user. /// The frame data. - Task UserSentFrames(string userId, FrameDataBundle data); + Task UserSentFrames(int userId, FrameDataBundle data); } } diff --git a/osu.Game/Online/Spectator/ISpectatorServer.cs b/osu.Game/Online/Spectator/ISpectatorServer.cs index 018fa6b66b..99893e385c 100644 --- a/osu.Game/Online/Spectator/ISpectatorServer.cs +++ b/osu.Game/Online/Spectator/ISpectatorServer.cs @@ -30,12 +30,12 @@ namespace osu.Game.Online.Spectator /// For offline users, a subscription will be created and data will begin streaming on next play. /// /// The user to subscribe to. - Task StartWatchingUser(string userId); + Task StartWatchingUser(int userId); /// /// Stop requesting spectating data for the specified user. Unsubscribes from receiving further data. /// /// The user to unsubscribe from. - Task EndWatchingUser(string userId); + Task EndWatchingUser(int userId); } } diff --git a/osu.Game/Online/Spectator/SpectatorState.cs b/osu.Game/Online/Spectator/SpectatorState.cs index 3d9997f006..6b2b8b8cb2 100644 --- a/osu.Game/Online/Spectator/SpectatorState.cs +++ b/osu.Game/Online/Spectator/SpectatorState.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; using osu.Game.Online.API; -using osu.Game.Rulesets.Mods; namespace osu.Game.Online.Spectator { diff --git a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs index 608123fbab..2665243e4c 100644 --- a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs +++ b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.SignalR.Client; @@ -20,7 +21,11 @@ namespace osu.Game.Online.Spectator { private HubConnection connection; - private readonly List watchingUsers = new List(); + private readonly List watchingUsers = new List(); + + public IBindableList PlayingUsers => playingUsers; + + private readonly BindableList playingUsers = new BindableList(); private readonly IBindable apiState = new Bindable(); @@ -37,7 +42,12 @@ namespace osu.Game.Online.Spectator private readonly SpectatorState currentState = new SpectatorState(); - public event Action OnNewFrames; + private bool isPlaying; + + /// + /// Called whenever new frames arrive from the server. + /// + public event Action OnNewFrames; [BackgroundDependencyLoader] private void load() @@ -82,13 +92,15 @@ namespace osu.Game.Online.Spectator .Build(); // until strong typed client support is added, each method must be manually bound (see https://github.com/dotnet/aspnetcore/issues/15198) - connection.On(nameof(ISpectatorClient.UserBeganPlaying), ((ISpectatorClient)this).UserBeganPlaying); - connection.On(nameof(ISpectatorClient.UserSentFrames), ((ISpectatorClient)this).UserSentFrames); - connection.On(nameof(ISpectatorClient.UserFinishedPlaying), ((ISpectatorClient)this).UserFinishedPlaying); + connection.On(nameof(ISpectatorClient.UserBeganPlaying), ((ISpectatorClient)this).UserBeganPlaying); + connection.On(nameof(ISpectatorClient.UserSentFrames), ((ISpectatorClient)this).UserSentFrames); + connection.On(nameof(ISpectatorClient.UserFinishedPlaying), ((ISpectatorClient)this).UserFinishedPlaying); connection.Closed += async ex => { isConnected = false; + playingUsers.Clear(); + if (ex != null) await tryUntilConnected(); }; @@ -105,6 +117,17 @@ namespace osu.Game.Online.Spectator // success isConnected = true; + + // resubscribe to watched users + var users = watchingUsers.ToArray(); + watchingUsers.Clear(); + foreach (var userId in users) + WatchUser(userId); + + // re-send state in case it wasn't received + if (isPlaying) + beginPlaying(); + break; } catch @@ -115,39 +138,23 @@ namespace osu.Game.Online.Spectator } } - Task ISpectatorClient.UserBeganPlaying(string userId, SpectatorState state) + Task ISpectatorClient.UserBeganPlaying(int userId, SpectatorState state) { - if (connection.ConnectionId != userId) - { - if (watchingUsers.Contains(userId)) - { - Console.WriteLine($"{connection.ConnectionId} received began playing for already watched user {userId}"); - } - else - { - Console.WriteLine($"{connection.ConnectionId} requesting watch other user {userId}"); - WatchUser(userId); - watchingUsers.Add(userId); - } - } - else - { - Console.WriteLine($"{connection.ConnectionId} Received user playing event for self {state}"); - } + if (!playingUsers.Contains(userId)) + playingUsers.Add(userId); return Task.CompletedTask; } - Task ISpectatorClient.UserFinishedPlaying(string userId, SpectatorState state) + Task ISpectatorClient.UserFinishedPlaying(int userId, SpectatorState state) { - Console.WriteLine($"{connection.ConnectionId} Received user finished event {state}"); + playingUsers.Remove(userId); return Task.CompletedTask; } - Task ISpectatorClient.UserSentFrames(string userId, FrameDataBundle data) + Task ISpectatorClient.UserSentFrames(int userId, FrameDataBundle data) { - Console.WriteLine($"{connection.ConnectionId} Received frames from {userId}: {data.Frames.First()}"); - OnNewFrames?.Invoke(data); + OnNewFrames?.Invoke(userId, data); return Task.CompletedTask; } @@ -155,10 +162,22 @@ namespace osu.Game.Online.Spectator { if (!isConnected) return; + if (isPlaying) + throw new InvalidOperationException($"Cannot invoke {nameof(BeginPlaying)} when already playing"); + + isPlaying = true; + // transfer state at point of beginning play currentState.BeatmapID = beatmap.Value.BeatmapInfo.OnlineBeatmapID; currentState.Mods = mods.Value.Select(m => new APIMod(m)); + beginPlaying(); + } + + private void beginPlaying() + { + Debug.Assert(isPlaying); + connection.SendAsync(nameof(ISpectatorServer.BeginPlaySession), currentState); } @@ -173,13 +192,21 @@ namespace osu.Game.Online.Spectator { if (!isConnected) return; + if (!isPlaying) + throw new InvalidOperationException($"Cannot invoke {nameof(EndPlaying)} when not playing"); + + isPlaying = false; connection.SendAsync(nameof(ISpectatorServer.EndPlaySession), currentState); } - public void WatchUser(string userId) + public void WatchUser(int userId) { if (!isConnected) return; + if (watchingUsers.Contains(userId)) + return; + + watchingUsers.Add(userId); connection.SendAsync(nameof(ISpectatorServer.StartWatchingUser), userId); }