Gopher2600/debugger/rewind.go
JetSetIlly 3522d2cdf2 fixed bug caused by mouse wheel event reporting an amount of zero
this cased a crash, originating with a bug in debugger/rewind.go

the rewind.GotoFrame() was called without first putting the emulation
into the rewind state. this caused an intentional panic in the catchup
loop
2024-08-11 18:58:44 +01:00

245 lines
6.9 KiB
Go

// This file is part of Gopher2600.
//
// Gopher2600 is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Gopher2600 is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Gopher2600. If not, see <https://www.gnu.org/licenses/>.
package debugger
// support functions for the rewind package that require more knowledge of
// the debugger than would otherwise be available.
import (
"fmt"
"github.com/jetsetilly/gopher2600/debugger/govern"
"github.com/jetsetilly/gopher2600/hardware/television/coords"
"github.com/jetsetilly/gopher2600/rewind"
)
// 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
}
// an amount of zero can be generated by touch events on macos. it's not
// something we normally have to worry about but this is the best way of
// dealing with it
if amount == 0 {
return
}
switch dbg.Mode() {
case govern.ModePlay:
fn := dbg.vcs.TV.GetCoords().Frame
tl := dbg.Rewind.GetTimeline()
if amount > 0 {
if fn+1 > tl.AvailableEnd {
dbg.setState(govern.Paused, govern.PausedAtEnd)
return
}
dbg.setState(govern.Rewinding, govern.RewindingForwards)
} else {
if fn-1 < tl.AvailableStart {
dbg.setState(govern.Paused, govern.PausedAtStart)
return
}
dbg.setState(govern.Rewinding, govern.RewindingBackwards)
}
dbg.Rewind.GotoFrame(fn + amount)
dbg.setState(govern.Paused, govern.Normal)
case govern.ModeDebugger:
fn := dbg.vcs.TV.GetCoords().Frame
fn += amount
// 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:
// the function to push to the debugger/emulation routine
doRewind := func() error {
// upate catchup context before starting rewind process
dbg.catchupContext = catchupRewindToFrame
if last {
err := dbg.Rewind.GotoLast()
if err != nil {
return err
}
} else {
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)
if fn > dbg.vcs.TV.GetCoords().Frame {
dbg.setState(govern.Rewinding, govern.RewindingForwards)
} else {
dbg.setState(govern.Rewinding, govern.RewindingBackwards)
}
dbg.unwindLoop(doRewind)
})
return true
}
panic(fmt.Sprintf("RewindToFrame: unsupported mode (%v)", dbg.Mode()))
}
// 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:
// the function to push to the debugger/emulation routine
doRewind := func() error {
// upate catchup context before starting rewind process
dbg.catchupContext = catchupGotoCoords
err := dbg.Rewind.GotoCoords(toCoords)
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)
fromCoords := dbg.vcs.TV.GetCoords()
if coords.GreaterThan(toCoords, fromCoords) {
dbg.setState(govern.Rewinding, govern.RewindingForwards)
} else {
dbg.setState(govern.Rewinding, govern.RewindingBackwards)
}
dbg.unwindLoop(doRewind)
})
return true
}
panic(fmt.Sprintf("GotoCoords: unsupported mode (%v)", dbg.Mode()))
}
// RerunLastNFrames measured from the current frame.
//
// This function should be run only from debugger mode.
func (dbg *Debugger) RerunLastNFrames(frames int, onSplice rewind.SpliceHook) bool {
if dbg.State() == govern.Rewinding {
return false
}
switch dbg.Mode() {
case govern.ModeDebugger:
// 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)
//
// if we're in between instruction boundaries therefore we need to push a
// GotoCoords() request. get the current coordinates now
correctCoords := !dbg.liveDisasmEntry.Result.Final
toCoords := dbg.vcs.TV.GetCoords()
// the function to push to the debugger/emulation routine
doRewind := func() error {
err := dbg.Rewind.RerunLastNFrames(frames, onSplice)
if err != nil {
return err
}
if correctCoords {
err = dbg.Rewind.GotoCoords(toCoords)
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() {
// upate catchup context before starting rewind process
dbg.catchupContext = catrupRerunLastNFrames
// 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.RewindingForwards)
dbg.unwindLoop(doRewind)
})
return true
}
panic(fmt.Sprintf("RerunLastNFrames: unsupported mode (%v)", dbg.Mode()))
}