Experimental support for preloading worlds when we would have autocast if manual cast is on.

This commit is contained in:
Nick Winter 2014-05-11 17:42:32 -07:00
parent 630c3b7d57
commit 74ef9bc987
11 changed files with 62 additions and 35 deletions

View file

@ -440,6 +440,10 @@ self.reportIn = function reportIn() {
self.postMessage({type: 'report-in'});
};
self.finalizePreload = function finalizePreload() {
self.world.finalizePreload(self.onWorldLoaded);
};
self.addEventListener('message', function(event) {
self[event.data.func](event.data.args);
});

View file

@ -112,6 +112,9 @@ module.exports = class Angel extends CocoClass
_.remove @shared.busyAngels, @
@doWork()
finalizePreload: ->
@worker.postMessage func: 'finalizePreload'
infinitelyLooped: =>
return if @aborting
problem = type: "runtime", level: "error", id: "runtime_InfiniteLoop", message: "Code never finished. It's either really slow or has an infinite loop."
@ -123,12 +126,12 @@ module.exports = class Angel extends CocoClass
return if @aborting
return @say "Not initialized for work yet." unless @initialized
if @shared.workQueue.length
work = @shared.workQueue.shift()
return _.defer @simulateSync, work if work.synchronous
@work = @shared.workQueue.shift()
return _.defer @simulateSync, @work if @work.synchronous
@say "Running world..."
@running = true
@shared.busyAngels.push @
@worker.postMessage func: 'runWorld', args: work
@worker.postMessage func: 'runWorld', args: @work
clearTimeout @purgatoryTimer
@purgatoryTimer = setInterval @testWorker, @infiniteLoopIntervalDuration
else
@ -139,6 +142,7 @@ module.exports = class Angel extends CocoClass
return unless @worker and @running
@say "Aborting..."
@running = false
@work = null
_.remove @shared.busyAngels, @
@abortTimeout = _.delay @fireWorker, @abortTimeoutDuration
@aborting = true

View file

@ -39,7 +39,7 @@ module.exports = class God extends CocoClass
_.delay (=> new Angel @angelsShare unless @destroyed), 250 * i for i in [0 ... angelCount]
destroy: ->
angel.destroy() for angel in @angelsShare.angels
angel.destroy() for angel in @angelsShare.angels.slice()
@angelsShare.goalManager?.destroy()
@debugWorker?.terminate()
@debugWorker?.removeEventListener 'message', @onDebugWorkerMessage
@ -54,10 +54,24 @@ module.exports = class God extends CocoClass
createWorld: (spells, preload=false) ->
console.log "#{@nick}: Let there be light upon #{@level.name}!"
angel.abort() for angel in @angelsShare.busyAngels # We really only ever want one world calculated per God.
userCodeMap = @getUserCodeMap spells
# We only want one world being simulated, so we abort other angels, unless we had one preloading this very code.
hadPreloader = false
for angel in @angelsShare.busyAngels
isPreloading = angel.running and angel.work.preload and _.isEqual angel.work.userCodeMap, userCodeMap, (a, b) ->
return a.raw is b.raw if a?.raw? and b?.raw?
undefined # Let default equality test suffice.
if not hadPreloader and isPreloading
angel.finalizePreload()
hadPreloader = true
else
angel.abort()
return if hadPreloader
@angelsShare.workQueue = []
@angelsShare.workQueue.push
userCodeMap: @getUserCodeMap(spells)
userCodeMap: userCodeMap
level: @level
goals: @angelsShare.goalManager?.getGoals()
headless: @angelsShare.headless

View file

@ -14,9 +14,9 @@ module.exports = class CastingScreen extends CocoClass
console.error @toString(), "needs a camera." unless @camera
console.error @toString(), "needs a layer." unless @layer
@build()
onCastingBegins: -> @show()
onCastingEnds: -> @hide()
onCastingBegins: (e) -> @show() unless e.preload
onCastingEnds: (e) -> @hide()
toString: -> "<CastingScreen>"

View file

@ -218,7 +218,7 @@ module.exports = class SpriteBoss extends CocoClass
@world = @options.world = e.world
@play()
onCastSpells: -> @stop()
onCastSpells: (e) -> @stop() unless e.preload
play: ->
sprite.play() for sprite in @spriteArray

View file

@ -331,7 +331,8 @@ module.exports = Surface = class Surface extends CocoClass
else
performToggle()
onCastSpells: ->
onCastSpells: (e) ->
return if e.preload
@casting = true
@wasPlayingWhenCastingBegan = @playing
Backbone.Mediator.publish 'level-set-playing', { playing: false }

View file

@ -119,6 +119,10 @@ module.exports = class World
loadProgressCallback? 1
loadedCallback()
finalizePreload: (loadedCallback) ->
@preloading = false
loadedCallback() if @ended
abort: ->
@aborted = true

View file

@ -183,7 +183,8 @@ module.exports = class PlaybackView extends View
onEditEditorConfig: ->
@openModalView(new EditorConfigModal())
onCastSpells: ->
onCastSpells: (e) ->
return if e.preload
@casting = true
@$progressScrubber.slider('disable', true)

View file

@ -57,6 +57,7 @@ module.exports = class CastButtonView extends View
@updateCastButton()
onCastSpells: (e) ->
return if e.preload
@casting = true
Backbone.Mediator.publish 'play-sound', trigger: 'cast', volume: 0.5
@updateCastButton()

View file

@ -51,7 +51,7 @@ module.exports = class SpellView extends View
@session = options.session
@listenTo(@session, 'change:multiplayer', @onMultiplayerChanged)
@spell = options.spell
@problems = {}
@problems = []
@writable = false unless me.team in @spell.permissions.readwrite # TODO: make this do anything
@highlightCurrentLine = _.throttle @highlightCurrentLine, 100
@ -289,30 +289,21 @@ module.exports = class SpellView extends View
callback() for callback in onAnyChange # Then these
@aceDoc.on 'change', @onCodeChangeMetaHandler
setRecompileNeeded: (needed=true) =>
if needed
@recompileNeeded = needed # and @recompileValid # todo, remove if not caring about validity
else
@recompileNeeded = false
setRecompileNeeded: (@recompileNeeded) =>
onCursorActivity: =>
@currentAutocastHandler?()
# Design for a simpler system?
# * Turn off ACE's JSHint worker
# * Keep Aether linting, debounced, on any significant change
# - Don't send runtime errors from in-progress worlds
# - All problems just vanish when you make any change to the code
# * You wouldn't accept any Aether updates/runtime information/errors unless its code was current when you got it
# * Store the last run Aether in each spellThang and use it whenever its code actually is current
# This suffers from the problem that any whitespace/comment changes will lose your info, but what else
# could you do other than somehow maintain a mapping from current to original code locations?
# I guess you could use dynamic markers for problem ranges and keep annotations/alerts in when insignificant
# * Store the last run Aether in each spellThang and use it whenever its code actually is current.
# Use dynamic markers for problem ranges and keep annotations/alerts in when insignificant
# changes happen, but always treat any change in the (trimmed) number of lines as a significant change.
# Ooh, that's pretty nice. Gets you most of the way there and is simple.
# - All problems have a master representation as a Problem, and we can easily generate all Problems from
# any Aether instance. Then when we switch contexts in any way, we clear, recreate, and reapply the Problems.
# * Problem alerts will have their own templated ProblemAlertViews
# * Problem alerts have their own templated ProblemAlertViews.
# * We'll only show the first problem alert, and it will always be at the bottom.
# Annotations and problem ranges can show all, I guess.
# * The editor will reserve space for one annotation as a codeless area.
@ -366,6 +357,7 @@ module.exports = class SpellView extends View
displayAether: (aether) ->
@displayedAether = aether
isCast = not _.isEmpty(aether.metrics) or _.some aether.problems.errors, {type: 'runtime'}
problem.destroy() for problem in @problems # Just in case another problem was added since clearAetherDisplay() ran.
@problems = []
annotations = []
seenProblemKeys = {}
@ -393,7 +385,6 @@ module.exports = class SpellView extends View
# But the error message display was delayed, so now trying:
# - Go after specified delay if a) and not b) or c)
guessWhetherFinished: (aether) ->
#@recompileValid = not aether.getAllProblems().length
valid = not aether.getAllProblems().length
cursorPosition = @ace.getCursorPosition()
currentLine = _.string.rtrim(@aceDoc.$lines[cursorPosition.row].replace(/[ \t]*\/\/[^"']*/g, '')) # trim // unless inside "
@ -402,15 +393,22 @@ module.exports = class SpellView extends View
#console.log "finished?", valid, endOfLine, beginningOfLine, cursorPosition, currentLine.length, aether, new Date() - 0, currentLine
if valid and (endOfLine or beginningOfLine)
if @autocastDelay > 60000
null #@cast true
@preload()
else
@recompile()
#console.log "recompile now!"
#else if not valid
# # if this works, we can get rid of all @recompileValid logic
# console.log "not valid, but so we'll wait to do it in", @autocastDelay + "ms"
#else
# console.log "valid but not at end of line; recompile in", @autocastDelay + "ms"
preload: ->
# Send this code over to the God for preloading, but don't change the cast state.
oldSource = @spell.source
oldSpellThangAethers = {}
for thangID, spellThang of @spell.thangs
oldSpellThangAethers[thangID] = spellThang.aether.serialize() # Get raw, pure, and problems
@spell.transpile @getSource()
@cast true
@spell.source = oldSource
for thangID, spellThang of @spell.thangs
for key, value of oldSpellThangAethers[thangID]
spellThang.aether[key] = value
onSpellChanged: (e) ->
@spellHasChanged = true

View file

@ -205,7 +205,7 @@ module.exports = class TomeView extends View
reloadAllCode: ->
spell.view.reloadCode false for spellKey, spell of @spells when spell.team is me.team or (spell.team in ["common", "neutral", null])
Backbone.Mediator.publish 'tome:cast-spells', spells: @spells
Backbone.Mediator.publish 'tome:cast-spells', spells: @spells, preload: false
updateLanguageForAllSpells: ->
spell.updateLanguageAether() for spellKey, spell of @spells