mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2025-05-01 16:33:38 -04:00
First steps towards hover debugger improvements
This commit is contained in:
parent
0de5e715c7
commit
544cc62cdb
3 changed files with 278 additions and 4 deletions
app
210
app/assets/javascripts/workers/worker_debug.js
Normal file
210
app/assets/javascripts/workers/worker_debug.js
Normal file
|
@ -0,0 +1,210 @@
|
||||||
|
// 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 = "Worker";
|
||||||
|
|
||||||
|
self.logLimit = 200;
|
||||||
|
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;
|
||||||
|
|
||||||
|
importScripts('/javascripts/world.js');
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
var World = self.require('lib/world/world');
|
||||||
|
var GoalManager = self.require('lib/world/GoalManager');
|
||||||
|
|
||||||
|
self.getCurrentFrame = function getCurrentFrame(args) { return self.world.frames.length; };
|
||||||
|
|
||||||
|
self.runWorld = function runWorld(args) {
|
||||||
|
self.postedErrors = {};
|
||||||
|
self.t0 = new Date();
|
||||||
|
self.firstWorld = args.firstWorld;
|
||||||
|
self.postedErrors = false;
|
||||||
|
self.logsLogged = 0;
|
||||||
|
|
||||||
|
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.loadFrames(self.onWorldLoaded, self.onWorldError, self.onWorldLoadProgress);
|
||||||
|
};
|
||||||
|
|
||||||
|
self.runWorldUntilFrame = function runWorldUntilFrame(args) {
|
||||||
|
self.postedErrors = {};
|
||||||
|
self.t0 = new Date();
|
||||||
|
self.firstWorld = args.firstWorld;
|
||||||
|
self.postedErrors = false;
|
||||||
|
self.logsLogged = 0;
|
||||||
|
if (!self.world)
|
||||||
|
{
|
||||||
|
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.world.loadFramesUntilFrame(args.frame, self.onWorldLoaded, self.onWorldError, self.onWorldLoadProgress);
|
||||||
|
};
|
||||||
|
|
||||||
|
self.onWorldLoaded = function onWorldLoaded() {
|
||||||
|
var t1 = new Date();
|
||||||
|
var diff = t1 - self.t0;
|
||||||
|
var transferableSupported = self.transferableSupported();
|
||||||
|
try {
|
||||||
|
var serialized = self.world.serialize();
|
||||||
|
}
|
||||||
|
catch(error) {
|
||||||
|
console.log("World serialization error:", error.toString() + "\n" + error.stack || error.stackTrace);
|
||||||
|
}
|
||||||
|
var t2 = new Date();
|
||||||
|
//console.log("About to transfer", serialized.serializedWorld.trackedPropertiesPerThangValues, serialized.transferableObjects);
|
||||||
|
try {
|
||||||
|
if(transferableSupported)
|
||||||
|
self.postMessage({type: 'new-debug-world', serialized: serialized.serializedWorld, goalStates: self.goalManager.getGoalStates()}, serialized.transferableObjects);
|
||||||
|
else
|
||||||
|
self.postMessage({type: 'new-debug-world', serialized: serialized.serializedWorld, goalStates: self.goalManager.getGoalStates()});
|
||||||
|
}
|
||||||
|
catch(error) {
|
||||||
|
console.log("World delivery error:", error.toString() + "\n" + error.stack || error.stackTrace);
|
||||||
|
}
|
||||||
|
var t3 = new Date();
|
||||||
|
console.log("And it was so: (" + (diff / self.world.totalFrames).toFixed(3) + "ms per frame,", self.world.totalFrames, "frames)\nSimulation :", diff + "ms \nSerialization:", (t2 - t1) + "ms\nDelivery :", (t3 - t2) + "ms");
|
||||||
|
};
|
||||||
|
|
||||||
|
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'});
|
|
@ -23,6 +23,8 @@ module.exports = class God
|
||||||
Backbone.Mediator.subscribe 'tome:cast-spells', @onTomeCast, @
|
Backbone.Mediator.subscribe 'tome:cast-spells', @onTomeCast, @
|
||||||
@fillWorkerPool = _.throttle @fillWorkerPool, 3000, leading: false
|
@fillWorkerPool = _.throttle @fillWorkerPool, 3000, leading: false
|
||||||
@fillWorkerPool()
|
@fillWorkerPool()
|
||||||
|
#TODO: have this as a constructor option
|
||||||
|
@debugWorker = @createDebugWorker()
|
||||||
|
|
||||||
onTomeCast: (e) ->
|
onTomeCast: (e) ->
|
||||||
return if @dead
|
return if @dead
|
||||||
|
@ -48,14 +50,31 @@ module.exports = class God
|
||||||
worker.creationTime = new Date()
|
worker.creationTime = new Date()
|
||||||
worker.addEventListener 'message', @onWorkerMessage
|
worker.addEventListener 'message', @onWorkerMessage
|
||||||
worker
|
worker
|
||||||
|
|
||||||
|
createDebugWorker: ->
|
||||||
|
worker = new Worker '/javascripts/workers/worker_debug.js'
|
||||||
|
worker.creationTime = new Date()
|
||||||
|
worker.addEventListener 'message', @onDebugWorkerMessage
|
||||||
|
console.log "GOD: Created debug worker"
|
||||||
|
worker
|
||||||
|
|
||||||
onWorkerMessage: (event) =>
|
onWorkerMessage: (event) =>
|
||||||
worker = event.target
|
worker = event.target
|
||||||
if event.data.type is 'worker-initialized'
|
if event.data.type is 'worker-initialized'
|
||||||
#console.log @id, "worker initialized after", ((new Date()) - worker.creationTime), "ms (before it was needed)"
|
#console.log @id, "worker initialized after", ((new Date()) - worker.creationTime), "ms (before it was needed)"
|
||||||
worker.initialized = true
|
worker.initialized = true
|
||||||
worker.removeEventListener 'message', @onWorkerMessage
|
worker.removeEventListener 'message', @onWorkerMessage
|
||||||
|
|
||||||
|
onDebugWorkerMessage: (event) =>
|
||||||
|
worker = event.target
|
||||||
|
switch event.data.type
|
||||||
|
when "worker-initialized"
|
||||||
|
worker.initialized = true
|
||||||
|
when 'new-debug-world'
|
||||||
|
console.log "Created new debug world!"
|
||||||
|
when 'console-log'
|
||||||
|
console.log "|" + @id + "'s " + @id + "|", event.data.args...
|
||||||
|
|
||||||
getAngel: ->
|
getAngel: ->
|
||||||
freeAngel = null
|
freeAngel = null
|
||||||
for angel in @angels
|
for angel in @angels
|
||||||
|
@ -106,7 +125,22 @@ module.exports = class God
|
||||||
firstWorld: @firstWorld
|
firstWorld: @firstWorld
|
||||||
goals: @goalManager?.getGoals()
|
goals: @goalManager?.getGoals()
|
||||||
}}
|
}}
|
||||||
|
|
||||||
|
createDebugWorldUntilFrame: (frame) ->
|
||||||
|
@debugWorker.postMessage
|
||||||
|
func : 'runWorldUntilFrame'
|
||||||
|
args:
|
||||||
|
worldName: @level.name
|
||||||
|
userCodeMap: @getUserCodeMap()
|
||||||
|
level: @level
|
||||||
|
firstWorld: @firstWorld
|
||||||
|
goals: @goalManager?.getGoals()
|
||||||
|
frame: frame
|
||||||
|
|
||||||
|
getDebugWorldCurrentFrame: ->
|
||||||
|
@debugWorker.postMessage
|
||||||
|
func: 'getCurrentFrame'
|
||||||
|
|
||||||
beholdWorld: (angel, serialized, goalStates) ->
|
beholdWorld: (angel, serialized, goalStates) ->
|
||||||
worldCreation = angel.started
|
worldCreation = angel.started
|
||||||
angel.free()
|
angel.free()
|
||||||
|
|
|
@ -103,6 +103,36 @@ module.exports = class World
|
||||||
loadProgressCallback? 1
|
loadProgressCallback? 1
|
||||||
loadedCallback()
|
loadedCallback()
|
||||||
|
|
||||||
|
loadFramesUntilFrame: (frameToLoadUntil, loadedCallback, errorCallback, loadProgressCallback) ->
|
||||||
|
return if @aborted
|
||||||
|
unless @thangs.length
|
||||||
|
console.log "Warning: loadFrames called on empty World"
|
||||||
|
t1 = now()
|
||||||
|
@t0 ?= t1
|
||||||
|
i = @frames.length
|
||||||
|
while i < frameToLoadUntil
|
||||||
|
try
|
||||||
|
@getFrame(i)
|
||||||
|
++i # increment this after we have succeeded in getting the frame, otherwise we'll have to do that frame again
|
||||||
|
catch error
|
||||||
|
# Not an Aether.errors.UserCodeError; maybe we can't recover
|
||||||
|
@addError error
|
||||||
|
for error in (@unhandledRuntimeErrors ? [])
|
||||||
|
return unless errorCallback error # errorCallback tells us whether the error is recoverable
|
||||||
|
@unhandledRuntimeErrors = []
|
||||||
|
t2 = now()
|
||||||
|
if t2 - t1 > PROGRESS_UPDATE_INTERVAL
|
||||||
|
loadProgressCallback? i / @totalFrames
|
||||||
|
t1 = t2
|
||||||
|
if t2 - @t0 > 1000
|
||||||
|
console.log(' Loaded', i, 'of', frameToLoadUntil, "(+" + (t2 - @t0).toFixed(0) + "ms)")
|
||||||
|
@t0 = t2
|
||||||
|
setTimeout((=> @loadFrames(loadedCallback, errorCallback, loadProgressCallback)), 0)
|
||||||
|
return
|
||||||
|
@ended = true
|
||||||
|
loadProgressCallback? 1
|
||||||
|
loadedCallback()
|
||||||
|
|
||||||
abort: ->
|
abort: ->
|
||||||
@aborted = true
|
@aborted = true
|
||||||
|
|
||||||
|
@ -221,7 +251,7 @@ module.exports = class World
|
||||||
@scriptNotes.push scriptNote
|
@scriptNotes.push scriptNote
|
||||||
return unless @goalManager
|
return unless @goalManager
|
||||||
@goalManager.submitWorldGenerationEvent(channel, event, @frames.length)
|
@goalManager.submitWorldGenerationEvent(channel, event, @frames.length)
|
||||||
|
|
||||||
setGoalState: (goalID, status) ->
|
setGoalState: (goalID, status) ->
|
||||||
@goalManager.setGoalState(goalID, status)
|
@goalManager.setGoalState(goalID, status)
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue