<commit_before>/*#ifndef(UMD)*/
(function () { /* Begin of privacy scope */
    "use strict";
/*#endif*/

/*
    This package contains general helpers that will be used everywhere else
*/

    var
        _arrayEachWithResult = function (array, memberCallback, defaultResult) {
            var
                result,
                len = array.length,
                idx;
            for (idx = 0; idx < len; ++idx) {
                result = memberCallback.apply(this, [idx, array[idx]]);
                if (undefined !== result) {
                    return result;
                }
            }
            return defaultResult;
        },

        _arrayEach = function (array, memberCallback) {
            var
                len = array.length,
                idx;
            for (idx = 0; idx < len; ++idx) {
                memberCallback.apply(this, [idx, array[idx]]);
            }
        },

        _dictionaryEachWithResult = function (dictionary, memberCallback,
            defaultResult) {
            var
                result,
                member;
            for (member in dictionary) {
                if (dictionary.hasOwnProperty(member)) {
                    result = memberCallback.apply(this, [member,
                        dictionary[member]]);
                    if (undefined !== result) {
                        return result;
                    }
                }
            }
            return defaultResult;
        },

        _dictionaryEach = function (dictionary, memberCallback) {
            var
                member;
            for (member in dictionary) {
                if (dictionary.hasOwnProperty(member)) {
                    memberCallback.apply(this, [member, dictionary[member]]);
                }
            }
        },

        _assign = function (member, value) {
            // this = gpf.extend's arguments
            // this[0] is dictionary
            this[0][member] = value;
        },

        _assignOrCall = function (member, value) {
            // this = gpf.extend's arguments
            var
                dictionary = this[0],
                overwriteCallback = this[2];
            // TODO: see if in is faster
            if (undefined !== dictionary[member]) {
                overwriteCallback(dictionary, member, value);
            } else {
                dictionary[member] = value;
            }
        },

        _likeSearchInDone = function /*gpf:inline*/ (array, a, b) {
            var
                idx,
                ia,
                ib,
                len = array.length;
            for (idx = 0; idx < len; ++idx) {
                ia = array[idx].a;
                ib = array[idx].b;
                if ((ia === a && ib === b) || (ib === a && ia === b)) {
                    return idx;
                }
            }
            return undefined;
        },

        _likeTypes = function /*gpf:inline*/ (a, b, alike) {
            if (alike && ("object" === typeof a || "object" === typeof b)) {
                /*
                 One of the two is an object but not the other,
                 Consider downcasting Number and String
                 */
                if (a instanceof String || b instanceof String) {
                    return a.toString() ===  b.toString();
                }
                if (a instanceof Number || b instanceof Number) {
                    return a.valueOf() ===  b.valueOf();
                }
                return false;
            }
            return false;
        },

        _likeCompareMembers = function /*gpf:inline*/ (ma, mb, alike, stacks) {
            if (ma !== mb) {
                if (typeof ma !== typeof mb && !_likeTypes(ma, mb, alike)) {
                    return false;
                }
                if (null === ma || null === mb
                    || "object" !== typeof ma) {
                    return false; // Because we know that ma !== mb
                }
                if (undefined === _likeSearchInDone(stacks.done, ma, mb)) {
                    stacks.todo.push(ma);
                    stacks.todo.push(mb);
                }
            }
            return true;
        },

        _likeMembers = function /*gpf:inline*/ (a, b, alike) {
            var
                member,
                count,
                stacks = {
                    todo: [a, b],
                    done: []
                };
            while (0 !== stacks.todo.length) {
                b = stacks.todo.pop();
                a = stacks.todo.pop();
                if (a.prototype !== b.prototype) {
                    return false;
                }
                stacks.done.push({a: a, b: b });
                count = 0;
                for (member in a) {
                    if (a.hasOwnProperty(member)) {
                        ++count;
                        if (!_likeCompareMembers(a[member], b[member], alike,
                            stacks)) {
                            return false;
                        }
                    }
                }
                for (member in b) {
                    if (b.hasOwnProperty(member)) {
                        --count;
                    }
                }
                if (0 !== count) {
                    return false;
                }
            }
            return true;
        },

        _values = {
            boolean: function (value, valueType, defaultValue) {
                if ("string" === valueType) {
                    if ("yes" === value || "true" === value) {
                        return true;
                    }
                    return 0 !== parseInt(value, 10);
                }
                if ("number" === valueType) {
                    return 0 !== value;
                }
                return defaultValue;
            },

            number:  function (value, valueType, defaultValue) {
                if ("string" === valueType) {
                    return parseFloat(value);
                }
                return defaultValue;
            },

            string: function (value, valueType, defaultValue) {
                gpf.interfaces.ignoreParameter(valueType);
                gpf.interfaces.ignoreParameter(defaultValue);
                if (value instanceof Date) {
                    return gpf.dateToComparableFormat(value);
                }
                return value.toString();
            },

            object: function (value, valueType, defaultValue) {
                if (defaultValue instanceof Date && "string" === valueType) {
                    return gpf.dateFromComparableFormat(value);
                }
                return defaultValue;
            }
        },

        // https://github.com/jshint/jshint/issues/525
        Func = Function, // avoid JSHint error

        /**
         * An empty function
         * @private
         */
        _emptyFunc = function () {
            return undefined;
        };

    /*jshint unused: false */ // Because of arguments
    /*
     * Enumerate dictionary members and call memberCallback for each of them.
     * If defaultResult is defined, memberCallback may return a result.
     * If memberCallback returns anything, the function stops and returns it.
     * Otherwise, the defaultResult is returned.
     * When defaultResult is not defined, memberCallback result is ignored.
     *
     * @param {Object|Array} dictionary
     * @param {Function} memberCallback
     * @param {*} defaultResult
     * @return {*}
     * @chainable
     * @forwardThis
     */
    gpf.each = function (dictionary, memberCallback, defaultResult) {
        if (undefined === defaultResult) {
            if (dictionary instanceof Array) {
                _arrayEach.apply(this, arguments);
            } else {
                _dictionaryEach.apply(this, arguments);
            }
            return undefined;
        }
        if (dictionary instanceof Array) {
            return _arrayEachWithResult.apply(this, arguments);
        }
        return _dictionaryEachWithResult.apply(this, arguments);
    };
    /*jshint unused: true */

    /*
     * Appends members of additionalProperties to the dictionary object.
     * If a conflict has to be handled (i.e. member exists on both objects),
     * the overwriteCallback has to handle it.
     *
     * @param {Object} dictionary
     * @param {Object} additionalProperties
     * @param {Function} overwriteCallback
     * @return {Object} the modified dictionary
     * @chainable
     */
    gpf.extend = function (dictionary, additionalProperties,
        overwriteCallback) {
        var callbackToUse;
        if (undefined === overwriteCallback) {
            callbackToUse = _assign;
        } else {
            callbackToUse = _assignOrCall;
        }
        gpf.each.apply(arguments, [additionalProperties, callbackToUse]);
        return dictionary;
    };

    gpf.extend(gpf, {

        _alpha: "abcdefghijklmnopqrstuvwxyz",
        _ALPHA: "ABCDEFGHIJKLMNOPQRSTUVWXYZ",
        _digit: "0123456789",

        _func: function (source) {
/*#ifdef(DEBUG)*/
            try {
/*#endif*/
                return new Func(source);
/*#ifdef(DEBUG)*/
            } catch (e) {
                console.error("An exception occurred compiling:\r\n"
                    + source);
                return null;
            }
/*#endif*/
        },

        /**
         * An empty function
         * return {Function}
         */
        emptyFunction: function () {
            return _emptyFunc;
        },

        /*
         * Converts the provided value to match the expectedType.
         * If not specified or impossible to do so, defaultValue is returned.
         * When expectedType is not provided, it is deduced from defaultValue.
         *
         * @param {*} value
         * @param {*} default value
         * @param {String} [expectedType=typeof defaultValue] expected type
         * @return {*}
         */
        value: function (value, defaultValue, expectedType) {
            var valueType = typeof value;
            if (!expectedType) {
                expectedType = typeof defaultValue;
            }
            if (expectedType === valueType) {
                return value;
            }
            if ("undefined" === valueType || !value) {
                return defaultValue;
            }
            return _values[expectedType](value, valueType, defaultValue);
        },

        /*
         * Compares a and b and return true if they are look-alike (all members
         * have the same type and same value).
         *
         * NOTES:
         * 14/04/2013 17:19:43
         * Generates too much recursion, changed the algorithm to avoid
         * recursion using document.body (and any kind of object that references
         * other objects) I found that it was necessary to keep track of already
         * processed objects.
         *
         * @param {*} a
         * @param {*} b
         * @param {Boolean} [alike=false] alike Allow to be tolerant on
         *        primitive types compared with their object equivalent
         * @return {Boolean}
         */
        like: function (a, b, alike) {
            if (a === b) {
                return true;
            }
            if (typeof a !== typeof b) {
                return _likeTypes(a, b, alike);
            }
            if (null === a || null === b || "object" !== typeof a) {
                return false;
            }
            return _likeMembers(a, b, alike);
        },

        /**
         * Shallow copy an object
         *
         * @param {Object} obj
         * @return {Object}
         */
        clone: function (obj) {
            /*
             * http://stackoverflow.com/questions/122102/what-is-the-most-
             * efficient-way-to-clone-an-object/5344074#5344074
             */
            return gpf.json.parse(gpf.json.stringify(obj));
        },

        /*
         * Find the first member of dictionary which value equals to value.
         *
         * @param {Object/array} dictionary
         * @param {*} value
         * @return {String/number/undefined} undefined if not found
         */
        test: function (dictionary, value) {
            var idx;
            if (dictionary instanceof Array) {
                idx = dictionary.length;
                while (idx > 0) {
                    if (dictionary[--idx] === value) {
                        return idx;
                    }
                }
            } else {
                for (idx in dictionary) {
                    if (dictionary.hasOwnProperty(idx)
                        && dictionary[idx] === value) {
                        return idx;
                    }
                }
            }
            return undefined;
        },

        /*
         * Inserts the value in the array if not already present.
         *
         * @param {Array} array
         * @param {*} value
         * @return {Array}
         * @chainable
         */
        set: function (array, value) {
            gpf.ASSERT(array instanceof Array,
                "gpf.set must be used with an Array");
            var idx = array.length;
            while (idx > 0) {
                if (array[--idx] === value) {
                    return array; // Already set
                }
            }
            array.push(value);
            return array;
        },

        /*
         * Removes the member of 'dictionary' which value equals 'value'.
         * NOTE: that the object is modified.
         *
         * @param {Object/array} dictionary
         * @param {*} value
         * @return {Object/array} dictionary
         * @chainable
         */
        clear: function (dictionary, value) {
            var idx;
            if (dictionary instanceof Array) {
                idx = dictionary.length;
                while (idx > 0) {
                    if (dictionary[--idx] === value) {
                        dictionary.splice(idx, 1);
                        break;
                    }
                }
            } else {
                for (idx in dictionary) {
                    if (dictionary.hasOwnProperty(idx)
                        && dictionary[idx] === value) {
                        break;
                    }
                }
            }
            return dictionary;
        },

        /**
         * XOR
         *
         * @param {Boolean} a
         * @param {Boolean} b
         */
        xor: function (a, b) {
            return a && !b || !a && b;
        },

        /**
         * Capitalize the string
         *
         * @param {String} that
         * @return {String}
         */
        capitalize: function (that) {
            return that.charAt(0).toUpperCase() + that.substr(1);
        },

        /**
         * Generic callback handler
         *
         * @param {Function} handler
         * @param {Object} scope
         * @constructor
         * @class gpf.Callback
         */
        Callback: function (handler, scope) {
            if (handler) {
                this._handler = handler;
            }
            if (scope) {
                this._scope = scope;
            }
        }

    });

    gpf.extend(gpf.Callback.prototype, {
        _handler: gpf._func(""),
        _scope: null,

        /**
         * Get the handler function
         *
         * @returns {Function}
         */
        handler: function () {
            return this._handler;
        },

        /**
         * Get the scope
         *
         * @returns {Object}
         */
        scope: function () {
            return this._scope;
        },

        /**
         * Executes the callback and override the scope if not defined
         *
         * @param {Object} scope Scope to apply if none set in the callback
         * @param {*} ... Forwarded to the callback handler
         * @returns {*}
         */
        call: function() {
            var
                scope = arguments[0],
                args = [],
                len = arguments.length,
                idx;
            for (idx = 1; idx < len; ++idx) {
                args.push(arguments[idx]);
            }
            return this.apply(scope, args);
        },

        /**
         * Executes the callback and override the scope if not defined
         *
         * @param {Object} scope Scope to apply if none set in the callback
         * @param {*[]} args Forwarded to the callback handler
         * @returns {*}
         */
        apply: function(scope, args) {
            var finalScope = gpf.Callback.resolveScope(this._scope || scope);
            return this._handler.apply(finalScope, args || []);
        }
    });

    gpf.extend(gpf.Callback, {

        /**
         * Resolve function scope
         *
         * @static
         */
        resolveScope: function (scope) {
            if (null === scope || undefined === scope) {
                scope = gpf.context();
            }
            return scope;
        },

        /**
         * Build a parameter array with
         * - Placeholders for known parameters
         * - Leading parameters filled wih the provided params
         *
         * @param {Number} count
         * @param {*} [params=undefined] params Additional parameters
         * appended at the end of the parameter list
         * @return {Array}
         * @static
         */
        buildParamArray: function (count, params) {
            var
                len,
                result,
                idx;
            if (params) {
                len = params.length;
                result = new Array(count + len);
                for (idx = 0; idx < len; ++idx) {
                    result[count] = params[idx];
                    ++count;
                }
            } else {
                result = new Array(count);
            }
            return result;
        },

        /**
         * Helper to call a function with a variable list of parameters
         *
         * @param {Function} callback
         * @param {Object} scope
         * @param {Array} paramArray array of parameters built with
         * gpf.Callback.buildParamArray
         * @param {...*} var_args
         * @return {*}
         */
        doApply: function (callback, scope, paramArray) {
            var
                len = arguments.length,
                idx = 3,
                paramIdx = 0;
            while (idx < len) {
                paramArray[paramIdx] = arguments[idx];
                ++idx;
                ++paramIdx;
            }
            return callback.apply(scope, paramArray);
        },

        /**
         * Get a method that is bound to the object
         *
         * @param {Object} obj
         * @param {String} method
         * @returns {*}
         */
        bind: function (obj, method) {
            gpf.ASSERT("string" === typeof method,
                "Provide method name");
            var _boundMember = method + ":boundToThis";
            gpf.ASSERT("function" === typeof obj[method],
                "Only methods can be bound");
            if (undefined === obj[_boundMember]) {
                obj[_boundMember] = function () {
                    return obj[method].apply(obj, arguments);
                };
            }
            return obj[_boundMember];
        }

    });

/*#ifndef(UMD)*/
}()); /* End of privacy scope */
/*#endif*/<commit_msg>Added empty function factorisation<commit_after>/*#ifndef(UMD)*/
(function () { /* Begin of privacy scope */
    "use strict";
/*#endif*/

/*
    This package contains general helpers that will be used everywhere else
*/

    var
        _arrayEachWithResult = function (array, memberCallback, defaultResult) {
            var
                result,
                len = array.length,
                idx;
            for (idx = 0; idx < len; ++idx) {
                result = memberCallback.apply(this, [idx, array[idx]]);
                if (undefined !== result) {
                    return result;
                }
            }
            return defaultResult;
        },

        _arrayEach = function (array, memberCallback) {
            var
                len = array.length,
                idx;
            for (idx = 0; idx < len; ++idx) {
                memberCallback.apply(this, [idx, array[idx]]);
            }
        },

        _dictionaryEachWithResult = function (dictionary, memberCallback,
            defaultResult) {
            var
                result,
                member;
            for (member in dictionary) {
                if (dictionary.hasOwnProperty(member)) {
                    result = memberCallback.apply(this, [member,
                        dictionary[member]]);
                    if (undefined !== result) {
                        return result;
                    }
                }
            }
            return defaultResult;
        },

        _dictionaryEach = function (dictionary, memberCallback) {
            var
                member;
            for (member in dictionary) {
                if (dictionary.hasOwnProperty(member)) {
                    memberCallback.apply(this, [member, dictionary[member]]);
                }
            }
        },

        _assign = function (member, value) {
            // this = gpf.extend's arguments
            // this[0] is dictionary
            this[0][member] = value;
        },

        _assignOrCall = function (member, value) {
            // this = gpf.extend's arguments
            var
                dictionary = this[0],
                overwriteCallback = this[2];
            // TODO: see if in is faster
            if (undefined !== dictionary[member]) {
                overwriteCallback(dictionary, member, value);
            } else {
                dictionary[member] = value;
            }
        },

        _likeSearchInDone = function /*gpf:inline*/ (array, a, b) {
            var
                idx,
                ia,
                ib,
                len = array.length;
            for (idx = 0; idx < len; ++idx) {
                ia = array[idx].a;
                ib = array[idx].b;
                if ((ia === a && ib === b) || (ib === a && ia === b)) {
                    return idx;
                }
            }
            return undefined;
        },

        _likeTypes = function /*gpf:inline*/ (a, b, alike) {
            if (alike && ("object" === typeof a || "object" === typeof b)) {
                /*
                 One of the two is an object but not the other,
                 Consider downcasting Number and String
                 */
                if (a instanceof String || b instanceof String) {
                    return a.toString() ===  b.toString();
                }
                if (a instanceof Number || b instanceof Number) {
                    return a.valueOf() ===  b.valueOf();
                }
                return false;
            }
            return false;
        },

        _likeCompareMembers = function /*gpf:inline*/ (ma, mb, alike, stacks) {
            if (ma !== mb) {
                if (typeof ma !== typeof mb && !_likeTypes(ma, mb, alike)) {
                    return false;
                }
                if (null === ma || null === mb
                    || "object" !== typeof ma) {
                    return false; // Because we know that ma !== mb
                }
                if (undefined === _likeSearchInDone(stacks.done, ma, mb)) {
                    stacks.todo.push(ma);
                    stacks.todo.push(mb);
                }
            }
            return true;
        },

        _likeMembers = function /*gpf:inline*/ (a, b, alike) {
            var
                member,
                count,
                stacks = {
                    todo: [a, b],
                    done: []
                };
            while (0 !== stacks.todo.length) {
                b = stacks.todo.pop();
                a = stacks.todo.pop();
                if (a.prototype !== b.prototype) {
                    return false;
                }
                stacks.done.push({a: a, b: b });
                count = 0;
                for (member in a) {
                    if (a.hasOwnProperty(member)) {
                        ++count;
                        if (!_likeCompareMembers(a[member], b[member], alike,
                            stacks)) {
                            return false;
                        }
                    }
                }
                for (member in b) {
                    if (b.hasOwnProperty(member)) {
                        --count;
                    }
                }
                if (0 !== count) {
                    return false;
                }
            }
            return true;
        },

        _values = {
            boolean: function (value, valueType, defaultValue) {
                if ("string" === valueType) {
                    if ("yes" === value || "true" === value) {
                        return true;
                    }
                    return 0 !== parseInt(value, 10);
                }
                if ("number" === valueType) {
                    return 0 !== value;
                }
                return defaultValue;
            },

            number:  function (value, valueType, defaultValue) {
                if ("string" === valueType) {
                    return parseFloat(value);
                }
                return defaultValue;
            },

            string: function (value, valueType, defaultValue) {
                gpf.interfaces.ignoreParameter(valueType);
                gpf.interfaces.ignoreParameter(defaultValue);
                if (value instanceof Date) {
                    return gpf.dateToComparableFormat(value);
                }
                return value.toString();
            },

            object: function (value, valueType, defaultValue) {
                if (defaultValue instanceof Date && "string" === valueType) {
                    return gpf.dateFromComparableFormat(value);
                }
                return defaultValue;
            }
        },

        // https://github.com/jshint/jshint/issues/525
        Func = Function, // avoid JSHint error

        /**
         * An empty function
         * @private
         */
        _emptyFunc = function () {
            return undefined;
        };

    /*jshint unused: false */ // Because of arguments
    /*
     * Enumerate dictionary members and call memberCallback for each of them.
     * If defaultResult is defined, memberCallback may return a result.
     * If memberCallback returns anything, the function stops and returns it.
     * Otherwise, the defaultResult is returned.
     * When defaultResult is not defined, memberCallback result is ignored.
     *
     * @param {Object|Array} dictionary
     * @param {Function} memberCallback
     * @param {*} defaultResult
     * @return {*}
     * @chainable
     * @forwardThis
     */
    gpf.each = function (dictionary, memberCallback, defaultResult) {
        if (undefined === defaultResult) {
            if (dictionary instanceof Array) {
                _arrayEach.apply(this, arguments);
            } else {
                _dictionaryEach.apply(this, arguments);
            }
            return undefined;
        }
        if (dictionary instanceof Array) {
            return _arrayEachWithResult.apply(this, arguments);
        }
        return _dictionaryEachWithResult.apply(this, arguments);
    };
    /*jshint unused: true */

    /*
     * Appends members of additionalProperties to the dictionary object.
     * If a conflict has to be handled (i.e. member exists on both objects),
     * the overwriteCallback has to handle it.
     *
     * @param {Object} dictionary
     * @param {Object} additionalProperties
     * @param {Function} overwriteCallback
     * @return {Object} the modified dictionary
     * @chainable
     */
    gpf.extend = function (dictionary, additionalProperties,
        overwriteCallback) {
        var callbackToUse;
        if (undefined === overwriteCallback) {
            callbackToUse = _assign;
        } else {
            callbackToUse = _assignOrCall;
        }
        gpf.each.apply(arguments, [additionalProperties, callbackToUse]);
        return dictionary;
    };

    gpf.extend(gpf, {

        _alpha: "abcdefghijklmnopqrstuvwxyz",
        _ALPHA: "ABCDEFGHIJKLMNOPQRSTUVWXYZ",
        _digit: "0123456789",

        _func: function (source) {
/*#ifdef(DEBUG)*/
            try {
/*#endif*/
                if (!source) {
                    return _emptyFunc;
                } else {
                    return new Func(source);
                }
/*#ifdef(DEBUG)*/
            } catch (e) {
                console.error("An exception occurred compiling:\r\n"
                    + source);
                return null;
            }
/*#endif*/
        },

        /**
         * An empty function
         * return {Function}
         */
        emptyFunction: function () {
            return _emptyFunc;
        },

        /*
         * Converts the provided value to match the expectedType.
         * If not specified or impossible to do so, defaultValue is returned.
         * When expectedType is not provided, it is deduced from defaultValue.
         *
         * @param {*} value
         * @param {*} default value
         * @param {String} [expectedType=typeof defaultValue] expected type
         * @return {*}
         */
        value: function (value, defaultValue, expectedType) {
            var valueType = typeof value;
            if (!expectedType) {
                expectedType = typeof defaultValue;
            }
            if (expectedType === valueType) {
                return value;
            }
            if ("undefined" === valueType || !value) {
                return defaultValue;
            }
            return _values[expectedType](value, valueType, defaultValue);
        },

        /*
         * Compares a and b and return true if they are look-alike (all members
         * have the same type and same value).
         *
         * NOTES:
         * 14/04/2013 17:19:43
         * Generates too much recursion, changed the algorithm to avoid
         * recursion using document.body (and any kind of object that references
         * other objects) I found that it was necessary to keep track of already
         * processed objects.
         *
         * @param {*} a
         * @param {*} b
         * @param {Boolean} [alike=false] alike Allow to be tolerant on
         *        primitive types compared with their object equivalent
         * @return {Boolean}
         */
        like: function (a, b, alike) {
            if (a === b) {
                return true;
            }
            if (typeof a !== typeof b) {
                return _likeTypes(a, b, alike);
            }
            if (null === a || null === b || "object" !== typeof a) {
                return false;
            }
            return _likeMembers(a, b, alike);
        },

        /**
         * Shallow copy an object
         *
         * @param {Object} obj
         * @return {Object}
         */
        clone: function (obj) {
            /*
             * http://stackoverflow.com/questions/122102/what-is-the-most-
             * efficient-way-to-clone-an-object/5344074#5344074
             */
            return gpf.json.parse(gpf.json.stringify(obj));
        },

        /*
         * Find the first member of dictionary which value equals to value.
         *
         * @param {Object/array} dictionary
         * @param {*} value
         * @return {String/number/undefined} undefined if not found
         */
        test: function (dictionary, value) {
            var idx;
            if (dictionary instanceof Array) {
                idx = dictionary.length;
                while (idx > 0) {
                    if (dictionary[--idx] === value) {
                        return idx;
                    }
                }
            } else {
                for (idx in dictionary) {
                    if (dictionary.hasOwnProperty(idx)
                        && dictionary[idx] === value) {
                        return idx;
                    }
                }
            }
            return undefined;
        },

        /*
         * Inserts the value in the array if not already present.
         *
         * @param {Array} array
         * @param {*} value
         * @return {Array}
         * @chainable
         */
        set: function (array, value) {
            gpf.ASSERT(array instanceof Array,
                "gpf.set must be used with an Array");
            var idx = array.length;
            while (idx > 0) {
                if (array[--idx] === value) {
                    return array; // Already set
                }
            }
            array.push(value);
            return array;
        },

        /*
         * Removes the member of 'dictionary' which value equals 'value'.
         * NOTE: that the object is modified.
         *
         * @param {Object/array} dictionary
         * @param {*} value
         * @return {Object/array} dictionary
         * @chainable
         */
        clear: function (dictionary, value) {
            var idx;
            if (dictionary instanceof Array) {
                idx = dictionary.length;
                while (idx > 0) {
                    if (dictionary[--idx] === value) {
                        dictionary.splice(idx, 1);
                        break;
                    }
                }
            } else {
                for (idx in dictionary) {
                    if (dictionary.hasOwnProperty(idx)
                        && dictionary[idx] === value) {
                        break;
                    }
                }
            }
            return dictionary;
        },

        /**
         * XOR
         *
         * @param {Boolean} a
         * @param {Boolean} b
         */
        xor: function (a, b) {
            return a && !b || !a && b;
        },

        /**
         * Capitalize the string
         *
         * @param {String} that
         * @return {String}
         */
        capitalize: function (that) {
            return that.charAt(0).toUpperCase() + that.substr(1);
        },

        /**
         * Generic callback handler
         *
         * @param {Function} handler
         * @param {Object} scope
         * @constructor
         * @class gpf.Callback
         */
        Callback: function (handler, scope) {
            if (handler) {
                this._handler = handler;
            }
            if (scope) {
                this._scope = scope;
            }
        }

    });

    gpf.extend(gpf.Callback.prototype, {
        _handler: gpf._func(""),
        _scope: null,

        /**
         * Get the handler function
         *
         * @returns {Function}
         */
        handler: function () {
            return this._handler;
        },

        /**
         * Get the scope
         *
         * @returns {Object}
         */
        scope: function () {
            return this._scope;
        },

        /**
         * Executes the callback and override the scope if not defined
         *
         * @param {Object} scope Scope to apply if none set in the callback
         * @param {*} ... Forwarded to the callback handler
         * @returns {*}
         */
        call: function() {
            var
                scope = arguments[0],
                args = [],
                len = arguments.length,
                idx;
            for (idx = 1; idx < len; ++idx) {
                args.push(arguments[idx]);
            }
            return this.apply(scope, args);
        },

        /**
         * Executes the callback and override the scope if not defined
         *
         * @param {Object} scope Scope to apply if none set in the callback
         * @param {*[]} args Forwarded to the callback handler
         * @returns {*}
         */
        apply: function(scope, args) {
            var finalScope = gpf.Callback.resolveScope(this._scope || scope);
            return this._handler.apply(finalScope, args || []);
        }
    });

    gpf.extend(gpf.Callback, {

        /**
         * Resolve function scope
         *
         * @static
         */
        resolveScope: function (scope) {
            if (null === scope || undefined === scope) {
                scope = gpf.context();
            }
            return scope;
        },

        /**
         * Build a parameter array with
         * - Placeholders for known parameters
         * - Leading parameters filled wih the provided params
         *
         * @param {Number} count
         * @param {*} [params=undefined] params Additional parameters
         * appended at the end of the parameter list
         * @return {Array}
         * @static
         */
        buildParamArray: function (count, params) {
            var
                len,
                result,
                idx;
            if (params) {
                len = params.length;
                result = new Array(count + len);
                for (idx = 0; idx < len; ++idx) {
                    result[count] = params[idx];
                    ++count;
                }
            } else {
                result = new Array(count);
            }
            return result;
        },

        /**
         * Helper to call a function with a variable list of parameters
         *
         * @param {Function} callback
         * @param {Object} scope
         * @param {Array} paramArray array of parameters built with
         * gpf.Callback.buildParamArray
         * @param {...*} var_args
         * @return {*}
         */
        doApply: function (callback, scope, paramArray) {
            var
                len = arguments.length,
                idx = 3,
                paramIdx = 0;
            while (idx < len) {
                paramArray[paramIdx] = arguments[idx];
                ++idx;
                ++paramIdx;
            }
            return callback.apply(scope, paramArray);
        },

        /**
         * Get a method that is bound to the object
         *
         * @param {Object} obj
         * @param {String} method
         * @returns {*}
         */
        bind: function (obj, method) {
            gpf.ASSERT("string" === typeof method,
                "Provide method name");
            var _boundMember = method + ":boundToThis";
            gpf.ASSERT("function" === typeof obj[method],
                "Only methods can be bound");
            if (undefined === obj[_boundMember]) {
                obj[_boundMember] = function () {
                    return obj[method].apply(obj, arguments);
                };
            }
            return obj[_boundMember];
        }

    });

/*#ifndef(UMD)*/
}()); /* End of privacy scope */
/*#endif*/<|endoftext|>


<commit_before>var parser = require('./parser');
var TileMap = require('./tilemap');
var Direction = require('./direction');
var Interpreter = require('./interpreter');

function Cursor(cursor) {
  this.id = cursor.id;
  this.segment = cursor.segment;
  this.x = cursor.x;
  this.y = cursor.y;
  this.direction = {};
  this.direction.x = cursor.direction.x;
  this.direction.y = cursor.direction.y;
  this.selected = cursor.selected;
  this.memory = cursor.memory.slice(); // Copy machine state.
  this.seek = cursor.seek || false;
  this.visit = 0;
}

// Returns initial machine cursor
Cursor.init = function() {
  var initialMemory = [];
  for(var i = 0; i < 28; ++i) initialMemory.push(0);
  return new Cursor({
    id: 0,
    segment: 0,
    x: 0,
    y: 0,
    direction: {
      x: 0,
      y: 1
    },
    selected: 0,
    memory: initialMemory
  });
}

// Calculates memory diff
Cursor.prototype.diff = function(other) {
  return this.memory.map(function(value, key) {
    return value - other.memory[key];
  });
}

// Predicts the path of the code

function Predictor(code) {
  if (typeof code == 'string') {
    this.map = parser.parse(code);
  } else {
    this.map = code;
  }
  // Create segment heading map
  this.reset();
  this.updated = [];
}

Predictor.prototype.reset = function() {
  this.segments = [];
  this.stack = [];
  this.headingMap = new TileMap(this.map.width, this.map.height);
  var cursor = Cursor.init();
  var segment = [];
  segment.push(cursor);
  this.segments.push(segment);
  this.stack.push(cursor);
}

Predictor.prototype.next = function() {
  if (this.stack.length === 0) return false;
  // Store previous cursor.. I doubt it'll be used actually.
  var oldCursor = this.stack.pop();
  var cursor = new Cursor(oldCursor);
  if(!cursor.seek) oldCursor.then = cursor;
  // Fetch current segment
  var segment = this.segments[cursor.segment];
  var direction = cursor.direction;
  // Store previous reversed direction
  var preDir = Direction.convertToBits(-direction.x, -direction.y);
  var tile = this.map.get(cursor.x, cursor.y);
  var headingTile = this.headingMap.get(cursor.x, cursor.y);
  // Create headingTile if not exists.
  if (headingTile == null) {
    headingTile = {};
    this.headingMap.set(cursor.x, cursor.y, headingTile);
  }
  // Continues execution in new segment if this is set.
  var newSegment = false;
  // Stop execution if this is set.
  var stop = false;
  if (tile != null) {
    // Fetch x, y value from tile's direction
    var tileDir = Direction.map[tile.direction];
    // Calculate the direction where the cursor will go
    direction.x = Direction.calculate(direction.x, tileDir.x);
    direction.y = Direction.calculate(direction.y, tileDir.y);
    // Fetch command
    var command = Interpreter.CommandMap[tile.command];
    // Just skip if command is null
    if(command != null) {
      if(cursor.memory[cursor.selected] >= command.data) {
        cursor.memory[cursor.selected] -= command.data; // Data we lose
        cursor.memory[cursor.selected] += command.output; // Data we get
        if (tile.command == 'select') {
          cursor.selected = tile.data;
        }
        if (tile.command == 'move') {
          cursor.memory[tile.data] ++;
        }
        if (tile.command == 'end') {
          // End of segment; Stop processing.
          // Techincally 'end' operator requires one data to stop,
          // But most programs won't work with that.
          stop = true;
        }
        if (tile.command == 'condition') {
          // Condition; Always create new segment.
          // Simply create new cursor with new segment, flip direction,
          // move position and save it.
          var newCursor = new Cursor(cursor);
          newCursor.direction.x = -direction.x;
          newCursor.direction.y = -direction.y;
          oldCursor.otherwise = newCursor;
          this.processCursor(newCursor, segment, tile, headingTile,
            stop, true, preDir);
        }
      } else {
        // Underflow has occurred; Go to opposite direction.
        direction.x = -direction.x;
        direction.y = -direction.y;
        // For consistency, this should create a new segment;
        newSegment = true;
      }
    }
  }
  this.processCursor(cursor, segment, tile, headingTile,
    stop, newSegment, preDir);
  return this.stack.length > 0;
};

Predictor.prototype.processCursor = function(cursor, segment, tile, headingTile,
  stop, newSegment, preDir) {
  // Don't save current cursor and increment it if this is set.
  var seek = false;
  var direction = cursor.direction;
  var directionBits = Direction.convertToBits(direction.x, direction.y, true);
  if (headingTile[directionBits]) {
    var before = headingTile[directionBits];
    before.visit ++;
    // Continue cursor in seek mode if memory has less data than before.
    var hasLess = !cursor.memory.every(function(value, key) {
      var diff = before.memory[key] - value;
      // Maximum memory space
      if(diff <= 0 && value >= 16) cursor.memory[key] = 16;
      // Check 16 times, then just check if it has less data.
      if(before.visit > 16) return diff <= 0;
      else return diff == 0;
    });
    before.memory = cursor.memory.slice();
    seek = hasLess;
    // Copy segment and ID to honor condition
    // But 'merging' cursor shouldn't.
    if(cursor.seek) {
      cursor.id = before.id;
      cursor.segment = before.segment;
    }
    if(!hasLess) stop = true;
  }
  if (!stop) {
    if(!seek) {
      if (newSegment) {
        cursor.id = -1;
        cursor.segment = this.segments.length;
        segment = [];
        this.segments.push(segment);
      }
      // Increment cursor id to avoid confliction
      cursor.id ++;
      // Insert segment into segment.
      segment.push(cursor);
      // Write current cursor;
      headingTile[directionBits] = cursor;
    }
    cursor.seek = seek;
    // Push current cursor to stack.
    if(seek) this.stack.unshift(cursor);
    else this.stack.push(cursor);
  }
  // Since this is the copy of original object, we can safely modify it.
  // This communicates with the 'old' data protocol, for now.
  Direction.process(cursor, this.map, direction, preDir, this.updated,
    cursor.segment, false);
}

// Assembly command map to support ashembly
var AssemblyMap = {
  'end': 'halt',
  'add': 'add',
  'multiply': 'mul',
  'subtract': 'sub',
  'divide': 'div',
  'mod': 'mod',
  'pop': 'pop',
  'pop-number': 'popnum',
  'pop-unicode': 'popchar',
  'push': 'push',
  'push-number': 'pushnum',
  'push-unicode': 'pushchar',
  'copy': 'dup',
  'flip': 'swap',
  'select': 'sel',
  'move': 'mov',
  'compare': 'cmp',
  'condition': 'brz'
};

// Converts code to Assembly. Just for fun! :D
Predictor.prototype.assembly = function() {
  var resolves = [];
  var labels = [];
  var codes = [];
  // Start reading code from segment 0, id 0
  for(var segmentId = 0; segmentId < this.segments.length; ++segmentId) {
    var segment = this.segments[segmentId];
    for(var id = 0; id < segment.length; ++id) {
      var cursor = segment[id];
      cursor.index = codes.length;
      var headingTile = this.headingMap.get(cursor.x, cursor.y);
      var tile = this.map.get(cursor.x, cursor.y);
      if(tile == null) continue;
      if(tile.command != 'none') {
        var command = Interpreter.CommandMap[tile.command];
        var flipBit = Direction.convertToBits(-cursor.direction.x,
          -cursor.direction.y, true);
        if(headingTile[flipBit]) {
          if(command.data > 0) {
            var code = ['brpop'+command.data, headingTile[flipBit]];
            codes.push(code);
            resolves.push(code);
          }
        }
        var code = [AssemblyMap[tile.command]];
        if(tile.command == 'push') code[1] = tile.data;
        if(tile.command == 'select') code[1] = tile.data;
        if(tile.command == 'move') code[1] = tile.data;
        if(tile.command == 'condition') {
          code[1] = cursor.otherwise;
          resolves.push(code);
        }
        codes.push(code);
      } else {
        if(id == segment.length - 1) {
          // Fetch x, y value from tile's direction
          var tileDir = Direction.map[tile.direction];
          // Calculate the direction where the cursor will go
          var dirX = Direction.calculate(cursor.direction.x, tileDir.x);
          var dirY = Direction.calculate(cursor.direction.y, tileDir.y);
          var dirBit = Direction.convertToBits(dirX, dirY, true);
          var targetTile = headingTile[dirBit];
          if(!targetTile) continue;
          var code = ['jmp', targetTile];
          codes.push(code);
          resolves.push(code);
        }
      }
    }
  }
  for(var i = 0; i < resolves.length; ++i) {
    var idx = resolves[i][1].index;
    if(labels.indexOf(idx) == -1) labels.push(idx);
    resolves[i][1] = 'p'+labels.indexOf(idx);
  }
  var returned = [];
  codes.forEach(function(v, k) {
    var label = labels.indexOf(k);
    if(label != -1) {
      returned.push(":p"+label);
    }
    returned.push(v.join(' '));
  });
  console.log(returned.join("\n"));
}

module.exports = Predictor;
<commit_msg>Change Predictor to make new segments much longer<commit_after>var parser = require('./parser');
var TileMap = require('./tilemap');
var Direction = require('./direction');
var Interpreter = require('./interpreter');

function Cursor(cursor) {
  this.id = cursor.id;
  this.segment = cursor.segment;
  this.x = cursor.x;
  this.y = cursor.y;
  this.direction = {};
  this.direction.x = cursor.direction.x;
  this.direction.y = cursor.direction.y;
  this.selected = cursor.selected;
  this.memory = cursor.memory.slice(); // Copy machine state.
  this.seek = cursor.seek || false;
  this.visit = 0;
}

// Returns initial machine cursor
Cursor.init = function() {
  var initialMemory = [];
  for(var i = 0; i < 28; ++i) initialMemory.push(0);
  return new Cursor({
    id: 0,
    segment: 0,
    x: 0,
    y: 0,
    direction: {
      x: 0,
      y: 1
    },
    selected: 0,
    memory: initialMemory
  });
}

// Calculates memory diff
Cursor.prototype.diff = function(other) {
  return this.memory.map(function(value, key) {
    return value - other.memory[key];
  });
}

// Predicts the path of the code

function Predictor(code) {
  if (typeof code == 'string') {
    this.map = parser.parse(code);
  } else {
    this.map = code;
  }
  // Create segment heading map
  this.reset();
  this.updated = [];
}

Predictor.prototype.reset = function() {
  this.segments = [];
  this.stack = [];
  this.headingMap = new TileMap(this.map.width, this.map.height);
  var cursor = Cursor.init();
  var segment = [];
  segment.push(cursor);
  this.segments.push(segment);
  this.stack.push(cursor);
}

Predictor.prototype.next = function() {
  if (this.stack.length === 0) return false;
  // Store previous cursor.. I doubt it'll be used actually.
  var oldCursor = this.stack.pop();
  var cursor = new Cursor(oldCursor);
  if(!cursor.seek) oldCursor.then = cursor;
  // Fetch current segment
  var segment = this.segments[cursor.segment];
  var direction = cursor.direction;
  // Store previous reversed direction
  var preDir = Direction.convertToBits(-direction.x, -direction.y);
  var tile = this.map.get(cursor.x, cursor.y);
  var headingTile = this.headingMap.get(cursor.x, cursor.y);
  // Create headingTile if not exists.
  if (headingTile == null) {
    headingTile = {};
    this.headingMap.set(cursor.x, cursor.y, headingTile);
  }
  // Continues execution in new segment if this is set.
  var newSegment = false;
  // Stop execution if this is set.
  var stop = false;
  if (tile != null) {
    // Fetch x, y value from tile's direction
    var tileDir = Direction.map[tile.direction];
    // Calculate the direction where the cursor will go
    direction.x = Direction.calculate(direction.x, tileDir.x);
    direction.y = Direction.calculate(direction.y, tileDir.y);
    // Fetch command
    var command = Interpreter.CommandMap[tile.command];
    // Just skip if command is null
    if(command != null) {
      if(cursor.memory[cursor.selected] >= command.data) {
        cursor.memory[cursor.selected] -= command.data; // Data we lose
        cursor.memory[cursor.selected] += command.output; // Data we get
        if (tile.command == 'select') {
          cursor.selected = tile.data;
        }
        if (tile.command == 'move') {
          cursor.memory[tile.data] ++;
        }
        if (tile.command == 'end') {
          // End of segment; Stop processing.
          // Techincally 'end' operator requires one data to stop,
          // But most programs won't work with that.
          stop = true;
        }
        if (tile.command == 'condition') {
          // Condition; Always create new segment.
          // Simply create new cursor with new segment, flip direction,
          // move position and save it.
          var newCursor = new Cursor(cursor);
          newCursor.direction.x = -direction.x;
          newCursor.direction.y = -direction.y;
          oldCursor.otherwise = newCursor;
          this.processCursor(newCursor, segment, tile, headingTile,
            stop, true, preDir);
        }
      } else {
        // Underflow has occurred; Go to opposite direction.
        direction.x = -direction.x;
        direction.y = -direction.y;
        // For consistency, this should create a new segment;
        newSegment = true;
      }
    }
  }
  this.processCursor(cursor, segment, tile, headingTile,
    stop, newSegment, preDir);
  return this.stack.length > 0;
};

Predictor.prototype.processCursor = function(cursor, segment, tile, headingTile,
  stop, newSegment, preDir) {
  // Don't save current cursor and increment it if this is set.
  var seek = false;
  var direction = cursor.direction;
  var directionBits = Direction.convertToBits(direction.x, direction.y, true);
  if (headingTile[directionBits]) {
    var before = headingTile[directionBits];
    before.visit ++;
    // Continue cursor in seek mode if memory has less data than before.
    var hasLess = !cursor.memory.every(function(value, key) {
      var diff = before.memory[key] - value;
      // Maximum memory space
      if(diff <= 0 && value >= 16) cursor.memory[key] = 16;
      // Check 16 times, then just check if it has less data.
      if(before.visit > 16) return diff <= 0;
      else return diff == 0;
    });
    before.memory = cursor.memory.slice();
    seek = hasLess;
    // Copy segment and ID to honor condition
    // But 'merging' cursor shouldn't.
    if(cursor.seek) {
      cursor.id = before.id;
      cursor.segment = before.segment;
    }
    if(!hasLess) stop = true;
  }
  if (!stop) {
    if(!seek) {
      if (newSegment) {
        cursor.id = -1;
        cursor.segment = this.segments.length;
        segment = [];
        this.segments.push(segment);
      }
      // Increment cursor id to avoid confliction
      cursor.id ++;
      // Insert segment into segment.
      segment.push(cursor);
      // Write current cursor;
      headingTile[directionBits] = cursor;
    }
    cursor.seek = seek;
    // Push current cursor to stack.
    this.stack.unshift(cursor);
  }
  // Since this is the copy of original object, we can safely modify it.
  // This communicates with the 'old' data protocol, for now.
  Direction.process(cursor, this.map, direction, preDir, this.updated,
    cursor.segment, false);
}

// Assembly command map to support ashembly
var AssemblyMap = {
  'end': 'halt',
  'add': 'add',
  'multiply': 'mul',
  'subtract': 'sub',
  'divide': 'div',
  'mod': 'mod',
  'pop': 'pop',
  'pop-number': 'popnum',
  'pop-unicode': 'popchar',
  'push': 'push',
  'push-number': 'pushnum',
  'push-unicode': 'pushchar',
  'copy': 'dup',
  'flip': 'swap',
  'select': 'sel',
  'move': 'mov',
  'compare': 'cmp',
  'condition': 'brz'
};

// Converts code to Assembly. Just for fun! :D
Predictor.prototype.assembly = function() {
  var resolves = [];
  var labels = [];
  var codes = [];
  // Start reading code from segment 0, id 0
  for(var segmentId = 0; segmentId < this.segments.length; ++segmentId) {
    var segment = this.segments[segmentId];
    for(var id = 0; id < segment.length; ++id) {
      var cursor = segment[id];
      cursor.index = codes.length;
      var headingTile = this.headingMap.get(cursor.x, cursor.y);
      var tile = this.map.get(cursor.x, cursor.y);
      if(tile == null) continue;
      if(tile.command != 'none') {
        var command = Interpreter.CommandMap[tile.command];
        var flipBit = Direction.convertToBits(-cursor.direction.x,
          -cursor.direction.y, true);
        if(headingTile[flipBit]) {
          if(command.data > 0) {
            var code = ['brpop'+command.data, headingTile[flipBit]];
            codes.push(code);
            resolves.push(code);
          }
        }
        var code = [AssemblyMap[tile.command]];
        if(tile.command == 'push') code[1] = tile.data;
        if(tile.command == 'select') code[1] = tile.data;
        if(tile.command == 'move') code[1] = tile.data;
        if(tile.command == 'condition') {
          code[1] = cursor.otherwise;
          resolves.push(code);
        }
        codes.push(code);
      } else {
        if(id == segment.length - 1) {
          // Fetch x, y value from tile's direction
          var tileDir = Direction.map[tile.direction];
          // Calculate the direction where the cursor will go
          var dirX = Direction.calculate(cursor.direction.x, tileDir.x);
          var dirY = Direction.calculate(cursor.direction.y, tileDir.y);
          var dirBit = Direction.convertToBits(dirX, dirY, true);
          var targetTile = headingTile[dirBit];
          if(!targetTile) continue;
          var code = ['jmp', targetTile];
          codes.push(code);
          resolves.push(code);
        }
      }
    }
  }
  for(var i = 0; i < resolves.length; ++i) {
    var idx = resolves[i][1].index;
    if(labels.indexOf(idx) == -1) labels.push(idx);
    resolves[i][1] = 'p'+labels.indexOf(idx);
  }
  var returned = [];
  codes.forEach(function(v, k) {
    var label = labels.indexOf(k);
    if(label != -1) {
      returned.push(":p"+label);
    }
    returned.push(v.join(' '));
  });
  console.log(returned.join("\n"));
}

module.exports = Predictor;
<|endoftext|>


<commit_before>define([
	'goo/loaders/handlers/ConfigHandler',
	'goo/util/rsvp',
	'goo/scripts/OrbitCamControlScript',
	'goo/scriptpack/OrbitNPanControlScript',
	'goo/scriptpack/FlyControlScript',
	'goo/scriptpack/WASDControlScript',
	'goo/scriptpack/BasicControlScript',
	'goo/util/PromiseUtil',
	'goo/util/ObjectUtil',
	'goo/entities/SystemBus',

	'goo/scripts/ScriptUtils',
	'goo/scripts/Scripts'
],
/** @lends */
function (
	ConfigHandler,
	RSVP,
	OrbitCamControlScript,
	OrbitNPanControlScript,
	FlyControlScript,
	WASDControlScript,
	BasicControlScript,
	PromiseUtil,
	_,
	SystemBus,

	ScriptUtils,
	Scripts
) {
	'use strict';

	/**
	* @class
	* @private
	*/
	function ScriptHandler() {
		ConfigHandler.apply(this, arguments);
		this._bodyCache = {};
		this._dependencyPromises = {};
		this._currentScriptLoading = null;
		this._addGlobalErrorListener();
	}

	ScriptHandler.prototype = Object.create(ConfigHandler.prototype);
	ScriptHandler.prototype.constructor = ScriptHandler;
	ConfigHandler._registerClass('script', ScriptHandler);

	/**
	 * Creates a script data wrapper object to be used in the engine
	 */
	ScriptHandler.prototype._create = function () {
		return {
			externals: {},
			setup: null,
			update: null,
			run: null,
			cleanup: null,
			parameters: {},
			name: null
		};
	};

	/**
	 * Remove this script from the cache, and runs the cleanup method of the script.
	 * @param {string} ref the script guid
	 */
	ScriptHandler.prototype._remove = function (ref) {
		var script = this._objects[ref];
		if (script && script.cleanup && script.context) {
			try {
				script.cleanup(script.parameters, script.context, Scripts.getClasses());
			} catch (e) {
				// Some cleanup error
			}
		}
		delete this._objects[ref];
		delete this._bodyCache[ref];
	};

	/**
	 * Update a user-defined script (not a script available in the engine).
	 * If the new body (in the data model config) differs from the cached body,
	 * the script will be reloaded (by means of a script tag).
	 *
	 * @param {object} script the cached engine script object
	 * @param {object} config the data model config
	 */
	ScriptHandler.prototype._updateFromCustom = function (script, config) {
		// No change, do nothing
		if (this._bodyCache[config.id] === config.body) { return script; }

		delete script.errors;
		this._bodyCache[config.id] = config.body;

		// delete the old script tag and add a new one
		var oldScriptElement = document.getElementById(ScriptHandler.DOM_ID_PREFIX + config.id);
		if (oldScriptElement) {
			oldScriptElement.parentNode.removeChild(oldScriptElement);
		}

		// create this script collection if it does not exist yet
		if (!window._gooScriptFactories) {
			// this holds script factories in 'compiled' form
			window._gooScriptFactories = {};
		}

		// get a script factory in string form
		var scriptFactoryStr = [
			"window._gooScriptFactories['" + config.id + "'] = function () { 'use strict';",
			config.body,
			' var obj = {',
			'  externals: {}',
			' };',
			' if (typeof parameters !== "undefined") {',
			'  obj.externals.parameters = parameters;',
			' }',
			' if (typeof setup !== "undefined") {',
			'  obj.setup = setup;',
			' }',
			' if (typeof cleanup !== "undefined") {',
			'  obj.cleanup = cleanup;',
			' }',
			' if (typeof update !== "undefined") {',
			'  obj.update = update;',
			' }',
			' return obj;',
			'};'
		].join('\n');

		// create the element and add it to the page so the user can debug it
		// addition and execution of the script happens synchronously
		var newScriptElement = document.createElement('script');
		newScriptElement.id = ScriptHandler.DOM_ID_PREFIX + config.id;
		newScriptElement.innerHTML = scriptFactoryStr;
		newScriptElement.async = false;
		this._currentScriptLoading = config.id;
		document.body.appendChild(newScriptElement);

		var newScript = window._gooScriptFactories[config.id];
		if (newScript) {
			try {
				newScript = newScript();
				script.id = config.id;
				safeUp(newScript, script);
				script.setup = newScript.setup;
				script.update = newScript.update;
				script.cleanup = newScript.cleanup;
				script.parameters = {};
				script.enabled = false;
			} catch (e) {
				var err = {
					message: e.toString()
				};
				// TODO Test if this works across browsers
				/**/
				var m = e.stack.split('\n')[1].match(/(\d+):\d+\)$/);
				if (m) {
					err.line = parseInt(m[1], 10) - 1;
				}
				/**/
				setError(script, err);
			}
			this._currentScriptLoading = null;
		}
		// generate names from external variable names
		if (script.externals) {
			ScriptUtils.fillDefaultNames(script.externals.parameters);
		}

		return script;
	};

	/**
	 * Update a script that is from the engine. Checks if the class name has changed
	 * and if so, creates a new script object from the new class.
	 * @param {object} script needs to have a className property
	 * @param {object} config data model config
	 * @deprecated
	 */
	ScriptHandler.prototype._updateFromClass = function (script, config) {
		if (!script.externals || script.externals.name !== config.className) {
			var newScript = Scripts.create(config.className);
			if (!newScript) {
				throw new Error('Unrecognized script name');
			}
			script.id = config.id;
			script.externals = newScript.externals;
			script.setup = newScript.setup;
			script.update = newScript.update;
			script.run = newScript.run;
			script.cleanup = newScript.cleanup;
			script.parameters = newScript.parameters || {};
			script.enabled = false;

			// generate names from external variable names
			ScriptUtils.fillDefaultNames(script.externals.parameters);
		}

		return script;
	};

	ScriptHandler.prototype._update = function (ref, config, options) {
		var that = this;
		return ConfigHandler.prototype._update.call(this, ref, config, options)
		.then(function (script) {
			if (!script) { return; }

			var promises = [];

			if (config.body && config.dependencies) {
				delete script.dependencyErrors;
				_.forEach(config.dependencies, function (dependencyConfig) {
					promises.push(that._addDependency(script, dependencyConfig.url, config.id));
				}, null, 'sortValue');
			}

			return RSVP.all(promises)
			.then(function () {
				if (config.className) { // Engine script.
					that._updateFromClass(script, config, options);
				} else if (config.body) { // Custom script.
					that._updateFromCustom(script, config, options);
				}

				if (config.body) {
					SystemBus.emit('goo.scriptExternals', {
						id: config.id,
						externals: script.externals
					});
				}

				script.name = config.name;

				if (script.errors || script.dependencyErrors) {
					SystemBus.emit('goo.scriptError', {
						id: ref,
						errors: script.errors,
						dependencyErrors: script.dependencyErrors
					});
					return script;
				}
				else {
					SystemBus.emit('goo.scriptError', {id: ref, errors: null});
				}
				_.extend(script.parameters, config.options);
				return script;
			});
		});
	};

	/**
	 * Loads an external javascript lib as a dependency to this script (if it's
	 * not already loaded). If the dependency fails to load, an error is set
	 * on the script.
	 * @param {object} script config
	 * @param {string} url location of the javascript lib
	 * @param {string} scriptId the guid of the script
	 * @return {RSVP.Promise} a promise that resolves when the dependency is loaded
	 */
	ScriptHandler.prototype._addDependency = function (script, url, scriptId) {
		var that = this;
		var scriptElem = document.querySelector('script[src="' + url + '"]');
		if (scriptElem) {
			return this._dependencyPromises[url] || PromiseUtil.resolve();
		}

		scriptElem = document.createElement('script');
		scriptElem.src = url;
		scriptElem.setAttribute('data-script-id', scriptId);

		document.body.appendChild(scriptElem);

		return this._dependencyPromises[url] = PromiseUtil.createPromise(function (resolve, reject) {
			scriptElem.onload = function () {
				resolve();
				delete that._dependencyPromises[url];
			};

			scriptElem.onerror = function () {
				console.log('Dependency loading caused errors');
				var err = {
					message: 'Could not load dependency',
					file: url
				};
				setError(script, err);
				scriptElem.parentNode.removeChild(scriptElem);
				resolve();
				delete that._dependencyPromises[url];
			};
		});
	};

	/**
	 * Add a global error listener that catches script errors, and tries to match
	 * them to scripts loaded with this handler. If an error is registered, the
	 * script is reset and an error message is appended to it.
	 * @private
	 *
	 */
	ScriptHandler.prototype._addGlobalErrorListener = function () {
		var that = this;
		window.addEventListener('error', function (evt) {
			if (evt.filename) {
				var scriptElem = document.querySelector('script[src="' + evt.filename + '"]');
				if (scriptElem) {
					var scriptId = scriptElem.getAttribute('data-script-id');
					var script = that._objects[scriptId];
					if (script) {
						var error = {
							message: evt.message,
							line: evt.lineno,
							file: evt.filename
						};
						setError(script, error);
					}
					scriptElem.parentNode.removeChild(scriptElem);
				}
			}
			if (that._currentScriptLoading) {
				var oldScriptElement = document.getElementById(ScriptHandler.DOM_ID_PREFIX + that._currentScriptLoading);
				if (oldScriptElement) {
					oldScriptElement.parentNode.removeChild(oldScriptElement);
				}
				delete window._gooScriptFactories[that._currentScriptLoading];
				var script = that._objects[that._currentScriptLoading];
				var error = {
					message: evt.message,
					line: evt.lineno - 1
				};
				setError(script, error);
				that._currentScriptLoading = null;
			}
		});
	};

	var types = ['string', 'float', 'int', 'vec3', 'boolean', 'texture', 'entity'];

	/**
	 * Validate external parameters
	 * @private
	 */
	function safeUp(script, outScript) {
		var errors = script.errors || [];
		if (typeof script.externals !== 'object') {
			outScript.externals = {};
			return;
		}
		var externals = script.externals;
		if (externals.parameters && !(externals.parameters instanceof Array)) {
			errors.push('externals.parameters needs to be an array');
		}
		if (errors.length) {
			outScript.errors = errors;
			return;
		}
		if (!externals.parameters) {
			return;
		}
		outScript.externals.parameters = [];
		for (var i = 0; i < externals.parameters.length; i++) {
			var param = externals.parameters[i];
			if (typeof param.key !== 'string' || param.key.length === 0) {
				errors.push({ message: 'Parameter key needs to be a non-empty string' });
				continue;
			}
			if (param.name !== undefined && (typeof param.name !== 'string' || param.name.length === 0)) {
				errors.push({ message: 'Parameter name needs to be a non-empty string' });
				continue;
			}
			if (types.indexOf(param.type) === -1) {
				errors.push({ message: 'Parameter type needs to be one of (' + types.join(', ') + ')' });
				continue;
			}
			if (param.control !== undefined && (typeof param.control !== 'string' || param.control.length === 0)) {
				errors.push({ message: 'Parameter control needs to be a non-empty string' });
				continue;
			}
			if (param.options !== undefined && !(param.options instanceof Array)) {
				errors.push({ message: 'Parameter key needs to be array' });
				continue;
			}
			if (param.min !== undefined && typeof param.min !== 'number') {
				errors.push({ message: 'Parameter min needs to be number' });
				continue;
			}
			if (param.max !== undefined && typeof param.max !== 'number') {
				errors.push({ message: 'Parameter max needs to be number' });
				continue;
			}
			if (param.scale !== undefined && typeof param.scale !== 'number') {
				errors.push({ message: 'Parameter scale needs to be number' });
				continue;
			}
			if (param.decimals !== undefined && typeof param.decimals !== 'number') {
				errors.push({ message: 'Parameter decimals needs to be number' });
				continue;
			}
			if (param.precision !== undefined && typeof param.precision !== 'number') {
				errors.push({ message: 'Parameter precision needs to be number' });
				continue;
			}
			if (param.exponential !== undefined && typeof param.exponential !== 'boolean') {
				errors.push({ message: 'Parameter exponential needs to be boolean' });
				continue;
			}
			if (param['default'] === null) {
				param['default'] = ScriptUtils.defaultsByType[param.type];
				if (typeof param['default'] === 'undefined') {
					errors.push({ message: 'Parameter default is missing or of wrong type' });
					continue;
				}
			}
			outScript.externals.parameters.push(param);
		}
		if (errors.length) {
			outScript.errors = errors;
		}
	}

	/**
	 * Flag a script with an error. The script will be disabled.
	 * @param {object} script
	 * @param {object} error
	 * @param {string} error.message
	 * @param {Number} [error.line]
	 * @param {string} [error.file]
	 * @private
	 */
	function setError(script, error) {
		if (error.file) {
			var message = error.message;
			if (error.line) {
				message += ' - on line ' + error.line;
			}
			script.dependencyErrors = script.dependencyErrors || {};
			script.dependencyErrors[error.file] = error;
		} else {
			script.errors = script.errors || [];
			var message = error.message;
			if (error.line) {
				message += ' - on line ' + error.line;
			}
			script.errors.push(error);

			script.setup = null;
			script.update = null;
			script.run = null;
			script.cleanup = null;

			script.parameters = {};
			script.enabled = false;
		}

	}

	ScriptHandler.DOM_ID_PREFIX = '_script_';

	return ScriptHandler;
});<commit_msg>Some script loading errors do not fire onerror, checking if a timeout could solve it https://trello.com/c/KWarjG0w/1715-major-stuck-on-loading-phase-if-an-external-resources-uses-http-instead-of-https<commit_after>define([
	'goo/loaders/handlers/ConfigHandler',
	'goo/util/rsvp',
	'goo/scripts/OrbitCamControlScript',
	'goo/scriptpack/OrbitNPanControlScript',
	'goo/scriptpack/FlyControlScript',
	'goo/scriptpack/WASDControlScript',
	'goo/scriptpack/BasicControlScript',
	'goo/util/PromiseUtil',
	'goo/util/ObjectUtil',
	'goo/entities/SystemBus',

	'goo/scripts/ScriptUtils',
	'goo/scripts/Scripts'
],
/** @lends */
function (
	ConfigHandler,
	RSVP,
	OrbitCamControlScript,
	OrbitNPanControlScript,
	FlyControlScript,
	WASDControlScript,
	BasicControlScript,
	PromiseUtil,
	_,
	SystemBus,

	ScriptUtils,
	Scripts
) {
	'use strict';

	var DEPENDENCY_LOAD_TIMEOUT = 6000;

	/**
	* @class
	* @private
	*/
	function ScriptHandler() {
		ConfigHandler.apply(this, arguments);
		this._bodyCache = {};
		this._dependencyPromises = {};
		this._currentScriptLoading = null;
		this._addGlobalErrorListener();
	}

	ScriptHandler.prototype = Object.create(ConfigHandler.prototype);
	ScriptHandler.prototype.constructor = ScriptHandler;
	ConfigHandler._registerClass('script', ScriptHandler);

	/**
	 * Creates a script data wrapper object to be used in the engine
	 */
	ScriptHandler.prototype._create = function () {
		return {
			externals: {},
			setup: null,
			update: null,
			run: null,
			cleanup: null,
			parameters: {},
			name: null
		};
	};

	/**
	 * Remove this script from the cache, and runs the cleanup method of the script.
	 * @param {string} ref the script guid
	 */
	ScriptHandler.prototype._remove = function (ref) {
		var script = this._objects[ref];
		if (script && script.cleanup && script.context) {
			try {
				script.cleanup(script.parameters, script.context, Scripts.getClasses());
			} catch (e) {
				// Some cleanup error
			}
		}
		delete this._objects[ref];
		delete this._bodyCache[ref];
	};

	/**
	 * Update a user-defined script (not a script available in the engine).
	 * If the new body (in the data model config) differs from the cached body,
	 * the script will be reloaded (by means of a script tag).
	 *
	 * @param {object} script the cached engine script object
	 * @param {object} config the data model config
	 */
	ScriptHandler.prototype._updateFromCustom = function (script, config) {
		// No change, do nothing
		if (this._bodyCache[config.id] === config.body) { return script; }

		delete script.errors;
		this._bodyCache[config.id] = config.body;

		// delete the old script tag and add a new one
		var oldScriptElement = document.getElementById(ScriptHandler.DOM_ID_PREFIX + config.id);
		if (oldScriptElement) {
			oldScriptElement.parentNode.removeChild(oldScriptElement);
		}

		// create this script collection if it does not exist yet
		if (!window._gooScriptFactories) {
			// this holds script factories in 'compiled' form
			window._gooScriptFactories = {};
		}

		// get a script factory in string form
		var scriptFactoryStr = [
			"window._gooScriptFactories['" + config.id + "'] = function () { 'use strict';",
			config.body,
			' var obj = {',
			'  externals: {}',
			' };',
			' if (typeof parameters !== "undefined") {',
			'  obj.externals.parameters = parameters;',
			' }',
			' if (typeof setup !== "undefined") {',
			'  obj.setup = setup;',
			' }',
			' if (typeof cleanup !== "undefined") {',
			'  obj.cleanup = cleanup;',
			' }',
			' if (typeof update !== "undefined") {',
			'  obj.update = update;',
			' }',
			' return obj;',
			'};'
		].join('\n');

		// create the element and add it to the page so the user can debug it
		// addition and execution of the script happens synchronously
		var newScriptElement = document.createElement('script');
		newScriptElement.id = ScriptHandler.DOM_ID_PREFIX + config.id;
		newScriptElement.innerHTML = scriptFactoryStr;
		newScriptElement.async = false;
		this._currentScriptLoading = config.id;
		document.body.appendChild(newScriptElement);

		var newScript = window._gooScriptFactories[config.id];
		if (newScript) {
			try {
				newScript = newScript();
				script.id = config.id;
				safeUp(newScript, script);
				script.setup = newScript.setup;
				script.update = newScript.update;
				script.cleanup = newScript.cleanup;
				script.parameters = {};
				script.enabled = false;
			} catch (e) {
				var err = {
					message: e.toString()
				};
				// TODO Test if this works across browsers
				/**/
				var m = e.stack.split('\n')[1].match(/(\d+):\d+\)$/);
				if (m) {
					err.line = parseInt(m[1], 10) - 1;
				}
				/**/
				setError(script, err);
			}
			this._currentScriptLoading = null;
		}
		// generate names from external variable names
		if (script.externals) {
			ScriptUtils.fillDefaultNames(script.externals.parameters);
		}

		return script;
	};

	/**
	 * Update a script that is from the engine. Checks if the class name has changed
	 * and if so, creates a new script object from the new class.
	 * @param {object} script needs to have a className property
	 * @param {object} config data model config
	 * @deprecated
	 */
	ScriptHandler.prototype._updateFromClass = function (script, config) {
		if (!script.externals || script.externals.name !== config.className) {
			var newScript = Scripts.create(config.className);
			if (!newScript) {
				throw new Error('Unrecognized script name');
			}
			script.id = config.id;
			script.externals = newScript.externals;
			script.setup = newScript.setup;
			script.update = newScript.update;
			script.run = newScript.run;
			script.cleanup = newScript.cleanup;
			script.parameters = newScript.parameters || {};
			script.enabled = false;

			// generate names from external variable names
			ScriptUtils.fillDefaultNames(script.externals.parameters);
		}

		return script;
	};

	ScriptHandler.prototype._update = function (ref, config, options) {
		var that = this;
		return ConfigHandler.prototype._update.call(this, ref, config, options)
		.then(function (script) {
			if (!script) { return; }

			var promises = [];

			if (config.body && config.dependencies) {
				delete script.dependencyErrors;
				_.forEach(config.dependencies, function (dependencyConfig) {
					promises.push(that._addDependency(script, dependencyConfig.url, config.id));
				}, null, 'sortValue');
			}

			return RSVP.all(promises)
			.then(function () {
				if (config.className) { // Engine script.
					that._updateFromClass(script, config, options);
				} else if (config.body) { // Custom script.
					that._updateFromCustom(script, config, options);
				}

				if (config.body) {
					SystemBus.emit('goo.scriptExternals', {
						id: config.id,
						externals: script.externals
					});
				}

				script.name = config.name;

				if (script.errors || script.dependencyErrors) {
					SystemBus.emit('goo.scriptError', {
						id: ref,
						errors: script.errors,
						dependencyErrors: script.dependencyErrors
					});
					return script;
				}
				else {
					SystemBus.emit('goo.scriptError', {id: ref, errors: null});
				}
				_.extend(script.parameters, config.options);
				return script;
			});
		});
	};

	/**
	 * Loads an external javascript lib as a dependency to this script (if it's
	 * not already loaded). If the dependency fails to load, an error is set
	 * on the script.
	 * @param {object} script config
	 * @param {string} url location of the javascript lib
	 * @param {string} scriptId the guid of the script
	 * @return {RSVP.Promise} a promise that resolves when the dependency is loaded
	 */
	ScriptHandler.prototype._addDependency = function (script, url, scriptId) {
		var that = this;
		var scriptElem = document.querySelector('script[src="' + url + '"]');
		if (scriptElem) {
			return this._dependencyPromises[url] || PromiseUtil.resolve();
		}

		scriptElem = document.createElement('script');
		scriptElem.src = url;
		scriptElem.setAttribute('data-script-id', scriptId);

		document.body.appendChild(scriptElem);

		return this._dependencyPromises[url] = PromiseUtil.createPromise(function (resolve, reject) {
			scriptElem.onload = function () {
				resolve();
				delete that._dependencyPromises[url];
			};

			function fireError(message) {
				var err = {
					message: message
					file: url
				};
				setError(script, err);
				scriptElem.parentNode.removeChild(scriptElem);
				resolve();
				delete that._dependencyPromises[url];
			}

			scriptElem.onerror = function (e) {
				console.error(e);
				fireError('Could not load dependency');
			};

			// Some errors (notably https/http security ones) don't fire onerror, so we have to wait
			setTimeout(function() {
				fireError('Could not load dependency, timeout');
			}, DEPENDENCY_LOAD_TIMEOUT);

		});
	};

	/**
	 * Add a global error listener that catches script errors, and tries to match
	 * them to scripts loaded with this handler. If an error is registered, the
	 * script is reset and an error message is appended to it.
	 * @private
	 *
	 */
	ScriptHandler.prototype._addGlobalErrorListener = function () {
		var that = this;
		window.addEventListener('error', function (evt) {
			if (evt.filename) {
				var scriptElem = document.querySelector('script[src="' + evt.filename + '"]');
				if (scriptElem) {
					var scriptId = scriptElem.getAttribute('data-script-id');
					var script = that._objects[scriptId];
					if (script) {
						var error = {
							message: evt.message,
							line: evt.lineno,
							file: evt.filename
						};
						setError(script, error);
					}
					scriptElem.parentNode.removeChild(scriptElem);
				}
			}
			if (that._currentScriptLoading) {
				var oldScriptElement = document.getElementById(ScriptHandler.DOM_ID_PREFIX + that._currentScriptLoading);
				if (oldScriptElement) {
					oldScriptElement.parentNode.removeChild(oldScriptElement);
				}
				delete window._gooScriptFactories[that._currentScriptLoading];
				var script = that._objects[that._currentScriptLoading];
				var error = {
					message: evt.message,
					line: evt.lineno - 1
				};
				setError(script, error);
				that._currentScriptLoading = null;
			}
		});
	};

	var types = ['string', 'float', 'int', 'vec3', 'boolean', 'texture', 'entity'];

	/**
	 * Validate external parameters
	 * @private
	 */
	function safeUp(script, outScript) {
		var errors = script.errors || [];
		if (typeof script.externals !== 'object') {
			outScript.externals = {};
			return;
		}
		var externals = script.externals;
		if (externals.parameters && !(externals.parameters instanceof Array)) {
			errors.push('externals.parameters needs to be an array');
		}
		if (errors.length) {
			outScript.errors = errors;
			return;
		}
		if (!externals.parameters) {
			return;
		}
		outScript.externals.parameters = [];
		for (var i = 0; i < externals.parameters.length; i++) {
			var param = externals.parameters[i];
			if (typeof param.key !== 'string' || param.key.length === 0) {
				errors.push({ message: 'Parameter key needs to be a non-empty string' });
				continue;
			}
			if (param.name !== undefined && (typeof param.name !== 'string' || param.name.length === 0)) {
				errors.push({ message: 'Parameter name needs to be a non-empty string' });
				continue;
			}
			if (types.indexOf(param.type) === -1) {
				errors.push({ message: 'Parameter type needs to be one of (' + types.join(', ') + ')' });
				continue;
			}
			if (param.control !== undefined && (typeof param.control !== 'string' || param.control.length === 0)) {
				errors.push({ message: 'Parameter control needs to be a non-empty string' });
				continue;
			}
			if (param.options !== undefined && !(param.options instanceof Array)) {
				errors.push({ message: 'Parameter key needs to be array' });
				continue;
			}
			if (param.min !== undefined && typeof param.min !== 'number') {
				errors.push({ message: 'Parameter min needs to be number' });
				continue;
			}
			if (param.max !== undefined && typeof param.max !== 'number') {
				errors.push({ message: 'Parameter max needs to be number' });
				continue;
			}
			if (param.scale !== undefined && typeof param.scale !== 'number') {
				errors.push({ message: 'Parameter scale needs to be number' });
				continue;
			}
			if (param.decimals !== undefined && typeof param.decimals !== 'number') {
				errors.push({ message: 'Parameter decimals needs to be number' });
				continue;
			}
			if (param.precision !== undefined && typeof param.precision !== 'number') {
				errors.push({ message: 'Parameter precision needs to be number' });
				continue;
			}
			if (param.exponential !== undefined && typeof param.exponential !== 'boolean') {
				errors.push({ message: 'Parameter exponential needs to be boolean' });
				continue;
			}
			if (param['default'] === null) {
				param['default'] = ScriptUtils.defaultsByType[param.type];
				if (typeof param['default'] === 'undefined') {
					errors.push({ message: 'Parameter default is missing or of wrong type' });
					continue;
				}
			}
			outScript.externals.parameters.push(param);
		}
		if (errors.length) {
			outScript.errors = errors;
		}
	}

	/**
	 * Flag a script with an error. The script will be disabled.
	 * @param {object} script
	 * @param {object} error
	 * @param {string} error.message
	 * @param {Number} [error.line]
	 * @param {string} [error.file]
	 * @private
	 */
	function setError(script, error) {
		if (error.file) {
			var message = error.message;
			if (error.line) {
				message += ' - on line ' + error.line;
			}
			script.dependencyErrors = script.dependencyErrors || {};
			script.dependencyErrors[error.file] = error;
		} else {
			script.errors = script.errors || [];
			var message = error.message;
			if (error.line) {
				message += ' - on line ' + error.line;
			}
			script.errors.push(error);

			script.setup = null;
			script.update = null;
			script.run = null;
			script.cleanup = null;

			script.parameters = {};
			script.enabled = false;
		}

	}

	ScriptHandler.DOM_ID_PREFIX = '_script_';

	return ScriptHandler;
});<|endoftext|>