2014-01-03 13:32:13 -05: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." ;
2014-03-19 19:12:35 -04:00
// 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 ;
} ;
}
2014-01-03 13:32:13 -05:00
// 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' ) ;
2014-01-30 19:36:36 -05:00
// 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
} ) ;
2014-01-03 13:32:13 -05:00
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' ) ;
2014-05-08 14:43:00 -04:00
Aether . addGlobal ( 'Vector' , require ( 'lib/world/vector' ) ) ;
Aether . addGlobal ( '_' , _ ) ;
2014-05-08 12:47:01 -04:00
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' } ) ;
} ;
2014-01-03 13:32:13 -05:00
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 . onWorldLoaded = function onWorldLoaded ( ) {
self . goalManager . worldGenerationEnded ( ) ;
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-world' , serialized : serialized . serializedWorld , goalStates : self . goalManager . getGoalStates ( ) } , serialized . transferableObjects ) ;
else
self . postMessage ( { type : 'new-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 . world = null ;
} ;
self . onWorldError = function onWorldError ( error ) {
2014-05-08 14:43:00 -04:00
if ( error . isUserCodeProblem ) {
var errorKey = error . userInfo . key ;
if ( ! errorKey || ! self . postedErrors [ errorKey ] ) {
self . postMessage ( { type : 'user-code-problem' , problem : error } ) ;
self . postedErrors [ errorKey ] = error ;
2014-01-03 13:32:13 -05:00
}
}
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 ) ;
} ) ;
2014-02-27 23:01:27 -05:00
self . postMessage ( { type : 'worker-initialized' } ) ;