// There's no reason that this file is in JavaScript instead of CoffeeScript. // We should convert it and update the brunch config. // If we wanted to be more robust, we could use this: https://github.com/padolsey/operative/blob/master/src/operative.js if(typeof window !== 'undefined' || !self.importScripts) throw "Attempt to load worker_world into main window instead of web worker."; // Taken from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind // This is here for running simuations in enviroments lacking function.bind (PhantomJS mostly) if (!Function.prototype.bind) { Function.prototype.bind = function (oThis) { if (typeof this !== "function") { // closest thing possible to the ECMAScript 5 internal IsCallable function throw new TypeError("Function.prototype.bind (Shim) - target is not callable"); } var aArgs = Array.prototype.slice.call(arguments, 1), fToBind = this, fNOP = function () {}, fBound = function () { return fToBind.apply(this instanceof fNOP && oThis ? this : oThis, aArgs.concat(Array.prototype.slice.call(arguments))); }; fNOP.prototype = this.prototype; fBound.prototype = new fNOP(); return fBound; }; } // assign global window so that Brunch's require (in world.js) can go into it self.window = self; self.workerID = "DebugWorker"; self.logLimit = 2000; self.logsLogged = 0; var console = { log: function() { if(self.logsLogged++ == self.logLimit) self.postMessage({type: 'console-log', args: ["Log limit " + self.logLimit + " reached; shutting up."], id: self.workerID}); else if(self.logsLogged < self.logLimit) { args = [].slice.call(arguments); for(var i = 0; i < args.length; ++i) { if(args[i] && args[i].constructor) { if(args[i].constructor.className === "Thang" || args[i].isComponent) args[i] = args[i].toString(); } } try { self.postMessage({type: 'console-log', args: args, id: self.workerID}); } catch(error) { self.postMessage({type: 'console-log', args: ["Could not post log: " + args, error.toString(), error.stack, error.stackTrace], id: self.workerID}); } } }}; // so that we don't crash when debugging statements happen console.error = console.info = console.log; self.console = console; // We could do way more from this: http://stackoverflow.com/questions/10653809/making-webworkers-a-safe-environment Object.defineProperty(self, "XMLHttpRequest", { get: function() { throw new Error("Access to XMLHttpRequest is forbidden."); }, configurable: false }); self.transferableSupported = function transferableSupported() { // Not in IE, even in IE 11 try { var ab = new ArrayBuffer(1); worker.postMessage(ab, [ab]); return ab.byteLength == 0; } catch(error) { return false; } return false; } importScripts('/javascripts/world.js'); var World = self.require('lib/world/world'); var GoalManager = self.require('lib/world/GoalManager'); serializedClasses = { "Thang": self.require('lib/world/thang'), "Vector": self.require('lib/world/vector'), "Rectangle": self.require('lib/world/rectangle') } self.getCurrentFrame = function getCurrentFrame(args) { return self.world.frames.length; }; //optimize this later self.currentUserCodeMapCopy = {}; self.currentWorldFrame = 0; self.maxSerializationDepth = 3; self.stringifyValue = function(value, depth) { var brackets, i, isArray, isObject, key, prefix, s, sep, size, v, values, _i, _j, _len, _len1, _ref, _ref1, _ref2, _ref3; if (!value || _.isString(value)) { return value; } if (_.isFunction(value)) { if (depth === 2) { return void 0; } else { return "<Function>"; } } if (value === this.thang && depth) { return "<this " + value.id + ">"; } if (depth === 2) { if (((_ref = value.constructor) != null ? _ref.className : void 0) === "Thang") { value = "<" + (value.type || value.spriteName) + " - " + value.id + ", " + (value.pos ? value.pos.toString() : 'non-physical') + ">"; } else { value = value.toString(); } return value; } isArray = _.isArray(value); isObject = _.isObject(value); if (!(isArray || isObject)) { return value.toString(); } brackets = isArray ? ["[", "]"] : ["{", "}"]; size = _.size(value); if (!size) { return brackets.join(""); } values = []; if (isArray) { for (_i = 0, _len = value.length; _i < _len; _i++) { v = value[_i]; s = this.stringifyValue(v, depth + 1); if (s !== void 0) { values.push("" + s); } } } else { _ref2 = (_ref1 = value.apiProperties) != null ? _ref1 : _.keys(value); for (_j = 0, _len1 = _ref2.length; _j < _len1; _j++) { key = _ref2[_j]; if (key[0] === "_") continue; s = this.stringifyValue(value[key], depth + 1); if (s !== void 0) { values.push(key + ": " + s); } } } sep = '\n' + ((function() { var _k, _results; _results = []; for (i = _k = 0; 0 <= depth ? _k < depth : _k > depth; i = 0 <= depth ? ++_k : --_k) { _results.push(" "); } return _results; })()).join(''); prefix = (_ref3 = value.constructor) != null ? _ref3.className : void 0; if (isArray) { if (prefix == null) { prefix = "Array"; } } if (isObject) { if (prefix == null) { prefix = "Object"; } } prefix = prefix ? prefix + " " : ""; return "" + prefix + brackets[0] + sep + " " + (values.join(sep + ' ')) + sep + brackets[1]; }; var cache = {}; self.invalidateCache = function () { cache = {}; }; self.retrieveValueFromCache = function (thangID, spellID, variableChain, frame) { var frameCache, thangCache, spellCache; if ((frameCache = cache[frame]) && (thangCache = frameCache[thangID]) && (spellCache = thangCache[spellID])) return spellCache[variableChain.join()]; return undefined; }; self.updateCache = function (thangID, spellID, variableChain, frame, value) { var key, keys, currentObject; keys = [frame,thangID, spellID, variableChain.join()]; currentObject = cache; for (var i = 0, len = keys.length - 1; i < len; i++) { key = keys[i]; if (!(key in currentObject)) currentObject[key] = {}; currentObject = currentObject[key]; } currentObject[keys[keys.length - 1]] = value; }; self.retrieveValueFromFrame = function retrieveValueFromFrame(args) { var cacheValue; if (args.frame === self.currentWorldFrame && (cacheValue = self.retrieveValueFromCache(args.currentThangID, args.currentSpellID, args.variableChain, args.frame))) return self.postMessage({type: 'debug-value-return', serialized: {"key": args.variableChain.join("."), "value": cacheValue}}); var retrieveProperty = function retrieveProperty(currentThangID, currentSpellID, variableChain) { var prop; var value; var keys = []; for (var i = 0, len = variableChain.length; i < len; i++) { prop = variableChain[i]; if (prop === "this") { value = self.world.thangMap[currentThangID]; } else if (i === 0) { try { var flowStates = self.world.userCodeMap[currentThangID][currentSpellID].flow.states; //we have to go to the second last flowState as we run the world for one additional frame //to collect the flow value = _.last(flowStates[flowStates.length - 2].statements).variables[prop]; } catch (e) { value = undefined; } } else { value = value[prop]; } keys.push(prop); if (!value) break; var classOfValue; if (classOfValue = serializedClasses[value.CN]) { if (value.CN === "Thang") { var thang = self.world.thangMap[value.id]; value = thang || "<Thang " + value.id + " (non-existent)>" } else { value = classOfValue.deserializeFromAether(value); } } } var serializedProperty = { "key": keys.join("."), "value": self.stringifyValue(value,0) }; self.updateCache(currentThangID,currentSpellID,variableChain, args.frame, serializedProperty.value); self.postMessage({type: 'debug-value-return', serialized: serializedProperty}); }; self.enableFlowOnThangSpell(args.currentThangID, args.currentSpellID, args.userCodeMap); self.setupWorldToRunUntilFrame(args); self.world.loadFramesUntilFrame( args.frame, retrieveProperty.bind({},args.currentThangID, args.currentSpellID, args.variableChain), self.onWorldError, self.onWorldLoadProgress ); }; self.enableFlowOnThangSpell = function enableFlowOnThang(thangID, spellID, userCodeMap) { try { if (userCodeMap[thangID][spellID].originalOptions.includeFlow === true && userCodeMap[thangID][spellID].originalOptions.noSerializationInFlow === true) return; else { userCodeMap[thangID][spellID].originalOptions.includeFlow = true; userCodeMap[thangID][spellID].originalOptions.noSerializationInFlow = true; var temporaryAether = Aether.deserialize(userCodeMap[thangID][spellID]); temporaryAether.transpile(temporaryAether.raw); userCodeMap[thangID][spellID] = temporaryAether.serialize(); } } catch (e) { console.log("there was an error enabling flow on thang spell:" + e) } }; self.setupWorldToRunUntilFrame = function setupWorldToRunUntilFrame(args) { self.postedErrors = {}; self.t0 = new Date(); self.firstWorld = args.firstWorld; self.postedErrors = false; self.logsLogged = 0; var stringifiedUserCodeMap = JSON.stringify(args.userCodeMap); var userCodeMapHasChanged = ! _.isEqual(self.currentUserCodeMapCopy, stringifiedUserCodeMap); self.currentUserCodeMapCopy = stringifiedUserCodeMap; if (!self.world || userCodeMapHasChanged || args.frame != self.currentWorldFrame) { self.invalidateCache(); try { self.world = new World(args.worldName, args.userCodeMap); if (args.level) self.world.loadFromLevel(args.level, true); self.goalManager = new GoalManager(self.world); self.goalManager.setGoals(args.goals); self.goalManager.setCode(args.userCodeMap); self.goalManager.worldGenerationWillBegin(); self.world.setGoalManager(self.goalManager); } catch (error) { self.onWorldError(error); return; } Math.random = self.world.rand.randf; // so user code is predictable self.world.totalFrames = args.frame; //hack to work around error checking self.currentWorldFrame = args.frame; } }; self.runWorldUntilFrame = function runWorldUntilFrame(args) { self.setupWorldToRunUntilFrame(args); self.world.loadFramesUntilFrame(args.frame, self.onWorldLoaded, self.onWorldError, self.onWorldLoadProgress); }; self.onWorldLoaded = function onWorldLoaded() { console.log("World loaded!"); }; self.onWorldError = function onWorldError(error) { if(error instanceof Aether.problems.UserCodeProblem) { if(!self.postedErrors[error.key]) { var problem = error.serialize(); self.postMessage({type: 'user-code-problem', problem: problem}); self.postedErrors[error.key] = problem; } } else { console.log("Non-UserCodeError:", error.toString() + "\n" + error.stack || error.stackTrace); } /* We don't actually have the recoverable property any more; hmm if(!self.firstWorld && !error.recoverable) { self.abort(); return false; } */ return true; }; self.onWorldLoadProgress = function onWorldLoadProgress(progress) { self.postMessage({type: 'world-load-progress-changed', progress: progress}); }; self.abort = function abort() { if(self.world && self.world.name) { console.log("About to abort:", self.world.name, typeof self.world.abort); if(typeof self.world !== "undefined") self.world.abort(); self.world = null; } self.postMessage({type: 'abort'}); }; self.reportIn = function reportIn() { self.postMessage({type: 'reportIn'}); } self.addEventListener('message', function(event) { self[event.data.func](event.data.args); }); self.postMessage({type: 'worker-initialized'});