Fixed a ton of memory leaks, but not all of them.

This commit is contained in:
Nick Winter 2014-02-06 14:00:27 -08:00
parent b696a3f188
commit 6af2d34f59
10 changed files with 56 additions and 17 deletions

View file

@ -20,7 +20,7 @@ elementAcceptsKeystrokes = (el) ->
# not radio, checkbox, range, or color # not radio, checkbox, range, or color
return (tag is 'textarea' or (tag is 'input' and type in textInputTypes) or el.contentEditable in ["", "true"]) and not (el.readOnly or el.disabled) return (tag is 'textarea' or (tag is 'input' and type in textInputTypes) or el.contentEditable in ["", "true"]) and not (el.readOnly or el.disabled)
COMMON_FILES = ['/images/modal_background.png', '/images/level/code_palette_background.png'] COMMON_FILES = ['/images/pages/base/modal_background.png', '/images/level/code_palette_background.png']
preload = (arrayOfImages) -> preload = (arrayOfImages) ->
$(arrayOfImages).each -> $(arrayOfImages).each ->
$('<img/>')[0].src = @ $('<img/>')[0].src = @

View file

@ -3,7 +3,7 @@
## Uncomment to imitate IE9 (and in world_utils.coffee) ## Uncomment to imitate IE9 (and in world_utils.coffee)
#window.Worker = null #window.Worker = null
#window.Float32Array = null #window.Float32Array = null
# (except that we won't have included vendor_with_box2d.js, so Collision won't run, things won't move, etc.) # Also uncomment vendor_with_box2d.js in index.html if you want Collision to run and things to move.
module.exports = class God module.exports = class God
@ids: ['Athena', 'Baldr', 'Crom', 'Dagr', 'Eris', 'Freyja', 'Great Gish', 'Hades', 'Ishtar', 'Janus', 'Khronos', 'Loki', 'Marduk', 'Negafook', 'Odin', 'Poseidon', 'Quetzalcoatl', 'Ra', 'Shiva', 'Thor', 'Umvelinqangi', 'Týr', 'Vishnu', 'Wepwawet', 'Xipe Totec', 'Yahweh', 'Zeus', '上帝', 'Tiamat', '盘古', 'Phoebe', 'Artemis', 'Osiris', "嫦娥", 'Anhur', 'Teshub', 'Enlil', 'Perkele', 'Aether', 'Chaos', 'Hera', 'Iris', 'Theia', 'Uranus', 'Stribog', 'Sabazios', 'Izanagi', 'Ao', 'Tāwhirimātea', 'Tengri', 'Inmar', 'Torngarsuk', 'Centzonhuitznahua', 'Hunab Ku', 'Apollo', 'Helios', 'Thoth', 'Hyperion', 'Alectrona', 'Eos', 'Mitra', 'Saranyu', 'Freyr', 'Koyash', 'Atropos', 'Clotho', 'Lachesis', 'Tyche', 'Skuld', 'Urðr', 'Verðandi', 'Camaxtli', 'Huhetotl', 'Set', 'Anu', 'Allah', 'Anshar', 'Hermes', 'Lugh', 'Brigit', 'Manannan Mac Lir', 'Persephone', 'Mercury', 'Venus', 'Mars', 'Azrael', 'He-Man', 'Anansi', 'Issek', 'Mog', 'Kos', 'Amaterasu Omikami', 'Raijin', 'Susanowo', 'Blind Io', 'The Lady', 'Offler', 'Ptah', 'Anubis', 'Ereshkigal', 'Nergal', 'Thanatos', 'Macaria', 'Angelos', 'Erebus', 'Hecate', 'Hel', 'Orcus', 'Ishtar-Deela Nakh', 'Prometheus', 'Hephaestos', 'Sekhmet', 'Ares', 'Enyo', 'Otrera', 'Pele', 'Hadúr', 'Hachiman', 'Dayisun Tngri', 'Ullr', 'Lua', 'Minerva'] @ids: ['Athena', 'Baldr', 'Crom', 'Dagr', 'Eris', 'Freyja', 'Great Gish', 'Hades', 'Ishtar', 'Janus', 'Khronos', 'Loki', 'Marduk', 'Negafook', 'Odin', 'Poseidon', 'Quetzalcoatl', 'Ra', 'Shiva', 'Thor', 'Umvelinqangi', 'Týr', 'Vishnu', 'Wepwawet', 'Xipe Totec', 'Yahweh', 'Zeus', '上帝', 'Tiamat', '盘古', 'Phoebe', 'Artemis', 'Osiris', "嫦娥", 'Anhur', 'Teshub', 'Enlil', 'Perkele', 'Aether', 'Chaos', 'Hera', 'Iris', 'Theia', 'Uranus', 'Stribog', 'Sabazios', 'Izanagi', 'Ao', 'Tāwhirimātea', 'Tengri', 'Inmar', 'Torngarsuk', 'Centzonhuitznahua', 'Hunab Ku', 'Apollo', 'Helios', 'Thoth', 'Hyperion', 'Alectrona', 'Eos', 'Mitra', 'Saranyu', 'Freyr', 'Koyash', 'Atropos', 'Clotho', 'Lachesis', 'Tyche', 'Skuld', 'Urðr', 'Verðandi', 'Camaxtli', 'Huhetotl', 'Set', 'Anu', 'Allah', 'Anshar', 'Hermes', 'Lugh', 'Brigit', 'Manannan Mac Lir', 'Persephone', 'Mercury', 'Venus', 'Mars', 'Azrael', 'He-Man', 'Anansi', 'Issek', 'Mog', 'Kos', 'Amaterasu Omikami', 'Raijin', 'Susanowo', 'Blind Io', 'The Lady', 'Offler', 'Ptah', 'Anubis', 'Ereshkigal', 'Nergal', 'Thanatos', 'Macaria', 'Angelos', 'Erebus', 'Hecate', 'Hel', 'Orcus', 'Ishtar-Deela Nakh', 'Prometheus', 'Hephaestos', 'Sekhmet', 'Ares', 'Enyo', 'Otrera', 'Pele', 'Hadúr', 'Hachiman', 'Dayisun Tngri', 'Ullr', 'Lua', 'Minerva']
@ -25,17 +25,22 @@ module.exports = class God
@createWorld() @createWorld()
getAngel: -> getAngel: ->
freeAngel = null
for angel in @angels for angel in @angels
return angel.enslave() unless angel.busy if angel.busy
angel.abort()
else
freeAngel ?= angel
return freeAngel.enslave() if freeAngel
maxedOut = @angels.length is @maxAngels maxedOut = @angels.length is @maxAngels
if not maxedOut if not maxedOut
angel = new Angel @ angel = new Angel @
@angels.push angel @angels.push angel
return angel.enslave() return angel.enslave()
oldestAngel = {started: new Date(2099, 1, 1)} #oldestAngel = {started: new Date(2099, 1, 1)}
for angel in @angels #for angel in @angels
oldestAngel = angel if angel.started < oldestAngel.started # oldestAngel = angel if angel.started < oldestAngel.started
oldestAngel.abort() #oldestAngel.abort()
null null
angelInfinitelyLooped: (angel) -> angelInfinitelyLooped: (angel) ->
@ -65,6 +70,13 @@ module.exports = class God
else else
@worldWaiting = true @worldWaiting = true
return return
console.log "about to post message", @getUserCodeMap(), @level, @firstWorld, @goalManager?.getGoals(), JSON.stringify({
worldName: @world.name
userCodeMap: @getUserCodeMap()
level: @level
firstWorld: @firstWorld
goals: @goalManager?.getGoals()
}).length
angel.worker.postMessage {func: 'runWorld', args: { angel.worker.postMessage {func: 'runWorld', args: {
worldName: @world.name worldName: @world.name
userCodeMap: @getUserCodeMap() userCodeMap: @getUserCodeMap()
@ -91,8 +103,11 @@ module.exports = class God
Backbone.Mediator.publish('god:new-world-created', world: @world, firstWorld: @firstWorld, errorCount: errorCount, goalStates: @latestGoalStates) Backbone.Mediator.publish('god:new-world-created', world: @world, firstWorld: @firstWorld, errorCount: errorCount, goalStates: @latestGoalStates)
for scriptNote in @world.scriptNotes for scriptNote in @world.scriptNotes
Backbone.Mediator.publish scriptNote.channel, scriptNote.event Backbone.Mediator.publish scriptNote.channel, scriptNote.event
@goalManager?.world = newWorld
@firstWorld = false @firstWorld = false
@testWorld = null @testWorld = null
unless _.find @angels, 'busy'
@spells = null # Don't hold onto old spells; memory leaks
getUserCodeMap: -> getUserCodeMap: ->
userCodeMap = {} userCodeMap = {}
@ -171,6 +186,7 @@ class Angel
@busy = true @busy = true
@started = new Date() @started = new Date()
@purgatoryTimer = setInterval @testWorker, @infiniteLoopIntervalDuration @purgatoryTimer = setInterval @testWorker, @infiniteLoopIntervalDuration
@spawnWorker() unless @worker
@ @
free: -> free: ->
@ -178,6 +194,8 @@ class Angel
@started = null @started = null
clearInterval @purgatoryTimer clearInterval @purgatoryTimer
@purgatoryTimer = null @purgatoryTimer = null
@worker.terminate()
@worker = null
@ @
abort: -> abort: ->
@ -186,8 +204,8 @@ class Angel
terminate: => terminate: =>
@worker.terminate() @worker.terminate()
@worker = null
return if @dead return if @dead
@spawnWorker()
@free() @free()
@god.angelAborted @ @god.angelAborted @
@ -219,7 +237,9 @@ class Angel
clearTimeout @abortTimeout clearTimeout @abortTimeout
@free() @free()
@god.angelAborted @ @god.angelAborted @
@worker.terminate() if @god.dead if @god.dead
@worker.terminate()
@worker = null
when 'reportIn' when 'reportIn'
clearTimeout @condemnTimeout clearTimeout @condemnTimeout
else else

View file

@ -116,7 +116,8 @@ module.exports = class LevelLoader extends CocoClass
# World init # World init
initWorld: -> initWorld: ->
return if @world return if @initialized
@initialized = true
@world = new World @level.get('name') @world = new World @level.get('name')
serializedLevel = @level.serialize(@supermodel) serializedLevel = @level.serialize(@supermodel)
@world.loadFromLevel serializedLevel, false @world.loadFromLevel serializedLevel, false
@ -203,9 +204,9 @@ module.exports = class LevelLoader extends CocoClass
notifyProgress: -> notifyProgress: ->
Backbone.Mediator.publish 'level-loader:progress-changed', progress: @progress() Backbone.Mediator.publish 'level-loader:progress-changed', progress: @progress()
@initWorld() if @allDone() @initWorld() if @allDone()
# @trigger 'ready-to-init-world' if @allDone()
@trigger 'loaded-all' if @progress() is 1 @trigger 'loaded-all' if @progress() is 1
destroy: -> destroy: ->
@world = null # don't hold onto garbage
@supermodel.off 'loaded-one', @onSupermodelLoadedOne @supermodel.off 'loaded-one', @onSupermodelLoadedOne
super() super()

View file

@ -19,6 +19,7 @@ module.exports = class SpriteBoss extends CocoClass
'level-suppress-selection-sounds': 'onSuppressSelectionSounds' 'level-suppress-selection-sounds': 'onSuppressSelectionSounds'
'level-lock-select': 'onSetLockSelect' 'level-lock-select': 'onSetLockSelect'
'level:restarted': 'onLevelRestarted' 'level:restarted': 'onLevelRestarted'
'god:new-world-created': 'onNewWorld'
constructor: (@options) -> constructor: (@options) ->
super() super()
@ -87,7 +88,7 @@ module.exports = class SpriteBoss extends CocoClass
@selectionMark = new Mark name: 'selection', camera: @camera, layer: @spriteLayers["Ground"], thangType: @thangTypeFor("Selection") @selectionMark = new Mark name: 'selection', camera: @camera, layer: @spriteLayers["Ground"], thangType: @thangTypeFor("Selection")
createSpriteOptions: (options) -> createSpriteOptions: (options) ->
_.extend options, camera: @camera, resolutionFactor: 4, groundLayer: @spriteLayers["Ground"], textLayer: @surfaceTextLayer, floatingLayer: @spriteLayers["Floating"], markThangTypes: @markThangTypes(), spriteSheetCache: @spriteSheetCache, showInvisible: @options.showInvisible, world: @world _.extend options, camera: @camera, resolutionFactor: 4, groundLayer: @spriteLayers["Ground"], textLayer: @surfaceTextLayer, floatingLayer: @spriteLayers["Floating"], markThangTypes: @markThangTypes(), spriteSheetCache: @spriteSheetCache, showInvisible: @options.showInvisible
createIndieSprites: (indieSprites, withWizards) -> createIndieSprites: (indieSprites, withWizards) ->
unless @indieSprites unless @indieSprites
@ -195,6 +196,9 @@ module.exports = class SpriteBoss extends CocoClass
spriteFor: (thangID) -> @sprites[thangID] spriteFor: (thangID) -> @sprites[thangID]
onNewWorld: (e) ->
@world = @options.world = e.world
# Selection # Selection
onSuppressSelectionSounds: (e) -> @suppressSelectionSounds = e.suppress onSuppressSelectionSounds: (e) -> @suppressSelectionSounds = e.suppress

View file

@ -90,6 +90,7 @@ module.exports = class GoalManager extends CocoClass
# passes the word along # passes the word along
onNewWorldCreated: (e) => onNewWorldCreated: (e) =>
@updateGoalStates(e.goalStates) if e.goalStates? @updateGoalStates(e.goalStates) if e.goalStates?
@world = e.world
updateGoalStates: (newGoalStates) -> updateGoalStates: (newGoalStates) ->
for goalID, goalState of newGoalStates for goalID, goalState of newGoalStates

View file

@ -78,7 +78,7 @@ module.exports = class World
(@runtimeErrors ?= []).push error (@runtimeErrors ?= []).push error
(@unhandledRuntimeErrors ?= []).push error (@unhandledRuntimeErrors ?= []).push error
loadFrames: (loadedCallback, errorCallback, loadProgressCallback) => loadFrames: (loadedCallback, errorCallback, loadProgressCallback) ->
return if @aborted return if @aborted
unless @thangs.length unless @thangs.length
console.log "Warning: loadFrames called on empty World (no thangs)." console.log "Warning: loadFrames called on empty World (no thangs)."

View file

@ -28,7 +28,10 @@ module.exports = class Spell
@tabView.render() @tabView.render()
addThang: (thang) -> addThang: (thang) ->
@thangs[thang.id] ?= {thang: thang, aether: @createAether(thang), castAether: null} if @thangs[thang.id]
@thangs[thang.id].thang = thang
else
@thangs[thang.id] = {thang: thang, aether: @createAether(thang), castAether: null}
removeThangID: (thangID) -> removeThangID: (thangID) ->
delete @thangs[thangID] delete @thangs[thangID]

View file

@ -134,3 +134,7 @@ module.exports = class ThangListEntryView extends View
return unless currentThang = e.world.thangMap[@thang.id] return unless currentThang = e.world.thangMap[@thang.id]
@$el.toggle Boolean(currentThang.exists) @$el.toggle Boolean(currentThang.exists)
@$el.toggleClass 'dead', currentThang.health <= 0 if currentThang.exists @$el.toggleClass 'dead', currentThang.health <= 0 if currentThang.exists
destroy: ->
super()
@avatar.destroy()

View file

@ -65,6 +65,7 @@ module.exports = class TomeView extends View
else else
@cast() @cast()
console.warn "Warning: There are no Programmable Thangs in this level, which makes it unplayable." console.warn "Warning: There are no Programmable Thangs in this level, which makes it unplayable."
delete @options.thangs
onNewWorld: (e) -> onNewWorld: (e) ->
thangs = _.filter e.world.thangs, 'isSelectable' thangs = _.filter e.world.thangs, 'isSelectable'
@ -95,7 +96,7 @@ module.exports = class TomeView extends View
@spells[spellKey].addThang thang for spellKey in spellKeys @spells[spellKey].addThang thang for spellKey in spellKeys
else else
delete @thangSpells[thangID] delete @thangSpells[thangID]
@spells[spellKey].removeThangID thangID for spellKey in spellKeys spell.removeThangID thangID for spell in @spells
null null
onSpellLoaded: (e) -> onSpellLoaded: (e) ->

View file

@ -50,6 +50,7 @@ module.exports = class PlayLevelView extends View
'level-focus-dom': 'onFocusDom' 'level-focus-dom': 'onFocusDom'
'level-disable-controls': 'onDisableControls' 'level-disable-controls': 'onDisableControls'
'level-enable-controls': 'onEnableControls' 'level-enable-controls': 'onEnableControls'
'god:new-world-created': 'onNewWorld'
'god:infinite-loop': 'onInfiniteLoop' 'god:infinite-loop': 'onInfiniteLoop'
'bus:connected': 'onBusConnected' 'bus:connected': 'onBusConnected'
'level-reload-from-data': 'onLevelReloadFromData' 'level-reload-from-data': 'onLevelReloadFromData'
@ -102,7 +103,6 @@ module.exports = class PlayLevelView extends View
load: -> load: ->
@levelLoader = new LevelLoader(@levelID, @supermodel, @sessionID) @levelLoader = new LevelLoader(@levelID, @supermodel, @sessionID)
@levelLoader.once 'ready-to-init-world', @onReadyToInitWorld
@levelLoader.once 'loaded-all', @onLevelLoaderLoaded @levelLoader.once 'loaded-all', @onLevelLoaderLoaded
getRenderData: -> getRenderData: ->
@ -120,6 +120,8 @@ module.exports = class PlayLevelView extends View
@session = @levelLoader.session @session = @levelLoader.session
@level = @levelLoader.level @level = @levelLoader.level
@world = @levelLoader.world @world = @levelLoader.world
@levelLoader.destroy()
@levelLoader = null
@loadingScreen.destroy() @loadingScreen.destroy()
@setTeam @world.teamForPlayer 1 # We don't know which player we are; this will go away--temp TODO @setTeam @world.teamForPlayer 1 # We don't know which player we are; this will go away--temp TODO
@initSurface() @initSurface()
@ -206,6 +208,9 @@ module.exports = class PlayLevelView extends View
$('#level-done-button', @$el).hide() $('#level-done-button', @$el).hide()
window.tracker?.trackEvent 'Confirmed Restart', level: @world.name, label: @world.name window.tracker?.trackEvent 'Confirmed Restart', level: @world.name, label: @world.name
onNewWorld: (e) ->
@world = e.world
onInfiniteLoop: (e) -> onInfiniteLoop: (e) ->
return unless e.firstWorld return unless e.firstWorld
@openModalView new InfiniteLoopModal() @openModalView new InfiniteLoopModal()
@ -378,7 +383,7 @@ module.exports = class PlayLevelView extends View
destroy: -> destroy: ->
super() super()
@levelLoader.destroy() @levelLoader?.destroy()
@surface?.destroy() @surface?.destroy()
@god?.destroy() @god?.destroy()
@goalManager?.destroy() @goalManager?.destroy()