playcanvas
Version:
Open-source WebGL/WebGPU 3D engine for the web
291 lines (290 loc) • 9.82 kB
JavaScript
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 { SCRIPT_INITIALIZE, SCRIPT_POST_INITIALIZE } from "./constants.js";
class Script extends EventHandler {
/**
* Create a new Script instance.
*
* @param {object} args - The input arguments object.
* @param {AppBase} args.app - The {@link AppBase} that is running the script.
* @param {Entity} args.entity - The {@link Entity} that the script is attached to.
*/
constructor(args) {
super();
/**
* The {@link AppBase} that the instance of this script belongs to.
*
* @type {AppBase}
*/
__publicField(this, "app");
/**
* The {@link Entity} that the instance of this script belongs to.
*
* @type {Entity}
*/
__publicField(this, "entity");
/** @private */
__publicField(this, "_enabled");
/** @private */
__publicField(this, "_enabledOld");
/** @private */
__publicField(this, "_initialized");
/** @private */
__publicField(this, "_postInitialized");
/** @private */
__publicField(this, "__destroyed");
/** @private */
__publicField(this, "__scriptType");
/**
* The order in the script component that the methods of this script instance will run
* relative to other script instances in the component.
*
* @type {number}
* @private
*/
__publicField(this, "__executionOrder");
this.initScript(args);
}
/**
* Sets the enabled state of the script instance. When disabled, no update methods will be
* called on each tick. `initialize` and `postInitialize` methods will run once when the script
* instance is next in the `enabled` state during an app tick.
*
* @type {boolean}
*/
set enabled(value) {
this._enabled = !!value;
if (this.enabled === this._enabledOld) return;
this._enabledOld = this.enabled;
this.fire(this.enabled ? "enable" : "disable");
this.fire("state", this.enabled);
if (!this._initialized && this.enabled) {
this._initialized = true;
this.fire("preInitialize");
if (this.initialize) {
this.entity.script._scriptMethod(this, SCRIPT_INITIALIZE);
}
}
if (this._initialized && !this._postInitialized && this.enabled && !this.entity.script._beingEnabled) {
this._postInitialized = true;
if (this.postInitialize) {
this.entity.script._scriptMethod(this, SCRIPT_POST_INITIALIZE);
}
}
}
/**
* Gets the running state of the script instance. Returns true when the script instance is
* enabled and its owning {@link Entity} (and all ancestors) and {@link ScriptComponent} are
* also enabled; otherwise false.
*
* @type {boolean}
*/
get enabled() {
return this._enabled && !this._destroyed && this.entity.script.enabled && this.entity.enabled;
}
/**
* @typedef {object} ScriptInitializationArgs
* @property {boolean} [enabled] - True if the script instance is in running state.
* @property {AppBase} app - The {@link AppBase} that is running the script.
* @property {Entity} entity - The {@link Entity} that the script is attached to.
*/
/**
* @param {ScriptInitializationArgs} args - The input arguments object.
* @protected
*/
initScript(args) {
const script = this.constructor;
Debug.assert(args && args.app && args.entity, `script [${script.__name}] has missing arguments in constructor`);
this.app = args.app;
this.entity = args.entity;
this._enabled = typeof args.enabled === "boolean" ? args.enabled : true;
this._enabledOld = this.enabled;
this.__destroyed = false;
this.__scriptType = script;
this.__executionOrder = -1;
}
/**
* Sets the unique name of the script.
*
* @type {string|null}
*/
static set scriptName(value) {
this.__name = value;
}
/**
* Gets the unique name of the script.
*
* @type {string|null}
*/
static get scriptName() {
return this.__name;
}
/**
* @function
* @name Script#[initialize]
* @description Called when script is about to run for the first time.
*/
/**
* @function
* @name Script#[postInitialize]
* @description Called after all initialize methods are executed in the same tick or enabling chain of actions.
*/
/**
* @function
* @name Script#[update]
* @description Called for enabled (running state) scripts on each tick.
* @param {number} dt - The delta time in seconds since the last frame.
*/
/**
* @function
* @name Script#[postUpdate]
* @description Called for enabled (running state) scripts on each tick, after update.
* @param {number} dt - The delta time in seconds since the last frame.
*/
/**
* @function
* @name Script#[swap]
* @description Called when a Script that already exists in the registry gets redefined. If the
* new Script has a `swap` method, then it will be executed to perform hot-reload at runtime.
* @param {Script} old - Old instance of the scriptType to copy data to the new instance.
*/
}
/**
* Fired when a script instance becomes enabled.
*
* @event
* @example
* export class PlayerController extends Script {
* static scriptName = 'playerController';
* initialize() {
* this.on('enable', () => {
* // Script Instance is now enabled
* });
* }
* };
*/
__publicField(Script, "EVENT_ENABLE", "enable");
/**
* Fired when a script instance becomes disabled.
*
* @event
* @example
* export class PlayerController extends Script {
* static scriptName = 'playerController';
* initialize() {
* this.on('disable', () => {
* // Script Instance is now disabled
* });
* }
* };
*/
__publicField(Script, "EVENT_DISABLE", "disable");
/**
* Fired when a script instance changes state to enabled or disabled. The handler is passed a
* boolean parameter that states whether the script instance is now enabled or disabled.
*
* @event
* @example
* export class PlayerController extends Script {
* static scriptName = 'playerController';
* initialize() {
* this.on('state', (enabled) => {
* console.log(`Script Instance is now ${enabled ? 'enabled' : 'disabled'}`);
* });
* }
* };
*/
__publicField(Script, "EVENT_STATE", "state");
/**
* Fired when a script instance is destroyed and removed from component.
*
* @event
* @example
* export class PlayerController extends Script {
* static scriptName = 'playerController';
* initialize() {
* this.on('destroy', () => {
* // no longer part of the entity
* // this is a good place to clean up allocated resources used by the script
* });
* }
* };
*/
__publicField(Script, "EVENT_DESTROY", "destroy");
/**
* Fired when script attributes have changed. This event is available in two forms. They are as
* follows:
*
* 1. `attr` - Fired for any attribute change. The handler is passed the name of the attribute
* that changed, the value of the attribute before the change and the value of the attribute
* after the change.
* 2. `attr:[name]` - Fired for a specific attribute change. The handler is passed the value of
* the attribute before the change and the value of the attribute after the change.
*
* @event
* @example
* export class PlayerController extends Script {
* static scriptName = 'playerController';
* initialize() {
* this.on('attr', (name, newValue, oldValue) => {
* console.log(`Attribute '${name}' changed from '${oldValue}' to '${newValue}'`);
* });
* }
* };
* @example
* export class PlayerController extends Script {
* static scriptName = 'playerController';
* initialize() {
* this.on('attr:speed', (newValue, oldValue) => {
* console.log(`Attribute 'speed' changed from '${oldValue}' to '${newValue}'`);
* });
* }
* };
*/
__publicField(Script, "EVENT_ATTR", "attr");
/**
* Fired when a script instance had an exception. The script instance will be automatically
* disabled. The handler is passed an Error object containing the details of the
* exception and the name of the method that threw the exception.
*
* @event
* @example
* export class PlayerController extends Script {
* static scriptName = 'playerController';
* initialize() {
* this.on('error', (err, method) => {
* // caught an exception
* console.log(err.stack);
* });
* }
* };
*/
__publicField(Script, "EVENT_ERROR", "error");
/**
* @type {string|null}
* @private
*/
__publicField(Script, "__name", null);
// Will be assigned when calling createScript or registerScript.
/**
* @param {*} constructorFn - The constructor function of the script type.
* @returns {string} The script name.
* @private
*/
__publicField(Script, "__getScriptName", getScriptName);
const funcNameRegex = /^\s*function(?:\s|\s*\/\*.*\*\/\s*)+([^(\s\/]*)\s*/;
function getScriptName(constructorFn) {
if (typeof constructorFn !== "function") return void 0;
if (constructorFn.scriptName) return constructorFn.scriptName;
if ("name" in Function.prototype) return constructorFn.name;
if (constructorFn === Function || constructorFn === Function.prototype.constructor) return "Function";
const match = `${constructorFn}`.match(funcNameRegex);
return match ? match[1] : void 0;
}
export {
Script,
getScriptName
};