1
0
mirror of https://github.com/ppy/osu.git synced 2024-11-06 06:17:23 +08:00

Don't watch every user in normal gameplay (but allow so in test)

This commit is contained in:
Dean Herbert 2020-10-22 18:37:19 +09:00
parent 1ab6f41b3b
commit 34e889e66e
5 changed files with 78 additions and 38 deletions

View File

@ -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<int> 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),
},

View File

@ -12,20 +12,20 @@ namespace osu.Game.Online.Spectator
/// </summary>
/// <param name="userId">The user.</param>
/// <param name="state">The state of gameplay.</param>
Task UserBeganPlaying(string userId, SpectatorState state);
Task UserBeganPlaying(int userId, SpectatorState state);
/// <summary>
/// Signals that a user has finished a play session.
/// </summary>
/// <param name="userId">The user.</param>
/// <param name="state">The state of gameplay.</param>
Task UserFinishedPlaying(string userId, SpectatorState state);
Task UserFinishedPlaying(int userId, SpectatorState state);
/// <summary>
/// Called when new frames are available for a subscribed user's play session.
/// </summary>
/// <param name="userId">The user.</param>
/// <param name="data">The frame data.</param>
Task UserSentFrames(string userId, FrameDataBundle data);
Task UserSentFrames(int userId, FrameDataBundle data);
}
}

View File

@ -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.
/// </summary>
/// <param name="userId">The user to subscribe to.</param>
Task StartWatchingUser(string userId);
Task StartWatchingUser(int userId);
/// <summary>
/// Stop requesting spectating data for the specified user. Unsubscribes from receiving further data.
/// </summary>
/// <param name="userId">The user to unsubscribe from.</param>
Task EndWatchingUser(string userId);
Task EndWatchingUser(int userId);
}
}

View File

@ -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
{

View File

@ -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<string> watchingUsers = new List<string>();
private readonly List<int> watchingUsers = new List<int>();
public IBindableList<int> PlayingUsers => playingUsers;
private readonly BindableList<int> playingUsers = new BindableList<int>();
private readonly IBindable<APIState> apiState = new Bindable<APIState>();
@ -37,7 +42,12 @@ namespace osu.Game.Online.Spectator
private readonly SpectatorState currentState = new SpectatorState();
public event Action<FrameDataBundle> OnNewFrames;
private bool isPlaying;
/// <summary>
/// Called whenever new frames arrive from the server.
/// </summary>
public event Action<int, FrameDataBundle> 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<string, SpectatorState>(nameof(ISpectatorClient.UserBeganPlaying), ((ISpectatorClient)this).UserBeganPlaying);
connection.On<string, FrameDataBundle>(nameof(ISpectatorClient.UserSentFrames), ((ISpectatorClient)this).UserSentFrames);
connection.On<string, SpectatorState>(nameof(ISpectatorClient.UserFinishedPlaying), ((ISpectatorClient)this).UserFinishedPlaying);
connection.On<int, SpectatorState>(nameof(ISpectatorClient.UserBeganPlaying), ((ISpectatorClient)this).UserBeganPlaying);
connection.On<int, FrameDataBundle>(nameof(ISpectatorClient.UserSentFrames), ((ISpectatorClient)this).UserSentFrames);
connection.On<int, SpectatorState>(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);
}