Last active
September 22, 2021 20:23
-
-
Save zHaytam/69d7c1e15b1c57457b9177be1ef34329 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using Microsoft.JSInterop; | |
using System; | |
using System.Buffers; | |
using System.Collections.Concurrent; | |
using System.Collections.Generic; | |
using System.Diagnostics; | |
using System.Text.Json; | |
using System.Threading; | |
using System.Threading.Tasks; | |
namespace Blazor.Diagrams.Interop | |
{ | |
public class BatchJsRuntime : IJSRuntime | |
{ | |
private long _nextId = 1; | |
private readonly ConcurrentQueue<JsCall> _calls; | |
private readonly ConcurrentDictionary<long, CancellationTokenRegistration> _cancellationRegistrations; | |
private readonly IJSRuntime _jsRuntime; | |
private Timer _timer; | |
public BatchJsRuntime(IJSRuntime jsRuntime) | |
{ | |
_jsRuntime = jsRuntime; | |
_calls = new ConcurrentQueue<JsCall>(); | |
_cancellationRegistrations = new ConcurrentDictionary<long, CancellationTokenRegistration>(); | |
} | |
public JsonSerializerOptions JsonSerializerOptions { get; } = new JsonSerializerOptions | |
{ | |
MaxDepth = 32, | |
PropertyNamingPolicy = JsonNamingPolicy.CamelCase, | |
PropertyNameCaseInsensitive = true | |
}; | |
public ValueTask<TValue> InvokeAsync<TValue>(string identifier, object[] args) | |
{ | |
return InvokeAsync<TValue>(identifier, CancellationToken.None, args); | |
} | |
public ValueTask<TValue> InvokeAsync<TValue>(string identifier, CancellationToken cancellationToken, object[] args) | |
{ | |
var id = Interlocked.Increment(ref _nextId); | |
var tcs = new TaskCompletionSource<TValue>(); | |
var call = new JsCall(id, identifier, args, tcs, typeof(TValue)); | |
_calls.Enqueue(call); | |
// Handle cancellations | |
if (cancellationToken.CanBeCanceled) | |
{ | |
_cancellationRegistrations[id] = cancellationToken.Register(() => | |
{ | |
tcs.TrySetCanceled(cancellationToken); | |
if (_cancellationRegistrations.TryGetValue(id, out var registration)) | |
{ | |
registration.Dispose(); | |
} | |
}); | |
} | |
if (_timer == null) | |
{ | |
_timer = new Timer(OnTimerTick, null, TimeSpan.FromMilliseconds(50), TimeSpan.FromMilliseconds(50)); | |
} | |
return new ValueTask<TValue>(tcs.Task); | |
} | |
private async void OnTimerTick(object state) | |
{ | |
if (_calls.Count == 0) | |
return; | |
var currentCalls = new List<JsCall>(); | |
var callJsObjects = new List<object>(); | |
while (_calls.TryDequeue(out var call)) | |
{ | |
currentCalls.Add(call); | |
callJsObjects.Add(new | |
{ | |
identifier = call.Identifier, | |
args = call.Args != null && call.Args.Length != 0 ? call.Args : null | |
}); | |
} | |
var results = await _jsRuntime.InvokeAsync<JsResult[]>("batchJsInterop", callJsObjects); | |
for (var i = 0; i < results.Length; i++) | |
{ | |
var call = currentCalls[i]; | |
var result = results[i]; | |
if (result.Success) | |
{ | |
object resultObj = result.ReturnValue == null ? null : ToObject((JsonElement)result.ReturnValue, call.Type, JsonSerializerOptions); | |
TcsUtil.SetResult(call.Tcs, call.Type, resultObj); | |
} | |
else | |
{ | |
TcsUtil.SetException(call.Tcs, call.Type, new BatchJsException(result.Error)); | |
} | |
} | |
} | |
private static object ToObject(JsonElement element, Type type, JsonSerializerOptions options = null) | |
{ | |
var bufferWriter = new ArrayBufferWriter<byte>(); | |
using (var writer = new Utf8JsonWriter(bufferWriter)) | |
{ | |
element.WriteTo(writer); | |
} | |
return JsonSerializer.Deserialize(bufferWriter.WrittenSpan, type, options); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment