Began streaming a mighty stream.

This commit is contained in:
Nick Winter 2014-08-21 16:27:52 -07:00
parent 9a116d81be
commit d55afa77b7
16 changed files with 115 additions and 51 deletions

View file

@ -2,7 +2,7 @@ var window = self;
var Global = self;
importScripts("/javascripts/lodash.js", "/javascripts/aether.js");
console.log("Aether Tome worker has finished importing scripts.");
//console.log("Aether Tome worker has finished importing scripts.");
var aethers = {};
var createAether = function (spellKey, options)

View file

@ -300,7 +300,7 @@ self.setupDebugWorldToRunUntilFrame = function (args) {
}
Math.random = self.debugWorld.rand.randf; // so user code is predictable
Aether.replaceBuiltin("Math", Math);
replacedLoDash = _.runInContext(self);
var replacedLoDash = _.runInContext(self);
for(var key in replacedLoDash)
_[key] = replacedLoDash[key];
}
@ -358,13 +358,34 @@ self.runWorld = function runWorld(args) {
}
Math.random = self.world.rand.randf; // so user code is predictable
Aether.replaceBuiltin("Math", Math);
replacedLoDash = _.runInContext(self);
var replacedLoDash = _.runInContext(self);
for(var key in replacedLoDash)
_[key] = replacedLoDash[key];
self.postMessage({type: 'start-load-frames'});
self.world.loadFrames(self.onWorldLoaded, self.onWorldError, self.onWorldLoadProgress);
};
self.serializeFramesSoFar = function serializeFramesSoFar() {
if(!self.world) return console.error("hmm, no world when we went to serialize some frames?");
var goalStates = self.goalManager.getGoalStates();
var transferableSupported = self.transferableSupported();
var serialized = self.world.serializeFramesSoFar();
if(!serialized) {
console.log("Tried to serialize some frames, but none have been simulated since last time; still at", self.world.framesSerializedSoFar);
return;
}
try {
var message = {type: 'some-frames-serialized', serialized: serialized.serializedWorld, goalStates: goalStates, startFrame: serialized.startFrame, endFrame: serialized.endFrame};
if(transferableSupported)
self.postMessage(message, serialized.transferableObjects);
else
self.postMessage(message);
}
catch(error) {
console.log("World delivery error:", error.toString() + "\n" + error.stack || error.stackTrace);
}
};
self.onWorldLoaded = function onWorldLoaded() {
self.goalManager.worldGenerationEnded();
var goalStates = self.goalManager.getGoalStates();
@ -384,7 +405,7 @@ self.onWorldLoaded = function onWorldLoaded() {
var t2 = new Date();
//console.log("About to transfer", serialized.serializedWorld.trackedPropertiesPerThangValues, serialized.transferableObjects);
try {
var message = {type: 'new-world', serialized: serialized.serializedWorld, goalStates: goalStates};
var message = {type: 'new-world', serialized: serialized.serializedWorld, goalStates: goalStates, startFrame: serialized.startFrame, endFrame: serialized.endFrame};
if(transferableSupported)
self.postMessage(message, serialized.transferableObjects);
else

View file

@ -27,7 +27,8 @@ init = ->
# Set up Backbone.Mediator schemas
setUpDefinitions()
setUpChannels()
Backbone.Mediator.setValidationEnabled document.location.href.search(/codecombat.com/) is -1
#Backbone.Mediator.setValidationEnabled document.location.href.search(/codecombat.com/) is -1
Backbone.Mediator.setValidationEnabled false # STREAM: Should change back
app.initialize()
Backbone.history.start({ pushState: true })
handleNormalUrls()

View file

@ -67,6 +67,8 @@ module.exports = class Angel extends CocoClass
# We pay attention to certain progress indicators as the world loads.
when 'world-load-progress-changed'
Backbone.Mediator.publish 'god:world-load-progress-changed', event.data
unless event.data.progress is 1 or @work.preload or @work.headless or @work.synchronous or @deserializingStreamingFrames
@worker.postMessage func: 'serializeFramesSoFar' # Stream it!
when 'console-log'
@log event.data.args...
when 'user-code-problem'
@ -80,9 +82,16 @@ module.exports = class Angel extends CocoClass
else
@fireWorker()
# We have some of the frames serialized, so let's send the partially simulated world to the Surface.
when 'some-frames-serialized'
console.log "angel received some frames", event.data.serialized, "with goals", event.data.goalStates, "and streaming into world", @shared.streamingWorld
@deserializingStreamingFrames = true
@beholdWorld event.data.serialized, event.data.goalStates, event.data.startFrame, event.data.endFrame, @shared.streamingWorld
# Either the world finished simulating successfully, or we abort the worker.
when 'new-world'
@beholdWorld event.data.serialized, event.data.goalStates
console.log "angel received alll frames", event.data.serialized
@beholdWorld event.data.serialized, event.data.goalStates, event.data.startFrame, event.data.endFrame
when 'abort'
@say 'Aborted.', event.data
clearTimeout @abortTimeout
@ -99,26 +108,33 @@ module.exports = class Angel extends CocoClass
Backbone.Mediator.publish 'god:goals-calculated', goalStates: goalStates
@finishWork() if @shared.headless
beholdWorld: (serialized, goalStates) ->
beholdWorld: (serialized, goalStates, startFrame, endFrame, streamingWorld) ->
return if @aborting
# Toggle BOX2D_ENABLED during deserialization so that if we have box2d in the namespace, the Collides Components still don't try to create bodies for deserialized Thangs upon attachment.
window.BOX2D_ENABLED = false
World.deserialize serialized, @shared.worldClassMap, @shared.lastSerializedWorldFrames, @finishBeholdingWorld(goalStates)
World.deserialize serialized, @shared.worldClassMap, @shared.lastSerializedWorldFrames, @finishBeholdingWorld(goalStates), startFrame, endFrame, streamingWorld
window.BOX2D_ENABLED = true
@shared.lastSerializedWorldFrames = serialized.frames
finishBeholdingWorld: (goalStates) -> (world) =>
return if @aborting
world.findFirstChangedFrame @shared.world
@shared.world = world
errorCount = (t for t in @shared.world.thangs when t.errorsOut).length
Backbone.Mediator.publish 'god:new-world-created', world: world, firstWorld: @shared.firstWorld, errorCount: errorCount, goalStates: goalStates, team: me.team
for scriptNote in @shared.world.scriptNotes
Backbone.Mediator.publish scriptNote.channel, scriptNote.event
@shared.goalManager?.world = world
@finishWork()
finished = world.frames.length is world.totalFrames
if finished
world.findFirstChangedFrame @shared.world
@shared.world = world
Backbone.Mediator.publish 'god:new-world-created', world: world, firstWorld: @shared.firstWorld, goalStates: goalStates, team: me.team if @shared.firstWorld
for scriptNote in @shared.world.scriptNotes
Backbone.Mediator.publish scriptNote.channel, scriptNote.event
@shared.goalManager?.world = world
@finishWork()
else
@shared.streamingWorld = world
#Backbone.Mediator.publish 'god:new-world-created', world: world, firstWorld: @shared.firstWorld, goalStates: goalStates, team: me.team
#Backbone.Mediator.publish 'god:streaming-world-updated', world: world, firstWorld: @shared.firstWorld, goalStates: goalStates, team: me.team
@deserializingStreamingFrames = false
finishWork: ->
@shared.streamingWorld = null
@shared.firstWorld = false
@running = false
_.remove @shared.busyAngels, @
@ -127,6 +143,7 @@ module.exports = class Angel extends CocoClass
finalizePreload: ->
@say 'Finalize preload.'
@worker.postMessage func: 'finalizePreload'
@work.preload = false
infinitelyLooped: =>
@say 'On infinitely looped! Aborting?', @aborting

View file

@ -14,7 +14,7 @@ module.exports = LinkedInHandler = class LinkedInHandler extends CocoClass
onLinkedInLoaded: (e) ->
IN.Event.on IN, 'auth', @onLinkedInAuth
onLinkedInAuth: (e) => console.log 'Authorized with LinkedIn'
onLinkedInAuth: (e) => #console.log 'Authorized with LinkedIn'
constructEmployerAgreementObject: (cb) =>
IN.API.Profile('me')

View file

@ -1,6 +1,6 @@
module.exports = initializeLinkedIn = ->
window.linkedInAsyncInit = ->
console.log 'Linkedin async init success!'
#console.log 'Linkedin async init success!'
Backbone.Mediator.publish 'linkedin-loaded'
linkedInSnippet =

View file

@ -19,6 +19,7 @@ module.exports = class SpriteBoss extends CocoClass
'level-lock-select': 'onSetLockSelect'
'level:restarted': 'onLevelRestarted'
'god:new-world-created': 'onNewWorld'
'god:streaming-world-updated': 'onNewWorld'
'camera:dragged': 'onCameraDragged'
'sprite:loaded': -> @update(true)

View file

@ -63,6 +63,7 @@ module.exports = Surface = class Surface extends CocoClass
'level-set-surface-camera': 'onSetCamera'
'level:restarted': 'onLevelRestarted'
'god:new-world-created': 'onNewWorld'
'god:streaming-world-updated': 'onNewWorld'
'tome:cast-spells': 'onCastSpells'
'level-set-letterbox': 'onSetLetterbox'
'application:idle-changed': 'onIdleChanged'
@ -413,7 +414,7 @@ module.exports = Surface = class Surface extends CocoClass
@surfaceLayer.addChild @cameraBorder = new CameraBorder bounds: @camera.bounds
@screenLayer.addChild new Letterbox canvasWidth: canvasWidth, canvasHeight: canvasHeight
@spriteBoss = new SpriteBoss camera: @camera, surfaceLayer: @surfaceLayer, surfaceTextLayer: @surfaceTextLayer, world: @world, thangTypes: @options.thangTypes, choosing: @options.choosing, navigateToSelection: @options.navigateToSelection, showInvisible: @options.showInvisible
@castingScreen ?= new CastingScreen camera: @camera, layer: @screenLayer
#@castingScreen ?= new CastingScreen camera: @camera, layer: @screenLayer # Not needed with world streaming.
@playbackOverScreen ?= new PlaybackOverScreen camera: @camera, layer: @screenLayer
@stage.enableMouseOver(10)
@stage.addEventListener 'stagemousemove', @onMouseMove

View file

@ -10,7 +10,7 @@ WorldScriptNote = require './world_script_note'
{now, consolidateThangs, typedArraySupport} = require './world_utils'
Component = require 'lib/world/component'
System = require 'lib/world/system'
PROGRESS_UPDATE_INTERVAL = 200
PROGRESS_UPDATE_INTERVAL = 100
DESERIALIZATION_INTERVAL = 20
ITEM_ORIGINAL = '53e12043b82921000051cdf9'
@ -21,11 +21,13 @@ module.exports = class World
preloading: false # Whether we are just preloading a world in case we soon cast it
debugging: false # Whether we are just rerunning to debug a world we've already cast
headless: false # Whether we are just simulating for goal states instead of all serialized results
framesSerializedSoFar: 0
apiProperties: ['age', 'dt']
constructor: (@userCodeMap, classMap) ->
# classMap is needed for deserializing Worlds, Thangs, and other classes
@classMap = classMap ? {Vector: Vector, Rectangle: Rectangle, Thang: Thang, Ellipse: Ellipse, LineSegment: LineSegment}
Thang.resetThangIDs()
@aRandomID = Math.random()
@userCodeMap ?= {}
@thangs = []
@ -66,7 +68,6 @@ module.exports = class World
@thangMap[thang.id] = thang
thangDialogueSounds: ->
if @frames.length < @totalFrames then throw new Error('World should be over before grabbing dialogue')
[sounds, seen] = [[], {}]
for frame in @frames
for thangID, state of frame.thangStateMap
@ -287,9 +288,17 @@ module.exports = class World
addTrackedProperties: (props...) ->
@trackedProperties = (@trackedProperties ? []).concat props
serialize: ->
serializeFramesSoFar: ->
return null if @frames.length is @framesSerializedSoFar
serialized = @serialize @framesSerializedSoFar, @frames.length
@framesSerializedSoFar = @frames.length
serialized
serialize: (startFrame=0, endFrame=null) ->
# Code hotspot; optimize it
if @frames.length < @totalFrames then throw new Error('World Should Be Over Before Serialization')
if not endFrame? and @frames.length < @totalFrames then throw new Error('World Should Be Over Before Serialization')
endFrame ?= @totalFrames
console.log "... world serializing frames from", startFrame, "to", endFrame
[transferableObjects, nontransferableObjects] = [0, 0]
o = {totalFrames: @totalFrames, maxTotalFrames: @maxTotalFrames, frameRate: @frameRate, dt: @dt, victory: @victory, userCodeMap: {}, trackedProperties: {}}
o.trackedProperties[prop] = @[prop] for prop in @trackedProperties or []
@ -305,19 +314,20 @@ module.exports = class World
o.trackedPropertiesPerThangKeys = []
o.trackedPropertiesPerThangTypes = []
trackedPropertiesPerThangValues = [] # We won't send these, just the offsets and the storage buffer
o.trackedPropertiesPerThangValuesOffsets = [] # Needed to reconstruct ArrayBufferViews on other end, since Firefox has bugs transfering those: https://bugzilla.mozilla.org/show_bug.cgi?id=841904 and https://bugzilla.mozilla.org/show_bug.cgi?id=861925
o.trackedPropertiesPerThangValuesOffsets = [] # Needed to reconstruct ArrayBufferViews on other end, since Firefox has bugs transfering those: https://bugzilla.mozilla.org/show_bug.cgi?id=841904 and https://bugzilla.mozilla.org/show_bug.cgi?id=861925 # Actually, as of January 2014, it should be fixed.
transferableStorageBytesNeeded = 0
nFrames = @frames.length
nFrames = endFrame - startFrame
streaming = nFrames < @totalFrames
for thang in @thangs
# Don't serialize empty trackedProperties for stateless Thangs which haven't changed (like obstacles).
# Check both, since sometimes people mark stateless Thangs but don't change them, and those should still be tracked, and the inverse doesn't work on the other end (we'll just think it doesn't exist then).
continue if thang.stateless and not _.some(thang.trackedPropertiesUsed, Boolean)
continue if thang.stateless and not _.some(thang.trackedPropertiesUsed, Boolean) and not streaming
o.trackedPropertiesThangIDs.push thang.id
trackedPropertiesIndices = []
trackedPropertiesKeys = []
trackedPropertiesTypes = []
for used, propIndex in thang.trackedPropertiesUsed
continue unless used
continue unless used or streaming
trackedPropertiesIndices.push propIndex
trackedPropertiesKeys.push thang.trackedPropertiesKeys[propIndex]
trackedPropertiesTypes.push thang.trackedPropertiesTypes[propIndex]
@ -357,8 +367,8 @@ module.exports = class World
t1 = now()
o.frameHashes = []
for frame, frameIndex in @frames
o.frameHashes.push frame.serialize(frameIndex, o.trackedPropertiesThangIDs, o.trackedPropertiesPerThangIndices, o.trackedPropertiesPerThangTypes, trackedPropertiesPerThangValues, o.specialValuesToKeys, o.specialKeysToValues)
for frameIndex in [startFrame ... endFrame]
o.frameHashes.push @frames[frameIndex].serialize(frameIndex, o.trackedPropertiesThangIDs, o.trackedPropertiesPerThangIndices, o.trackedPropertiesPerThangTypes, trackedPropertiesPerThangValues, o.specialValuesToKeys, o.specialKeysToValues)
t2 = now()
unless typedArraySupport
@ -368,28 +378,29 @@ module.exports = class World
flattened.push value
o.storageBuffer = flattened
#console.log 'Allocating memory:', (t1 - t0).toFixed(0), 'ms; assigning values:', (t2 - t1).toFixed(0), 'ms, so', ((t2 - t1) / @frames.length).toFixed(3), 'ms per frame'
#console.log 'Allocating memory:', (t1 - t0).toFixed(0), 'ms; assigning values:', (t2 - t1).toFixed(0), 'ms, so', ((t2 - t1) / nFrames).toFixed(3), 'ms per frame for', nFrames, 'frames'
#console.log 'Got', transferableObjects, 'transferable objects and', nontransferableObjects, 'nontransferable; stored', transferableStorageBytesNeeded, 'bytes transferably'
o.thangs = (t.serialize() for t in @thangs.concat(@extraneousThangs ? []))
o.scriptNotes = (sn.serialize() for sn in @scriptNotes)
if o.scriptNotes.length > 200
console.log 'Whoa, serializing a lot of WorldScriptNotes here:', o.scriptNotes.length
{serializedWorld: o, transferableObjects: [o.storageBuffer]}
{serializedWorld: o, transferableObjects: [o.storageBuffer], startFrame: startFrame, endFrame: endFrame}
@deserialize: (o, classMap, oldSerializedWorldFrames, finishedWorldCallback) ->
@deserialize: (o, classMap, oldSerializedWorldFrames, finishedWorldCallback, startFrame, endFrame, streamingWorld) ->
# Code hotspot; optimize it
#console.log 'Deserializing', o, 'length', JSON.stringify(o).length
#console.log JSON.stringify(o)
#console.log 'Got special keys and values:', o.specialValuesToKeys, o.specialKeysToValues
perf = {}
perf.t0 = now()
w = new World o.userCodeMap, classMap
nFrames = endFrame - startFrame
w = streamingWorld ? new World o.userCodeMap, classMap
[w.totalFrames, w.maxTotalFrames, w.frameRate, w.dt, w.scriptNotes, w.victory] = [o.totalFrames, o.maxTotalFrames, o.frameRate, o.dt, o.scriptNotes ? [], o.victory]
w[prop] = val for prop, val of o.trackedProperties
perf.t1 = now()
w.thangs = (Thang.deserialize(thang, w, classMap) for thang in o.thangs)
w.thangs = (Thang.deserialize(thang, w, classMap) for thang in o.thangs) # TODO: just do the new ones?
w.setThang thang for thang in w.thangs
w.scriptNotes = (WorldScriptNote.deserialize(sn, w, classMap) for sn in o.scriptNotes)
perf.t2 = now()
@ -400,7 +411,7 @@ module.exports = class World
o.trackedPropertiesPerThangValues.push (trackedPropertiesValues = [])
trackedPropertiesValuesOffsets = o.trackedPropertiesPerThangValuesOffsets[thangIndex]
for type, propIndex in trackedPropertyTypes
storage = ThangState.createArrayForType(type, o.totalFrames, o.storageBuffer, trackedPropertiesValuesOffsets[propIndex])[0]
storage = ThangState.createArrayForType(type, nFrames, o.storageBuffer, trackedPropertiesValuesOffsets[propIndex])[0]
unless typedArraySupport
# This could be more efficient
i = trackedPropertiesValuesOffsets[propIndex]
@ -409,26 +420,30 @@ module.exports = class World
perf.t3 = now()
perf.batches = 0
w.frames = []
_.delay @deserializeSomeFrames, 1, o, w, finishedWorldCallback, perf
w.frames = [] unless streamingWorld
clearTimeout @deserializationTimeout if @deserializationTimeout
@deserializationTimeout = _.delay @deserializeSomeFrames, 1, o, w, finishedWorldCallback, perf, startFrame, endFrame
# Spread deserialization out across multiple calls so the interface stays responsive
@deserializeSomeFrames: (o, w, finishedWorldCallback, perf) =>
@deserializeSomeFrames: (o, w, finishedWorldCallback, perf, startFrame, endFrame) =>
++perf.batches
startTime = now()
for frameIndex in [w.frames.length ... o.totalFrames]
for frameIndex in [w.frames.length ... endFrame]
w.frames.push WorldFrame.deserialize(w, frameIndex, o.trackedPropertiesThangIDs, o.trackedPropertiesThangs, o.trackedPropertiesPerThangKeys, o.trackedPropertiesPerThangTypes, o.trackedPropertiesPerThangValues, o.specialKeysToValues, o.frameHashes[frameIndex])
if (now() - startTime) > DESERIALIZATION_INTERVAL
_.delay @deserializeSomeFrames, 1, o, w, finishedWorldCallback, perf
console.log " Deserialization not finished, let's do it again soon. Have:", w.frames.length, ", wanted from", startFrame, "to", endFrame
@deserializationTimeout = _.delay @deserializeSomeFrames, 1, o, w, finishedWorldCallback, perf, startFrame, endFrame
return
@finishDeserializing w, finishedWorldCallback, perf
@deserializationTimeout = null
@finishDeserializing w, finishedWorldCallback, perf, startFrame, endFrame
@finishDeserializing: (w, finishedWorldCallback, perf) ->
@finishDeserializing: (w, finishedWorldCallback, perf, startFrame, endFrame) ->
perf.t4 = now()
nFrames = endFrame - startFrame
w.ended = true
w.getFrame(w.totalFrames - 1).restoreState()
w.getFrame(endFrame - 1).restoreState()
perf.t5 = now()
console.log 'Deserialization:', (perf.t5 - perf.t0).toFixed(0) + 'ms (' + ((perf.t5 - perf.t0) / w.frames.length).toFixed(3) + 'ms per frame).', perf.batches, 'batches.'
console.log 'Deserialization:', (perf.t5 - perf.t0).toFixed(0) + 'ms (' + ((perf.t5 - perf.t0) / nFrames).toFixed(3) + 'ms per frame).', perf.batches, 'batches.'
if false
console.log ' Deserializing--constructing new World:', (perf.t1 - perf.t0).toFixed(2) + 'ms'
console.log ' Deserializing--Thangs and ScriptNotes:', (perf.t2 - perf.t1).toFixed(2) + 'ms'

View file

@ -111,7 +111,7 @@ module.exports = class Level extends CocoModel
visit = (c) ->
return if c in sorted
lc = _.find levelComponents, {original: c.original}
console.error thang.id or thang.name, 'couldn\'t find lc for', c, 'of', levelComponents unless lc
#console.error thang.id or thang.name, 'couldn\'t find lc for', c, 'of', levelComponents unless lc # STREAM: uncomment
return unless lc
if lc.name is 'Programmable'
# Programmable always comes last

View file

@ -188,7 +188,7 @@ module.exports = class ThangType extends CocoModel
portrait = if portrait then '(Portrait)' else ''
name = _.string.rpad @get('name'), 20
time = _.string.lpad '' + new Date().getTime() - startTime, 6
console.debug "Built sheet: #{name} #{time}ms #{kind} #{portrait}"
#console.debug "Built sheet: #{name} #{time}ms #{kind} #{portrait}" # STREAM: uncomment
spriteSheetKey: (options) ->
colorConfigs = []
@ -276,7 +276,7 @@ module.exports = class ThangType extends CocoModel
@get('components') or [],
(compRef) -> compRef.original is LevelComponent.ItemID)
return itemComponentRef?.config?.slots or []
getFrontFacingStats: ->
stats = []
for component in @get('components') or []
@ -317,4 +317,4 @@ module.exports = class ThangType extends CocoModel
snippets = config.programmableSnippets
if snippets.length
stats.push { name: 'Snippets', value: snippets.join(', ') }
stats
stats

View file

@ -11,5 +11,8 @@ module.exports =
'god:new-world-created':
{} # TODO schema
'god:streaming-world-updated':
{} # TODO schema
'god:world-load-progress-changed':
{} # TODO schema

View file

@ -55,6 +55,7 @@ module.exports = class SpectateLevelView extends RootView
'surface:world-set-up': 'onSurfaceSetUpNewWorld'
'level:set-team': 'setTeam'
'god:new-world-created': 'loadSoundsForWorld'
'god:streaming-world-updated': 'loadSoundsForWorld'
'next-game-pressed': 'onNextGamePressed'
'level:started': 'onLevelStarted'
'level:loading-view-unveiled': 'onLoadingViewUnveiled'

View file

@ -22,6 +22,7 @@ module.exports = class LevelPlaybackView extends CocoView
'level-toggle-grid': 'onToggleGrid'
'surface:frame-changed': 'onFrameChanged'
'god:new-world-created': 'onNewWorld'
'god:streaming-world-updated': 'onNewWorld' # Maybe?
'level-set-letterbox': 'onSetLetterbox'
'tome:cast-spells': 'onCastSpells'
@ -329,7 +330,9 @@ module.exports = class LevelPlaybackView extends CocoView
return if @shouldIgnore()
Backbone.Mediator.publish 'level-set-time', ratio: ratio, scrubDuration: duration
shouldIgnore: -> return @disabled or @casting or false
shouldIgnore: ->
#return @disabled or @casting or false # STREAM: figure this out
return false
onTogglePlay: (e) ->
e?.preventDefault()

View file

@ -53,6 +53,7 @@ module.exports = class PlayLevelView extends RootView
'level-disable-controls': 'onDisableControls'
'level-enable-controls': 'onEnableControls'
'god:new-world-created': 'onNewWorld'
'god:streaming-world-updated': 'onNewWorld'
'god:infinite-loop': 'onInfiniteLoop'
'level-reload-from-data': 'onLevelReloadFromData'
'level-reload-thang-type': 'onLevelReloadThangType'

View file

@ -128,10 +128,10 @@ module.exports = class SpellView extends CocoView
# passEvent: true # https://github.com/ajaxorg/ace/blob/master/lib/ace/keyboard/keybinding.js#L114
# No easy way to selectively cancel shift+space, since we don't get access to the event.
# Maybe we could temporarily set ourselves to read-only if we somehow know that a script is active?
exec: =>
exec: =>
if @scriptRunning
Backbone.Mediator.publish 'level:shift-space-pressed'
else
else
@ace.insert ' '
addCommand