Many improvements to real-time streaming and flags.

This commit is contained in:
Nick Winter 2014-08-23 22:24:00 -07:00
parent 2dca4d72fc
commit be07f9cfb9
5 changed files with 54 additions and 35 deletions

View file

@ -460,7 +460,7 @@ self.finalizePreload = function finalizePreload() {
};
self.addFlagEvent = function addFlagEvent(flagEvent) {
if(!self.world || self.world.framesSerializedSoFar == self.world.frames.length) return;
if(!self.world) return;
self.world.addFlagEvent(flagEvent);
};

View file

@ -69,6 +69,7 @@ module.exports = Surface = class Surface extends CocoClass
'camera:zoom-updated': 'onZoomUpdated'
'playback:real-time-playback-started': 'onRealTimePlaybackStarted'
'playback:real-time-playback-ended': 'onRealTimePlaybackEnded'
#'god:world-load-progress-changed': -> console.log 'it is actually', @world.age
shortcuts:
'ctrl+\\, ⌘+\\': 'onToggleDebug'
@ -123,7 +124,7 @@ module.exports = Surface = class Surface extends CocoClass
@showLevel()
@updateState true if @loaded
# TODO: synchronize both ways of choosing whether to show coords (@world via UI System or @options via World Select modal)
if @world.showCoordinates and @options.coords
if @world.showCoordinates and @options.coords and not @coordinateDisplay
@coordinateDisplay = new CoordinateDisplay camera: @camera
@surfaceTextLayer.addChild @coordinateDisplay
@onFrameChanged()
@ -369,10 +370,19 @@ module.exports = Surface = class Surface extends CocoClass
@setWorld event.world
@onFrameChanged(true)
fastForwardBuffer = 2 # Make sure that real-time playback doesn't need to buffer more than this many seconds.
if @playing and (ffToFrame = Math.min(event.firstChangedFrame, @frameBeforeCast, event.world.frames.length)) and ffToFrame > @currentFrame + fastForwardBuffer * @world.frameRate
fastForwardBuffer = 2
if @playing and not @realTime and (ffToFrame = Math.min(event.firstChangedFrame, @frameBeforeCast, @world.frames.length)) and ffToFrame > @currentFrame + fastForwardBuffer * @world.frameRate
@fastForwardingToFrame = ffToFrame
@fastForwardingSpeed = Math.max 4, 4 * 90 / (@world.maxTotalFrames * @world.dt)
else if @realTime
lag = (@world.frames.length - 1) * @world.dt - @world.age
intendedLag = @world.realTimeBufferMax + @world.dt
if lag > intendedLag * 1.2
@fastForwardingToFrame = @world.frames.length - @world.realTimeBufferMax * @world.frameRate
@fastForwardingSpeed = lag / intendedLag
else
@fastForwardingToFrame = @fastForwardingSpeed = null
#console.log "on new world, lag", lag, "intended lag", intendedLag, "fastForwardingToFrame", @fastForwardingToFrame, "speed", @fastForwardingSpeed, "cause we are at", @world.age, "of", @world.frames.length * @world.dt
# initialization

View file

@ -13,7 +13,8 @@ System = require 'lib/world/system'
PROGRESS_UPDATE_INTERVAL = 100
DESERIALIZATION_INTERVAL = 10
REAL_TIME_BUFFER_MIN = 2 * PROGRESS_UPDATE_INTERVAL
REAL_TIME_BUFFER_MAX = 5 * PROGRESS_UPDATE_INTERVAL
REAL_TIME_BUFFER_MAX = 3 * PROGRESS_UPDATE_INTERVAL
REAL_TIME_BUFFERED_WAIT_INTERVAL = 0.5 * PROGRESS_UPDATE_INTERVAL
ITEM_ORIGINAL = '53e12043b82921000051cdf9'
module.exports = class World
@ -25,6 +26,7 @@ module.exports = class World
headless: false # Whether we are just simulating for goal states instead of all serialized results
framesSerializedSoFar: 0
apiProperties: ['age', 'dt']
realTimeBufferMax: REAL_TIME_BUFFER_MAX / 1000
constructor: (@userCodeMap, classMap) ->
# classMap is needed for deserializing Worlds, Thangs, and other classes
@classMap = classMap ? {Vector: Vector, Rectangle: Rectangle, Thang: Thang, Ellipse: Ellipse, LineSegment: LineSegment}
@ -100,6 +102,35 @@ module.exports = class World
frameToLoadUntil = @totalFrames
i = @frames.length
while i < frameToLoadUntil and i < @totalFrames
t2 = now()
if @realTime
shouldUpdateProgress = @shouldUpdateRealTimePlayback t2
shouldDelayRealTimeSimulation = not shouldUpdateProgress and @shouldDelayRealTimeSimulation t2
else
shouldUpdateProgress = t2 - t1 > PROGRESS_UPDATE_INTERVAL
shouldDelayRealTimeSimulation = false
if shouldUpdateProgress or shouldDelayRealTimeSimulation
if shouldUpdateProgress
@lastRealTimeUpdate = i * @dt if @realTime
#console.log 'we think it is now', (t2 - @worldLoadStartTime) / 1000, 'so delivering', @lastRealTimeUpdate
loadProgressCallback? i / @totalFrames unless @preloading
t1 = t2
if t2 - @t0 > 1000
console.log ' Loaded', i, 'of', @totalFrames, '(+' + (t2 - @t0).toFixed(0) + 'ms)' unless @realTime
@t0 = t2
continueFn = =>
return if @destroyed
if loadUntilFrame
@loadFrames(loadedCallback,errorCallback,loadProgressCallback, skipDeferredLoading, loadUntilFrame)
else
@loadFrames(loadedCallback, errorCallback, loadProgressCallback, skipDeferredLoading)
if skipDeferredLoading
continueFn()
else
delay = if shouldDelayRealTimeSimulation then REAL_TIME_BUFFERED_WAIT_INTERVAL else 0
setTimeout(continueFn, delay)
return
if @debugging
for thang in @thangs when thang.isProgrammable
userCode = @userCodeMap[thang.id] ? {}
@ -116,33 +147,7 @@ module.exports = class World
for error in (@unhandledRuntimeErrors ? [])
return unless errorCallback error # errorCallback tells us whether the error is recoverable
@unhandledRuntimeErrors = []
t2 = now()
if @realTime
shouldUpdateProgress = @shouldUpdateRealTimePlayback t2
shouldDelayRealTimeSimulation = not shouldUpdateProgress and @shouldDelayRealTimeSimulation t2
else
shouldUpdateProgress = t2 - t1 > PROGRESS_UPDATE_INTERVAL
shouldDelayRealTimeSimulation = false
if shouldUpdateProgress or shouldDelayRealTimeSimulation
if shouldUpdateProgress
@lastRealTimeUpdate = i * @dt if @realTime
loadProgressCallback? i / @totalFrames unless @preloading
t1 = t2
if t2 - @t0 > 1000
console.log ' Loaded', i, 'of', @totalFrames, '(+' + (t2 - @t0).toFixed(0) + 'ms)'
@t0 = t2
continueFn = =>
return if @destroyed
if loadUntilFrame
@loadFrames(loadedCallback,errorCallback,loadProgressCallback, skipDeferredLoading, loadUntilFrame)
else
@loadFrames(loadedCallback, errorCallback, loadProgressCallback, skipDeferredLoading)
if skipDeferredLoading
continueFn()
else
delay = if shouldDelayRealTimeSimulation then PROGRESS_UPDATE_INTERVAL else 0
setTimeout(continueFn, delay)
return
unless @debugging
@ended = true
system.finish @thangs for system in @systems
@ -159,6 +164,8 @@ module.exports = class World
shouldUpdateRealTimePlayback: (t) ->
return false unless @realTime
return false if @frames.length * @dt is @lastRealTimeUpdate
timeLoaded = @frames.length * @dt * 1000
timeSinceStart = t - @worldLoadStartTime
remainingBuffer = @lastRealTimeUpdate * 1000 - timeSinceStart
remainingBuffer < REAL_TIME_BUFFER_MIN
@ -475,7 +482,7 @@ module.exports = class World
w.ended = true
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.'
#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'

View file

@ -48,7 +48,7 @@ module.exports = class LevelFlagsView extends CocoView
onStageMouseDown: (e) ->
return unless @flagColor and @realTime
pos = x: e.worldPos.x, y: e.worldPos.y
flag = player: me.id, team: me.team, color: @flagColor, pos: pos, time: @world.dt * @world.frames.length + 1, active: true
flag = player: me.id, team: me.team, color: @flagColor, pos: pos, time: @world.dt * @world.frames.length, active: true
@flags[@flagColor] = flag
@flagHistory.push flag
Backbone.Mediator.publish 'level:flag-updated', flag
@ -57,7 +57,7 @@ module.exports = class LevelFlagsView extends CocoView
removeFlag: (e) ->
delete @flags[e.color]
console.log e.color, 'deleted'
flag = player: me.id, team: me.team, color: e.color, time: @world.dt * @world.frames.length + 1, active: false
flag = player: me.id, team: me.team, color: e.color, time: @world.dt * @world.frames.length, active: false
@flagHistory.push flag
Backbone.Mediator.publish 'level:flag-updated', flag

View file

@ -152,6 +152,7 @@ module.exports = class LevelPlaybackView extends CocoView
@$el.find('#music-button').toggleClass('music-on', me.get('music'))
onSetLetterbox: (e) ->
return if @realTime
@togglePlaybackControls !e.on
@disabled = e.on
@ -211,6 +212,7 @@ module.exports = class LevelPlaybackView extends CocoView
$('#volume-button', @$el).removeClass('disabled')
onEnableControls: (e) ->
return if @realTime
if not e.controls or 'playback' in e.controls
@disabled = false
$('button', @$el).removeClass('disabled')