From aeb68d14c40f78e4d3a2e721941d03f53635e3c7 Mon Sep 17 00:00:00 2001 From: KristofferStrube Date: Mon, 8 Jul 2024 20:18:02 +0200 Subject: [PATCH] Restructured to share more code for two JsonJob types. --- .../BaseJsonJob.cs | 143 ++++++++++++++++++ .../JsonJob.cs | 127 +--------------- .../TaskJsonJob.cs | 130 +--------------- 3 files changed, 153 insertions(+), 247 deletions(-) create mode 100644 src/KristofferStrube.Blazor.WebWorkers/BaseJsonJob.cs diff --git a/src/KristofferStrube.Blazor.WebWorkers/BaseJsonJob.cs b/src/KristofferStrube.Blazor.WebWorkers/BaseJsonJob.cs new file mode 100644 index 0000000..6091270 --- /dev/null +++ b/src/KristofferStrube.Blazor.WebWorkers/BaseJsonJob.cs @@ -0,0 +1,143 @@ +using KristofferStrube.Blazor.DOM; +using KristofferStrube.Blazor.Window; +using System.Collections.Concurrent; +using System.Runtime.InteropServices.JavaScript; +using System.Runtime.Versioning; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace KristofferStrube.Blazor.WebWorkers; + +/// +/// The base definition of a job that uses JSON as its serialization mechanism for the input and output. +/// +/// The input type for the job. +/// The output type for the job. +public abstract class BaseJsonJob : IJob +{ + private static readonly bool inputIsString = typeof(TInput) == typeof(string); + private static readonly bool outputIsString = typeof(TOutput) == typeof(string); + + /// + public static async Task> InitializeAsync(Worker worker, ConcurrentDictionary> pendingTasks) + { + EventListener eventListener = default!; + eventListener = await EventListener.CreateAsync(worker.JSRuntime, async e => + { + JobResponse response = await e.GetDataAsync(); + if (pendingTasks.Remove(response.RequestIdentifier, out TaskCompletionSource? successTaskCompletionSource)) + { + if (outputIsString && response.OutputSerialized is TOutput stringOutput) + { + successTaskCompletionSource.SetResult(stringOutput); + } + else + { + successTaskCompletionSource.SetResult(JsonSerializer.Deserialize(response.OutputSerialized)!); + } + } + }); + + await worker.AddOnMessageEventListenerAsync(eventListener); + + return eventListener; + } + + /// + public static async Task ExecuteAsync(TInput input, Worker worker, ConcurrentDictionary> pendingTasks) where TJob : IJob + { + string requestIdentifier = Guid.NewGuid().ToString(); + var tcs = new TaskCompletionSource(); + pendingTasks[requestIdentifier] = tcs; + + string inputSerialized = input is string stringInput + ? stringInput + : JsonSerializer.Serialize(input); + + await worker.PostMessageAsync(new JobArguments() + { + RequestIdentifier = requestIdentifier, + InputSerialized = inputSerialized + }); + + return await tcs.Task; + } + + /// + /// Gets the input from a message. + /// + /// The message sent to the worker. + /// Returns the input. + [SupportedOSPlatform("browser")] + protected (TInput input, string requestIdentifier) GetInputAndRequestIdentifier(JSObject message) + { + JSObject data = message.GetPropertyAsJSObject("data")!; + string? inputSerialized = data.GetPropertyAsString("inputSerialized"); + string requestIdentifier = data.GetPropertyAsString("requestIdentifier")!; + + TInput input = inputIsString && inputSerialized is TInput stringInput + ? stringInput + : JsonSerializer.Deserialize(inputSerialized!)!; + + return (input, requestIdentifier); + } + + /// + /// Posts the output. + /// + /// The output serialized. + /// The . + [SupportedOSPlatform("browser")] + protected void PostOutput(TOutput output, string requestIdentifier) + { + JSObject outputObject = Imports.CreateObject(); + + outputObject.SetProperty("requestIdentifier", requestIdentifier); + if (output is string stringOutput) + { + outputObject.SetProperty("outputSerialized", stringOutput); + } + else + { + outputObject.SetProperty("outputSerialized", JsonSerializer.Serialize(output)); + } + + Imports.PostMessage(outputObject); + } + + /// + /// The arguments that are parsed to the job. + /// + public class JobArguments + { + /// + /// The unique identifier for the specific request. Used to identify which task has finished when the job responds. + /// + [JsonPropertyName("requestIdentifier")] + public required string RequestIdentifier { get; set; } + + /// + /// The input serialized to JSON. + /// + [JsonPropertyName("inputSerialized")] + public required string InputSerialized { get; set; } + } + + /// + /// The format of the respond. + /// + public class JobResponse + { + /// + /// The same unique identifier sent in . + /// + [JsonPropertyName("requestIdentifier")] + public required string RequestIdentifier { get; set; } + + /// + /// The output serialized to JSON. + /// + [JsonPropertyName("outputSerialized")] + public required string OutputSerialized { get; set; } + } +} diff --git a/src/KristofferStrube.Blazor.WebWorkers/JsonJob.cs b/src/KristofferStrube.Blazor.WebWorkers/JsonJob.cs index ce80e0e..8e70bf5 100644 --- a/src/KristofferStrube.Blazor.WebWorkers/JsonJob.cs +++ b/src/KristofferStrube.Blazor.WebWorkers/JsonJob.cs @@ -1,19 +1,14 @@ -using KristofferStrube.Blazor.DOM; -using KristofferStrube.Blazor.Window; -using System.Collections.Concurrent; -using System.Runtime.InteropServices.JavaScript; -using System.Runtime.Versioning; +using System.Runtime.Versioning; using System.Text.Json; -using System.Text.Json.Serialization; namespace KristofferStrube.Blazor.WebWorkers; /// /// A job that uses JSON as its serialization mechanism for the input and output. /// -/// -/// -public abstract class JsonJob : IJob +/// The input type for the job. +/// The output type for the job. +public abstract class JsonJob : BaseJsonJob { /// /// The actual work being done by the job. This will be run when the job is executed. @@ -37,51 +32,6 @@ public TOutput ExecuteWithoutUsingWorker(TInput input) return outputSerializedAndDeserialized; } - /// - public static async Task> InitializeAsync(Worker worker, ConcurrentDictionary> pendingTasks) - { - EventListener eventListener = default!; - eventListener = await EventListener.CreateAsync(worker.JSRuntime, async e => - { - JobResponse response = await e.GetDataAsync(); - if (pendingTasks.Remove(response.RequestIdentifier, out TaskCompletionSource? successTaskCompletionSource)) - { - if (typeof(TOutput) == typeof(string) && response.OutputSerialized is TOutput stringOutput) - { - successTaskCompletionSource.SetResult(stringOutput); - } - else - { - successTaskCompletionSource.SetResult(JsonSerializer.Deserialize(response.OutputSerialized)!); - } - } - }); - - await worker.AddOnMessageEventListenerAsync(eventListener); - - return eventListener; - } - - /// - public static async Task ExecuteAsync(TInput input, Worker worker, ConcurrentDictionary> pendingTasks) where TJob : IJob - { - string requestIdentifier = Guid.NewGuid().ToString(); - var tcs = new TaskCompletionSource(); - pendingTasks[requestIdentifier] = tcs; - - string inputSerialized = input is string stringInput - ? stringInput - : JsonSerializer.Serialize(input); - - await worker.PostMessageAsync(new JobArguments() - { - RequestIdentifier = requestIdentifier, - InputSerialized = inputSerialized - }); - - return await tcs.Task; - } - /// /// This method is called from the Worker project to start listening for events /// @@ -90,15 +40,7 @@ public async Task StartAsync() { Imports.RegisterOnMessage(message => { - JSObject data = message.GetPropertyAsJSObject("data")!; - string? inputSerialized = data.GetPropertyAsString("inputSerialized"); - string requestIdentifier = data.GetPropertyAsString("requestIdentifier")!; - - if (inputSerialized is null) return; - - TInput input = typeof(TInput) == typeof(string) && inputSerialized is TInput stringInput - ? stringInput - : JsonSerializer.Deserialize(inputSerialized)!; + (TInput input, string requestIdentifier) = GetInputAndRequestIdentifier(message); TOutput output = Work(input); @@ -108,63 +50,4 @@ public async Task StartAsync() TaskCompletionSource tcs = new(); await tcs.Task; } - - /// - /// Posts the output. - /// - /// The output serialized. - /// The . - [SupportedOSPlatform("browser")] - private void PostOutput(TOutput output, string requestIdentifier) - { - JSObject outputObject = Imports.CreateObject(); - - outputObject.SetProperty("requestIdentifier", requestIdentifier); - if (output is string stringOutput) - { - outputObject.SetProperty("outputSerialized", stringOutput); - } - else - { - outputObject.SetProperty("outputSerialized", JsonSerializer.Serialize(output)); - } - - Imports.PostMessage(outputObject); - } - - /// - /// The arguments that are parsed to the job. - /// - public class JobArguments - { - /// - /// The unique identifier for the specific request. Used to identify which task has finished when the job responds. - /// - [JsonPropertyName("requestIdentifier")] - public required string RequestIdentifier { get; set; } - - /// - /// The input serialized to JSON. - /// - [JsonPropertyName("inputSerialized")] - public required string InputSerialized { get; set; } - } - - /// - /// The format of the respond. - /// - public class JobResponse - { - /// - /// The same unique identifier sent in . - /// - [JsonPropertyName("requestIdentifier")] - public required string RequestIdentifier { get; set; } - - /// - /// The output serialized to JSON. - /// - [JsonPropertyName("outputSerialized")] - public required string OutputSerialized { get; set; } - } } diff --git a/src/KristofferStrube.Blazor.WebWorkers/TaskJsonJob.cs b/src/KristofferStrube.Blazor.WebWorkers/TaskJsonJob.cs index 82b3492..68635df 100644 --- a/src/KristofferStrube.Blazor.WebWorkers/TaskJsonJob.cs +++ b/src/KristofferStrube.Blazor.WebWorkers/TaskJsonJob.cs @@ -1,19 +1,14 @@ -using KristofferStrube.Blazor.DOM; -using KristofferStrube.Blazor.Window; -using System.Collections.Concurrent; -using System.Runtime.InteropServices.JavaScript; -using System.Runtime.Versioning; +using System.Runtime.Versioning; using System.Text.Json; -using System.Text.Json.Serialization; namespace KristofferStrube.Blazor.WebWorkers; /// /// A job that uses JSON as its serialization mechanism for the input and output. This is different from a as this returns task. /// -/// -/// -public abstract class TaskJsonJob : IJob +/// The input type for the job. +/// The output type for the job. +public abstract class TaskJsonJob : BaseJsonJob { /// /// The actual work being done by the job. This will be run when the job is executed. @@ -37,54 +32,6 @@ public async Task ExecuteWithoutUsingWorker(TInput input) return outputSerializedAndDeserialized; } - - /// - public static async Task> InitializeAsync(Worker worker, ConcurrentDictionary> pendingTasks) - { - EventListener eventListener = default!; - eventListener = await EventListener.CreateAsync(worker.JSRuntime, async e => - { - JobResponse response = await e.GetDataAsync(); - if (pendingTasks.Remove(response.RequestIdentifier, out TaskCompletionSource? successTaskCompletionSource)) - { - if (typeof(TOutput) == typeof(string) && response.OutputSerialized is TOutput stringOutput) - { - successTaskCompletionSource.SetResult(stringOutput); - } - else - { - successTaskCompletionSource.SetResult(JsonSerializer.Deserialize(response.OutputSerialized)!); - } - } - }); - - await worker.AddOnMessageEventListenerAsync(eventListener); - - return eventListener; - } - - /// - /// How an input is transfered to the for the . - /// - public static async Task ExecuteAsync(TInput input, Worker worker, ConcurrentDictionary> pendingTasks) where TJob : IJob - { - string requestIdentifier = Guid.NewGuid().ToString(); - var tcs = new TaskCompletionSource(); - pendingTasks[requestIdentifier] = tcs; - - string inputSerialized = input is string stringInput - ? stringInput - : JsonSerializer.Serialize(input); - - await worker.PostMessageAsync(new JobArguments() - { - RequestIdentifier = requestIdentifier, - InputSerialized = inputSerialized - }); - - return await tcs.Task; - } - /// /// This method is called from the Worker project to start listening for events /// @@ -93,15 +40,7 @@ public async Task StartAsync() { Imports.RegisterOnMessage(async message => { - JSObject data = message.GetPropertyAsJSObject("data")!; - string? inputSerialized = data.GetPropertyAsString("inputSerialized"); - string requestIdentifier = data.GetPropertyAsString("requestIdentifier")!; - - if (inputSerialized is null) return; - - TInput input = typeof(TInput) == typeof(string) && inputSerialized is TInput stringInput - ? stringInput - : JsonSerializer.Deserialize(inputSerialized)!; + (TInput input, string requestIdentifier) = GetInputAndRequestIdentifier(message); TOutput output = await Work(input); @@ -111,63 +50,4 @@ public async Task StartAsync() TaskCompletionSource tcs = new(); await tcs.Task; } - - /// - /// Posts the output. - /// - /// The output serialized. - /// The . - [SupportedOSPlatform("browser")] - private void PostOutput(TOutput output, string requestIdentifier) - { - JSObject outputObject = Imports.CreateObject(); - - outputObject.SetProperty("requestIdentifier", requestIdentifier); - if (output is string stringOutput) - { - outputObject.SetProperty("outputSerialized", stringOutput); - } - else - { - outputObject.SetProperty("outputSerialized", JsonSerializer.Serialize(output)); - } - - Imports.PostMessage(outputObject); - } - - /// - /// The arguments that are parsed to the job. - /// - public class JobArguments - { - /// - /// The unique identifier for the specific request. Used to identify which task has finished when the job responds. - /// - [JsonPropertyName("requestIdentifier")] - public required string RequestIdentifier { get; set; } - - /// - /// The input serialized to JSON. - /// - [JsonPropertyName("inputSerialized")] - public required string InputSerialized { get; set; } - } - - /// - /// The format of the respond. - /// - public class JobResponse - { - /// - /// The same unique identifier sent in . - /// - [JsonPropertyName("requestIdentifier")] - public required string RequestIdentifier { get; set; } - - /// - /// The output serialized to JSON. - /// - [JsonPropertyName("outputSerialized")] - public required string OutputSerialized { get; set; } - } }