mirror of
https://github.com/ppy/osu.git
synced 2026-05-29 03:59:54 +08:00
ca44d8233d
RFC Until now, if the initial `BeginPlaySession()` call failed, the client would continue operating as if it didn't - it would still continue to send frames and call `EndPlaySession()` at the end of a session. Server-side, two things generally can happen after this: - The sent frames and the `EndPlaySession()` call are [completely](https://github.com/ppy/osu-server-spectator/blob/7bab117e9d161455485368f63a0607a9e53f9f8a/osu.Server.Spectator/Hubs/Spectator/SpectatorHub.cs#L122-L125) [ignored](https://github.com/ppy/osu-server-spectator/blob/7bab117e9d161455485368f63a0607a9e53f9f8a/osu.Server.Spectator/Hubs/Spectator/SpectatorHub.cs#L153-L157) as no-ops, or - A hub filter (like `ClientVersionChecker`) that failed the initial `BeginPlaySession()` call continues to fail the calls to `SendFrameData()` and `EndPlaySession()`, all the while creating a storm in logs, because it needs to throw `HubException`s to communicate to users that they need to update their game, and the exceptions can't be silenced from logs because they look like every other failure. To that end, this has two goals: reduce useless network traffic, and reduce noise in spectator server logs after the client version checks were recently reactivated. Probably needs tests, but unsure if everyone's going to be on board with this to begin with to be quite frank, so I'm leaving tests for when I'm told this needs tests.
136 lines
5.2 KiB
C#
136 lines
5.2 KiB
C#
// 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.
|
|
|
|
using System;
|
|
using System.Diagnostics;
|
|
using System.Threading.Tasks;
|
|
using Microsoft.AspNetCore.SignalR.Client;
|
|
using osu.Framework.Allocation;
|
|
using osu.Framework.Bindables;
|
|
using osu.Framework.Logging;
|
|
using osu.Game.Online.API;
|
|
using osu.Game.Online.Multiplayer;
|
|
|
|
namespace osu.Game.Online.Spectator
|
|
{
|
|
public partial 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.SpectatorUrl;
|
|
}
|
|
|
|
[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);
|
|
connection.On<int, long>(nameof(ISpectatorClient.UserScoreProcessed), ((ISpectatorClient)this).UserScoreProcessed);
|
|
connection.On<SpectatorUser[]>(nameof(ISpectatorClient.UserStartedWatching), ((ISpectatorClient)this).UserStartedWatching);
|
|
connection.On<int>(nameof(ISpectatorClient.UserEndedWatching), ((ISpectatorClient)this).UserEndedWatching);
|
|
connection.On(nameof(IStatefulUserHubClient.DisconnectRequested), ((IStatefulUserHubClient)this).DisconnectRequested);
|
|
};
|
|
|
|
IsConnected.BindTo(connector.IsConnected);
|
|
}
|
|
}
|
|
|
|
protected override async Task<bool> BeginPlayingInternal(long? scoreToken, SpectatorState state)
|
|
{
|
|
if (!IsConnected.Value)
|
|
return false;
|
|
|
|
Debug.Assert(connection != null);
|
|
|
|
try
|
|
{
|
|
await connection.InvokeAsync(nameof(ISpectatorServer.BeginPlaySession), scoreToken, state).ConfigureAwait(false);
|
|
return true;
|
|
}
|
|
catch (Exception exception)
|
|
{
|
|
if (exception.GetHubExceptionMessage() == HubClientConnector.SERVER_SHUTDOWN_MESSAGE)
|
|
{
|
|
Debug.Assert(connector != null);
|
|
|
|
await connector.Reconnect().ConfigureAwait(false);
|
|
return await BeginPlayingInternal(scoreToken, state).ConfigureAwait(false);
|
|
}
|
|
|
|
// Exceptions can occur if, for instance, the locally played beatmap doesn't have a server-side counterpart.
|
|
// For now, let's ignore these so they don't cause unobserved exceptions to appear to the user (and sentry),
|
|
// but log to disk for diagnostic purposes.
|
|
Logger.Log($"{nameof(OnlineSpectatorClient)}.{nameof(BeginPlayingInternal)} failed: {exception.Message}", LoggingTarget.Network);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
protected override Task SendFramesInternal(FrameDataBundle bundle)
|
|
{
|
|
if (!IsConnected.Value)
|
|
return Task.CompletedTask;
|
|
|
|
Debug.Assert(connection != null);
|
|
|
|
return connection.SendAsync(nameof(ISpectatorServer.SendFrameData), bundle);
|
|
}
|
|
|
|
protected override Task EndPlayingInternal(SpectatorState state)
|
|
{
|
|
if (!IsConnected.Value)
|
|
return Task.CompletedTask;
|
|
|
|
Debug.Assert(connection != null);
|
|
|
|
return connection.InvokeAsync(nameof(ISpectatorServer.EndPlaySession), state);
|
|
}
|
|
|
|
protected override Task WatchUserInternal(int userId)
|
|
{
|
|
if (!IsConnected.Value)
|
|
return Task.CompletedTask;
|
|
|
|
Debug.Assert(connection != null);
|
|
|
|
return connection.InvokeAsync(nameof(ISpectatorServer.StartWatchingUser), userId);
|
|
}
|
|
|
|
protected override Task StopWatchingUserInternal(int userId)
|
|
{
|
|
if (!IsConnected.Value)
|
|
return Task.CompletedTask;
|
|
|
|
Debug.Assert(connection != null);
|
|
|
|
return connection.InvokeAsync(nameof(ISpectatorServer.EndWatchingUser), userId);
|
|
}
|
|
|
|
protected override async Task DisconnectInternal()
|
|
{
|
|
await base.DisconnectInternal().ConfigureAwait(false);
|
|
|
|
if (connector == null)
|
|
return;
|
|
|
|
await connector.Disconnect().ConfigureAwait(false);
|
|
}
|
|
}
|
|
}
|