Skip to content

Instantly share code, notes, and snippets.

@zHaytam
Last active September 22, 2021 20:23
Show Gist options
  • Save zHaytam/69d7c1e15b1c57457b9177be1ef34329 to your computer and use it in GitHub Desktop.
Save zHaytam/69d7c1e15b1c57457b9177be1ef34329 to your computer and use it in GitHub Desktop.
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