UNPKG

@woosh/meep-engine

Version:

Pure JavaScript game engine. Fully featured and production ready.

193 lines (158 loc) 6.06 kB
import { convertPathToURL } from "../../../engine/network/convertPathToURL.js"; import { codeToURL } from "../../codegen/codeToURL.js"; import LineBuilder from "../../codegen/LineBuilder.js"; import WorkerProxy from "./WorkerProxy.js"; const RxMatchFunctionName = /(function\s*)([a-zA-Z0-9_]+)?(\s*\([^\]]*\)\s*\{.*)/g; class WorkerBuilder { /** * * @type {string[]} */ imports = []; methods = {}; /** * * @type {{f:function, name:string}[]} */ functions = []; preamble = new LineBuilder(); /** * * @param {string} code */ addCode(code) { this.preamble.add(code); } /** * * @param {string} name * @param {function|string} method */ addMethod(name, method) { this.methods[name] = method; } /** * NOTE: take care when using minifiers as they may mangle function names * @deprecated * @param name * @param {function} f */ importFunction(name, f) { this.functions.push({ f, name }); } /** * * @param {string} path */ importScript(path) { const qualified_path = convertPathToURL(path); this.imports.push(qualified_path); } /** * * @returns {WorkerProxy} */ build() { const codeLines = []; codeLines.push('var globalScope = globalThis;'); //handle imports this.imports.forEach(function (url) { codeLines.push("globalScope.importScripts('" + url + "');"); }); codeLines.push(this.preamble.build()); //handle functions this.functions.forEach(function (spec) { const f = spec.f; const function_string = f.toString(); // We rewrite the function to explicitly set function name. This is needed to work around various code compressors such as Uglify or Terser that mange original function names or remove them altogether const renamed_function_string = function_string.replace(RxMatchFunctionName, `$1${spec.name}$3`); codeLines.push(renamed_function_string); }); //api hash codeLines.push('var api = {};'); for (let apiName in this.methods) { if (this.methods.hasOwnProperty(apiName)) { const method = this.methods[apiName]; const type_of_method = typeof method; let method_code; if (type_of_method === 'function') { method_code = method.toString(); } else if (type_of_method === 'string') { method_code = method; } else { throw new Error(`Unsupported method type '${type_of_method}'`); } codeLines.push("api['" + apiName + "'] = " + method_code + ";"); } } //api handler Array.prototype.push.apply(codeLines, [ //language=js `function extractTransferables(obj, result) { if (typeof obj !== "object") { return; //not an object, skip } else if (obj instanceof ArrayBuffer) { result.push(obj); } else if (obj.buffer instanceof ArrayBuffer) { result.push(obj.buffer); } else if (typeof ImageBitmap !== "undefined" && obj instanceof ImageBitmap) { result.push(obj); } else { for (var i in obj) { if (obj.hasOwnProperty(i)) { extractTransferables(obj[i], result); } } } }`, //language=js `globalScope.onmessage = function (event) { const eventData = event.data; const requestId = eventData.id const methodName = eventData.methodName; const parameters = eventData.parameters; const method = api[methodName]; function sendResult(result) { const transferables = []; extractTransferables(result, transferables); globalScope.postMessage({ methodName: methodName, id: requestId, result: result }, transferables); } function sendError(error) { let stack = ""; try { stack = error.stack.split("\\n") } catch (e) { } globalScope.postMessage({ methodName: methodName, id: requestId, error: { message: error.message, stack: stack } }); } if (method === undefined) { sendError(new Error("API named \'" + methodName + "\' was not found.")); } else { try { const method_result = method.apply(null, parameters); if(typeof method_result.then === "function"){ // thenable method_result.then(sendResult, sendError); }else{ // straight value sendResult(method_result); } } catch (e) { sendError(e); } } };` ]); const code = codeLines.join("\n"); const workerURL = codeToURL(code); return new WorkerProxy(workerURL, this.methods); } }; export default WorkerBuilder;