Source: framework/components/script/component.js

Object.assign(pc, function () {
    /**
     * @component
     * @name pc.ScriptComponent
     * @class The ScriptComponent allows you to extend the functionality of an Entity by attaching your own Script Types defined in JavaScript files
     * to be executed with access to the Entity. For more details on scripting see <a href="//developer.playcanvas.com/user-manual/scripting/">Scripting</a>.
     * @param {pc.ScriptComponentSystem} system The ComponentSystem that created this Component
     * @param {pc.Entity} entity The Entity that this Component is attached to.
     * @extends pc.Component
     * @property {ScriptType[]} scripts An array of all script instances attached to an entity. This Array shall not be modified by developer.
     */

    var ScriptComponent = function ScriptComponent(system, entity) {
        pc.Component.call(this, system, entity);

        // holds all script instances for this component
        this._scripts = [];
        // holds all script instances with an update method
        this._updateList = new pc.SortedLoopArray({ sortBy: '__executionOrder' });
        // holds all script instances with a postUpdate method
        this._postUpdateList = new pc.SortedLoopArray({ sortBy: '__executionOrder' });

        this._scriptsIndex = {};
        this._destroyedScripts = [];
        this._destroyed = false;
        this._scriptsData = null;
        this._oldState = true;

        // override default 'enabled' property of base pc.Component
        // because this is faster
        this._enabled = true;

        // whether this component is currently being enabled
        this._beingEnabled = false;
        // if true then we are currently looping through
        // script instances. This is used to prevent a scripts array
        // from being modified while a loop is being executed
        this._isLoopingThroughScripts = false;

        // the order that this component will be updated
        // by the script system. This is set by the system itself.
        this._executionOrder = -1;

        this.on('set_enabled', this._onSetEnabled, this);
    };
    ScriptComponent.prototype = Object.create(pc.Component.prototype);
    ScriptComponent.prototype.constructor = ScriptComponent;

    ScriptComponent.scriptMethods = {
        initialize: 'initialize',
        postInitialize: 'postInitialize',
        update: 'update',
        postUpdate: 'postUpdate',
        swap: 'swap'
    };

    /**
     * @event
     * @name pc.ScriptComponent#enable
     * @description Fired when Component becomes enabled
     * Note: this event does not take in account entity or any of its parent enabled state
     * @example
     * entity.script.on('enable', function () {
     *     // component is enabled
     * });
     */

    /**
     * @event
     * @name pc.ScriptComponent#disable
     * @description Fired when Component becomes disabled
     * Note: this event does not take in account entity or any of its parent enabled state
     * @example
     * entity.script.on('disable', function () {
     *     // component is disabled
     * });
     */

    /**
     * @event
     * @name pc.ScriptComponent#state
     * @description Fired when Component changes state to enabled or disabled
     * Note: this event does not take in account entity or any of its parent enabled state
     * @param {Boolean} enabled True if now enabled, False if disabled
     * @example
     * entity.script.on('state', function (enabled) {
     *     // component changed state
     * });
     */

    /**
     * @event
     * @name pc.ScriptComponent#remove
     * @description Fired when Component is removed from entity
     * @example
     * entity.script.on('remove', function () {
     *     // entity has no more script component
     * });
     */

    /**
     * @event
     * @name pc.ScriptComponent#create
     * @description Fired when a script instance is created and attached to component
     * @param {String} name The name of the Script Type
     * @param {ScriptType} scriptInstance The instance of the {@link ScriptType} that has been created
     * @example
     * entity.script.on('create', function (name, scriptInstance) {
     *     // new script instance added to component
     * });
     */

    /**
     * @event
     * @name pc.ScriptComponent#create:[name]
     * @description Fired when a script instance is created and attached to component
     * @param {ScriptType} scriptInstance The instance of the {@link ScriptType} that has been created
     * @example
     * entity.script.on('create:playerController', function (scriptInstance) {
     *     // new script instance 'playerController' is added to component
     * });
     */

    /**
     * @event
     * @name pc.ScriptComponent#destroy
     * @description Fired when a script instance is destroyed and removed from component
     * @param {String} name The name of the Script Type
     * @param {ScriptType} scriptInstance The instance of the {@link ScriptType} that has been destroyed
     * @example
     * entity.script.on('destroy', function (name, scriptInstance) {
     *     // script instance has been destroyed and removed from component
     * });
     */

    /**
     * @event
     * @name pc.ScriptComponent#destroy:[name]
     * @description Fired when a script instance is destroyed and removed from component
     * @param {ScriptType} scriptInstance The instance of the {@link ScriptType} that has been destroyed
     * @example
     * entity.script.on('destroy:playerController', function (scriptInstance) {
     *     // script instance 'playerController' has been destroyed and removed from component
     * });
     */

    /**
     * @event
     * @name pc.ScriptComponent#move
     * @description Fired when a script instance is moved in component
     * @param {String} name The name of the Script Type
     * @param {ScriptType} scriptInstance The instance of the {@link ScriptType} that has been moved
     * @param {Number} ind New position index
     * @param {Number} indOld Old position index
     * @example
     * entity.script.on('move', function (name, scriptInstance, ind, indOld) {
     *     // script instance has been moved in component
     * });
     */

    /**
     * @event
     * @name pc.ScriptComponent#move:[name]
     * @description Fired when a script instance is moved in component
     * @param {ScriptType} scriptInstance The instance of the {@link ScriptType} that has been moved
     * @param {Number} ind New position index
     * @param {Number} indOld Old position index
     * @example
     * entity.script.on('move:playerController', function (scriptInstance, ind, indOld) {
     *     // script instance 'playerController' has been moved in component
     * });
     */

    /**
     * @event
     * @name pc.ScriptComponent#error
     * @description Fired when a script instance had an exception
     * @param {ScriptType} scriptInstance The instance of the {@link ScriptType} that raised the exception
     * @param {Error} err Native JS Error object with details of an error
     * @param {String} method The method of the script instance that the exception originated from.
     * @example
     * entity.script.on('error', function (scriptInstance, err, method) {
     *     // script instance caught an exception
     * });
     */

    Object.assign(ScriptComponent.prototype, {
        onEnable: function () {
            this._beingEnabled = true;
            this._checkState();

            if (!this.entity._beingEnabled) {
                this.onPostStateChange();
            }

            this._beingEnabled = false;
        },

        onDisable: function () {
            this._checkState();
        },

        onPostStateChange: function () {
            var script;

            var wasLooping = this._beginLooping();

            for (var i = 0, len = this.scripts.length; i < len; i++) {
                script = this.scripts[i];

                if (script._initialized && !script._postInitialized && script.enabled) {
                    script._postInitialized = true;

                    if (script.postInitialize)
                        this._scriptMethod(script, ScriptComponent.scriptMethods.postInitialize);
                }
            }

            this._endLooping(wasLooping);
        },

        // Sets isLoopingThroughScripts to false and returns
        // its previous value
        _beginLooping: function () {
            var looping = this._isLoopingThroughScripts;
            this._isLoopingThroughScripts = true;
            return looping;
        },

        // Restores isLoopingThroughScripts to the specified parameter
        // If all loops are over then remove destroyed scripts form the _scripts array
        _endLooping: function (wasLoopingBefore) {
            this._isLoopingThroughScripts = wasLoopingBefore;
            if (!this._isLoopingThroughScripts) {
                this._removeDestroyedScripts();
            }
        },

        // We also need this handler because it is fired
        // when value === old instead of onEnable and onDisable
        // which are only fired when value !== old
        _onSetEnabled: function (prop, old, value) {
            this._beingEnabled = true;
            this._checkState();
            this._beingEnabled = false;
        },

        _checkState: function () {
            var state = this.enabled && this.entity.enabled;
            if (state === this._oldState)
                return;

            this._oldState = state;

            this.fire(state ? 'enable' : 'disable');
            this.fire('state', state);

            if (state) {
                this.system._addComponentToEnabled(this);
            } else {
                this.system._removeComponentFromEnabled(this);
            }

            var wasLooping = this._beginLooping();

            var script;
            for (var i = 0, len = this.scripts.length; i < len; i++) {
                script = this.scripts[i];
                script.enabled = script._enabled;
            }

            this._endLooping(wasLooping);
        },

        _onBeforeRemove: function () {
            this.fire('remove');

            var wasLooping = this._beginLooping();

            // destroy all scripts
            for (var i = 0; i < this.scripts.length; i++) {
                var script = this.scripts[i];
                if (!script) continue;

                this.destroy(script.__scriptType.__name);
            }

            this._endLooping(wasLooping);
        },

        _removeDestroyedScripts: function () {
            var len = this._destroyedScripts.length;
            if (!len) return;

            var i;
            for (i = 0; i < len; i++) {
                var script = this._destroyedScripts[i];
                this._removeScriptInstance(script);
            }

            this._destroyedScripts.length = 0;

            // update execution order for scripts
            this._resetExecutionOrder(0, this._scripts.length);
        },

        _onInitializeAttributes: function () {
            for (var i = 0, len = this.scripts.length; i < len; i++)
                this.scripts[i].__initializeAttributes();
        },

        _scriptMethod: function (script, method, arg) {
            // #ifdef DEBUG
            try {
            // #endif
                script[method](arg);
            // #ifdef DEBUG
            } catch (ex) {
                // disable script if it fails to call method
                script.enabled = false;

                if (!script._callbacks || !script._callbacks.error) {
                    console.warn('unhandled exception while calling "' + method + '" for "' + script.__scriptType.__name + '" script: ', ex);
                    console.error(ex);
                }

                script.fire('error', ex, method);
                this.fire('error', script, ex, method);
            }
            // #endif
        },

        _onInitialize: function () {
            var script, scripts = this._scripts;

            var wasLooping = this._beginLooping();

            for (var i = 0, len = scripts.length; i < len; i++) {
                script = scripts[i];
                if (!script._initialized && script.enabled) {
                    script._initialized = true;
                    if (script.initialize)
                        this._scriptMethod(script, ScriptComponent.scriptMethods.initialize);
                }
            }

            this._endLooping(wasLooping);
        },

        _onPostInitialize: function () {
            this.onPostStateChange();
        },

        _onUpdate: function (dt) {
            var self = this;
            var list = self._updateList;
            if (! list.length) return;

            var script;

            var wasLooping = self._beginLooping();

            for (list.loopIndex = 0; list.loopIndex < list.length; list.loopIndex++) {
                script = list.items[list.loopIndex];
                if (script.enabled) {
                    self._scriptMethod(script, ScriptComponent.scriptMethods.update, dt);
                }
            }

            self._endLooping(wasLooping);
        },

        _onPostUpdate: function (dt) {
            var self = this;
            var list = self._postUpdateList;
            if (! list.length) return;

            var wasLooping = self._beginLooping();

            var script;

            for (list.loopIndex = 0; list.loopIndex < list.length; list.loopIndex++) {
                script = list.items[list.loopIndex];
                if (script.enabled) {
                    self._scriptMethod(script, ScriptComponent.scriptMethods.postUpdate, dt);
                }
            }

            self._endLooping(wasLooping);
        },

        /**
         * @private
         * Inserts script instance into the scripts array at the specified index. Also inserts the script
         * into the update list if it has an update method and the post update list if it has a postUpdate method.
         * @param {Object} scriptInstance The script instance
         * @param {Number} index The index where to insert the script at. If -1 then append it at the end.
         * @param {Number} scriptsLength The length of the scripts array.
         */
        _insertScriptInstance: function (scriptInstance, index, scriptsLength) {
            if (index === -1) {
                // append script at the end and set execution order
                this._scripts.push(scriptInstance);
                scriptInstance.__executionOrder = scriptsLength;

                // append script to the update list if it has an update method
                if (scriptInstance.update) {
                    this._updateList.append(scriptInstance);
                }

                // add script to the postUpdate list if it has a postUpdate method
                if (scriptInstance.postUpdate) {
                    this._postUpdateList.append(scriptInstance);
                }
            } else {
                // insert script at index and set execution order
                this._scripts.splice(index, 0, scriptInstance);
                scriptInstance.__executionOrder = index;

                // now we also need to update the execution order of all
                // the script instances that come after this script
                this._resetExecutionOrder(index + 1, scriptsLength + 1);

                // insert script to the update list if it has an update method
                // in the right order
                if (scriptInstance.update) {
                    this._updateList.insert(scriptInstance);
                }

                // insert script to the postUpdate list if it has a postUpdate method
                // in the right order
                if (scriptInstance.postUpdate) {
                    this._postUpdateList.insert(scriptInstance);
                }
            }
        },

        _removeScriptInstance: function (scriptInstance) {
            var idx = this._scripts.indexOf(scriptInstance);
            if (idx === -1) return idx;

            this._scripts.splice(idx, 1);

            if (scriptInstance.update) {
                this._updateList.remove(scriptInstance);
            }

            if (scriptInstance.postUpdate) {
                this._postUpdateList.remove(scriptInstance);
            }

            return idx;
        },

        _resetExecutionOrder: function (startIndex, scriptsLength) {
            for (var i = startIndex; i < scriptsLength; i++) {
                this._scripts[i].__executionOrder = i;
            }
        },

        /**
         * @function
         * @name pc.ScriptComponent#has
         * @description Detect if script is attached to an entity using name of {@link ScriptType}.
         * @param {String} name The name of the Script Type
         * @returns {Boolean} If script is attached to an entity
         * @example
         * if (entity.script.has('playerController')) {
         *     // entity has script
         * }
         */
        has: function (name) {
            var scriptType = name;

            // shorthand using script name
            if (typeof scriptType === 'string')
                scriptType = this.system.app.scripts.get(scriptType);

            return !!this._scriptsIndex[scriptType.__name];
        },

        /**
         * @function
         * @name pc.ScriptComponent#create
         * @description Create a script instance using name of a {@link ScriptType} and attach to an entity script component.
         * @param {String} name The name of the Script Type
         * @param {Object} [args] Object with arguments for a script
         * @param {Boolean} [args.enabled] if script instance is enabled after creation
         * @param {Object} [args.attributes] Object with values for attributes, where key is name of an attribute
         * @returns {ScriptType} 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 by name in the {@link pc.ScriptRegistry}.
         * @example
         * entity.script.create('playerController', {
         *     attributes: {
         *         speed: 4
         *     }
         * });
         */
        create: function (name, args) {
            var self = this;
            args = args || { };

            var scriptType = name;
            var scriptName = name;

            // shorthand using script name
            if (typeof scriptType === 'string') {
                scriptType = this.system.app.scripts.get(scriptType);
            } else if (scriptType) {
                scriptName = scriptType.__name;
            }

            if (scriptType) {
                if (!this._scriptsIndex[scriptType.__name] || !this._scriptsIndex[scriptType.__name].instance) {
                    // create script instance
                    var scriptInstance = new scriptType({
                        app: this.system.app,
                        entity: this.entity,
                        enabled: args.hasOwnProperty('enabled') ? args.enabled : true,
                        attributes: args.attributes
                    });

                    var len = this._scripts.length;
                    var ind = -1;
                    if (typeof args.ind === 'number' && args.ind !== -1 && len > args.ind)
                        ind = args.ind;

                    this._insertScriptInstance(scriptInstance, ind, len);

                    this._scriptsIndex[scriptType.__name] = {
                        instance: scriptInstance,
                        onSwap: function () {
                            self.swap(scriptType.__name);
                        }
                    };

                    this[scriptType.__name] = scriptInstance;

                    if (!args.preloading)
                        scriptInstance.__initializeAttributes();

                    this.fire('create', scriptType.__name, scriptInstance);
                    this.fire('create:' + scriptType.__name, scriptInstance);

                    this.system.app.scripts.on('swap:' + scriptType.__name, this._scriptsIndex[scriptType.__name].onSwap);

                    if (!args.preloading) {

                        if (scriptInstance.enabled && !scriptInstance._initialized) {
                            scriptInstance._initialized = true;

                            if (scriptInstance.initialize)
                                this._scriptMethod(scriptInstance, ScriptComponent.scriptMethods.initialize);
                        }

                        if (scriptInstance.enabled && !scriptInstance._postInitialized) {
                            scriptInstance._postInitialized = true;
                            if (scriptInstance.postInitialize)
                                this._scriptMethod(scriptInstance, ScriptComponent.scriptMethods.postInitialize);
                        }
                    }


                    return scriptInstance;
                }

                console.warn('script \'' + scriptName + '\' is already added to entity \'' + this.entity.name + '\'');
            } else {
                this._scriptsIndex[scriptName] = {
                    awaiting: true,
                    ind: this._scripts.length
                };

                console.warn('script \'' + scriptName + '\' is not found, awaiting it to be added to registry');
            }

            return null;
        },

        /**
         * @function
         * @name pc.ScriptComponent#destroy
         * @description Destroy the script instance that is attached to an entity.
         * @param {String} name The name of the Script Type
         * @returns {Boolean} If it was successfully destroyed
         * @example
         * entity.script.destroy('playerController');
         */
        destroy: function (name) {
            var scriptName = name;
            var scriptType = name;

            // shorthand using script name
            if (typeof scriptType === 'string') {
                scriptType = this.system.app.scripts.get(scriptType);
                if (scriptType)
                    scriptName = scriptType.__name;
            }

            var scriptData = this._scriptsIndex[scriptName];
            delete this._scriptsIndex[scriptName];
            if (!scriptData) return false;

            if (scriptData.instance && !scriptData.instance._destroyed) {
                scriptData.instance.enabled = false;
                scriptData.instance._destroyed = true;

                // if we are not currently looping through our scripts
                // then it's safe to remove the script
                if (!this._isLoopingThroughScripts) {
                    var ind = this._removeScriptInstance(scriptData.instance);
                    if (ind >= 0) {
                        this._resetExecutionOrder(ind, this._scripts.length);
                    }
                } else {
                    // otherwise push the script in _destroyedScripts and
                    // remove it from _scripts when the loop is over
                    this._destroyedScripts.push(scriptData.instance);
                }
            }

            // remove swap event
            this.system.app.scripts.off('swap:' + scriptName, scriptData.onSwap);

            delete this[scriptName];

            this.fire('destroy', scriptName, scriptData.instance || null);
            this.fire('destroy:' + scriptName, scriptData.instance || null);

            if (scriptData.instance)
                scriptData.instance.fire('destroy');

            return true;
        },

        swap: function (script) {
            var scriptType = script;

            // shorthand using script name
            if (typeof scriptType === 'string')
                scriptType = this.system.app.scripts.get(scriptType);

            var old = this._scriptsIndex[scriptType.__name];
            if (!old || !old.instance) return false;

            var scriptInstanceOld = old.instance;
            var ind = this._scripts.indexOf(scriptInstanceOld);

            var scriptInstance = new scriptType({
                app: this.system.app,
                entity: this.entity,
                enabled: scriptInstanceOld.enabled,
                attributes: scriptInstanceOld.__attributes
            });

            if (!scriptInstance.swap)
                return false;

            scriptInstance.__initializeAttributes();

            // add to component
            this._scripts[ind] = scriptInstance;
            this._scriptsIndex[scriptType.__name].instance = scriptInstance;
            this[scriptType.__name] = scriptInstance;

            // set execution order and make sure we update
            // our update and postUpdate lists
            scriptInstance.__executionOrder = ind;
            if (scriptInstanceOld.update) {
                this._updateList.remove(scriptInstanceOld);
            }
            if (scriptInstanceOld.postUpdate) {
                this._postUpdateList.remove(scriptInstanceOld);
            }

            if (scriptInstance.update) {
                this._updateList.insert(scriptInstance);
            }
            if (scriptInstance.postUpdate) {
                this._postUpdateList.insert(scriptInstance);
            }

            this._scriptMethod(scriptInstance, ScriptComponent.scriptMethods.swap, scriptInstanceOld);

            this.fire('swap', scriptType.__name, scriptInstance);
            this.fire('swap:' + scriptType.__name, scriptInstance);

            return true;
        },

        /**
         * @function
         * @private
         * @name pc.ScriptComponent#resolveDuplicatedEntityReferenceProperties
         * @description When an entity is cloned and it has entity script attributes that point
         * to other entities in the same subtree that is cloned, then we want the new script attributes to point
         * at the cloned entities. This method remaps the script attributes for this entity and it assumes that this
         * entity is the result of the clone operation.
         * @param {pc.ScriptComponent} oldScriptComponent The source script component that belongs to the entity that was being cloned.
         * @param {Object} duplicatedIdsMap A dictionary with guid-entity values that contains the entities that were cloned
         */
        resolveDuplicatedEntityReferenceProperties: function (oldScriptComponent, duplicatedIdsMap) {
            var newScriptComponent = this.entity.script;

            // for each script in the old compononent
            for (var scriptName in oldScriptComponent._scriptsIndex) {
                // get the script type from the script registry
                var scriptType = this.system.app.scripts.get(scriptName);
                if (! scriptType) {
                    continue;
                }

                // get the script from the component's index
                var script = oldScriptComponent._scriptsIndex[scriptName];
                if (! script || ! script.instance) {
                    continue;
                }

                // if __attributesRaw exists then it means that the new entity
                // has not yet initialized its attributes so put the new guid in there,
                // otherwise it means that the attributes have already been initialized
                // so convert the new guid to an entity
                // and put it in the new attributes
                var newAttributesRaw = newScriptComponent[scriptName].__attributesRaw;
                var newAttributes = newScriptComponent[scriptName].__attributes;
                if (! newAttributesRaw && ! newAttributes) {
                    continue;
                }

                // get the old script attributes from the instance
                var oldAttributes = script.instance.__attributes;
                for (var attributeName in oldAttributes) {
                    if (! oldAttributes[attributeName]) {
                        continue;
                    }

                    // get the attribute definition from the script type
                    var attribute = scriptType.attributes.get(attributeName);
                    if (! attribute || attribute.type !== 'entity') {
                        continue;
                    }

                    if (attribute.array) {
                        // handle entity array attribute
                        var oldGuidArray = oldAttributes[attributeName];
                        var len = oldGuidArray.length;
                        if (! len) {
                            continue;
                        }

                        var newGuidArray = oldGuidArray.slice();
                        for (var i = 0; i < len; i++) {
                            var guid = newGuidArray[i] instanceof pc.Entity ? newGuidArray[i].getGuid() : newGuidArray[i];
                            if (duplicatedIdsMap[guid]) {
                                // if we are using attributesRaw then use the guid otherwise use the entity
                                newGuidArray[i] = newAttributesRaw ? duplicatedIdsMap[guid].getGuid() : duplicatedIdsMap[guid];
                            }
                        }

                        if (newAttributesRaw) {
                            newAttributesRaw[attributeName] = newGuidArray;
                        } else {
                            newAttributes[attributeName] = newGuidArray;
                        }
                    } else {
                        // handle regular entity attribute
                        var oldGuid = oldAttributes[attributeName];
                        if (oldGuid instanceof pc.Entity) {
                            oldGuid = oldGuid.getGuid();
                        } else if (typeof oldGuid !== 'string') {
                            continue;
                        }

                        if (duplicatedIdsMap[oldGuid]) {
                            if (newAttributesRaw) {
                                newAttributesRaw[attributeName] = duplicatedIdsMap[oldGuid].getGuid();
                            } else {
                                newAttributes[attributeName] = duplicatedIdsMap[oldGuid];
                            }
                        }

                    }
                }
            }
        },

        /**
         * @function
         * @name pc.ScriptComponent#move
         * @description Move script instance to different position to alter update order of scripts within entity.
         * @param {String} name The name of the Script Type
         * @param {Number} ind New position index
         * @returns {Boolean} If it was successfully moved
         * @example
         * entity.script.move('playerController', 0);
         */
        move: function (name, ind) {
            var len = this._scripts.length;
            if (ind >= len || ind < 0)
                return false;

            var scriptName = name;

            if (typeof scriptName !== 'string')
                scriptName = name.__name;

            var scriptData = this._scriptsIndex[scriptName];
            if (!scriptData || !scriptData.instance)
                return false;

            var indOld = this._scripts.indexOf(scriptData.instance);
            if (indOld === -1 || indOld === ind)
                return false;

            // move script to another position
            this._scripts.splice(ind, 0, this._scripts.splice(indOld, 1)[0]);

            // reset execution order for scripts and re-sort update and postUpdate lists
            this._resetExecutionOrder(0, len);
            this._updateList.sort();
            this._postUpdateList.sort();

            this.fire('move', scriptName, scriptData.instance, ind, indOld);
            this.fire('move:' + scriptName, scriptData.instance, ind, indOld);

            return true;
        }
    });

    Object.defineProperty(ScriptComponent.prototype, 'enabled', {
        get: function () {
            return this._enabled;
        },
        set: function (value) {
            var oldValue = this._enabled;
            this._enabled = value;
            this.fire('set', 'enabled', oldValue, value);
        }
    });

    Object.defineProperty(ScriptComponent.prototype, 'scripts', {
        get: function () {
            return this._scripts;
        },
        set: function (value) {
            this._scriptsData = value;

            for (var key in value) {
                if (!value.hasOwnProperty(key))
                    continue;

                var script = this._scriptsIndex[key];
                if (script) {
                    // existing script

                    // enabled
                    if (typeof value[key].enabled === 'boolean')
                        script.enabled = !!value[key].enabled;

                    // attributes
                    if (typeof value[key].attributes === 'object') {
                        for (var attr in value[key].attributes) {
                            if (pc.createScript.reservedAttributes[attr])
                                continue;

                            if (!script.__attributes.hasOwnProperty(attr)) {
                                // new attribute
                                var scriptType = this.system.app.scripts.get(key);
                                if (scriptType)
                                    scriptType.attributes.add(attr, { });
                            }

                            // update attribute
                            script[attr] = value[key].attributes[attr];
                        }
                    }
                } else {
                    // TODO scripts2
                    // new script
                    console.log(this.order);
                }
            }
        }
    });

    return {
        ScriptComponent: ScriptComponent
    };
}());