Feature/shared web worker (#7)

* Added SharedWorker to the renderer.
* test RenderEngine using shared workers.
* Creating primitives for shared workers, the issue is this queued work thing is mainly what these are going to be used for.
I should write this for any open cv operation or any media operations, this should be keeping things snappy.
This commit is contained in:
ArEnSc authored and GitHub committed 2024-09-12 02:40:13 -04:00
1 parent f586e2e25c
commit 95ca2c333b
12 files changed
+10008 -66

No files matched your search

+9211
View File
File diff suppressed because it is too large. Load diff
+2
View File
@@ -12,6 +12,8 @@
"dependencies": {
"@headlessui/react": "^2.1.3",
"@preact/signals-react": "^2.2.0",
"@techstark/opencv-js": "^4.10.0-release.1",
"@zip.js/zip.js": "^2.7.52",
"konva": "^9.3.14",
"react": "^18.3.1",
"react-dom": "^18.3.1",
+64 -8
View File
@@ -2,10 +2,15 @@ import Konva from "konva";
import { VideoNode } from "./Nodes/VideoNode";
import { uiAccess } from "~/signals";
import { uiEvents } from "~/signals";
import { RenderEngine } from "./RenderEngine";
import { RenderEngine } from "./RenderingPrimitives/RenderEngine";
import { toolbarMain } from "~/signals/uiAccess/toolbarMain";
import { ToolbarMainButtonNames } from "~/components/features/ToolbarMain/enum";
import {
WorkFunction,
ProgressData,
WorkQueue,
} from "./WorkerPrimitives/GenericWorker";
import { SharedWorkerClient } from "./WorkerPrimitives/SharedWorkerClient";
export class Engine {
private canvasReference: HTMLDivElement;
@@ -53,11 +58,6 @@ export class Engine {
this.addVideo(video.file);
});
uiEvents.toolbarMain.AI_STYLIZE.onClick(async (event) => {
// disable the buttons to debounce
await this.renderEngine.startProcessing();
});
// TODO: You may listen to all the image toolbar events here
uiEvents.imageToolbar.MOVE.onClick(() => {
console.log("move");
@@ -77,12 +77,13 @@ export class Engine {
"SLEEP",
`${sleepytstart.getMinutes()}:${sleepytstart.getSeconds()}`,
);
await this.sleep(5000);
await this.renderEngine.startProcessing();
const sleeptend = new Date();
console.log(
"DONE",
`${sleeptend.getMinutes()}:${sleeptend.getSeconds()}`,
);
uiAccess.toolbarMain.changeButtonState(
ToolbarMainButtonNames.AI_STYLIZE,
{ disabled: false },
@@ -100,6 +101,61 @@ export class Engine {
this.stage.draw(); // Redraw the canvas
}
// Sandbox is quickly a way to test your idea.
public sandbox() {
// How to use:
// const exampleWorkFunction: WorkFunction<number, number> = async (
// data: number,
// reportProgress: (progress: number) => void,
// ) => {
// // Simulate some asynchronous work with progress reporting
// for (let i = 0; i <= 100; i += 20) {
// await new Promise((resolve) => setTimeout(resolve, 200));
// reportProgress(i);
// }
// return data * 2;
// };
// const progressCallback = (progressData: ProgressData) => {
// console.log(
// `Job ${progressData.jobId} progress: ${progressData.progress}%`,
// );
// };
// const workQueue = new WorkQueue<number, number>(
// exampleWorkFunction,
// progressCallback,
// );
// workQueue.addWork(1, 10);
// workQueue.addWork(2, 20);
// workQueue.addWork(3, 30);
// const sharedWorker = new SharedWorker(
// "src\\KonvaApp\\WorkerPrimitives\\NumberSharedWorker.ts",
// {
// type: "module",
// },
// );
// // Get the port for communication
// let port = sharedWorker.port;
// port.start();
// // Set up the message event listener
// port.onmessage = this.onMessage.bind(this);
// port.postMessage({ jobID: 1, data: 2, isDoneStreaming: false });
const value = new SharedWorkerClient<number, number, number>(
"src\\KonvaApp\\WorkerPrimitives\\NumberSharedWorker.ts",
undefined,
);
value.send({ jobID: 1, data: 1, isDoneStreaming: false });
}
public onMessage(event: MessageEvent) {
console.log("Message From Shared Worker");
console.log(event);
}
public initializeStage(sceneToken: string) {
// load canvas that was originaly saved TODO Save manager for resharing.
@@ -1,11 +1,15 @@
import { VideoNode } from "./Nodes/VideoNode";
import { VideoNode } from "../Nodes/VideoNode";
import Konva from "konva";
import { RenderTask, WorkerEvent } from "./RenderTask";
// https://www.aiseesoft.com/resource/phone-aspect-ratio-screen-resolution.html#:~:text=16%3A9%20Aspect%20Ratio
export class RenderEngine {
private videoNodes: VideoNode[];
private offScreenCanvas: OffscreenCanvas;
private context: OffscreenCanvasRenderingContext2D | null;
private offscreenCanvasCache: OffscreenCanvas[] = [];
private isProcessing: boolean;
private frames: ImageBitmap[];
@@ -18,6 +22,10 @@ export class RenderEngine {
private positionX: number;
private positionY: number;
private canUseSharedWorker: boolean;
private sharedWorker: SharedWorker | undefined;
private port: MessagePort | undefined;
private maxFrames: number;
constructor(videoLayer: Konva.Layer, offScreenCanvas: OffscreenCanvas) {
this.videoNodes = [];
@@ -39,6 +47,7 @@ export class RenderEngine {
this.videoLayer = videoLayer;
this.port = undefined;
const captureCanvas = new Konva.Rect({
x: this.positionX,
y: this.positionY,
@@ -50,6 +59,8 @@ export class RenderEngine {
draggable: false,
});
this.maxFrames = 7 * 24;
// DEBUG ONLY
// const rectangle = new Konva.Rect({
// x: this.positionX,
@@ -63,8 +74,69 @@ export class RenderEngine {
// });
//this.videoLayer.add(rectangle);
this.videoLayer.add(captureCanvas);
// set furest back
// set back
captureCanvas.setZIndex(0);
this.sharedWorker = undefined;
this.canUseSharedWorker = false;
this.setupSharedWorker();
}
// Manage the worker
onMessage(event: MessageEvent) {
const message = event.data;
console.log("Main script received:", message);
// Send a response back to the worker
if (!this.port) {
return console.log("Undefined Worker");
}
}
sendMessage() {
if (!this.port) {
return console.log("Undefined Worker");
}
this.port.postMessage("Hello from Main Script");
}
async sendCanvasPayload(renderTask: RenderTask) {
if (!this.port) {
return console.log("Undefined Worker");
}
this.port.postMessage(renderTask);
}
setupSharedWorker() {
if (typeof SharedWorker !== "undefined") {
this.offscreenCanvasCache = [];
// canvas size should be defined here
for (let i = 0; i < this.maxFrames; i++) {
let offScreenCanvas = new OffscreenCanvas(this.width, this.height);
this.offscreenCanvasCache.push(offScreenCanvas);
}
console.log("Shared Workers are supported in this browser.");
// Debug chrome://inspect/#workers
// TODO ensure that the cors is solved for this.
this.sharedWorker = new SharedWorker(
"src\\KonvaApp\\RenderingPrimitives\\RenderSharedWorker.ts",
{
type: "module",
},
);
// Get the port for communication
this.port = this.sharedWorker.port;
this.port.start();
// Set up the message event listener
this.port.onmessage = this.onMessage.bind(this);
this.canUseSharedWorker = true;
} else {
console.log("Shared Workers are not supported in this browser.");
// Handle the lack of Shared Worker support (e.g., fallback to another solution)
this.canUseSharedWorker = false;
}
}
// This function uses a portion of the video layer to capture just the capture canvas.
@@ -197,16 +269,41 @@ export class RenderEngine {
} // End frame time
this.videoLayer.draw();
// SCOPES the capture for the context
// Correct size for the mobile canvas.
this.offScreenCanvas.width = this.width;
this.offScreenCanvas.height = this.height;
// use main thread
if (this.canUseSharedWorker === false) {
// SCOPES the capture for the context
// Correct size for the mobile canvas.
this.offScreenCanvas.width = this.width;
this.offScreenCanvas.height = this.height;
if (this.context) {
// This crops it starting at position X / Y where the mobile canvas is
// Then picks the height and width range
// then we draw it at 0,0,width and height of the canvas
this.context.drawImage(
this.videoLayer.canvas._canvas,
this.positionX,
this.positionY,
this.width,
this.height,
0,
0,
this.width,
this.height,
);
if (this.context) {
// This crops it starting at position X / Y where the mobile canvas is
// Then picks the height and width range
// then we draw it at 0,0,width and height of the canvas
this.context.drawImage(
const blob = await this.offScreenCanvas.convertToBlob({
quality: 1.0,
type: "image/jpeg",
});
await this.blobToFile(blob, `${j}`);
} // end of for each frame
} else {
console.log("Using Shared Worker");
const canvas = this.offscreenCanvasCache[j];
const context = canvas.getContext("2d");
// decode on the shared webworker.
context?.drawImage(
this.videoLayer.canvas._canvas,
this.positionX,
this.positionY,
@@ -217,56 +314,29 @@ export class RenderEngine {
this.width,
this.height,
);
const blob = await this.offScreenCanvas.convertToBlob({
quality: 1.0,
type: "image/jpeg",
this.sendCanvasPayload({
jobID: 1,
isDone: false,
event: WorkerEvent.DATA,
data: {
offscreenCanvas: canvas,
id: j,
},
});
await this.blobToFile(blob, `${j}`);
} // end of for each frame
if (j == this.maxFrames - 1) {
this.sendCanvasPayload({
jobID: 1,
isDone: true,
event: WorkerEvent.DATA,
data: undefined,
});
}
}
}
}
//DO NOT USE: This code is to test whether or not we can seek frames on each video nodes.
//To check the correctness of the seeking.
private async processFrame() {
if (!this.isProcessing) return;
for (let j = 0; j < this.videoNodes.length; j++) {
const videoNode = this.videoNodes[j];
videoNode.stop();
const numberOfFrames = videoNode.getNumberFrames();
for (let i = 0; i <= numberOfFrames; i++) {
const frameTime = this.calculateFrameTime(i, videoNode.fps);
console.log(i);
console.log(frameTime);
console.log(videoNode.duration);
if (frameTime < videoNode.duration) {
await videoNode.seek(frameTime);
this.offScreenCanvas.width = videoNode.node.getSize().width;
this.offScreenCanvas.height = videoNode.node.getSize().height;
if (this.context) {
this.context.drawImage(
videoNode.videoComponent,
0,
0,
this.offScreenCanvas.width,
this.offScreenCanvas.height,
);
console.log("Pushing");
this.frames.push(this.offScreenCanvas.transferToImageBitmap());
}
} // end of if
} // end of for.
}
console.log(this.frames);
}
private blobToFile(blob: Blob, index: string) {
try {
const link = document.createElement("a");
@@ -283,3 +353,43 @@ export class RenderEngine {
}
}
}
// //DO NOT USE: This code is to test whether or not we can seek frames on each video nodes.
// //To check the correctness of the seeking.
// private async processFrame() {
// if (!this.isProcessing) return;
// for (let j = 0; j < this.videoNodes.length; j++) {
// const videoNode = this.videoNodes[j];
// videoNode.stop();
// const numberOfFrames = videoNode.getNumberFrames();
// for (let i = 0; i <= numberOfFrames; i++) {
// const frameTime = this.calculateFrameTime(i, videoNode.fps);
// console.log(i);
// console.log(frameTime);
// console.log(videoNode.duration);
// if (frameTime < videoNode.duration) {
// await videoNode.seek(frameTime);
// this.offScreenCanvas.width = videoNode.node.getSize().width;
// this.offScreenCanvas.height = videoNode.node.getSize().height;
// if (this.context) {
// this.context.drawImage(
// videoNode.videoComponent,
// 0,
// 0,
// this.offScreenCanvas.width,
// this.offScreenCanvas.height,
// );
// console.log("Pushing");
// this.frames.push(this.offScreenCanvas.transferToImageBitmap());
// }
// } // end of if
// } // end of for.
// }
// console.log(this.frames);
// }
@@ -0,0 +1,132 @@
// RenderSharedWorker.ts
import { BlobReader, BlobWriter, ZipWriter } from "@zip.js/zip.js";
import { RenderTask } from "./RenderTask";
class FrameProcessorWorker {
public imageType: string;
private zipFileWriter: BlobWriter;
private zipWriter: ZipWriter<Blob>;
private decodeQueue: RenderTask[];
public isProcessing: boolean;
public final_length: number;
public item_count: number;
public port: MessagePort;
constructor(port: MessagePort) {
this.final_length = 0;
this.imageType = "image/jpeg";
// Zipping
this.decodeQueue = [];
this.zipFileWriter = new BlobWriter(this.imageType);
this.zipWriter = new ZipWriter(this.zipFileWriter);
this.isProcessing = false;
this.item_count = 0;
this.port = port;
}
async startZipProcessQueue(): Promise<void> {
if (this.decodeQueue.length > 0 && this.isProcessing == false) {
this.isProcessing = true;
const task = this.decodeQueue.shift();
if (task) {
const { offscreenCanvas, id } = task;
// Proceed with processing the task
try {
// Convert the canvas content to a Blob
const blob = await offscreenCanvas.convertToBlob({
quality: 1.0,
type: this.imageType,
});
// Send the Blob back to the main thread
console.log(`Zipping Frame ${id} to Zip`);
const name = String(id).padStart(5, "0"); // '0009'
await this.zipWriter.add(`${name}.jpg`, new BlobReader(blob));
this.item_count++;
} catch (error) {
console.error("Error processing image:", error);
self.postMessage({ error: (error as Error).message }); //
} finally {
this.isProcessing = false;
}
} else {
console.log("Queue is empty, no task to process.");
return;
}
}
// have this function loop
setTimeout(this.startZipProcessQueue.bind(this), 0);
}
async zipFile() {
// ensure that this queue is done before zipping
console.log(`Waiting to Finish Decoding: ${this.decodeQueue.length}`);
console.log(`Decoded Count: ${this.item_count}`);
console.log(`Final Length: ${this.final_length}`);
if (this.item_count === this.final_length) {
console.log("Zipping");
const result = await this.zipWriter.close();
self.postMessage({ result: result });
// reset the item count and final length
this.item_count = 0;
this.final_length = 0;
// Zipping
this.decodeQueue = [];
this.zipFileWriter = new BlobWriter(this.imageType);
this.zipWriter = new ZipWriter(this.zipFileWriter);
this.isProcessing = false;
return;
} else {
setTimeout(this.zipFile.bind(this), 0);
}
}
public enqueueTask(offscreenCanvas: OffscreenCanvas, id: number): void {
this.decodeQueue.push({ offscreenCanvas, id });
}
}
self.onconnect = (e: any) => {
const port = e.ports[0];
console.log("RenderSharedWorker Started");
const runner = new FrameProcessorWorker(port);
let start = false;
port.addEventListener("message", async (event: any) => {
//const { type, offscreenCanvas, frame, totalFrames, zip } = event.data;
console.log(event.data);
//console.log(`${type}: ${frame} ${zip}`);
if (start == false) {
start = true;
//runner.imageType = type;
await runner.startZipProcessQueue();
}
// if (zip) {
// await runner.zipFile();
// return;
// }
// runner.enqueueTask(offscreenCanvas, frame);
const workerResult = "Got Message for Startup";
port.postMessage(workerResult);
});
port.start(); // Required when using addEventListener. Otherwise called implicitly by onmessage setter.
};
@@ -0,0 +1,37 @@
import { bool } from "@techstark/opencv-js";
export enum WorkerEvent {
START = "start",
PROGRESS = "progress",
COMPLETE = "complete",
DATA = "data",
ERROR = "error",
}
export interface RenderTaskPayload {
offscreenCanvas: OffscreenCanvas;
id: number;
}
// RenderSharedWorker.ts
export interface RenderTask {
jobID: number;
isDone: bool;
event: WorkerEvent;
data: RenderTaskPayload | undefined;
}
export interface RenderTaskProgress {
isProcessing: boolean;
progress: number;
jobID: number;
event: WorkerEvent;
}
export interface RenderTaskResult {
event: Worker;
jobID: number;
data: {
mediaUrl: string;
};
}
@@ -0,0 +1,162 @@
// Item in and Result of the Promise, the response is a boolean
// that will dictate whether we should return the result to the main thread.
import { ResponseType, SharedWorkerResponse } from "./SharedWorkerBase";
// Does work with the data and returns progress and any progress data.
export type WorkFunction<I, R, P> = (
workItem: I,
reportProgress: (progress: number, data: P) => void,
) => Promise<[R, boolean]>;
// Work Item with Type T
export interface WorkItem<T> {
jobID: number;
data: T;
finishedStreamingInput: boolean;
}
// Work Result R
export interface WorkResult<R> {
jobID: number;
data: R;
}
// Streamed Progress with or without data
export interface ProgressData<P> {
jobID: number;
progress: number;
data: P | undefined;
}
export enum WorkerEvent {
START = "start",
PROGRESS = "progress",
COMPLETE = "complete",
DATA = "data",
ERROR = "error",
}
// Handles streaming work in a queue.
export class WorkQueue<I, R, P> {
private queue: WorkItem<I>[] = [];
private isProcessing: boolean = false;
// Streaming workers wait for a stop before they return a result used for frames.
// if not then it produces one result for one work item off the main thread.
public isStreamingWorker: boolean = true;
protected workFunction: WorkFunction<I, R, P> | undefined;
protected resultFunction: (result: WorkResult<R>) => void;
protected errorFunction: (error: SharedWorkerResponse<I, R>) => void;
protected progressCallback: (
progressData: ProgressData<P>,
) => void | undefined;
constructor(
workFunction: WorkFunction<I, R, P>,
progressCallback: (progressData: ProgressData<P>) => void,
resultFunction: (result: WorkResult<R>) => void,
errorFunction: (error: SharedWorkerResponse<I, R>) => void,
) {
this.workFunction = workFunction;
this.progressCallback = progressCallback;
this.resultFunction = resultFunction;
this.errorFunction = errorFunction;
}
async addWork(workItem: WorkItem<I>): Promise<void> {
this.queue.push(workItem);
}
private async processQueue(): Promise<void> {
while (this.queue.length > 0) {
const workItem = this.queue.shift();
if (workItem) {
try {
// The Data E here is used to stream back any extra images or anything.
const reportProgress = (progress: number, data: P) => {
this.progressCallback({
jobID: workItem.jobID,
progress: progress,
data: data, // could be url's some kind of data
});
};
if (!this.workFunction) {
console.log("Work Function is Null Generic Worker");
return;
}
const result = await this.workFunction(workItem.data, reportProgress);
// should report
if (result[1] === true) {
this.reportResult({ jobID: workItem.jobID, data: result[0] });
} else {
console.log("Work Function is not done producing result");
}
} catch (error) {
// catches errors from the worker and sends it back as an error
console.error(`Error processing job ${workItem.jobID}:`, error);
if (!this.errorFunction) {
console.log("Didn't Setup Error Function in Generic Worker");
return;
}
this.errorFunction({
jobID: workItem.jobID,
responseType: ResponseType.error,
data: error as string,
});
// proprogate to the error callback
}
}
}
console.log("Done Processing Waiting");
setTimeout(this.processQueue.bind(this), 500);
}
public start() {
if (this.isProcessing) {
console.log("Started Already");
return;
}
this.isProcessing = true;
this.processQueue();
}
public reportResult(result: WorkResult<R>): void {
console.log(`Job ${result.jobID} completed with result:`, result.data);
// You can replace this with any reporting mechanism you need
if (!this.resultFunction) {
console.log("Missing Result Function in Generic Worker");
return;
}
// passes off to sharedworkerbase to be overriden
this.resultFunction(result);
}
}
// How to use:
// const exampleWorkFunction: WorkFunction<number, number> = async (
// data: number,
// reportProgress: (progress: number) => void,
// ) => {
// // Simulate some asynchronous work with progress reporting
// for (let i = 0; i <= 100; i += 20) {
// await new Promise((resolve) => setTimeout(resolve, 200));
// reportProgress(i);
// }
// return data * 2;
// };
// const progressCallback = (progressData: ProgressData) => {
// console.log(`Job ${progressData.jobId} progress: ${progressData.progress}%`);
// };
// const workQueue = new WorkQueue<number, number>(
// exampleWorkFunction,
// progressCallback,
// );
// workQueue.addWork(1, 10);
// workQueue.addWork(2, 20);
// workQueue.addWork(3, 30);
@@ -0,0 +1,77 @@
import { ProgressData, WorkResult } from "./GenericWorker";
import {
SharedWorkerBase,
SharedWorkerRequest,
SharedWorkerResponse,
ResponseType,
} from "./SharedWorkerBase";
class SharedNumberWorker extends SharedWorkerBase<number, number, number> {
constructor(port: MessagePort) {
super(port);
this.setup(this.workFunction.bind(this), this.progressFunction.bind(this));
}
// Data here will be shipped off for progressive loading
async workFunction(
item: number,
reportProgress: (progress: number, data: number) => void,
): Promise<[number, boolean]> {
console.log(`Working Item ${item}`);
for (let i = 0; i < 1000; i++) {}
reportProgress(item, 100);
return [5, true];
}
progressFunction(progress: ProgressData<number>) {
console.log(
`Progress Function JobID:${progress.jobID} Data:${progress.data} Progress:${progress.progress}`,
);
// send out to node as a worker response
this.send({
jobID: progress.jobID,
responseType: ResponseType.progress,
data: progress.data,
});
}
reportResult(result: WorkResult<number>) {
console.log(`Result: jobID:${result.jobID} result:${result.data}`);
this.send({
jobID: result.jobID,
responseType: ResponseType.result,
data: result.data,
});
}
async receive(request: SharedWorkerRequest<number>) {
console.log("Received Request");
console.log(request);
this.submitWork({
jobID: request.jobID,
data: request.data,
isDoneStreaming: request.isDoneStreaming,
});
}
}
// This is a copy paste to create a worker now.
self.onconnect = (e: any) => {
const port = e.ports[0];
console.log("NumberSharedWorker Started");
let worker: SharedNumberWorker | undefined = undefined;
let started = false;
if (started === false) {
started = true;
worker = new SharedNumberWorker(port);
worker.start();
}
// Response For Start.
const workerResult = "Number Shared Worker Started";
port.postMessage(workerResult);
port.start(); // Required when using addEventListener. Otherwise called implicitly by onmessage setter.
};
@@ -0,0 +1,112 @@
import {
WorkFunction,
ProgressData,
WorkQueue,
WorkResult,
} from "./GenericWorker";
// Protocol
export enum ResponseType {
progress,
result,
error,
}
export enum RequestType {
cancel,
start,
startStream,
endStream,
}
// These are the containers to send data over the ports
// where R is response data
// where P is progress data
// where I is item data
export interface SharedWorkerResponse<R, P> {
jobID: number;
responseType: ResponseType;
data: R | P | string | undefined;
}
export interface SharedWorkerRequest<I> {
jobID: number;
data: I;
isDoneStreaming: boolean;
}
// Where I is the work item data
// Where R is the response data
// Where P is the progress data
export class SharedWorkerBase<I, R, P> {
private port: MessagePort;
protected workQueue: WorkQueue<I, R, P> | undefined;
constructor(port: MessagePort) {
this.port = port;
this.workQueue = undefined;
this.port.onmessage = this.receiveFromPort.bind(this);
}
setup(
workFunction: WorkFunction<I, R, P> | undefined,
progressCallback: (progressData: ProgressData<P>) => void,
) {
if (!workFunction) {
console.log("Work Function is Undefined.");
return;
}
this.workQueue = new WorkQueue<I, R, P>(
workFunction,
progressCallback,
this.reportResult.bind(this),
this.errorFunction.bind(this),
);
}
// Override
public errorFunction(error: SharedWorkerResponse<I, R>) {
console.log("Please Override Function");
}
// Call to start the worker
public async start() {
if (!this.workQueue) {
console.log("Work Queue Not Initialized");
return;
}
this.workQueue.start();
}
protected async send(data: SharedWorkerResponse<R, P>) {
this.port.postMessage(data);
}
// Must override in SharedWorker subclass
public async receive(request: SharedWorkerRequest<I>) {
// override this
console.log("receive Override in subclass");
}
public async receiveFromPort(event: any) {
console.log(`UnWrapping Event ${event}`);
this.receive(event.data);
}
public async submitWork(request: SharedWorkerRequest<I>) {
if (!this.workQueue) {
console.log("Missing work queue cannot submit work");
return;
}
this.workQueue.addWork({
jobID: request.jobID,
data: request.data,
finishedStreamingInput: request.isDoneStreaming,
});
}
// override this for subclass;
public reportResult(result: WorkResult<R>): void {
console.log("Must Override reportResult");
}
}
@@ -0,0 +1,30 @@
import { SharedWorkerRequest, SharedWorkerResponse } from "./SharedWorkerBase";
export class SharedWorkerClient<I, R, P> {
private port: MessagePort;
private sharedWorker: SharedWorker;
private messageReceived: (response: SharedWorkerResponse<R, P>) => void;
constructor(
workerPath: string,
messageReceived: (response: SharedWorkerResponse<R, P>) => void,
) {
// "src\\KonvaApp\\WorkerPrimitives\\NumberSharedWorker.ts"
this.sharedWorker = new SharedWorker(workerPath, {
type: "module",
});
this.port = this.sharedWorker.port;
// make sure to bind this for this from the invoker
this.port.onmessage = this.onMessage.bind(this);
this.port.start();
}
async onMessage(event: MessageEvent) {
// returns progress and returns result need to check error tomorrow then were good to go.
console.log(`GOTBACK ${JSON.stringify(event.data)}`);
}
async send(sharedWorkerRequest: SharedWorkerRequest<I>) {
this.port.postMessage(sharedWorkerRequest);
}
}
+4 -1
View File
@@ -3,6 +3,9 @@ import { Engine } from "./Engine";
export { Engine as EngineType };
export const KonvaApp = (element: HTMLDivElement) => {
const engine = new Engine(element);
engine.initializeStage("");
engine.sandbox();
//engine.initializeStage("");
return engine;
};
+10
View File
@@ -936,6 +936,11 @@
resolved "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.10.5.tgz"
integrity sha512-WlJp8CipNzBz8Q4g159uMfswYHZ8p1+xJwLBdnbBTcSSu0zVoY6i27Suw5J0Y88YBnJ9jHR8jJMH/qSuZWWfNw==
"@techstark/opencv-js@^4.10.0-release.1":
version "4.10.0-release.1"
resolved "https://registry.npmjs.org/@techstark/opencv-js/-/opencv-js-4.10.0-release.1.tgz"
integrity sha512-S4XELidRiQeA0q1s9VQLo540wCxUo24r1O4C+LqZ6llX+sPCXvZCPv3Ice8dEIr0uavyZ8YZeKXSBdDgMXSXjw==
"@tufjs/canonical-json@2.0.0":
version "2.0.0"
resolved "https://registry.npmjs.org/@tufjs/canonical-json/-/canonical-json-2.0.0.tgz"
@@ -1109,6 +1114,11 @@
"@types/babel__core" "^7.20.5"
react-refresh "^0.14.2"
"@zip.js/zip.js@^2.7.52":
version "2.7.52"
resolved "https://registry.npmjs.org/@zip.js/zip.js/-/zip.js-2.7.52.tgz"
integrity sha512-+5g7FQswvrCHwYKNMd/KFxZSObctLSsQOgqBSi0LzwHo3li9Eh1w5cF5ndjQw9Zbr3ajVnd2+XyiX85gAetx1Q==
abbrev@^2.0.0:
version "2.0.0"
resolved "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz"