2014-05-10 21:24:50 -04:00
// This file is in JavaScript because we can't figure out how to get brunch to compile it bare.
2014-01-03 13:32:13 -05:00
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" ) ;
}
2014-05-09 17:50:08 -04:00
var aArgs = Array . prototype . slice . call ( arguments , 1 ) ,
fToBind = this ,
2014-03-19 19:12:35 -04:00
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-05-10 21:24:50 -04:00
// Assign global window so that Brunch's require (in world.js) can go into it
2014-01-03 13:32:13 -05:00
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 ) {
2014-05-10 21:24:50 -04:00
var args = [ ] . slice . call ( arguments ) ;
2014-01-03 13:32:13 -05:00
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 ) {
2014-06-22 01:31:10 -04:00
try {
self . postMessage ( { type : 'console-log' , args : [ "Could not post log: " + args , error . toString ( ) , error . stack , error . stackTrace ] , id : self . workerID } ) ;
}
catch ( error2 ) {
self . postMessage ( { type : 'console-log' , args : [ "Wow, we had a serious problem trying to console.log something." ] } ) ;
}
2014-01-03 13:32:13 -05:00
}
}
} } ; // so that we don't crash when debugging statements happen
2014-05-10 21:24:50 -04:00
console . error = console . warn = console . info = console . debug = console . log ;
2014-01-03 13:32:13 -05:00
self . console = console ;
2014-07-06 18:01:20 -04:00
self . importScripts ( '/javascripts/lodash.js' , '/javascripts/world.js' , '/javascripts/aether.js' ) ;
2014-01-03 13:32:13 -05:00
2014-06-04 17:47:09 -04:00
var restricted = [ "XMLHttpRequest" , "importScripts" , "Worker" ] ;
for ( var i = 0 ; i < restricted . length ; ++ i ) {
// We could do way more from this: http://stackoverflow.com/questions/10653809/making-webworkers-a-safe-environment
Object . defineProperty ( self , restricted [ i ] , {
get : function ( ) { throw new Error ( "Access to that global property is forbidden." ) ; } ,
configurable : false
} ) ;
}
2014-01-30 19:36:36 -05:00
2014-01-03 13:32:13 -05:00
self . transferableSupported = function transferableSupported ( ) {
2014-05-10 21:24:50 -04:00
if ( typeof self . _transferableSupported !== 'undefined' ) return self . _transferableSupported ;
2014-01-03 13:32:13 -05:00
// Not in IE, even in IE 11
try {
var ab = new ArrayBuffer ( 1 ) ;
worker . postMessage ( ab , [ ab ] ) ;
2014-05-10 21:24:50 -04:00
return self . _transferableSupported = ab . byteLength == 0 ;
2014-01-03 13:32:13 -05:00
} catch ( error ) {
2014-05-10 21:24:50 -04:00
return self . _transferableSupported = false ;
2014-01-03 13:32:13 -05:00
}
2014-05-10 21:24:50 -04:00
return self . _transferableSupported = false ;
} ;
2014-01-03 13:32:13 -05:00
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' ) ,
2014-07-16 18:57:53 -04:00
"Rectangle" : self . require ( 'lib/world/rectangle' ) ,
"Ellipse" : self . require ( 'lib/world/ellipse' ) ,
"LineSegment" : self . require ( 'lib/world/line_segment' )
2014-05-08 12:47:01 -04:00
} ;
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 ] ;
} ;
2014-05-10 21:24:50 -04:00
2014-05-08 12:47:01 -04:00
self . retrieveValueFromFrame = function retrieveValueFromFrame ( args ) {
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
{
2014-05-18 14:41:42 -04:00
if ( Aether . globals [ prop ] )
{
value = Aether . globals [ prop ] ;
}
else
{
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 - 1 ] . statements ) . variables [ prop ] ;
}
2014-05-08 12:47:01 -04: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 . debugWorld . thangMap [ value . id ] ;
2014-05-10 21:24:50 -04:00
value = thang || "<Thang " + value . id + " (non-existent)>" ;
2014-05-08 12:47:01 -04:00
}
else
{
value = classOfValue . deserializeFromAether ( value ) ;
}
}
}
var serializedProperty = {
"key" : keys . join ( "." ) ,
"value" : self . stringifyValue ( value , 0 )
} ;
self . postMessage ( { type : 'debug-value-return' , serialized : serializedProperty } ) ;
} ;
self . enableFlowOnThangSpell ( args . currentThangID , args . currentSpellID , args . userCodeMap ) ;
self . setupDebugWorldToRunUntilFrame ( args ) ;
2014-05-09 17:48:43 -04:00
self . debugWorld . loadFrames (
2014-05-10 21:24:50 -04:00
retrieveProperty . bind ( { } , args . currentThangID , args . currentSpellID , args . variableChain ) ,
2014-05-08 12:47:01 -04:00
self . onDebugWorldError ,
2014-05-09 17:48:43 -04:00
self . onDebugWorldProgress ,
false ,
args . frame
2014-05-08 12:47:01 -04:00
) ;
} ;
self . enableFlowOnThangSpell = function ( thangID , spellID , userCodeMap ) {
try {
2014-05-16 19:52:55 -04:00
var options = userCodeMap [ thangID ] [ spellID ] . originalOptions ;
if ( options . includeFlow === true && options . noSerializationInFlow === true )
2014-05-08 12:47:01 -04:00
return ;
else
{
2014-05-16 19:52:55 -04:00
options . includeFlow = true ;
options . noSerializationInFlow = true ;
2014-05-08 12:47:01 -04:00
var temporaryAether = Aether . deserialize ( userCodeMap [ thangID ] [ spellID ] ) ;
temporaryAether . transpile ( temporaryAether . raw ) ;
userCodeMap [ thangID ] [ spellID ] = temporaryAether . serialize ( ) ;
}
}
2014-05-10 21:24:50 -04:00
catch ( error ) {
console . log ( "Debug error enabling flow on" , thangID , spellID + ":" , error . toString ( ) + "\n" + error . stack || error . stackTrace ) ;
2014-05-08 12:47:01 -04:00
}
} ;
self . setupDebugWorldToRunUntilFrame = function ( args ) {
self . debugPostedErrors = { } ;
self . debugt0 = new Date ( ) ;
self . logsLogged = 0 ;
var stringifiedUserCodeMap = JSON . stringify ( args . userCodeMap ) ;
var userCodeMapHasChanged = ! _ . isEqual ( self . currentUserCodeMapCopy , stringifiedUserCodeMap ) ;
self . currentUserCodeMapCopy = stringifiedUserCodeMap ;
2014-05-09 17:48:43 -04:00
if ( ! self . debugWorld || userCodeMapHasChanged || args . frame < self . currentDebugWorldFrame ) {
2014-05-08 12:47:01 -04:00
try {
2014-05-10 21:24:50 -04:00
self . debugWorld = new World ( args . userCodeMap ) ;
2014-05-15 17:54:31 -04:00
self . debugWorld . levelSessionIDs = args . levelSessionIDs ;
2014-05-08 12:47:01 -04:00
if ( args . level )
self . debugWorld . loadFromLevel ( args . level , true ) ;
2014-05-10 21:24:50 -04:00
self . debugWorld . debugging = true ;
2014-05-08 12:47:01 -04:00
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
2014-05-29 15:26:01 -04:00
Aether . replaceBuiltin ( "Math" , Math ) ;
2014-08-21 19:27:52 -04:00
var replacedLoDash = _ . runInContext ( self ) ;
2014-07-13 21:53:01 -04:00
for ( var key in replacedLoDash )
_ [ key ] = replacedLoDash [ key ] ;
2014-05-08 12:47:01 -04:00
}
2014-05-09 17:48:43 -04:00
self . debugWorld . totalFrames = args . frame ; //hack to work around error checking
self . currentDebugWorldFrame = args . frame ;
2014-05-08 12:47:01 -04:00
} ;
self . onDebugWorldLoaded = function onDebugWorldLoaded ( ) {
2014-05-12 14:16:02 -04:00
self . postMessage ( { type : 'debug-world-loaded' } ) ;
2014-05-08 12:47:01 -04:00
} ;
self . onDebugWorldError = function onDebugWorldError ( error ) {
2014-05-09 17:50:08 -04:00
2014-05-09 17:30:33 -04:00
if ( ! error . isUserCodeProblem ) {
console . log ( "Debug Non-UserCodeError:" , error . toString ( ) + "\n" + error . stack || error . stackTrace ) ;
2014-05-08 12:47:01 -04:00
}
return true ;
} ;
self . onDebugWorldProgress = function onDebugWorldProgress ( progress ) {
self . postMessage ( { type : 'debug-world-load-progress-changed' , progress : progress } ) ;
} ;
self . debugAbort = function ( ) {
2014-05-10 21:24:50 -04:00
if ( self . debugWorld ) {
self . debugWorld . abort ( ) ;
2014-05-22 22:05:05 -04:00
self . debugWorld . destroy ( ) ;
2014-05-08 12:47:01 -04:00
self . debugWorld = null ;
}
2014-05-10 21:24:50 -04:00
self . postMessage ( { type : 'debug-abort' } ) ;
2014-05-08 12:47:01 -04:00
} ;
2014-01-03 13:32:13 -05:00
self . runWorld = function runWorld ( args ) {
self . postedErrors = { } ;
self . t0 = new Date ( ) ;
self . logsLogged = 0 ;
2014-05-09 17:50:08 -04:00
2014-01-03 13:32:13 -05:00
try {
2014-05-10 21:24:50 -04:00
self . world = new World ( args . userCodeMap ) ;
2014-05-15 17:54:31 -04:00
self . world . levelSessionIDs = args . levelSessionIDs ;
2014-01-03 13:32:13 -05:00
if ( args . level )
self . world . loadFromLevel ( args . level , true ) ;
2014-05-10 21:24:50 -04:00
self . world . preloading = args . preload ;
self . world . headless = args . headless ;
2014-08-23 00:35:08 -04:00
self . world . realTime = args . realTime ;
2014-01-03 13:32:13 -05:00
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-29 15:26:01 -04:00
Aether . replaceBuiltin ( "Math" , Math ) ;
2014-08-21 19:27:52 -04:00
var replacedLoDash = _ . runInContext ( self ) ;
2014-07-13 21:53:01 -04:00
for ( var key in replacedLoDash )
_ [ key ] = replacedLoDash [ key ] ;
2014-05-10 21:24:50 -04:00
self . postMessage ( { type : 'start-load-frames' } ) ;
2014-01-03 13:32:13 -05:00
self . world . loadFrames ( self . onWorldLoaded , self . onWorldError , self . onWorldLoadProgress ) ;
} ;
2014-08-21 19:27:52 -04:00
self . serializeFramesSoFar = function serializeFramesSoFar ( ) {
2014-08-22 17:59:32 -04:00
if ( ! self . world ) return ; // We probably got this message late, after delivering the world.
2014-08-22 00:23:45 -04:00
if ( self . world . framesSerializedSoFar == self . world . frames . length ) return ;
self . onWorldLoaded ( ) ;
self . world . framesSerializedSoFar = self . world . frames . length ;
2014-08-21 19:27:52 -04:00
} ;
2014-01-03 13:32:13 -05:00
self . onWorldLoaded = function onWorldLoaded ( ) {
2014-08-22 00:23:45 -04:00
if ( self . world . framesSerializedSoFar == self . world . frames . length ) return ;
if ( self . world . ended )
self . goalManager . worldGenerationEnded ( ) ;
2014-05-10 21:24:50 -04:00
var goalStates = self . goalManager . getGoalStates ( ) ;
2014-08-22 00:23:45 -04:00
if ( self . world . ended )
self . postMessage ( { type : 'end-load-frames' , goalStates : goalStates } ) ;
2014-01-03 13:32:13 -05:00
var t1 = new Date ( ) ;
var diff = t1 - self . t0 ;
2014-05-10 21:24:50 -04:00
if ( self . world . headless )
return console . log ( 'Headless simulation completed in ' + diff + 'ms.' ) ;
2014-01-03 13:32:13 -05:00
var transferableSupported = self . transferableSupported ( ) ;
try {
var serialized = self . world . serialize ( ) ;
}
catch ( error ) {
console . log ( "World serialization error:" , error . toString ( ) + "\n" + error . stack || error . stackTrace ) ;
}
2014-08-22 00:23:45 -04:00
2014-01-03 13:32:13 -05:00
var t2 = new Date ( ) ;
//console.log("About to transfer", serialized.serializedWorld.trackedPropertiesPerThangValues, serialized.transferableObjects);
2014-08-22 00:23:45 -04:00
var messageType = self . world . ended ? 'new-world' : 'some-frames-serialized' ;
2014-01-03 13:32:13 -05:00
try {
2014-08-22 00:23:45 -04:00
var message = { type : messageType , serialized : serialized . serializedWorld , goalStates : goalStates , startFrame : serialized . startFrame , endFrame : serialized . endFrame } ;
2014-01-03 13:32:13 -05:00
if ( transferableSupported )
2014-05-10 21:24:50 -04:00
self . postMessage ( message , serialized . transferableObjects ) ;
2014-01-03 13:32:13 -05:00
else
2014-05-10 21:24:50 -04:00
self . postMessage ( message ) ;
2014-01-03 13:32:13 -05:00
}
catch ( error ) {
console . log ( "World delivery error:" , error . toString ( ) + "\n" + error . stack || error . stackTrace ) ;
}
2014-08-22 00:23:45 -04:00
if ( self . world . ended ) {
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 . goalManager . destroy ( ) ;
self . world . destroy ( ) ;
self . world = null ;
}
2014-01-03 13:32:13 -05:00
} ;
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 ) ;
2014-05-26 21:45:00 -04:00
self . postMessage ( { type : 'non-user-code-problem' , problem : { message : error . toString ( ) } } ) ;
2014-01-03 13:32:13 -05:00
}
/ * 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
2014-05-10 21:24:50 -04:00
if ( ! error . recoverable ) {
2014-01-03 13:32:13 -05:00
self . abort ( ) ;
return false ;
}
* /
return true ;
} ;
self . onWorldLoadProgress = function onWorldLoadProgress ( progress ) {
self . postMessage ( { type : 'world-load-progress-changed' , progress : progress } ) ;
} ;
self . abort = function abort ( ) {
2014-05-10 21:24:50 -04:00
if ( self . world ) {
self . world . abort ( ) ;
2014-05-20 11:00:44 -04:00
self . world . goalManager . destroy ( ) ;
2014-05-22 22:05:05 -04:00
self . world . destroy ( ) ;
2014-01-03 13:32:13 -05:00
self . world = null ;
}
self . postMessage ( { type : 'abort' } ) ;
} ;
self . reportIn = function reportIn ( ) {
2014-05-10 21:24:50 -04:00
self . postMessage ( { type : 'report-in' } ) ;
} ;
2014-01-03 13:32:13 -05:00
2014-05-11 20:42:32 -04:00
self . finalizePreload = function finalizePreload ( ) {
self . world . finalizePreload ( self . onWorldLoaded ) ;
} ;
2014-08-23 20:26:56 -04:00
self . addFlagEvent = function addFlagEvent ( flagEvent ) {
2014-08-24 01:24:00 -04:00
if ( ! self . world ) return ;
2014-08-23 20:26:56 -04:00
self . world . addFlagEvent ( flagEvent ) ;
2014-08-23 00:35:08 -04:00
} ;
2014-08-26 01:05:24 -04:00
self . stopRealTimePlayback = function stopRealTimePlayback ( ) {
if ( ! self . world ) return ;
self . world . realTime = false ;
} ;
2014-01-03 13:32:13 -05:00
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' } ) ;