Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Script alias #6237

Merged
merged 13 commits into from
Apr 15, 2024
44 changes: 22 additions & 22 deletions src/framework/components/script/component.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { Entity } from '../../entity.js';
*/
class ScriptComponent extends Component {
/**
* Fired when a {@link ScriptType} instance is created and attached to the script component.
* Fired when a {@link Script} instance is created and attached to the script component.
* This event is available in two forms. They are as follows:
*
* 1. `create` - Fired when a script instance is created. The name of the script type and the
Expand All @@ -40,7 +40,7 @@ class ScriptComponent extends Component {
static EVENT_CREATE = 'create';

/**
* Fired when a {@link ScriptType} instance is destroyed and removed from the script component.
* Fired when a {@link Script} instance is destroyed and removed from the script component.
* This event is available in two forms. They are as follows:
*
* 1. `destroy` - Fired when a script instance is destroyed. The name of the script type and
Expand Down Expand Up @@ -109,7 +109,7 @@ class ScriptComponent extends Component {
static EVENT_STATE = 'state';

/**
* Fired when the index of a {@link ScriptType} instance is changed in the script component.
* Fired when the index of a {@link Script} instance is changed in the script component.
* This event is available in two forms. They are as follows:
*
* 1. `move` - Fired when a script instance is moved. The name of the script type, the script
Expand All @@ -130,7 +130,7 @@ class ScriptComponent extends Component {
static EVENT_MOVE = 'move';

/**
* Fired when a {@link ScriptType} instance had an exception. The handler is passed the script
* Fired when a {@link Script} instance had an exception. The handler is passed the script
* instance, the exception and the method name that the exception originated from.
*
* @event
Expand All @@ -154,7 +154,7 @@ class ScriptComponent extends Component {
/**
* Holds all script instances for this component.
*
* @type {import('../../script/script-type.js').ScriptType[]}
* @type {import('../../script/script.js').Script[]}
* @private
*/
this._scripts = [];
Expand Down Expand Up @@ -191,7 +191,7 @@ class ScriptComponent extends Component {
* An array of all script instances attached to an entity. This array is read-only and should
* not be modified by developer.
*
* @type {import('../../script/script-type.js').ScriptType[]}
* @type {import('../../script/script.js').Script[]}
*/
set scripts(value) {
this._scriptsData = value;
Expand Down Expand Up @@ -546,8 +546,8 @@ class ScriptComponent extends Component {
/**
* Detect if script is attached to an entity.
*
* @param {string|typeof import('../../script/script-type.js').ScriptType} nameOrType - The
* name or type of {@link ScriptType}.
* @param {string|typeof import('../../script/script.js').Script} nameOrType - The
* name or type of {@link Script}.
* @returns {boolean} If script is attached to an entity.
* @example
* if (entity.script.has('playerController')) {
Expand All @@ -570,9 +570,9 @@ class ScriptComponent extends Component {
/**
* Get a script instance (if attached).
*
* @param {string|typeof import('../../script/script-type.js').ScriptType} nameOrType - The
* name or type of {@link ScriptType}.
* @returns {import('../../script/script-type.js').ScriptType|null} If script is attached, the
* @param {string|typeof import('../../script/script.js').Script} nameOrType - The
* name or type of {@link Script}.
* @returns {import('../../script/script.js').Script|null} If script is attached, the
* instance is returned. Otherwise null is returned.
* @example
* const controller = entity.script.get('playerController');
Expand All @@ -594,8 +594,8 @@ class ScriptComponent extends Component {
/**
* Create a script instance and attach to an entity script component.
*
* @param {string|typeof import('../../script/script-type.js').ScriptType} nameOrType - The
* name or type of {@link ScriptType}.
* @param {string|typeof import('../../script/script.js').Script} nameOrType - The
* name or type of {@link Script}.
* @param {object} [args] - Object with arguments for a script.
* @param {boolean} [args.enabled] - If script instance is enabled after creation. Defaults to
* true.
Expand All @@ -605,9 +605,9 @@ class ScriptComponent extends Component {
* script and attributes must be initialized manually. Defaults to false.
* @param {number} [args.ind] - The index where to insert the script instance at. Defaults to
* -1, which means append it at the end.
* @returns {import('../../script/script-type.js').ScriptType|null} Returns an instance of a
* {@link ScriptType} if successfully attached to an entity, or null if it failed because a
* script with a same name has already been added or if the {@link ScriptType} cannot be found
* @returns {import('../../script/script.js').Script|null} Returns an instance of a
* {@link Script} if successfully attached to an entity, or null if it failed because a
* script with a same name has already been added or if the {@link Script} cannot be found
* by name in the {@link ScriptRegistry}.
* @example
* entity.script.create('playerController', {
Expand Down Expand Up @@ -699,8 +699,8 @@ class ScriptComponent extends Component {
/**
* Destroy the script instance that is attached to an entity.
*
* @param {string|typeof import('../../script/script-type.js').ScriptType} nameOrType - The
* name or type of {@link ScriptType}.
* @param {string|typeof import('../../script/script.js').Script} nameOrType - The
* name or type of {@link Script}.
* @returns {boolean} If it was successfully destroyed.
* @example
* entity.script.destroy('playerController');
Expand Down Expand Up @@ -756,8 +756,8 @@ class ScriptComponent extends Component {
/**
* Swap the script instance.
*
* @param {string|typeof import('../../script/script-type.js').ScriptType} nameOrType - The
* name or type of {@link ScriptType}.
* @param {string|typeof import('../../script/script.js').Script} nameOrType - The
* name or type of {@link Script}.
* @returns {boolean} If it was successfully swapped.
* @private
*/
Expand Down Expand Up @@ -927,8 +927,8 @@ class ScriptComponent extends Component {
/**
* Move script instance to different position to alter update order of scripts within entity.
*
* @param {string|typeof import('../../script/script-type.js').ScriptType} nameOrType - The
* name or type of {@link ScriptType}.
* @param {string|typeof import('../../script/script.js').Script} nameOrType - The
* name or type of {@link Script}.
* @param {number} ind - New position index.
* @returns {boolean} If it was successfully moved.
* @example
Expand Down
8 changes: 4 additions & 4 deletions src/framework/entity.js
Original file line number Diff line number Diff line change
Expand Up @@ -400,8 +400,8 @@ class Entity extends GraphNode {
/**
* Search the entity and all of its descendants for the first script instance of specified type.
*
* @param {string|typeof import('./script/script-type.js').ScriptType} nameOrType - The name or type of {@link ScriptType}.
* @returns {import('./script/script-type.js').ScriptType|undefined} A script instance of specified type, if the entity or any of its descendants
* @param {string|typeof import('./script/script.js').Script} nameOrType - The name or type of {@link Script}.
* @returns {import('./script/script.js').Script|undefined} A script instance of specified type, if the entity or any of its descendants
* has one. Returns undefined otherwise.
* @example
* // Get the first found "playerController" instance in the hierarchy tree that starts with this entity
Expand All @@ -415,8 +415,8 @@ class Entity extends GraphNode {
/**
* Search the entity and all of its descendants for all script instances of specified type.
*
* @param {string|typeof import('./script/script-type.js').ScriptType} nameOrType - The name or type of {@link ScriptType}.
* @returns {import('./script/script-type.js').ScriptType[]} All script instances of specified type in the entity or any of its
* @param {string|typeof import('./script/script.js').Script} nameOrType - The name or type of {@link Script}.
* @returns {import('./script/script.js').Script[]} All script instances of specified type in the entity or any of its
* descendants. Returns empty array if none found.
* @example
* // Get all "playerController" instances in the hierarchy tree that starts with this entity
Expand Down
4 changes: 2 additions & 2 deletions src/framework/handlers/script.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { platform } from '../../core/platform.js';
import { script } from '../script.js';
import { ScriptType } from '../script/script-type.js';
import { ScriptType } from '../script/script.js';
import { ScriptTypes } from '../script/script-types.js';
import { registerScript } from '../script/script.js';
import { registerScript } from '../script/script-create.js';
import { ResourceLoader } from './loader.js';

import { ResourceHandler } from './handler.js';
Expand Down
6 changes: 3 additions & 3 deletions src/framework/script/script-attributes.js
Original file line number Diff line number Diff line change
Expand Up @@ -150,16 +150,16 @@ function rawToValue(app, args, value, old) {

/**
* Container of Script Attribute definitions. Implements an interface to add/remove attributes and
* store their definition for a {@link ScriptType}. Note: An instance of ScriptAttributes is
* created automatically by each {@link ScriptType}.
* store their definition for a {@link Script}. Note: An instance of ScriptAttributes is
* created automatically by each {@link Script}.
*
* @category Script
*/
class ScriptAttributes {
/**
* Create a new ScriptAttributes instance.
*
* @param {typeof import('./script-type.js').ScriptType} scriptType - Script Type that attributes relate to.
* @param {typeof import('./script.js').Script} scriptType - Script Type that attributes relate to.
*/
constructor(scriptType) {
this.scriptType = scriptType;
Expand Down
156 changes: 156 additions & 0 deletions src/framework/script/script-create.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
import { Debug } from '../../core/debug.js';
import { EventHandler } from '../../core/event-handler.js';

import { script } from '../script.js';
import { AppBase } from '../app-base.js';

import { ScriptAttributes } from './script-attributes.js';
import { ScriptType } from './script.js';
marklundin marked this conversation as resolved.
Show resolved Hide resolved
import { ScriptTypes } from './script-types.js';

const reservedScriptNames = new Set([
'system', 'entity', 'create', 'destroy', 'swap', 'move', 'data',
'scripts', '_scripts', '_scriptsIndex', '_scriptsData',
'enabled', '_oldState', 'onEnable', 'onDisable', 'onPostStateChange',
'_onSetEnabled', '_checkState', '_onBeforeRemove',
'_onInitializeAttributes', '_onInitialize', '_onPostInitialize',
'_onUpdate', '_onPostUpdate',
'_callbacks', '_callbackActive', 'has', 'get', 'on', 'off', 'fire', 'once', 'hasEvent'
]);

function getReservedScriptNames() {
return reservedScriptNames;
}

/**
* Create and register a new {@link Script}. It returns new class type (constructor function),
* which is auto-registered to {@link ScriptRegistry} using its name. This is the main interface to
* create Script Types, to define custom logic using JavaScript, that is used to create interaction
* for entities.
*
* @param {string} name - Unique Name of a Script Type. If a Script Type with the same name has
* already been registered and the new one has a `swap` method defined in its prototype, then it
* will perform hot swapping of existing Script Instances on entities using this new Script Type.
* Note: There is a reserved list of names that cannot be used, such as list below as well as some
* starting from `_` (underscore): system, entity, create, destroy, swap, move, scripts, onEnable,
* onDisable, onPostStateChange, has, on, off, fire, once, hasEvent.
* @param {AppBase} [app] - Optional application handler, to choose which {@link ScriptRegistry}
* to add a script to. By default it will use `Application.getApplication()` to get current
* {@link AppBase}.
* @returns {typeof ScriptType|null} A class type (constructor function) that inherits {@link Script},
* which the developer is meant to further extend by adding attributes and prototype methods.
* Returns null if there was an error.
* @example
* var Turning = pc.createScript('turn');
*
* // define 'speed' attribute that is available in Editor UI
* Turning.attributes.add('speed', {
* type: 'number',
* default: 180,
* placeholder: 'deg/s'
* });
*
* // runs every tick
* Turning.prototype.update = function (dt) {
* this.entity.rotate(0, this.speed * dt, 0);
* };
* @category Script
*/
function createScript(name, app) {
if (script.legacy) {
Debug.error('This project is using the legacy script system. You cannot call pc.createScript().');
return null;
}

if (reservedScriptNames.has(name))
throw new Error(`Script name '${name}' is reserved, please rename the script`);

const scriptType = function (args) {
EventHandler.prototype.initEventHandler.call(this);
ScriptType.prototype.initScriptType.call(this, args);
marklundin marked this conversation as resolved.
Show resolved Hide resolved
};

scriptType.prototype = Object.create(ScriptType.prototype);
scriptType.prototype.constructor = scriptType;

scriptType.extend = ScriptType.extend;
scriptType.attributes = new ScriptAttributes(scriptType);

registerScript(scriptType, name, app);
return scriptType;
}

// Editor uses this - migrate to ScriptAttributes.reservedNames and delete this
const reservedAttributes = {};
ScriptAttributes.reservedNames.forEach((value, value2, set) => {
reservedAttributes[value] = 1;
});
createScript.reservedAttributes = reservedAttributes;

/* eslint-disable jsdoc/check-examples */
/**
* Register a existing class type as a Script Type to {@link ScriptRegistry}. Useful when defining
* a ES6 script class that extends {@link Script} (see example).
*
* @param {typeof ScriptType} script - The existing class type (constructor function) to be
* registered as a Script Type. Class must extend {@link Script} (see example). Please note: A
* class created using {@link createScript} is auto-registered, and should therefore not be pass
* into {@link registerScript} (which would result in swapping out all related script instances).
* @param {string} [name] - Optional unique name of the Script Type. By default it will use the
* same name as the existing class. If a Script Type with the same name has already been registered
* and the new one has a `swap` method defined in its prototype, then it will perform hot swapping
* of existing Script Instances on entities using this new Script Type. Note: There is a reserved
* list of names that cannot be used, such as list below as well as some starting from `_`
* (underscore): system, entity, create, destroy, swap, move, scripts, onEnable, onDisable,
* onPostStateChange, has, on, off, fire, once, hasEvent.
* @param {AppBase} [app] - Optional application handler, to choose which {@link ScriptRegistry}
* to register the script type to. By default it will use `Application.getApplication()` to get
* current {@link AppBase}.
* @example
* // define a ES6 script class
* class PlayerController extends pc.Script {
*
* initialize() {
* // called once on initialize
* }
*
* update(dt) {
* // called each tick
* }
* }
*
* // register the class as a script
* pc.registerScript(PlayerController);
*
* // declare script attributes (Must be after pc.registerScript())
* PlayerController.attributes.add('attribute1', {type: 'number'});
* @category Script
*/
function registerScript(script, name, app) {
if (script.legacy) {
Debug.error('This project is using the legacy script system. You cannot call pc.registerScript().');
return;
}

if (typeof script !== 'function')
throw new Error(`script class: '${script}' must be a constructor function (i.e. class).`);

if (!(script.prototype instanceof ScriptType))
throw new Error(`script class: '${ScriptType.__getScriptName(script)}' does not extend pc.Script.`);

name = name || script.__name || ScriptType.__getScriptName(script);

if (reservedScriptNames.has(name))
throw new Error(`script name: '${name}' is reserved, please change script name`);

script.__name = name;

// add to scripts registry
const registry = app ? app.scripts : AppBase.getApplication().scripts;
registry.add(script);

ScriptTypes.push(script, script.legacy);
}
/* eslint-enable jsdoc/check-examples */

export { createScript, registerScript, getReservedScriptNames };
Loading