Began streaming a mighty stream.
This commit is contained in:
parent
9a116d81be
commit
d55afa77b7
16 changed files with 115 additions and 51 deletions
app
assets/javascripts/workers
initialize.coffeelib
models
schemas/subscriptions
views/play
|
@ -2,7 +2,7 @@ var window = self;
|
||||||
var Global = self;
|
var Global = self;
|
||||||
|
|
||||||
importScripts("/javascripts/lodash.js", "/javascripts/aether.js");
|
importScripts("/javascripts/lodash.js", "/javascripts/aether.js");
|
||||||
console.log("Aether Tome worker has finished importing scripts.");
|
//console.log("Aether Tome worker has finished importing scripts.");
|
||||||
var aethers = {};
|
var aethers = {};
|
||||||
|
|
||||||
var createAether = function (spellKey, options)
|
var createAether = function (spellKey, options)
|
||||||
|
|
|
@ -300,7 +300,7 @@ self.setupDebugWorldToRunUntilFrame = function (args) {
|
||||||
}
|
}
|
||||||
Math.random = self.debugWorld.rand.randf; // so user code is predictable
|
Math.random = self.debugWorld.rand.randf; // so user code is predictable
|
||||||
Aether.replaceBuiltin("Math", Math);
|
Aether.replaceBuiltin("Math", Math);
|
||||||
replacedLoDash = _.runInContext(self);
|
var replacedLoDash = _.runInContext(self);
|
||||||
for(var key in replacedLoDash)
|
for(var key in replacedLoDash)
|
||||||
_[key] = replacedLoDash[key];
|
_[key] = replacedLoDash[key];
|
||||||
}
|
}
|
||||||
|
@ -358,13 +358,34 @@ self.runWorld = function runWorld(args) {
|
||||||
}
|
}
|
||||||
Math.random = self.world.rand.randf; // so user code is predictable
|
Math.random = self.world.rand.randf; // so user code is predictable
|
||||||
Aether.replaceBuiltin("Math", Math);
|
Aether.replaceBuiltin("Math", Math);
|
||||||
replacedLoDash = _.runInContext(self);
|
var replacedLoDash = _.runInContext(self);
|
||||||
for(var key in replacedLoDash)
|
for(var key in replacedLoDash)
|
||||||
_[key] = replacedLoDash[key];
|
_[key] = replacedLoDash[key];
|
||||||
self.postMessage({type: 'start-load-frames'});
|
self.postMessage({type: 'start-load-frames'});
|
||||||
self.world.loadFrames(self.onWorldLoaded, self.onWorldError, self.onWorldLoadProgress);
|
self.world.loadFrames(self.onWorldLoaded, self.onWorldError, self.onWorldLoadProgress);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
self.serializeFramesSoFar = function serializeFramesSoFar() {
|
||||||
|
if(!self.world) return console.error("hmm, no world when we went to serialize some frames?");
|
||||||
|
var goalStates = self.goalManager.getGoalStates();
|
||||||
|
var transferableSupported = self.transferableSupported();
|
||||||
|
var serialized = self.world.serializeFramesSoFar();
|
||||||
|
if(!serialized) {
|
||||||
|
console.log("Tried to serialize some frames, but none have been simulated since last time; still at", self.world.framesSerializedSoFar);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
var message = {type: 'some-frames-serialized', serialized: serialized.serializedWorld, goalStates: goalStates, startFrame: serialized.startFrame, endFrame: serialized.endFrame};
|
||||||
|
if(transferableSupported)
|
||||||
|
self.postMessage(message, serialized.transferableObjects);
|
||||||
|
else
|
||||||
|
self.postMessage(message);
|
||||||
|
}
|
||||||
|
catch(error) {
|
||||||
|
console.log("World delivery error:", error.toString() + "\n" + error.stack || error.stackTrace);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
self.onWorldLoaded = function onWorldLoaded() {
|
self.onWorldLoaded = function onWorldLoaded() {
|
||||||
self.goalManager.worldGenerationEnded();
|
self.goalManager.worldGenerationEnded();
|
||||||
var goalStates = self.goalManager.getGoalStates();
|
var goalStates = self.goalManager.getGoalStates();
|
||||||
|
@ -384,7 +405,7 @@ self.onWorldLoaded = function onWorldLoaded() {
|
||||||
var t2 = new Date();
|
var t2 = new Date();
|
||||||
//console.log("About to transfer", serialized.serializedWorld.trackedPropertiesPerThangValues, serialized.transferableObjects);
|
//console.log("About to transfer", serialized.serializedWorld.trackedPropertiesPerThangValues, serialized.transferableObjects);
|
||||||
try {
|
try {
|
||||||
var message = {type: 'new-world', serialized: serialized.serializedWorld, goalStates: goalStates};
|
var message = {type: 'new-world', serialized: serialized.serializedWorld, goalStates: goalStates, startFrame: serialized.startFrame, endFrame: serialized.endFrame};
|
||||||
if(transferableSupported)
|
if(transferableSupported)
|
||||||
self.postMessage(message, serialized.transferableObjects);
|
self.postMessage(message, serialized.transferableObjects);
|
||||||
else
|
else
|
||||||
|
|
|
@ -27,7 +27,8 @@ init = ->
|
||||||
# Set up Backbone.Mediator schemas
|
# Set up Backbone.Mediator schemas
|
||||||
setUpDefinitions()
|
setUpDefinitions()
|
||||||
setUpChannels()
|
setUpChannels()
|
||||||
Backbone.Mediator.setValidationEnabled document.location.href.search(/codecombat.com/) is -1
|
#Backbone.Mediator.setValidationEnabled document.location.href.search(/codecombat.com/) is -1
|
||||||
|
Backbone.Mediator.setValidationEnabled false # STREAM: Should change back
|
||||||
app.initialize()
|
app.initialize()
|
||||||
Backbone.history.start({ pushState: true })
|
Backbone.history.start({ pushState: true })
|
||||||
handleNormalUrls()
|
handleNormalUrls()
|
||||||
|
|
|
@ -67,6 +67,8 @@ module.exports = class Angel extends CocoClass
|
||||||
# We pay attention to certain progress indicators as the world loads.
|
# We pay attention to certain progress indicators as the world loads.
|
||||||
when 'world-load-progress-changed'
|
when 'world-load-progress-changed'
|
||||||
Backbone.Mediator.publish 'god:world-load-progress-changed', event.data
|
Backbone.Mediator.publish 'god:world-load-progress-changed', event.data
|
||||||
|
unless event.data.progress is 1 or @work.preload or @work.headless or @work.synchronous or @deserializingStreamingFrames
|
||||||
|
@worker.postMessage func: 'serializeFramesSoFar' # Stream it!
|
||||||
when 'console-log'
|
when 'console-log'
|
||||||
@log event.data.args...
|
@log event.data.args...
|
||||||
when 'user-code-problem'
|
when 'user-code-problem'
|
||||||
|
@ -80,9 +82,16 @@ module.exports = class Angel extends CocoClass
|
||||||
else
|
else
|
||||||
@fireWorker()
|
@fireWorker()
|
||||||
|
|
||||||
|
# We have some of the frames serialized, so let's send the partially simulated world to the Surface.
|
||||||
|
when 'some-frames-serialized'
|
||||||
|
console.log "angel received some frames", event.data.serialized, "with goals", event.data.goalStates, "and streaming into world", @shared.streamingWorld
|
||||||
|
@deserializingStreamingFrames = true
|
||||||
|
@beholdWorld event.data.serialized, event.data.goalStates, event.data.startFrame, event.data.endFrame, @shared.streamingWorld
|
||||||
|
|
||||||
# Either the world finished simulating successfully, or we abort the worker.
|
# Either the world finished simulating successfully, or we abort the worker.
|
||||||
when 'new-world'
|
when 'new-world'
|
||||||
@beholdWorld event.data.serialized, event.data.goalStates
|
console.log "angel received alll frames", event.data.serialized
|
||||||
|
@beholdWorld event.data.serialized, event.data.goalStates, event.data.startFrame, event.data.endFrame
|
||||||
when 'abort'
|
when 'abort'
|
||||||
@say 'Aborted.', event.data
|
@say 'Aborted.', event.data
|
||||||
clearTimeout @abortTimeout
|
clearTimeout @abortTimeout
|
||||||
|
@ -99,26 +108,33 @@ module.exports = class Angel extends CocoClass
|
||||||
Backbone.Mediator.publish 'god:goals-calculated', goalStates: goalStates
|
Backbone.Mediator.publish 'god:goals-calculated', goalStates: goalStates
|
||||||
@finishWork() if @shared.headless
|
@finishWork() if @shared.headless
|
||||||
|
|
||||||
beholdWorld: (serialized, goalStates) ->
|
beholdWorld: (serialized, goalStates, startFrame, endFrame, streamingWorld) ->
|
||||||
return if @aborting
|
return if @aborting
|
||||||
# Toggle BOX2D_ENABLED during deserialization so that if we have box2d in the namespace, the Collides Components still don't try to create bodies for deserialized Thangs upon attachment.
|
# Toggle BOX2D_ENABLED during deserialization so that if we have box2d in the namespace, the Collides Components still don't try to create bodies for deserialized Thangs upon attachment.
|
||||||
window.BOX2D_ENABLED = false
|
window.BOX2D_ENABLED = false
|
||||||
World.deserialize serialized, @shared.worldClassMap, @shared.lastSerializedWorldFrames, @finishBeholdingWorld(goalStates)
|
World.deserialize serialized, @shared.worldClassMap, @shared.lastSerializedWorldFrames, @finishBeholdingWorld(goalStates), startFrame, endFrame, streamingWorld
|
||||||
window.BOX2D_ENABLED = true
|
window.BOX2D_ENABLED = true
|
||||||
@shared.lastSerializedWorldFrames = serialized.frames
|
@shared.lastSerializedWorldFrames = serialized.frames
|
||||||
|
|
||||||
finishBeholdingWorld: (goalStates) -> (world) =>
|
finishBeholdingWorld: (goalStates) -> (world) =>
|
||||||
return if @aborting
|
return if @aborting
|
||||||
world.findFirstChangedFrame @shared.world
|
finished = world.frames.length is world.totalFrames
|
||||||
@shared.world = world
|
if finished
|
||||||
errorCount = (t for t in @shared.world.thangs when t.errorsOut).length
|
world.findFirstChangedFrame @shared.world
|
||||||
Backbone.Mediator.publish 'god:new-world-created', world: world, firstWorld: @shared.firstWorld, errorCount: errorCount, goalStates: goalStates, team: me.team
|
@shared.world = world
|
||||||
for scriptNote in @shared.world.scriptNotes
|
Backbone.Mediator.publish 'god:new-world-created', world: world, firstWorld: @shared.firstWorld, goalStates: goalStates, team: me.team if @shared.firstWorld
|
||||||
Backbone.Mediator.publish scriptNote.channel, scriptNote.event
|
for scriptNote in @shared.world.scriptNotes
|
||||||
@shared.goalManager?.world = world
|
Backbone.Mediator.publish scriptNote.channel, scriptNote.event
|
||||||
@finishWork()
|
@shared.goalManager?.world = world
|
||||||
|
@finishWork()
|
||||||
|
else
|
||||||
|
@shared.streamingWorld = world
|
||||||
|
#Backbone.Mediator.publish 'god:new-world-created', world: world, firstWorld: @shared.firstWorld, goalStates: goalStates, team: me.team
|
||||||
|
#Backbone.Mediator.publish 'god:streaming-world-updated', world: world, firstWorld: @shared.firstWorld, goalStates: goalStates, team: me.team
|
||||||
|
@deserializingStreamingFrames = false
|
||||||
|
|
||||||
finishWork: ->
|
finishWork: ->
|
||||||
|
@shared.streamingWorld = null
|
||||||
@shared.firstWorld = false
|
@shared.firstWorld = false
|
||||||
@running = false
|
@running = false
|
||||||
_.remove @shared.busyAngels, @
|
_.remove @shared.busyAngels, @
|
||||||
|
@ -127,6 +143,7 @@ module.exports = class Angel extends CocoClass
|
||||||
finalizePreload: ->
|
finalizePreload: ->
|
||||||
@say 'Finalize preload.'
|
@say 'Finalize preload.'
|
||||||
@worker.postMessage func: 'finalizePreload'
|
@worker.postMessage func: 'finalizePreload'
|
||||||
|
@work.preload = false
|
||||||
|
|
||||||
infinitelyLooped: =>
|
infinitelyLooped: =>
|
||||||
@say 'On infinitely looped! Aborting?', @aborting
|
@say 'On infinitely looped! Aborting?', @aborting
|
||||||
|
|
|
@ -14,7 +14,7 @@ module.exports = LinkedInHandler = class LinkedInHandler extends CocoClass
|
||||||
onLinkedInLoaded: (e) ->
|
onLinkedInLoaded: (e) ->
|
||||||
IN.Event.on IN, 'auth', @onLinkedInAuth
|
IN.Event.on IN, 'auth', @onLinkedInAuth
|
||||||
|
|
||||||
onLinkedInAuth: (e) => console.log 'Authorized with LinkedIn'
|
onLinkedInAuth: (e) => #console.log 'Authorized with LinkedIn'
|
||||||
|
|
||||||
constructEmployerAgreementObject: (cb) =>
|
constructEmployerAgreementObject: (cb) =>
|
||||||
IN.API.Profile('me')
|
IN.API.Profile('me')
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
module.exports = initializeLinkedIn = ->
|
module.exports = initializeLinkedIn = ->
|
||||||
window.linkedInAsyncInit = ->
|
window.linkedInAsyncInit = ->
|
||||||
console.log 'Linkedin async init success!'
|
#console.log 'Linkedin async init success!'
|
||||||
Backbone.Mediator.publish 'linkedin-loaded'
|
Backbone.Mediator.publish 'linkedin-loaded'
|
||||||
|
|
||||||
linkedInSnippet =
|
linkedInSnippet =
|
||||||
|
|
|
@ -19,6 +19,7 @@ module.exports = class SpriteBoss extends CocoClass
|
||||||
'level-lock-select': 'onSetLockSelect'
|
'level-lock-select': 'onSetLockSelect'
|
||||||
'level:restarted': 'onLevelRestarted'
|
'level:restarted': 'onLevelRestarted'
|
||||||
'god:new-world-created': 'onNewWorld'
|
'god:new-world-created': 'onNewWorld'
|
||||||
|
'god:streaming-world-updated': 'onNewWorld'
|
||||||
'camera:dragged': 'onCameraDragged'
|
'camera:dragged': 'onCameraDragged'
|
||||||
'sprite:loaded': -> @update(true)
|
'sprite:loaded': -> @update(true)
|
||||||
|
|
||||||
|
|
|
@ -63,6 +63,7 @@ module.exports = Surface = class Surface extends CocoClass
|
||||||
'level-set-surface-camera': 'onSetCamera'
|
'level-set-surface-camera': 'onSetCamera'
|
||||||
'level:restarted': 'onLevelRestarted'
|
'level:restarted': 'onLevelRestarted'
|
||||||
'god:new-world-created': 'onNewWorld'
|
'god:new-world-created': 'onNewWorld'
|
||||||
|
'god:streaming-world-updated': 'onNewWorld'
|
||||||
'tome:cast-spells': 'onCastSpells'
|
'tome:cast-spells': 'onCastSpells'
|
||||||
'level-set-letterbox': 'onSetLetterbox'
|
'level-set-letterbox': 'onSetLetterbox'
|
||||||
'application:idle-changed': 'onIdleChanged'
|
'application:idle-changed': 'onIdleChanged'
|
||||||
|
@ -413,7 +414,7 @@ module.exports = Surface = class Surface extends CocoClass
|
||||||
@surfaceLayer.addChild @cameraBorder = new CameraBorder bounds: @camera.bounds
|
@surfaceLayer.addChild @cameraBorder = new CameraBorder bounds: @camera.bounds
|
||||||
@screenLayer.addChild new Letterbox canvasWidth: canvasWidth, canvasHeight: canvasHeight
|
@screenLayer.addChild new Letterbox canvasWidth: canvasWidth, canvasHeight: canvasHeight
|
||||||
@spriteBoss = new SpriteBoss camera: @camera, surfaceLayer: @surfaceLayer, surfaceTextLayer: @surfaceTextLayer, world: @world, thangTypes: @options.thangTypes, choosing: @options.choosing, navigateToSelection: @options.navigateToSelection, showInvisible: @options.showInvisible
|
@spriteBoss = new SpriteBoss camera: @camera, surfaceLayer: @surfaceLayer, surfaceTextLayer: @surfaceTextLayer, world: @world, thangTypes: @options.thangTypes, choosing: @options.choosing, navigateToSelection: @options.navigateToSelection, showInvisible: @options.showInvisible
|
||||||
@castingScreen ?= new CastingScreen camera: @camera, layer: @screenLayer
|
#@castingScreen ?= new CastingScreen camera: @camera, layer: @screenLayer # Not needed with world streaming.
|
||||||
@playbackOverScreen ?= new PlaybackOverScreen camera: @camera, layer: @screenLayer
|
@playbackOverScreen ?= new PlaybackOverScreen camera: @camera, layer: @screenLayer
|
||||||
@stage.enableMouseOver(10)
|
@stage.enableMouseOver(10)
|
||||||
@stage.addEventListener 'stagemousemove', @onMouseMove
|
@stage.addEventListener 'stagemousemove', @onMouseMove
|
||||||
|
|
|
@ -10,7 +10,7 @@ WorldScriptNote = require './world_script_note'
|
||||||
{now, consolidateThangs, typedArraySupport} = require './world_utils'
|
{now, consolidateThangs, typedArraySupport} = require './world_utils'
|
||||||
Component = require 'lib/world/component'
|
Component = require 'lib/world/component'
|
||||||
System = require 'lib/world/system'
|
System = require 'lib/world/system'
|
||||||
PROGRESS_UPDATE_INTERVAL = 200
|
PROGRESS_UPDATE_INTERVAL = 100
|
||||||
DESERIALIZATION_INTERVAL = 20
|
DESERIALIZATION_INTERVAL = 20
|
||||||
ITEM_ORIGINAL = '53e12043b82921000051cdf9'
|
ITEM_ORIGINAL = '53e12043b82921000051cdf9'
|
||||||
|
|
||||||
|
@ -21,11 +21,13 @@ module.exports = class World
|
||||||
preloading: false # Whether we are just preloading a world in case we soon cast it
|
preloading: false # Whether we are just preloading a world in case we soon cast it
|
||||||
debugging: false # Whether we are just rerunning to debug a world we've already cast
|
debugging: false # Whether we are just rerunning to debug a world we've already cast
|
||||||
headless: false # Whether we are just simulating for goal states instead of all serialized results
|
headless: false # Whether we are just simulating for goal states instead of all serialized results
|
||||||
|
framesSerializedSoFar: 0
|
||||||
apiProperties: ['age', 'dt']
|
apiProperties: ['age', 'dt']
|
||||||
constructor: (@userCodeMap, classMap) ->
|
constructor: (@userCodeMap, classMap) ->
|
||||||
# classMap is needed for deserializing Worlds, Thangs, and other classes
|
# classMap is needed for deserializing Worlds, Thangs, and other classes
|
||||||
@classMap = classMap ? {Vector: Vector, Rectangle: Rectangle, Thang: Thang, Ellipse: Ellipse, LineSegment: LineSegment}
|
@classMap = classMap ? {Vector: Vector, Rectangle: Rectangle, Thang: Thang, Ellipse: Ellipse, LineSegment: LineSegment}
|
||||||
Thang.resetThangIDs()
|
Thang.resetThangIDs()
|
||||||
|
@aRandomID = Math.random()
|
||||||
|
|
||||||
@userCodeMap ?= {}
|
@userCodeMap ?= {}
|
||||||
@thangs = []
|
@thangs = []
|
||||||
|
@ -66,7 +68,6 @@ module.exports = class World
|
||||||
@thangMap[thang.id] = thang
|
@thangMap[thang.id] = thang
|
||||||
|
|
||||||
thangDialogueSounds: ->
|
thangDialogueSounds: ->
|
||||||
if @frames.length < @totalFrames then throw new Error('World should be over before grabbing dialogue')
|
|
||||||
[sounds, seen] = [[], {}]
|
[sounds, seen] = [[], {}]
|
||||||
for frame in @frames
|
for frame in @frames
|
||||||
for thangID, state of frame.thangStateMap
|
for thangID, state of frame.thangStateMap
|
||||||
|
@ -287,9 +288,17 @@ module.exports = class World
|
||||||
addTrackedProperties: (props...) ->
|
addTrackedProperties: (props...) ->
|
||||||
@trackedProperties = (@trackedProperties ? []).concat props
|
@trackedProperties = (@trackedProperties ? []).concat props
|
||||||
|
|
||||||
serialize: ->
|
serializeFramesSoFar: ->
|
||||||
|
return null if @frames.length is @framesSerializedSoFar
|
||||||
|
serialized = @serialize @framesSerializedSoFar, @frames.length
|
||||||
|
@framesSerializedSoFar = @frames.length
|
||||||
|
serialized
|
||||||
|
|
||||||
|
serialize: (startFrame=0, endFrame=null) ->
|
||||||
# Code hotspot; optimize it
|
# Code hotspot; optimize it
|
||||||
if @frames.length < @totalFrames then throw new Error('World Should Be Over Before Serialization')
|
if not endFrame? and @frames.length < @totalFrames then throw new Error('World Should Be Over Before Serialization')
|
||||||
|
endFrame ?= @totalFrames
|
||||||
|
console.log "... world serializing frames from", startFrame, "to", endFrame
|
||||||
[transferableObjects, nontransferableObjects] = [0, 0]
|
[transferableObjects, nontransferableObjects] = [0, 0]
|
||||||
o = {totalFrames: @totalFrames, maxTotalFrames: @maxTotalFrames, frameRate: @frameRate, dt: @dt, victory: @victory, userCodeMap: {}, trackedProperties: {}}
|
o = {totalFrames: @totalFrames, maxTotalFrames: @maxTotalFrames, frameRate: @frameRate, dt: @dt, victory: @victory, userCodeMap: {}, trackedProperties: {}}
|
||||||
o.trackedProperties[prop] = @[prop] for prop in @trackedProperties or []
|
o.trackedProperties[prop] = @[prop] for prop in @trackedProperties or []
|
||||||
|
@ -305,19 +314,20 @@ module.exports = class World
|
||||||
o.trackedPropertiesPerThangKeys = []
|
o.trackedPropertiesPerThangKeys = []
|
||||||
o.trackedPropertiesPerThangTypes = []
|
o.trackedPropertiesPerThangTypes = []
|
||||||
trackedPropertiesPerThangValues = [] # We won't send these, just the offsets and the storage buffer
|
trackedPropertiesPerThangValues = [] # We won't send these, just the offsets and the storage buffer
|
||||||
o.trackedPropertiesPerThangValuesOffsets = [] # Needed to reconstruct ArrayBufferViews on other end, since Firefox has bugs transfering those: https://bugzilla.mozilla.org/show_bug.cgi?id=841904 and https://bugzilla.mozilla.org/show_bug.cgi?id=861925
|
o.trackedPropertiesPerThangValuesOffsets = [] # Needed to reconstruct ArrayBufferViews on other end, since Firefox has bugs transfering those: https://bugzilla.mozilla.org/show_bug.cgi?id=841904 and https://bugzilla.mozilla.org/show_bug.cgi?id=861925 # Actually, as of January 2014, it should be fixed.
|
||||||
transferableStorageBytesNeeded = 0
|
transferableStorageBytesNeeded = 0
|
||||||
nFrames = @frames.length
|
nFrames = endFrame - startFrame
|
||||||
|
streaming = nFrames < @totalFrames
|
||||||
for thang in @thangs
|
for thang in @thangs
|
||||||
# Don't serialize empty trackedProperties for stateless Thangs which haven't changed (like obstacles).
|
# Don't serialize empty trackedProperties for stateless Thangs which haven't changed (like obstacles).
|
||||||
# Check both, since sometimes people mark stateless Thangs but don't change them, and those should still be tracked, and the inverse doesn't work on the other end (we'll just think it doesn't exist then).
|
# Check both, since sometimes people mark stateless Thangs but don't change them, and those should still be tracked, and the inverse doesn't work on the other end (we'll just think it doesn't exist then).
|
||||||
continue if thang.stateless and not _.some(thang.trackedPropertiesUsed, Boolean)
|
continue if thang.stateless and not _.some(thang.trackedPropertiesUsed, Boolean) and not streaming
|
||||||
o.trackedPropertiesThangIDs.push thang.id
|
o.trackedPropertiesThangIDs.push thang.id
|
||||||
trackedPropertiesIndices = []
|
trackedPropertiesIndices = []
|
||||||
trackedPropertiesKeys = []
|
trackedPropertiesKeys = []
|
||||||
trackedPropertiesTypes = []
|
trackedPropertiesTypes = []
|
||||||
for used, propIndex in thang.trackedPropertiesUsed
|
for used, propIndex in thang.trackedPropertiesUsed
|
||||||
continue unless used
|
continue unless used or streaming
|
||||||
trackedPropertiesIndices.push propIndex
|
trackedPropertiesIndices.push propIndex
|
||||||
trackedPropertiesKeys.push thang.trackedPropertiesKeys[propIndex]
|
trackedPropertiesKeys.push thang.trackedPropertiesKeys[propIndex]
|
||||||
trackedPropertiesTypes.push thang.trackedPropertiesTypes[propIndex]
|
trackedPropertiesTypes.push thang.trackedPropertiesTypes[propIndex]
|
||||||
|
@ -357,8 +367,8 @@ module.exports = class World
|
||||||
|
|
||||||
t1 = now()
|
t1 = now()
|
||||||
o.frameHashes = []
|
o.frameHashes = []
|
||||||
for frame, frameIndex in @frames
|
for frameIndex in [startFrame ... endFrame]
|
||||||
o.frameHashes.push frame.serialize(frameIndex, o.trackedPropertiesThangIDs, o.trackedPropertiesPerThangIndices, o.trackedPropertiesPerThangTypes, trackedPropertiesPerThangValues, o.specialValuesToKeys, o.specialKeysToValues)
|
o.frameHashes.push @frames[frameIndex].serialize(frameIndex, o.trackedPropertiesThangIDs, o.trackedPropertiesPerThangIndices, o.trackedPropertiesPerThangTypes, trackedPropertiesPerThangValues, o.specialValuesToKeys, o.specialKeysToValues)
|
||||||
t2 = now()
|
t2 = now()
|
||||||
|
|
||||||
unless typedArraySupport
|
unless typedArraySupport
|
||||||
|
@ -368,28 +378,29 @@ module.exports = class World
|
||||||
flattened.push value
|
flattened.push value
|
||||||
o.storageBuffer = flattened
|
o.storageBuffer = flattened
|
||||||
|
|
||||||
#console.log 'Allocating memory:', (t1 - t0).toFixed(0), 'ms; assigning values:', (t2 - t1).toFixed(0), 'ms, so', ((t2 - t1) / @frames.length).toFixed(3), 'ms per frame'
|
#console.log 'Allocating memory:', (t1 - t0).toFixed(0), 'ms; assigning values:', (t2 - t1).toFixed(0), 'ms, so', ((t2 - t1) / nFrames).toFixed(3), 'ms per frame for', nFrames, 'frames'
|
||||||
#console.log 'Got', transferableObjects, 'transferable objects and', nontransferableObjects, 'nontransferable; stored', transferableStorageBytesNeeded, 'bytes transferably'
|
#console.log 'Got', transferableObjects, 'transferable objects and', nontransferableObjects, 'nontransferable; stored', transferableStorageBytesNeeded, 'bytes transferably'
|
||||||
|
|
||||||
o.thangs = (t.serialize() for t in @thangs.concat(@extraneousThangs ? []))
|
o.thangs = (t.serialize() for t in @thangs.concat(@extraneousThangs ? []))
|
||||||
o.scriptNotes = (sn.serialize() for sn in @scriptNotes)
|
o.scriptNotes = (sn.serialize() for sn in @scriptNotes)
|
||||||
if o.scriptNotes.length > 200
|
if o.scriptNotes.length > 200
|
||||||
console.log 'Whoa, serializing a lot of WorldScriptNotes here:', o.scriptNotes.length
|
console.log 'Whoa, serializing a lot of WorldScriptNotes here:', o.scriptNotes.length
|
||||||
{serializedWorld: o, transferableObjects: [o.storageBuffer]}
|
{serializedWorld: o, transferableObjects: [o.storageBuffer], startFrame: startFrame, endFrame: endFrame}
|
||||||
|
|
||||||
@deserialize: (o, classMap, oldSerializedWorldFrames, finishedWorldCallback) ->
|
@deserialize: (o, classMap, oldSerializedWorldFrames, finishedWorldCallback, startFrame, endFrame, streamingWorld) ->
|
||||||
# Code hotspot; optimize it
|
# Code hotspot; optimize it
|
||||||
#console.log 'Deserializing', o, 'length', JSON.stringify(o).length
|
#console.log 'Deserializing', o, 'length', JSON.stringify(o).length
|
||||||
#console.log JSON.stringify(o)
|
#console.log JSON.stringify(o)
|
||||||
#console.log 'Got special keys and values:', o.specialValuesToKeys, o.specialKeysToValues
|
#console.log 'Got special keys and values:', o.specialValuesToKeys, o.specialKeysToValues
|
||||||
perf = {}
|
perf = {}
|
||||||
perf.t0 = now()
|
perf.t0 = now()
|
||||||
w = new World o.userCodeMap, classMap
|
nFrames = endFrame - startFrame
|
||||||
|
w = streamingWorld ? new World o.userCodeMap, classMap
|
||||||
[w.totalFrames, w.maxTotalFrames, w.frameRate, w.dt, w.scriptNotes, w.victory] = [o.totalFrames, o.maxTotalFrames, o.frameRate, o.dt, o.scriptNotes ? [], o.victory]
|
[w.totalFrames, w.maxTotalFrames, w.frameRate, w.dt, w.scriptNotes, w.victory] = [o.totalFrames, o.maxTotalFrames, o.frameRate, o.dt, o.scriptNotes ? [], o.victory]
|
||||||
w[prop] = val for prop, val of o.trackedProperties
|
w[prop] = val for prop, val of o.trackedProperties
|
||||||
|
|
||||||
perf.t1 = now()
|
perf.t1 = now()
|
||||||
w.thangs = (Thang.deserialize(thang, w, classMap) for thang in o.thangs)
|
w.thangs = (Thang.deserialize(thang, w, classMap) for thang in o.thangs) # TODO: just do the new ones?
|
||||||
w.setThang thang for thang in w.thangs
|
w.setThang thang for thang in w.thangs
|
||||||
w.scriptNotes = (WorldScriptNote.deserialize(sn, w, classMap) for sn in o.scriptNotes)
|
w.scriptNotes = (WorldScriptNote.deserialize(sn, w, classMap) for sn in o.scriptNotes)
|
||||||
perf.t2 = now()
|
perf.t2 = now()
|
||||||
|
@ -400,7 +411,7 @@ module.exports = class World
|
||||||
o.trackedPropertiesPerThangValues.push (trackedPropertiesValues = [])
|
o.trackedPropertiesPerThangValues.push (trackedPropertiesValues = [])
|
||||||
trackedPropertiesValuesOffsets = o.trackedPropertiesPerThangValuesOffsets[thangIndex]
|
trackedPropertiesValuesOffsets = o.trackedPropertiesPerThangValuesOffsets[thangIndex]
|
||||||
for type, propIndex in trackedPropertyTypes
|
for type, propIndex in trackedPropertyTypes
|
||||||
storage = ThangState.createArrayForType(type, o.totalFrames, o.storageBuffer, trackedPropertiesValuesOffsets[propIndex])[0]
|
storage = ThangState.createArrayForType(type, nFrames, o.storageBuffer, trackedPropertiesValuesOffsets[propIndex])[0]
|
||||||
unless typedArraySupport
|
unless typedArraySupport
|
||||||
# This could be more efficient
|
# This could be more efficient
|
||||||
i = trackedPropertiesValuesOffsets[propIndex]
|
i = trackedPropertiesValuesOffsets[propIndex]
|
||||||
|
@ -409,26 +420,30 @@ module.exports = class World
|
||||||
perf.t3 = now()
|
perf.t3 = now()
|
||||||
|
|
||||||
perf.batches = 0
|
perf.batches = 0
|
||||||
w.frames = []
|
w.frames = [] unless streamingWorld
|
||||||
_.delay @deserializeSomeFrames, 1, o, w, finishedWorldCallback, perf
|
clearTimeout @deserializationTimeout if @deserializationTimeout
|
||||||
|
@deserializationTimeout = _.delay @deserializeSomeFrames, 1, o, w, finishedWorldCallback, perf, startFrame, endFrame
|
||||||
|
|
||||||
# Spread deserialization out across multiple calls so the interface stays responsive
|
# Spread deserialization out across multiple calls so the interface stays responsive
|
||||||
@deserializeSomeFrames: (o, w, finishedWorldCallback, perf) =>
|
@deserializeSomeFrames: (o, w, finishedWorldCallback, perf, startFrame, endFrame) =>
|
||||||
++perf.batches
|
++perf.batches
|
||||||
startTime = now()
|
startTime = now()
|
||||||
for frameIndex in [w.frames.length ... o.totalFrames]
|
for frameIndex in [w.frames.length ... endFrame]
|
||||||
w.frames.push WorldFrame.deserialize(w, frameIndex, o.trackedPropertiesThangIDs, o.trackedPropertiesThangs, o.trackedPropertiesPerThangKeys, o.trackedPropertiesPerThangTypes, o.trackedPropertiesPerThangValues, o.specialKeysToValues, o.frameHashes[frameIndex])
|
w.frames.push WorldFrame.deserialize(w, frameIndex, o.trackedPropertiesThangIDs, o.trackedPropertiesThangs, o.trackedPropertiesPerThangKeys, o.trackedPropertiesPerThangTypes, o.trackedPropertiesPerThangValues, o.specialKeysToValues, o.frameHashes[frameIndex])
|
||||||
if (now() - startTime) > DESERIALIZATION_INTERVAL
|
if (now() - startTime) > DESERIALIZATION_INTERVAL
|
||||||
_.delay @deserializeSomeFrames, 1, o, w, finishedWorldCallback, perf
|
console.log " Deserialization not finished, let's do it again soon. Have:", w.frames.length, ", wanted from", startFrame, "to", endFrame
|
||||||
|
@deserializationTimeout = _.delay @deserializeSomeFrames, 1, o, w, finishedWorldCallback, perf, startFrame, endFrame
|
||||||
return
|
return
|
||||||
@finishDeserializing w, finishedWorldCallback, perf
|
@deserializationTimeout = null
|
||||||
|
@finishDeserializing w, finishedWorldCallback, perf, startFrame, endFrame
|
||||||
|
|
||||||
@finishDeserializing: (w, finishedWorldCallback, perf) ->
|
@finishDeserializing: (w, finishedWorldCallback, perf, startFrame, endFrame) ->
|
||||||
perf.t4 = now()
|
perf.t4 = now()
|
||||||
|
nFrames = endFrame - startFrame
|
||||||
w.ended = true
|
w.ended = true
|
||||||
w.getFrame(w.totalFrames - 1).restoreState()
|
w.getFrame(endFrame - 1).restoreState()
|
||||||
perf.t5 = now()
|
perf.t5 = now()
|
||||||
console.log 'Deserialization:', (perf.t5 - perf.t0).toFixed(0) + 'ms (' + ((perf.t5 - perf.t0) / w.frames.length).toFixed(3) + 'ms per frame).', perf.batches, 'batches.'
|
console.log 'Deserialization:', (perf.t5 - perf.t0).toFixed(0) + 'ms (' + ((perf.t5 - perf.t0) / nFrames).toFixed(3) + 'ms per frame).', perf.batches, 'batches.'
|
||||||
if false
|
if false
|
||||||
console.log ' Deserializing--constructing new World:', (perf.t1 - perf.t0).toFixed(2) + 'ms'
|
console.log ' Deserializing--constructing new World:', (perf.t1 - perf.t0).toFixed(2) + 'ms'
|
||||||
console.log ' Deserializing--Thangs and ScriptNotes:', (perf.t2 - perf.t1).toFixed(2) + 'ms'
|
console.log ' Deserializing--Thangs and ScriptNotes:', (perf.t2 - perf.t1).toFixed(2) + 'ms'
|
||||||
|
|
|
@ -111,7 +111,7 @@ module.exports = class Level extends CocoModel
|
||||||
visit = (c) ->
|
visit = (c) ->
|
||||||
return if c in sorted
|
return if c in sorted
|
||||||
lc = _.find levelComponents, {original: c.original}
|
lc = _.find levelComponents, {original: c.original}
|
||||||
console.error thang.id or thang.name, 'couldn\'t find lc for', c, 'of', levelComponents unless lc
|
#console.error thang.id or thang.name, 'couldn\'t find lc for', c, 'of', levelComponents unless lc # STREAM: uncomment
|
||||||
return unless lc
|
return unless lc
|
||||||
if lc.name is 'Programmable'
|
if lc.name is 'Programmable'
|
||||||
# Programmable always comes last
|
# Programmable always comes last
|
||||||
|
|
|
@ -188,7 +188,7 @@ module.exports = class ThangType extends CocoModel
|
||||||
portrait = if portrait then '(Portrait)' else ''
|
portrait = if portrait then '(Portrait)' else ''
|
||||||
name = _.string.rpad @get('name'), 20
|
name = _.string.rpad @get('name'), 20
|
||||||
time = _.string.lpad '' + new Date().getTime() - startTime, 6
|
time = _.string.lpad '' + new Date().getTime() - startTime, 6
|
||||||
console.debug "Built sheet: #{name} #{time}ms #{kind} #{portrait}"
|
#console.debug "Built sheet: #{name} #{time}ms #{kind} #{portrait}" # STREAM: uncomment
|
||||||
|
|
||||||
spriteSheetKey: (options) ->
|
spriteSheetKey: (options) ->
|
||||||
colorConfigs = []
|
colorConfigs = []
|
||||||
|
@ -276,7 +276,7 @@ module.exports = class ThangType extends CocoModel
|
||||||
@get('components') or [],
|
@get('components') or [],
|
||||||
(compRef) -> compRef.original is LevelComponent.ItemID)
|
(compRef) -> compRef.original is LevelComponent.ItemID)
|
||||||
return itemComponentRef?.config?.slots or []
|
return itemComponentRef?.config?.slots or []
|
||||||
|
|
||||||
getFrontFacingStats: ->
|
getFrontFacingStats: ->
|
||||||
stats = []
|
stats = []
|
||||||
for component in @get('components') or []
|
for component in @get('components') or []
|
||||||
|
@ -317,4 +317,4 @@ module.exports = class ThangType extends CocoModel
|
||||||
snippets = config.programmableSnippets
|
snippets = config.programmableSnippets
|
||||||
if snippets.length
|
if snippets.length
|
||||||
stats.push { name: 'Snippets', value: snippets.join(', ') }
|
stats.push { name: 'Snippets', value: snippets.join(', ') }
|
||||||
stats
|
stats
|
||||||
|
|
|
@ -11,5 +11,8 @@ module.exports =
|
||||||
'god:new-world-created':
|
'god:new-world-created':
|
||||||
{} # TODO schema
|
{} # TODO schema
|
||||||
|
|
||||||
|
'god:streaming-world-updated':
|
||||||
|
{} # TODO schema
|
||||||
|
|
||||||
'god:world-load-progress-changed':
|
'god:world-load-progress-changed':
|
||||||
{} # TODO schema
|
{} # TODO schema
|
||||||
|
|
|
@ -55,6 +55,7 @@ module.exports = class SpectateLevelView extends RootView
|
||||||
'surface:world-set-up': 'onSurfaceSetUpNewWorld'
|
'surface:world-set-up': 'onSurfaceSetUpNewWorld'
|
||||||
'level:set-team': 'setTeam'
|
'level:set-team': 'setTeam'
|
||||||
'god:new-world-created': 'loadSoundsForWorld'
|
'god:new-world-created': 'loadSoundsForWorld'
|
||||||
|
'god:streaming-world-updated': 'loadSoundsForWorld'
|
||||||
'next-game-pressed': 'onNextGamePressed'
|
'next-game-pressed': 'onNextGamePressed'
|
||||||
'level:started': 'onLevelStarted'
|
'level:started': 'onLevelStarted'
|
||||||
'level:loading-view-unveiled': 'onLoadingViewUnveiled'
|
'level:loading-view-unveiled': 'onLoadingViewUnveiled'
|
||||||
|
|
|
@ -22,6 +22,7 @@ module.exports = class LevelPlaybackView extends CocoView
|
||||||
'level-toggle-grid': 'onToggleGrid'
|
'level-toggle-grid': 'onToggleGrid'
|
||||||
'surface:frame-changed': 'onFrameChanged'
|
'surface:frame-changed': 'onFrameChanged'
|
||||||
'god:new-world-created': 'onNewWorld'
|
'god:new-world-created': 'onNewWorld'
|
||||||
|
'god:streaming-world-updated': 'onNewWorld' # Maybe?
|
||||||
'level-set-letterbox': 'onSetLetterbox'
|
'level-set-letterbox': 'onSetLetterbox'
|
||||||
'tome:cast-spells': 'onCastSpells'
|
'tome:cast-spells': 'onCastSpells'
|
||||||
|
|
||||||
|
@ -329,7 +330,9 @@ module.exports = class LevelPlaybackView extends CocoView
|
||||||
return if @shouldIgnore()
|
return if @shouldIgnore()
|
||||||
Backbone.Mediator.publish 'level-set-time', ratio: ratio, scrubDuration: duration
|
Backbone.Mediator.publish 'level-set-time', ratio: ratio, scrubDuration: duration
|
||||||
|
|
||||||
shouldIgnore: -> return @disabled or @casting or false
|
shouldIgnore: ->
|
||||||
|
#return @disabled or @casting or false # STREAM: figure this out
|
||||||
|
return false
|
||||||
|
|
||||||
onTogglePlay: (e) ->
|
onTogglePlay: (e) ->
|
||||||
e?.preventDefault()
|
e?.preventDefault()
|
||||||
|
|
|
@ -53,6 +53,7 @@ module.exports = class PlayLevelView extends RootView
|
||||||
'level-disable-controls': 'onDisableControls'
|
'level-disable-controls': 'onDisableControls'
|
||||||
'level-enable-controls': 'onEnableControls'
|
'level-enable-controls': 'onEnableControls'
|
||||||
'god:new-world-created': 'onNewWorld'
|
'god:new-world-created': 'onNewWorld'
|
||||||
|
'god:streaming-world-updated': 'onNewWorld'
|
||||||
'god:infinite-loop': 'onInfiniteLoop'
|
'god:infinite-loop': 'onInfiniteLoop'
|
||||||
'level-reload-from-data': 'onLevelReloadFromData'
|
'level-reload-from-data': 'onLevelReloadFromData'
|
||||||
'level-reload-thang-type': 'onLevelReloadThangType'
|
'level-reload-thang-type': 'onLevelReloadThangType'
|
||||||
|
|
|
@ -128,10 +128,10 @@ module.exports = class SpellView extends CocoView
|
||||||
# passEvent: true # https://github.com/ajaxorg/ace/blob/master/lib/ace/keyboard/keybinding.js#L114
|
# passEvent: true # https://github.com/ajaxorg/ace/blob/master/lib/ace/keyboard/keybinding.js#L114
|
||||||
# No easy way to selectively cancel shift+space, since we don't get access to the event.
|
# No easy way to selectively cancel shift+space, since we don't get access to the event.
|
||||||
# Maybe we could temporarily set ourselves to read-only if we somehow know that a script is active?
|
# Maybe we could temporarily set ourselves to read-only if we somehow know that a script is active?
|
||||||
exec: =>
|
exec: =>
|
||||||
if @scriptRunning
|
if @scriptRunning
|
||||||
Backbone.Mediator.publish 'level:shift-space-pressed'
|
Backbone.Mediator.publish 'level:shift-space-pressed'
|
||||||
else
|
else
|
||||||
@ace.insert ' '
|
@ace.insert ' '
|
||||||
|
|
||||||
addCommand
|
addCommand
|
||||||
|
|
Reference in a new issue