mirror of
https://github.com/JetSetIlly/Gopher2600.git
synced 2025-04-02 11:02:17 -04:00
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
245 lines
6.9 KiB
Go
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()))
|
|
}
|