1
0
mirror of https://github.com/ppy/osu.git synced 2025-01-12 12:22:56 +08:00

Split online connectivity into OnlineSpectatorClient

This commit is contained in:
smoogipoo 2021-05-20 16:30:56 +09:00
parent 6beeb7f7c4
commit df80531a0a
3 changed files with 142 additions and 89 deletions

View File

@ -0,0 +1,89 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable enable
using System.Threading.Tasks;
using Microsoft.AspNetCore.SignalR.Client;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Game.Online.API;
namespace osu.Game.Online.Spectator
{
public class OnlineSpectatorClient : SpectatorClient
{
private readonly string endpoint;
private IHubClientConnector? connector;
public override IBindable<bool> IsConnected { get; } = new BindableBool();
private HubConnection? connection => connector?.CurrentConnection;
public OnlineSpectatorClient(EndpointConfiguration endpoints)
{
endpoint = endpoints.SpectatorEndpointUrl;
}
[BackgroundDependencyLoader]
private void load(IAPIProvider api)
{
connector = api.GetHubConnector(nameof(SpectatorClient), endpoint);
if (connector != null)
{
connector.ConfigureConnection = connection =>
{
// until strong typed client support is added, each method must be manually bound
// (see https://github.com/dotnet/aspnetcore/issues/15198)
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);
};
IsConnected.BindTo(connector.IsConnected);
}
}
protected override Task BeginPlayingInternal(SpectatorState state)
{
if (!IsConnected.Value)
return Task.CompletedTask;
return connection.SendAsync(nameof(ISpectatorServer.BeginPlaySession), state);
}
protected override Task SendFramesInternal(FrameDataBundle data)
{
if (!IsConnected.Value)
return Task.CompletedTask;
return connection.SendAsync(nameof(ISpectatorServer.SendFrameData), data);
}
protected override Task EndPlayingInternal(SpectatorState state)
{
if (!IsConnected.Value)
return Task.CompletedTask;
return connection.SendAsync(nameof(ISpectatorServer.EndPlaySession), state);
}
protected override Task WatchUserInternal(int userId)
{
if (!IsConnected.Value)
return Task.CompletedTask;
return connection.SendAsync(nameof(ISpectatorServer.StartWatchingUser), userId);
}
protected override Task StopWatchingUserInternal(int userId)
{
if (!IsConnected.Value)
return Task.CompletedTask;
return connection.SendAsync(nameof(ISpectatorServer.EndWatchingUser), userId);
}
}
}

View File

@ -7,7 +7,6 @@ using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Microsoft.AspNetCore.SignalR.Client;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
@ -23,21 +22,18 @@ using osu.Game.Screens.Play;
namespace osu.Game.Online.Spectator
{
public class SpectatorClient : Component, ISpectatorClient
public abstract class SpectatorClient : Component, ISpectatorClient
{
/// <summary>
/// The maximum milliseconds between frame bundle sends.
/// </summary>
public const double TIME_BETWEEN_SENDS = 200;
private readonly string endpoint;
[CanBeNull]
private IHubClientConnector connector;
private readonly IBindable<bool> isConnected = new BindableBool();
private HubConnection connection => connector?.CurrentConnection;
/// <summary>
/// Whether the <see cref="SpectatorClient"/> is currently connected.
/// This is NOT thread safe and usage should be scheduled.
/// </summary>
public abstract IBindable<bool> IsConnected { get; }
private readonly List<int> watchingUsers = new List<int>();
@ -63,7 +59,7 @@ namespace osu.Game.Online.Spectator
private readonly SpectatorState currentState = new SpectatorState();
private bool isPlaying;
protected bool IsPlaying { get; private set; }
/// <summary>
/// Called whenever new frames arrive from the server.
@ -80,59 +76,39 @@ namespace osu.Game.Online.Spectator
/// </summary>
public event Action<int, SpectatorState> OnUserFinishedPlaying;
public SpectatorClient(EndpointConfiguration endpoints)
{
endpoint = endpoints.SpectatorEndpointUrl;
}
[BackgroundDependencyLoader]
private void load(IAPIProvider api)
private void load()
{
connector = api.GetHubConnector(nameof(SpectatorClient), endpoint);
if (connector != null)
IsConnected.BindValueChanged(connected =>
{
connector.ConfigureConnection = connection =>
if (connected.NewValue)
{
// until strong typed client support is added, each method must be manually bound
// (see https://github.com/dotnet/aspnetcore/issues/15198)
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);
};
// get all the users that were previously being watched
int[] users;
isConnected.BindTo(connector.IsConnected);
isConnected.BindValueChanged(connected =>
lock (userLock)
{
users = watchingUsers.ToArray();
watchingUsers.Clear();
}
// resubscribe to watched users.
foreach (var userId in users)
WatchUser(userId);
// re-send state in case it wasn't received
if (IsPlaying)
BeginPlayingInternal(currentState);
}
else
{
if (connected.NewValue)
lock (userLock)
{
// get all the users that were previously being watched
int[] users;
lock (userLock)
{
users = watchingUsers.ToArray();
watchingUsers.Clear();
}
// resubscribe to watched users.
foreach (var userId in users)
WatchUser(userId);
// re-send state in case it wasn't received
if (isPlaying)
beginPlaying();
playingUsers.Clear();
playingUserStates.Clear();
}
else
{
lock (userLock)
{
playingUsers.Clear();
playingUserStates.Clear();
}
}
}, true);
}
}
}, true);
}
Task ISpectatorClient.UserBeganPlaying(int userId, SpectatorState state)
@ -176,10 +152,10 @@ namespace osu.Game.Online.Spectator
public void BeginPlaying(GameplayBeatmap beatmap, Score score)
{
if (isPlaying)
if (IsPlaying)
throw new InvalidOperationException($"Cannot invoke {nameof(BeginPlaying)} when already playing");
isPlaying = true;
IsPlaying = true;
// transfer state at point of beginning play
currentState.BeatmapID = beatmap.BeatmapInfo.OnlineBeatmapID;
@ -189,36 +165,20 @@ namespace osu.Game.Online.Spectator
currentBeatmap = beatmap.PlayableBeatmap;
currentScore = score;
beginPlaying();
BeginPlayingInternal(currentState);
}
private void beginPlaying()
{
Debug.Assert(isPlaying);
if (!isConnected.Value) return;
connection.SendAsync(nameof(ISpectatorServer.BeginPlaySession), currentState);
}
public void SendFrames(FrameDataBundle data)
{
if (!isConnected.Value) return;
lastSend = connection.SendAsync(nameof(ISpectatorServer.SendFrameData), data);
}
public void SendFrames(FrameDataBundle data) => lastSend = SendFramesInternal(data);
public void EndPlaying()
{
isPlaying = false;
IsPlaying = false;
currentBeatmap = null;
if (!isConnected.Value) return;
connection.SendAsync(nameof(ISpectatorServer.EndPlaySession), currentState);
EndPlayingInternal(currentState);
}
public virtual void WatchUser(int userId)
public void WatchUser(int userId)
{
lock (userLock)
{
@ -226,27 +186,31 @@ namespace osu.Game.Online.Spectator
return;
watchingUsers.Add(userId);
if (!isConnected.Value)
return;
}
connection.SendAsync(nameof(ISpectatorServer.StartWatchingUser), userId);
WatchUserInternal(userId);
}
public virtual void StopWatchingUser(int userId)
public void StopWatchingUser(int userId)
{
lock (userLock)
{
watchingUsers.Remove(userId);
if (!isConnected.Value)
return;
}
connection.SendAsync(nameof(ISpectatorServer.EndWatchingUser), userId);
StopWatchingUserInternal(userId);
}
protected abstract Task BeginPlayingInternal(SpectatorState state);
protected abstract Task SendFramesInternal(FrameDataBundle data);
protected abstract Task EndPlayingInternal(SpectatorState state);
protected abstract Task WatchUserInternal(int userId);
protected abstract Task StopWatchingUserInternal(int userId);
private readonly Queue<LegacyReplayFrame> pendingFrames = new Queue<LegacyReplayFrame>();
private double lastSendTime;

View File

@ -240,7 +240,7 @@ namespace osu.Game
dependencies.CacheAs(API ??= new APIAccess(LocalConfig, endpoints, VersionHash));
dependencies.CacheAs(spectatorClient = new SpectatorClient(endpoints));
dependencies.CacheAs(spectatorClient = new OnlineSpectatorClient(endpoints));
dependencies.CacheAs(multiplayerClient = new MultiplayerClient(endpoints));
var defaultBeatmap = new DummyWorkingBeatmap(Audio, Textures);