UNPKG

playcanvas

Version:

PlayCanvas WebGL game engine

188 lines (186 loc) 7.04 kB
// wrapper function that caches the func result on first invocation and // then subsequently returns the cached value const cachedResult = (func)=>{ const uninitToken = {}; let result = uninitToken; return ()=>{ if (result === uninitToken) { result = func(); } return result; }; }; class Impl { static{ this.modules = {}; } static{ // returns true if the running host supports wasm modules (all browsers except IE) this.wasmSupported = cachedResult(()=>{ try { if (typeof WebAssembly === 'object' && typeof WebAssembly.instantiate === 'function') { const module = new WebAssembly.Module(Uint8Array.of(0x0, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00)); if (module instanceof WebAssembly.Module) { return new WebAssembly.Instance(module) instanceof WebAssembly.Instance; } } } catch (e) {} return false; }); } // load a script static loadScript(url, callback) { const s = document.createElement("script"); s.setAttribute('src', url); s.onload = ()=>{ callback(null); }; s.onerror = ()=>{ callback(`Failed to load script='${url}'`); }; document.body.appendChild(s); } // load a wasm module static loadWasm(moduleName, config, callback) { const loadUrl = Impl.wasmSupported() && config.glueUrl && config.wasmUrl ? config.glueUrl : config.fallbackUrl; if (loadUrl) { Impl.loadScript(loadUrl, (err)=>{ if (err) { callback(err, null); } else { const module = window[moduleName]; // clear the module from the global window since we used to store global instance here window[moduleName] = undefined; // instantiate the module module({ locateFile: ()=>config.wasmUrl, onAbort: ()=>{ callback('wasm module aborted.'); } }).then((instance)=>{ callback(null, instance); }); } }); } else { callback('No supported wasm modules found.', null); } } // get state object for the named module static getModule(name) { if (!Impl.modules.hasOwnProperty(name)) { Impl.modules[name] = { config: null, initializing: false, instance: null, callbacks: [] }; } return Impl.modules[name]; } static initialize(moduleName, module) { if (module.initializing) { return; } const config = module.config; if (config.glueUrl || config.wasmUrl || config.fallbackUrl) { module.initializing = true; Impl.loadWasm(moduleName, config, (err, instance)=>{ if (err) { if (config.errorHandler) { config.errorHandler(err); } else { console.error(`failed to initialize module=${moduleName} error=${err}`); } } else { module.instance = instance; module.callbacks.forEach((callback)=>{ callback(instance); }); } }); } } } /** * @callback ModuleErrorCallback * Callback used by {@link WasmModule.setConfig}. * @param {string} error - If the instance fails to load this will contain a description of the error. * @returns {void} */ /** * @callback ModuleInstanceCallback * Callback used by {@link WasmModule.getInstance}. * @param {any} moduleInstance - The module instance. * @returns {void} */ /** * A pure static utility class which supports immediate and lazy loading of * [WebAssembly](https://developer.mozilla.org/en-US/docs/WebAssembly) modules. Note that you can * load WebAssembly modules even before instantiating your {@link AppBase} instance. * * This class is generally only needed if you are developing against the Engine directly. Editor * projects automatically load WebAssembly modules included in the project's assets. * * Do not use this class to load the Basis WebAssembly module. Instead, please refer to * {@link basisInitialize}. * * @example * // Load the Ammo.js physics engine * pc.WasmModule.setConfig('Ammo', { * glueUrl: `ammo.wasm.js`, * wasmUrl: `ammo.wasm.wasm`, * fallbackUrl: `ammo.js` * }); * await new Promise((resolve) => { * pc.WasmModule.getInstance('Ammo', () => resolve()); * }); */ class WasmModule { /** * Set a wasm module's configuration. * * @param {string} moduleName - Name of the module. * @param {object} [config] - The configuration object. * @param {string} [config.glueUrl] - URL of glue script. * @param {string} [config.wasmUrl] - URL of the wasm script. * @param {string} [config.fallbackUrl] - URL of the fallback script to use when wasm modules * aren't supported. * @param {number} [config.numWorkers] - For modules running on worker threads, the number of * threads to use. Default value is based on module implementation. * @param {ModuleErrorCallback} [config.errorHandler] - Function to be called if the module fails * to download. */ static setConfig(moduleName, config) { const module = Impl.getModule(moduleName); module.config = config; if (module.callbacks.length > 0) { // start module initialize immediately since there are pending getInstance requests Impl.initialize(moduleName, module); } } /** * Get a wasm module's configuration. * * @param {string} moduleName - Name of the module. * @returns {object | undefined} The previously set configuration. */ static getConfig(moduleName) { return Impl.modules?.[moduleName]?.config; } /** * Get a wasm module instance. The instance will be created if necessary and returned * in the second parameter to callback. * * @param {string} moduleName - Name of the module. * @param {ModuleInstanceCallback} callback - The function called when the instance is * available. */ static getInstance(moduleName, callback) { const module = Impl.getModule(moduleName); if (module.instance) { callback(module.instance); } else { module.callbacks.push(callback); if (module.config) { // config has been provided, kick off module initialize Impl.initialize(moduleName, module); } } } } export { WasmModule };