Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions osu.Desktop/IPC/Messages/HitCountMessage.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// 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 Newtonsoft.Json;

namespace osu.Desktop.IPC.Messages
{
public class HitCountMessage : OsuWebSocketMessage
{
[JsonProperty("new_hits")]
public long NewHits { get; init; }
}
}
19 changes: 19 additions & 0 deletions osu.Desktop/IPC/Messages/OsuWebSocketMessage.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// 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 Newtonsoft.Json;
using osu.Framework.Extensions.TypeExtensions;

namespace osu.Desktop.IPC.Messages
{
public abstract class OsuWebSocketMessage
{
[JsonProperty("type")]
public string Type { get; }

protected OsuWebSocketMessage()
{
Type = GetType().ReadableName();
}
}
}
75 changes: 75 additions & 0 deletions osu.Desktop/IPC/OsuWebSocketProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// 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.Linq;
using System.Threading;
using osu.Desktop.IPC.Messages;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions;
using osu.Framework.Graphics;
using osu.Framework.Logging;
using osu.Game.Configuration;
using osu.Game.IPC;
using osu.Game.Online.Multiplayer;
using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring;
using JsonConvert = Newtonsoft.Json.JsonConvert;

namespace osu.Desktop.IPC
{
public partial class OsuWebSocketProvider : Component
{
private WebSocketServer? server;
private readonly Bindable<ScoreInfo> lastLocalScore = new Bindable<ScoreInfo>();

[BackgroundDependencyLoader]
private void load(SessionStatics sessionStatics)
{
server = new WebSocketServer(49727);
server.StartAsync().FireAndForget(onError: ex => Logger.Error(ex, "Failed to start websocket"));

sessionStatics.BindWith(Static.LastLocalUserScore, lastLocalScore);
}

protected override void LoadComplete()
{
base.LoadComplete();

lastLocalScore.BindValueChanged(val =>
{
if (val.NewValue == null)
return;

if (server?.IsRunning != true)
return;

var msg = new HitCountMessage { NewHits = val.NewValue.Statistics.Where(kv => kv.Key.IsBasic() && kv.Key.IsHit()).Sum(kv => kv.Value) };
broadcast(msg);
});
}

private void broadcast(OsuWebSocketMessage message)
{
if (server?.IsRunning != true)
return;

string messageString = JsonConvert.SerializeObject(message);
server.BroadcastAsync(messageString).FireAndForget();
}

protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);

if (server?.IsRunning == true)
{
var cts = new CancellationTokenSource();
cts.CancelAfter(TimeSpan.FromSeconds(10));
server.StopAsync(cts.Token).WaitSafely();
server = null;
}
}
}
}
6 changes: 6 additions & 0 deletions osu.Desktop/OsuGameDesktop.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using System.Reflection;
using System.Runtime.Versioning;
using Microsoft.Win32;
using osu.Desktop.IPC;
using osu.Desktop.Performance;
using osu.Desktop.Security;
using osu.Framework.Platform;
Expand Down Expand Up @@ -35,6 +36,8 @@ internal partial class OsuGameDesktop : OsuGame

public bool IsFirstRun { get; init; }

public bool EnableWebSocketServer { get; init; }

public OsuGameDesktop(string[]? args = null)
: base(args)
{
Expand Down Expand Up @@ -148,6 +151,9 @@ protected override void LoadComplete()

osuSchemeLinkIPCChannel = new OsuSchemeLinkIPCChannel(Host, this);
archiveImportIPCChannel = new ArchiveImportIPCChannel(Host, this);

if (EnableWebSocketServer)
Add(new OsuWebSocketProvider());
}

public override void SetHost(GameHost host)
Expand Down
3 changes: 2 additions & 1 deletion osu.Desktop/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,8 @@ public static void Main(string[] args)
{
host.Run(new OsuGameDesktop(args)
{
IsFirstRun = isFirstRun
IsFirstRun = isFirstRun,
EnableWebSocketServer = Environment.GetEnvironmentVariable("OSU_WEBSOCKET_SERVER") == "1",
});
}
}
Expand Down
61 changes: 61 additions & 0 deletions osu.Game.Tests/IPC/WebSocketClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// 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.Net.WebSockets;
using System.Threading;
using System.Threading.Tasks;
using osu.Game.IPC;

namespace osu.Game.Tests.IPC
{
public sealed class WebSocketClient : IDisposable
{
public event Action<string>? MessageReceived;
public event Action? Closed;

private readonly int port;
private WebSocketChannel? channel;

public WebSocketClient(int port)
{
this.port = port;
}

public async Task Start(CancellationToken cancellationToken = default)
{
var webSocket = new ClientWebSocket();
await webSocket.ConnectAsync(new Uri($@"ws://localhost:{port}/"), cancellationToken);
channel = new WebSocketChannel(webSocket);
channel.MessageReceived += (msg) => MessageReceived?.Invoke(msg);

Check warning

Code scanning / InspectCode

Redundant lambda signature parentheses Warning test

Redundant lambda signature parentheses
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
channel.ClosedPrematurely += () => Closed?.Invoke();
channel.Start(cancellationToken);
}

public async Task SendAsync(string message)
{
if (channel == null)
throw new InvalidOperationException($@"Must {nameof(Start)} first.");

await channel.SendAsync(message);
}

public async Task StopAsync(CancellationToken stoppingToken = default)
{
try
{
if (channel != null)
await channel.StopAsync(stoppingToken).ConfigureAwait(false);
}
catch (OperationCanceledException)
{
// has to be caught manually because outer task isn't accepting `stoppingToken`.
}
}

public void Dispose()
{
channel?.Dispose();
}
}
}
Loading
Loading