mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2024-11-25 00:28:31 -05:00
Merge branch 'feature/change-supermodel-to-general-resource-loader' of https://github.com/gintau/codecombat into gintau-feature/change-supermodel-to-general-resource-loader
This commit is contained in:
commit
94e574cee8
7 changed files with 261 additions and 125 deletions
|
@ -59,14 +59,16 @@ module.exports = class LevelLoader extends CocoClass
|
|||
|
||||
# Unless you specify cache:false, sometimes the browser will use a cached session
|
||||
# and players will 'lose' code
|
||||
@session.fetch({cache:false})
|
||||
@listenToOnce(@session, 'sync', @onSessionLoaded)
|
||||
session_res = @supermodel.addModelResource(@session, @session.url, {cache:false})
|
||||
session_res.load()
|
||||
|
||||
if @opponentSessionID
|
||||
@opponentSession = new LevelSession()
|
||||
@opponentSession.url = "/db/level_session/#{@opponentSessionID}"
|
||||
@opponentSession.fetch()
|
||||
@listenToOnce(@opponentSession, 'sync', @onSessionLoaded)
|
||||
opponentSession_res = @supermodel.addModelResource(@opponentSession, @opponentSession.url)
|
||||
opponentSession_res.load()
|
||||
|
||||
sessionsLoaded: ->
|
||||
return true if @headless
|
||||
|
@ -217,7 +219,7 @@ module.exports = class LevelLoader extends CocoClass
|
|||
progress: ->
|
||||
return 0 unless @level.loaded
|
||||
overallProgress = 0
|
||||
supermodelProgress = @supermodel.progress()
|
||||
supermodelProgress = @supermodel.getProgress()
|
||||
overallProgress += supermodelProgress * 0.7
|
||||
overallProgress += 0.1 if @sessionsLoaded()
|
||||
if @headless
|
||||
|
@ -226,6 +228,7 @@ module.exports = class LevelLoader extends CocoClass
|
|||
spriteMapProgress = if supermodelProgress is 1 then 0.2 else 0
|
||||
spriteMapProgress *= @spriteSheetsBuilt / @spriteSheetsToBuild if @spriteSheetsToBuild
|
||||
overallProgress += spriteMapProgress
|
||||
|
||||
return overallProgress
|
||||
|
||||
notifyProgress: ->
|
||||
|
|
|
@ -1,20 +1,27 @@
|
|||
class SuperModel
|
||||
module.exports = class SuperModel extends Backbone.Model
|
||||
constructor: ->
|
||||
@models = {}
|
||||
@collections = {}
|
||||
@schemas = {}
|
||||
_.extend(@, Backbone.Events)
|
||||
|
||||
populateModel: (model) ->
|
||||
@mustPopulate = model
|
||||
model.saveBackups = @shouldSaveBackups(model)
|
||||
model.fetch() unless model.loaded or model.loading
|
||||
@listenToOnce(model, 'sync', @modelLoaded) unless model.loaded
|
||||
@listenToOnce(model, 'error', @modelErrored) unless model.loaded
|
||||
# model.fetch() unless model.loaded or model.loading
|
||||
# @listenToOnce(model, 'sync', @modelLoaded) unless model.loaded
|
||||
# @listenToOnce(model, 'error', @modelErrored) unless model.loaded
|
||||
url = model.url()
|
||||
@models[url] = model unless @models[url]?
|
||||
@modelLoaded(model) if model.loaded
|
||||
|
||||
modelRes = @addModelResource(model, url)
|
||||
schema = model.schema()
|
||||
schemaRes = @addModelResource(schema, schema.urlRoot)
|
||||
@schemas[schema.urlRoot] = schema
|
||||
modelRes.addDependency(schemaRes.name)
|
||||
|
||||
modelRes.load()
|
||||
|
||||
# replace or overwrite
|
||||
shouldLoadReference: (model) -> true
|
||||
shouldLoadProjection: (model) -> false
|
||||
|
@ -25,25 +32,8 @@ class SuperModel
|
|||
@trigger 'error'
|
||||
@removeEventsFromModel(model)
|
||||
|
||||
modelLoaded: (model) ->
|
||||
model.loadSchema()
|
||||
schema = model.schema()
|
||||
unless schema.loaded
|
||||
@schemas[schema.urlRoot] = schema
|
||||
return schema.once('sync', => @modelLoaded(model))
|
||||
refs = model.getReferencedModels(model.attributes, schema.attributes, '/', @shouldLoadProjection)
|
||||
refs = [] unless @mustPopulate is model or @shouldPopulate(model)
|
||||
# console.log 'Loaded', model.get('name')
|
||||
for ref, i in refs when @shouldLoadReference ref
|
||||
ref.saveBackups = @shouldSaveBackups(ref)
|
||||
refURL = ref.url()
|
||||
continue if @models[refURL]
|
||||
@models[refURL] = ref
|
||||
ref.fetch()
|
||||
@listenToOnce(ref, 'sync', @modelLoaded)
|
||||
|
||||
modelLoaded: (model) ->
|
||||
@trigger 'loaded-one', model: model
|
||||
@trigger 'loaded-all' if @finished()
|
||||
@removeEventsFromModel(model)
|
||||
|
||||
removeEventsFromModel: (model) ->
|
||||
|
@ -96,21 +86,218 @@ class SuperModel
|
|||
@addModel(model)
|
||||
collection
|
||||
|
||||
progress: ->
|
||||
total = 0
|
||||
loaded = 0
|
||||
|
||||
for model in _.values @models
|
||||
total += 1
|
||||
loaded += 1 if model.loaded
|
||||
for schema in _.values @schemas
|
||||
total += 1
|
||||
loaded += 1 if schema.loaded
|
||||
|
||||
return 1.0 unless total
|
||||
return loaded / total
|
||||
|
||||
finished: ->
|
||||
return @progress() == 1.0
|
||||
return ResourceManager.progress == 1.0 or Object.keys(ResourceManager.resources).length == 0
|
||||
|
||||
module.exports = SuperModel
|
||||
|
||||
addModelResource: (modelOrCollection, name, fetchOptions, value=1)->
|
||||
@checkName(name)
|
||||
res = new ModelResource(modelOrCollection, name, fetchOptions, value)
|
||||
@storeResource(name, res, value)
|
||||
return res
|
||||
|
||||
addRequestResource: (name, jqxhrOptions, value=1)->
|
||||
@checkName(name)
|
||||
res = new RequestResource(name, jqxhrOptions, value)
|
||||
@storeResource(name, res, value)
|
||||
return res
|
||||
|
||||
addSomethingResource: (name, value=1)->
|
||||
@checkName(name)
|
||||
res = new SomethingResource(name, value)
|
||||
@storeResource(name, res, value)
|
||||
return res
|
||||
|
||||
checkName: (name)->
|
||||
if not name
|
||||
throw new Error('Resource name should not be empty.')
|
||||
if name in ResourceManager.resources
|
||||
throw new Error('Resource name has been used.')
|
||||
|
||||
storeResource: (name, resource, value)->
|
||||
ResourceManager.resources[name] = resource
|
||||
@listenToOnce(resource, 'resource:loaded', @onResourceLoaded)
|
||||
@listenToOnce(resource, 'resource:failed', @onResourceFailed)
|
||||
ResourceManager.denom += value
|
||||
|
||||
loadResources: ()->
|
||||
for name, res of ResourceManager.resources
|
||||
res.load()
|
||||
|
||||
onResourceLoaded: (r)=>
|
||||
@modelLoaded(r.model)
|
||||
# Check if the model has references
|
||||
if r.constructor.name == 'ModelResource'
|
||||
model = r.model
|
||||
@addModelRefencesToLoad(model)
|
||||
@updateProgress(r)
|
||||
else
|
||||
@updateProgress(r)
|
||||
|
||||
onResourceFailed: (r)=>
|
||||
@modelErrored(r.model)
|
||||
|
||||
addModelRefencesToLoad: (model)->
|
||||
schema = model.schema?()
|
||||
return unless schema
|
||||
|
||||
refs = model.getReferencedModels(model.attributes, schema.attributes, '/', @shouldLoadProjection)
|
||||
refs = [] unless @mustPopulate is model or @shouldPopulate(model)
|
||||
|
||||
for ref, i in refs when @shouldLoadReference ref
|
||||
ref.saveBackups = @shouldSaveBackups(ref)
|
||||
refURL = ref.url()
|
||||
|
||||
continue if @models[refURL]
|
||||
|
||||
@models[refURL] = ref
|
||||
res = @addModelResource(ref, refURL)
|
||||
res.load()
|
||||
|
||||
updateProgress: (r)=>
|
||||
ResourceManager.num += r.value
|
||||
ResourceManager.progress = ResourceManager.num / ResourceManager.denom
|
||||
|
||||
@trigger('superModel:updateProgress', ResourceManager.progress)
|
||||
@trigger 'loaded-all' if @finished()
|
||||
|
||||
getResource: (name)->
|
||||
return ResourceManager.resources[name]
|
||||
|
||||
getProgress: ()-> return ResourceManager.progress
|
||||
|
||||
# Both SuperModel and Resource access this class.
|
||||
# Set resources as static so no need to load resources multiple times when more than one view is used.
|
||||
class ResourceManager
|
||||
@num = 0
|
||||
@denom = 0
|
||||
@showing = false
|
||||
@progress = 0
|
||||
@resources: {}
|
||||
|
||||
class Resource extends Backbone.Model
|
||||
constructor: (name, value=1)->
|
||||
@name = name
|
||||
@value = value
|
||||
@dependencies = []
|
||||
@isLoading = false
|
||||
@isLoaded = false
|
||||
@model = null
|
||||
@loadDeferred = null
|
||||
@value = 1
|
||||
|
||||
addDependency: (name)->
|
||||
depRes = ResourceManager.resources[name]
|
||||
throw new Error('Resource not found') unless depRes
|
||||
return if (depRes.isLoaded or name == @name)
|
||||
@dependencies.push(name)
|
||||
|
||||
markLoaded: ()->
|
||||
@trigger('resource:loaded', @) if not @isLoaded
|
||||
@isLoaded = true
|
||||
@isLoading = false
|
||||
|
||||
markFailed: ()->
|
||||
@trigger('resource:failed', @) if not @isLoaded
|
||||
@isLoaded = false
|
||||
@isLoading = false
|
||||
|
||||
load: ()->
|
||||
isReadyForLoad: ()-> return not (@isloaded and @isLoading)
|
||||
getModel: ()-> @model
|
||||
|
||||
class ModelResource extends Resource
|
||||
constructor: (modelOrCollection, name, fetchOptions, value)->
|
||||
super(name, value)
|
||||
@model = modelOrCollection
|
||||
@fetchOptions = fetchOptions
|
||||
|
||||
load: ()->
|
||||
return @loadDeferred.promise() if @isLoading or @isLoaded
|
||||
|
||||
@isLoading = true
|
||||
@loadDeferred = $.Deferred()
|
||||
$.when.apply($, @loadDependencies())
|
||||
.then(@onLoadDependenciesSuccess, @onLoadDependenciesFailed)
|
||||
.always(()=> @isLoading = false)
|
||||
|
||||
return @loadDeferred.promise()
|
||||
|
||||
loadDependencies: ()->
|
||||
promises = []
|
||||
|
||||
for resName in @dependencies
|
||||
dep = ResourceManager.resources[resName]
|
||||
continue if not dep.isReadyForLoad()
|
||||
promises.push(dep.load())
|
||||
|
||||
return promises
|
||||
|
||||
onLoadDependenciesSuccess: ()=>
|
||||
@model.fetch(@fetchOptions)
|
||||
|
||||
@listenToOnce(@model, 'sync', ()=>
|
||||
@markLoaded()
|
||||
@loadDeferred.resolve(@)
|
||||
)
|
||||
|
||||
@listenToOnce(@model, 'error', ()=>
|
||||
@markFailed()
|
||||
@loadDeferred.reject(@)
|
||||
)
|
||||
|
||||
onLoadDependenciesFailed: ()=>
|
||||
@markFailed()
|
||||
@loadDeferred.reject(@)
|
||||
|
||||
|
||||
class RequestResource extends Resource
|
||||
constructor: (name, jqxhrOptions, value)->
|
||||
super(name, value)
|
||||
@model = $.ajax(jqxhrOptions)
|
||||
@jqxhrOptions = jqxhrOptions
|
||||
@loadDeferred = @model
|
||||
|
||||
load: ()->
|
||||
return @loadDeferred.promise() if @isLoading or @isLoaded
|
||||
|
||||
@isLoading = true
|
||||
$.when.apply($, @loadDependencies())
|
||||
.then(@onLoadDependenciesSuccess, @onLoadDependenciesFailed)
|
||||
.always(()=> @isLoading = false)
|
||||
|
||||
return @loadDeferred.promise()
|
||||
|
||||
loadDependencies: ()->
|
||||
promises = []
|
||||
for depName in @dependecies
|
||||
dep = ResourceManager.resources[depName]
|
||||
continue if not dep.isReadyForLoad()
|
||||
promises.push(dep.load())
|
||||
|
||||
return promises
|
||||
|
||||
onLoadDependenciesSuccess: ()->
|
||||
@model = $.ajax(@jqxhrOptions)
|
||||
@model.done(()=> @markLoaded()).failed(()=> @markFailed())
|
||||
|
||||
onLoadDependenciesFailed: ()->
|
||||
@markFailed()
|
||||
|
||||
|
||||
class SomethingResource extends Resource
|
||||
constructor: (name, value)->
|
||||
super(value)
|
||||
@name = name
|
||||
@loadDeferred = $.Deferred()
|
||||
|
||||
load: ()->
|
||||
return @loadDeferred.promise()
|
||||
|
||||
markLoaded: ()->
|
||||
@loadDeferred.resolve()
|
||||
super()
|
||||
|
||||
markFailed: ()->
|
||||
@loadDeferred.reject()
|
||||
super()
|
|
@ -58,7 +58,7 @@ module.exports = class EditorLevelView extends View
|
|||
|
||||
onLevelLoaded: ->
|
||||
@files = new DocumentFiles(@level)
|
||||
@files.fetch()
|
||||
@supermodel.addModelResource(@files, @files.url).load()
|
||||
|
||||
onAllLoaded: ->
|
||||
@level.unset('nextLevel') if _.isString(@level.get('nextLevel'))
|
||||
|
|
|
@ -56,17 +56,21 @@ module.exports = class ThangTypeEditView extends View
|
|||
@insertSubView(new ErrorView())
|
||||
)
|
||||
|
||||
@thangType.fetch()
|
||||
@thangType.loadSchema()
|
||||
thang_res = @supermodel.addModelResource(@thangType, 'thang_type')
|
||||
thang_schema_res = @supermodel.addModelResource(@thangType.schema(), 'thang_type_schema')
|
||||
thang_res.addDependency('thang_type_schema')
|
||||
|
||||
thang_res.load()
|
||||
@listenToOnce(@thangType.schema(), 'sync', @onThangTypeSync)
|
||||
@listenToOnce(@thangType, 'sync', @onThangTypeSync)
|
||||
|
||||
@refreshAnimation = _.debounce @refreshAnimation, 500
|
||||
|
||||
onThangTypeSync: ->
|
||||
return unless @thangType.loaded and ThangType.hasSchema()
|
||||
@startsLoading = false
|
||||
@files = new DocumentFiles(@thangType)
|
||||
@files.fetch()
|
||||
@supermodel.addModelResource(@files, @files.url).load()
|
||||
@render()
|
||||
|
||||
getRenderData: (context={}) ->
|
||||
|
|
|
@ -26,12 +26,6 @@ module.exports = class CocoView extends Backbone.View
|
|||
|
||||
# load progress properties
|
||||
loadProgress:
|
||||
num: 0
|
||||
denom: 0
|
||||
showing: false
|
||||
resources: [] # models and collections
|
||||
requests: [] # jqxhr's
|
||||
somethings: [] # everything else
|
||||
progress: 0
|
||||
|
||||
# Setup, Teardown
|
||||
|
@ -48,6 +42,10 @@ module.exports = class CocoView extends Backbone.View
|
|||
@listenToShortcuts()
|
||||
@updateProgressBar = _.debounce @updateProgressBar, 100
|
||||
# Backbone.Mediator handles subscription setup/teardown automatically
|
||||
|
||||
@listenToOnce(@supermodel, 'loaded-all', ()=>@onLoaded)
|
||||
@listenToOnce(@supermodel, 'superModel:updateProgress', @updateProgress)
|
||||
|
||||
super options
|
||||
|
||||
destroy: ->
|
||||
|
@ -87,8 +85,13 @@ module.exports = class CocoView extends Backbone.View
|
|||
super()
|
||||
return @template if _.isString(@template)
|
||||
@$el.html @template(@getRenderData())
|
||||
|
||||
if not @supermodel.finished()
|
||||
@showLoading()
|
||||
else
|
||||
@hideLoading()
|
||||
|
||||
@afterRender()
|
||||
@showLoading() if @loading()
|
||||
@$el.i18n()
|
||||
@
|
||||
|
||||
|
@ -103,81 +106,18 @@ module.exports = class CocoView extends Backbone.View
|
|||
context
|
||||
|
||||
afterRender: ->
|
||||
|
||||
# Resource and request loading management for any given view
|
||||
|
||||
addResourceToLoad: (modelOrCollection, name, value=1) ->
|
||||
res = {resource:modelOrCollection, value:value, name:name, loaded: modelOrCollection.loaded}
|
||||
@hideLoading()
|
||||
|
||||
@loadProgress.resources.push res
|
||||
@loadProgress.denom += value
|
||||
|
||||
@listenToOnce modelOrCollection, 'sync', ()=>
|
||||
# Sprite builder only works after rendering, if callback creates sprite, we need to update progress first.
|
||||
res.loaded = true
|
||||
@updateProgress(res)
|
||||
|
||||
@listenTo modelOrCollection, 'error', @onResourceLoadFailed
|
||||
@updateProgress(res)
|
||||
|
||||
addRequestToLoad: (jqxhr, name, retryFunc, value=1) ->
|
||||
res = {request:jqxhr, value:value, name: name, retryFunc: retryFunc, loaded:false}
|
||||
|
||||
@loadProgress.requests.push res
|
||||
@loadProgress.denom += value
|
||||
|
||||
jqxhr.done ()=>
|
||||
res.loaded = true
|
||||
@updateProgress(res)
|
||||
|
||||
jqxhr.fail ()=>
|
||||
@onRequestLoadFailed(jqxhr)
|
||||
|
||||
addSomethingToLoad: (name, value=1) ->
|
||||
res = {name: name, value: value, loaded: false}
|
||||
|
||||
@loadProgress.somethings.push res
|
||||
@loadProgress.denom += value
|
||||
|
||||
@updateProgress(res)
|
||||
|
||||
somethingLoaded: (name) ->
|
||||
r = _.find @loadProgress.somethings, {name: name}
|
||||
return console.error 'Could not find something called', name if not r
|
||||
r.loaded = true
|
||||
@updateProgress(r)
|
||||
|
||||
loading: ->
|
||||
return false if @loaded
|
||||
for r in @loadProgress.resources
|
||||
return true if not r.resource.loaded
|
||||
for r in @loadProgress.requests
|
||||
return true if not r.request.status
|
||||
for r in @loadProgress.somethings
|
||||
return true if not r.loaded
|
||||
return false
|
||||
|
||||
updateProgress: (r)=>
|
||||
console.debug 'Loaded', r.name, r.loaded
|
||||
|
||||
denom = @loadProgress.denom
|
||||
@loadProgress.num += r.value if r.loaded
|
||||
num = @loadProgress.num
|
||||
|
||||
progress = if denom then num / denom else 0
|
||||
# sometimes the denominator isn't known from the outset, so make sure the overall progress only goes up
|
||||
updateProgress: (progress)=>
|
||||
@loadProgress.progress = progress if progress > @loadProgress.progress
|
||||
@updateProgressBar()
|
||||
|
||||
if num is denom
|
||||
@onLoaded()
|
||||
@updateProgressBar(progress)
|
||||
|
||||
updateProgressBar: =>
|
||||
prog = "#{parseInt(@loadProgress.progress*100)}%"
|
||||
updateProgressBar: (progress) =>
|
||||
prog = "#{parseInt(progress*100)}%"
|
||||
@$el.find('.loading-screen .progress-bar').css('width', prog)
|
||||
|
||||
onLoaded: ->
|
||||
@render()
|
||||
#@render()
|
||||
|
||||
# Error handling for loading
|
||||
|
||||
|
|
|
@ -15,6 +15,7 @@ module.exports = class LevelLoadingView extends View
|
|||
tip = _.sample(tips)
|
||||
$(tip).removeClass('to-remove')
|
||||
@$el.find('.to-remove').remove()
|
||||
@hideLoading()
|
||||
|
||||
onLevelLoaderProgressChanged: (e) ->
|
||||
@progress = e.progress
|
||||
|
@ -33,7 +34,7 @@ module.exports = class LevelLoadingView extends View
|
|||
_.delay @reallyUnveil, 1000
|
||||
|
||||
reallyUnveil: =>
|
||||
return if @destroyed
|
||||
return if @destroyed or @progress < 1
|
||||
loadingDetails = @$el.find('.loading-details')
|
||||
duration = parseFloat loadingDetails.css 'transition-duration'
|
||||
loadingDetails.css 'top', -loadingDetails.outerHeight(true)
|
||||
|
|
|
@ -86,6 +86,7 @@ module.exports = class PlayLevelView extends View
|
|||
@listenToOnce(@supermodel, 'error', @onLevelLoadError)
|
||||
@saveScreenshot = _.throttle @saveScreenshot, 30000
|
||||
|
||||
@insertSubView @loadingView = new LoadingView {}
|
||||
if @isEditorPreview
|
||||
f = =>
|
||||
@supermodel.shouldSaveBackups = (model) ->
|
||||
|
@ -361,11 +362,11 @@ module.exports = class PlayLevelView extends View
|
|||
.css('transform', "rotate(#{@pointerRotation}rad) translate(-3px, #{@pointerRadialDistance}px)")
|
||||
.css('top', target_top - 50)
|
||||
.css('left', target_left - 50)
|
||||
setTimeout((=>
|
||||
setTimeout(()=>
|
||||
@animatePointer()
|
||||
clearInterval(@pointerInterval)
|
||||
@pointerInterval = setInterval(@animatePointer, 1200)
|
||||
), 1)
|
||||
, 1)
|
||||
|
||||
|
||||
animatePointer: ->
|
||||
|
|
Loading…
Reference in a new issue