From b9f4093b083d0cdd2114fd8cb99197b2136b19a1 Mon Sep 17 00:00:00 2001 From: Scott Erickson <sderickson@gmail.com> Date: Mon, 28 Apr 2014 14:58:58 -0700 Subject: [PATCH 01/12] Set up the LevelLoader to load names instead of whole ThangTypes. --- app/collections/ThangNamesCollection.coffee | 11 +++++++++++ app/lib/LevelLoader.coffee | 18 ++++++++++++------ app/locale/en.coffee | 1 + app/models/SuperModel.coffee | 3 +-- app/views/play/level_view.coffee | 5 ----- server/commons/Handler.coffee | 8 ++++---- 6 files changed, 29 insertions(+), 17 deletions(-) create mode 100644 app/collections/ThangNamesCollection.coffee diff --git a/app/collections/ThangNamesCollection.coffee b/app/collections/ThangNamesCollection.coffee new file mode 100644 index 000000000..1897d8b12 --- /dev/null +++ b/app/collections/ThangNamesCollection.coffee @@ -0,0 +1,11 @@ +ThangType = require 'models/ThangType' +CocoCollection = require 'collections/CocoCollection' + +module.exports = class ThangNamesCollection extends CocoCollection + url: '/db/thang.type/names' + model: ThangType + isCachable: false + + constructor: (ids) -> + console.log 'data', {type:'POST', data:{ids:ids}} + super([], {type:'POST', data:{ids:ids}}) diff --git a/app/lib/LevelLoader.coffee b/app/lib/LevelLoader.coffee index 5063982d9..4404acb17 100644 --- a/app/lib/LevelLoader.coffee +++ b/app/lib/LevelLoader.coffee @@ -4,6 +4,7 @@ LevelSystem = require 'models/LevelSystem' Article = require 'models/Article' LevelSession = require 'models/LevelSession' ThangType = require 'models/ThangType' +ThangNamesCollection = require 'collections/ThangNamesCollection' CocoClass = require 'lib/CocoClass' AudioPlayer = require 'lib/AudioPlayer' @@ -21,6 +22,7 @@ World = require 'lib/world/world' module.exports = class LevelLoader extends CocoClass constructor: (options) -> + @t0 = new Date().getTime() super() @supermodel = options.supermodel @levelID = options.levelID @@ -98,12 +100,15 @@ module.exports = class LevelLoader extends CocoClass articleVersions.push _.pick(article, ['original', 'majorVersion']) objUniq = (array) -> _.uniq array, false, (arg) -> JSON.stringify(arg) - - for thangID in _.uniq thangIDs - url = "/db/thang.type/#{thangID}/version" - url += "?project=true" if @headless and not @editorMode - res = @maybeLoadURL url, ThangType, 'thang' - @listenToOnce res.model, 'sync', @buildSpriteSheetsForThangType if res + + thangNames = new ThangNamesCollection(thangIDs) + @supermodel.loadCollection thangNames, 'thang_names' +# for thangID in _.uniq thangIDs +# url = "/db/thang.type/#{thangID}/version" +# url += "?project=true" if @headless and not @editorMode +# res = @maybeLoadURL url, ThangType, 'thang' +# @listenToOnce res.model, 'sync', @buildSpriteSheetsForThangType if res + for obj in objUniq componentVersions url = "/db/level.component/#{obj.original}/version/#{obj.majorVersion}" @maybeLoadURL url, LevelComponent, 'component' @@ -192,6 +197,7 @@ module.exports = class LevelLoader extends CocoClass @world = new World @level.get('name') serializedLevel = @level.serialize(@supermodel) @world.loadFromLevel serializedLevel, false + console.log 'INITIALIZED!', (new Date().getTime() - @t0) / 1000 # Initial Sound Loading diff --git a/app/locale/en.coffee b/app/locale/en.coffee index 0f2194e8d..3a4b332a7 100644 --- a/app/locale/en.coffee +++ b/app/locale/en.coffee @@ -695,5 +695,6 @@ opponent_session: "Opponent Session" article: "Article" user_names: "User Names" + thang_names: "Thang Names" files: "Files" top_simulators: "Top Simulators" diff --git a/app/models/SuperModel.coffee b/app/models/SuperModel.coffee index 4a296d4a6..0ca6fd7fd 100644 --- a/app/models/SuperModel.coffee +++ b/app/models/SuperModel.coffee @@ -80,7 +80,6 @@ module.exports = class SuperModel extends Backbone.Model return _.values @models registerModel: (model) -> - console.log 'register model?', model, model.getURL @models[model.getURL()] = model getCollection: (collection) -> @@ -94,7 +93,7 @@ module.exports = class SuperModel extends Backbone.Model @registerCollection(collection) registerCollection: (collection) -> - @collections[collection.getURL()] = collection + @collections[collection.getURL()] = collection if collection.isCachable # consolidate models for model, i in collection.models cachedModel = @getModelByURL(model.getURL()) diff --git a/app/views/play/level_view.coffee b/app/views/play/level_view.coffee index 0118b5242..3d976e032 100644 --- a/app/views/play/level_view.coffee +++ b/app/views/play/level_view.coffee @@ -83,7 +83,6 @@ module.exports = class PlayLevelView extends View @sessionID = @getQueryVariable 'session' $(window).on('resize', @onWindowResize) - @listenToOnce(@supermodel, 'error', @onLevelLoadError) @saveScreenshot = _.throttle @saveScreenshot, 30000 if @isEditorPreview @@ -96,10 +95,6 @@ module.exports = class PlayLevelView extends View @load() application.tracker?.trackEvent 'Started Level Load', level: @levelID, label: @levelID - onLevelLoadError: (e) -> - # TODO NOW: remove this in favor of the supermodel handling it - application.router.navigate "/play?not_found=#{@levelID}", {trigger: true} - setLevel: (@level, @supermodel) -> @god?.level = @level.serialize @supermodel if @world diff --git a/server/commons/Handler.coffee b/server/commons/Handler.coffee index b4590b2f0..e3b7911d8 100644 --- a/server/commons/Handler.coffee +++ b/server/commons/Handler.coffee @@ -112,7 +112,7 @@ module.exports = class Handler ids = ids.split(',') if _.isString ids ids = _.uniq ids - project = {name:1} + project = {name:1, original:1} sort = {'version.major':-1, 'version.minor':-1} makeFunc = (id) => @@ -120,8 +120,8 @@ module.exports = class Handler criteria = {original:mongoose.Types.ObjectId(id)} @modelClass.findOne(criteria, project).sort(sort).exec (err, document) -> return done(err) if err - callback(null, document?.toObject() or {}) - + callback(null, document?.toObject() or null) + funcs = {} for id in ids return errors.badInput(res, "Given an invalid id: #{id}") unless Handler.isID(id) @@ -129,7 +129,7 @@ module.exports = class Handler async.parallel funcs, (err, results) -> return errors.serverError err if err - res.send results + res.send (d for d in _.values(results) when d) res.end() getPatchesFor: (req, res, id) -> From 2952d7702c202ed7ba61fd678b2622a476213645 Mon Sep 17 00:00:00 2001 From: Scott Erickson <sderickson@gmail.com> Date: Fri, 2 May 2014 08:20:03 -0700 Subject: [PATCH 02/12] Merge branch 'master' into feature/thangload Conflicts: app/lib/LevelLoader.coffee app/views/play/level_view.coffee --- app/lib/LevelLoader.coffee | 9 --------- app/views/play/level_view.coffee | 5 ----- 2 files changed, 14 deletions(-) diff --git a/app/lib/LevelLoader.coffee b/app/lib/LevelLoader.coffee index edd8f8881..26c743925 100644 --- a/app/lib/LevelLoader.coffee +++ b/app/lib/LevelLoader.coffee @@ -105,7 +105,6 @@ module.exports = class LevelLoader extends CocoClass objUniq = (array) -> _.uniq array, false, (arg) -> JSON.stringify(arg) -<<<<<<< HEAD thangNames = new ThangNamesCollection(thangIDs) @supermodel.loadCollection thangNames, 'thang_names' # for thangID in _.uniq thangIDs @@ -113,14 +112,6 @@ module.exports = class LevelLoader extends CocoClass # url += "?project=true" if @headless and not @editorMode # res = @maybeLoadURL url, ThangType, 'thang' # @listenToOnce res.model, 'sync', @buildSpriteSheetsForThangType if res - -======= - for thangID in _.uniq thangIDs - url = "/db/thang.type/#{thangID}/version" - url += "?project=true" if @headless and not @editorMode - res = @maybeLoadURL url, ThangType, 'thang' - @listenToOnce res.model, 'sync', @buildSpriteSheetsForThangType if res ->>>>>>> master for obj in objUniq componentVersions url = "/db/level.component/#{obj.original}/version/#{obj.majorVersion}" @maybeLoadURL url, LevelComponent, 'component' diff --git a/app/views/play/level_view.coffee b/app/views/play/level_view.coffee index cae22214c..5fd97af78 100644 --- a/app/views/play/level_view.coffee +++ b/app/views/play/level_view.coffee @@ -93,9 +93,6 @@ module.exports = class PlayLevelView extends View @load() application.tracker?.trackEvent 'Started Level Load', level: @levelID, label: @levelID -<<<<<<< HEAD - setLevel: (@level, @supermodel) -> -======= onLevelLoadError: (e) -> # TODO NOW: remove this in favor of the supermodel handling it application.router.navigate "/play?not_found=#{@levelID}", {trigger: true} @@ -104,8 +101,6 @@ module.exports = class PlayLevelView extends View @supermodel.models = givenSupermodel.models @supermodel.collections = givenSupermodel.collections @supermodel.shouldSaveBackups = givenSupermodel.shouldSaveBackups - ->>>>>>> master @god?.level = @level.serialize @supermodel if @world serializedLevel = @level.serialize(@supermodel) From 06bc96d7d045fa84fe3164a252e7953f7cf5a835 Mon Sep 17 00:00:00 2001 From: Scott Erickson <sderickson@gmail.com> Date: Fri, 2 May 2014 10:31:20 -0700 Subject: [PATCH 03/12] Set up the level loader to get thangtype names first, then load the thang types themselves so the world can be generated in parallel with thang type loading and rendering. --- app/collections/CocoCollection.coffee | 7 ++++- app/collections/ThangNamesCollection.coffee | 9 ++++--- app/lib/LevelLoader.coffee | 30 +++++++++++++-------- app/lib/surface/SpriteBoss.coffee | 4 +-- app/models/CocoModel.coffee | 6 +++-- app/models/SuperModel.coffee | 1 + 6 files changed, 38 insertions(+), 19 deletions(-) diff --git a/app/collections/CocoCollection.coffee b/app/collections/CocoCollection.coffee index 64074a327..817b0700f 100644 --- a/app/collections/CocoCollection.coffee +++ b/app/collections/CocoCollection.coffee @@ -8,4 +8,9 @@ module.exports = class CocoCollection extends Backbone.Collection model.loaded = true for model in @models getURL: -> - return if _.isString @url then @url else @url() \ No newline at end of file + return if _.isString @url then @url else @url() + + fetch: -> + @jqxhr = super(arguments...) + @loading = true + @jqxhr diff --git a/app/collections/ThangNamesCollection.coffee b/app/collections/ThangNamesCollection.coffee index 1897d8b12..414628d84 100644 --- a/app/collections/ThangNamesCollection.coffee +++ b/app/collections/ThangNamesCollection.coffee @@ -6,6 +6,9 @@ module.exports = class ThangNamesCollection extends CocoCollection model: ThangType isCachable: false - constructor: (ids) -> - console.log 'data', {type:'POST', data:{ids:ids}} - super([], {type:'POST', data:{ids:ids}}) + constructor: (@ids) -> super() + + fetch: (options) -> + options ?= {} + _.extend options, {type:'POST', data:{ids:@ids}} + super(options) diff --git a/app/lib/LevelLoader.coffee b/app/lib/LevelLoader.coffee index 26c743925..074d8c76d 100644 --- a/app/lib/LevelLoader.coffee +++ b/app/lib/LevelLoader.coffee @@ -105,19 +105,18 @@ module.exports = class LevelLoader extends CocoClass objUniq = (array) -> _.uniq array, false, (arg) -> JSON.stringify(arg) - thangNames = new ThangNamesCollection(thangIDs) - @supermodel.loadCollection thangNames, 'thang_names' -# for thangID in _.uniq thangIDs -# url = "/db/thang.type/#{thangID}/version" -# url += "?project=true" if @headless and not @editorMode -# res = @maybeLoadURL url, ThangType, 'thang' -# @listenToOnce res.model, 'sync', @buildSpriteSheetsForThangType if res + worldNecessities = [] + + @thangIDs = _.uniq thangIDs + thangNames = new ThangNamesCollection(@thangIDs) + worldNecessities.push @supermodel.loadCollection(thangNames, 'thang_names') + for obj in objUniq componentVersions url = "/db/level.component/#{obj.original}/version/#{obj.majorVersion}" - @maybeLoadURL url, LevelComponent, 'component' + worldNecessities.push @maybeLoadURL(url, LevelComponent, 'component') for obj in objUniq systemVersions url = "/db/level.system/#{obj.original}/version/#{obj.majorVersion}" - @maybeLoadURL url, LevelSystem, 'system' + worldNecessities.push @maybeLoadURL(url, LevelSystem, 'system') for obj in objUniq articleVersions url = "/db/article/#{obj.original}/version/#{obj.majorVersion}" @maybeLoadURL url, Article, 'article' @@ -128,6 +127,17 @@ module.exports = class LevelLoader extends CocoClass unless @headless and not @editorMode wizard = ThangType.loadUniversalWizard() @supermodel.loadModel wizard, 'thang' + + jqxhrs = (resource.jqxhr for resource in worldNecessities when resource?.jqxhr) + $.when(jqxhrs...).done(@onWorldNecessitiesLoaded) + + onWorldNecessitiesLoaded: => + @initWorld() + for thangID in @thangIDs + url = "/db/thang.type/#{thangID}/version" + url += "?project=true" if @headless and not @editorMode + res = @maybeLoadURL url, ThangType, 'thang' + @listenToOnce res.model, 'sync', @buildSpriteSheetsForThangType if res maybeLoadURL: (url, Model, resourceName) -> return if @supermodel.getModel(url) @@ -138,7 +148,6 @@ module.exports = class LevelLoader extends CocoClass @loadLevelSounds() @denormalizeSession() app.tracker.updatePlayState(@level, @session) unless @headless - @initWorld() denormalizeSession: -> return if @headless or @sessionDenormalized or @spectateMode @@ -201,7 +210,6 @@ module.exports = class LevelLoader extends CocoClass @world = new World @level.get('name') serializedLevel = @level.serialize(@supermodel) @world.loadFromLevel serializedLevel, false - console.log 'INITIALIZED!', (new Date().getTime() - @t0) / 1000 # Initial Sound Loading diff --git a/app/lib/surface/SpriteBoss.coffee b/app/lib/surface/SpriteBoss.coffee index 03e05e0c3..1eb7aa8b7 100644 --- a/app/lib/surface/SpriteBoss.coffee +++ b/app/lib/surface/SpriteBoss.coffee @@ -48,7 +48,7 @@ module.exports = class SpriteBoss extends CocoClass toString: -> "<SpriteBoss: #{@spriteArray.length} sprites>" thangTypeFor: (type) -> - _.find @options.thangTypes, (m) -> m.get('original') is type or m.get('name') is type + _.find @options.thangTypes, (m) -> m.get('actions') and m.get('original') is type or m.get('name') is type createLayers: -> @spriteLayers = {} @@ -145,7 +145,7 @@ module.exports = class SpriteBoss extends CocoClass addThangToSprites: (thang, layer=null) -> return console.warn 'Tried to add Thang to the surface it already has:', thang.id if @sprites[thang.id] - thangType = _.find @options.thangTypes, (m) -> m.get('name') is thang.spriteName + thangType = _.find @options.thangTypes, (m) -> m.get('actions') and m.get('name') is thang.spriteName options = @createSpriteOptions thang: thang options.resolutionFactor = if thangType.get('kind') is 'Floor' then 2 else 4 sprite = new CocoSprite thangType, options diff --git a/app/models/CocoModel.coffee b/app/models/CocoModel.coffee index a7d408adf..ab2737ad4 100644 --- a/app/models/CocoModel.coffee +++ b/app/models/CocoModel.coffee @@ -36,6 +36,8 @@ class CocoModel extends Backbone.Model @loading = false @markToRevert() @loadFromBackup() + + getNormalizedURL: -> "#{@urlRoot}/#{@id}" set: -> res = super(arguments...) @@ -75,9 +77,9 @@ class CocoModel extends Backbone.Model return super attrs, options fetch: -> - res = super(arguments...) + @jqxhr = super(arguments...) @loading = true - res + @jqxhr markToRevert: -> if @type() is 'ThangType' diff --git a/app/models/SuperModel.coffee b/app/models/SuperModel.coffee index 14f08ef11..f2bc57de4 100644 --- a/app/models/SuperModel.coffee +++ b/app/models/SuperModel.coffee @@ -199,6 +199,7 @@ class ModelResource extends Resource super(name, value) @model = modelOrCollection @fetchOptions = fetchOptions + @jqxhr = @model.jqxhr load: -> @markLoading() From d54149d0f31e302ac5ec94c3bc1cbc0495d0fa09 Mon Sep 17 00:00:00 2001 From: Nick Winter <livelily@gmail.com> Date: Fri, 2 May 2014 12:32:41 -0700 Subject: [PATCH 04/12] Reworked LevelView loading priorities to try to get world simulating earlier. --- .../javascripts/workers/aether_worker.js | 4 +- app/lib/God.coffee | 7 ++-- app/lib/LevelLoader.coffee | 5 ++- app/locale/en.coffee | 1 + app/styles/editor/patches.sass | 3 ++ app/views/editor/patch_modal.coffee | 18 ++++----- app/views/editor/patches_view.coffee | 14 +++---- app/views/play/level/tome/spell_view.coffee | 2 +- app/views/play/level_view.coffee | 37 +++++++++++-------- 9 files changed, 50 insertions(+), 41 deletions(-) diff --git a/app/assets/javascripts/workers/aether_worker.js b/app/assets/javascripts/workers/aether_worker.js index 9fbad8155..4bda39f6a 100644 --- a/app/assets/javascripts/workers/aether_worker.js +++ b/app/assets/javascripts/workers/aether_worker.js @@ -2,7 +2,7 @@ var window = self; var Global = self; importScripts("/javascripts/tome_aether.js"); -console.log("imported scripts!"); +console.log("Aether Tome worker has finished importing scripts."); var aethers = {}; var createAether = function (spellKey, options) @@ -96,4 +96,4 @@ self.addEventListener('message', function(e) { var returnObject = {"message":message, "function":"none"}; self.postMessage(JSON.stringify(returnObject)); } -}, false); \ No newline at end of file +}, false); diff --git a/app/lib/God.coffee b/app/lib/God.coffee index 6fe758391..7a71d717f 100644 --- a/app/lib/God.coffee +++ b/app/lib/God.coffee @@ -17,11 +17,11 @@ module.exports = class God @id = God.nextID() options ?= {} @maxAngels = options.maxAngels ? 2 # How many concurrent web workers to use; if set past 8, make up more names - @maxWorkerPoolSize = options.maxWorkerPoolSize ? 2 # ~20MB per idle worker + @maxWorkerPoolSize = options.maxWorkerPoolSize ? 1 # ~20MB per idle worker @angels = [] @firstWorld = true Backbone.Mediator.subscribe 'tome:cast-spells', @onTomeCast, @ - @fillWorkerPool = _.throttle @fillWorkerPool, 3000, leading: false + @fillWorkerPool = _.throttle @fillWorkerPool, 3000 @fillWorkerPool() onTomeCast: (e) -> @@ -87,7 +87,7 @@ module.exports = class God Backbone.Mediator.publish 'god:user-code-problem', problem: problem createWorld: -> - #console.log @id + ': "Let there be light upon', @world.name + '!"' + console.log @id + ': "Let there be light upon', @level.name + '!"' unless Worker? # profiling world simulation is easier on main thread, or we are IE9 setTimeout @simulateWorld, 1 return @@ -98,7 +98,6 @@ module.exports = class God else @worldWaiting = true return - #console.log "going to run world with code", @getUserCodeMap() angel.worker.postMessage {func: 'runWorld', args: { worldName: @level.name userCodeMap: @getUserCodeMap() diff --git a/app/lib/LevelLoader.coffee b/app/lib/LevelLoader.coffee index 074d8c76d..c709d6814 100644 --- a/app/lib/LevelLoader.coffee +++ b/app/lib/LevelLoader.coffee @@ -110,7 +110,7 @@ module.exports = class LevelLoader extends CocoClass @thangIDs = _.uniq thangIDs thangNames = new ThangNamesCollection(@thangIDs) worldNecessities.push @supermodel.loadCollection(thangNames, 'thang_names') - + for obj in objUniq componentVersions url = "/db/level.component/#{obj.original}/version/#{obj.majorVersion}" worldNecessities.push @maybeLoadURL(url, LevelComponent, 'component') @@ -127,7 +127,7 @@ module.exports = class LevelLoader extends CocoClass unless @headless and not @editorMode wizard = ThangType.loadUniversalWizard() @supermodel.loadModel wizard, 'thang' - + jqxhrs = (resource.jqxhr for resource in worldNecessities when resource?.jqxhr) $.when(jqxhrs...).done(@onWorldNecessitiesLoaded) @@ -210,6 +210,7 @@ module.exports = class LevelLoader extends CocoClass @world = new World @level.get('name') serializedLevel = @level.serialize(@supermodel) @world.loadFromLevel serializedLevel, false + console.log "World has been initialized from level loader." # Initial Sound Loading diff --git a/app/locale/en.coffee b/app/locale/en.coffee index 3a4b332a7..9b3469b9c 100644 --- a/app/locale/en.coffee +++ b/app/locale/en.coffee @@ -685,6 +685,7 @@ user_schema: "User Schema" user_profile: "User Profile" patches: "Patches" + patched_model: "Source Document" model: "Model" system: "System" component: "Component" diff --git a/app/styles/editor/patches.sass b/app/styles/editor/patches.sass index 110370137..f4130ec5a 100644 --- a/app/styles/editor/patches.sass +++ b/app/styles/editor/patches.sass @@ -1,3 +1,6 @@ .patches-view .status-buttons margin-bottom: 10px + + .patch-icon + cursor: pointer diff --git a/app/views/editor/patch_modal.coffee b/app/views/editor/patch_modal.coffee index f5dc339cf..6436ce943 100644 --- a/app/views/editor/patch_modal.coffee +++ b/app/views/editor/patch_modal.coffee @@ -8,7 +8,7 @@ module.exports = class PatchModal extends ModalView template: template plain: true modalWidthPercent: 60 - + events: 'click #withdraw-button': 'withdrawPatch' 'click #reject-button': 'rejectPatch' @@ -24,8 +24,8 @@ module.exports = class PatchModal extends ModalView @originalSource = new targetModel.constructor({_id:targetID}) @originalSource.fetch() @listenToOnce @originalSource, 'sync', @onOriginalLoaded - @addResourceToLoad(@originalSource) - + @supermodel.addModelResource @originalSource, 'patched_model' # TODO: Doesn't work? + getRenderData: -> c = super() c.isPatchCreator = @patch.get('creator') is auth.me.id @@ -33,14 +33,14 @@ module.exports = class PatchModal extends ModalView c.status = @patch.get 'status' c.patch = @patch c - + afterRender: -> return if @originalSource.loading headModel = null if @targetModel.hasWriteAccess() headModel = @originalSource.clone(false) headModel.set(@targetModel.attributes) - + pendingModel = @originalSource.clone(false) pendingModel.applyDelta(@patch.get('delta')) @@ -48,18 +48,18 @@ module.exports = class PatchModal extends ModalView changeEl = @$el.find('.changes-stub') @insertSubView(@deltaView, changeEl) super() - + acceptPatch: -> delta = @deltaView.getApplicableDelta() @targetModel.applyDelta(delta) @patch.setStatus('accepted') @trigger 'accepted-patch' @hide() - + rejectPatch: -> @patch.setStatus('rejected') @hide() - + withdrawPatch: -> @patch.setStatus('withdrawn') - @hide() \ No newline at end of file + @hide() diff --git a/app/views/editor/patches_view.coffee b/app/views/editor/patches_view.coffee index 1c005df16..17aef667b 100644 --- a/app/views/editor/patches_view.coffee +++ b/app/views/editor/patches_view.coffee @@ -8,7 +8,7 @@ module.exports = class PatchesView extends CocoView template: template className: 'patches-view' status: 'pending' - + events: 'change .status-buttons': 'onStatusButtonsChanged' 'click .patch-icon': 'openPatchModal' @@ -16,17 +16,16 @@ module.exports = class PatchesView extends CocoView constructor: (@model, options) -> super(options) @initPatches() - + initPatches: -> @startedLoading = false @patches = new PatchesCollection([], {}, @model, @status) - + load: -> - console.log 'load patches view?' @initPatches() @patches = @supermodel.loadCollection(@patches, 'patches').model @listenTo @patches, 'sync', @onPatchesLoaded - + onPatchesLoaded: -> ids = (p.get('creator') for p in @patches.models) jqxhrOptions = nameLoader.loadNames ids @@ -38,19 +37,20 @@ module.exports = class PatchesView extends CocoView c.patches = @patches.models c.status c - + afterRender: -> @$el.find(".#{@status}").addClass 'active' onStatusButtonsChanged: (e) -> @status = $(e.target).val() @reloadPatches() - + reloadPatches: -> @load() @render() openPatchModal: (e) -> + console.log "open patch modal" patch = _.find @patches.models, {id:$(e.target).data('patch-id')} modal = new PatchModal(patch, @model) @openModalView(modal) diff --git a/app/views/play/level/tome/spell_view.coffee b/app/views/play/level/tome/spell_view.coffee index 1e2f5ed3a..ace34a266 100644 --- a/app/views/play/level/tome/spell_view.coffee +++ b/app/views/play/level/tome/spell_view.coffee @@ -64,7 +64,7 @@ module.exports = class SpellView extends View @createFirepad() else # needs to happen after the code generating this view is complete - setTimeout @onAllLoaded, 1 + _.defer @onAllLoaded createACE: -> # Test themes and settings here: http://ace.ajax.org/build/kitchen-sink.html diff --git a/app/views/play/level_view.coffee b/app/views/play/level_view.coffee index 5fd97af78..56e219ec3 100644 --- a/app/views/play/level_view.coffee +++ b/app/views/play/level_view.coffee @@ -110,8 +110,8 @@ module.exports = class PlayLevelView extends View load: -> @loadStartTime = new Date() - @levelLoader = new LevelLoader supermodel: @supermodel, levelID: @levelID, sessionID: @sessionID, opponentSessionID: @getQueryVariable('opponent'), team: @getQueryVariable("team") @god = new God() + @levelLoader = new LevelLoader supermodel: @supermodel, levelID: @levelID, sessionID: @sessionID, opponentSessionID: @getQueryVariable('opponent'), team: @getQueryVariable("team") getRenderData: -> c = super() @@ -131,6 +131,9 @@ module.exports = class PlayLevelView extends View updateProgress: (progress) -> super(progress) + if not @worldInitialized and @levelLoader.session.loaded and @levelLoader.level.loaded and @levelLoader.world and (not @levelLoader.opponentSession or @levelLoader.opponentSession.loaded) + @grabLevelLoaderData() + @onWorldInitialized() return if @seenDocs return unless @levelLoader.session.loaded and @levelLoader.level.loaded return unless showFrequency = @levelLoader.level.get('showsGuide') @@ -142,6 +145,21 @@ module.exports = class PlayLevelView extends View return unless article.loaded @showGuide() + onWorldInitialized: -> + @worldInitialized = true + team = @getQueryVariable("team") ? @world.teamForPlayer(0) + @loadOpponentTeam(team) + @god.level = @level.serialize @supermodel + @god.worldClassMap = @world.classMap + @setTeam team + @initGoalManager() + @insertSubviews ladderGame: (@level.get('type') is "ladder") + @initVolume() + @listenTo(@session, 'change:multiplayer', @onMultiplayerChanged) + @originalSessionState = $.extend(true, {}, @session.get('state')) + @register() + @controlBar.setBus(@bus) + showGuide: -> @seenDocs = true DocsModal = require './level/modal/docs_modal' @@ -163,21 +181,10 @@ module.exports = class PlayLevelView extends View if not (@levelLoader.level.get('type') in ['ladder', 'ladder-tutorial']) me.set('lastLevel', @levelID) me.save() - @grabLevelLoaderData() - team = @getQueryVariable("team") ? @world.teamForPlayer(0) - @loadOpponentTeam(team) - @god.level = @level.serialize @supermodel - @god.worldClassMap = @world.classMap - @setTeam team + @levelLoader.destroy() + @levelLoader = null @initSurface() - @initGoalManager() @initScriptManager() - @insertSubviews ladderGame: (@level.get('type') is "ladder") - @initVolume() - @listenTo(@session, 'change:multiplayer', @onMultiplayerChanged) - @originalSessionState = $.extend(true, {}, @session.get('state')) - @register() - @controlBar.setBus(@bus) @surface.showLevel() if @otherSession # TODO: colorize name and cloud by team, colorize wizard by user's color config @@ -188,8 +195,6 @@ module.exports = class PlayLevelView extends View @world = @levelLoader.world @level = @levelLoader.level @otherSession = @levelLoader.opponentSession - @levelLoader.destroy() - @levelLoader = null loadOpponentTeam: (myTeam) -> opponentSpells = [] From aee90c72bb9e152c2b3a09b7694d87eeaf7e3d72 Mon Sep 17 00:00:00 2001 From: Scott Erickson <sderickson@gmail.com> Date: Fri, 2 May 2014 17:03:30 -0700 Subject: [PATCH 05/12] Most of the way there with the raster images. Just need to figure out loading with PreloadJS and the SuperModel. --- app/lib/LevelLoader.coffee | 4 ++ app/lib/surface/CocoSprite.coffee | 50 +++++++++++++++---- app/lib/surface/SpriteBoss.coffee | 10 ++-- app/models/ThangType.coffee | 4 +- app/schemas/models/thang_type.coffee | 1 + app/views/editor/thang/edit.coffee | 28 ++++++++++- app/views/play/level/hud_view.coffee | 15 +++--- .../levels/thangs/thang_type_handler.coffee | 29 +++++------ 8 files changed, 104 insertions(+), 37 deletions(-) diff --git a/app/lib/LevelLoader.coffee b/app/lib/LevelLoader.coffee index 074d8c76d..e7ee8d423 100644 --- a/app/lib/LevelLoader.coffee +++ b/app/lib/LevelLoader.coffee @@ -168,6 +168,10 @@ module.exports = class LevelLoader extends CocoClass # Building sprite sheets buildSpriteSheetsForThangType: (thangType) -> + # TODO: Finish making sure the supermodel loads the raster image before triggering load complete, and that the cocosprite has access to the asset. +# if f = thangType.get('raster') +# queue = new createjs.LoadQueue() +# queue.loadFile('/file/'+f) @grabThangTypeTeams() unless @thangTypeTeams for team in @thangTypeTeams[thangType.get('original')] ? [null] spriteOptions = {resolutionFactor: 4, async: false} diff --git a/app/lib/surface/CocoSprite.coffee b/app/lib/surface/CocoSprite.coffee index 55a1982e4..8c5a4de7d 100644 --- a/app/lib/surface/CocoSprite.coffee +++ b/app/lib/surface/CocoSprite.coffee @@ -70,7 +70,7 @@ module.exports = CocoSprite = class CocoSprite extends CocoClass @age = 0 @scaleFactor = @targetScaleFactor = 1 @displayObject = new createjs.Container() - if @thangType.get('actions') + if @thangType.get('actions') or @thangType.get('raster') @setupSprite() else @stillLoading = true @@ -79,9 +79,29 @@ module.exports = CocoSprite = class CocoSprite extends CocoClass setupSprite: -> @stillLoading = false - @actions = @thangType.getActions() - @buildFromSpriteSheet @buildSpriteSheet() - @createMarks() + if @thangType.get('raster') + @isRaster = true + @setUpRasterImage() + @actions = {} + else + @actions = @thangType.getActions() + @buildFromSpriteSheet @buildSpriteSheet() + @createMarks() + + setUpRasterImage: -> + raster = @thangType.get('raster') + sprite = @imageObject = new createjs.Bitmap('/file/'+raster) + @displayObject.addChild(sprite) + @configureMouse() + @originalScaleX = sprite.scaleX + @originalScaleY = sprite.scaleY + @displayObject.sprite = @ + @displayObject.layerPriority = @thangType.get 'layerPriority' + @displayObject.name = @thang?.spriteName or @thangType.get 'name' + reg = @getOffset 'registration' + @imageObject.regX = -reg.x + @imageObject.regY = -reg.y + @updateScale() destroy: -> mark.destroy() for name, mark of @marks @@ -143,6 +163,7 @@ module.exports = CocoSprite = class CocoSprite extends CocoClass @playAction(@actionQueue.splice(0,1)[0]) if @actionQueue.length playAction: (action) -> + return if @isRaster @currentAction = action return @hide() unless action.animation or action.container or action.relatedActions @show() @@ -244,13 +265,14 @@ module.exports = CocoSprite = class CocoSprite extends CocoClass updateScale: -> if @thangType.get('matchWorldDimensions') and @thang if @thang.width isnt @lastThangWidth or @thang.height isnt @lastThangHeight - [@lastThangWidth, @lastThangHeight] = [@thang.width, @thang.height] bounds = @imageObject.getBounds() + return unless bounds # TODO: remove this because it's a bandaid over the image sometimes not being loaded @imageObject.scaleX = @thang.width * Camera.PPM / bounds.width @imageObject.scaleY = @thang.height * Camera.PPM * @options.camera.y2x / bounds.height unless @thang.spriteName is 'Beam' @imageObject.scaleX *= @thangType.get('scale') ? 1 @imageObject.scaleY *= @thangType.get('scale') ? 1 + [@lastThangWidth, @lastThangHeight] = [@thang.width, @thang.height] return scaleX = if @getActionProp 'flipX' then -1 else 1 scaleY = if @getActionProp 'flipY' then -1 else 1 @@ -267,6 +289,12 @@ module.exports = CocoSprite = class CocoSprite extends CocoClass angle = -angle if angle < 0 angle = 180 - angle if angle > 90 scaleX = 0.5 + 0.5 * (90 - angle) / 90 + + if @isRaster # scale is worked into building the sprite sheet for animations + scale = @thangType.get('scale') or 1 + scaleX *= scale + scaleY *= scale + scaleFactorX = @thang.scaleFactorX ? @scaleFactor scaleFactorY = @thang.scaleFactorY ? @scaleFactor @imageObject.scaleX = @originalScaleX * scaleX * scaleFactorX @@ -319,6 +347,7 @@ module.exports = CocoSprite = class CocoSprite extends CocoClass ################################################## updateAction: -> + return if @isRaster action = @determineAction() isDifferent = action isnt @currentRootAction or action is null if not action and @thang?.actionActivated and not @stopLogging @@ -440,10 +469,11 @@ module.exports = CocoSprite = class CocoSprite extends CocoClass def = x: 0, y: {registration: 0, torso: -50, mouth: -60, aboveHead: -100}[prop] pos = @getActionProp 'positions', prop, def pos = x: pos.x, y: pos.y - scale = @getActionProp 'scale', null, 1 - scale *= @options.resolutionFactor if prop is 'registration' - pos.x *= scale - pos.y *= scale + if not @isRaster + scale = @getActionProp 'scale', null, 1 + scale *= @options.resolutionFactor if prop is 'registration' + pos.x *= scale + pos.y *= scale if @thang and prop isnt 'registration' scaleFactor = @thang.scaleFactor ? 1 pos.x *= @thang.scaleFactorX ? scaleFactor @@ -654,7 +684,7 @@ module.exports = CocoSprite = class CocoSprite extends CocoClass z = @shadow.pos.z @shadow.pos = pos @shadow.pos.z = z - @imageObject.gotoAndPlay(endAnimation) + @imageObject.gotoAndPlay?(endAnimation) return @shadow.action = 'move' diff --git a/app/lib/surface/SpriteBoss.coffee b/app/lib/surface/SpriteBoss.coffee index 1eb7aa8b7..63df6c789 100644 --- a/app/lib/surface/SpriteBoss.coffee +++ b/app/lib/surface/SpriteBoss.coffee @@ -145,7 +145,11 @@ module.exports = class SpriteBoss extends CocoClass addThangToSprites: (thang, layer=null) -> return console.warn 'Tried to add Thang to the surface it already has:', thang.id if @sprites[thang.id] - thangType = _.find @options.thangTypes, (m) -> m.get('actions') and m.get('name') is thang.spriteName + thangType = _.find @options.thangTypes, (m) -> + return false unless m.get('actions') or m.get('raster') + return m.get('name') is thang.spriteName + thangType ?= _.find @options.thangTypes, (m) -> return m.get('name') is thang.spriteName + options = @createSpriteOptions thang: thang options.resolutionFactor = if thangType.get('kind') is 'Floor' then 2 else 4 sprite = new CocoSprite thangType, options @@ -221,12 +225,12 @@ module.exports = class SpriteBoss extends CocoClass onCastSpells: -> @stop() play: -> - sprite.imageObject.play() for sprite in @spriteArray + sprite.imageObject.play?() for sprite in @spriteArray @selectionMark?.play() @targetMark?.play() stop: -> - sprite.imageObject.stop() for sprite in @spriteArray + sprite.imageObject.stop?() for sprite in @spriteArray @selectionMark?.stop() @targetMark?.stop() diff --git a/app/models/ThangType.coffee b/app/models/ThangType.coffee index 21cd1fadd..321f47990 100644 --- a/app/models/ThangType.coffee +++ b/app/models/ThangType.coffee @@ -210,8 +210,8 @@ module.exports = class ThangType extends CocoModel @tick = null stage - uploadGenericPortrait: (callback) -> - src = @getPortraitSource() + uploadGenericPortrait: (callback, src) -> + src ?= @getPortraitSource() return callback?() unless src src = src.replace('data:image/png;base64,', '').replace(/\ /g, '+') body = diff --git a/app/schemas/models/thang_type.coffee b/app/schemas/models/thang_type.coffee index eb78c1c11..37624c180 100644 --- a/app/schemas/models/thang_type.coffee +++ b/app/schemas/models/thang_type.coffee @@ -123,6 +123,7 @@ _.extend ThangTypeSchema.properties, title: 'Scale' type: 'number' positions: PositionsSchema + raster: { type: 'string', format: 'image-file', title: 'Raster Image' } colorGroups: c.object title: 'Color Groups' additionalProperties: diff --git a/app/views/editor/thang/edit.coffee b/app/views/editor/thang/edit.coffee index 4d1cc71e0..e728e438c 100644 --- a/app/views/editor/thang/edit.coffee +++ b/app/views/editor/thang/edit.coffee @@ -197,6 +197,7 @@ module.exports = class ThangTypeEditView extends View # animation select refreshAnimation: -> + return @showRasterImage() if @thangType.get('raster') options = @getSpriteOptions() @thangType.resetSpriteSheetCache() spriteSheet = @thangType.buildSpriteSheet(options) @@ -207,6 +208,13 @@ module.exports = class ThangTypeEditView extends View @showAnimation() @updatePortrait() + showRasterImage: -> + sprite = new CocoSprite(@thangType, @getSpriteOptions()) + @currentSprite?.destroy() + @currentSprite = sprite + @showDisplayObject(sprite.displayObject) + @updateScale() + showAnimation: (animationName) -> animationName = @$el.find('#animations-select').val() unless _.isString animationName return unless animationName @@ -310,8 +318,13 @@ module.exports = class ThangTypeEditView extends View res.success => url = "/editor/thang/#{newThangType.get('slug') or newThangType.id}" - newThangType.uploadGenericPortrait -> - document.location.href = url + portraitSource = null + if @thangType.get('raster') + image = @currentSprite.imageObject.image + portraitSource = imageToPortrait image + # bit of a hacky way to get that portrait + success = -> document.location.href = url + newThangType.uploadGenericPortrait success, portraitSource clearRawData: -> @thangType.resetRawData() @@ -393,3 +406,14 @@ module.exports = class ThangTypeEditView extends View destroy: -> @camera?.destroy() super() + +imageToPortrait = (img) -> + canvas = document.createElement("canvas") + canvas.width = 100 + canvas.height = 100 + ctx = canvas.getContext("2d") + scaleX = 100 / img.width + scaleY = 100 / img.height + ctx.scale scaleX, scaleY + ctx.drawImage img, 0, 0 + canvas.toDataURL("image/png") \ No newline at end of file diff --git a/app/views/play/level/hud_view.coffee b/app/views/play/level/hud_view.coffee index 3b6f660af..70b2ffd73 100644 --- a/app/views/play/level/hud_view.coffee +++ b/app/views/play/level/hud_view.coffee @@ -112,16 +112,19 @@ module.exports = class HUDView extends View options = thang.getSpriteOptions() or {} options.async = false options.colorConfig = colorConfig if colorConfig - stage = thangType.getPortraitStage options wrapper = @$el.find '.thang-canvas-wrapper' - newCanvas = $(stage.canvas).addClass('thang-canvas') - wrapper.empty().append(newCanvas) team = @thang?.team or @speakerSprite?.thang?.team wrapper.removeClass (i, css) -> (css.match(/\bteam-\S+/g) or []).join ' ' wrapper.addClass "team-#{team}" - stage.update() - @stage?.stopTalking() - @stage = stage + if thangType.get('raster') + wrapper.empty().append($('<img />').attr('src', '/file/'+thangType.get('raster'))) + else + stage = thangType.getPortraitStage options + newCanvas = $(stage.canvas).addClass('thang-canvas') + wrapper.empty().append(newCanvas) + stage.update() + @stage?.stopTalking() + @stage = stage onThangBeganTalking: (e) -> return unless @stage and @thang is e.thang diff --git a/server/levels/thangs/thang_type_handler.coffee b/server/levels/thangs/thang_type_handler.coffee index abdecd529..a8d5c05e7 100644 --- a/server/levels/thangs/thang_type_handler.coffee +++ b/server/levels/thangs/thang_type_handler.coffee @@ -5,21 +5,22 @@ ThangTypeHandler = class ThangTypeHandler extends Handler modelClass: ThangType jsonSchema: require '../../../app/schemas/models/thang_type' editableProperties: [ - 'name', - 'raw', - 'actions', - 'soundTriggers', - 'rotationType', - 'matchWorldDimensions', - 'shadow', - 'layerPriority', - 'staticImage', - 'scale', - 'positions', - 'snap', - 'components', - 'colorGroups', + 'name' + 'raw' + 'actions' + 'soundTriggers' + 'rotationType' + 'matchWorldDimensions' + 'shadow' + 'layerPriority' + 'staticImage' + 'scale' + 'positions' + 'snap' + 'components' + 'colorGroups' 'kind' + 'raster' ] hasAccess: (req) -> From 73222a1a98d54d5080cf95621358fcc6d028dce1 Mon Sep 17 00:00:00 2001 From: Nick Winter <livelily@gmail.com> Date: Sat, 3 May 2014 09:13:26 -0700 Subject: [PATCH 06/12] Don't start drawing Surface until needed. --- app/views/play/level_view.coffee | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/views/play/level_view.coffee b/app/views/play/level_view.coffee index 56e219ec3..16ed16943 100644 --- a/app/views/play/level_view.coffee +++ b/app/views/play/level_view.coffee @@ -185,10 +185,6 @@ module.exports = class PlayLevelView extends View @levelLoader = null @initSurface() @initScriptManager() - @surface.showLevel() - if @otherSession - # TODO: colorize name and cloud by team, colorize wizard by user's color config - @surface.createOpponentWizard id: @otherSession.get('creator'), name: @otherSession.get('creatorName'), team: @otherSession.get('team') grabLevelLoaderData: -> @session = @levelLoader.session @@ -215,6 +211,10 @@ module.exports = class PlayLevelView extends View @session.set 'multiplayer', false onLevelStarted: (e) -> + @surface.showLevel() + if @otherSession + # TODO: colorize name and cloud by team, colorize wizard by user's color config + @surface.createOpponentWizard id: @otherSession.get('creator'), name: @otherSession.get('creatorName'), team: @otherSession.get('team') @loadingView?.unveil() onLoadingViewUnveiled: (e) -> From 1d88b6eefe9c8ff85b134335daa5a29b88312add Mon Sep 17 00:00:00 2001 From: Scott Erickson <sderickson@gmail.com> Date: Tue, 13 May 2014 10:26:33 -0700 Subject: [PATCH 07/12] Most of the way there getting it to work with loading thang types dynamically throughout the play view. --- app/lib/surface/CocoSprite.coffee | 4 +++- app/lib/surface/Layer.coffee | 1 + app/lib/surface/Mark.coffee | 4 ++-- app/lib/surface/SpriteBoss.coffee | 2 ++ app/models/SuperModel.coffee | 2 +- app/models/ThangType.coffee | 12 +++++++++-- app/views/play/level/hud_view.coffee | 7 +++++++ app/views/play/level/thang_avatar_view.coffee | 20 +++++++++++++++---- 8 files changed, 42 insertions(+), 10 deletions(-) diff --git a/app/lib/surface/CocoSprite.coffee b/app/lib/surface/CocoSprite.coffee index 493c8cd5e..f3a2327a2 100644 --- a/app/lib/surface/CocoSprite.coffee +++ b/app/lib/surface/CocoSprite.coffee @@ -70,7 +70,7 @@ module.exports = CocoSprite = class CocoSprite extends CocoClass @age = 0 @scaleFactor = @targetScaleFactor = 1 @displayObject = new createjs.Container() - if @thangType.get('actions') or @thangType.get('raster') + if @thangType.isFullyLoaded() @setupSprite() else @stillLoading = true @@ -146,6 +146,7 @@ module.exports = CocoSprite = class CocoSprite extends CocoClass queueAction: (action) -> # The normal way to have an action play + return unless @thangType.isFullyLoaded() action = @actions[action] if _.isString(action) action ?= @actions.idle @actionQueue = [] @@ -266,6 +267,7 @@ module.exports = CocoSprite = class CocoSprite extends CocoClass @hasMoved = true updateScale: -> + return unless @imageObject if @thangType.get('matchWorldDimensions') and @thang if @thang.width isnt @lastThangWidth or @thang.height isnt @lastThangHeight bounds = @imageObject.getBounds() diff --git a/app/lib/surface/Layer.coffee b/app/lib/surface/Layer.coffee index a0f8d5ad4..1e438a2ea 100644 --- a/app/lib/surface/Layer.coffee +++ b/app/lib/surface/Layer.coffee @@ -101,4 +101,5 @@ module.exports = class Layer extends createjs.Container cache: -> return unless @children.length bounds = @getBounds() + return unless bounds super bounds.x, bounds.y, bounds.width, bounds.height, 2 diff --git a/app/lib/surface/Mark.coffee b/app/lib/surface/Mark.coffee index 7c9abb91f..1b29aa375 100644 --- a/app/lib/surface/Mark.coffee +++ b/app/lib/surface/Mark.coffee @@ -200,7 +200,7 @@ module.exports = class Mark extends CocoClass Backbone.Mediator.publish 'sprite:loaded' update: (pos=null) -> - return false unless @on and @mark + return false unless @on and @mark and @sprite?.thangType.isFullyLoaded() @mark.visible = not @hidden @updatePosition pos @updateRotation() @@ -242,7 +242,7 @@ module.exports = class Mark extends CocoClass oldMark.parent.removeChild oldMark return unless @name in ["selection", "target", "repair", "highlight"] scale = 0.5 - if @sprite + if @sprite?.imageObject size = @sprite.getAverageDimension() size += 60 if @name is 'selection' size += 60 if @name is 'repair' diff --git a/app/lib/surface/SpriteBoss.coffee b/app/lib/surface/SpriteBoss.coffee index fce5c299d..ea3825e1e 100644 --- a/app/lib/surface/SpriteBoss.coffee +++ b/app/lib/surface/SpriteBoss.coffee @@ -201,6 +201,8 @@ module.exports = class SpriteBoss extends CocoClass cache: (update=false) -> return if @cached and not update wallSprites = (sprite for sprite in @spriteArray when sprite.thangType?.get('name').search(/(dungeon|indoor).wall/i) isnt -1) + unless _.all (s.thangType.isFullyLoaded() for s in wallSprites) + return walls = (sprite.thang for sprite in wallSprites) @world.calculateBounds() wallGrid = new Grid walls, @world.size()... diff --git a/app/models/SuperModel.coffee b/app/models/SuperModel.coffee index 9d610447a..8541333b1 100644 --- a/app/models/SuperModel.coffee +++ b/app/models/SuperModel.coffee @@ -19,7 +19,7 @@ module.exports = class SuperModel extends Backbone.Model loadModel: (model, name, fetchOptions, value=1) -> cachedModel = @getModelByURL(model.getURL()) if cachedModel - console.debug 'Model cache hit', cachedModel.getURL(), 'already loaded', cachedModel.loaded + console.debug 'Model cache hit', cachedModel.get('name') or cachedModel.getURL() if cachedModel.loaded res = @addModelResource(cachedModel, name, fetchOptions, 0) res.markLoaded() diff --git a/app/models/ThangType.coffee b/app/models/ThangType.coffee index 321f47990..e29ec3215 100644 --- a/app/models/ThangType.coffee +++ b/app/models/ThangType.coffee @@ -26,12 +26,18 @@ module.exports = class ThangType extends CocoModel @buildActions() @spriteSheets = {} @building = {} + + isFullyLoaded: -> + # TODO: Come up with a better way to identify when the model doesn't have everything needed to build the sprite. ie when it's a projection without all the required data. + return @get('actions') or @get('raster') # needs one of these two things getActions: -> + return {} unless @isFullyLoaded() return @actions or @buildActions() buildActions: -> - @actions = $.extend(true, {}, @get('actions') or {}) + return null unless @isFullyLoaded() + @actions = $.extend(true, {}, @get('actions')) for name, action of @actions action.name = name for relatedName, relatedAction of action.relatedActions ? {} @@ -52,6 +58,7 @@ module.exports = class ThangType extends CocoModel options buildSpriteSheet: (options) -> + return false unless @isFullyLoaded() @options = @fillOptions options key = @spriteSheetKey(@options) return if @building[key] @@ -146,7 +153,7 @@ module.exports = class ThangType extends CocoModel return true t0 = new Date() spriteSheet = @builder.build() - console.warn "Built #{@get('name')} in #{new Date() - t0}ms on main thread." + console.debug "Built #{@get('name')} in #{new Date() - t0}ms." @spriteSheets[key] = spriteSheet delete @building[key] spriteSheet @@ -180,6 +187,7 @@ module.exports = class ThangType extends CocoModel stage?.toDataURL() getPortraitStage: (spriteOptionsOrKey, size=100) -> + return unless @isFullyLoaded() key = spriteOptionsOrKey key = if _.isString(key) then key else @spriteSheetKey(@fillOptions(key)) spriteSheet = @spriteSheets[key] diff --git a/app/views/play/level/hud_view.coffee b/app/views/play/level/hud_view.coffee index deeab1f04..87625d631 100644 --- a/app/views/play/level/hud_view.coffee +++ b/app/views/play/level/hud_view.coffee @@ -109,6 +109,13 @@ module.exports = class HUDView extends View @update() createAvatar: (thangType, thang, colorConfig) -> + unless thangType.isFullyLoaded() + args = arguments + unless @listeningToCreateAvatar + @listenToOnce thangType, 'sync', -> @createAvatar(args...) + @listeningToCreateAvatar = true + return + @listeningToCreateAvatar = false options = thang.getSpriteOptions() or {} options.async = false options.colorConfig = colorConfig if colorConfig diff --git a/app/views/play/level/thang_avatar_view.coffee b/app/views/play/level/thang_avatar_view.coffee index 3c22fc5cb..85e485bac 100644 --- a/app/views/play/level/thang_avatar_view.coffee +++ b/app/views/play/level/thang_avatar_view.coffee @@ -14,16 +14,28 @@ module.exports = class ThangAvatarView extends View super options @thang = options.thang @includeName = options.includeName + @thangType = @getSpriteThangType() + if not @thangType + console.error 'Thang avatar view expected a thang type to be provided.' + return + + unless @thangType.isFullyLoaded() or @thangType.loading + @thangType.fetch() + + @supermodel.loadModel @thangType, 'thang' + + getSpriteThangType: -> + thangs = @supermodel.getModels(ThangType) + thangs = (t for t in thangs when t.get('name') is @thang.spriteName) + loadedThangs = (t for t in thangs when t.isFullyLoaded()) + return loadedThangs[0] or thangs[0] # try to return one with all the goods, otherwise a projection getRenderData: (context={}) -> context = super context context.thang = @thang - thangs = @supermodel.getModels(ThangType) - thangs = (t for t in thangs when t.get('name') is @thang.spriteName) - thang = thangs[0] options = @thang?.getSpriteOptions() or {} options.async = false - context.avatarURL = thang.getPortraitSource(options) + context.avatarURL = @thangType.getPortraitSource(options) context.includeName = @includeName context From b4ad34eb8ffb126dca05b2f0ca551320087ae1ca Mon Sep 17 00:00:00 2001 From: Scott Erickson <sderickson@gmail.com> Date: Tue, 13 May 2014 10:51:55 -0700 Subject: [PATCH 08/12] LevelLoader only builds and loads thang types in the world on the first frame, letting everything else happen lazily. --- app/lib/LevelLoader.coffee | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/app/lib/LevelLoader.coffee b/app/lib/LevelLoader.coffee index c7abdb3aa..dced1ec67 100644 --- a/app/lib/LevelLoader.coffee +++ b/app/lib/LevelLoader.coffee @@ -108,8 +108,8 @@ module.exports = class LevelLoader extends CocoClass worldNecessities = [] @thangIDs = _.uniq thangIDs - thangNames = new ThangNamesCollection(@thangIDs) - worldNecessities.push @supermodel.loadCollection(thangNames, 'thang_names') + @thangNames = new ThangNamesCollection(@thangIDs) + worldNecessities.push @supermodel.loadCollection(@thangNames, 'thang_names') for obj in objUniq componentVersions url = "/db/level.component/#{obj.original}/version/#{obj.majorVersion}" @@ -133,11 +133,16 @@ module.exports = class LevelLoader extends CocoClass onWorldNecessitiesLoaded: => @initWorld() - for thangID in @thangIDs - url = "/db/thang.type/#{thangID}/version" - url += "?project=true" if @headless and not @editorMode - res = @maybeLoadURL url, ThangType, 'thang' - @listenToOnce res.model, 'sync', @buildSpriteSheetsForThangType if res + return if @headless and not @editorMode + thangsToLoad = _.uniq( (t.spriteName for t in @world.thangs) ) + nameModelTuples = ([thangType.get('name'), thangType] for thangType in @thangNames.models) + nameModelMap = _.zipObject nameModelTuples + + for thangTypeName in thangsToLoad + thang = nameModelMap[thangTypeName] + thang.fetch() + thang = @supermodel.loadModel(thang, 'thang').model + @listenToOnce thang, 'sync', @buildSpriteSheetsForThangType maybeLoadURL: (url, Model, resourceName) -> return if @supermodel.getModel(url) From 4b404ae1248754d832a29c39a5f59c8484908c31 Mon Sep 17 00:00:00 2001 From: Scott Erickson <sderickson@gmail.com> Date: Tue, 13 May 2014 14:39:45 -0700 Subject: [PATCH 09/12] Finished tuning the supermodel loading and progress. --- app/lib/LevelLoader.coffee | 31 ++++++++++++++++--- app/locale/en.coffee | 1 + app/models/SuperModel.coffee | 3 +- app/models/ThangType.coffee | 5 +-- app/views/play/level/thang_avatar_view.coffee | 2 +- 5 files changed, 33 insertions(+), 9 deletions(-) diff --git a/app/lib/LevelLoader.coffee b/app/lib/LevelLoader.coffee index dced1ec67..911ea698c 100644 --- a/app/lib/LevelLoader.coffee +++ b/app/lib/LevelLoader.coffee @@ -137,12 +137,18 @@ module.exports = class LevelLoader extends CocoClass thangsToLoad = _.uniq( (t.spriteName for t in @world.thangs) ) nameModelTuples = ([thangType.get('name'), thangType] for thangType in @thangNames.models) nameModelMap = _.zipObject nameModelTuples + @spriteSheetsToBuild = [] for thangTypeName in thangsToLoad - thang = nameModelMap[thangTypeName] - thang.fetch() - thang = @supermodel.loadModel(thang, 'thang').model - @listenToOnce thang, 'sync', @buildSpriteSheetsForThangType + thangType = nameModelMap[thangTypeName] + thangType.fetch() + thangType = @supermodel.loadModel(thangType, 'thang').model + res = @supermodel.addSomethingResource "sprite_sheet", 5 + res.thangType = thangType + res.markLoading() + @spriteSheetsToBuild.push res + + @buildLoopInterval = setInterval @buildLoop, 5 maybeLoadURL: (url, Model, resourceName) -> return if @supermodel.getModel(url) @@ -150,9 +156,22 @@ module.exports = class LevelLoader extends CocoClass @supermodel.loadModel(model, resourceName) onSupermodelLoaded: -> + console.log 'SuperModel for Level loaded in', new Date().getTime() - @t0, 'ms' @loadLevelSounds() @denormalizeSession() app.tracker.updatePlayState(@level, @session) unless @headless + + buildLoop: => + return if @lastBuilt and new Date().getTime() - @lastBuilt < 10 + return clearInterval @buildLoopInterval unless @spriteSheetsToBuild.length + + for spriteSheetResource, i in @spriteSheetsToBuild + if spriteSheetResource.thangType.loaded + @buildSpriteSheetsForThangType spriteSheetResource.thangType + @spriteSheetsToBuild.splice i, 1 + @lastBuilt = new Date().getTime() + spriteSheetResource.markLoaded() + return denormalizeSession: -> return if @headless or @sessionDenormalized or @spectateMode @@ -246,3 +265,7 @@ module.exports = class LevelLoader extends CocoClass # everything else sound wise is loaded as needed as worlds are generated progress: -> @supermodel.progress + + destroy: -> + clearInterval @buildLoopInterval if @buildLoopInterval + super() diff --git a/app/locale/en.coffee b/app/locale/en.coffee index a41ebff08..5c10f0a1c 100644 --- a/app/locale/en.coffee +++ b/app/locale/en.coffee @@ -715,6 +715,7 @@ top_simulators: "Top Simulators" source_document: "Source Document" document: "Document" # note to diplomats: not a physical document, a document in MongoDB, ie a record in a database + sprite_sheet: "Sprite Sheet" delta: added: "Added" diff --git a/app/models/SuperModel.coffee b/app/models/SuperModel.coffee index 8541333b1..9545d2a4e 100644 --- a/app/models/SuperModel.coffee +++ b/app/models/SuperModel.coffee @@ -19,7 +19,6 @@ module.exports = class SuperModel extends Backbone.Model loadModel: (model, name, fetchOptions, value=1) -> cachedModel = @getModelByURL(model.getURL()) if cachedModel - console.debug 'Model cache hit', cachedModel.get('name') or cachedModel.getURL() if cachedModel.loaded res = @addModelResource(cachedModel, name, fetchOptions, 0) res.markLoaded() @@ -141,7 +140,7 @@ module.exports = class SuperModel extends Backbone.Model @listenToOnce(resource, 'loaded', @onResourceLoaded) @listenTo(resource, 'failed', @onResourceFailed) @denom += value - @updateProgress() if @denom + _.defer @updateProgress if @denom onResourceLoaded: (r) -> @num += r.value diff --git a/app/models/ThangType.coffee b/app/models/ThangType.coffee index e29ec3215..345bc0264 100644 --- a/app/models/ThangType.coffee +++ b/app/models/ThangType.coffee @@ -61,7 +61,9 @@ module.exports = class ThangType extends CocoModel return false unless @isFullyLoaded() @options = @fillOptions options key = @spriteSheetKey(@options) + if ss = @spriteSheets[key] then return ss return if @building[key] + @t0 = new Date().getTime() @initBuild(options) @addGeneralFrames() unless @options.portraitOnly @addPortrait() @@ -151,9 +153,8 @@ module.exports = class ThangType extends CocoModel @builder.buildAsync() unless buildQueue.length > 1 @builder.on 'complete', @onBuildSpriteSheetComplete, @, true, key return true - t0 = new Date() spriteSheet = @builder.build() - console.debug "Built #{@get('name')} in #{new Date() - t0}ms." + console.debug "Built #{@get('name')} in #{new Date().getTime() - @t0}ms." @spriteSheets[key] = spriteSheet delete @building[key] spriteSheet diff --git a/app/views/play/level/thang_avatar_view.coffee b/app/views/play/level/thang_avatar_view.coffee index 85e485bac..a1af2102b 100644 --- a/app/views/play/level/thang_avatar_view.coffee +++ b/app/views/play/level/thang_avatar_view.coffee @@ -35,7 +35,7 @@ module.exports = class ThangAvatarView extends View context.thang = @thang options = @thang?.getSpriteOptions() or {} options.async = false - context.avatarURL = @thangType.getPortraitSource(options) + context.avatarURL = @thangType.getPortraitSource(options) unless @thangType.loading context.includeName = @includeName context From 54af58baeeaa3d6ef22255fe08a00d0620b46533 Mon Sep 17 00:00:00 2001 From: Scott Erickson <sderickson@gmail.com> Date: Wed, 14 May 2014 09:24:52 -0700 Subject: [PATCH 10/12] SuperModel no longer emits progress going backward ever, and added a way to prevent it from getting to high in the middle of a chain of loading. --- app/lib/LevelLoader.coffee | 2 ++ app/models/SuperModel.coffee | 11 +++++++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/app/lib/LevelLoader.coffee b/app/lib/LevelLoader.coffee index 911ea698c..61e0a7918 100644 --- a/app/lib/LevelLoader.coffee +++ b/app/lib/LevelLoader.coffee @@ -25,6 +25,7 @@ module.exports = class LevelLoader extends CocoClass @t0 = new Date().getTime() super() @supermodel = options.supermodel + @supermodel.setMaxProgress 0.2 @levelID = options.levelID @sessionID = options.sessionID @opponentSessionID = options.opponentSessionID @@ -133,6 +134,7 @@ module.exports = class LevelLoader extends CocoClass onWorldNecessitiesLoaded: => @initWorld() + @supermodel.clearMaxProgress() return if @headless and not @editorMode thangsToLoad = _.uniq( (t.spriteName for t in @world.thangs) ) nameModelTuples = ([thangType.get('name'), thangType] for thangType in @thangNames.models) diff --git a/app/models/SuperModel.coffee b/app/models/SuperModel.coffee index 9545d2a4e..ef68d39c9 100644 --- a/app/models/SuperModel.coffee +++ b/app/models/SuperModel.coffee @@ -5,6 +5,7 @@ module.exports = class SuperModel extends Backbone.Model @progress = 0 @resources = {} @rid = 0 + @maxProgress = 1 @models = {} @collections = {} @@ -154,11 +155,17 @@ module.exports = class SuperModel extends Backbone.Model # 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 - return if @progress is newProg + newProg = Math.min @maxProgress, newProg + return if @progress >= newProg @progress = newProg @trigger('update-progress', @progress) @trigger('loaded-all') if @finished() - + + setMaxProgress: (@maxProgress) -> + clearMaxProgress: -> + @maxProgress = 1 + _.defer @updateProgress + getProgress: -> return @progress getResource: (rid) -> From 0be813e42499d29e43096d55248a6a47fede179e Mon Sep 17 00:00:00 2001 From: Scott Erickson <sderickson@gmail.com> Date: Wed, 14 May 2014 10:35:16 -0700 Subject: [PATCH 11/12] Figured out how to get the world to properly get shown if the world is created before the level loader is completely finished. --- app/lib/surface/Surface.coffee | 2 ++ app/views/play/level_view.coffee | 10 +++------- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/app/lib/surface/Surface.coffee b/app/lib/surface/Surface.coffee index 506626c51..62f0901b2 100644 --- a/app/lib/surface/Surface.coffee +++ b/app/lib/surface/Surface.coffee @@ -84,6 +84,8 @@ module.exports = Surface = class Surface extends CocoClass @initAudio() @onResize = _.debounce @onResize, 500 $(window).on 'resize', @onResize + if @world.ended + _.defer => @setWorld @world destroy: -> @dead = true diff --git a/app/views/play/level_view.coffee b/app/views/play/level_view.coffee index e005c1948..af874a04b 100644 --- a/app/views/play/level_view.coffee +++ b/app/views/play/level_view.coffee @@ -60,7 +60,6 @@ module.exports = class PlayLevelView extends View 'surface:world-set-up': 'onSurfaceSetUpNewWorld' 'level:session-will-save': 'onSessionWillSave' 'level:set-team': 'setTeam' - 'god:new-world-created': 'loadSoundsForWorld' 'level:started': 'onLevelStarted' 'level:loading-view-unveiled': 'onLoadingViewUnveiled' @@ -309,9 +308,6 @@ module.exports = class PlayLevelView extends View $('#level-done-button', @$el).hide() application.tracker?.trackEvent 'Confirmed Restart', level: @world.name, label: @world.name - onNewWorld: (e) -> - @world = e.world - onInfiniteLoop: (e) -> return unless e.firstWorld @openModalView new InfiniteLoopModal() @@ -484,11 +480,11 @@ module.exports = class PlayLevelView extends View # Dynamic sound loading - loadSoundsForWorld: (e) -> + onNewWorld: (e) -> return if @headless - world = e.world + @world = e.world thangTypes = @supermodel.getModels(ThangType) - for [spriteName, message] in world.thangDialogueSounds() + for [spriteName, message] in @world.thangDialogueSounds() continue unless thangType = _.find thangTypes, (m) -> m.get('name') is spriteName continue unless sound = AudioPlayer.soundForDialogue message, thangType.get('soundTriggers') AudioPlayer.preloadSoundReference sound From 09fbfdb360de079af155bc793a282a99cbcfddda Mon Sep 17 00:00:00 2001 From: Scott Erickson <sderickson@gmail.com> Date: Wed, 14 May 2014 11:13:36 -0700 Subject: [PATCH 12/12] Fixed a bug with the simulator due to the changes made to the supermodel. --- app/lib/simulator/Simulator.coffee | 2 +- app/models/SuperModel.coffee | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/app/lib/simulator/Simulator.coffee b/app/lib/simulator/Simulator.coffee index 8c4d9df17..b89677512 100644 --- a/app/lib/simulator/Simulator.coffee +++ b/app/lib/simulator/Simulator.coffee @@ -74,7 +74,7 @@ module.exports = class Simulator extends CocoClass return @supermodel ?= new SuperModel() - + @supermodel.resetProgress() @levelLoader = new LevelLoader supermodel: @supermodel, levelID: levelID, sessionID: @task.getFirstSessionID(), headless: true if @supermodel.finished() @simulateGame() diff --git a/app/models/SuperModel.coffee b/app/models/SuperModel.coffee index ef68d39c9..cbfc1d2d2 100644 --- a/app/models/SuperModel.coffee +++ b/app/models/SuperModel.coffee @@ -162,6 +162,7 @@ module.exports = class SuperModel extends Backbone.Model @trigger('loaded-all') if @finished() setMaxProgress: (@maxProgress) -> + resetProgress: -> @progress = 0 clearMaxProgress: -> @maxProgress = 1 _.defer @updateProgress