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
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) ->
$(arrayOfImages).each ->
$('<img/>')[0].src = @

View file

@ -3,7 +3,7 @@
## Uncomment to imitate IE9 (and in world_utils.coffee)
#window.Worker = 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
@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()
getAngel: ->
freeAngel = null
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
if not maxedOut
angel = new Angel @
@angels.push angel
return angel.enslave()
oldestAngel = {started: new Date(2099, 1, 1)}
for angel in @angels
oldestAngel = angel if angel.started < oldestAngel.started
oldestAngel.abort()
#oldestAngel = {started: new Date(2099, 1, 1)}
#for angel in @angels
# oldestAngel = angel if angel.started < oldestAngel.started
#oldestAngel.abort()
null
angelInfinitelyLooped: (angel) ->
@ -65,6 +70,13 @@ module.exports = class God
else
@worldWaiting = true
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: {
worldName: @world.name
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)
for scriptNote in @world.scriptNotes
Backbone.Mediator.publish scriptNote.channel, scriptNote.event
@goalManager?.world = newWorld
@firstWorld = false
@testWorld = null
unless _.find @angels, 'busy'
@spells = null # Don't hold onto old spells; memory leaks
getUserCodeMap: ->
userCodeMap = {}
@ -171,6 +186,7 @@ class Angel
@busy = true
@started = new Date()
@purgatoryTimer = setInterval @testWorker, @infiniteLoopIntervalDuration
@spawnWorker() unless @worker
@
free: ->
@ -178,6 +194,8 @@ class Angel
@started = null
clearInterval @purgatoryTimer
@purgatoryTimer = null
@worker.terminate()
@worker = null
@
abort: ->
@ -186,8 +204,8 @@ class Angel
terminate: =>
@worker.terminate()
@worker = null
return if @dead
@spawnWorker()
@free()
@god.angelAborted @
@ -219,7 +237,9 @@ class Angel
clearTimeout @abortTimeout
@free()
@god.angelAborted @
@worker.terminate() if @god.dead
if @god.dead
@worker.terminate()
@worker = null
when 'reportIn'
clearTimeout @condemnTimeout
else

View file

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

View file

@ -19,6 +19,7 @@ module.exports = class SpriteBoss extends CocoClass
'level-suppress-selection-sounds': 'onSuppressSelectionSounds'
'level-lock-select': 'onSetLockSelect'
'level:restarted': 'onLevelRestarted'
'god:new-world-created': 'onNewWorld'
constructor: (@options) ->
super()
@ -87,7 +88,7 @@ module.exports = class SpriteBoss extends CocoClass
@selectionMark = new Mark name: 'selection', camera: @camera, layer: @spriteLayers["Ground"], thangType: @thangTypeFor("Selection")
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) ->
unless @indieSprites
@ -195,6 +196,9 @@ module.exports = class SpriteBoss extends CocoClass
spriteFor: (thangID) -> @sprites[thangID]
onNewWorld: (e) ->
@world = @options.world = e.world
# Selection
onSuppressSelectionSounds: (e) -> @suppressSelectionSounds = e.suppress

View file

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

View file

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

View file

@ -28,7 +28,10 @@ module.exports = class Spell
@tabView.render()
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) ->
delete @thangs[thangID]

View file

@ -134,3 +134,7 @@ module.exports = class ThangListEntryView extends View
return unless currentThang = e.world.thangMap[@thang.id]
@$el.toggle Boolean(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
@cast()
console.warn "Warning: There are no Programmable Thangs in this level, which makes it unplayable."
delete @options.thangs
onNewWorld: (e) ->
thangs = _.filter e.world.thangs, 'isSelectable'
@ -95,7 +96,7 @@ module.exports = class TomeView extends View
@spells[spellKey].addThang thang for spellKey in spellKeys
else
delete @thangSpells[thangID]
@spells[spellKey].removeThangID thangID for spellKey in spellKeys
spell.removeThangID thangID for spell in @spells
null
onSpellLoaded: (e) ->

View file

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