UNPKG

playcanvas

Version:

Open-source WebGL/WebGPU 3D engine for the web

245 lines (244 loc) 9.01 kB
var __defProp = Object.defineProperty; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value); import { Debug } from "../../core/debug.js"; import { EventHandler } from "../../core/event-handler.js"; import { reservedScriptNames } from "./constants.js"; import { getScriptRegistryName } from "./script.js"; class ScriptRegistry extends EventHandler { /** * Create a new ScriptRegistry instance. * * @param {AppBase} app - Application to attach registry to. */ constructor(app) { super(); /** * A Map of script names to script classes. A Map is used (rather than a plain object) so that * script names which collide with `Object.prototype` members - e.g. `hasOwnProperty`, * `toString`, `__proto__` - are stored and looked up safely. * * @type {Map<string, typeof ScriptType>} * @private */ __publicField(this, "_scripts", /* @__PURE__ */ new Map()); /** * @type {typeof ScriptType[]} * @private */ __publicField(this, "_list", []); /** * A Map of script names to attribute schemas. * * @type {Map<string, AttributeSchema>} * @private */ __publicField(this, "_scriptSchemas", /* @__PURE__ */ new Map()); this.app = app; } destroy() { this.app = null; this.off(); } /** * Registers a schema against a script instance. * * @param {string} id - The key to use to store the schema * @param {AttributeSchema} schema - An schema definition for the script */ addSchema(id, schema) { if (!schema) return; this._scriptSchemas.set(id, schema); } /** * Returns a schema for a given script name. * * @param {string} id - The key to store the schema under * @returns {AttributeSchema | undefined} - The schema stored under the key */ getSchema(id) { return this._scriptSchemas.get(id); } /** * Add a script to the registry, keyed by its name. The name is taken from the script's static * `scriptName` property (for {@link Script} classes), or assigned by {@link createScript} / * {@link registerScript}. Note: when {@link createScript} or {@link registerScript} is called, * the script is added to the registry automatically, so calling this method directly is only * required when registering a {@link Script} class manually (e.g. in an engine-only project). * * If a script with the same name already exists in the registry, and the new script has a * `swap` method defined, it will perform code hot swapping automatically in an async manner. * * @param {typeof Script | typeof ScriptType} script - The script class to add. Must have a * resolvable name (a static `scriptName`, an assigned `__name`, or an inferable class name). * @returns {boolean} True if the script was added for the first time. False if a script with * the same name already exists, or if the script has no resolvable name. * @example * var PlayerController = pc.createScript('playerController'); * // playerController Script Type will be added to pc.ScriptRegistry automatically * console.log(app.scripts.has('playerController')); // outputs true * @example * // engine-only: register an ESM Script class manually * class Rotator extends pc.Script { * static scriptName = 'rotator'; * } * app.scripts.add(Rotator); * console.log(app.scripts.has('rotator')); // outputs true */ add(script) { const scriptName = getScriptRegistryName(script); if (!scriptName) { Debug.error(`script class '${script?.name ?? script}' has no name and cannot be added to the script registry.`); return false; } if (reservedScriptNames.has(scriptName)) { Debug.error(`script name '${scriptName}' is reserved and cannot be added to the script registry.`); return false; } script.__name = scriptName; if (this._scripts.has(scriptName)) { setTimeout(() => { if (script.prototype.swap) { const old = this._scripts.get(scriptName); const ind = this._list.indexOf(old); this._list[ind] = script; this._scripts.set(scriptName, script); this.fire("swap", scriptName, script); this.fire(`swap:${scriptName}`, script); } else { console.warn(`script registry already has '${scriptName}' script, define 'swap' method for new script type to enable code hot swapping`); } }); return false; } this._scripts.set(scriptName, script); this._list.push(script); this.fire("add", scriptName, script); this.fire(`add:${scriptName}`, script); setTimeout(() => { if (!this._scripts.has(scriptName)) { return; } if (!this.app || !this.app.systems || !this.app.systems.script) { return; } const components = this.app.systems.script._components; let attributes; const scriptInstances = []; const scriptInstancesInitialized = []; for (components.loopIndex = 0; components.loopIndex < components.length; components.loopIndex++) { const component = components.items[components.loopIndex]; if (component._scriptsIndex[scriptName] && component._scriptsIndex[scriptName].awaiting) { if (component._scriptsData && component._scriptsData[scriptName]) { attributes = component._scriptsData[scriptName].attributes; } const scriptInstance = component.create(scriptName, { preloading: true, ind: component._scriptsIndex[scriptName].ind, attributes }); if (scriptInstance) { scriptInstances.push(scriptInstance); } for (const script2 of component.scripts) { component.initializeAttributes(script2); } } } for (let i = 0; i < scriptInstances.length; i++) { if (scriptInstances[i].enabled) { scriptInstances[i]._initialized = true; scriptInstancesInitialized.push(scriptInstances[i]); if (scriptInstances[i].initialize) { scriptInstances[i].initialize(); } } } for (let i = 0; i < scriptInstancesInitialized.length; i++) { if (!scriptInstancesInitialized[i].enabled || scriptInstancesInitialized[i]._postInitialized) { continue; } scriptInstancesInitialized[i]._postInitialized = true; if (scriptInstancesInitialized[i].postInitialize) { scriptInstancesInitialized[i].postInitialize(); } } }); return true; } /** * Remove {@link ScriptType}. * * @param {string|typeof ScriptType} nameOrType - The name or type * of {@link ScriptType}. * @returns {boolean} True if removed or False if already not in registry. * @example * app.scripts.remove('playerController'); */ remove(nameOrType) { let scriptType = nameOrType; let scriptName = nameOrType; if (typeof scriptName !== "string") { scriptName = scriptType.__name; } else { scriptType = this.get(scriptName); } if (this.get(scriptName) !== scriptType) { return false; } this._scripts.delete(scriptName); const ind = this._list.indexOf(scriptType); this._list.splice(ind, 1); this.fire("remove", scriptName, scriptType); this.fire(`remove:${scriptName}`, scriptType); return true; } /** * Get {@link ScriptType} by name. * * @param {string} name - Name of a {@link ScriptType}. * @returns {typeof ScriptType} The Script Type if it exists in the * registry or null otherwise. * @example * var PlayerController = app.scripts.get('playerController'); */ get(name) { return this._scripts.get(name) || null; } /** * Check if a {@link ScriptType} with the specified name is in the registry. * * @param {string|typeof ScriptType} nameOrType - The name or type * of {@link ScriptType}. * @returns {boolean} True if {@link ScriptType} is in registry. * @example * if (app.scripts.has('playerController')) { * // playerController is in pc.ScriptRegistry * } */ has(nameOrType) { if (typeof nameOrType === "string") { return this._scripts.has(nameOrType); } if (!nameOrType) return false; const scriptName = nameOrType.__name; return this._scripts.get(scriptName) === nameOrType; } /** * Get list of all {@link ScriptType}s from registry. * * @returns {Array<typeof ScriptType>} list of all {@link ScriptType}s * in registry. * @example * // logs array of all Script Type names available in registry * console.log(app.scripts.list().map(function (o) { * return o.name; * })); */ list() { return this._list; } } export { ScriptRegistry };