Gopher2600/debugger/loop_playmode.go

158 lines
4.8 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
import (
"github.com/jetsetilly/gopher2600/debugger/govern"
"github.com/jetsetilly/gopher2600/debugger/terminal"
"github.com/jetsetilly/gopher2600/gui"
"github.com/jetsetilly/gopher2600/hardware"
"github.com/jetsetilly/gopher2600/userinput"
)
func (dbg *Debugger) forceROMSelector() error {
dbg.forcedROMselection = make(chan bool, 1)
err := dbg.gui.SetFeature(gui.ReqROMSelector)
if err != nil {
return err
}
return nil
}
func (dbg *Debugger) playLoop() error {
// if forcedROMSelection is active (is not nil) then the event loop is
// simplified for the duration (until it is set to nil)
for dbg.forcedROMselection != nil {
select {
case sig := <-dbg.events.Signal:
return dbg.events.SignalHandler(sig)
case ev := <-dbg.events.PushedFunction:
ev()
case ev := <-dbg.events.PushedFunctionImmediate:
ev()
return nil
case ev := <-dbg.events.UserInput:
if _, ok := ev.(userinput.EventQuit); ok {
return terminal.UserQuit
}
case <-dbg.forcedROMselection:
dbg.forcedROMselection = nil
}
}
// only check for end of measurement period every PerformanceBrake CPU
// instructions
performanceBrake := 0
// update lastBank at the start of the play loop
dbg.liveBankInfo = dbg.vcs.Mem.Cart.GetBank(dbg.vcs.CPU.PC.Address())
// run and handle events
return dbg.vcs.Run(func() (govern.State, error) {
// update counters. because of the way LastResult works we need to make
// sure we only use it in the event that the CPU RdyFlag is set
//
// if it isn't then we know that the CPU ticked once before returning
// and allowing this function to run. meaning the the number of cycles
// tell the counter to Step() is ColorClocksPerCPUCycle
//
// in all other cases the number of cycles to count is ColorClocksPerCPUCycle
// multiplied by the number of cycles in the instruction
if dbg.vcs.CPU.RdyFlg {
dbg.counter.Step(dbg.vcs.CPU.LastResult.Cycles*hardware.ColorClocksPerCPUCycle, dbg.liveBankInfo)
} else {
dbg.counter.Step(hardware.ColorClocksPerCPUCycle, dbg.liveBankInfo)
}
// we must keep lastBank updated during the play loop
dbg.liveBankInfo = dbg.vcs.Mem.Cart.GetBank(dbg.vcs.CPU.PC.Address())
// record state. we do this before any of the conditions below that may
// result in an early return from the function
if dbg.state.Load().(govern.State) == govern.Running {
dbg.Rewind.RecordState()
}
// run continueCheck() function is called every CPU instruction. for
// some halt conditions this is too infrequent
//
// for this reason we should never find ourselves in the playLoop if
// these halt conditions exist. see setMode() function
dbg.halting.check()
if dbg.halting.halt {
// set debugging mode. halting messages will be preserved and
// shown when entering debugging mode
dbg.setMode(govern.ModeDebugger)
return govern.Ending, nil
}
if dbg.Mode() != govern.ModePlay {
return govern.Ending, nil
}
// return without checking interface unless we exceed the
// PerformanceBrake value
performanceBrake++
if performanceBrake < hardware.PerformanceBrake {
return dbg.State(), nil
}
performanceBrake = 0
// how we wait for read events depends on whether the emulation is
// paused or not. if emulation is paused then we halt until an event is
// received. this reduces CPU usage when paused
if dbg.state.Load().(govern.State) == govern.Paused {
select {
case <-dbg.readEventsPulse.C:
err := dbg.readEventsHandler()
if err != nil {
return govern.Ending, err
}
}
} else {
select {
case <-dbg.readEventsPulse.C:
err := dbg.readEventsHandler()
if err != nil {
return govern.Ending, err
}
default:
}
}
// resolve rewinding
if dbg.rewindKeyboardAccumulation != 0 {
amount := 0
if dbg.rewindKeyboardAccumulation < 0 {
if dbg.rewindKeyboardAccumulation > -100 {
dbg.rewindKeyboardAccumulation--
}
amount = (dbg.rewindKeyboardAccumulation / 10) - 1
} else {
if dbg.rewindKeyboardAccumulation < 100 {
dbg.rewindKeyboardAccumulation++
}
amount = (dbg.rewindKeyboardAccumulation / 10) + 1
}
dbg.RewindByAmount(amount)
}
return dbg.State(), nil
})
}