rewinding by mouse wheel works in debugger

rewinds/FF by 10 frames or by a single frame if shift key is held
This commit is contained in:
JetSetIlly 2024-04-12 09:31:39 +01:00
parent 7e1f1f17b6
commit 8f2eb8753f
7 changed files with 140 additions and 70 deletions

View file

@ -214,20 +214,17 @@ type Debugger struct {
// moving or for how long the keyboard (or gamepad bumpers) have been
// depressed.
//
// when rewinding by mousewheel, events are likely to be sent during the
// rewind catchup loop so we accumulate the mousewheel delta and rewind
// when we return to the normal loop
//
// keyboard/bumper rewind is slightly different. for every machine cycle
// (in the normal playloop - not the catchup loop) that the keyboard is
// held down we increase (or decrease when going backwards) the
// accumulation value. we use this to determine how quickly the rewind
// should progress. the accumulation value is zeroed when the key/bumpers
// are released
//
// * playmode only
// for keyboard rewinding, the amount to rewind by increases for as long as
// the key-combo (gamepad bumper) is being held. the amount is reset when
// the key-combo/bumper is released
rewindKeyboardAccumulation int
// when rewinding by mouse wheel we just use the delta information from the
// input device. however, we might miss mousewheel events during the
// debugger catchup loop. the rewindMouseWheelAccumulation value allows us
// to accumulate the delta value until we have the opportunity to issue
// another rewind instruction
rewindMouseWheelAccumulation int
rewindKeyboardAccumulation int
// audio tracker stores audio state over time
Tracker *tracker.Tracker

View file

@ -25,16 +25,20 @@ import (
"github.com/jetsetilly/gopher2600/hardware/television/coords"
)
// RewindByAmount moves forwards or backwards by specified frames. Negative
// numbers indicate backwards
// RewindByAmount moves forwards or backwards by specified frames. Positive
// numbers to "rewind" forwards and negative numbers to rewind backwards.
func (dbg *Debugger) RewindByAmount(amount int) {
if dbg.state.Load().(govern.State) == govern.Rewinding {
return
}
switch dbg.Mode() {
case govern.ModePlay:
coords := dbg.vcs.TV.GetCoords()
fn := dbg.vcs.TV.GetCoords().Frame
tl := dbg.Rewind.GetTimeline()
if amount < 0 {
if coords.Frame-1 < tl.AvailableStart {
if fn-1 < tl.AvailableStart {
dbg.setState(govern.Paused, govern.PausedAtStart)
return
}
@ -42,30 +46,61 @@ func (dbg *Debugger) RewindByAmount(amount int) {
}
if amount > 0 {
if coords.Frame+1 > tl.AvailableEnd {
if fn+1 > tl.AvailableEnd {
dbg.setState(govern.Paused, govern.PausedAtEnd)
return
}
dbg.setState(govern.Rewinding, govern.RewindingForwards)
}
dbg.Rewind.GotoFrame(coords.Frame + amount)
dbg.Rewind.GotoFrame(fn + amount)
dbg.setState(govern.Paused, govern.Normal)
return
}
case govern.ModeDebugger:
fn := dbg.vcs.TV.GetCoords().Frame
fn += amount
panic(fmt.Sprintf("Rewind: unsupported mode (%v)", dbg.Mode()))
// the function to push to the debugger/emulation routine
doRewind := func() error {
// upate catchup context before starting rewind process
dbg.catchupContext = catchupRewindToFrame
err := dbg.Rewind.GotoFrame(fn)
if err != nil {
return err
}
return nil
}
// how we push the doRewind() function depends on what kind of inputloop we
// are currently in
dbg.PushFunctionImmediate(func() {
// set state to govern.Rewinding as soon as possible (but
// remembering that we must do it in the debugger goroutine)
//
// not that we're not worried about detecing PausedAtEnd and
// PausedAtStart conditions like we do in the ModePlay case
if amount > 0 {
dbg.setState(govern.Rewinding, govern.RewindingForwards)
} else {
dbg.setState(govern.Rewinding, govern.RewindingBackwards)
}
dbg.unwindLoop(doRewind)
})
}
}
// RewindToFrame measure from the current frame.
//
// This function should be run only from debugger mode.
func (dbg *Debugger) RewindToFrame(fn int, last bool) bool {
if dbg.State() == govern.Rewinding {
return false
}
switch dbg.Mode() {
case govern.ModeDebugger:
if dbg.State() == govern.Rewinding {
return false
}
// the function to push to the debugger/emulation routine
doRewind := func() error {
// upate catchup context before starting rewind process
@ -106,13 +141,15 @@ func (dbg *Debugger) RewindToFrame(fn int, last bool) bool {
}
// GotoCoords rewinds the emulation to the specified coordinates.
//
// This function should be run only from debugger mode.
func (dbg *Debugger) GotoCoords(toCoords coords.TelevisionCoords) bool {
if dbg.State() == govern.Rewinding {
return false
}
switch dbg.Mode() {
case govern.ModeDebugger:
if dbg.State() == govern.Rewinding {
return false
}
// the function to push to the debugger/emulation routine
doRewind := func() error {
// upate catchup context before starting rewind process
@ -148,13 +185,15 @@ func (dbg *Debugger) GotoCoords(toCoords coords.TelevisionCoords) bool {
}
// RerunLastNFrames measured from the current frame.
//
// This function should be run only from debugger mode.
func (dbg *Debugger) RerunLastNFrames(frames int) bool {
if dbg.State() == govern.Rewinding {
return false
}
switch dbg.Mode() {
case govern.ModeDebugger:
if dbg.State() == govern.Rewinding {
return false
}
// the disadvantage of RerunLastNFrames() is that it will always land on a
// CPU instruction boundary (this is because we must unwind the existing
// input loop before calling the rewind function)
@ -189,7 +228,7 @@ func (dbg *Debugger) RerunLastNFrames(frames int) bool {
// set state to govern.Rewinding as soon as possible (but
// remembering that we must do it in the debugger goroutine)
dbg.setState(govern.Rewinding, govern.Normal)
dbg.setState(govern.Rewinding, govern.RewindingForwards)
dbg.unwindLoop(doRewind)
})

View file

@ -46,12 +46,24 @@ func (dbg *Debugger) userInputHandler(ev userinput.Event) error {
// special handling of some user input (not passed to the VCS as controller input)
switch dbg.Mode() {
case govern.ModeDebugger:
switch ev := ev.(type) {
case userinput.EventMouseWheel:
var amount int
switch ev.Mod {
case userinput.KeyModShift:
amount = int(ev.Delta)
default:
amount = int(ev.Delta) * 5
}
dbg.RewindByAmount(dbg.rewindMouseWheelAccumulation + amount)
return nil
}
case govern.ModePlay:
switch ev := ev.(type) {
case userinput.EventMouseWheel:
amount := int(ev.Delta) + dbg.rewindMouseWheelAccumulation
dbg.rewindMouseWheelAccumulation = 0
dbg.RewindByAmount(amount)
dbg.RewindByAmount(int(ev.Delta))
return nil
case userinput.EventKeyboard:

View file

@ -18,7 +18,6 @@ package sdlimgui
import (
"time"
"github.com/jetsetilly/gopher2600/debugger/govern"
"github.com/jetsetilly/gopher2600/hardware/riot/ports/plugging"
"github.com/jetsetilly/gopher2600/logger"
"github.com/jetsetilly/gopher2600/userinput"
@ -209,24 +208,38 @@ func (img *SdlImgui) Service() {
img.polling.alerted = true
case *sdl.MouseWheelEvent:
var deltaX, deltaY float32
if ev.X > 0 {
deltaX++
} else if ev.X < 0 {
deltaX--
}
if ev.Y > 0 {
deltaY++
} else if ev.Y < 0 {
deltaY--
}
imgui.CurrentIO().AddMouseWheelDelta(-deltaX/4, deltaY/4)
// only respond to mouse wheel events if the window has
// input focus. this is because without input focus
// getKeyMod() will always return userinput.KeyModNone. it
// is confusing if the mousewheel is working but no keyboard
// modifiers are affecting it
if img.plt.window.GetFlags()&sdl.WINDOW_INPUT_FOCUS == sdl.WINDOW_INPUT_FOCUS {
var deltaX, deltaY float32
if ev.X > 0 {
deltaX++
} else if ev.X < 0 {
deltaX--
}
if ev.Y > 0 {
deltaY++
} else if ev.Y < 0 {
deltaY--
}
if img.mode.Load().(govern.Mode) != govern.ModePlay || !img.wm.playmodeWindows[winSelectROMID].playmodeIsOpen() {
select {
case input <- userinput.EventMouseWheel{Delta: deltaY}:
default:
logger.Log("sdlimgui", "dropped mouse wheel event")
// forward mouse wheel event to emulation only for playmode
// or if the mouse is immediately over the TV image in the
// debugger TV screen window
if img.isPlaymode() || img.wm.dbgScr.mouseHover {
select {
case input <- userinput.EventMouseWheel{
Delta: deltaY,
Mod: getKeyMod(),
}:
default:
logger.Log("sdlimgui", "dropped mouse wheel event")
}
} else {
imgui.CurrentIO().AddMouseWheelDelta(-deltaX/4, deltaY/4)
}
}

View file

@ -254,19 +254,6 @@ func (img *SdlImgui) serviceKeyboard(ev *sdl.KeyboardEvent) {
// forward keypresses to userinput.Event channel
if img.isCaptured() || (img.isPlaymode() && !imgui.IsAnyItemActive()) {
mod := userinput.KeyModNone
if sdl.GetModState()&sdl.KMOD_LALT == sdl.KMOD_LALT ||
sdl.GetModState()&sdl.KMOD_RALT == sdl.KMOD_RALT {
mod = userinput.KeyModAlt
} else if sdl.GetModState()&sdl.KMOD_LSHIFT == sdl.KMOD_LSHIFT ||
sdl.GetModState()&sdl.KMOD_RSHIFT == sdl.KMOD_RSHIFT {
mod = userinput.KeyModShift
} else if sdl.GetModState()&sdl.KMOD_LCTRL == sdl.KMOD_LCTRL ||
sdl.GetModState()&sdl.KMOD_RCTRL == sdl.KMOD_RCTRL {
mod = userinput.KeyModCtrl
}
switch ev.Type {
case sdl.KEYDOWN:
fallthrough
@ -275,7 +262,7 @@ func (img *SdlImgui) serviceKeyboard(ev *sdl.KeyboardEvent) {
case img.dbg.UserInput() <- userinput.EventKeyboard{
Key: sdl.GetScancodeName(ev.Keysym.Scancode),
Down: ev.Type == sdl.KEYDOWN,
Mod: mod,
Mod: getKeyMod(),
}:
default:
logger.Log("sdlimgui", "dropped keyboard event")
@ -293,3 +280,17 @@ func (img *SdlImgui) serviceKeyboard(ev *sdl.KeyboardEvent) {
img.updateKeyModifier()
}
}
func getKeyMod() userinput.KeyMod {
if sdl.GetModState()&sdl.KMOD_LALT == sdl.KMOD_LALT ||
sdl.GetModState()&sdl.KMOD_RALT == sdl.KMOD_RALT {
return userinput.KeyModAlt
} else if sdl.GetModState()&sdl.KMOD_LSHIFT == sdl.KMOD_LSHIFT ||
sdl.GetModState()&sdl.KMOD_RSHIFT == sdl.KMOD_RSHIFT {
return userinput.KeyModShift
} else if sdl.GetModState()&sdl.KMOD_LCTRL == sdl.KMOD_LCTRL ||
sdl.GetModState()&sdl.KMOD_RCTRL == sdl.KMOD_RCTRL {
return userinput.KeyModCtrl
}
return userinput.KeyModNone
}

View file

@ -90,6 +90,9 @@ type winDbgScr struct {
// magnification fields
magnifyTooltip dbgScrMagnifyTooltip
magnifyWindow dbgScrMagnifyWindow
// whether mouse is hovering over screen image
mouseHover bool
}
func newWinDbgScr(img *SdlImgui) (window, error) {
@ -201,11 +204,15 @@ func (win *winDbgScr) draw() {
imgui.PushStyleVarVec2(imgui.StyleVarFramePadding, imgui.Vec2{0.0, 0.0})
imgui.PushStyleColor(imgui.StyleColorDragDropTarget, win.img.cols.Transparent)
if !win.crtPreview && win.elements {
imgui.ImageButton(imgui.TextureID(win.elementsTexture.getID()), imgui.Vec2{win.scaledWidth, win.scaledHeight})
} else {
imgui.ImageButton(imgui.TextureID(win.displayTexture.getID()), imgui.Vec2{win.scaledWidth, win.scaledHeight})
}
win.mouseHover = imgui.IsItemHovered()
win.paintDragAndDrop()
imgui.PopStyleColor()

View file

@ -70,6 +70,7 @@ type EventMouseButton struct {
// EventMouseWheel data is generated by the mouse wheel (jog wheel).
type EventMouseWheel struct {
Delta float32
Mod KeyMod
}
// DPadDirection indentifies the direction the dpad is being pressed.