From 4127aaa9882c3b2007849e982b9fcc1d0b551ec4 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 27 Oct 2022 14:34:24 +0900 Subject: [PATCH 1/9] Extract general elements from HubClientConnector into SocketClientConnector --- osu.Game/Online/HubClient.cs | 28 ++++ osu.Game/Online/HubClientConnector.cs | 161 +------------------- osu.Game/Online/SocketClient.cs | 24 +++ osu.Game/Online/SocketClientConnector.cs | 183 +++++++++++++++++++++++ 4 files changed, 242 insertions(+), 154 deletions(-) create mode 100644 osu.Game/Online/HubClient.cs create mode 100644 osu.Game/Online/SocketClient.cs create mode 100644 osu.Game/Online/SocketClientConnector.cs diff --git a/osu.Game/Online/HubClient.cs b/osu.Game/Online/HubClient.cs new file mode 100644 index 0000000000..262e298f34 --- /dev/null +++ b/osu.Game/Online/HubClient.cs @@ -0,0 +1,28 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.SignalR.Client; + +namespace osu.Game.Online +{ + public class HubClient : SocketClient + { + public readonly HubConnection Connection; + + public HubClient(HubConnection connection) + { + Connection = connection; + Connection.Closed += InvokeClosed; + } + + public override Task StartAsync(CancellationToken cancellationToken) => Connection.StartAsync(cancellationToken); + + public override async ValueTask DisposeAsync() + { + await base.DisposeAsync().ConfigureAwait(false); + await Connection.DisposeAsync().ConfigureAwait(false); + } + } +} diff --git a/osu.Game/Online/HubClientConnector.cs b/osu.Game/Online/HubClientConnector.cs index 6bfe09e911..33e9f92817 100644 --- a/osu.Game/Online/HubClientConnector.cs +++ b/osu.Game/Online/HubClientConnector.cs @@ -10,13 +10,11 @@ using Microsoft.AspNetCore.SignalR.Client; using Microsoft.Extensions.DependencyInjection; using Newtonsoft.Json; using osu.Framework; -using osu.Framework.Bindables; -using osu.Framework.Logging; using osu.Game.Online.API; namespace osu.Game.Online { - public class HubClientConnector : IHubClientConnector + public class HubClientConnector : SocketClientConnector, IHubClientConnector { public const string SERVER_SHUTDOWN_MESSAGE = "Server is shutting down."; @@ -25,7 +23,6 @@ namespace osu.Game.Online /// public Action? ConfigureConnection { get; set; } - private readonly string clientName; private readonly string endpoint; private readonly string versionHash; private readonly bool preferMessagePack; @@ -34,18 +31,7 @@ namespace osu.Game.Online /// /// The current connection opened by this connector. /// - public HubConnection? CurrentConnection { get; private set; } - - /// - /// Whether this is connected to the hub, use to access the connection, if this is true. - /// - public IBindable IsConnected => isConnected; - - private readonly Bindable isConnected = new Bindable(); - private readonly SemaphoreSlim connectionLock = new SemaphoreSlim(1); - private CancellationTokenSource connectCancelSource = new CancellationTokenSource(); - - private readonly IBindable apiState = new Bindable(); + public new HubConnection? CurrentConnection => ((HubClient?)base.CurrentConnection)?.Connection; /// /// Constructs a new . @@ -56,99 +42,16 @@ namespace osu.Game.Online /// The hash representing the current game version, used for verification purposes. /// Whether to use MessagePack for serialisation if available on this platform. public HubClientConnector(string clientName, string endpoint, IAPIProvider api, string versionHash, bool preferMessagePack = true) + : base(api) { - this.clientName = clientName; + ClientName = clientName; this.endpoint = endpoint; this.api = api; this.versionHash = versionHash; this.preferMessagePack = preferMessagePack; - - apiState.BindTo(api.State); - apiState.BindValueChanged(_ => Task.Run(connectIfPossible), true); } - public Task Reconnect() - { - Logger.Log($"{clientName} reconnecting...", LoggingTarget.Network); - return Task.Run(connectIfPossible); - } - - private async Task connectIfPossible() - { - switch (apiState.Value) - { - case APIState.Failing: - case APIState.Offline: - await disconnect(true); - break; - - case APIState.Online: - await connect(); - break; - } - } - - private async Task connect() - { - cancelExistingConnect(); - - if (!await connectionLock.WaitAsync(10000).ConfigureAwait(false)) - throw new TimeoutException("Could not obtain a lock to connect. A previous attempt is likely stuck."); - - try - { - while (apiState.Value == APIState.Online) - { - // ensure any previous connection was disposed. - // this will also create a new cancellation token source. - await disconnect(false).ConfigureAwait(false); - - // this token will be valid for the scope of this connection. - // if cancelled, we can be sure that a disconnect or reconnect is handled elsewhere. - var cancellationToken = connectCancelSource.Token; - - cancellationToken.ThrowIfCancellationRequested(); - - Logger.Log($"{clientName} connecting...", LoggingTarget.Network); - - try - { - // importantly, rebuild the connection each attempt to get an updated access token. - CurrentConnection = buildConnection(cancellationToken); - - await CurrentConnection.StartAsync(cancellationToken).ConfigureAwait(false); - - Logger.Log($"{clientName} connected!", LoggingTarget.Network); - isConnected.Value = true; - return; - } - catch (OperationCanceledException) - { - //connection process was cancelled. - throw; - } - catch (Exception e) - { - await handleErrorAndDelay(e, cancellationToken).ConfigureAwait(false); - } - } - } - finally - { - connectionLock.Release(); - } - } - - /// - /// Handles an exception and delays an async flow. - /// - private async Task handleErrorAndDelay(Exception exception, CancellationToken cancellationToken) - { - Logger.Log($"{clientName} connect attempt failed: {exception.Message}", LoggingTarget.Network); - await Task.Delay(5000, cancellationToken).ConfigureAwait(false); - } - - private HubConnection buildConnection(CancellationToken cancellationToken) + protected override Task BuildConnectionAsync(CancellationToken cancellationToken) { var builder = new HubConnectionBuilder() .WithUrl(endpoint, options => @@ -188,59 +91,9 @@ namespace osu.Game.Online ConfigureConnection?.Invoke(newConnection); - newConnection.Closed += ex => onConnectionClosed(ex, cancellationToken); - return newConnection; + return Task.FromResult((SocketClient)new HubClient(newConnection)); } - private async Task onConnectionClosed(Exception? ex, CancellationToken cancellationToken) - { - isConnected.Value = false; - - if (ex != null) - await handleErrorAndDelay(ex, cancellationToken).ConfigureAwait(false); - else - Logger.Log($"{clientName} disconnected", LoggingTarget.Network); - - // make sure a disconnect wasn't triggered (and this is still the active connection). - if (!cancellationToken.IsCancellationRequested) - await Task.Run(connect, default).ConfigureAwait(false); - } - - private async Task disconnect(bool takeLock) - { - cancelExistingConnect(); - - if (takeLock) - { - if (!await connectionLock.WaitAsync(10000).ConfigureAwait(false)) - throw new TimeoutException("Could not obtain a lock to disconnect. A previous attempt is likely stuck."); - } - - try - { - if (CurrentConnection != null) - await CurrentConnection.DisposeAsync().ConfigureAwait(false); - } - finally - { - CurrentConnection = null; - if (takeLock) - connectionLock.Release(); - } - } - - private void cancelExistingConnect() - { - connectCancelSource.Cancel(); - connectCancelSource = new CancellationTokenSource(); - } - - public override string ToString() => $"Connector for {clientName} ({(IsConnected.Value ? "connected" : "not connected")}"; - - public void Dispose() - { - apiState.UnbindAll(); - cancelExistingConnect(); - } + protected override string ClientName { get; } } } diff --git a/osu.Game/Online/SocketClient.cs b/osu.Game/Online/SocketClient.cs new file mode 100644 index 0000000000..3b4aa1b49b --- /dev/null +++ b/osu.Game/Online/SocketClient.cs @@ -0,0 +1,24 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace osu.Game.Online +{ + public abstract class SocketClient : IAsyncDisposable + { + public event Func? Closed; + + protected Task InvokeClosed(Exception? exception) => Closed?.Invoke(exception) ?? Task.CompletedTask; + + public abstract Task StartAsync(CancellationToken cancellationToken); + + public virtual ValueTask DisposeAsync() + { + Closed = null; + return new ValueTask(Task.CompletedTask); + } + } +} diff --git a/osu.Game/Online/SocketClientConnector.cs b/osu.Game/Online/SocketClientConnector.cs new file mode 100644 index 0000000000..823e724ef9 --- /dev/null +++ b/osu.Game/Online/SocketClientConnector.cs @@ -0,0 +1,183 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Threading; +using System.Threading.Tasks; +using osu.Framework.Bindables; +using osu.Framework.Extensions.TypeExtensions; +using osu.Framework.Graphics; +using osu.Framework.Logging; +using osu.Game.Online.API; + +namespace osu.Game.Online +{ + public abstract class SocketClientConnector : Component + { + /// + /// Whether this is connected to the hub, use to access the connection, if this is true. + /// + public IBindable IsConnected => isConnected; + + /// + /// The current connection opened by this connector. + /// + public SocketClient? CurrentConnection { get; private set; } + + private readonly Bindable isConnected = new Bindable(); + private readonly SemaphoreSlim connectionLock = new SemaphoreSlim(1); + private CancellationTokenSource connectCancelSource = new CancellationTokenSource(); + + private readonly IBindable apiState = new Bindable(); + + /// + /// Constructs a new . + /// + /// An API provider used to react to connection state changes. + protected SocketClientConnector(IAPIProvider api) + { + apiState.BindTo(api.State); + apiState.BindValueChanged(_ => Task.Run(connectIfPossible), true); + } + + public Task Reconnect() + { + Logger.Log($"{ClientName} reconnecting...", LoggingTarget.Network); + return Task.Run(connectIfPossible); + } + + private async Task connectIfPossible() + { + switch (apiState.Value) + { + case APIState.Failing: + case APIState.Offline: + await disconnect(true); + break; + + case APIState.Online: + await connect(); + break; + } + } + + private async Task connect() + { + cancelExistingConnect(); + + if (!await connectionLock.WaitAsync(10000).ConfigureAwait(false)) + throw new TimeoutException("Could not obtain a lock to connect. A previous attempt is likely stuck."); + + try + { + while (apiState.Value == APIState.Online) + { + // ensure any previous connection was disposed. + // this will also create a new cancellation token source. + await disconnect(false).ConfigureAwait(false); + + // this token will be valid for the scope of this connection. + // if cancelled, we can be sure that a disconnect or reconnect is handled elsewhere. + var cancellationToken = connectCancelSource.Token; + + cancellationToken.ThrowIfCancellationRequested(); + + Logger.Log($"{ClientName} connecting...", LoggingTarget.Network); + + try + { + // importantly, rebuild the connection each attempt to get an updated access token. + CurrentConnection = await BuildConnectionAsync(cancellationToken).ConfigureAwait(false); + CurrentConnection.Closed += ex => onConnectionClosed(ex, cancellationToken); + + cancellationToken.ThrowIfCancellationRequested(); + + await CurrentConnection.StartAsync(cancellationToken).ConfigureAwait(false); + + Logger.Log($"{ClientName} connected!", LoggingTarget.Network); + isConnected.Value = true; + return; + } + catch (OperationCanceledException) + { + //connection process was cancelled. + throw; + } + catch (Exception e) + { + await handleErrorAndDelay(e, cancellationToken).ConfigureAwait(false); + } + } + } + finally + { + connectionLock.Release(); + } + } + + /// + /// Handles an exception and delays an async flow. + /// + private async Task handleErrorAndDelay(Exception exception, CancellationToken cancellationToken) + { + Logger.Log($"{ClientName} connect attempt failed: {exception.Message}", LoggingTarget.Network); + await Task.Delay(5000, cancellationToken).ConfigureAwait(false); + } + + protected abstract Task BuildConnectionAsync(CancellationToken cancellationToken); + + private async Task onConnectionClosed(Exception? ex, CancellationToken cancellationToken) + { + isConnected.Value = false; + + if (ex != null) + await handleErrorAndDelay(ex, cancellationToken).ConfigureAwait(false); + else + Logger.Log($"{ClientName} disconnected", LoggingTarget.Network); + + // make sure a disconnect wasn't triggered (and this is still the active connection). + if (!cancellationToken.IsCancellationRequested) + await Task.Run(connect, default).ConfigureAwait(false); + } + + private async Task disconnect(bool takeLock) + { + cancelExistingConnect(); + + if (takeLock) + { + if (!await connectionLock.WaitAsync(10000).ConfigureAwait(false)) + throw new TimeoutException("Could not obtain a lock to disconnect. A previous attempt is likely stuck."); + } + + try + { + if (CurrentConnection != null) + await CurrentConnection.DisposeAsync().ConfigureAwait(false); + } + finally + { + CurrentConnection = null; + if (takeLock) + connectionLock.Release(); + } + } + + private void cancelExistingConnect() + { + connectCancelSource.Cancel(); + connectCancelSource = new CancellationTokenSource(); + } + + protected virtual string ClientName => GetType().ReadableName(); + + public override string ToString() => $"{ClientName} ({(IsConnected.Value ? "connected" : "not connected")})"; + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + apiState.UnbindAll(); + cancelExistingConnect(); + } + } +} From 46d1713e28c1726c28d998bd89a9c481cb7a53ce Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 2 Nov 2022 11:43:22 +0900 Subject: [PATCH 2/9] Rename Socket* -> PersistentEndpoint* --- osu.Game/Online/HubClient.cs | 2 +- osu.Game/Online/HubClientConnector.cs | 6 +++--- .../{SocketClient.cs => PersistentEndpointClient.cs} | 2 +- ...tConnector.cs => PersistentEndpointClientConnector.cs} | 8 ++++---- 4 files changed, 9 insertions(+), 9 deletions(-) rename osu.Game/Online/{SocketClient.cs => PersistentEndpointClient.cs} (90%) rename osu.Game/Online/{SocketClientConnector.cs => PersistentEndpointClientConnector.cs} (95%) diff --git a/osu.Game/Online/HubClient.cs b/osu.Game/Online/HubClient.cs index 262e298f34..07428f58b4 100644 --- a/osu.Game/Online/HubClient.cs +++ b/osu.Game/Online/HubClient.cs @@ -7,7 +7,7 @@ using Microsoft.AspNetCore.SignalR.Client; namespace osu.Game.Online { - public class HubClient : SocketClient + public class HubClient : PersistentEndpointClient { public readonly HubConnection Connection; diff --git a/osu.Game/Online/HubClientConnector.cs b/osu.Game/Online/HubClientConnector.cs index 33e9f92817..6f246f6dd3 100644 --- a/osu.Game/Online/HubClientConnector.cs +++ b/osu.Game/Online/HubClientConnector.cs @@ -14,7 +14,7 @@ using osu.Game.Online.API; namespace osu.Game.Online { - public class HubClientConnector : SocketClientConnector, IHubClientConnector + public class HubClientConnector : PersistentEndpointClientConnector, IHubClientConnector { public const string SERVER_SHUTDOWN_MESSAGE = "Server is shutting down."; @@ -51,7 +51,7 @@ namespace osu.Game.Online this.preferMessagePack = preferMessagePack; } - protected override Task BuildConnectionAsync(CancellationToken cancellationToken) + protected override Task BuildConnectionAsync(CancellationToken cancellationToken) { var builder = new HubConnectionBuilder() .WithUrl(endpoint, options => @@ -91,7 +91,7 @@ namespace osu.Game.Online ConfigureConnection?.Invoke(newConnection); - return Task.FromResult((SocketClient)new HubClient(newConnection)); + return Task.FromResult((PersistentEndpointClient)new HubClient(newConnection)); } protected override string ClientName { get; } diff --git a/osu.Game/Online/SocketClient.cs b/osu.Game/Online/PersistentEndpointClient.cs similarity index 90% rename from osu.Game/Online/SocketClient.cs rename to osu.Game/Online/PersistentEndpointClient.cs index 3b4aa1b49b..d6ed2d8aec 100644 --- a/osu.Game/Online/SocketClient.cs +++ b/osu.Game/Online/PersistentEndpointClient.cs @@ -7,7 +7,7 @@ using System.Threading.Tasks; namespace osu.Game.Online { - public abstract class SocketClient : IAsyncDisposable + public abstract class PersistentEndpointClient : IAsyncDisposable { public event Func? Closed; diff --git a/osu.Game/Online/SocketClientConnector.cs b/osu.Game/Online/PersistentEndpointClientConnector.cs similarity index 95% rename from osu.Game/Online/SocketClientConnector.cs rename to osu.Game/Online/PersistentEndpointClientConnector.cs index 823e724ef9..fca7f2ed48 100644 --- a/osu.Game/Online/SocketClientConnector.cs +++ b/osu.Game/Online/PersistentEndpointClientConnector.cs @@ -12,7 +12,7 @@ using osu.Game.Online.API; namespace osu.Game.Online { - public abstract class SocketClientConnector : Component + public abstract class PersistentEndpointClientConnector : Component { /// /// Whether this is connected to the hub, use to access the connection, if this is true. @@ -22,7 +22,7 @@ namespace osu.Game.Online /// /// The current connection opened by this connector. /// - public SocketClient? CurrentConnection { get; private set; } + public PersistentEndpointClient? CurrentConnection { get; private set; } private readonly Bindable isConnected = new Bindable(); private readonly SemaphoreSlim connectionLock = new SemaphoreSlim(1); @@ -34,7 +34,7 @@ namespace osu.Game.Online /// Constructs a new . /// /// An API provider used to react to connection state changes. - protected SocketClientConnector(IAPIProvider api) + protected PersistentEndpointClientConnector(IAPIProvider api) { apiState.BindTo(api.State); apiState.BindValueChanged(_ => Task.Run(connectIfPossible), true); @@ -124,7 +124,7 @@ namespace osu.Game.Online await Task.Delay(5000, cancellationToken).ConfigureAwait(false); } - protected abstract Task BuildConnectionAsync(CancellationToken cancellationToken); + protected abstract Task BuildConnectionAsync(CancellationToken cancellationToken); private async Task onConnectionClosed(Exception? ex, CancellationToken cancellationToken) { From c9108ce41bd3afc65a931bbd7f79a770030b75df Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 2 Nov 2022 11:44:16 +0900 Subject: [PATCH 3/9] Rename StartAsync -> ConnectAsync --- osu.Game/Online/HubClient.cs | 2 +- osu.Game/Online/PersistentEndpointClient.cs | 2 +- osu.Game/Online/PersistentEndpointClientConnector.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Online/HubClient.cs b/osu.Game/Online/HubClient.cs index 07428f58b4..583f15a4a4 100644 --- a/osu.Game/Online/HubClient.cs +++ b/osu.Game/Online/HubClient.cs @@ -17,7 +17,7 @@ namespace osu.Game.Online Connection.Closed += InvokeClosed; } - public override Task StartAsync(CancellationToken cancellationToken) => Connection.StartAsync(cancellationToken); + public override Task ConnectAsync(CancellationToken cancellationToken) => Connection.StartAsync(cancellationToken); public override async ValueTask DisposeAsync() { diff --git a/osu.Game/Online/PersistentEndpointClient.cs b/osu.Game/Online/PersistentEndpointClient.cs index d6ed2d8aec..e0307f7c6a 100644 --- a/osu.Game/Online/PersistentEndpointClient.cs +++ b/osu.Game/Online/PersistentEndpointClient.cs @@ -13,7 +13,7 @@ namespace osu.Game.Online protected Task InvokeClosed(Exception? exception) => Closed?.Invoke(exception) ?? Task.CompletedTask; - public abstract Task StartAsync(CancellationToken cancellationToken); + public abstract Task ConnectAsync(CancellationToken cancellationToken); public virtual ValueTask DisposeAsync() { diff --git a/osu.Game/Online/PersistentEndpointClientConnector.cs b/osu.Game/Online/PersistentEndpointClientConnector.cs index fca7f2ed48..7082757b15 100644 --- a/osu.Game/Online/PersistentEndpointClientConnector.cs +++ b/osu.Game/Online/PersistentEndpointClientConnector.cs @@ -92,7 +92,7 @@ namespace osu.Game.Online cancellationToken.ThrowIfCancellationRequested(); - await CurrentConnection.StartAsync(cancellationToken).ConfigureAwait(false); + await CurrentConnection.ConnectAsync(cancellationToken).ConfigureAwait(false); Logger.Log($"{ClientName} connected!", LoggingTarget.Network); isConnected.Value = true; From e59c8b7d24fff0072a1392442808ad55634cb6ba Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 2 Nov 2022 11:49:04 +0900 Subject: [PATCH 4/9] Use IDisposable instead --- .../PersistentEndpointClientConnector.cs | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/osu.Game/Online/PersistentEndpointClientConnector.cs b/osu.Game/Online/PersistentEndpointClientConnector.cs index 7082757b15..2ea0c95ec1 100644 --- a/osu.Game/Online/PersistentEndpointClientConnector.cs +++ b/osu.Game/Online/PersistentEndpointClientConnector.cs @@ -6,13 +6,12 @@ using System.Threading; using System.Threading.Tasks; using osu.Framework.Bindables; using osu.Framework.Extensions.TypeExtensions; -using osu.Framework.Graphics; using osu.Framework.Logging; using osu.Game.Online.API; namespace osu.Game.Online { - public abstract class PersistentEndpointClientConnector : Component + public abstract class PersistentEndpointClientConnector : IDisposable { /// /// Whether this is connected to the hub, use to access the connection, if this is true. @@ -173,11 +172,23 @@ namespace osu.Game.Online public override string ToString() => $"{ClientName} ({(IsConnected.Value ? "connected" : "not connected")})"; - protected override void Dispose(bool isDisposing) + private bool isDisposed; + + protected virtual void Dispose(bool isDisposing) { - base.Dispose(isDisposing); + if (isDisposed) + return; + apiState.UnbindAll(); cancelExistingConnect(); + + isDisposed = true; + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); } } } From 30800c925299a7701e3f83853f47ee22afeb8386 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 2 Nov 2022 13:15:34 +0900 Subject: [PATCH 5/9] Add/adjust xmldocs --- osu.Game/Online/PersistentEndpointClient.cs | 11 +++++++++++ osu.Game/Online/PersistentEndpointClientConnector.cs | 8 ++++++-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/osu.Game/Online/PersistentEndpointClient.cs b/osu.Game/Online/PersistentEndpointClient.cs index e0307f7c6a..32c243fbbb 100644 --- a/osu.Game/Online/PersistentEndpointClient.cs +++ b/osu.Game/Online/PersistentEndpointClient.cs @@ -9,10 +9,21 @@ namespace osu.Game.Online { public abstract class PersistentEndpointClient : IAsyncDisposable { + /// + /// An event notifying the that the connection has been closed + /// public event Func? Closed; + /// + /// Notifies the that the connection has been closed. + /// + /// The exception that the connection closed with. protected Task InvokeClosed(Exception? exception) => Closed?.Invoke(exception) ?? Task.CompletedTask; + /// + /// Connects the client to the remote service to begin processing messages. + /// + /// A cancellation token to stop processing messages. public abstract Task ConnectAsync(CancellationToken cancellationToken); public virtual ValueTask DisposeAsync() diff --git a/osu.Game/Online/PersistentEndpointClientConnector.cs b/osu.Game/Online/PersistentEndpointClientConnector.cs index 2ea0c95ec1..70e10c6c7d 100644 --- a/osu.Game/Online/PersistentEndpointClientConnector.cs +++ b/osu.Game/Online/PersistentEndpointClientConnector.cs @@ -14,7 +14,7 @@ namespace osu.Game.Online public abstract class PersistentEndpointClientConnector : IDisposable { /// - /// Whether this is connected to the hub, use to access the connection, if this is true. + /// Whether the managed connection is currently connected. When true use to access the connection. /// public IBindable IsConnected => isConnected; @@ -30,7 +30,7 @@ namespace osu.Game.Online private readonly IBindable apiState = new Bindable(); /// - /// Constructs a new . + /// Constructs a new . /// /// An API provider used to react to connection state changes. protected PersistentEndpointClientConnector(IAPIProvider api) @@ -123,6 +123,10 @@ namespace osu.Game.Online await Task.Delay(5000, cancellationToken).ConfigureAwait(false); } + /// + /// Creates a new . + /// + /// A cancellation token to stop the process. protected abstract Task BuildConnectionAsync(CancellationToken cancellationToken); private async Task onConnectionClosed(Exception? ex, CancellationToken cancellationToken) From 37300ba9e25ae1d2e3f6e9dd747d27b5fa4d18c9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 2 Nov 2022 13:55:01 +0900 Subject: [PATCH 6/9] Standardise "Visual Settings" components to fix mismatched paddings and labels --- .../Play/PlayerSettings/PlayerCheckbox.cs | 21 ++++++++++++------- .../Play/PlayerSettings/VisualSettings.cs | 17 ++------------- 2 files changed, 15 insertions(+), 23 deletions(-) diff --git a/osu.Game/Screens/Play/PlayerSettings/PlayerCheckbox.cs b/osu.Game/Screens/Play/PlayerSettings/PlayerCheckbox.cs index dc09676254..cea03d2155 100644 --- a/osu.Game/Screens/Play/PlayerSettings/PlayerCheckbox.cs +++ b/osu.Game/Screens/Play/PlayerSettings/PlayerCheckbox.cs @@ -1,22 +1,27 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Allocation; +using osu.Framework.Graphics; using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; +using osu.Game.Overlays.Settings; namespace osu.Game.Screens.Play.PlayerSettings { - public class PlayerCheckbox : OsuCheckbox + public class PlayerCheckbox : SettingsCheckbox { - [BackgroundDependencyLoader] - private void load(OsuColour colours) + protected override Drawable CreateControl() => new PlayerCheckboxControl(); + + public class PlayerCheckboxControl : OsuCheckbox { - Nub.AccentColour = colours.Yellow; - Nub.GlowingAccentColour = colours.YellowLighter; - Nub.GlowColour = colours.YellowDark; + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + Nub.AccentColour = colours.Yellow; + Nub.GlowingAccentColour = colours.YellowLighter; + Nub.GlowColour = colours.YellowDark; + } } } } diff --git a/osu.Game/Screens/Play/PlayerSettings/VisualSettings.cs b/osu.Game/Screens/Play/PlayerSettings/VisualSettings.cs index e55af0bba7..bb3360acec 100644 --- a/osu.Game/Screens/Play/PlayerSettings/VisualSettings.cs +++ b/osu.Game/Screens/Play/PlayerSettings/VisualSettings.cs @@ -1,12 +1,9 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Game.Configuration; -using osu.Game.Graphics.Sprites; using osu.Game.Localisation; namespace osu.Game.Screens.Play.PlayerSettings @@ -24,26 +21,16 @@ namespace osu.Game.Screens.Play.PlayerSettings { Children = new Drawable[] { - new OsuSpriteText - { - Text = GameplaySettingsStrings.BackgroundDim - }, dimSliderBar = new PlayerSliderBar { + LabelText = GameplaySettingsStrings.BackgroundDim, DisplayAsPercentage = true }, - new OsuSpriteText - { - Text = GameplaySettingsStrings.BackgroundBlur - }, blurSliderBar = new PlayerSliderBar { + LabelText = GameplaySettingsStrings.BackgroundBlur, DisplayAsPercentage = true }, - new OsuSpriteText - { - Text = "Toggles:" - }, showStoryboardToggle = new PlayerCheckbox { LabelText = GraphicsSettingsStrings.StoryboardVideo }, beatmapSkinsToggle = new PlayerCheckbox { LabelText = SkinSettingsStrings.BeatmapSkins }, beatmapColorsToggle = new PlayerCheckbox { LabelText = SkinSettingsStrings.BeatmapColours }, From e761c0395d411aadc40e565f8deeff43c641c4a6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 2 Nov 2022 14:47:56 +0900 Subject: [PATCH 7/9] Fix multiple notifications arriving for imports in edge cases --- osu.Game/Overlays/Notifications/ProgressNotification.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Notifications/ProgressNotification.cs b/osu.Game/Overlays/Notifications/ProgressNotification.cs index 4cf47013bd..7b62c95179 100644 --- a/osu.Game/Overlays/Notifications/ProgressNotification.cs +++ b/osu.Game/Overlays/Notifications/ProgressNotification.cs @@ -148,7 +148,7 @@ namespace osu.Game.Overlays.Notifications } } - private bool completionSent; + private int completionSent; /// /// Attempt to post a completion notification. @@ -162,11 +162,11 @@ namespace osu.Game.Overlays.Notifications if (CompletionTarget == null) return; - if (completionSent) + // Thread-safe barrier, as this may be called by a web request and also scheduled to the update thread at the same time. + if (Interlocked.Increment(ref completionSent) == 0) return; CompletionTarget.Invoke(CreateCompletionNotification()); - completionSent = true; Close(false); } From e5f53b1ad8346c3d0e9241bfd79138df2aae3121 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 2 Nov 2022 15:18:47 +0900 Subject: [PATCH 8/9] Use Interlocked.Exhange() instead Increment isn't correct since it returns the post-incremented value. It also always increments. --- osu.Game/Overlays/Notifications/ProgressNotification.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Notifications/ProgressNotification.cs b/osu.Game/Overlays/Notifications/ProgressNotification.cs index 7b62c95179..54a1d69a9e 100644 --- a/osu.Game/Overlays/Notifications/ProgressNotification.cs +++ b/osu.Game/Overlays/Notifications/ProgressNotification.cs @@ -163,7 +163,7 @@ namespace osu.Game.Overlays.Notifications return; // Thread-safe barrier, as this may be called by a web request and also scheduled to the update thread at the same time. - if (Interlocked.Increment(ref completionSent) == 0) + if (Interlocked.Exchange(ref completionSent, 1) == 0) return; CompletionTarget.Invoke(CreateCompletionNotification()); From db34f238c09597748ff5c9e6c22211d8b8346641 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 2 Nov 2022 15:47:30 +0900 Subject: [PATCH 9/9] Fix inverted condition --- osu.Game/Overlays/Notifications/ProgressNotification.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Notifications/ProgressNotification.cs b/osu.Game/Overlays/Notifications/ProgressNotification.cs index 54a1d69a9e..9812feb4a1 100644 --- a/osu.Game/Overlays/Notifications/ProgressNotification.cs +++ b/osu.Game/Overlays/Notifications/ProgressNotification.cs @@ -163,7 +163,7 @@ namespace osu.Game.Overlays.Notifications return; // Thread-safe barrier, as this may be called by a web request and also scheduled to the update thread at the same time. - if (Interlocked.Exchange(ref completionSent, 1) == 0) + if (Interlocked.Exchange(ref completionSent, 1) == 1) return; CompletionTarget.Invoke(CreateCompletionNotification());