mirror of
https://github.com/ppy/osu.git
synced 2025-01-12 16:02:55 +08:00
Split online connectivity into OnlineSpectatorClient
This commit is contained in:
parent
6beeb7f7c4
commit
df80531a0a
89
osu.Game/Online/Spectator/OnlineSpectatorClient.cs
Normal file
89
osu.Game/Online/Spectator/OnlineSpectatorClient.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user