forked from playcanvas/engine
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathscript-type.js
More file actions
345 lines (309 loc) · 12.3 KB
/
script-type.js
File metadata and controls
345 lines (309 loc) · 12.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
Object.assign(pc, function () {
var funcNameRegex = new RegExp('^\\s*function(?:\\s|\\s*\\/\\*.*\\*\\/\\s*)+([^\\(\\s\\/]*)\\s*');
/**
* @class
* @name pc.ScriptType
* @augments pc.EventHandler
* @classdesc Represents the type of a script. It is returned by {@link pc.createScript}.
* Also referred to as Script Type.
*
* The type is to be extended using its JavaScript prototype. There is a **list of methods**
* that will be executed by the engine on instances of this type, such as:
*
* * initialize
* * postInitialize
* * update
* * postUpdate
* * swap
*
* **initialize** and **postInitialize** - are called if defined when script is about to run
* for the first time - postInitialize will run after all initialize methods are executed in
* the same tick or enabling chain of actions.
*
* **update** and **postUpdate** - methods are called if defined for enabled (running state)
* scripts on each tick.
*
* **swap** - This method will be called when a {@link pc.ScriptType} that already exists in
* the registry gets redefined. If the new {@link pc.ScriptType} has a `swap` method in its
* prototype, then it will be executed to perform hot-reload at runtime.
* @property {pc.Application} app The {@link pc.Application} that the instance of this type
* belongs to.
* @property {pc.Entity} entity The {@link pc.Entity} that the instance of this type belongs to.
* @property {boolean} enabled True if the instance of this type is in running state. False
* when script is not running, because the Entity or any of its parents are disabled or the
* Script Component is disabled or the Script Instance is disabled. When disabled no update
* methods will be called on each tick. initialize and postInitialize methods will run once
* when the script instance is in `enabled` state during app tick.
* @param {object} args - The input arguments object
* @param {pc.Application} args.app - The {@link pc.Application} that is running the script
* @param {pc.Entity} args.entity - The {@link pc.Entity} that the script is attached to
*
*/
var ScriptType = function (args) {
pc.EventHandler.call(this);
var script = this.constructor; // get script type, i.e. function (class)
// #ifdef DEBUG
if (!args || !args.app || !args.entity) {
console.warn('script \'' + script.__name + '\' has missing arguments in constructor');
}
// #endif
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.__attributes = { };
this.__attributesRaw = args.attributes || { }; // need at least an empty object to make sure default attributes are initialized
this.__scriptType = script;
// the order in the script component that the
// methods of this script instance will run relative to
// other script instances in the component
this.__executionOrder = -1;
};
ScriptType.prototype = Object.create(pc.EventHandler.prototype);
ScriptType.prototype.constructor = ScriptType;
/**
* @private
* @readonly
* @static
* @name pc.ScriptType.__name
* @type {string}
* @description Name of a Script Type.
*/
ScriptType.__name = null; // Will be assigned when calling createScript or registerScript.
ScriptType.__getScriptName = function (constructorFn) {
if (typeof constructorFn !== 'function') return undefined;
if ('name' in Function.prototype) return constructorFn.name;
if (constructorFn === Function || constructorFn === Function.prototype.constructor) return 'Function';
var match = ("" + constructorFn).match(funcNameRegex);
return match ? match[1] : undefined;
};
/**
* @field
* @static
* @readonly
* @name pc.ScriptType.scriptName
* @type {string|null}
* @description Name of a Script Type
*/
Object.defineProperty(ScriptType, 'scriptName', {
get: function () {
return this.__name;
}
});
/**
* @field
* @static
* @readonly
* @name pc.ScriptType.attributes
* @type {pc.ScriptAttributes}
* @description The interface to define attributes for Script Types. Refer to {@link pc.ScriptAttributes}.
* @example
* var PlayerController = pc.createScript('playerController');
*
* PlayerController.attributes.add('speed', {
* type: 'number',
* title: 'Speed',
* placeholder: 'km/h',
* default: 22.2
* });
*/
Object.defineProperty(ScriptType, 'attributes', {
get: function () {
if (!this.hasOwnProperty('__attributes')) this.__attributes = new pc.ScriptAttributes(this);
return this.__attributes;
}
});
// initialize attributes
ScriptType.prototype.__initializeAttributes = function (force) {
if (!force && !this.__attributesRaw)
return;
// set attributes values
for (var key in this.__scriptType.attributes.index) {
if (this.__attributesRaw && this.__attributesRaw.hasOwnProperty(key)) {
this[key] = this.__attributesRaw[key];
} else if (!this.__attributes.hasOwnProperty(key)) {
if (this.__scriptType.attributes.index[key].hasOwnProperty('default')) {
this[key] = this.__scriptType.attributes.index[key].default;
} else {
this[key] = null;
}
}
}
this.__attributesRaw = null;
};
/**
* @readonly
* @static
* @function
* @name pc.ScriptType.extend
* @param {object} methods - Object with methods, where key - is name of method, and value - is function.
* @description Shorthand function to extend Script Type prototype with list of methods.
* @example
* var PlayerController = pc.createScript('playerController');
*
* PlayerController.extend({
* initialize: function () {
* // called once on initialize
* },
* update: function (dt) {
* // called each tick
* }
* });
*/
ScriptType.extend = function (methods) {
for (var key in methods) {
if (!methods.hasOwnProperty(key))
continue;
this.prototype[key] = methods[key];
}
};
/**
* @function
* @name pc.ScriptType#[initialize]
* @description Called when script is about to run for the first time.
*/
/**
* @function
* @name pc.ScriptType#[postInitialize]
* @description Called after all initialize methods are executed in the same tick or enabling chain of actions.
*/
/**
* @function
* @name pc.ScriptType#[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 pc.ScriptType#[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 pc.ScriptType#[swap]
* @description Called when a ScriptType that already exists in the registry
* gets redefined. If the new ScriptType has a `swap` method in its prototype,
* then it will be executed to perform hot-reload at runtime.
*/
/**
* @event
* @name pc.ScriptType#enable
* @description Fired when a script instance becomes enabled.
* @example
* PlayerController.prototype.initialize = function () {
* this.on('enable', function () {
* // Script Instance is now enabled
* });
* };
*/
/**
* @event
* @name pc.ScriptType#disable
* @description Fired when a script instance becomes disabled.
* @example
* PlayerController.prototype.initialize = function () {
* this.on('disable', function () {
* // Script Instance is now disabled
* });
* };
*/
/**
* @event
* @name pc.ScriptType#state
* @description Fired when a script instance changes state to enabled or disabled.
* @param {boolean} enabled - True if now enabled, False if disabled.
* @example
* PlayerController.prototype.initialize = function () {
* this.on('state', function (enabled) {
* console.log('Script Instance is now ' + (enabled ? 'enabled' : 'disabled'));
* });
* };
*/
/**
* @event
* @name pc.ScriptType#destroy
* @description Fired when a script instance is destroyed and removed from component.
* @example
* PlayerController.prototype.initialize = function () {
* this.on('destroy', function () {
* // no more part of an entity
* // good place to cleanup entity from destroyed script
* });
* };
*/
/**
* @event
* @name pc.ScriptType#attr
* @description Fired when any script attribute has been changed.
* @param {string} name - Name of attribute.
* @param {object} value - New value.
* @param {object} valueOld - Old value.
* @example
* PlayerController.prototype.initialize = function () {
* this.on('attr', function (name, value, valueOld) {
* console.log(name + ' been changed from ' + valueOld + ' to ' + value);
* });
* };
*/
/**
* @event
* @name pc.ScriptType#attr:[name]
* @description Fired when a specific script attribute has been changed.
* @param {object} value - New value.
* @param {object} valueOld - Old value.
* @example
* PlayerController.prototype.initialize = function () {
* this.on('attr:speed', function (value, valueOld) {
* console.log('speed been changed from ' + valueOld + ' to ' + value);
* });
* };
*/
/**
* @event
* @name pc.ScriptType#error
* @description Fired when a script instance had an exception. The script instance will be automatically disabled.
* @param {Error} err - Native JavaScript Error object with details of error.
* @param {string} method - The method of the script instance that the exception originated from.
* @example
* PlayerController.prototype.initialize = function () {
* this.on('error', function (err, method) {
* // caught an exception
* console.log(err.stack);
* });
* };
*/
Object.defineProperty(ScriptType.prototype, 'enabled', {
get: function () {
return this._enabled && !this._destroyed && this.entity.script.enabled && this.entity.enabled;
},
set: function (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);
// initialize script if not initialized yet and script is enabled
if (!this._initialized && this.enabled) {
this._initialized = true;
this.__initializeAttributes(true);
if (this.initialize)
this.entity.script._scriptMethod(this, pc.ScriptComponent.scriptMethods.initialize);
}
// post initialize script if not post initialized yet and still enabled
// (initilize might have disabled the script so check this.enabled again)
// Warning: Do not do this if the script component is currently being enabled
// because in this case post initialize must be called after all the scripts
// in the script component have been initialized first
if (this._initialized && !this._postInitialized && this.enabled && !this.entity.script._beingEnabled) {
this._postInitialized = true;
if (this.postInitialize)
this.entity.script._scriptMethod(this, pc.ScriptComponent.scriptMethods.postInitialize);
}
}
});
return {
ScriptType: ScriptType
};
}());