1
0
mirror of https://github.com/ppy/osu.git synced 2024-11-11 09:27:29 +08:00

Centralise and improve messaging around online state

When the server requests a disconnect due to a user connecting
via a second device, the client will now log the user out on the first
device and show a notification informing them of the cause of
disconnection.
This commit is contained in:
Bartłomiej Dach 2023-11-21 14:39:33 +09:00
parent 2391035e49
commit 42fada578e
No known key found for this signature in database
8 changed files with 155 additions and 17 deletions

View File

@ -100,7 +100,11 @@ namespace osu.Game.Online
return Task.FromResult((PersistentEndpointClient)new HubClient(newConnection));
}
Task IHubClientConnector.Disconnect() => base.Disconnect();
async Task IHubClientConnector.Disconnect()
{
await Disconnect().ConfigureAwait(false);
API.Logout();
}
protected override string ClientName { get; }
}

View File

@ -12,7 +12,6 @@ using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Development;
using osu.Framework.Graphics;
using osu.Framework.Logging;
using osu.Game.Database;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests.Responses;
@ -88,6 +87,11 @@ namespace osu.Game.Online.Multiplayer
/// </summary>
public event Action? ResultsReady;
/// <summary>
/// Invoked just prior to disconnection requested by the server via <see cref="IStatefulUserHubClient.DisconnectRequested"/>.
/// </summary>
public event Action? Disconnecting;
/// <summary>
/// Whether the <see cref="MultiplayerClient"/> is currently connected.
/// This is NOT thread safe and usage should be scheduled.
@ -155,10 +159,7 @@ namespace osu.Game.Online.Multiplayer
{
// clean up local room state on server disconnect.
if (!connected.NewValue && Room != null)
{
Logger.Log("Clearing room due to multiplayer server connection loss.", LoggingTarget.Runtime, LogLevel.Important);
LeaveRoom();
}
}));
}
@ -881,7 +882,11 @@ namespace osu.Game.Online.Multiplayer
Task IStatefulUserHubClient.DisconnectRequested()
{
Schedule(() => DisconnectInternal());
Schedule(() =>
{
Disconnecting?.Invoke();
DisconnectInternal();
});
return Task.CompletedTask;
}
}

View File

@ -0,0 +1,120 @@
// 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 osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Screens;
using osu.Game.Online.API;
using osu.Game.Online.Multiplayer;
using osu.Game.Online.Spectator;
using osu.Game.Overlays;
using osu.Game.Overlays.Notifications;
using osu.Game.Screens.OnlinePlay;
namespace osu.Game.Online
{
public partial class OnlineStatusNotifier : Component
{
private readonly Func<IScreen> getCurrentScreen;
[Resolved]
private MultiplayerClient multiplayerClient { get; set; } = null!;
[Resolved]
private SpectatorClient spectatorClient { get; set; } = null!;
[Resolved]
private INotificationOverlay? notificationOverlay { get; set; }
private IBindable<APIState> apiState = null!;
private IBindable<bool> multiplayerState = null!;
private IBindable<bool> spectatorState = null!;
private bool forcedDisconnection;
public OnlineStatusNotifier(Func<IScreen> getCurrentScreen)
{
this.getCurrentScreen = getCurrentScreen;
}
[BackgroundDependencyLoader]
private void load(IAPIProvider api)
{
apiState = api.State.GetBoundCopy();
multiplayerState = multiplayerClient.IsConnected.GetBoundCopy();
spectatorState = spectatorClient.IsConnected.GetBoundCopy();
multiplayerClient.Disconnecting += notifyAboutForcedDisconnection;
spectatorClient.Disconnecting += notifyAboutForcedDisconnection;
}
private void notifyAboutForcedDisconnection()
{
if (forcedDisconnection)
return;
forcedDisconnection = true;
notificationOverlay?.Post(new SimpleErrorNotification
{
Icon = FontAwesome.Solid.ExclamationCircle,
Text = "You have been logged out on this device due to a login to your account on another device."
});
}
protected override void LoadComplete()
{
base.LoadComplete();
apiState.BindValueChanged(_ =>
{
if (apiState.Value == APIState.Online)
forcedDisconnection = false;
Scheduler.AddOnce(updateState);
});
multiplayerState.BindValueChanged(_ => Scheduler.AddOnce(updateState));
spectatorState.BindValueChanged(_ => Scheduler.AddOnce(updateState));
}
private void updateState()
{
if (forcedDisconnection)
return;
if (apiState.Value == APIState.Offline && getCurrentScreen() is OnlinePlayScreen)
{
notificationOverlay?.Post(new SimpleErrorNotification
{
Icon = FontAwesome.Solid.ExclamationCircle,
Text = "API connection was lost. Can't continue with online play."
});
return;
}
if (!multiplayerClient.IsConnected.Value && multiplayerClient.Room != null)
{
notificationOverlay?.Post(new SimpleErrorNotification
{
Icon = FontAwesome.Solid.ExclamationCircle,
Text = "Connection to the multiplayer server was lost. Exiting multiplayer."
});
}
// TODO: handle spectator server failure somehow?
}
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
if (spectatorClient.IsNotNull())
spectatorClient.Disconnecting -= notifyAboutForcedDisconnection;
if (multiplayerClient.IsNotNull())
multiplayerClient.Disconnecting -= notifyAboutForcedDisconnection;
}
}
}

View File

@ -115,12 +115,14 @@ namespace osu.Game.Online.Spectator
return connection.InvokeAsync(nameof(ISpectatorServer.EndWatchingUser), userId);
}
protected override Task DisconnectInternal()
protected override async Task DisconnectInternal()
{
if (connector == null)
return Task.CompletedTask;
await base.DisconnectInternal().ConfigureAwait(false);
return connector.Disconnect();
if (connector == null)
return;
await connector.Disconnect().ConfigureAwait(false);
}
}
}

View File

@ -70,6 +70,11 @@ namespace osu.Game.Online.Spectator
/// </summary>
public virtual event Action<int, long>? OnUserScoreProcessed;
/// <summary>
/// Invoked just prior to disconnection requested by the server via <see cref="IStatefulUserHubClient.DisconnectRequested"/>.
/// </summary>
public event Action? Disconnecting;
/// <summary>
/// A dictionary containing all users currently being watched, with the number of watching components for each user.
/// </summary>
@ -297,7 +302,11 @@ namespace osu.Game.Online.Spectator
protected abstract Task StopWatchingUserInternal(int userId);
protected abstract Task DisconnectInternal();
protected virtual Task DisconnectInternal()
{
Disconnecting?.Invoke();
return Task.CompletedTask;
}
protected override void Update()
{

View File

@ -1054,6 +1054,7 @@ namespace osu.Game
Add(difficultyRecommender);
Add(externalLinkOpener = new ExternalLinkOpener());
Add(new MusicKeyBindingHandler());
Add(new OnlineStatusNotifier(() => ScreenStack.CurrentScreen));
// side overlays which cancel each other.
var singleDisplaySideOverlays = new OverlayContainer[] { Settings, Notifications, FirstRunOverlay };

View File

@ -71,7 +71,7 @@ namespace osu.Game.Screens.OnlinePlay
screenStack = new OnlinePlaySubScreenStack { RelativeSizeAxes = Axes.Both },
new Header(ScreenTitle, screenStack),
RoomManager,
ongoingOperationTracker
ongoingOperationTracker,
}
};
}
@ -79,10 +79,7 @@ namespace osu.Game.Screens.OnlinePlay
private void onlineStateChanged(ValueChangedEvent<APIState> state) => Schedule(() =>
{
if (state.NewValue != APIState.Online)
{
Logger.Log("API connection was lost, can't continue with online play", LoggingTarget.Network, LogLevel.Important);
Schedule(forcefullyExit);
}
});
protected override void LoadComplete()

View File

@ -181,10 +181,10 @@ namespace osu.Game.Tests.Visual.Spectator
});
}
protected override Task DisconnectInternal()
protected override async Task DisconnectInternal()
{
await base.DisconnectInternal().ConfigureAwait(false);
isConnected.Value = false;
return Task.CompletedTask;
}
}
}