TeaWeb/web/app/workers/codec/CodecWorker.ts

183 lines
6.1 KiB
TypeScript

import {CodecType} from "tc-backend/web/codec/Codec";
import {
CWMessageCommand,
CWCommand,
CWMessage,
CWMessageResponse,
CWMessageErrorResponse, CWCommandResponseType
} from "tc-backend/web/codec/CodecWorkerMessages";
const prefix = "[CodecWorker] ";
export interface CodecWorker {
name();
initialise?() : string;
deinitialise();
decode(buffer: Uint8Array, responseBuffer: (length: number) => Uint8Array) : number | string;
encode(buffer: Uint8Array, responseBuffer: (length: number) => Uint8Array) : number | string;
reset();
}
let supported_types = {};
export function register_codec(type: CodecType, allocator: (options?: any) => Promise<CodecWorker>) {
supported_types[type] = allocator;
}
let initialize_callback: () => Promise<true | string>;
export function set_initialize_callback(callback: () => Promise<true | string>) {
initialize_callback = callback;
}
export let codec_instance: CodecWorker;
let globally_initialized = false;
let global_initialize_result;
let commandTransferableResponse: Transferable[];
let messageHandlers: { [T in keyof CWCommand]: (message: CWCommand[T]) => Promise<CWCommandResponseType<T>> } = {} as any;
function registerCommandHandler<T extends keyof CWCommand>(command: T, callback: (message: CWCommand[T]) => Promise<CWCommandResponseType<T>>) {
messageHandlers[command as any] = callback;
}
const handleOwnerMessage = (e: MessageEvent) => {
const timestampReceived = Date.now();
const message = e.data as CWMessage;
if(message.type === "error" || message.type === "success") {
console.warn("%sReceived a command response within the worker. We're not sending any commands so this should not happen!", prefix);
return;
} else if(message.type === "notify") {
console.warn("%sReceived a notify within the worker. This should not happen!", prefix);
return;
} else if(message.type === "command") {
const command = message as CWMessageCommand;
const sendExecuteError = error => {
let errorMessage;
if(typeof error === "string") {
errorMessage = error;
} else if(error instanceof Error) {
console.error("%sMessage handle error: %o", prefix, error);
errorMessage = error.message;
} else {
console.error("%sMessage handle error: %o", prefix, error);
errorMessage = "lookup the console";
}
postMessage({
type: "error",
error: errorMessage,
timestampReceived: timestampReceived,
timestampSend: Date.now(),
token: command.token
} as CWMessageErrorResponse, undefined, commandTransferableResponse);
};
const sendExecuteResult = result => {
postMessage({
type: "success",
response: result,
timestampReceived: timestampReceived,
timestampSend: Date.now(),
token: command.token
} as CWMessageResponse, undefined);
};
const handler = messageHandlers[message.command as any];
if(!handler) {
sendExecuteError("unknown command");
return;
}
handler(command.payload).then(sendExecuteResult).catch(sendExecuteError);
}
};
addEventListener("message", handleOwnerMessage);
/* command handlers */
registerCommandHandler("global-initialize", async () => {
const init_result = globally_initialized ? global_initialize_result : await initialize_callback();
globally_initialized = true;
if(typeof init_result === "string")
throw init_result;
});
registerCommandHandler("initialise", async data => {
console.log(prefix + "Initialize for codec %s", CodecType[data.type as CodecType]);
if(!supported_types[data.type])
throw "type unsupported";
try {
codec_instance = await supported_types[data.type](data);
} catch(ex) {
console.error("%sFailed to allocate codec: %o", prefix, ex);
throw typeof ex === "string" ? ex : "failed to allocate codec";
}
const error = codec_instance.initialise();
if(error)
throw error;
});
registerCommandHandler("reset", async () => {
codec_instance.reset();
});
registerCommandHandler("finalize", async () => {
/* memory will be cleaned up by its own */
});
let responseBuffer: Uint8Array;
const popResponseBuffer = () => { const temp = responseBuffer; responseBuffer = undefined; return temp; }
registerCommandHandler("decode-payload", async data => {
if(!codec_instance)
throw "codec not initialized/initialize failed";
const byteLength = codec_instance.decode(new Uint8Array(data.buffer, data.byteOffset, data.byteLength), length => {
if(length > data.maxByteLength)
throw "source buffer too small to hold the result";
//return responseBuffer = new Uint8Array(length);
return responseBuffer = new Uint8Array(data.buffer, 0, data.maxByteLength);
});
const buffer = popResponseBuffer();
if(typeof byteLength === "string") {
throw byteLength;
}
commandTransferableResponse = [buffer.buffer];
return {
buffer: buffer.buffer,
byteLength: byteLength,
byteOffset: 0,
};
});
registerCommandHandler("encode-payload", async data => {
if(!codec_instance)
throw "codec not initialized/initialize failed";
const byteLength = codec_instance.encode(new Uint8Array(data.buffer, data.byteOffset, data.byteLength), length => {
if(length > data.maxByteLength)
throw "source buffer too small to hold the result";
//return responseBuffer = new Uint8Array(length);
return responseBuffer = new Uint8Array(data.buffer, 0, data.maxByteLength);
});
const buffer = popResponseBuffer();
if(typeof byteLength === "string") {
throw byteLength;
}
commandTransferableResponse = [buffer.buffer];
return {
buffer: buffer.buffer,
byteLength: byteLength,
byteOffset: 0
};
});