From 172d97ed830498694c062f12f89ff540c6d9a776 Mon Sep 17 00:00:00 2001
From: Nick Winter <livelily@gmail.com>
Date: Wed, 15 Jan 2014 13:04:48 -0800
Subject: [PATCH] Fixed #87 I hope; at least, dramatically reduced memory
 leakage.

---
 app/lib/God.coffee                            |  2 ++
 app/lib/surface/SpriteBoss.coffee             |  8 ++------
 app/lib/world/world.coffee                    |  3 ++-
 app/locale/fr.coffee                          | 10 +++++-----
 app/models/ThangType.coffee                   | 19 ++++++++++---------
 app/views/play/level/hud_view.coffee          |  4 ++++
 app/views/play/level/thang_avatar_view.coffee |  4 ++++
 app/views/play/level/tome/spell.coffee        |  1 -
 .../level/tome/spell_list_entry_view.coffee   |  4 ++++
 .../tome/spell_list_tab_entry_view.coffee     |  4 ++++
 .../play/level/tome/spell_list_view.coffee    |  6 +++++-
 .../tome/spell_palette_entry_view.coffee      |  4 ++--
 .../play/level/tome/spell_palette_view.coffee |  2 +-
 13 files changed, 45 insertions(+), 26 deletions(-)

diff --git a/app/lib/God.coffee b/app/lib/God.coffee
index 044bd5c32..3e3686b1a 100644
--- a/app/lib/God.coffee
+++ b/app/lib/God.coffee
@@ -18,6 +18,7 @@ module.exports = class God
     @angels = []
     @firstWorld = true
     Backbone.Mediator.subscribe 'tome:cast-spells', @onTomeCast, @
+    console.log @id, "initialized with world", @world.id
 
   onTomeCast: (e) ->
     return if @dead
@@ -92,6 +93,7 @@ module.exports = class God
     for scriptNote in @world.scriptNotes
       Backbone.Mediator.publish scriptNote.channel, scriptNote.event
     @firstWorld = false
+    @testWorld = null
 
   getUserCodeMap: ->
     userCodeMap = {}
diff --git a/app/lib/surface/SpriteBoss.coffee b/app/lib/surface/SpriteBoss.coffee
index 14718d136..ded4db112 100644
--- a/app/lib/surface/SpriteBoss.coffee
+++ b/app/lib/surface/SpriteBoss.coffee
@@ -179,15 +179,11 @@ module.exports = class SpriteBoss extends CocoClass
     wallSprites = (sprite for thangID, sprite of @sprites when sprite.thangType?.get('name') is 'Dungeon Wall')
     walls = (sprite.thang for sprite in wallSprites)
     @world.calculateBounds()
-    if @wallGrid and @wallGrid.width is @world.width and @wallGrid.height is @world.height
-      @wallGrid.update walls
-    else
-      @wallGrid = new Grid walls, @world.size()...
+    wallGrid = new Grid walls, @world.size()...
     for wallSprite in wallSprites
-      wallSprite.updateActionDirection @wallGrid
+      wallSprite.updateActionDirection wallGrid
       wallSprite.updateScale()
       wallSprite.updatePosition()
-      # TODO: there's some bug whereby a new wall isn't drawn properly when cached the first time
     #console.log @wallGrid.toString()
     @spriteLayers["Obstacle"].uncache() if @spriteLayers["Obstacle"].cacheID  # might have changed sizes
     @spriteLayers["Obstacle"].cache()
diff --git a/app/lib/world/world.coffee b/app/lib/world/world.coffee
index a6ba95418..75794f1a1 100644
--- a/app/lib/world/world.coffee
+++ b/app/lib/world/world.coffee
@@ -28,6 +28,7 @@ module.exports = class World
     @scriptNotes = []
     @rand = new Rand 0
     @frames = [new WorldFrame(@, 0)]
+    @id = Math.random()
   age: 0
   ended: false
 
@@ -255,7 +256,7 @@ module.exports = class World
     o = {name: @name, totalFrames: @totalFrames, maxTotalFrames: @maxTotalFrames, frameRate: @frameRate, dt: @dt, victory: @victory, userCodeMap: {}}
 
     o[prop] = @[prop] for prop in @trackedProperties or []
-    
+
     for thangID, methods of @userCodeMap
       serializedMethods = o.userCodeMap[thangID] = {}
       for methodName, method of methods
diff --git a/app/locale/fr.coffee b/app/locale/fr.coffee
index 57cd483d0..680604b06 100644
--- a/app/locale/fr.coffee
+++ b/app/locale/fr.coffee
@@ -280,10 +280,10 @@ module.exports = nativeDescription: "français", englishDescription: "French", t
     and: "et"
 
   about:
-     who_is_codecombat: "Qui est CodeCombat?"
-     why_codecombat: "Pourquoi CodeCombat?"
-     who_description_prefix: "avons commencé ensembles en 2013. Nous avons aussi créé "
-     who_description_suffix: "en 2008, qui a grandi jusqu'à devenir la première application web et iOS pour apprendre à écrire les caractères chinois et japonais."
+    who_is_codecombat: "Qui est CodeCombat?"
+    why_codecombat: "Pourquoi CodeCombat?"
+    who_description_prefix: "avons commencé ensembles en 2013. Nous avons aussi créé "
+    who_description_suffix: "en 2008, qui a grandi jusqu'à devenir la première application web et iOS pour apprendre à écrire les caractères chinois et japonais."
 #    who_description_ending: "Now it's time to teach people to write code."
 #    why_paragraph_1: "When making Skritter, George didn't know how to program and was constantly frustrated by his inability to implement his ideas. Afterwards, he tried learning, but the lessons were too slow. His housemate, wanting to reskill and stop teaching, tried Codecademy, but \"got bored.\" Each week another friend started Codecademy, then dropped off. We realized it was the same problem we'd solved with Skritter: people learning a skill via slow, intensive lessons when what they need is fast, extensive practice. We know how to fix that."
 #    why_paragraph_2: "Need to learn to code? You don't need lessons. You need to write a lot of code and have a great time doing it."
@@ -293,7 +293,7 @@ module.exports = nativeDescription: "français", englishDescription: "French", t
 #    why_paragraph_3_italic_caps: "NO MOM I HAVE TO FINISH THE LEVEL!"
 #    why_paragraph_3_suffix: "That's why CodeCombat is a multiplayer game, not a gamified lesson course. We won't stop until you can't stop--but this time, that's a good thing."
 #    why_paragraph_4: "If you're going to get addicted to some game, get addicted to this one and become one of the wizards of the tech age."
-     why_ending: "Et au fait, c'est gratuit. "
+    why_ending: "Et au fait, c'est gratuit. "
 #    why_ending_url: "Start wizarding now!"
 #    george_description: "CEO, business guy, web designer, game designer, and champion of beginning programmers everywhere."
 #    scott_description: "Programmer extraordinaire, software architect, kitchen wizard, and master of finances. Scott is the reasonable one."
diff --git a/app/models/ThangType.coffee b/app/models/ThangType.coffee
index 62885e3b4..cb54bbc00 100644
--- a/app/models/ThangType.coffee
+++ b/app/models/ThangType.coffee
@@ -24,7 +24,7 @@ module.exports = class ThangType extends CocoModel
 
   getActions: ->
     return @actions or @buildActions()
-    
+
   buildActions: ->
     @actions = _.cloneDeep(@get('actions'))
     for name, action of @actions
@@ -33,12 +33,12 @@ module.exports = class ThangType extends CocoModel
         relatedAction.name = action.name + "_" + relatedName
         @actions[relatedAction.name] = relatedAction
     @actions
-    
+
   getSpriteSheet: (options) ->
     options = @fillOptions options
     key = @spriteSheetKey(options)
     return @spriteSheets[key] or @buildSpriteSheet(options)
-    
+
   fillOptions: (options) ->
     options ?= {}
     options = _.clone options
@@ -60,7 +60,7 @@ module.exports = class ThangType extends CocoModel
     @builder = new createjs.SpriteSheetBuilder()
     @builder.padding = 2
     @frames = {}
-    
+
   addPortrait: ->
     # The portrait is built very differently than the other animations, so it gets a separate function.
     return unless @actions
@@ -98,7 +98,7 @@ module.exports = class ThangType extends CocoModel
       next = action.goesTo if action.goesTo
       next = false if action.loops is false
       @builder.addAnimation name, frames, next
-      
+
     for name, action of @actions when action.container and not action.animation
       continue if name is 'portrait'
       scale = @options.resolutionFactor * (action.scale or @get('scale') or 1)
@@ -131,15 +131,17 @@ module.exports = class ThangType extends CocoModel
       @builder.buildAsync()
       @builder.on 'complete', @onBuildSpriteSheetComplete, @, true, key
       return true
-    
+
     console.warn 'Building', @get('name'), 'and blocking the main thread. LevelLoader should have it built asynchronously instead.'
     spriteSheet = @builder.build()
     @spriteSheets[key] = spriteSheet
     spriteSheet
-    
+
   onBuildSpriteSheetComplete: (e, key) ->
     @spriteSheets[key] = e.target.spriteSheet
     @trigger 'build-complete'
+    @builder = null
+    @vectorParser = null
 
   spriteSheetKey: (options) ->
     colorConfigs = []
@@ -183,7 +185,7 @@ module.exports = class ThangType extends CocoModel
       createjs.Ticker.removeEventListener 'tick', @tick
       @tick = null
     stage
-    
+
   uploadGenericPortrait: (callback) ->
     src = @getPortraitSource()
     return callback?() unless src
@@ -198,4 +200,3 @@ module.exports = class ThangType extends CocoModel
 
   onFileUploaded: =>
     console.log 'Image uploaded'
-
diff --git a/app/views/play/level/hud_view.coffee b/app/views/play/level/hud_view.coffee
index 3eb295a6f..7a251205c 100644
--- a/app/views/play/level/hud_view.coffee
+++ b/app/views/play/level/hud_view.coffee
@@ -21,6 +21,7 @@ module.exports = class HUDView extends View
     'dialogue-sound-completed': 'onDialogueSoundCompleted'
     'thang-began-talking': 'onThangBeganTalking'
     'thang-finished-talking': 'onThangFinishedTalking'
+    'god:new-world-created': 'onNewWorld'
 
   events:
     'click': -> Backbone.Mediator.publish 'focus-editor'
@@ -61,6 +62,9 @@ module.exports = class HUDView extends View
   onSpriteClearDialogue: ->
     @clearSpeaker()
 
+  onNewWorld: (e) ->
+    @thang = e.world.thangMap[@thang.id] if @thang
+
   setThang: (thang, thangType) ->
     unless @speaker
       if not thang? and not @thang? then return
diff --git a/app/views/play/level/thang_avatar_view.coffee b/app/views/play/level/thang_avatar_view.coffee
index a03d4a63c..40dece4f1 100644
--- a/app/views/play/level/thang_avatar_view.coffee
+++ b/app/views/play/level/thang_avatar_view.coffee
@@ -8,6 +8,7 @@ module.exports = class ThangAvatarView extends View
 
   subscriptions:
     'tome:problems-updated': "onProblemsUpdated"
+    'god:new-world-created': 'onNewWorld'
 
   constructor: (options) ->
     super options
@@ -50,3 +51,6 @@ module.exports = class ThangAvatarView extends View
       worstLevel = level
       break
     @setProblems myProblems.length, worstLevel
+
+  onNewWorld: (e) ->
+    @options.thang = @thang = e.world.thangMap[@thang.id] if @thang
diff --git a/app/views/play/level/tome/spell.coffee b/app/views/play/level/tome/spell.coffee
index 0c256d751..19f88bd3a 100644
--- a/app/views/play/level/tome/spell.coffee
+++ b/app/views/play/level/tome/spell.coffee
@@ -17,7 +17,6 @@ module.exports = class Spell
     @thangs = {}
     @view = new SpellView {spell: @, session: @session}
     @view.render()  # Get it ready and code loaded in advance
-    console.log 'spell creates tab entry view', @supermodel
     @tabView = new SpellListTabEntryView spell: @, supermodel: @supermodel
     @tabView.render()
 
diff --git a/app/views/play/level/tome/spell_list_entry_view.coffee b/app/views/play/level/tome/spell_list_entry_view.coffee
index 944326103..66f259418 100644
--- a/app/views/play/level/tome/spell_list_entry_view.coffee
+++ b/app/views/play/level/tome/spell_list_entry_view.coffee
@@ -15,6 +15,7 @@ module.exports = class SpellListEntryView extends View
     'tome:problems-updated': "onProblemsUpdated"
     'level-disable-controls': 'onDisableControls'
     'level-enable-controls': 'onEnableControls'
+    'god:new-world-created': 'onNewWorld'
 
   events:
     'click': 'onClick'
@@ -96,3 +97,6 @@ module.exports = class SpellListEntryView extends View
     # Should refactor the disabling list so we can target the spell list separately?
     # Should not call it 'editor' any more?
     @$el.toggleClass('disabled', disabled).find('*').prop('disabled', disabled)
+
+  onNewWorld: (e) ->
+    @lastSelectedThang = e.world.thangMap[@lastSelectedThang.id] if @lastSelectedThang
diff --git a/app/views/play/level/tome/spell_list_tab_entry_view.coffee b/app/views/play/level/tome/spell_list_tab_entry_view.coffee
index 9c4092646..06e6639bf 100644
--- a/app/views/play/level/tome/spell_list_tab_entry_view.coffee
+++ b/app/views/play/level/tome/spell_list_tab_entry_view.coffee
@@ -10,6 +10,7 @@ module.exports = class SpellListTabEntryView extends SpellListEntryView
   subscriptions:
     'tome:spell-loaded': "onSpellLoaded"
     'tome:spell-changed': "onSpellChanged"
+    'god:new-world-created': 'onNewWorld'
 
   events:
     'click .spell-list-button': 'onDropdownClick'
@@ -26,6 +27,9 @@ module.exports = class SpellListTabEntryView extends SpellListEntryView
     super()
     @$el.addClass 'spell-tab'
 
+  onNewWorld: (e) ->
+    @thang = e.world.thangMap[@thang.id] if @thang
+
   setThang: (thang) ->
     return if thang.id is @thang?.id
     @thang = thang
diff --git a/app/views/play/level/tome/spell_list_view.coffee b/app/views/play/level/tome/spell_list_view.coffee
index bf09b1ca1..60821af49 100644
--- a/app/views/play/level/tome/spell_list_view.coffee
+++ b/app/views/play/level/tome/spell_list_view.coffee
@@ -14,7 +14,8 @@ module.exports = class SpellListView extends View
   id: 'spell-list-view'
   template: template
 
-  subscriptions: {}
+  subscriptions:
+    'god:new-world-created': 'onNewWorld'
 
   constructor: (options) ->
     super options
@@ -64,6 +65,9 @@ module.exports = class SpellListView extends View
       @$el.append entry.el
       entry.render()  # Render after appending so that we can access parent container for popover
 
+  onNewWorld: (e) ->
+    @thang = e.world.thangMap[@thang.id] if @thang
+
   setThangAndSpell: (@thang, @spell) ->
     @entries[0]?.setSelected false
     @sortSpells()
diff --git a/app/views/play/level/tome/spell_palette_entry_view.coffee b/app/views/play/level/tome/spell_palette_entry_view.coffee
index 37904ff54..56e6e40e0 100644
--- a/app/views/play/level/tome/spell_palette_entry_view.coffee
+++ b/app/views/play/level/tome/spell_palette_entry_view.coffee
@@ -51,6 +51,6 @@ module.exports = class SpellPaletteEntryView extends View
 
   onFrameChanged: (e) ->
     return unless e.selectedThang?.id is @thang.id
-    @thang = e.selectedThang  # Update our thang to the current version
-    @doc = Docs.getDocsFor(@thang, [@doc.prop])[0]
+    @options.thang = @thang = e.selectedThang  # Update our thang to the current version
+    @options.doc = @doc = Docs.getDocsFor(@thang, [@doc.prop])[0]
     @$el.find("code.current-value").text(@doc.formatValue())  # Don't call any functions. (?? What does this mean?)
diff --git a/app/views/play/level/tome/spell_palette_view.coffee b/app/views/play/level/tome/spell_palette_view.coffee
index ee080950e..445364274 100644
--- a/app/views/play/level/tome/spell_palette_view.coffee
+++ b/app/views/play/level/tome/spell_palette_view.coffee
@@ -55,7 +55,7 @@ module.exports = class SpellPaletteView extends View
 
   onFrameChanged: (e) ->
     return unless e.selectedThang?.id is @thang.id
-    @thang = e.selectedThang  # Update our thang to the current version
+    @options.thang = @thang = e.selectedThang  # Update our thang to the current version
 
   destroy: ->
     super()