mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2025-02-17 08:50:58 -05:00
Fixed many issues with world streaming.
This commit is contained in:
parent
9b31e28536
commit
611ecbf470
5 changed files with 62 additions and 44 deletions
|
@ -64,16 +64,6 @@ module.exports = class Angel extends CocoClass
|
|||
clearTimeout @condemnTimeout
|
||||
@beholdGoalStates event.data.goalStates # Work ends here if we're headless.
|
||||
|
||||
# We pay attention to certain progress indicators as the world loads.
|
||||
when 'world-load-progress-changed'
|
||||
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 or @shared.firstWorld
|
||||
@worker.postMessage func: 'serializeFramesSoFar' # Stream it!
|
||||
when 'console-log'
|
||||
@log event.data.args...
|
||||
when 'user-code-problem'
|
||||
Backbone.Mediator.publish 'god:user-code-problem', problem: event.data.problem
|
||||
|
||||
# We have to abort like an infinite loop if we see one of these; they're not really recoverable
|
||||
when 'non-user-code-problem'
|
||||
Backbone.Mediator.publish 'god:non-user-code-problem', problem: event.data.problem
|
||||
|
@ -82,16 +72,7 @@ module.exports = class Angel extends CocoClass
|
|||
else
|
||||
@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.
|
||||
when 'new-world'
|
||||
#console.log "angel received alll frames", event.data.serialized, "and have streaming world", @shared.streamingWorld
|
||||
@beholdWorld event.data.serialized, event.data.goalStates, event.data.startFrame, event.data.endFrame, @shared.streamingWorld
|
||||
# If it didn't finish simulating successfully, or we abort the worker.
|
||||
when 'abort'
|
||||
@say 'Aborted.', event.data
|
||||
clearTimeout @abortTimeout
|
||||
|
@ -100,6 +81,23 @@ module.exports = class Angel extends CocoClass
|
|||
_.remove @shared.busyAngels, @
|
||||
@doWork()
|
||||
|
||||
# We pay attention to certain progress indicators as the world loads.
|
||||
when 'console-log'
|
||||
@log event.data.args...
|
||||
when 'user-code-problem'
|
||||
Backbone.Mediator.publish 'god:user-code-problem', problem: event.data.problem
|
||||
when 'world-load-progress-changed'
|
||||
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 @deserializationQueue.length or @shared.firstWorld
|
||||
@worker.postMessage func: 'serializeFramesSoFar' # Stream it!
|
||||
|
||||
# We have some or all of the frames serialized, so let's send the (partially?) simulated world to the Surface.
|
||||
when 'some-frames-serialized', 'new-world'
|
||||
deserializationArgs = [event.data.serialized, event.data.goalStates, event.data.startFrame, event.data.endFrame, @shared.streamingWorld]
|
||||
@deserializationQueue.push deserializationArgs
|
||||
if @deserializationQueue.length is 1
|
||||
@beholdWorld deserializationArgs...
|
||||
|
||||
else
|
||||
@log 'Received unsupported message:', event.data
|
||||
|
||||
|
@ -131,12 +129,14 @@ module.exports = class Angel extends CocoClass
|
|||
@shared.goalManager?.world = world
|
||||
@finishWork()
|
||||
else
|
||||
@deserializingStreamingFrames = false
|
||||
@deserializationQueue.shift() # Finished with this deserialization.
|
||||
if deserializationArgs = @deserializationQueue[0] # Start another?
|
||||
@beholdWorld deserializationArgs...
|
||||
|
||||
finishWork: ->
|
||||
@shared.streamingWorld = null
|
||||
@shared.firstWorld = false
|
||||
@deserializingStreamingFrames = false
|
||||
@deserializationQueue = []
|
||||
@running = false
|
||||
_.remove @shared.busyAngels, @
|
||||
@doWork()
|
||||
|
@ -163,6 +163,7 @@ module.exports = class Angel extends CocoClass
|
|||
@say 'Running world...'
|
||||
@running = true
|
||||
@shared.busyAngels.push @
|
||||
@deserializationQueue = []
|
||||
@worker.postMessage func: 'runWorld', args: @work
|
||||
clearTimeout @purgatoryTimer
|
||||
@say 'Infinite loop timer started at interval of', @infiniteLoopIntervalDuration
|
||||
|
|
|
@ -378,7 +378,7 @@ module.exports = Surface = class Surface extends CocoClass
|
|||
ffScrubDuration = 1000 * ffToTime / ffSpeed
|
||||
ffScrubDuration = Math.min(ffScrubDuration, ffInterval)
|
||||
ffFactor = ffInterval / ffScrubDuration
|
||||
if ffFactor > 2
|
||||
if ffFactor > 1.5
|
||||
createjs.Tween.removeTweens(@)
|
||||
@scrubbingTo = null
|
||||
@fastForwarding = true
|
||||
|
|
|
@ -85,7 +85,7 @@ module.exports = class Thang
|
|||
throw new Error "Two types were specified for trackable property #{prop}: #{oldType} and #{type}."
|
||||
|
||||
keepTrackedProperty: (prop) ->
|
||||
# Hmm; can we do this faster?
|
||||
# Wish we could do this faster, but I can't think of how.
|
||||
propIndex = @trackedPropertiesKeys.indexOf prop
|
||||
if propIndex isnt -1
|
||||
@trackedPropertiesUsed[propIndex] = true
|
||||
|
@ -147,6 +147,8 @@ module.exports = class Thang
|
|||
for trackedFinalProperty in @trackedFinalProperties ? []
|
||||
# TODO: take some (but not all) of serialize logic from ThangState to handle other types
|
||||
o.finalState[trackedFinalProperty] = @[trackedFinalProperty]
|
||||
# Since we might keep tracked properties later during streaming, we need to know which we think are unused.
|
||||
o.unusedTrackedPropertyKeys = (@trackedPropertiesKeys[propIndex] for used, propIndex in @trackedPropertiesUsed when not used)
|
||||
o
|
||||
|
||||
@deserialize: (o, world, classMap) ->
|
||||
|
@ -154,6 +156,8 @@ module.exports = class Thang
|
|||
for [componentClassName, componentConfig] in o.components
|
||||
componentClass = classMap[componentClassName]
|
||||
t.addComponents [componentClass, componentConfig]
|
||||
t.unusedTrackedPropertyKeys = o.unusedTrackedPropertyKeys
|
||||
t.unusedTrackedPropertyValues = (t[prop] for prop in o.unusedTrackedPropertyKeys)
|
||||
for prop, val of o.finalState
|
||||
# TODO: take some (but not all) of deserialize logic from ThangState to handle other types
|
||||
t[prop] = val
|
||||
|
|
|
@ -65,7 +65,10 @@ module.exports = class ThangState
|
|||
# Get the property, whether we have it stored in @props or in @trackedPropertyValues. Optimize it.
|
||||
# Figured based on http://jsperf.com/object-vs-array-vs-native-linked-list/13 that it should be faster with small arrays to do the indexOf reads (each up to 24x faster) than to do a single object read, and then we don't have to maintain an extra @props object; just keep array
|
||||
propIndex = @trackedPropertyKeys.indexOf prop
|
||||
return null if propIndex is -1
|
||||
if propIndex is -1
|
||||
initialPropIndex = @thang.unusedTrackedPropertyKeys.indexOf prop
|
||||
return null if initialPropIndex is -1
|
||||
return @thang.unusedTrackedPropertyValues[initialPropIndex]
|
||||
value = @props[propIndex]
|
||||
return value if value isnt undefined or @hasRestored
|
||||
return @props[propIndex] = @getStoredProp propIndex
|
||||
|
@ -74,22 +77,28 @@ module.exports = class ThangState
|
|||
# Restore trackedProperties' values to @thang, retrieving them from @trackedPropertyValues if needed. Optimize it.
|
||||
return @ if @thang._state is @ and not @thang.partialState
|
||||
unless @hasRestored # Restoring in a deserialized World for first time
|
||||
for prop, propIndex in @thang.unusedTrackedPropertyKeys when @trackedPropertyKeys.indexOf(prop) is -1
|
||||
@thang[prop] = @thang.unusedTrackedPropertyValues[propIndex]
|
||||
props = []
|
||||
for prop, propIndex in @trackedPropertyKeys
|
||||
type = @trackedPropertyTypes[propIndex]
|
||||
storage = @trackedPropertyValues[propIndex]
|
||||
props.push(@thang[prop] = @getStoredProp propIndex, type, storage)
|
||||
props.push @thang[prop] = @getStoredProp propIndex, type, storage
|
||||
#console.log @frameIndex, @thang.id, prop, propIndex, type, storage, 'got', @thang[prop]
|
||||
@props = props
|
||||
@trackedPropertyTypes = @trackedPropertyValues = @specialKeysToValues = null # leave @trackedPropertyKeys for indexing
|
||||
@hasRestored = true
|
||||
else # Restoring later times
|
||||
for prop, propIndex in @thang.unusedTrackedPropertyKeys when @trackedPropertyKeys.indexOf(prop) is -1
|
||||
@thang[prop] = @thang.unusedTrackedPropertyValues[propIndex]
|
||||
for prop, propIndex in @trackedPropertyKeys
|
||||
@thang[prop] = @props[propIndex]
|
||||
@thang.partialState = false
|
||||
@
|
||||
|
||||
restorePartial: (ratio) ->
|
||||
# Don't think we need to worry about unusedTrackedPropertyValues here.
|
||||
# If it's not tracked yet, it'll very rarely partially change between frames; we can afford to miss the first one.
|
||||
inverse = 1 - ratio
|
||||
for prop, propIndex in @trackedPropertyKeys when prop is 'pos' or prop is 'rotation'
|
||||
if @hasRestored
|
||||
|
|
|
@ -11,7 +11,7 @@ WorldScriptNote = require './world_script_note'
|
|||
Component = require 'lib/world/component'
|
||||
System = require 'lib/world/system'
|
||||
PROGRESS_UPDATE_INTERVAL = 100
|
||||
DESERIALIZATION_INTERVAL = 20
|
||||
DESERIALIZATION_INTERVAL = 10
|
||||
ITEM_ORIGINAL = '53e12043b82921000051cdf9'
|
||||
|
||||
module.exports = class World
|
||||
|
@ -291,7 +291,7 @@ module.exports = class World
|
|||
# Code hotspot; optimize it
|
||||
startFrame = @framesSerializedSoFar
|
||||
endFrame = @frames.length
|
||||
console.log "... world serializing frames from", startFrame, "to", endFrame
|
||||
#console.log "... world serializing frames from", startFrame, "to", endFrame
|
||||
[transferableObjects, nontransferableObjects] = [0, 0]
|
||||
o = {totalFrames: @totalFrames, maxTotalFrames: @maxTotalFrames, frameRate: @frameRate, dt: @dt, victory: @victory, userCodeMap: {}, trackedProperties: {}}
|
||||
o.trackedProperties[prop] = @[prop] for prop in @trackedProperties or []
|
||||
|
@ -307,20 +307,21 @@ module.exports = class World
|
|||
o.trackedPropertiesPerThangKeys = []
|
||||
o.trackedPropertiesPerThangTypes = []
|
||||
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 # Actually, as of January 2014, it should be fixed.
|
||||
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. So we could try to undo the workaround.
|
||||
transferableStorageBytesNeeded = 0
|
||||
nFrames = endFrame - startFrame
|
||||
streaming = nFrames < @totalFrames
|
||||
for thang in @thangs
|
||||
# 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).
|
||||
continue if thang.stateless and not _.some(thang.trackedPropertiesUsed, Boolean) and not streaming
|
||||
# Check both, since sometimes people mark stateless Thangs but then 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).
|
||||
# If streaming the world, a thang marked stateless that actually change will get messed up. I think.
|
||||
continue if thang.stateless and not _.some(thang.trackedPropertiesUsed, Boolean)
|
||||
o.trackedPropertiesThangIDs.push thang.id
|
||||
trackedPropertiesIndices = []
|
||||
trackedPropertiesKeys = []
|
||||
trackedPropertiesTypes = []
|
||||
for used, propIndex in thang.trackedPropertiesUsed
|
||||
continue unless used# or streaming
|
||||
continue unless used
|
||||
trackedPropertiesIndices.push propIndex
|
||||
trackedPropertiesKeys.push thang.trackedPropertiesKeys[propIndex]
|
||||
trackedPropertiesTypes.push thang.trackedPropertiesTypes[propIndex]
|
||||
|
@ -418,6 +419,7 @@ module.exports = class World
|
|||
perf.t3 = now()
|
||||
|
||||
perf.batches = 0
|
||||
perf.framesCPUTime = 0
|
||||
w.frames = [] unless streamingWorld
|
||||
clearTimeout @deserializationTimeout if @deserializationTimeout
|
||||
@deserializationTimeout = _.delay @deserializeSomeFrames, 1, o, w, finishedWorldCallback, perf, startFrame, endFrame
|
||||
|
@ -428,26 +430,27 @@ module.exports = class World
|
|||
startTime = now()
|
||||
for frameIndex in [w.frames.length ... endFrame]
|
||||
w.frames.push WorldFrame.deserialize(w, frameIndex - startFrame, o.trackedPropertiesThangIDs, o.trackedPropertiesThangs, o.trackedPropertiesPerThangKeys, o.trackedPropertiesPerThangTypes, o.trackedPropertiesPerThangValues, o.specialKeysToValues, o.frameHashes[frameIndex - startFrame], w.dt * frameIndex)
|
||||
if (now() - startTime) > DESERIALIZATION_INTERVAL
|
||||
console.log " Deserialization not finished, let's do it again soon. Have:", w.frames.length, ", wanted from", startFrame, "to", endFrame
|
||||
elapsed = now() - startTime
|
||||
if elapsed > DESERIALIZATION_INTERVAL and frameIndex < endFrame - 1
|
||||
#console.log " Deserialization not finished, let's do it again soon. Have:", w.frames.length, ", wanted from", startFrame, "to", endFrame
|
||||
perf.framesCPUTime += elapsed
|
||||
@deserializationTimeout = _.delay @deserializeSomeFrames, 1, o, w, finishedWorldCallback, perf, startFrame, endFrame
|
||||
return
|
||||
@deserializationTimeout = null
|
||||
perf.framesCPUTime += elapsed
|
||||
@finishDeserializing w, finishedWorldCallback, perf, startFrame, endFrame
|
||||
|
||||
@finishDeserializing: (w, finishedWorldCallback, perf, startFrame, endFrame) ->
|
||||
perf.t4 = now()
|
||||
nFrames = endFrame - startFrame
|
||||
w.ended = true
|
||||
w.getFrame(endFrame - 1).restoreState()
|
||||
perf.t5 = now()
|
||||
console.log 'Deserialization:', (perf.t5 - perf.t0).toFixed(0) + 'ms (' + ((perf.t5 - perf.t0) / nFrames).toFixed(3) + 'ms per frame).', perf.batches, 'batches.'
|
||||
nFrames = endFrame - startFrame
|
||||
totalCPUTime = perf.t3 - perf.t0 + perf.framesCPUTime
|
||||
console.log 'Deserialization:', totalCPUTime.toFixed(0) + 'ms (' + (totalCPUTime / nFrames).toFixed(3) + 'ms per frame).', perf.batches, 'batches. Did', startFrame, 'to', endFrame, 'in', (perf.t4 - perf.t0).toFixed(0) + 'ms wall clock time.'
|
||||
if false
|
||||
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--reallocating memory:', (perf.t3 - perf.t2).toFixed(2) + 'ms'
|
||||
console.log ' Deserializing--WorldFrames:', (perf.t4 - perf.t3).toFixed(2) + 'ms'
|
||||
console.log ' Deserializing--restoring last WorldFrame:', (perf.t5 - perf.t4).toFixed(2) + 'ms'
|
||||
console.log ' Deserializing--WorldFrames:', (perf.t4 - perf.t3).toFixed(2) + 'ms wall clock time,', (perf.framesCPUTime).toFixed(2) + 'ms CPU time'
|
||||
finishedWorldCallback w
|
||||
|
||||
findFirstChangedFrame: (oldWorld) ->
|
||||
|
@ -456,10 +459,11 @@ module.exports = class World
|
|||
oldFrame = oldWorld.frames[i]
|
||||
break unless oldFrame and ((newFrame.hash is oldFrame.hash) or not newFrame.hash? or not oldFrame.hash?) # undefined gets in there when streaming at the last frame of each batch for some reason
|
||||
firstChangedFrame = i
|
||||
if @frames[i]
|
||||
console.log 'First changed frame is', firstChangedFrame, 'with hash', @frames[i].hash, 'compared to', oldWorld.frames[i]?.hash
|
||||
else
|
||||
console.log 'No frames were changed out of all', @frames.length
|
||||
if @frames.length is @totalFrames
|
||||
if @frames[i]
|
||||
console.log 'First changed frame is', firstChangedFrame, 'with hash', @frames[i].hash, 'compared to', oldWorld.frames[i]?.hash
|
||||
else
|
||||
console.log 'No frames were changed out of all', @frames.length
|
||||
firstChangedFrame
|
||||
|
||||
pointsForThang: (thangID, frameStart=0, frameEnd=null, camera=null, resolution=4) ->
|
||||
|
|
Loading…
Reference in a new issue