Added spell toolbar view. Made many time-travel debugger improvements.

This commit is contained in:
Nick Winter 2014-01-21 09:03:04 -08:00
parent f87c3a37e8
commit 6ea4f645f8
18 changed files with 276 additions and 56 deletions

View file

@ -8,6 +8,8 @@ CocoView = require 'views/kinds/CocoView'
preventBackspace = (event) ->
if event.keyCode is 8 and not elementAcceptsKeystrokes(event.srcElement or event.target)
event.preventDefault()
else if (key.ctrl or key.command) and not key.alt and event.keyCode in [219, 221] # prevent Ctrl/Cmd + [ / ]
event.preventDefault()
elementAcceptsKeystrokes = (el) ->
# http://stackoverflow.com/questions/1495219/how-can-i-prevent-the-backspace-key-from-navigating-back

View file

@ -152,7 +152,7 @@ class Angel
@ids[@lastID]
# https://github.com/codecombat/codecombat/issues/81 -- TODO: we need to wait for worker initialization first
infiniteLoopIntervalDuration: 15000 # check this often (must be more than the others added)
infiniteLoopIntervalDuration: 1500000 # check this often (must be more than the others added)
infiniteLoopTimeoutDuration: 1500 # wait this long when we check
abortTimeoutDuration: 500 # give in-process or dying workers this long to give up
constructor: (@god) ->

View file

@ -140,7 +140,7 @@ module.exports = class Mark extends CocoClass
worldZ = @sprite.thang.pos.z - @sprite.thang.depth / 2
@mark.alpha = 0.451 / Math.sqrt(worldZ / 2 + 1)
else
pos ?= @sprite.displayObject
pos ?= @sprite?.displayObject
@mark.x = pos.x
@mark.y = pos.y
if @name is 'highlight'

View file

@ -259,6 +259,6 @@ module.exports = class SpriteBoss extends CocoClass
target = thang?.target
targetPos = thang?.targetPos
targetPos = null if targetPos?.isZero?() # Null targetPos get serialized as (0, 0, 0)
@targetMark.toggle target or targetPos
@targetMark.setSprite if target then @sprites[target.id] else null
@targetMark.toggle @targetMark.sprite or targetPos
@targetMark.update if targetPos then @camera.worldToSurface targetPos else null

View file

@ -30,11 +30,13 @@
color: white
#cast-button-view
display: none
.cast-button-group
position: absolute
// Bottom/right margins must appear to scroll to size of any paper gashes
top: 2%
right: 4%
top: 55px
left: 20px
z-index: 2
@include opacity(77)
.button-progress-overlay

View file

@ -32,10 +32,11 @@
.ace_editor
@include box-sizing(border-box)
margin-top: 40px
width: 100%
height: 90%
height: -webkit-calc(100% - 60px)
height: calc(100% - 60px)
height: 83%
height: -webkit-calc(100% - 60px - 40px)
height: calc(100% - 60px - 40px)
position: relative
background-color: transparent
line-height: 20px
@ -79,3 +80,6 @@
.ace_bracket
// Override faint gray
border-color: #8FF
.ace_identifier
background-color: rgba(255, 128, 128, 0.15)

View file

@ -1,12 +1,10 @@
@import "../../../bootstrap/mixins"
.tome-debug-view
.spell-debug-view
position: absolute
z-index: 9001
max-width: 400px
padding: 10px
background: transparent url(/images/level/popover_background.png)
background-size: 100% 100%
border: 0
@include box-shadow(0 0 0 #000)

View file

@ -0,0 +1,54 @@
@import "../../../bootstrap/mixins"
.spell-toolbar-view
position: absolute
z-index: 2
top: 2px
left: 5px
box-sizing: border-box
padding-left: 150px
height: 36px
width: 95%
width: -webkit-calc(95% - 5px)
width: calc(95% - 5px)
background-color: rgba(100, 45, 210, 0.05)
.spell-progress
position: relative
height: 100%
width: 50%
display: inline-block
.progress
position: absolute
left: 0px
top: 8px
bottom: 0px
width: 100%
cursor: pointer
overflow: visible
.bar
@include transition(width .0s linear)
position: relative
pointer-events: none
background-color: #67A4C8
width: 50%
.scrubber-handle
position: absolute
pointer-events: none
right: -16px
top: -7px
background: transparent url(/images/level/playback_thumb.png)
width: 32px
height: 32px
.btn-group
// I don't know, I can figure this out for real later
margin: -26px 0 0 18px
.metrics
display: inline-block
margin: -30px 0 0 10px
vertical-align: middle

View file

@ -1,4 +1,4 @@
button.btn.btn-mini.btn-inverse#play-button.playing(title="Alt-P: Toggle level play/pause")
button.btn.btn-mini.btn-inverse#play-button.playing(title="Ctrl/Cmd + P: Toggle level play/pause")
i.icon-play.icon-white.big
i.icon-pause.icon-white.big
i.icon-repeat.icon-white.big
@ -16,19 +16,19 @@ button.btn.btn-mini.btn-inverse#music-button(title="Toggle Music")
.scrubber-handle
.btn-group.dropup#playback-settings
button.btn.btn-mini.btn-inverse#zoom-in-button
button.btn.btn-mini.btn-inverse#zoom-in-button(title="Zoom In (or scroll down)")
i.icon-zoom-in.icon-white
button.btn.btn-mini.btn-inverse#zoom-out-button
button.btn.btn-mini.btn-inverse#zoom-out-button(title="Zoom Out (or scroll up)")
i.icon-zoom-out.icon-white
button.btn.btn-mini.btn-inverse.dropdown-toggle(data-toggle="dropdown")#settings-button
i.icon-cog.icon-white.big
ul.dropdown-menu
if me.get('name') == "Nick"
li(title="\\: Toggle debug display").selectable#debug-toggle
li(title="Ctrl/Cmd + \\: Toggle debug display").selectable#debug-toggle
i.icon-globe
| Debug Mode
i.icon-ok.hide
li(title="G: Toggle grid display").selectable#grid-toggle
li(title="Ctrl/Cmd + G: Toggle grid display").selectable#grid-toggle
i.icon-th
span(data-i18n="play_level.grid") Grid
i.icon-ok.hide

View file

@ -1 +0,0 @@
code a debug view

View file

@ -0,0 +1,2 @@
pre
code

View file

@ -0,0 +1,22 @@
.spell-progress
.progress
.bar
.scrubber-handle
.btn-group
button.btn.btn-mini.btn-inverse.banner.step-backward(title="Ctrl/Cmd + Alt + [: Step Backward")
i.icon-arrow-left.icon-white
button.btn.btn-mini.btn-inverse.banner.step-forward(title="Ctrl/Cmd + Alt + ]: Step Forward")
i.icon-arrow-right.icon-white
.metrics
code.statements-metric
span.metric.statement-index
| /
span.metric.statements-executed
span.metric.statements-executed-total
|
code.calls-metric
span.metric.call-index
| /
span.metric.calls-executed

View file

@ -24,7 +24,6 @@ module.exports = class CastButtonView extends View
context.castShortcutVerbose = @castShortcutVerbose
context
afterRender: ->
super()
# TODO: use a User setting instead of localStorage

View file

@ -64,13 +64,13 @@ module.exports = class Spell
functionParameters: @parameters
yieldConditionally: thang.plan?
requiresThis: thang.requiresThis
includeFlow:
callIndex: 9001
includeFlow: true
#callIndex: 0
#timelessVariables: ['i']
#statementIndex: 9001
#if @name is 'chooseAction' or not (me.team in @permissions.readwrite) or thang.id is 'Thoktar' # Gridmancer can't handle it
# #console.log "Turning off includeFlow for", @spellKey
# aetherOptions.includeFlow = false
if not (me.team in @permissions.readwrite)# or @name is 'chooseAction' or thang.id is 'Thoktar' # Gridmancer can't handle it
#console.log "Turning off includeFlow for", @spellKey
aetherOptions.includeFlow = false
aether = new Aether aetherOptions
aether

View file

@ -1,13 +1,11 @@
View = require 'views/kinds/CocoView'
template = require 'templates/play/level/tome/debug'
template = require 'templates/play/level/tome/spell_debug'
Range = ace.require("ace/range").Range
module.exports = class DebugView extends View
className: 'tome-debug-view'
className: 'spell-debug-view'
template: template
subscriptions: {}
events: {}
constructor: (options) ->
@ -18,20 +16,30 @@ module.exports = class DebugView extends View
afterRender: ->
super()
@ace.on "mousemove", @onMouseMove
#@ace.on "click", onClick # same ACE API as mousemove
setVariableStates: (@variableStates) ->
@update()
onMouseMove: (e) =>
pos = e.getDocumentPosition()
token = e.editor.session.getTokenAt pos.row, pos.column
column = pos.column
until column < 0
if token = e.editor.session.getTokenAt pos.row, column
break if token.type is 'identifier'
column = token.start - 1
else
--column
if token?.type is 'identifier' and token.value of @variableStates
@variable = token.value
@pos = {left: e.domEvent.offsetX + 50, top: e.domEvent.offsetY + 10}
@pos = {left: e.domEvent.offsetX + 50, top: e.domEvent.offsetY + 50}
@markerRange = new Range pos.row, token.start, pos.row, token.start + token.value.length
else
@variable = null
@markerRange = null
@variable = @markerRange = null
@update()
onMouseOut: (e) =>
@variable = @markerRange = null
@update()
update: ->

View file

@ -0,0 +1,94 @@
View = require 'views/kinds/CocoView'
template = require 'templates/play/level/tome/spell_toolbar'
module.exports = class SpellToolbarView extends View
className: 'spell-toolbar-view'
template: template
subscriptions:
'spell-step-backward': 'onStepBackward'
'spell-step-forward': 'onStepForward'
events:
'mousemove .progress': 'onProgressHover'
'mouseout .progress': 'onProgressMouseOut'
'click .step-backward': 'onStepBackward'
'click .step-forward': 'onStepForward'
constructor: (options) ->
super options
@ace = options.ace
afterRender: ->
super()
setStatementIndex: (statementIndex) ->
return unless total = @callState?.statementsExecuted
@statementIndex = Math.min(total - 1, Math.max(0, statementIndex))
@statementRatio = @statementIndex / (total - 1)
@statementTime = @callState.statements[@statementIndex].userInfo.time
@$el.find('.bar').css('width', 100 * @statementRatio + '%')
Backbone.Mediator.publish 'tome:spell-statement-index-updated', statementIndex: @statementIndex, ace: @ace
@$el.find('.step-backward').prop('disabled', @statementIndex is 0)
@$el.find('.step-forward').prop('disabled', @statementIndex is total - 1)
@updateMetrics()
updateMetrics: ->
statementsExecuted = @callState.statementsExecuted
$metrics = @$el.find('.metrics')
if @metrics.callsExecuted > 1
$metrics.find('.call-index').text @callIndex + 1
$metrics.find('.calls-executed').text @metrics.callsExecuted
$metrics.find('.calls-metric').show().attr('title', "Method call #{@callIndex + 1} of #{@metrics.callsExecuted} calls")
else
$metrics.find('.calls-metric').hide()
if @metrics.statementsExecuted
$metrics.find('.statement-index').text @statementIndex + 1
$metrics.find('.statements-executed').text statementsExecuted
if @metrics.statementsExecuted > statementsExecuted
$metrics.find('.statements-executed-total').text " (#{@metrics.statementsExecuted})"
titleSuffix = " (#{@metrics.statementsExecuted} statements total)"
else
$metrics.find('.statements-executed-total').text ""
titleSuffix = ""
$metrics.find('.statements-metric').show().attr('title', "Statement #{@statementIndex + 1} of #{statementsExecuted} this call#{titleSuffix}")
else
$metrics.find('.statements-metric').hide()
setStatementRatio: (ratio) ->
return unless total = @callState?.statementsExecuted
@setStatementIndex Math.floor ratio * total
onProgressHover: (e) ->
@setStatementRatio e.offsetX / @$el.find('.progress').width()
@updateTime()
@maintainIndexHover = true
onProgressMouseOut: (e) ->
@maintainIndexHover = false
onStepBackward: (e) -> @step -1
onStepForward: (e) -> @step 1
step: (delta) ->
lastTime = @statementTime
@setStatementIndex @statementIndex + delta
@updateTime() if @statementIndex isnt lastTime
updateTime: ->
@maintainIndexScrub = true
clearTimeout @maintainIndexScrubTimeout if @maintainIndexScrubTimeout
@maintainIndexScrubTimeout = _.delay (=> @maintainIndexScrub = false), 500
Backbone.Mediator.publish 'level-set-time', time: @statementTime, scrubDuration: 500
setCallState: (callState, statementIndex, @callIndex, @metrics) ->
return if callState is @callState and statementIndex is @statementIndex
return unless @callState = callState
if not @maintainIndexHover and not @maintainIndexScrub and statementIndex? and callState.statements[statementIndex].userInfo.time isnt @statementTime
@setStatementIndex statementIndex
else
@setStatementRatio @statementRatio
# Not sure yet whether it's better to maintain @statementIndex or @statementRatio
#else if @statementRatio is 1 or not @statementIndex?
# @setStatementRatio 1
#else
# @setStatementIndex @statementIndex

View file

@ -4,7 +4,8 @@ template = require 'templates/play/level/tome/spell'
filters = require 'lib/image_filter'
Range = ace.require("ace/range").Range
Problem = require './problem'
DebugView = require './debug_view'
SpellDebugView = require './spell_debug_view'
SpellToolbarView = require './spell_toolbar_view'
module.exports = class SpellView extends View
id: 'spell-view'
@ -26,9 +27,10 @@ module.exports = class SpellView extends View
'level:session-will-save': 'onSessionWillSave'
'modal-closed': 'focus'
'focus-editor': 'focus'
'tome:spell-statement-index-updated': 'onStatementIndexUpdated'
events:
'click .ace': -> console.log 'clicked ace', @
'mouseout': 'onMouseOut'
constructor: (options) ->
super options
@ -88,24 +90,30 @@ module.exports = class SpellView extends View
name: 'end-all-scripts'
bindKey: {win: 'Escape', mac: 'Escape'}
exec: -> Backbone.Mediator.publish 'level:escape-pressed'
# TODO: These don't work on, for example, Danish keyboards. Figure out a more universal solution.
# @ace.commands.addCommand
# name: 'toggle-grid'
# bindKey: {win: 'Alt-G', mac: 'Alt-G'}
# exec: -> Backbone.Mediator.publish 'level-toggle-grid'
# @ace.commands.addCommand
# name: 'toggle-debug'
# bindKey: {win: 'Alt-\\', mac: 'Alt-\\'}
# exec: -> Backbone.Mediator.publish 'level-toggle-debug'
# @ace.commands.addCommand
# name: 'level-scrub-forward'
# bindKey: {win: 'Alt-]', mac: 'Alt-]'}
# exec: -> Backbone.Mediator.publish 'level-scrub-forward'
# @ace.commands.addCommand
# name: 'level-scrub-back'
# bindKey: {win: 'Alt-[', mac: 'Alt-['}
# exec: -> Backbone.Mediator.publish 'level-scrub-back'
@ace.commands.addCommand
name: 'toggle-grid'
bindKey: {win: 'Ctrl-G', mac: 'Command-G|Ctrl-G'}
exec: -> Backbone.Mediator.publish 'level-toggle-grid'
@ace.commands.addCommand
name: 'toggle-debug'
bindKey: {win: 'Ctrl-\\', mac: 'Command-\\|Ctrl-\\'}
exec: -> Backbone.Mediator.publish 'level-toggle-debug'
@ace.commands.addCommand
name: 'level-scrub-forward'
bindKey: {win: 'Ctrl-]', mac: 'Command-]|Ctrl-]'}
exec: -> Backbone.Mediator.publish 'level-scrub-forward'
@ace.commands.addCommand
name: 'level-scrub-back'
bindKey: {win: 'Ctrl-[', mac: 'Command-[|Ctrl-]'}
exec: -> Backbone.Mediator.publish 'level-scrub-back'
@ace.commands.addCommand
name: 'spell-step-forward'
bindKey: {win: 'Ctrl-Alt-]', mac: 'Command-Alt-]|Ctrl-Alt-]'}
exec: -> Backbone.Mediator.publish 'spell-step-forward'
@ace.commands.addCommand
name: 'spell-step-backward'
bindKey: {win: 'Ctrl-Alt-[', mac: 'Command-Alt-[|Ctrl-Alt-]'}
exec: -> Backbone.Mediator.publish 'spell-step-backward'
fillACE: ->
@ace.setValue @spell.source
@ -148,9 +156,16 @@ module.exports = class SpellView extends View
@eventsSuppressed = false # Now that the initial change is in, we can start running any changed code
createDebugView: ->
@debugView = new DebugView ace: @ace
@debugView = new SpellDebugView ace: @ace
@$el.append @debugView.render().$el.hide()
createToolbarView: ->
@toolbarView = new SpellToolbarView ace: @ace
@$el.prepend @toolbarView.render().$el
onMouseOut: (e) ->
@debugView.onMouseOut e
getSource: ->
@ace.getValue() # could also do @firepad.getText()
@ -374,12 +389,21 @@ module.exports = class SpellView extends View
@thang = e.selectedThang # update our thang to the current version
@highlightCurrentLine()
onStatementIndexUpdated: (e) ->
return unless e.ace is @ace
@highlightCurrentLine()
highlightCurrentLine: (flow) =>
# TODO: move this whole thing into SpellDebugView or somewhere?
flow ?= @spellThang?.castAether?.flow
return unless flow
executed = []
matched = false
for callState, callNumber in flow.states or []
states = flow.states ? []
currentCallIndex = null
for callState, callNumber in states
if not currentCallIndex? and callState.userInfo?.time > @thang.world.age
currentCallIndex = callNumber - 1
if matched
executed.pop()
break
@ -390,6 +414,8 @@ module.exports = class SpellView extends View
break
_.last(executed).push state
#state.executing = true if state.userInfo?.time is @thang.world.age # no work
currentCallIndex ?= callNumber - 1
#console.log "got call index", currentCallIndex, "for time", @thang.world.age, "out of", states.length
# TODO: don't redo the markers if they haven't actually changed
text = @aceDoc.getValue()
@ -406,9 +432,17 @@ module.exports = class SpellView extends View
@debugView.setVariableStates {}
@aceSession.removeGutterDecoration row, 'executing' for row in [0 ... @aceSession.getLength()]
$(@ace.container).find('.ace_gutter-cell.executing').removeClass('executing')
return unless executed.length
unless executed.length
@toolbarView?.$el.hide()
return
unless @toolbarView or (@spell.name is "plan" and @spellThang.castAether.metrics.statementsExecuted < 20)
@createToolbarView()
lastExecuted = _.last executed
@toolbarView?.$el.show()
statementIndex = Math.max 0, lastExecuted.length - 1
@toolbarView?.setCallState states[currentCallIndex], statementIndex, currentCallIndex, @spellThang.castAether.metrics
marked = {}
lastExecuted = lastExecuted[0 .. @toolbarView.statementIndex] if @toolbarView?.statementIndex?
for state, i in lastExecuted
#clazz = if state.executing then 'executing' else 'executed' # doesn't work
clazz = if i is lastExecuted.length - 1 then 'executing' else 'executed'
@ -419,7 +453,7 @@ module.exports = class SpellView extends View
++marked[key]
else
@debugView.setVariableStates state.variables
console.log "at", state.userInfo.time, "vars are now:", state.variables
#console.log "at", state.userInfo.time, "vars are now:", state.variables
[start, end] = [offsetToPos(state.range[0]), offsetToPos(state.range[1])]
markerRange = new Range(start.row, start.column, end.row, end.column)
markerRange.start = @aceDoc.createAnchor markerRange.start

View file

@ -114,6 +114,7 @@ module.exports = class TomeView extends View
@spellTabView?.$el.after('<div id="' + @spellTabView.id + '"></div>').detach()
@spellTabView = null
@removeSubView @spellPaletteView if @spellPaletteView
@castButton?.$el.hide()
@thangList?.$el.show()
onSpriteSelected: (e) ->
@ -137,6 +138,7 @@ module.exports = class TomeView extends View
@$el.find('#' + @spellTabView.id).after(@spellTabView.el).remove()
@spellView.setThang thang
@spellTabView.setThang thang
@castButton.$el.show()
@thangList.$el.hide()
@spellPaletteView = @insertSubView new SpellPaletteView thang: thang
@spellPaletteView.toggleControls {}, @spellView.controlsEnabled # TODO: know when palette should have been disabled but didn't exist