Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Write to websocket asynchronously, plus fix thread-safety issues #60

Merged
merged 1 commit into from
Apr 18, 2021
Merged
Show file tree
Hide file tree
Changes from all 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
36 changes: 27 additions & 9 deletions BeatSaberHTTPStatus/HTTPServer.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
using System;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using IPA.Utilities.Async;
using SimpleJSON;
using WebSocketSharp;
using WebSocketSharp.Net;
Expand Down Expand Up @@ -44,7 +47,8 @@ public void OnHTTPGet(HttpRequestEventArgs e) {
res.ContentType = "application/json";
res.ContentEncoding = Encoding.UTF8;

var stringifiedStatus = Encoding.UTF8.GetBytes(statusManager.statusJSON.ToString());
// read game info from on game thread
var stringifiedStatus = UnityMainThreadTaskScheduler.Factory.StartNew(() => Encoding.UTF8.GetBytes(statusManager.statusJSON.ToString())).Result;

res.ContentLength64 = stringifiedStatus.Length;
res.Close(stringifiedStatus, false);
Expand All @@ -59,24 +63,38 @@ public void OnHTTPGet(HttpRequestEventArgs e) {

public class StatusBroadcastBehavior : WebSocketBehavior {
private StatusManager statusManager;
private Task readyToWrite = Task.CompletedTask;
private readonly CancellationTokenSource connectionClosed = new CancellationTokenSource();

public void SetStatusManager(StatusManager statusManager) {
this.statusManager = statusManager;

statusManager.statusChange += OnStatusChange;
}

protected override void OnOpen() {
JSONObject eventJSON = new JSONObject();
/// <summary>Queue data to send on the websocket in-order. This method is thread-safe.</summary>
private void QueuedSend(string data) {
var promise = new TaskCompletionSource<object>();
var oldReadyToWrite = Interlocked.Exchange(ref readyToWrite, promise.Task);
oldReadyToWrite.ContinueWith(t => {
SendAsync(data, b => {
opl- marked this conversation as resolved.
Show resolved Hide resolved
promise.SetResult(null);
});
}, connectionClosed.Token, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default);
}

eventJSON["event"] = "hello";
eventJSON["time"] = new JSONNumber(Plugin.GetCurrentTime());
eventJSON["status"] = statusManager.statusJSON;
protected override void OnOpen() {
UnityMainThreadTaskScheduler.Factory.StartNew(() => {
JSONObject eventJSON = new JSONObject();
eventJSON["event"] = "hello";
eventJSON["time"] = new JSONNumber(Plugin.GetCurrentTime());
eventJSON["status"] = statusManager.statusJSON;

Send(eventJSON.ToString());
QueuedSend(eventJSON.ToString());
}, connectionClosed.Token);
}

protected override void OnClose(CloseEventArgs e) {
connectionClosed.Cancel();
statusManager.statusChange -= OnStatusChange;
}

Expand Down Expand Up @@ -108,7 +126,7 @@ public void OnStatusChange(StatusManager statusManager, ChangedProperties change
eventJSON["beatmapEvent"] = statusManager.beatmapEventJSON;
}

Send(eventJSON.ToString());
QueuedSend(eventJSON.ToString());
}
}
}
2 changes: 1 addition & 1 deletion BeatSaberHTTPStatus/StatusManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ public void EmitStatusUpdate(ChangedProperties changedProps, string cause) {
}
if (changedProps.beatmapEvent) UpdateBeatmapEventJSON();

if (statusChange != null) statusChange(this, changedProps, cause);
statusChange?.Invoke(this, changedProps, cause);
}

private void UpdateAll() {
Expand Down