From f1a4249f18f8a1e030a64d484d91e4e755f47bd0 Mon Sep 17 00:00:00 2001
From: Nick Winter <livelily@gmail.com>
Date: Tue, 18 Nov 2014 11:21:29 -0800
Subject: [PATCH] More reductions in memory usage for iPad.

---
 app/lib/surface/MusicPlayer.coffee                 | 1 +
 app/models/SuperModel.coffee                       | 1 +
 app/schemas/subscriptions/ipad.coffee              | 6 ++++--
 app/views/play/WorldMapView.coffee                 | 3 +++
 app/views/play/level/ControlBarView.coffee         | 6 +++++-
 app/views/play/level/PlayLevelView.coffee          | 8 ++++++--
 app/views/play/level/modal/HeroVictoryModal.coffee | 6 +++---
 7 files changed, 23 insertions(+), 8 deletions(-)

diff --git a/app/lib/surface/MusicPlayer.coffee b/app/lib/surface/MusicPlayer.coffee
index a3e1f0cb3..bf07dbf3c 100644
--- a/app/lib/surface/MusicPlayer.coffee
+++ b/app/lib/surface/MusicPlayer.coffee
@@ -25,6 +25,7 @@ module.exports = class MusicPlayer extends CocoClass
     @onPlayMusic(@standingBy) if @standingBy
 
   onPlayMusic: (e) ->
+    return if application.isIPadApp  # Hard to measure, but just guessing this will save memory.
     src = e.file
     src = "/file#{e.file}#{AudioPlayer.ext}"
     if (not e.file) or src is @currentMusic?.src
diff --git a/app/models/SuperModel.coffee b/app/models/SuperModel.coffee
index 158f864f9..8da29ea70 100644
--- a/app/models/SuperModel.coffee
+++ b/app/models/SuperModel.coffee
@@ -181,6 +181,7 @@ module.exports = class SuperModel extends Backbone.Model
     @num += r.value
     _.defer @updateProgress
     r.clean()
+    @stopListening r, 'failed', @onResourceFailed
     @trigger 'resource-loaded', r
 
   onResourceFailed: (r) ->
diff --git a/app/schemas/subscriptions/ipad.coffee b/app/schemas/subscriptions/ipad.coffee
index c4013e52d..9c16a600c 100644
--- a/app/schemas/subscriptions/ipad.coffee
+++ b/app/schemas/subscriptions/ipad.coffee
@@ -2,10 +2,12 @@ c = require 'schemas/schemas'
 
 module.exports =
   'ipad:products': c.object {required: ['products']},
-    products: c.array {}, 
+    products: c.array {},
       c.object {},
         price: { type: 'string' }
         id: { type: 'string' }
-        
+
   'ipad:iap-complete': c.object {},
     productID: { type: 'string' }
+
+  'ipad:memory-warning': c.object {}
diff --git a/app/views/play/WorldMapView.coffee b/app/views/play/WorldMapView.coffee
index a5c263e2c..fc556515d 100644
--- a/app/views/play/WorldMapView.coffee
+++ b/app/views/play/WorldMapView.coffee
@@ -31,6 +31,8 @@ module.exports = class WorldMapView extends RootView
     'click #volume-button': 'onToggleVolume'
 
   constructor: (options, @terrain) ->
+    if options and application.isIPAdApp  # TODO: later only clear the SuperModel if it has received a memory warning (not in app store yet)
+      options.supermodel = null
     @terrain ?= 'dungeon' # or 'forest'
     super options
     @nextLevel = @getQueryVariable 'next'
@@ -282,6 +284,7 @@ module.exports = class WorldMapView extends RootView
     storage.save("loaded-menu-music-#{@terrain}", true) unless @probablyCachedMusic
 
   preloadTopHeroes: ->
+    return  # Don't do this because these two have feature images, so we don't need the raw vector data for them. Later they'll all have feature images...
     for heroID in ['captain', 'knight']
       url = "/db/thang.type/#{ThangType.heroes[heroID]}/version"
       continue if @supermodel.getModel url
diff --git a/app/views/play/level/ControlBarView.coffee b/app/views/play/level/ControlBarView.coffee
index 354e46bd4..b718543a9 100644
--- a/app/views/play/level/ControlBarView.coffee
+++ b/app/views/play/level/ControlBarView.coffee
@@ -16,6 +16,7 @@ module.exports = class ControlBarView extends CocoView
     'bus:player-states-changed': 'onPlayerStatesChanged'
     'level:disable-controls': 'onDisableControls'
     'level:enable-controls': 'onEnableControls'
+    'ipad:memory-warning': 'onIPadMemoryWarning'
 
   events:
     'click #next-game-button': -> Backbone.Mediator.publish 'level:next-game-pressed', {}
@@ -56,7 +57,7 @@ module.exports = class ControlBarView extends CocoView
     if c.isMultiplayerLevel = @isMultiplayerLevel
       c.multiplayerStatus = @multiplayerStatusManager?.status
     c.spectateGame = @spectateGame
-    @homeViewArgs = [{supermodel: @supermodel}]
+    @homeViewArgs = [{supermodel: if @hasReceivedMemoryWarning then null else @supermodel}]
     if @level.get('type', true) in ['ladder', 'ladder-tutorial', 'hero-ladder']
       levelID = @level.get('slug').replace /\-tutorial$/, ''
       @homeLink = c.homeLink = '/play/ladder/' + levelID
@@ -105,6 +106,9 @@ module.exports = class ControlBarView extends CocoView
       for level in campaign.levels
         return campaign.id if level.id is slug
 
+  onIPadMemoryWarning: (e) ->
+    @hasReceivedMemoryWarning = true
+
   destroy: ->
     @setupManager?.destroy()
     @multiplayerStatusManager?.destroy()
diff --git a/app/views/play/level/PlayLevelView.coffee b/app/views/play/level/PlayLevelView.coffee
index cd216a0fd..82ef103fa 100644
--- a/app/views/play/level/PlayLevelView.coffee
+++ b/app/views/play/level/PlayLevelView.coffee
@@ -77,6 +77,7 @@ module.exports = class PlayLevelView extends RootView
     'real-time-multiplayer:joined-game': 'onRealTimeMultiplayerJoinedGame'
     'real-time-multiplayer:left-game': 'onRealTimeMultiplayerLeftGame'
     'real-time-multiplayer:manual-cast': 'onRealTimeMultiplayerCast'
+    'ipad:memory-warning': 'onIPadMemoryWarning'
 
   events:
     'click #level-done-button': 'onDonePressed'
@@ -435,7 +436,7 @@ module.exports = class PlayLevelView extends RootView
 
   showVictory: ->
     @endHighlight()
-    options = {level: @level, supermodel: @supermodel, session: @session}
+    options = {level: @level, supermodel: @supermodel, session: @session, hasReceivedMemoryWarning: @hasReceivedMemoryWarning}
     ModalClass = if @level.get('type', true) in ['hero', 'hero-ladder', 'hero-coop'] then HeroVictoryModal else VictoryModal
     victoryModal = new ModalClass(options)
     @openModalView(victoryModal)
@@ -459,7 +460,7 @@ module.exports = class PlayLevelView extends RootView
     Backbone.Mediator.publish 'router:navigate', {
       route: nextLevelURL,
       viewClass: PlayLevelView,
-      viewArgs: [{supermodel: @supermodel}, nextLevelID]}
+      viewArgs: [{supermodel: if @hasReceivedMemoryWarning then null else @supermodel}, nextLevelID]}
 
   getNextLevel: ->
     return null unless nextLevelOriginal = @level.get('nextLevel')?.original
@@ -902,3 +903,6 @@ module.exports = class PlayLevelView extends RootView
     if sessionState?
       # TODO: Don't hardcode spellName
       Backbone.Mediator.publish 'level:select-sprite', thangID: sessionState.selected, spellName: 'plan'
+
+  onIPadMemoryWarning: (e) ->
+    @hasReceivedMemoryWarning = true
diff --git a/app/views/play/level/modal/HeroVictoryModal.coffee b/app/views/play/level/modal/HeroVictoryModal.coffee
index 6e16c442e..dab851483 100644
--- a/app/views/play/level/modal/HeroVictoryModal.coffee
+++ b/app/views/play/level/modal/HeroVictoryModal.coffee
@@ -298,7 +298,7 @@ module.exports = class HeroVictoryModal extends ModalView
     skipPrompt ||= not (@skipAheadLevelLink or @morePractiveLevelLink) and me.getBranchingGroup() is 'choice-explicit'
     if skipPrompt
       # Preserve the supermodel as we navigate back to the world map.
-      Backbone.Mediator.publish 'router:navigate', route: nextLevelLink, viewClass: require('views/play/WorldMapView'), viewArgs: [{supermodel: @supermodel}, @getNextLevelCampaign()]
+      Backbone.Mediator.publish 'router:navigate', route: nextLevelLink, viewClass: require('views/play/WorldMapView'), viewArgs: [{supermodel: if @options.hasReceivedMemoryWarning then null else @supermodel}, @getNextLevelCampaign()]
     else
       # Hide everything except the buttons prompting them for which kind of next level to do
       @$el.find('.modal-footer, .modal-body > *').hide()
@@ -309,10 +309,10 @@ module.exports = class HeroVictoryModal extends ModalView
     route = $(e.target).data('href') or "/play/#{@getNextLevelCampaign()}"
     application.tracker?.trackEvent 'Branch Selected', level: @level.get('slug'), label: @level.get('slug'), branch: $(e.target).data('branch-key'), branchingGroup: me.getBranchingGroup(), route: route
     # Preserve the supermodel as we navigate back to world map.
-    Backbone.Mediator.publish 'router:navigate', route: route, viewClass: require('views/play/WorldMapView'), viewArgs: [{supermodel: @supermodel}, @getNextLevelCampaign()]
+    Backbone.Mediator.publish 'router:navigate', route: route, viewClass: require('views/play/WorldMapView'), viewArgs: [{supermodel: if @options.hasReceivedMemoryWarning then null else @supermodel}, @getNextLevelCampaign()]
 
   onClickReturnToLadder: (e) ->
     e.preventDefault()
     route = $(e.target).data('href')
     # Preserve the supermodel as we navigate back to the ladder.
-    Backbone.Mediator.publish 'router:navigate', route: route, viewClass: require('views/play/ladder/LadderView'), viewArgs: [{supermodel: @supermodel}, @level.get('slug')]
+    Backbone.Mediator.publish 'router:navigate', route: route, viewClass: require('views/play/ladder/LadderView'), viewArgs: [{supermodel: if @options.hasReceivedMemoryWarning then null else @supermodel}, @level.get('slug')]