2014-04-28 15:05:54 -07:00
// 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 ;
2014-05-01 11:12:44 -07:00
self . workerID = "DebugWorker" ;
2014-04-28 15:05:54 -07:00
2014-05-01 11:12:44 -07:00
self . logLimit = 2000 ;
2014-04-28 15:05:54 -07:00
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 ;
}
2014-05-05 13:26:37 -07:00
importScripts ( '/javascripts/world.js' ) ;
2014-04-28 15:05:54 -07:00
var World = self . require ( 'lib/world/world' ) ;
var GoalManager = self . require ( 'lib/world/GoalManager' ) ;
2014-05-05 13:26:37 -07:00
serializedClasses = {
"Thang" : self . require ( 'lib/world/thang' ) ,
"Vector" : self . require ( 'lib/world/vector' ) ,
"Rectangle" : self . require ( 'lib/world/rectangle' )
}
2014-04-28 15:05:54 -07:00
self . getCurrentFrame = function getCurrentFrame ( args ) { return self . world . frames . length ; } ;
2014-05-01 11:12:44 -07:00
//optimize this later
2014-05-01 16:08:46 -07:00
self . currentUserCodeMapCopy = { } ;
2014-05-01 11:12:44 -07:00
self . currentWorldFrame = 0 ;
2014-04-28 15:05:54 -07:00
2014-05-01 16:08:46 -07:00
self . maxSerializationDepth = 3 ;
2014-05-06 10:06:32 -07:00
2014-05-05 13:26:37 -07:00
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 ] ;
2014-05-06 15:07:06 -07:00
if ( key [ 0 ] === "_" ) continue ;
2014-05-05 13:26:37 -07:00
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 ] ;
} ;
2014-05-01 16:08:46 -07:00
2014-05-06 11:36:46 -07:00
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 ;
} ;
2014-05-05 13:26:37 -07:00
self . retrieveValueFromFrame = function retrieveValueFromFrame ( args ) {
2014-05-06 11:36:46 -07:00
var cacheValue ;
2014-05-06 15:07:06 -07:00
if ( args . frame === self . currentWorldFrame && ( cacheValue = self . retrieveValueFromCache ( args . currentThangID , args . currentSpellID , args . variableChain , args . frame ) ) )
2014-05-07 10:03:49 -07:00
return self . postMessage ( { type : 'debug-value-return' , serialized : { "key" : args . variableChain . join ( "." ) , "value" : cacheValue } } ) ;
2014-05-06 11:36:46 -07:00
2014-05-05 13:26:37 -07:00
var retrieveProperty = function retrieveProperty ( currentThangID , currentSpellID , variableChain )
2014-05-01 16:08:46 -07:00
{
2014-05-05 13:26:37 -07:00
var prop ;
var value ;
var keys = [ ] ;
2014-05-07 09:34:37 -07:00
for ( var i = 0 , len = variableChain . length ; i < len ; i ++ ) {
2014-05-05 13:26:37 -07:00
prop = variableChain [ i ] ;
if ( prop === "this" )
{
value = self . world . thangMap [ currentThangID ] ;
}
else if ( i === 0 )
{
try
{
2014-05-07 10:20:22 -07:00
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 ] ;
2014-05-05 13:26:37 -07:00
}
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 ) ;
}
}
2014-05-07 09:34:37 -07:00
}
2014-05-05 13:26:37 -07:00
var serializedProperty = {
"key" : keys . join ( "." ) ,
"value" : self . stringifyValue ( value , 0 )
} ;
2014-05-06 11:36:46 -07:00
self . updateCache ( currentThangID , currentSpellID , variableChain , args . frame , serializedProperty . value ) ;
2014-05-05 13:26:37 -07:00
self . postMessage ( { type : 'debug-value-return' , serialized : serializedProperty } ) ;
2014-05-01 16:08:46 -07:00
} ;
2014-05-05 13:26:37 -07:00
self . enableFlowOnThangSpell ( args . currentThangID , args . currentSpellID , args . userCodeMap ) ;
2014-05-01 16:08:46 -07:00
self . setupWorldToRunUntilFrame ( args ) ;
2014-05-05 13:26:37 -07:00
self . world . loadFramesUntilFrame (
2014-05-06 15:07:06 -07:00
args . frame ,
2014-05-05 13:26:37 -07:00
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 &&
2014-05-06 11:36:46 -07:00
userCodeMap [ thangID ] [ spellID ] . originalOptions . noSerializationInFlow === true )
2014-05-05 13:26:37 -07:00
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 )
}
2014-05-01 16:08:46 -07:00
} ;
self . setupWorldToRunUntilFrame = function setupWorldToRunUntilFrame ( args ) {
2014-04-28 15:05:54 -07:00
self . postedErrors = { } ;
self . t0 = new Date ( ) ;
self . firstWorld = args . firstWorld ;
self . postedErrors = false ;
self . logsLogged = 0 ;
2014-05-01 16:08:46 -07:00
var stringifiedUserCodeMap = JSON . stringify ( args . userCodeMap ) ;
var userCodeMapHasChanged = ! _ . isEqual ( self . currentUserCodeMapCopy , stringifiedUserCodeMap ) ;
self . currentUserCodeMapCopy = stringifiedUserCodeMap ;
2014-05-06 15:07:06 -07:00
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
2014-05-01 16:08:46 -07:00
2014-05-06 15:07:06 -07:00
self . world . totalFrames = args . frame ; //hack to work around error checking
self . currentWorldFrame = args . frame ;
}
2014-05-01 16:08:46 -07:00
} ;
self . runWorldUntilFrame = function runWorldUntilFrame ( args ) {
self . setupWorldToRunUntilFrame ( args ) ;
2014-05-06 11:36:46 -07:00
2014-05-01 16:08:46 -07:00
self . world . loadFramesUntilFrame ( args . frame , self . onWorldLoaded , self . onWorldError , self . onWorldLoadProgress ) ;
2014-05-01 11:12:44 -07:00
2014-04-28 15:05:54 -07:00
} ;
self . onWorldLoaded = function onWorldLoaded ( ) {
2014-05-01 16:08:46 -07:00
console . log ( "World loaded!" ) ;
2014-04-28 15:05:54 -07:00
} ;
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 ) ;
}
/ * W e d o n ' t a c t u a l l y h a v e t h e r e c o v e r a b l e p r o p e r t y a n y m o r e ; h m m
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' } ) ;