A ton of misc bug fixes and performance improvements, getting ready to merge world streaming and flags back in.

This commit is contained in:
Nick Winter 2014-08-24 21:39:34 -07:00
parent 1a7e4554f0
commit 718d586f07
21 changed files with 110 additions and 79 deletions

View file

@ -27,8 +27,7 @@ init = ->
# Set up Backbone.Mediator schemas
setUpDefinitions()
setUpChannels()
#Backbone.Mediator.setValidationEnabled document.location.href.search(/codecombat.com/) is -1
Backbone.Mediator.setValidationEnabled false # STREAM: Should change back
Backbone.Mediator.setValidationEnabled document.location.href.search(/codecombat.com/) is -1
app.initialize()
Backbone.history.start({ pushState: true })
handleNormalUrls()

View file

@ -91,7 +91,7 @@ module.exports = class God extends CocoClass
userCodeMap = {}
for spellKey, spell of spells
for thangID, spellThang of spell.thangs
continue if spellThang.thang.programmableMethods[spell.name].cloneOf
continue if spellThang.thang?.programmableMethods[spell.name].cloneOf
(userCodeMap[thangID] ?= {})[spell.name] = spellThang.aether.serialize()
userCodeMap

View file

@ -184,7 +184,7 @@ module.exports = class Simulator extends CocoClass
try
@commenceSimulationAndSetupCallback()
catch err
console.error 'There was an error in simulation:', err, "-- trying again in #{@retryDelayInSeconds} seconds"
console.error 'There was an error in simulation:', err, err.stack, "-- trying again in #{@retryDelayInSeconds} seconds"
@simulateAnotherTaskAfterDelay()
assignWorldAndLevelFromLevelLoaderAndDestroyIt: ->

View file

@ -59,7 +59,8 @@ module.exports = class WizardSprite extends IndieSprite
toggle: (to) ->
@imageObject?.visible = to
@labels.name?[if to then 'show' else 'hide']()
label[if to then 'show' else 'hide']() for name, label of @labels
mark.mark?.visible = to for name, mark of @marks
onPlayerStatesChanged: (e) ->
for playerID, state of e.states

View file

@ -47,7 +47,6 @@ module.exports = class ThangState
value = @specialKeysToValues[specialKey]
else if type is 'Thang'
specialKey = storage[@frameIndex]
console.error "couldn't find world from thang", @thang, "for", @specialKeysToValues[specialKey] unless @thang.world
value = @thang.world.getThangByID @specialKeysToValues[specialKey]
else if type is 'array'
specialKey = storage[@frameIndex]
@ -174,7 +173,6 @@ module.exports = class ThangState
# Optimize like no tomorrow--most performance-sensitive part of the whole app, called once per WorldFrame per Thang per trackedProperty, blocking the UI
ts = new ThangState
ts.thang = thang
console.error "couldn't find thang!", @ unless thang
ts.frameIndex = frameIndex
ts.trackedPropertyKeys = trackedPropertyKeys
ts.trackedPropertyTypes = trackedPropertyTypes

View file

@ -71,9 +71,11 @@ module.exports = class World
@thangs[i] = thang
@thangMap[thang.id] = thang
thangDialogueSounds: ->
thangDialogueSounds: (startFrame=0) ->
return [] unless startFrame < @frames.length
[sounds, seen] = [[], {}]
for frame in @frames
for frameIndex in [startFrame ... @frames.length]
frame = @frames[frameIndex]
for thangID, state of frame.thangStateMap
continue unless state.thang.say and sayMessage = state.getStateForProp 'sayMessage'
soundKey = state.thang.spriteName + ':' + sayMessage
@ -90,64 +92,30 @@ module.exports = class World
loadFrames: (loadedCallback, errorCallback, loadProgressCallback, skipDeferredLoading, loadUntilFrame) ->
return if @aborted
unless @thangs.length
console.log 'Warning: loadFrames called on empty World (no thangs).'
console.log 'Warning: loadFrames called on empty World (no thangs).' unless @thangs.length
t1 = now()
@t0 ?= t1
@worldLoadStartTime ?= t1
@lastRealTimeUpdate ?= 0
if loadUntilFrame
frameToLoadUntil = loadUntilFrame + 1
else
frameToLoadUntil = @totalFrames
continueLaterFn = =>
@loadFrames(loadedCallback, errorCallback, loadProgressCallback, skipDeferredLoading, loadUntilFrame) unless @destroyed
frameToLoadUntil = if loadUntilFrame then loadUntilFrame + 1 else @totalFrames # Might stop early if debugging.
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] ? {}
for methodName, aether of userCode
framesToLoadFlowBefore = if methodName is 'plan' or methodName is 'makeBid' then 200 else 1 # Adjust if plan() is taking even longer
aether._shouldSkipFlow = i < loadUntilFrame - framesToLoadFlowBefore
return unless @shouldContinueLoading t1, loadProgressCallback, skipDeferredLoading, continueLaterFn
@adjustFlowSettings loadUntilFrame if @debugging
try
@getFrame(i)
++i # increment this after we have succeeded in getting the frame, otherwise we'll have to do that frame again
++i # Increment this after we have succeeded in getting the frame, otherwise we'll have to do that frame again
catch error
# Not an Aether.errors.UserCodeError; maybe we can't recover
@addError error
@addError error # Not an Aether.errors.UserCodeError; maybe we can't recover
unless @preloading or @debugging
for error in (@unhandledRuntimeErrors ? [])
return unless errorCallback error # errorCallback tells us whether the error is recoverable
@unhandledRuntimeErrors = []
@finishLoadingFrames loadProgressCallback, loadedCallback
finishLoadingFrames: (loadProgressCallback, loadedCallback) ->
unless @debugging
@ended = true
system.finish @thangs for system in @systems
@ -170,6 +138,38 @@ module.exports = class World
remainingBuffer = @lastRealTimeUpdate * 1000 - timeSinceStart
remainingBuffer < REAL_TIME_BUFFER_MIN
shouldContinueLoading: (t1, loadProgressCallback, skipDeferredLoading, continueLaterFn) ->
t2 = now()
if @realTime
shouldUpdateProgress = @shouldUpdateRealTimePlayback t2
shouldDelayRealTimeSimulation = not shouldUpdateProgress and @shouldDelayRealTimeSimulation t2
else
shouldUpdateProgress = t2 - t1 > PROGRESS_UPDATE_INTERVAL
shouldDelayRealTimeSimulation = false
return true unless shouldUpdateProgress or shouldDelayRealTimeSimulation
# Stop loading frames for now; continue in a moment.
if shouldUpdateProgress
@lastRealTimeUpdate = @frames.length * @dt if @realTime
#console.log 'we think it is now', (t2 - @worldLoadStartTime) / 1000, 'so delivering', @lastRealTimeUpdate
loadProgressCallback? @frames.length / @totalFrames unless @preloading
t1 = t2
if t2 - @t0 > 1000
console.log ' Loaded', @frames.length, 'of', @totalFrames, '(+' + (t2 - @t0).toFixed(0) + 'ms)' unless @realTime
@t0 = t2
if skipDeferredLoading
continueLaterFn()
else
delay = if shouldDelayRealTimeSimulation then REAL_TIME_BUFFERED_WAIT_INTERVAL else 0
setTimeout continueLaterFn, delay
false
adjustFlowSettings: (loadUntilFrame) ->
for thang in @thangs when thang.isProgrammable
userCode = @userCodeMap[thang.id] ? {}
for methodName, aether of userCode
framesToLoadFlowBefore = if methodName is 'plan' or methodName is 'makeBid' then 200 else 1 # Adjust if plan() is taking even longer
aether._shouldSkipFlow = @frames.length < loadUntilFrame - framesToLoadFlowBefore
finalizePreload: (loadedCallback) ->
@preloading = false
loadedCallback() if @ended

View file

@ -498,7 +498,9 @@
space: "Space"
enter: "Enter"
escape: "Escape"
shift: "Shift"
cast_spell: "Cast current spell."
run_real_time: "Run in real time."
continue_script: "Continue past current script."
skip_scripts: "Skip past all skippable scripts."
toggle_playback: "Toggle play/pause."

View file

@ -63,6 +63,7 @@ module.exports = class ThangType extends CocoModel
options = _.clone options
options.resolutionFactor ?= SPRITE_RESOLUTION_FACTOR
options.async ?= false
options.thang = null # Don't hold onto any bad Thang references.
options
buildSpriteSheet: (options) ->
@ -188,7 +189,7 @@ module.exports = class ThangType extends CocoModel
portrait = if portrait then '(Portrait)' else ''
name = _.string.rpad @get('name'), 20
time = _.string.lpad '' + new Date().getTime() - startTime, 6
#console.debug "Built sheet: #{name} #{time}ms #{kind} #{portrait}" # STREAM: uncomment
console.debug "Built sheet: #{name} #{time}ms #{kind} #{portrait}"
spriteSheetKey: (options) ->
colorConfigs = []

View file

@ -62,9 +62,10 @@ module.exports =
additionalProperties: false
properties:
color:
type: 'string'
enum: ['green', 'black', 'violet']
description: 'The flag color to place next, or omitted/null if deselected.'
oneOf: [
{type: 'null'}
{type: 'string', enum: ['green', 'black', 'violet'], description: 'The flag color to place next, or omitted/null if deselected.'}
]
pos:
type: 'object'
additionalProperties: false

View file

@ -11,10 +11,11 @@ module.exports =
type: "object"
preload:
type: "boolean"
realTime:
type: "boolean"
required: []
additionalProperties: false
# TODO do we really need both 'cast-spell' and 'cast-spells'?
"tome:cast-spells":
title: "Cast Spells"
$schema: "http://json-schema.org/draft-04/schema#"
@ -25,6 +26,8 @@ module.exports =
type: "object"
preload:
type: "boolean"
realTime:
type: "boolean"
required: []
additionalProperties: false
@ -33,7 +36,9 @@ module.exports =
$schema: "http://json-schema.org/draft-04/schema#"
description: "Published when you wish to manually recast all spells"
type: "object"
properties: {}
properties:
realTime:
type: "boolean"
required: []
additionalProperties: false

View file

@ -26,4 +26,4 @@
color: black
&.violet-flag
color: violet

View file

@ -8,6 +8,10 @@ block modal-body-content
dt(title="Shift+" + enter)
code ⇧+#{enter}
dd(data-i18n="keyboard_shortcuts.cast_spell") Cast current spell.
dl.dl-horizontal
dt(title=ctrlName + "+Shift+" + enter)
code #{ctrl}+⇧+#{enter}
dd(data-i18n="keyboard_shortcuts.run_real_time") Run in real time.
dl.dl-horizontal
dt(title="Shift+" + space)
code ⇧+#{space}

View file

@ -1,5 +1,5 @@
div.btn-group.btn-group-lg.cast-button-group
.button-progress-overlay
button.btn.btn-inverse.banner.cast-button(title=castShortcutVerbose + ": Cast current spell", data-i18n="play_level.tome_cast_button_cast") Spell Cast
button.btn.btn-inverse.banner.cast-real-time-button(title=castRealTimeShortcutVerbose + ": Cast in real time")
button.btn.btn-inverse.banner.cast-button(title=castVerbose, data-i18n="play_level.tome_cast_button_cast") Spell Cast
button.btn.btn-inverse.banner.cast-real-time-button(title=castRealTimeVerbose)
i.glyphicon.glyphicon-play

View file

@ -197,8 +197,9 @@ module.exports = class LadderTabView extends CocoView
formatCount = d3.format(',.0')
x = d3.scale.linear().domain([-3000, 6000]).range([0, width])
minX = Math.floor(Math.min(histogramData...) / 1000) * 1000
maxX = Math.ceil(Math.max(histogramData...) / 1000) * 1000
x = d3.scale.linear().domain([minX, maxX]).range([0, width])
data = d3.layout.histogram().bins(x.ticks(20))(histogramData)
y = d3.scale.linear().domain([0, d3.max(data, (d) -> d.y)]).range([height, 10])

View file

@ -37,12 +37,13 @@ module.exports = class LevelFlagsView extends CocoView
@flagHistory = []
onRealTimePlaybackEnded: (e) ->
@realTime = false
@onFlagSelected color: null
@realTime = false
@$el.hide()
onFlagSelected: (e) ->
return if @flagColor is e.color
return unless @realTime
return if @flagColor is e.color and e.source is 'shortcut'
color = if e.source is 'button' and e.color is @flagColor then null else e.color
@flagColor = color
Backbone.Mediator.publish 'level:flag-color-selected', color: color
@ -72,4 +73,4 @@ module.exports = class LevelFlagsView extends CocoView
onNewWorld: (event) ->
return unless event.world.name is @world.name
@world = event.world
@world = @options.world = event.world

View file

@ -22,7 +22,7 @@ module.exports = class LevelPlaybackView extends CocoView
'level-toggle-grid': 'onToggleGrid'
'surface:frame-changed': 'onFrameChanged'
'god:new-world-created': 'onNewWorld'
'god:streaming-world-updated': 'onNewWorld' # Maybe?
'god:streaming-world-updated': 'onNewWorld'
'level-set-letterbox': 'onSetLetterbox'
'tome:cast-spells': 'onTomeCast'
'playback:real-time-playback-ended': 'onRealTimePlaybackEnded'

View file

@ -512,7 +512,12 @@ module.exports = class PlayLevelView extends RootView
@world = e.world
@world.scripts = scripts
thangTypes = @supermodel.getModels(ThangType)
for [spriteName, message] in @world.thangDialogueSounds()
startFrame = @lastWorldFramesLoaded ? 0
if @world.frames.length is @world.totalFrames # Finished loading
@lastWorldFramesLoaded = 0
else
@lastWorldFramesLoaded = @world.frames.length
for [spriteName, message] in @world.thangDialogueSounds startFrame
continue unless thangType = _.find thangTypes, (m) -> m.get('name') is spriteName
continue unless sound = AudioPlayer.soundForDialogue message, thangType.get('soundTriggers')
AudioPlayer.preloadSoundReference sound

View file

@ -21,13 +21,15 @@ module.exports = class CastButtonView extends CocoView
@spells = options.spells
@levelID = options.levelID
@castShortcut = '⇧↵'
@castShortcutVerbose = 'Shift+Enter'
@castRealTimeShortcutVerbose = 'Ctrl+Shift+Enter'
getRenderData: (context={}) ->
context = super context
context.castShortcutVerbose = @castShortcutVerbose
context.castRealTimeShortcutVerbose = @castRealTimeShortcutVerbose
shift = $.i18n.t 'keyboard_shortcuts.shift'
enter = $.i18n.t 'keyboard_shortcuts.enter'
castShortcutVerbose = "#{shift}+#{enter}"
castRealTimeShortcutVerbose = (if @isMac() then 'Cmd' else 'Ctrl') + '+' + castShortcutVerbose
context.castVerbose = castShortcutVerbose + ': ' + $.i18n.t('keyboard_shortcuts.cast_spell')
context.castRealTimeVerbose = castRealTimeShortcutVerbose + ': ' + $.i18n.t('keyboard_shortcuts.run_real_time')
context
afterRender: ->
@ -101,6 +103,6 @@ module.exports = class CastButtonView extends CocoView
@autocastDelay = delay = parseInt delay
me.set('autocastDelay', delay)
me.patch()
spell.view.setAutocastDelay delay for spellKey, spell of @spells
spell.view?.setAutocastDelay delay for spellKey, spell of @spells
@castOptions.find('a').each ->
$(@).toggleClass('selected', parseInt($(@).attr('data-delay')) is delay)

View file

@ -40,10 +40,13 @@ module.exports = class Spell
if @permissions.readwrite.length and sessionSource = @session.getSourceFor(@spellKey)
@source = sessionSource
@thangs = {}
@view = new SpellView {spell: @, session: @session, worker: @worker}
@view.render() # Get it ready and code loaded in advance
@tabView = new SpellListTabEntryView spell: @, supermodel: @supermodel, language: @language
@tabView.render()
if true # @canRead() # Surely we could avoid creating these if we'll never use them? TODO
@view = new SpellView {spell: @, session: @session, worker: @worker}
@view.render() # Get it ready and code loaded in advance
@tabView = new SpellListTabEntryView spell: @, supermodel: @supermodel, language: @language
@tabView.render()
#else
# @spell.loaded = true
@team = @permissions.readwrite[0] ? 'common'
Backbone.Mediator.publish 'tome:spell-created', spell: @

View file

@ -517,7 +517,7 @@ module.exports = class SpellView extends CocoView
spellThang.castAether = aether
spellThang.aether = @spell.createAether thang
#console.log thangID, @spell.spellKey, 'ran', aether.metrics.callsExecuted, 'times over', aether.metrics.statementsExecuted, 'statements, with max recursion depth', aether.metrics.maxDepth, 'and full flow/metrics', aether.metrics, aether.flow
@spell.transpile()
@spell.transpile() # TODO: is there any way we can avoid doing this if it hasn't changed? Causes a slight hang.
@updateAether false, false
# --------------------------------------------------------------------------------------------------

View file

@ -29,6 +29,8 @@ module.exports = class ThangListView extends CocoView
false
), @sortScoreForThang
@muggleThangs = _.sortBy _.without(@thangs, @readwriteThangs..., @readThangs...), @sortScoreForThang
if @muggleThangs.length > 15
@muggleThangs = [] # Don't render a zillion of these. Slow, too long, maybe not useful.
sortScoreForThang: (t) =>
# Sort by my team, then most spells and fewest shared Thangs per spell,
@ -73,6 +75,12 @@ module.exports = class ThangListView extends CocoView
null
adjustThangs: (spells, thangs) ->
# TODO: it would be nice to not have to do this any more, like if we migrate to the hero levels.
# Recreating all the ThangListEntryViews and their ThangAvatarViews is pretty slow.
# So they aren't even kept up-to-date during world streaming.
# Updating the existing subviews? Would be kind of complicated to get all the new thangs and spells propagated.
# I would do it, if I didn't think we were perhaps soon to not do the ThangList any more.
# Will temporary reduce the number of muggle thangs we're willing to draw.
@spells = @options.spells = spells
for entry in @entries
entry.$el.remove()