mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2025-02-17 08:50:58 -05:00
Merge branch 'master' of https://github.com/codecombat/codecombat
This commit is contained in:
commit
775a2a87e2
14 changed files with 383 additions and 393 deletions
|
@ -1,378 +0,0 @@
|
|||
// 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'});
|
|
@ -82,6 +82,278 @@ self.transferableSupported = function transferableSupported() {
|
|||
|
||||
var World = self.require('lib/world/world');
|
||||
var GoalManager = self.require('lib/world/GoalManager');
|
||||
var serializedClasses = {
|
||||
"Thang": self.require('lib/world/thang'),
|
||||
"Vector": self.require('lib/world/vector'),
|
||||
"Rectangle": self.require('lib/world/rectangle')
|
||||
};
|
||||
self.currentUserCodeMapCopy = "";
|
||||
self.currentDebugWorldFrame = 0;
|
||||
|
||||
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.currentDebugWorldFrame && (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.debugWorld.thangMap[currentThangID];
|
||||
|
||||
}
|
||||
else if (i === 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
var flowStates = self.debugWorld.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.debugWorld.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.setupDebugWorldToRunUntilFrame(args);
|
||||
self.debugWorld.loadFramesUntilFrame(
|
||||
args.frame,
|
||||
retrieveProperty.bind({},args.currentThangID, args.currentSpellID, args.variableChain),
|
||||
self.onDebugWorldError,
|
||||
self.onDebugWorldProgress
|
||||
);
|
||||
};
|
||||
|
||||
self.enableFlowOnThangSpell = function (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.setupDebugWorldToRunUntilFrame = function (args) {
|
||||
self.debugPostedErrors = {};
|
||||
self.debugt0 = new Date();
|
||||
self.debugPostedErrors = false;
|
||||
self.logsLogged = 0;
|
||||
|
||||
var stringifiedUserCodeMap = JSON.stringify(args.userCodeMap);
|
||||
var userCodeMapHasChanged = ! _.isEqual(self.currentUserCodeMapCopy, stringifiedUserCodeMap);
|
||||
self.currentUserCodeMapCopy = stringifiedUserCodeMap;
|
||||
if (!self.debugWorld || userCodeMapHasChanged || args.frame != self.currentDebugWorldFrame) {
|
||||
self.invalidateCache();
|
||||
try {
|
||||
self.debugWorld = new World(args.worldName, args.userCodeMap);
|
||||
if (args.level)
|
||||
self.debugWorld.loadFromLevel(args.level, true);
|
||||
self.debugGoalManager = new GoalManager(self.debugWorld);
|
||||
self.debugGoalManager.setGoals(args.goals);
|
||||
self.debugGoalManager.setCode(args.userCodeMap);
|
||||
self.debugGoalManager.worldGenerationWillBegin();
|
||||
self.debugWorld.setGoalManager(self.debugGoalManager);
|
||||
}
|
||||
catch (error) {
|
||||
self.onDebugWorldError(error);
|
||||
return;
|
||||
}
|
||||
Math.random = self.debugWorld.rand.randf; // so user code is predictable
|
||||
|
||||
self.debugWorld.totalFrames = args.frame; //hack to work around error checking
|
||||
self.currentDebugWorldFrame = args.frame;
|
||||
}
|
||||
};
|
||||
self.runDebugWorldUntilFrame = function (args) {
|
||||
self.setupDebugWorldToRunUntilFrame(args);
|
||||
|
||||
self.debugWorld.loadFramesUntilFrame(args.frame, self.onDebugWorldLoaded, self.onDebugWorldError, self.onDebugWorldProgress);
|
||||
|
||||
};
|
||||
|
||||
self.onDebugWorldLoaded = function onDebugWorldLoaded() {
|
||||
console.log("World loaded!");
|
||||
};
|
||||
|
||||
self.onDebugWorldError = function onDebugWorldError(error) {
|
||||
if(error instanceof Aether.problems.UserCodeProblem) {
|
||||
if(!self.debugPostedErrors[error.key]) {
|
||||
var problem = error.serialize();
|
||||
self.postMessage({type: 'user-code-problem', problem: problem});
|
||||
self.debugPostedErrors[error.key] = problem;
|
||||
}
|
||||
}
|
||||
else {
|
||||
console.log("Non-UserCodeError:", error.toString() + "\n" + error.stack || error.stackTrace);
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
self.onDebugWorldProgress = function onDebugWorldProgress(progress) {
|
||||
self.postMessage({type: 'debug-world-load-progress-changed', progress: progress});
|
||||
};
|
||||
|
||||
self.debugAbort = function () {
|
||||
if(self.debugWorld && self.debugWorld.name) {
|
||||
console.log("About to abort:", self.debugWorld.name, typeof self.debugWorld.abort);
|
||||
if(typeof self.debugWorld !== "undefined")
|
||||
self.debugWorld.abort();
|
||||
self.debugWorld = null;
|
||||
}
|
||||
self.postMessage({type: 'debugAbort'});
|
||||
};
|
||||
|
||||
|
||||
self.runWorld = function runWorld(args) {
|
||||
self.postedErrors = {};
|
||||
|
|
|
@ -57,7 +57,7 @@ module.exports = class God
|
|||
worker
|
||||
|
||||
createDebugWorker: ->
|
||||
worker = new Worker '/javascripts/workers/worker_debug.js'
|
||||
worker = new Worker '/javascripts/workers/worker_world.js'
|
||||
worker.creationTime = new Date()
|
||||
worker.addEventListener 'message', @onDebugWorkerMessage
|
||||
worker
|
||||
|
@ -146,7 +146,6 @@ module.exports = class God
|
|||
worldName: @level.name
|
||||
userCodeMap: @currentUserCodeMap
|
||||
level: @level
|
||||
firstWorld: @firstWorld
|
||||
goals: @goalManager?.getGoals()
|
||||
frame: args.frame
|
||||
currentThangID: args.thangID
|
||||
|
|
|
@ -712,3 +712,14 @@
|
|||
files: "Files"
|
||||
top_simulators: "Top Simulators"
|
||||
source_document: "Source Document"
|
||||
document: "Document" # note to diplomats: not a physical document, a document in MongoDB, ie a record in a database
|
||||
|
||||
delta:
|
||||
added: "Added"
|
||||
modified: "Modified"
|
||||
deleted: "Deleted"
|
||||
moved_index: "Moved Index"
|
||||
text_diff: "Text Diff"
|
||||
merge_conflict_with: "MERGE CONFLICT WITH"
|
||||
no_changes: "No Changes"
|
||||
|
||||
|
|
|
@ -162,6 +162,10 @@ class CocoModel extends Backbone.Model
|
|||
getDelta: ->
|
||||
differ = deltasLib.makeJSONDiffer()
|
||||
differ.diff @_revertAttributes, @attributes
|
||||
|
||||
getDeltaWith: (otherModel) ->
|
||||
differ = deltasLib.makeJSONDiffer()
|
||||
differ.diff @attributes, otherModel.attributes
|
||||
|
||||
applyDelta: (delta) ->
|
||||
newAttributes = $.extend(true, {}, @attributes)
|
||||
|
@ -172,6 +176,10 @@ class CocoModel extends Backbone.Model
|
|||
delta = @getDelta()
|
||||
deltasLib.expandDelta(delta, @_revertAttributes, @schema())
|
||||
|
||||
getExpandedDeltaWith: (otherModel) ->
|
||||
delta = @getDeltaWith(otherModel)
|
||||
deltasLib.expandDelta(delta, @attributes, @schema())
|
||||
|
||||
watch: (doWatch=true) ->
|
||||
$.ajax("#{@urlRoot}/#{@id}/watch", {type:'PUT', data:{on:doWatch}})
|
||||
@watching = -> doWatch
|
||||
|
|
|
@ -31,8 +31,9 @@ module.exports = class SuperModel extends Backbone.Model
|
|||
|
||||
else
|
||||
@registerModel(model)
|
||||
console.debug 'Registering model', model.getURL()
|
||||
return @addModelResource(model, name, fetchOptions, value).load()
|
||||
res = @addModelResource(model, name, fetchOptions, value)
|
||||
if not res.isLoaded then res.load()
|
||||
return res
|
||||
|
||||
loadCollection: (collection, name, fetchOptions, value=1) ->
|
||||
url = collection.getURL()
|
||||
|
@ -52,7 +53,9 @@ module.exports = class SuperModel extends Backbone.Model
|
|||
@listenToOnce collection, 'sync', (c) ->
|
||||
console.debug 'Registering collection', url
|
||||
@registerCollection c
|
||||
return @addModelResource(collection, name, fetchOptions, value).load()
|
||||
res = @addModelResource(collection, name, fetchOptions, value)
|
||||
res.load() if not (res.isLoading or res.isLoaded)
|
||||
return res
|
||||
|
||||
# replace or overwrite
|
||||
shouldSaveBackups: (model) -> false
|
||||
|
|
|
@ -37,10 +37,12 @@ mixin deltaPanel(delta, conflict)
|
|||
|
||||
if delta.conflict && !conflict
|
||||
.panel-body
|
||||
strong MERGE CONFLICT WITH
|
||||
strong(data-i18n="delta.merge_conflict_with") MERGE CONFLICT WITH
|
||||
+deltaPanel(delta.conflict, true)
|
||||
|
||||
.panel-group(id='delta-accordion-'+(counter))
|
||||
for delta in deltas
|
||||
+deltaPanel(delta)
|
||||
if !deltas.length
|
||||
alert.alert-warning(data-i18n="delta.no_changes") No changes
|
||||
|
|
@ -24,7 +24,7 @@ block header
|
|||
li
|
||||
a(href="#editor-level-settings-tab-view", data-toggle="tab", data-i18n="editor.level_tab_settings") Settings
|
||||
li
|
||||
a(href="#editor-level-components-tab-view", data-toggle="tab", data-i18n="editor.level_tab_components") Components
|
||||
a(href="#editor-level-components-tab-view", data-toggle="tab", data-i18n="editor.level_tab_components")#components-tab Components
|
||||
li
|
||||
a(href="#editor-level-systems-tab-view", data-toggle="tab", data-i18n="editor.level_tab_systems") Systems
|
||||
li
|
||||
|
|
|
@ -10,15 +10,21 @@ block modal-body-content
|
|||
if dataList
|
||||
table.table.table-condensed
|
||||
tr
|
||||
th
|
||||
th(data-i18n="general.name") Name
|
||||
th(data-i18n="general.version") Version
|
||||
th(data-i18n="general.commit_msg") Commit Message
|
||||
for data in dataList
|
||||
tr
|
||||
td
|
||||
input(type="checkbox", value=data._id).select
|
||||
td
|
||||
a(href="/editor/#{page}/#{data.slug || data._id}")
|
||||
| #{data.name}
|
||||
td #{data.version.major}.#{data.version.minor}
|
||||
td #{data.commitMessage}
|
||||
|
||||
div.delta-container
|
||||
div.delta-view
|
||||
|
||||
block modal-footer-content
|
|
@ -9,19 +9,63 @@ TEXTDIFF_OPTIONS =
|
|||
viewType: 1
|
||||
|
||||
module.exports = class DeltaView extends CocoView
|
||||
|
||||
###
|
||||
Takes a CocoModel instance (model) and displays changes since the
|
||||
last save (attributes vs _revertAttributes).
|
||||
|
||||
* If headModel is included, will look for and display conflicts with the changes in model.
|
||||
* If comparisonModel is included, will show deltas between model and comparisonModel instead
|
||||
of changes within model itself.
|
||||
|
||||
###
|
||||
|
||||
@deltaCounter: 0
|
||||
className: "delta-view"
|
||||
template: template
|
||||
|
||||
constructor: (options) ->
|
||||
super(options)
|
||||
@model = options.model
|
||||
@headModel = options.headModel
|
||||
@expandedDeltas = @model.getExpandedDelta()
|
||||
@expandedDeltas = []
|
||||
@skipPaths = options.skipPaths
|
||||
|
||||
for modelName in ['model', 'headModel', 'comparisonModel']
|
||||
continue unless m = options[modelName]
|
||||
@[modelName] = @supermodel.loadModel(m, 'document').model
|
||||
|
||||
@buildDeltas() if @supermodel.finished()
|
||||
console.log 'done constructing'
|
||||
|
||||
onLoaded: ->
|
||||
@buildDeltas()
|
||||
super()
|
||||
|
||||
buildDeltas: ->
|
||||
if @comparisonModel
|
||||
@expandedDeltas = @model.getExpandedDeltaWith(@comparisonModel)
|
||||
else
|
||||
@expandedDeltas = @model.getExpandedDelta()
|
||||
|
||||
@filterExpandedDeltas()
|
||||
|
||||
if @headModel
|
||||
@headDeltas = @headModel.getExpandedDelta()
|
||||
@conflicts = deltasLib.getConflicts(@headDeltas, @expandedDeltas)
|
||||
|
||||
filterExpandedDeltas: ->
|
||||
return unless @skipPaths
|
||||
for path, i in @skipPaths
|
||||
@skipPaths[i] = [path] if _.isString(path)
|
||||
newDeltas = []
|
||||
for delta in @expandedDeltas
|
||||
skip = false
|
||||
for skipPath in @skipPaths
|
||||
if _.isEqual _.first(delta.dataPath, skipPath.length), skipPath
|
||||
skip = true
|
||||
break
|
||||
newDeltas.push delta unless skip
|
||||
@expandedDeltas = newDeltas
|
||||
|
||||
getRenderData: ->
|
||||
c = super()
|
||||
c.deltas = @expandedDeltas
|
||||
|
|
|
@ -14,7 +14,6 @@ module.exports = class ComponentsTabView extends View
|
|||
className: 'tab-pane'
|
||||
|
||||
subscriptions:
|
||||
'level-thangs-changed': 'onLevelThangsChanged'
|
||||
'edit-level-component': 'editLevelComponent'
|
||||
'level-component-edited': 'onLevelComponentEdited'
|
||||
'level-component-editing-ended': 'onLevelComponentEditingEnded'
|
||||
|
@ -24,8 +23,8 @@ module.exports = class ComponentsTabView extends View
|
|||
'click #create-new-component-button-no-select': 'createNewLevelComponent'
|
||||
|
||||
onLoaded: ->
|
||||
onLevelThangsChanged: (e) ->
|
||||
thangsData = e.thangsData
|
||||
|
||||
refreshLevelThangsTreema: (thangsData) ->
|
||||
presentComponents = {}
|
||||
for thang in thangsData
|
||||
for component in thang.components
|
||||
|
|
|
@ -29,6 +29,7 @@ module.exports = class EditorLevelView extends View
|
|||
'click #fork-level-start-button': 'startForkingLevel'
|
||||
'click #level-history-button': 'showVersionHistory'
|
||||
'click #patches-tab': -> @patchesView.load()
|
||||
'click #components-tab': -> @componentsTab.refreshLevelThangsTreema @level.get('thangs')
|
||||
'click #level-patch-button': 'startPatchingLevel'
|
||||
'click #level-watch-button': 'toggleWatchLevel'
|
||||
'click #pop-level-i18n-button': -> @level.populateI18N()
|
||||
|
|
|
@ -50,7 +50,9 @@ module.exports = class EmployersView extends View
|
|||
renderCandidatesAndSetupScrolling: =>
|
||||
@render()
|
||||
$(".nano").nanoScroller()
|
||||
if window.location.hash.length is 25
|
||||
if window.history?.state?.lastViewedCandidateID
|
||||
$(".nano").nanoScroller({scrollTo:$("#" + window.history.state.lastViewedCandidateID)})
|
||||
else if window.location.hash.length is 25
|
||||
$(".nano").nanoScroller({scrollTo:$(window.location.hash)})
|
||||
|
||||
sortTable: ->
|
||||
|
@ -175,8 +177,13 @@ module.exports = class EmployersView extends View
|
|||
|
||||
onCandidateClicked: (e) ->
|
||||
id = $(e.target).closest('tr').data('candidate-id')
|
||||
window.location.hash = id
|
||||
if id
|
||||
if window.history
|
||||
oldState = _.cloneDeep window.history.state ? {}
|
||||
oldState["lastViewedCandidateID"] = id
|
||||
window.history.replaceState(oldState,"")
|
||||
else
|
||||
window.location.hash = id
|
||||
url = "/account/profile/#{id}"
|
||||
app.router.navigate url, {trigger: true}
|
||||
else
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
ModalView = require 'views/kinds/ModalView'
|
||||
template = require 'templates/modal/versions'
|
||||
tableTemplate = require 'templates/kinds/table'
|
||||
DeltaView = require 'views/editor/delta'
|
||||
|
||||
class VersionsViewCollection extends Backbone.Collection
|
||||
url: ""
|
||||
|
@ -20,6 +21,9 @@ module.exports = class VersionsModalView extends ModalView
|
|||
id: ""
|
||||
url: ""
|
||||
page: ""
|
||||
|
||||
events:
|
||||
'change input.select': 'onSelectionChanged'
|
||||
|
||||
constructor: (options, @ID, @model) ->
|
||||
super options
|
||||
|
@ -36,6 +40,18 @@ module.exports = class VersionsModalView extends ModalView
|
|||
@startsLoading = false
|
||||
@render()
|
||||
|
||||
onSelectionChanged: ->
|
||||
rows = @$el.find 'input.select:checked'
|
||||
deltaEl = @$el.find '.delta-view'
|
||||
@deltaView?.destroy()
|
||||
deltaEl.empty()
|
||||
if rows.length isnt 2 then return
|
||||
|
||||
laterVersion = new @model(_id:$(rows[0]).val())
|
||||
earlierVersion = new @model(_id:$(rows[1]).val())
|
||||
@deltaView = new DeltaView({model:earlierVersion, comparisonModel:laterVersion, skipPaths:['_id','version', 'commitMessage', 'parent', 'created', 'slug', 'index']})
|
||||
@insertSubView(@deltaView, deltaEl)
|
||||
|
||||
getRenderData: (context={}) ->
|
||||
context = super(context)
|
||||
context.page = @page
|
||||
|
|
Loading…
Reference in a new issue