Several performance improvements based on profiling.

This commit is contained in:
Nick Winter 2014-04-28 16:31:51 -07:00
parent e5e146c3c1
commit bd3c059c2a
7 changed files with 65 additions and 45 deletions

View file

@ -35,12 +35,13 @@ module.exports = class Layer extends createjs.Container
@transformStyle = options.transform ? Layer.TRANSFORM_CHILD
@camera = options.camera
console.error @toString(), "needs a camera." unless @camera
@updateLayerOrder = _.throttle @updateLayerOrder, 1 # don't call multiple times in one frame
@updateLayerOrder = _.throttle @updateLayerOrder, 1000 / 30 # Don't call multiple times in one frame; 30 FPS is probably good enough
Backbone.Mediator.subscribe(channel, @[func], @) for channel, func of @subscriptions
destroy: ->
child.destroy?() for child in @children
Backbone.Mediator.unsubscribe(channel, @[func], @) for channel, func of @subscriptions
delete @updateLayerOrder
toString: -> "<Layer #{@layerPriority}: #{@name}>"
@ -60,21 +61,30 @@ module.exports = class Layer extends createjs.Container
updateLayerOrder: =>
#console.log @, @toString(), "sorting children", _.clone @children if @name is 'Default'
@sortChildren (a, b) ->
alp = a.layerPriority ? 0
blp = b.layerPriority ? 0
return alp - blp if alp isnt blp
# TODO: remove this z stuff
az = if a.z then a.z else 1000
bz = if b.z then b.z else 1000
aThang = a.sprite?.thang
bThang = b.sprite?.thang
az -= 1 if aThang?.health < 0
bz -= 1 if bThang?.health < 0
if az == bz
return 0 unless aThang?.pos and bThang?.pos
return (bThang.pos.y - aThang.pos.y) or (bThang.pos.x - aThang.pos.x)
return az - bz
@sortChildren @layerOrderComparator
layerOrderComparator: (a, b) ->
# Optimize
alp = a.layerPriority or 0
blp = b.layerPriority or 0
return alp - blp if alp isnt blp
# TODO: remove this z stuff
az = a.z or 1000
bz = b.z or 1000
if aSprite = a.sprite
if aThang = aSprite.thang
aPos = aThang.pos
if aThang.health < 0
--az
if bSprite = b.sprite
if bThang = bSprite.thang
bPos = bThang.pos
if bThang.health < 0
--bz
if az is bz
return 0 unless aPos and bPos
return (bPos.y - aPos.y) or (bPos.x - aPos.x)
return az - bz
onZoomUpdated: (e) ->
return unless e.camera is @camera

View file

@ -34,17 +34,18 @@ module.exports = class SpriteBoss extends CocoClass
@world = options.world
@options.thangTypes ?= []
@sprites = {}
@spriteArray = [] # Mirror @sprites, but faster for when we just need to iterate
@selfWizardSprite = null
@createLayers()
@spriteSheetCache = {}
destroy: ->
@removeSprite sprite for thangID, sprite of @sprites
@removeSprite sprite for sprite in @spriteArray
@targetMark?.destroy()
@selectionMark?.destroy()
super()
toString: -> "<SpriteBoss: #{@sprites.length} sprites>"
toString: -> "<SpriteBoss: #{@spriteArray.length} sprites>"
thangTypeFor: (type) ->
_.find @options.thangTypes, (m) -> m.get('original') is type or m.get('name') is type
@ -77,6 +78,7 @@ module.exports = class SpriteBoss extends CocoClass
id ?= sprite.thang.id
console.error "Sprite collision! Already have:", id if @sprites[id]
@sprites[id] = sprite
@spriteArray.push sprite
layer ?= @spriteLayers["Obstacle"] if sprite.thang?.spriteName.search(/(dungeon|indoor).wall/i) isnt -1
layer ?= @layerForChild sprite.displayObject, sprite
layer.addChild sprite.displayObject
@ -117,7 +119,6 @@ module.exports = class SpriteBoss extends CocoClass
else
sprite.targetPos = if opponent.team is 'ogres' then {x:52, y: 28} else {x: 20, y:28}
createWizardSprite: (options) ->
sprite = new WizardSprite @thangTypeFor("Wizard"), @createSpriteOptions(options)
@addSprite sprite, sprite.thang.id, @spriteLayers["Floating"]
@ -138,7 +139,7 @@ module.exports = class SpriteBoss extends CocoClass
onSetDebug: (e) ->
return if e.debug is @debug
@debug = e.debug
sprite.setDebug @debug for thangID, sprite of @sprites
sprite.setDebug @debug for sprite in @spriteArray
onHighlightSprites: (e) ->
highlightedIDs = e.thangIDs or []
@ -159,15 +160,16 @@ module.exports = class SpriteBoss extends CocoClass
sprite.displayObject.parent.removeChild sprite.displayObject
thang = sprite.thang
delete @sprites[sprite.thang.id]
@spriteArray.splice @spriteArray.indexOf(sprite), 1
sprite.destroy()
sprite.thang = thang # Keep around so that we know which thang the destroyed thang was for
updateSounds: ->
sprite.playSounds() for thangID, sprite of @sprites # hmm; doesn't work for sprites which we didn't add yet in adjustSpriteExistence
sprite.playSounds() for sprite in @spriteArray # hmm; doesn't work for sprites which we didn't add yet in adjustSpriteExistence
update: (frameChanged) ->
@adjustSpriteExistence() if frameChanged
sprite.update frameChanged for thangID, sprite of @sprites
sprite.update frameChanged for sprite in @spriteArray
@updateSelection()
@spriteLayers["Default"].updateLayerOrder()
@cache()
@ -197,7 +199,7 @@ module.exports = class SpriteBoss extends CocoClass
cache: (update=false) ->
return if @cached and not update
wallSprites = (sprite for thangID, sprite of @sprites when sprite.thangType?.get('name').search(/(dungeon|indoor).wall/i) isnt -1)
wallSprites = (sprite for sprite in @spriteArray when sprite.thangType?.get('name').search(/(dungeon|indoor).wall/i) isnt -1)
walls = (sprite.thang for sprite in wallSprites)
@world.calculateBounds()
wallGrid = new Grid walls, @world.size()...
@ -222,12 +224,12 @@ module.exports = class SpriteBoss extends CocoClass
onCastSpells: -> @stop()
play: ->
sprite.imageObject.play() for thangID, sprite of @sprites
sprite.imageObject.play() for sprite in @spriteArray
@selectionMark?.play()
@targetMark?.play()
stop: ->
sprite.imageObject.stop() for thangID, sprite of @sprites
sprite.imageObject.stop() for sprite in @spriteArray
@selectionMark?.stop()
@targetMark?.stop()

View file

@ -221,8 +221,8 @@ module.exports = Surface = class Surface extends CocoClass
@currentFrame = tempFrame
frame = @world.getFrame(@getCurrentFrame())
frame.restoreState()
for thangID, sprite of @spriteBoss.sprites
sprite.playSounds false, Math.max(0.05, Math.min(1, 1 / @scrubbingPlaybackSpeed))
volume = Math.max(0.05, Math.min(1, 1 / @scrubbingPlaybackSpeed))
sprite.playSounds false, volume for sprite in @spriteBoss.spriteArray
tempFrame += if rising then 1 else -1
@currentFrame = actualCurrentFrame

View file

@ -8,14 +8,14 @@ module.exports = class SuperModel extends Backbone.Model
@models = {}
@collections = {}
# Since the supermodel has undergone some changes into being a loader and a cache interface,
# it's a bit wonky to use. The next couple functions are meant to cover the majority of
# use cases across the site. If they are used, the view will automatically handle errors,
# retries, progress, and filling the cache. Note that the resource it passes back will not
# necessarily have the same model or collection that was passed in, if it was fetched from
# the cache.
loadModel: (model, name, fetchOptions, value=1) ->
cachedModel = @getModelByURL(model.getURL())
if cachedModel
@ -28,12 +28,12 @@ module.exports = class SuperModel extends Backbone.Model
res = @addModelResource(cachedModel, name, fetchOptions, value)
res.markLoading()
return res
else
@registerModel(model)
console.debug 'Registering model', model.getURL()
return @addModelResource(model, name, fetchOptions, value).load()
loadCollection: (collection, name, fetchOptions, value=1) ->
url = collection.getURL()
if cachedCollection = @collections[url]
@ -46,7 +46,7 @@ module.exports = class SuperModel extends Backbone.Model
res = @addModelResource(cachedCollection, name, fetchOptions, value)
res.markLoading()
return res
else
@addCollection collection
@listenToOnce collection, 'sync', (c) ->
@ -56,7 +56,7 @@ module.exports = class SuperModel extends Backbone.Model
# replace or overwrite
shouldSaveBackups: (model) -> false
# Caching logic
getModel: (ModelClass_or_url, id) ->
@ -102,7 +102,7 @@ module.exports = class SuperModel extends Backbone.Model
else
@registerModel(model)
collection
# Tracking resources being loaded for this supermodel
finished: ->
@ -148,7 +148,7 @@ module.exports = class SuperModel extends Backbone.Model
@trigger('failed', source)
updateProgress: =>
# Because this is _.defer'd, this might end up getting called after
# Because this is _.defer'd, this might end up getting called after
# a bunch of things load all at once.
# So make sure we only emit events if @progress has changed.
newProg = if @denom then @num / @denom else 1
@ -161,8 +161,8 @@ module.exports = class SuperModel extends Backbone.Model
getResource: (rid) ->
return @resources[rid]
class Resource extends Backbone.Model
constructor: (name, value=1) ->
@ -185,11 +185,11 @@ class Resource extends Backbone.Model
@trigger('failed', {resource: @})
@isLoaded = @isLoading = false
@isFailed = true
markLoading: ->
@isLoaded = @isFailed = false
@isLoading = true
load: -> @

View file

@ -306,10 +306,11 @@ module.exports = class HUDView extends View
for actionName, action of @thang.actions
@updateActionElement(actionName, @timespans[actionName], @thang.action is actionName)
tableContainer = @$el.find('.table-container')
timelineWidth = tableContainer.find('tr:not(.secret) .action-timeline').width()
right = (1 - (@timeProgress ? 0)) * timelineWidth
arrow = tableContainer.find('.progress-arrow')
arrow.css 'right', right - arrow.width() / 2
@timelineWidth ||= tableContainer.find('tr:not(.secret) .action-timeline').width()
@actionArrowWidth ||= arrow.width()
right = (1 - (@timeProgress ? 0)) * @timelineWidth
arrow.css 'right', right - @actionArrowWidth / 2
tableContainer.find('.progress-line').css 'right', right
buildActionTimespans: ->

View file

@ -238,7 +238,7 @@ module.exports = class PlaybackView extends View
@currentTime = e.frame / e.world.frameRate
# Game will sometimes stop at 29.97, but with only one digit, this is unnecesary.
# @currentTime = @totalTime if Math.abs(@totalTime - @currentTime) < 0.04
@updatePopupContent()
@updatePopupContent() if @timePopup?.shown
@updateProgress(e.progress)
@updatePlayButton(e.progress)

View file

@ -156,9 +156,16 @@ module.exports = class ThangListEntryView extends View
@$el.toggleClass('disabled', not enabled)
onFrameChanged: (e) ->
# Optimize
return unless currentThang = e.world.thangMap[@thang.id]
@$el.toggle Boolean(currentThang.exists)
@$el.toggleClass 'dead', currentThang.health <= 0 if currentThang.exists
exists = Boolean currentThang.exists
if @thangDidExist isnt exists
@$el.toggle exists
@thangDidExist = exists
dead = exists and currentThang.health <= 0
if @thangWasDead isnt dead
@$el.toggleClass 'dead', dead
@thangWasDead = dead
destroy: ->
@avatar?.destroy()