Began streaming a mighty stream.

This commit is contained in:
Nick Winter 2014-08-21 16:27:52 -07:00
parent 9a116d81be
commit d55afa77b7
16 changed files with 115 additions and 51 deletions

View file

@ -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)

View file

@ -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

View file

@ -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()

View file

@ -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

View file

@ -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')

View file

@ -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 =

View file

@ -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)

View file

@ -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

View file

@ -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'

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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'

View file

@ -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()

View file

@ -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'

View file

@ -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