o debugger

- renamed metavideo concept to reflection
    - moved metavideo/reflection to its own package
    - added ReflectionNotRunning error
    - error message shown if overlay is turned on without reflection
	processing running
    - improved DISPLAY command

o sdl
    - type assertion panics now caught in sdl.SetFeture() and returned
	as a PanicError
This commit is contained in:
steve 2019-10-11 12:26:10 +01:00
parent 8db28019c6
commit 1814ed0544
15 changed files with 410 additions and 296 deletions

View file

@ -38,7 +38,7 @@ const (
cmdLast = "LAST"
cmdList = "LIST"
cmdMemMap = "MEMMAP"
cmdMetaVideo = "METAVIDEO"
cmdReflect = "REFLECT"
cmdMissile = "MISSILE"
cmdOnHalt = "ONHALT"
cmdOnStep = "ONSTEP"
@ -78,7 +78,7 @@ var commandTemplate = []string{
cmdDebuggerState,
cmdDigest + " (RESET)",
cmdDisassembly,
cmdDisplay + " (OFF|DEBUG|SCALE [%P]|DEBUGCOLORS)", // see notes
cmdDisplay + " (ON|OFF|DEBUG (ON|OFF)|SCALE [%P]|ALT (ON|OFF)|OVERLAY (ON|OFF))", // see notes
cmdDrop + " [BREAK|TRAP|WATCH] %N",
cmdGrep + " %S",
cmdHexLoad + " %N %N {%N}",
@ -86,7 +86,7 @@ var commandTemplate = []string{
cmdLast + " (DEFN)",
cmdList + " [BREAKS|TRAPS|WATCHES|ALL]",
cmdMemMap,
cmdMetaVideo + " (ON|OFF)",
cmdReflect + " (ON|OFF)",
cmdMissile + " (0|1)",
cmdOnHalt + " (OFF|ON|%S {%S})",
cmdOnStep + " (OFF|ON|%S {%S})",
@ -189,6 +189,17 @@ func (dbg *Debugger) parseCommand(userInput *string, interactive bool) (parseCom
return doNothing, err
}
// make sure all tokens have been handled. this should only happen if
// input has been allowed by ValidateTokens() but has not been
// explicitely consumed by entactCommand()
if interactive {
defer func() {
if !tokens.IsEnd() {
dbg.print(console.StyleError, fmt.Sprintf("unhandled arguments in user input (%s)", tokens.Remainder()))
}
}()
}
// the absolute best thing about the ValidateTokens() function is that we
// don't need to worrying too much about the success of tokens.Get() in the
// enactCommand() function below:
@ -597,18 +608,22 @@ func (dbg *Debugger) enactCommand(tokens *commandline.Tokens, interactive bool)
case cmdMemMap:
dbg.print(console.StyleInstrument, "%v", dbg.vcs.Mem.MemoryMap())
case cmdMetaVideo:
case cmdReflect:
option, _ := tokens.Get()
switch strings.ToUpper(option) {
case "OFF":
dbg.metaVideoProcess = false
dbg.reflectProcess = false
err := dbg.gui.SetFeature(gui.ReqSetOverlay, false)
if err != nil {
dbg.print(console.StyleError, err.Error())
}
case "ON":
dbg.metaVideoProcess = true
dbg.reflectProcess = true
}
if dbg.metaVideoProcess {
dbg.print(console.StyleEmulatorInfo, "metavideo processing: ON")
if dbg.reflectProcess {
dbg.print(console.StyleEmulatorInfo, "reflection: ON")
} else {
dbg.print(console.StyleEmulatorInfo, "metavideo processing: OFF")
dbg.print(console.StyleEmulatorInfo, "reflection: OFF")
}
case cmdExit:
@ -993,48 +1008,98 @@ func (dbg *Debugger) enactCommand(tokens *commandline.Tokens, interactive bool)
case cmdDisplay:
var err error
action, present := tokens.Get()
if present {
action, _ := tokens.Get()
action = strings.ToUpper(action)
switch action {
case "ON":
err = dbg.gui.SetFeature(gui.ReqSetVisibility, true)
if err != nil {
return doNothing, err
}
case "OFF":
err = dbg.gui.SetFeature(gui.ReqSetVisibility, false)
if err != nil {
return doNothing, err
}
case "DEBUG":
action, _ := tokens.Get()
action = strings.ToUpper(action)
switch action {
case "OFF":
err = dbg.gui.SetFeature(gui.ReqSetVisibility, false)
err = dbg.gui.SetFeature(gui.ReqSetMasking, false)
if err != nil {
return doNothing, err
}
case "DEBUG":
err = dbg.gui.SetFeature(gui.ReqToggleMasking)
if err != nil {
return doNothing, err
}
case "SCALE":
scl, present := tokens.Get()
if !present {
return doNothing, errors.New(errors.CommandError, fmt.Sprintf("value required for %s %s", command, action))
}
scale, err := strconv.ParseFloat(scl, 32)
if err != nil {
return doNothing, errors.New(errors.CommandError, fmt.Sprintf("%s %s value not valid (%s)", command, action, scl))
}
err = dbg.gui.SetFeature(gui.ReqSetScale, float32(scale))
return doNothing, err
case "DEBUGCOLORS":
err = dbg.gui.SetFeature(gui.ReqToggleAltColors)
if err != nil {
return doNothing, err
}
case "METASIGNALS":
err = dbg.gui.SetFeature(gui.ReqToggleShowMetaVideo)
case "ON":
err = dbg.gui.SetFeature(gui.ReqSetMasking, true)
if err != nil {
return doNothing, err
}
default:
// already caught by command line ValidateTokens()
err = dbg.gui.SetFeature(gui.ReqToggleMasking)
if err != nil {
return doNothing, err
}
}
} else {
err = dbg.gui.SetFeature(gui.ReqSetVisibility, true)
case "SCALE":
scl, present := tokens.Get()
if !present {
return doNothing, errors.New(errors.CommandError, fmt.Sprintf("value required for %s %s", command, action))
}
scale, err := strconv.ParseFloat(scl, 32)
if err != nil {
return doNothing, errors.New(errors.CommandError, fmt.Sprintf("%s %s value not valid (%s)", command, action, scl))
}
err = dbg.gui.SetFeature(gui.ReqSetScale, float32(scale))
return doNothing, err
case "ALT":
action, _ := tokens.Get()
action = strings.ToUpper(action)
switch action {
case "OFF":
err = dbg.gui.SetFeature(gui.ReqSetAltColors, false)
if err != nil {
return doNothing, err
}
case "ON":
err = dbg.gui.SetFeature(gui.ReqSetAltColors, true)
if err != nil {
return doNothing, err
}
default:
err = dbg.gui.SetFeature(gui.ReqToggleAltColors)
if err != nil {
return doNothing, err
}
}
case "OVERLAY":
if !dbg.reflectProcess {
return doNothing, errors.New(errors.ReflectionNotRunning)
}
action, _ := tokens.Get()
action = strings.ToUpper(action)
switch action {
case "OFF":
err = dbg.gui.SetFeature(gui.ReqSetOverlay, false)
if err != nil {
return doNothing, err
}
case "ON":
err = dbg.gui.SetFeature(gui.ReqSetOverlay, true)
if err != nil {
return doNothing, err
}
default:
err = dbg.gui.SetFeature(gui.ReqToggleOverlay)
if err != nil {
return doNothing, err
}
}
default:
err = dbg.gui.SetFeature(gui.ReqToggleVisibility)
if err != nil {
return doNothing, err
}

View file

@ -3,6 +3,7 @@ package debugger
import (
"gopher2600/debugger/commandline"
"gopher2600/debugger/console"
"gopher2600/debugger/reflection"
"gopher2600/debugger/script"
"gopher2600/disassembly"
"gopher2600/errors"
@ -45,13 +46,11 @@ type Debugger struct {
// most fruitfully performed through this structure
dbgmem *memoryDebug
// metavideo is additional information about the emulation state (ie.
// if a sprite was reset or if WSYNC is active, etc.)
//
// metavideo.Check() is called every video cycle to inform the gui of
// the metainformation of the last television signal
metaVideoProcess bool
metavideo *metavideoMonitor
// reflection is used to provideo additional information about the
// emulation. it is inherently slow so can be turned on/off with the
// reflectProcess switch
reflectProcess bool
relfectMonitor *reflection.Monitor
// halt conditions
breakpoints *breakpoints
@ -159,9 +158,9 @@ func NewDebugger(tvType string) (*Debugger, error) {
// set up debugging interface to memory
dbg.dbgmem = &memoryDebug{mem: dbg.vcs.Mem, symtable: &dbg.disasm.Symtable}
// set up metavideo monitor
dbg.metaVideoProcess = true
dbg.metavideo = newMetavideoMonitor(dbg.vcs, dbg.gui)
// set up reflection monitor
dbg.reflectProcess = true
dbg.relfectMonitor = reflection.NewMonitor(dbg.vcs, dbg.gui)
// set up breakpoints/traps
dbg.breakpoints = newBreakpoints(dbg)
@ -299,8 +298,8 @@ func (dbg *Debugger) videoCycle() error {
dbg.trapMessages = dbg.traps.check(dbg.trapMessages)
dbg.watchMessages = dbg.watches.check(dbg.watchMessages)
if dbg.metaVideoProcess {
return dbg.metavideo.Check()
if dbg.reflectProcess {
return dbg.relfectMonitor.Check()
}
return nil
@ -329,7 +328,11 @@ func (dbg *Debugger) inputLoop(inputter console.UserInput, videoCycle bool) erro
}
for {
dbg.checkInterruptsAndEvents()
err = dbg.checkInterruptsAndEvents()
if err != nil {
return err
}
if !dbg.running {
break // for loop
}
@ -459,7 +462,10 @@ func (dbg *Debugger) inputLoop(inputter console.UserInput, videoCycle bool) erro
}
}
dbg.checkInterruptsAndEvents()
err = dbg.checkInterruptsAndEvents()
if err != nil {
dbg.print(console.StyleError, err.Error())
}
if !dbg.running {
break // for loop
}

View file

@ -31,8 +31,14 @@ func (dbg *Debugger) guiEventHandler(event gui.Event) error {
// toggle debugging colours
err = dbg.gui.SetFeature(gui.ReqToggleAltColors)
case "2":
// toggle metasignals overlay
err = dbg.gui.SetFeature(gui.ReqToggleShowMetaVideo)
// toggle overlay
// !!TODO: handle error if reflection is not being processed
// if !dbg.reflectProcess {
// return errors.New(errors.ReflectionNotRunning)
// }
err = dbg.gui.SetFeature(gui.ReqToggleOverlay)
case "=":
fallthrough // equal sign is the same as plus, for convenience
@ -56,7 +62,9 @@ func (dbg *Debugger) guiEventHandler(event gui.Event) error {
return err
}
func (dbg *Debugger) checkInterruptsAndEvents() {
func (dbg *Debugger) checkInterruptsAndEvents() error {
var err error
// check interrupt channel and run any functions we find in there
select {
case <-dbg.intChan:
@ -86,9 +94,11 @@ func (dbg *Debugger) checkInterruptsAndEvents() {
}
}
case ev := <-dbg.guiChan:
dbg.guiEventHandler(ev)
err = dbg.guiEventHandler(ev)
default:
// pro-tip: default case required otherwise the select will block
// indefinately.
}
return err
}

View file

@ -19,7 +19,7 @@ var Help = map[string]string{
cmdLast: "Prints the result of the last cpu/video cycle",
cmdList: "List current entries for BREAKS and TRAPS",
cmdMemMap: "Display high-level VCS memory map",
cmdMetaVideo: "Turn metavideo processing on/off. Metavideo processing slows the debugger down.",
cmdReflect: "Turn reflection on/off. this will slow down the debugger.",
cmdMissile: "Display the current state of the missile 0/1 sprite",
cmdOnHalt: "Commands to run whenever emulation is halted (separate commands with comma)",
cmdOnStep: "Commands to run whenever emulation steps forward an cpu/video cycle (separate commands with comma)",

View file

@ -1,191 +0,0 @@
package debugger
// the whole metavideo system is slow. probably to do with indexing maps every
// video cycle. but I'm not too worried about it at the moment because it only
// ever runs in the debugger and the debugger is slow anyway (when compared to
// the playmode loop)
//
// it's also a bit of a hack. I didn't want to invade the emulation code too
// much but if we want to get fancier with this metavideo idea then we may have
// to. but in that case emulation performance should remain the priority.
import (
"gopher2600/gui/metavideo"
"gopher2600/hardware"
"gopher2600/hardware/tia/future"
"time"
)
// metavideoMonitor watches for writes to specific video related memory locations. when
// these locations are written to, a MetaSignal is sent to the Renderer
// implementation. moreover, if the monitor detects that the effect of the
// memory write is delayed or sustained, then the signal is repeated as
// appropriate.
type metavideoMonitor struct {
VCS *hardware.VCS
Renderer metavideo.Renderer
groupTIA metavideoGroup
groupPlayer0 metavideoGroup
groupPlayer1 metavideoGroup
groupMissile0 metavideoGroup
groupMissile1 metavideoGroup
groupBall metavideoGroup
}
func newMetavideoMonitor(vcs *hardware.VCS, renderer metavideo.Renderer) *metavideoMonitor {
mon := &metavideoMonitor{VCS: vcs, Renderer: renderer}
mon.groupTIA.addresses = metaSignals{
0x03: metavideo.MetaSignalAttributes{Label: "RSYNC", Red: 255, Green: 10, Blue: 0, Alpha: 255, Scheduled: true},
0x2a: metavideo.MetaSignalAttributes{Label: "HMOVE", Red: 255, Green: 20, Blue: 0, Alpha: 255, Scheduled: true},
0x2b: metavideo.MetaSignalAttributes{Label: "HMCLR", Red: 255, Green: 30, Blue: 0, Alpha: 255, Scheduled: false},
}
mon.groupPlayer0.addresses = metaSignals{
0x04: metavideo.MetaSignalAttributes{Label: "NUSIZx", Red: 0, Green: 10, Blue: 255, Alpha: 255, Scheduled: true},
0x10: metavideo.MetaSignalAttributes{Label: "RESPx", Red: 0, Green: 30, Blue: 255, Alpha: 255, Scheduled: true},
}
mon.groupPlayer1.addresses = metaSignals{
0x05: metavideo.MetaSignalAttributes{Label: "NUSIZx", Red: 0, Green: 50, Blue: 255, Alpha: 255, Scheduled: true},
0x11: metavideo.MetaSignalAttributes{Label: "RESPx", Red: 0, Green: 70, Blue: 255, Alpha: 255, Scheduled: true},
}
mon.groupMissile0.addresses = metaSignals{
0x04: metavideo.MetaSignalAttributes{Label: "NUSIZx", Red: 0, Green: 50, Blue: 255, Alpha: 255, Scheduled: false},
0x11: metavideo.MetaSignalAttributes{Label: "RESMx", Red: 0, Green: 70, Blue: 0, Alpha: 255, Scheduled: true},
}
mon.groupMissile1.addresses = metaSignals{
0x05: metavideo.MetaSignalAttributes{Label: "NUSIZx", Red: 0, Green: 50, Blue: 0, Alpha: 255, Scheduled: false},
0x12: metavideo.MetaSignalAttributes{Label: "RESMx", Red: 0, Green: 70, Blue: 0, Alpha: 255, Scheduled: true},
}
mon.groupBall.addresses = metaSignals{
0x14: metavideo.MetaSignalAttributes{Label: "RESBL", Red: 0, Green: 255, Blue: 10, Alpha: 255, Scheduled: true},
}
return mon
}
type metaSignals map[uint16]metavideo.MetaSignalAttributes
type metavideoGroup struct {
// the map of memory addresses to monitor
addresses metaSignals
// -----------------
// when memory has been written to we note the address and timestamp. then,
// a few cycles later, we check to see if lastAddress is one the group is
// interested in seeing
lastAddress uint16
lastAddressTimestamp time.Time
lastAddressFound int
// if the memory write resulted in an effect that won't occur until
// sometime in the future then the Delay attribute for the part of the
// system monitored by the group will yield a pointer to the future Event
lastEvent *future.Event
// a copy of the last metasignal sent to the metavideo renderer. we use
// this to repeat a signal when lastEvent is not nil and has not yet
// completed
signal metavideo.MetaSignalAttributes
}
// Check should be called every video cycle to record the current state of the
// emulation/system
func (mon *metavideoMonitor) Check() error {
if err := mon.groupWSYNC(); err != nil {
return err
}
if err := mon.checkGroup(&mon.groupTIA, mon.VCS.TIA.Delay); err != nil {
return err
}
if err := mon.checkGroup(&mon.groupPlayer0, mon.VCS.TIA.Video.Player0.Delay); err != nil {
return err
}
if err := mon.checkGroup(&mon.groupPlayer1, mon.VCS.TIA.Video.Player1.Delay); err != nil {
return err
}
if err := mon.checkGroup(&mon.groupMissile0, mon.VCS.TIA.Video.Missile0.Delay); err != nil {
return err
}
if err := mon.checkGroup(&mon.groupMissile1, mon.VCS.TIA.Video.Missile1.Delay); err != nil {
return err
}
if err := mon.checkGroup(&mon.groupBall, mon.VCS.TIA.Video.Ball.Delay); err != nil {
return err
}
return nil
}
func (mon *metavideoMonitor) groupWSYNC() error {
if mon.VCS.CPU.RdyFlg {
return nil
}
// special handling of WSYNC signal - we want every pixel to be coloured
// while the RdyFlag is false, not just when WSYNC is first triggered.
sig := metavideo.MetaSignalAttributes{Label: "WSYNC", Red: 0, Green: 0, Blue: 0, Alpha: 200}
return mon.Renderer.MetaSignal(sig)
}
func (mon *metavideoMonitor) checkGroup(group *metavideoGroup, delay future.Observer) error {
// if a new memory location (any memory location) has been written, then
// note the new address and begin the delayed metasignal process
//
// we filter on LastAccessTimeStamp rather than LastAccessAddress.
// filtering by address will probably work in most instances but it won't
// capture repeated writes to the same memory location.
if mon.VCS.Mem.LastAccessWrite && mon.VCS.Mem.LastAccessTimeStamp != group.lastAddressTimestamp {
group.lastAddress = mon.VCS.Mem.LastAccessAddress
group.lastAddressTimestamp = mon.VCS.Mem.LastAccessTimeStamp
// 4 cycles seems plenty of time for an address to be serviced
group.lastAddressFound = 4
}
var signalStart bool
var sig metavideo.MetaSignalAttributes
if group.lastAddressFound > 0 {
if sig, signalStart = group.addresses[group.lastAddress]; signalStart {
if sig.Scheduled {
// associate memory write with delay observation
if ev, ok := delay.Observe(sig.Label); ok {
group.lastEvent = ev
group.signal = sig
group.lastAddressFound = 1 // reduced to 0 almost immediately
}
} else {
group.lastEvent = nil
group.signal = sig
group.lastAddressFound = 1 // reduced to 0 almost immediately
}
}
group.lastAddressFound--
}
// send metasignal if an event is still running or if this is the end of a
// writeDelay period. the second condition catches memory writes that do
// not have an associated future.Event
if group.lastEvent != nil || signalStart {
group.lastEvent = nil
err := mon.Renderer.MetaSignal(group.signal)
if err != nil {
return err
}
}
return nil
}

View file

@ -0,0 +1,193 @@
package reflection
// the whole reflection system is slow. probably to do with indexing maps every
// video cycle. but I'm not too worried about it at the moment because it only
// ever runs in the debugger and the debugger is slow anyway (when compared to
// the playmode loop)
//
// it's also a bit of a hack. I didn't want to invade the emulation code too
// much but if we want to get fancier with this idea then we may have to. but
// in that case emulation performance should remain the priority.
import (
"gopher2600/gui/overlay"
"gopher2600/hardware"
"gopher2600/hardware/memory"
"gopher2600/hardware/tia/future"
"time"
)
// Monitor watches for writes to specific video related memory locations. when
// these locations are written to, a signal is sent to the overlay.Renderer
// implementation. moreover, if the monitor detects that the effect of the
// memory write is delayed or sustained, then the signal is repeated as
// appropriate.
type Monitor struct {
vcs *hardware.VCS
renderer overlay.Renderer
groupTIA addressMonitor
groupPlayer0 addressMonitor
groupPlayer1 addressMonitor
groupMissile0 addressMonitor
groupMissile1 addressMonitor
groupBall addressMonitor
}
// NewMonitor is the preferred method of initialisation for the Monitor type
func NewMonitor(vcs *hardware.VCS, renderer overlay.Renderer) *Monitor {
mon := &Monitor{vcs: vcs, renderer: renderer}
mon.groupTIA.addresses = overlaySignals{
0x03: overlay.Signal{Label: "RSYNC", Red: 255, Green: 10, Blue: 0, Alpha: 255, Scheduled: true},
0x2a: overlay.Signal{Label: "HMOVE", Red: 255, Green: 20, Blue: 0, Alpha: 255, Scheduled: true},
0x2b: overlay.Signal{Label: "HMCLR", Red: 255, Green: 30, Blue: 0, Alpha: 255, Scheduled: false},
}
mon.groupPlayer0.addresses = overlaySignals{
0x04: overlay.Signal{Label: "NUSIZx", Red: 0, Green: 10, Blue: 255, Alpha: 255, Scheduled: true},
0x10: overlay.Signal{Label: "RESPx", Red: 0, Green: 30, Blue: 255, Alpha: 255, Scheduled: true},
}
mon.groupPlayer1.addresses = overlaySignals{
0x05: overlay.Signal{Label: "NUSIZx", Red: 0, Green: 50, Blue: 255, Alpha: 255, Scheduled: true},
0x11: overlay.Signal{Label: "RESPx", Red: 0, Green: 70, Blue: 255, Alpha: 255, Scheduled: true},
}
mon.groupMissile0.addresses = overlaySignals{
0x04: overlay.Signal{Label: "NUSIZx", Red: 0, Green: 50, Blue: 255, Alpha: 255, Scheduled: false},
0x11: overlay.Signal{Label: "RESMx", Red: 0, Green: 70, Blue: 0, Alpha: 255, Scheduled: true},
}
mon.groupMissile1.addresses = overlaySignals{
0x05: overlay.Signal{Label: "NUSIZx", Red: 0, Green: 50, Blue: 0, Alpha: 255, Scheduled: false},
0x12: overlay.Signal{Label: "RESMx", Red: 0, Green: 70, Blue: 0, Alpha: 255, Scheduled: true},
}
mon.groupBall.addresses = overlaySignals{
0x14: overlay.Signal{Label: "RESBL", Red: 0, Green: 255, Blue: 10, Alpha: 255, Scheduled: true},
}
return mon
}
// Check should be called every video cycle to record the current state of the
// emulation/system
func (mon *Monitor) Check() error {
if err := mon.checkWSYNC(); err != nil {
return err
}
if err := mon.groupTIA.check(mon.renderer, mon.vcs.Mem, mon.vcs.TIA.Delay); err != nil {
return err
}
if err := mon.groupPlayer0.check(mon.renderer, mon.vcs.Mem, mon.vcs.TIA.Video.Player0.Delay); err != nil {
return err
}
if err := mon.groupPlayer1.check(mon.renderer, mon.vcs.Mem, mon.vcs.TIA.Video.Player1.Delay); err != nil {
return err
}
if err := mon.groupMissile0.check(mon.renderer, mon.vcs.Mem, mon.vcs.TIA.Video.Missile0.Delay); err != nil {
return err
}
if err := mon.groupMissile1.check(mon.renderer, mon.vcs.Mem, mon.vcs.TIA.Video.Missile1.Delay); err != nil {
return err
}
if err := mon.groupBall.check(mon.renderer, mon.vcs.Mem, mon.vcs.TIA.Video.Ball.Delay); err != nil {
return err
}
return nil
}
func (mon *Monitor) checkWSYNC() error {
if mon.vcs.CPU.RdyFlg {
return nil
}
// special handling of WSYNC signal - we want every pixel to be coloured
// while the RdyFlag is false, not just when WSYNC is first triggered.
sig := overlay.Signal{Label: "WSYNC", Red: 0, Green: 0, Blue: 0, Alpha: 200}
return mon.renderer.OverlaySignal(sig)
}
type overlaySignals map[uint16]overlay.Signal
type addressMonitor struct {
// the map of memory addresses to monitor
addresses overlaySignals
// -----------------
// when memory has been written to we note the address and timestamp. then,
// a few cycles later, we check to see if lastAddress is one the group is
// interested in seeing
lastAddress uint16
lastAddressTimestamp time.Time
lastAddressFound int
// if the memory write resulted in an effect that won't occur until
// sometime in the future then the Delay attribute for the part of the
// system monitored by the group will yield a pointer to the future Event
lastEvent *future.Event
// a copy of the last signal sent to the overlay renderer. we use
// this to repeat a signal when lastEvent is not nil and has not yet
// completed
signal overlay.Signal
}
func (adm *addressMonitor) check(rend overlay.Renderer, mem *memory.VCSMemory, delay future.Observer) error {
// if a new memory location (any memory location) has been written, then
// note the new address and begin the delayed signalling process
//
// we filter on LastAccessTimeStamp rather than LastAccessAddress.
// filtering by address will probably work in most instances but it won't
// capture repeated writes to the same memory location.
if mem.LastAccessWrite && mem.LastAccessTimeStamp != adm.lastAddressTimestamp {
adm.lastAddress = mem.LastAccessAddress
adm.lastAddressTimestamp = mem.LastAccessTimeStamp
// 4 cycles seems plenty of time for an address to be serviced
adm.lastAddressFound = 4
}
var signalStart bool
var sig overlay.Signal
if adm.lastAddressFound > 0 {
if sig, signalStart = adm.addresses[adm.lastAddress]; signalStart {
if sig.Scheduled {
// associate memory write with delay observation
if ev, ok := delay.Observe(sig.Label); ok {
adm.lastEvent = ev
adm.signal = sig
adm.lastAddressFound = 1 // reduced to 0 almost immediately
}
} else {
adm.lastEvent = nil
adm.signal = sig
adm.lastAddressFound = 1 // reduced to 0 almost immediately
}
}
adm.lastAddressFound--
}
// send signal if an event is still running or if this is the end of a
// writeDelay period. the second condition catches memory writes that do
// not have an associated future.Event
if adm.lastEvent != nil || signalStart {
adm.lastEvent = nil
err := rend.OverlaySignal(adm.signal)
if err != nil {
return err
}
}
return nil
}

View file

@ -8,6 +8,10 @@ const (
// panic()s and cause the program (or the sub-system) to cease as soon as
// possible.
//
// if is not practical to cause the program to cease then at the very
// least, the PanicError should result in the display of the error message
// in big, friendly letters.
//
// actual panic()s should only be used when the mistake is so heinous that
// it suggests a fundamental misunderstanding has taken place and so, as it
// were, all bets are off.
@ -33,6 +37,7 @@ const (
InvalidTarget
CommandError
TerminalError
ReflectionNotRunning
// script
ScriptScribeError

View file

@ -2,7 +2,7 @@ package errors
var messages = map[Errno]string{
// panics
PanicError: "fatality: %s: %s",
PanicError: "FATALITY: %s: %s",
// sentinals
UserInterrupt: "user interrupt",
@ -19,11 +19,12 @@ var messages = map[Errno]string{
DisasmError: "error during disassembly: %s",
// debugger
ParserError: "parser error: %s: %s (char %d)", // first placeholder is the command definition
ValidationError: "%s for %s",
InvalidTarget: "invalid target (%s)",
CommandError: "%s",
TerminalError: "%s",
ParserError: "parser error: %s: %s (char %d)", // first placeholder is the command definition
ValidationError: "%s for %s",
InvalidTarget: "invalid target (%s)",
CommandError: "%s",
TerminalError: "%s",
ReflectionNotRunning: "reflection process is not running",
// script
ScriptFileError: "script error: %s",

View file

@ -1,17 +1,20 @@
package gui
import (
"gopher2600/gui/metavideo"
"gopher2600/gui/overlay"
"gopher2600/television"
)
// FeatureReq is used to request the setting of a gui attribute
// eg. toggling the metavideo layer
// eg. toggling the overlay
type FeatureReq int
// list of valid feature requests
// list of valid feature requests. argument must be of the type specified or
// else the interface{} type conversion will fail and the application will
// probably crash
const (
ReqSetVisibility FeatureReq = iota // bool, optional bool (update on show)
ReqSetVisibility FeatureReq = iota // bool, optional bool (update on show) default true
ReqToggleVisibility // optional bool (update on show) default true
ReqSetVisibilityStable // none
ReqSetAllowDebugging // bool
ReqSetPause // bool
@ -19,8 +22,8 @@ const (
ReqToggleMasking // none
ReqSetAltColors // bool
ReqToggleAltColors // none
ReqSetShowMetaVideo // bool
ReqToggleShowMetaVideo // none
ReqSetOverlay // bool
ReqToggleOverlay // none
ReqSetScale // float
ReqIncScale // none
ReqDecScale // none
@ -31,7 +34,7 @@ type GUI interface {
television.Television
television.Renderer
television.AudioMixer
metavideo.Renderer
overlay.Renderer
// returns true if GUI is currently visible. false if not
IsVisible() bool

View file

@ -1,21 +1,21 @@
package metavideo
package overlay
// Renderer implementations will add signal information to a presentation layer
// somehow.
type Renderer interface {
MetaSignal(MetaSignalAttributes) error
OverlaySignal(Signal) error
}
// MetaSignalAttributes contains information about the last television signal. it is up to
// the Renderer to match this up with the last television signal
type MetaSignalAttributes struct {
// Signal contains additional debugging information from the last video cycle.
// it is up to the Renderer to match this up with the last television signal
type Signal struct {
Label string
// Renderer implementations are free to use the color information
// as they wish (adding alpha information seems a probable scenario).
Red, Green, Blue, Alpha byte
// whether the meta-signal is one that is "instant" or resolves after a
// whether the attribute is one that is "instant" or resolves after a
// short scheduled delay
Scheduled bool
}

View file

@ -1,12 +1,12 @@
package sdl
import (
"gopher2600/gui/metavideo"
"gopher2600/gui/overlay"
"github.com/veandco/go-sdl2/sdl"
)
type metaVideoOverlay struct {
type sdlOverlay struct {
scr *screen
texture *sdl.Texture
@ -18,8 +18,8 @@ type metaVideoOverlay struct {
labels [][]string
}
func newMetaVideoOverlay(scr *screen) (*metaVideoOverlay, error) {
mv := new(metaVideoOverlay)
func newSdlOverlay(scr *screen) (*sdlOverlay, error) {
mv := new(sdlOverlay)
mv.scr = scr
// our acutal screen data
@ -51,7 +51,7 @@ func newMetaVideoOverlay(scr *screen) (*metaVideoOverlay, error) {
return mv, nil
}
func (mv *metaVideoOverlay) setPixel(sig metavideo.MetaSignalAttributes) error {
func (mv *sdlOverlay) setPixel(sig overlay.Signal) error {
i := (mv.scr.lastY*mv.scr.maxWidth + mv.scr.lastX) * scrDepth
if i >= int32(len(mv.pixels)) {
@ -69,7 +69,7 @@ func (mv *metaVideoOverlay) setPixel(sig metavideo.MetaSignalAttributes) error {
return nil
}
func (mv *metaVideoOverlay) newFrame() {
func (mv *sdlOverlay) newFrame() {
// swap pixel array with pixelsFade array
// -- see comment in sdl.screen.newFrame() function for why we do this
swp := mv.pixels
@ -82,7 +82,7 @@ func (mv *metaVideoOverlay) newFrame() {
}
}
func (mv *metaVideoOverlay) update(paused bool) error {
func (mv *sdlOverlay) update(paused bool) error {
if paused {
err := mv.textureFade.Update(nil, mv.pixelsFade, int(mv.scr.maxWidth*scrDepth))
if err != nil {
@ -108,12 +108,12 @@ func (mv *metaVideoOverlay) update(paused bool) error {
return nil
}
// MetaSignal recieves (and processes) additional emulator information from the emulator
func (gtv *GUI) MetaSignal(sig metavideo.MetaSignalAttributes) error {
// OverlaySignal recieves (and processes) additional emulator information from the emulator
func (gtv *GUI) OverlaySignal(sig overlay.Signal) error {
// don't do anything if debugging is not enabled
if !gtv.allowDebugging {
return nil
}
return gtv.scr.metaVideo.setPixel(sig)
return gtv.scr.overlay.setPixel(sig)
}

View file

@ -3,10 +3,19 @@ package sdl
import (
"gopher2600/errors"
"gopher2600/gui"
"github.com/veandco/go-sdl2/sdl"
)
// SetFeature is used to set a television attribute
func (gtv *GUI) SetFeature(request gui.FeatureReq, args ...interface{}) error {
func (gtv *GUI) SetFeature(request gui.FeatureReq, args ...interface{}) (returnedErr error) {
// lazy (but clear) handling of type assertion errors
defer func() {
if r := recover(); r != nil {
returnedErr = errors.New(errors.PanicError, "sdl.SetFeature()", r)
}
}()
switch request {
case gui.ReqSetVisibilityStable:
err := gtv.scr.stb.resolveSetVisibility()
@ -27,6 +36,19 @@ func (gtv *GUI) SetFeature(request gui.FeatureReq, args ...interface{}) error {
gtv.scr.window.Hide()
}
case gui.ReqToggleVisibility:
if gtv.scr.window.GetFlags()&sdl.WINDOW_HIDDEN == sdl.WINDOW_HIDDEN {
gtv.scr.window.Show()
// update screen
// -- default args[1] of true if not present
if len(args) < 2 || args[1].(bool) {
gtv.update()
}
} else {
gtv.scr.window.Hide()
}
case gui.ReqSetAllowDebugging:
gtv.setDebugging(args[0].(bool))
gtv.update()
@ -51,12 +73,12 @@ func (gtv *GUI) SetFeature(request gui.FeatureReq, args ...interface{}) error {
gtv.scr.useAltPixels = !gtv.scr.useAltPixels
gtv.update()
case gui.ReqSetShowMetaVideo:
gtv.scr.showMetaVideo = args[0].(bool)
case gui.ReqSetOverlay:
gtv.scr.overlayActive = args[0].(bool)
gtv.update()
case gui.ReqToggleShowMetaVideo:
gtv.scr.showMetaVideo = !gtv.scr.showMetaVideo
case gui.ReqToggleOverlay:
gtv.scr.overlayActive = !gtv.scr.overlayActive
gtv.update()
case gui.ReqSetScale:

View file

@ -74,11 +74,11 @@ type screen struct {
altPixelsFade []byte
useAltPixels bool
// overlay for screen showing metasignal information
// overlay for screen showing additional debugging information
// -- always allocated but only used when tv.allowDebugging and
// showMetaVideo are true
metaVideo *metaVideoOverlay
showMetaVideo bool
// overlayActive are true
overlay *sdlOverlay
overlayActive bool
}
func newScreen(gtv *GUI) (*screen, error) {
@ -160,7 +160,7 @@ func (scr *screen) changeTVSpec() error {
}
// new overlay
scr.metaVideo, err = newMetaVideoOverlay(scr)
scr.overlay, err = newSdlOverlay(scr)
if err != nil {
return err
}
@ -323,9 +323,9 @@ func (scr *screen) update(paused bool) error {
scr.renderer.FillRect(&sdl.Rect{X: 0, Y: 0, W: int32(television.ClocksPerHblank), H: int32(scr.spec.ScanlinesTotal)})
}
// show metasignal overlay
if scr.gtv.allowDebugging && scr.showMetaVideo {
err = scr.metaVideo.update(paused)
// show overlay
if scr.gtv.allowDebugging && scr.overlayActive {
err = scr.overlay.update(paused)
if err != nil {
return err
}
@ -390,8 +390,8 @@ func (scr *screen) newFrame() {
scr.pixels = scr.pixelsFade
scr.pixelsFade = swp
// clear pixels in metavideo overlay
scr.metaVideo.newFrame()
// clear pixels in overlay
scr.overlay.newFrame()
// swap pixel array with pixelsFade array
// -- see comment above

View file

@ -56,7 +56,7 @@ func initDBSession(db *database.Session) error {
// RegressList displays all entries in the database
func RegressList(output io.Writer) error {
if output == nil {
return errors.New(errors.PanicError, "RegressList", "io.Writer should not be nil (use nopWriter)")
return errors.New(errors.PanicError, "RegressList()", "io.Writer should not be nil (use nopWriter)")
}
db, err := database.StartSession(regressionDBFile, database.ActivityReading, initDBSession)

View file

@ -25,7 +25,7 @@ type SignalAttributes struct {
// AltPixel allows the emulator to set an alternative color for each pixel
// - used to signal the debug color in addition to the regular color
// - arguable that this be sent as a metasignal
// - arguable that this be sent as some sort of meta-signal
AltPixel ColorSignal
// the HSyncSimple attribute is not part of the real TV spec. The signal