cartridge will reload when HUP signal is received

This commit is contained in:
JetSetIlly 2024-01-24 18:22:00 +00:00
parent bd170bd0b3
commit 3340e5cc8d
10 changed files with 100 additions and 90 deletions

View file

@ -391,15 +391,33 @@ func NewDebugger(opts CommandLineOptions, create CreateUserInterface) (*Debugger
// fast and the channel queue should be pretty lengthy to prevent dropped
// events (see PushFunction()).
dbg.events = &terminal.ReadEvents{
UserInput: make(chan userinput.Event, 10),
UserInputHandler: dbg.userInputHandler,
IntEvents: make(chan os.Signal, 1),
PushedFunctions: make(chan func(), 4096),
PushedFunctionsImmediate: make(chan func(), 4096),
UserInput: make(chan userinput.Event, 10),
UserInputHandler: dbg.userInputHandler,
Signal: make(chan os.Signal, 1),
SignalHandler: func(sig os.Signal) error {
switch sig {
case syscall.SIGHUP:
return terminal.UserReload
case syscall.SIGINT:
return terminal.UserInterrupt
case syscall.SIGQUIT:
return terminal.UserQuit
case syscall.SIGKILL:
// we're unlikely to receive the kill signal, it normally being
// intercepted by the terminal, but in case we do we treat it
// like the QUIT signal
return terminal.UserQuit
default:
}
return nil
},
PushedFunction: make(chan func(), 4096),
PushedFunctionImmediate: make(chan func(), 4096),
}
// connect Interrupt signal to dbg.events.intChan
signal.Notify(dbg.events.IntEvents, os.Interrupt, syscall.SIGHUP)
// connect signals to dbg.events.Signal channel. we include the Kill signal
// but the chances are it'll never be seen
signal.Notify(dbg.events.Signal, os.Interrupt, os.Kill, syscall.SIGHUP, syscall.SIGQUIT)
// allocate memory for user input
dbg.input = make([]byte, 255)
@ -715,9 +733,10 @@ func (dbg *Debugger) StartInDebugMode(filename string) error {
}
defer dbg.end()
err = dbg.run()
if err != nil {
if errors.Is(err, terminal.UserQuit) {
if errors.Is(err, terminal.UserSignal) {
return nil
}
return fmt.Errorf("debugger: %w", err)
@ -842,7 +861,7 @@ func (dbg *Debugger) StartInPlayMode(filename string) error {
err = dbg.run()
if err != nil {
if errors.Is(err, terminal.UserQuit) {
if errors.Is(err, terminal.UserSignal) {
return nil
}
return fmt.Errorf("debugger: %w", err)
@ -876,8 +895,8 @@ func (dbg *Debugger) CartYield(yield coprocessor.CoProcYieldType) coprocessor.Yi
return coprocessor.YieldHookContinue
}
// if emulation is in itialisation state then we cause coprocessor execution
// to end unless it's a memory or access erorr
// if emulation is in the intialisation state then we cause coprocessor
// execution to end unless it's a memory or access erorr
//
// this is an area that's likely to change. it's of particular interest to
// ACE and ELF ROMs in which the coprocessor is run very early in order to
@ -921,12 +940,15 @@ func (dbg *Debugger) run() error {
err := dbg.playLoop()
if err != nil {
// if we ever encounter a cartridge ejected error in playmode
// then simply open up the ROM selector
// then simply open up the ROM selector and continue with the
// running loop
if errors.Is(err, cartridge.Ejected) {
err = dbg.forceROMSelector()
if err != nil {
return fmt.Errorf("debugger: %w", err)
}
} else if errors.Is(err, terminal.UserReload) {
dbg.reloadCartridge()
} else {
return fmt.Errorf("debugger: %w", err)
}
@ -946,7 +968,13 @@ func (dbg *Debugger) run() error {
err := dbg.inputLoop(dbg.term, false)
if err != nil {
return fmt.Errorf("debugger: %w", err)
// there is no special handling for the cartridge ejected error,
// unlike in play mode
if errors.Is(err, terminal.UserReload) {
dbg.reloadCartridge()
} else {
return fmt.Errorf("debugger: %w", err)
}
}
default:
@ -1445,13 +1473,8 @@ func (dbg *Debugger) reloadCartridge() error {
}
// ReloadCartridge inserts the current cartridge and states the emulation over.
//
// It should not be run directly from the emulation/debugger goroutine, use
// reloadCartridge() for that
func (dbg *Debugger) ReloadCartridge() {
dbg.PushFunctionImmediate(func() {
dbg.unwindLoop(dbg.reloadCartridge)
})
dbg.events.Signal <- syscall.SIGHUP
}
func (dbg *Debugger) insertCartridge(filename string) error {

View file

@ -15,12 +15,6 @@
package debugger
import (
"syscall"
"github.com/jetsetilly/gopher2600/debugger/terminal"
)
// readEventsHandler is called by inputLoop to make sure the program is
// handling pushed events and/or user input.
//
@ -32,13 +26,8 @@ import (
func (dbg *Debugger) readEventsHandler() error {
for {
select {
case sig := <-dbg.events.IntEvents:
switch sig {
case syscall.SIGHUP:
dbg.reloadCartridge()
default:
return terminal.UserInterrupt
}
case sig := <-dbg.events.Signal:
return dbg.events.SignalHandler(sig)
case ev := <-dbg.events.UserInput:
err := dbg.events.UserInputHandler(ev)
@ -46,10 +35,10 @@ func (dbg *Debugger) readEventsHandler() error {
return err
}
case ev := <-dbg.events.PushedFunctions:
case ev := <-dbg.events.PushedFunction:
ev()
case ev := <-dbg.events.PushedFunctionsImmediate:
case ev := <-dbg.events.PushedFunctionImmediate:
ev()
return nil

View file

@ -190,11 +190,10 @@ func (dbg *Debugger) inputLoop(inputter terminal.Input, nonInstructionQuantum bo
if err != nil {
if errors.Is(err, terminal.UserInterrupt) {
dbg.handleInterrupt(inputter)
} else if errors.Is(err, terminal.UserSignal) {
return err
} else {
// don't print UserQuit error to terminal
if !errors.Is(err, terminal.UserQuit) {
dbg.printLine(terminal.StyleError, "%s", err)
}
dbg.printLine(terminal.StyleError, "%s", err)
}
}
@ -566,9 +565,8 @@ func (dbg *Debugger) termRead(inputter terminal.Input) error {
// user interrupts are used to quit or halt an operation
dbg.handleInterrupt(inputter)
} else if errors.Is(err, terminal.UserAbort) || errors.Is(err, io.EOF) {
// like user interrupts, abort are used to quit the application but
// more forcibly
} else if errors.Is(err, io.EOF) {
// an EOF error causes the emulation to exit immediately
dbg.running = false
dbg.continueEmulation = false
return nil
@ -581,8 +579,8 @@ func (dbg *Debugger) termRead(inputter terminal.Input) error {
return nil
}
// interrupt errors that are sent back to the debugger need some special care
// depending on the current state and what sort of terminal is being used.
// interrupt signals need some special care depending on the current state and
// what sort of terminal is being used.
func (dbg *Debugger) handleInterrupt(inputter terminal.Input) {
// end script scribe (if one is running)
err := dbg.scriptScribe.EndSession()

View file

@ -34,25 +34,23 @@ func (dbg *Debugger) forceROMSelector() error {
}
func (dbg *Debugger) playLoop() error {
if dbg.forcedROMselection != nil {
done := false
for !done {
select {
case <-dbg.events.IntEvents:
return terminal.UserInterrupt
case ev := <-dbg.events.PushedFunctions:
ev()
case ev := <-dbg.events.PushedFunctionsImmediate:
ev()
return nil
case ev := <-dbg.events.UserInput:
if _, ok := ev.(userinput.EventQuit); ok {
return terminal.UserQuit
}
case <-dbg.forcedROMselection:
dbg.forcedROMselection = nil
done = true
// 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
}
}

View file

@ -25,7 +25,7 @@ import (
// inserted into the emulation loop correctly.
func (dbg *Debugger) PushFunction(f func()) {
select {
case dbg.events.PushedFunctions <- f:
case dbg.events.PushedFunction <- f:
default:
logger.Log("debugger", "dropped raw event push")
}
@ -35,7 +35,7 @@ func (dbg *Debugger) PushFunction(f func()) {
// return to the input loop for immediate action.
func (dbg *Debugger) PushFunctionImmediate(f func()) {
select {
case dbg.events.PushedFunctionsImmediate <- f:
case dbg.events.PushedFunctionImmediate <- f:
default:
logger.Log("debugger", "dropped raw event push (to return channel)")
}

View file

@ -99,8 +99,8 @@ func (ct *ColorTerminal) TermRead(input []byte, prompt terminal.Prompt, events *
// wait for an event and respond
select {
case <-events.IntEvents:
return 0, terminal.UserInterrupt
case sig := <-events.Signal:
return 0, events.SignalHandler(sig)
case ev := <-events.UserInput:
ct.EasyTerm.TermPrint(ansi.CursorStore)
@ -110,10 +110,10 @@ func (ct *ColorTerminal) TermRead(input []byte, prompt terminal.Prompt, events *
return inputLen + 1, err
}
case ev := <-events.PushedFunctions:
case ev := <-events.PushedFunction:
ev()
case ev := <-events.PushedFunctionsImmediate:
case ev := <-events.PushedFunctionImmediate:
ev()
return 0, nil

View file

@ -95,8 +95,8 @@ func (pt PlainTerminal) TermRead(input []byte, prompt terminal.Prompt, events *t
// other events do not need to be checked - they will be serviced by the
// debugger inputer loop elsewhere
select {
case <-events.IntEvents:
return 0, terminal.UserInterrupt
case sig := <-events.Signal:
return 0, events.SignalHandler(sig)
default:
}

View file

@ -17,6 +17,7 @@ package terminal
import (
"errors"
"fmt"
"os"
"github.com/jetsetilly/gopher2600/userinput"
@ -53,30 +54,31 @@ type Input interface {
}
// sentinal errors controlling program exit
var UserInterrupt = errors.New("user interrupt")
var UserAbort = errors.New("user abort")
// UserQuit indicates an intentional quit and should probably be caught and silenced
var UserQuit = errors.New("user quit")
var (
UserSignal = errors.New("user signal")
UserQuit = fmt.Errorf("%w: quit", UserSignal)
UserInterrupt = fmt.Errorf("%w: interrupt", UserSignal)
UserReload = fmt.Errorf("%w: reload", UserSignal)
)
// ReadEvents *must* be monitored during a TermRead().
type ReadEvents struct {
// user input events. these are the inputs into the emulation (ie.
// joystick, paddle, etc.)
// user input events. these are the inputs into the emulation
// (ie. joystick, paddle, etc.)
UserInput chan userinput.Event
UserInputHandler func(userinput.Event) error
// interrupt signals from the operating system
IntEvents chan os.Signal
// signals from the operating system
Signal chan os.Signal
SignalHandler func(os.Signal) error
// PushedFunctions allows functions to be pushed into the debugger goroutine
//
// errors are not returned by PushedFunctions so errors should be logged
PushedFunctions chan func()
// PushedFunction allows functions to be pushed into the debugger goroutine.
// errors are not returned by PushedFunction so errors should be logged
PushedFunction chan func()
// PushedFunctionsImmediate is the same as PushedFunctions but handlers
// PushedFunctionImmediate is the same as PushedFunctions but handlers
// must return control to the inputloop after the function has run
PushedFunctionsImmediate chan func()
PushedFunctionImmediate chan func()
}
// Output defines the operations required by an interface that allows output.

View file

@ -37,14 +37,14 @@ func (dbg *Debugger) userInputHandler_catchUpLoop() {
}
func (dbg *Debugger) userInputHandler(ev userinput.Event) error {
// quite event
// quit event
switch ev.(type) {
case userinput.EventQuit:
dbg.running = false
return terminal.UserQuit
}
// mode specific special input (not passed to the VCS as controller input)
// special handling of some user input (not passed to the VCS as controller input)
switch dbg.Mode() {
case govern.ModePlay:
switch ev := ev.(type) {

View file

@ -121,13 +121,13 @@ func (trm *term) TermRead(buffer []byte, prompt terminal.Prompt, events *termina
copy(buffer, inp+"\n")
return len(inp) + 1, nil
case <-events.IntEvents:
return 0, terminal.UserInterrupt
case sig := <-events.Signal:
return 0, events.SignalHandler(sig)
case ev := <-events.PushedFunctions:
case ev := <-events.PushedFunction:
ev()
case ev := <-events.PushedFunctionsImmediate:
case ev := <-events.PushedFunctionImmediate:
ev()
return 0, nil