mirror of
https://github.com/JetSetIlly/Gopher2600.git
synced 2024-06-02 20:18:20 -04:00
input system and ports system separated
playback/recorder and driven input systems moved out of the the ports package and into a new input package. how the input systems interact has been clarified and improved - for example, it is now posssible for a playback file to be used to drive two emulations for comparison purposes the debugger startup procedure has been clarified with two distinct startup functions for playmode and debugger - each of which take different arguments. the clarity has allowed the reintroduction of recording and playback to the main play mode
This commit is contained in:
parent
7e5326fe83
commit
58848acdf9
|
@ -30,9 +30,9 @@ type TV interface {
|
||||||
AddAudioMixer(television.AudioMixer)
|
AddAudioMixer(television.AudioMixer)
|
||||||
}
|
}
|
||||||
|
|
||||||
// VCS defines the VCS functions required by a bot.
|
// Input defines the Input functions required by a bot.
|
||||||
type VCS interface {
|
type Input interface {
|
||||||
QueueEvent(ports.InputEvent) error
|
PushEvent(ports.InputEvent) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// Diagnostic instances are sent over the Feedback Diagnostic channel.
|
// Diagnostic instances are sent over the Feedback Diagnostic channel.
|
||||||
|
|
|
@ -152,8 +152,8 @@ func (obs *observer) EndRendering() error {
|
||||||
type videoChessBot struct {
|
type videoChessBot struct {
|
||||||
obs *observer
|
obs *observer
|
||||||
|
|
||||||
vcs bots.VCS
|
input bots.Input
|
||||||
tv bots.TV
|
tv bots.TV
|
||||||
|
|
||||||
// quit as soon as possible when a value appears on the channel
|
// quit as soon as possible when a value appears on the channel
|
||||||
quit chan bool
|
quit chan bool
|
||||||
|
@ -364,9 +364,9 @@ func (bot *videoChessBot) moveCursorOnceStep(portid plugging.PortID, direction p
|
||||||
|
|
||||||
waiting := true
|
waiting := true
|
||||||
for waiting {
|
for waiting {
|
||||||
bot.vcs.QueueEvent(ports.InputEvent{Port: portid, Ev: direction, D: ports.DataStickTrue})
|
bot.input.PushEvent(ports.InputEvent{Port: portid, Ev: direction, D: ports.DataStickTrue})
|
||||||
bot.waitForFrames(downDuration)
|
bot.waitForFrames(downDuration)
|
||||||
bot.vcs.QueueEvent(ports.InputEvent{Port: portid, Ev: direction, D: ports.DataStickFalse})
|
bot.input.PushEvent(ports.InputEvent{Port: portid, Ev: direction, D: ports.DataStickFalse})
|
||||||
select {
|
select {
|
||||||
case <-bot.obs.audioFeedback:
|
case <-bot.obs.audioFeedback:
|
||||||
waiting = false
|
waiting = false
|
||||||
|
@ -462,9 +462,9 @@ func (bot *videoChessBot) moveCursor(moveCol int, moveRow int, shortcut bool) {
|
||||||
|
|
||||||
waiting := true
|
waiting := true
|
||||||
for waiting {
|
for waiting {
|
||||||
bot.vcs.QueueEvent(ports.InputEvent{Port: plugging.PortLeftPlayer, Ev: ports.Fire, D: true})
|
bot.input.PushEvent(ports.InputEvent{Port: plugging.PortLeftPlayer, Ev: ports.Fire, D: true})
|
||||||
bot.waitForFrames(downDuration)
|
bot.waitForFrames(downDuration)
|
||||||
bot.vcs.QueueEvent(ports.InputEvent{Port: plugging.PortLeftPlayer, Ev: ports.Fire, D: false})
|
bot.input.PushEvent(ports.InputEvent{Port: plugging.PortLeftPlayer, Ev: ports.Fire, D: false})
|
||||||
select {
|
select {
|
||||||
case <-bot.obs.audioFeedback:
|
case <-bot.obs.audioFeedback:
|
||||||
waiting = false
|
waiting = false
|
||||||
|
@ -489,10 +489,10 @@ func (bot *videoChessBot) waitForFrames(n int) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewVideoChess creates a new bot able to play chess (via a UCI engine).
|
// NewVideoChess creates a new bot able to play chess (via a UCI engine).
|
||||||
func NewVideoChess(vcs bots.VCS, tv bots.TV) (bots.Bot, error) {
|
func NewVideoChess(vcs bots.Input, tv bots.TV) (bots.Bot, error) {
|
||||||
bot := &videoChessBot{
|
bot := &videoChessBot{
|
||||||
obs: newObserver(),
|
obs: newObserver(),
|
||||||
vcs: vcs,
|
input: vcs,
|
||||||
tv: tv,
|
tv: tv,
|
||||||
quit: make(chan bool),
|
quit: make(chan bool),
|
||||||
prevPosition: image.NewRGBA(image.Rect(0, 0, specification.ClksScanline, specification.AbsoluteMaxScanlines)),
|
prevPosition: image.NewRGBA(image.Rect(0, 0, specification.ClksScanline, specification.AbsoluteMaxScanlines)),
|
||||||
|
|
|
@ -26,17 +26,17 @@ import (
|
||||||
|
|
||||||
// Bots keeps track of the running bot and handles loading and termination.
|
// Bots keeps track of the running bot and handles loading and termination.
|
||||||
type Bots struct {
|
type Bots struct {
|
||||||
vcs bots.VCS
|
input bots.Input
|
||||||
tv bots.TV
|
tv bots.TV
|
||||||
|
|
||||||
running bots.Bot
|
running bots.Bot
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewBots is the preferred method of initialisation for the Bots type.
|
// NewBots is the preferred method of initialisation for the Bots type.
|
||||||
func NewBots(vcs bots.VCS, tv bots.TV) *Bots {
|
func NewBots(input bots.Input, tv bots.TV) *Bots {
|
||||||
return &Bots{
|
return &Bots{
|
||||||
vcs: vcs,
|
input: input,
|
||||||
tv: tv,
|
tv: tv,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,7 +48,7 @@ func (b *Bots) ActivateBot(cartHash string) (*bots.Feedback, error) {
|
||||||
|
|
||||||
switch cartHash {
|
switch cartHash {
|
||||||
case "043ef523e4fcb9fc2fc2fda21f15671bf8620fc3":
|
case "043ef523e4fcb9fc2fc2fda21f15671bf8620fc3":
|
||||||
b.running, err = chess.NewVideoChess(b.vcs, b.tv)
|
b.running, err = chess.NewVideoChess(b.input, b.tv)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, curated.Errorf("bots: %v", err)
|
return nil, curated.Errorf("bots: %v", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -98,11 +98,11 @@ func NewComparison(driverVCS *hardware.VCS) (*Comparison, error) {
|
||||||
|
|
||||||
// synchronise RIOT ports
|
// synchronise RIOT ports
|
||||||
sync := make(chan ports.TimedInputEvent, 32)
|
sync := make(chan ports.TimedInputEvent, 32)
|
||||||
err = cmp.VCS.RIOT.Ports.SynchroniseWithDriver(sync, tv)
|
err = cmp.VCS.Input.AttachPassenger(sync)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, curated.Errorf("comparison: %v", err)
|
return nil, curated.Errorf("comparison: %v", err)
|
||||||
}
|
}
|
||||||
err = driverVCS.RIOT.Ports.SynchroniseWithPassenger(sync, driverVCS.TV)
|
err = driverVCS.Input.AttachDriver(sync)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, curated.Errorf("comparison: %v", err)
|
return nil, curated.Errorf("comparison: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -180,6 +180,7 @@ func (cmp *Comparison) CreateFromLoader(cartload cartridgeloader.Loader) error {
|
||||||
cmp.isEmulating.Store(false)
|
cmp.isEmulating.Store(false)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
// not using setup system to attach cartridge. maybe we should?
|
||||||
err := cmp.VCS.AttachCartridge(cartload)
|
err := cmp.VCS.AttachCartridge(cartload)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cmp.driver.quit <- err
|
cmp.driver.quit <- err
|
||||||
|
|
|
@ -1352,55 +1352,55 @@ func (dbg *Debugger) processTokens(tokens *commandline.Tokens) error {
|
||||||
switch strings.ToUpper(arg) {
|
switch strings.ToUpper(arg) {
|
||||||
case "P0":
|
case "P0":
|
||||||
inp := ports.InputEvent{Port: plugging.PortPanel, Ev: ports.PanelTogglePlayer0Pro, D: nil}
|
inp := ports.InputEvent{Port: plugging.PortPanel, Ev: ports.PanelTogglePlayer0Pro, D: nil}
|
||||||
_, err = dbg.vcs.HandleInputEvent(inp)
|
_, err = dbg.vcs.Input.HandleInputEvent(inp)
|
||||||
case "P1":
|
case "P1":
|
||||||
inp := ports.InputEvent{Port: plugging.PortPanel, Ev: ports.PanelTogglePlayer1Pro, D: nil}
|
inp := ports.InputEvent{Port: plugging.PortPanel, Ev: ports.PanelTogglePlayer1Pro, D: nil}
|
||||||
_, err = dbg.vcs.HandleInputEvent(inp)
|
_, err = dbg.vcs.Input.HandleInputEvent(inp)
|
||||||
case "COL":
|
case "COL":
|
||||||
inp := ports.InputEvent{Port: plugging.PortPanel, Ev: ports.PanelToggleColor, D: nil}
|
inp := ports.InputEvent{Port: plugging.PortPanel, Ev: ports.PanelToggleColor, D: nil}
|
||||||
_, err = dbg.vcs.HandleInputEvent(inp)
|
_, err = dbg.vcs.Input.HandleInputEvent(inp)
|
||||||
}
|
}
|
||||||
case "SET":
|
case "SET":
|
||||||
arg, _ := tokens.Get()
|
arg, _ := tokens.Get()
|
||||||
switch strings.ToUpper(arg) {
|
switch strings.ToUpper(arg) {
|
||||||
case "P0PRO":
|
case "P0PRO":
|
||||||
inp := ports.InputEvent{Port: plugging.PortPanel, Ev: ports.PanelSetPlayer0Pro, D: true}
|
inp := ports.InputEvent{Port: plugging.PortPanel, Ev: ports.PanelSetPlayer0Pro, D: true}
|
||||||
_, err = dbg.vcs.HandleInputEvent(inp)
|
_, err = dbg.vcs.Input.HandleInputEvent(inp)
|
||||||
case "P1PRO":
|
case "P1PRO":
|
||||||
inp := ports.InputEvent{Port: plugging.PortPanel, Ev: ports.PanelSetPlayer1Pro, D: true}
|
inp := ports.InputEvent{Port: plugging.PortPanel, Ev: ports.PanelSetPlayer1Pro, D: true}
|
||||||
_, err = dbg.vcs.HandleInputEvent(inp)
|
_, err = dbg.vcs.Input.HandleInputEvent(inp)
|
||||||
case "P0AM":
|
case "P0AM":
|
||||||
inp := ports.InputEvent{Port: plugging.PortPanel, Ev: ports.PanelSetPlayer0Pro, D: false}
|
inp := ports.InputEvent{Port: plugging.PortPanel, Ev: ports.PanelSetPlayer0Pro, D: false}
|
||||||
_, err = dbg.vcs.HandleInputEvent(inp)
|
_, err = dbg.vcs.Input.HandleInputEvent(inp)
|
||||||
case "P1AM":
|
case "P1AM":
|
||||||
inp := ports.InputEvent{Port: plugging.PortPanel, Ev: ports.PanelSetPlayer1Pro, D: false}
|
inp := ports.InputEvent{Port: plugging.PortPanel, Ev: ports.PanelSetPlayer1Pro, D: false}
|
||||||
_, err = dbg.vcs.HandleInputEvent(inp)
|
_, err = dbg.vcs.Input.HandleInputEvent(inp)
|
||||||
case "COL":
|
case "COL":
|
||||||
inp := ports.InputEvent{Port: plugging.PortPanel, Ev: ports.PanelSetColor, D: true}
|
inp := ports.InputEvent{Port: plugging.PortPanel, Ev: ports.PanelSetColor, D: true}
|
||||||
_, err = dbg.vcs.HandleInputEvent(inp)
|
_, err = dbg.vcs.Input.HandleInputEvent(inp)
|
||||||
case "BW":
|
case "BW":
|
||||||
inp := ports.InputEvent{Port: plugging.PortPanel, Ev: ports.PanelSetColor, D: false}
|
inp := ports.InputEvent{Port: plugging.PortPanel, Ev: ports.PanelSetColor, D: false}
|
||||||
_, err = dbg.vcs.HandleInputEvent(inp)
|
_, err = dbg.vcs.Input.HandleInputEvent(inp)
|
||||||
}
|
}
|
||||||
case "HOLD":
|
case "HOLD":
|
||||||
arg, _ := tokens.Get()
|
arg, _ := tokens.Get()
|
||||||
switch strings.ToUpper(arg) {
|
switch strings.ToUpper(arg) {
|
||||||
case "SELECT":
|
case "SELECT":
|
||||||
inp := ports.InputEvent{Port: plugging.PortPanel, Ev: ports.PanelSelect, D: true}
|
inp := ports.InputEvent{Port: plugging.PortPanel, Ev: ports.PanelSelect, D: true}
|
||||||
_, err = dbg.vcs.HandleInputEvent(inp)
|
_, err = dbg.vcs.Input.HandleInputEvent(inp)
|
||||||
case "RESET":
|
case "RESET":
|
||||||
inp := ports.InputEvent{Port: plugging.PortPanel, Ev: ports.PanelReset, D: true}
|
inp := ports.InputEvent{Port: plugging.PortPanel, Ev: ports.PanelReset, D: true}
|
||||||
_, err = dbg.vcs.HandleInputEvent(inp)
|
_, err = dbg.vcs.Input.HandleInputEvent(inp)
|
||||||
}
|
}
|
||||||
case "RELEASE":
|
case "RELEASE":
|
||||||
arg, _ := tokens.Get()
|
arg, _ := tokens.Get()
|
||||||
switch strings.ToUpper(arg) {
|
switch strings.ToUpper(arg) {
|
||||||
case "SELECT":
|
case "SELECT":
|
||||||
inp := ports.InputEvent{Port: plugging.PortPanel, Ev: ports.PanelSelect, D: false}
|
inp := ports.InputEvent{Port: plugging.PortPanel, Ev: ports.PanelSelect, D: false}
|
||||||
_, err = dbg.vcs.HandleInputEvent(inp)
|
_, err = dbg.vcs.Input.HandleInputEvent(inp)
|
||||||
case "RESET":
|
case "RESET":
|
||||||
inp := ports.InputEvent{Port: plugging.PortPanel, Ev: ports.PanelReset, D: false}
|
inp := ports.InputEvent{Port: plugging.PortPanel, Ev: ports.PanelReset, D: false}
|
||||||
_, err = dbg.vcs.HandleInputEvent(inp)
|
_, err = dbg.vcs.Input.HandleInputEvent(inp)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1457,10 +1457,10 @@ func (dbg *Debugger) processTokens(tokens *commandline.Tokens) error {
|
||||||
switch n {
|
switch n {
|
||||||
case 0:
|
case 0:
|
||||||
inp := ports.InputEvent{Port: plugging.PortLeftPlayer, Ev: event, D: value}
|
inp := ports.InputEvent{Port: plugging.PortLeftPlayer, Ev: event, D: value}
|
||||||
_, err = dbg.vcs.HandleInputEvent(inp)
|
_, err = dbg.vcs.Input.HandleInputEvent(inp)
|
||||||
case 1:
|
case 1:
|
||||||
inp := ports.InputEvent{Port: plugging.PortRightPlayer, Ev: event, D: value}
|
inp := ports.InputEvent{Port: plugging.PortRightPlayer, Ev: event, D: value}
|
||||||
_, err = dbg.vcs.HandleInputEvent(inp)
|
_, err = dbg.vcs.Input.HandleInputEvent(inp)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -1478,18 +1478,18 @@ func (dbg *Debugger) processTokens(tokens *commandline.Tokens) error {
|
||||||
case 0:
|
case 0:
|
||||||
if strings.ToUpper(key) == "NONE" {
|
if strings.ToUpper(key) == "NONE" {
|
||||||
inp := ports.InputEvent{Port: plugging.PortLeftPlayer, Ev: ports.KeypadUp, D: nil}
|
inp := ports.InputEvent{Port: plugging.PortLeftPlayer, Ev: ports.KeypadUp, D: nil}
|
||||||
_, err = dbg.vcs.HandleInputEvent(inp)
|
_, err = dbg.vcs.Input.HandleInputEvent(inp)
|
||||||
} else {
|
} else {
|
||||||
inp := ports.InputEvent{Port: plugging.PortLeftPlayer, Ev: ports.KeypadDown, D: rune(key[0])}
|
inp := ports.InputEvent{Port: plugging.PortLeftPlayer, Ev: ports.KeypadDown, D: rune(key[0])}
|
||||||
_, err = dbg.vcs.HandleInputEvent(inp)
|
_, err = dbg.vcs.Input.HandleInputEvent(inp)
|
||||||
}
|
}
|
||||||
case 1:
|
case 1:
|
||||||
if strings.ToUpper(key) == "NONE" {
|
if strings.ToUpper(key) == "NONE" {
|
||||||
inp := ports.InputEvent{Port: plugging.PortRightPlayer, Ev: ports.KeypadUp, D: nil}
|
inp := ports.InputEvent{Port: plugging.PortRightPlayer, Ev: ports.KeypadUp, D: nil}
|
||||||
_, err = dbg.vcs.HandleInputEvent(inp)
|
_, err = dbg.vcs.Input.HandleInputEvent(inp)
|
||||||
} else {
|
} else {
|
||||||
inp := ports.InputEvent{Port: plugging.PortLeftPlayer, Ev: ports.KeypadDown, D: rune(key[0])}
|
inp := ports.InputEvent{Port: plugging.PortLeftPlayer, Ev: ports.KeypadDown, D: rune(key[0])}
|
||||||
_, err = dbg.vcs.HandleInputEvent(inp)
|
_, err = dbg.vcs.Input.HandleInputEvent(inp)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -45,8 +45,10 @@ import (
|
||||||
"github.com/jetsetilly/gopher2600/hardware/television/coords"
|
"github.com/jetsetilly/gopher2600/hardware/television/coords"
|
||||||
"github.com/jetsetilly/gopher2600/logger"
|
"github.com/jetsetilly/gopher2600/logger"
|
||||||
"github.com/jetsetilly/gopher2600/prefs"
|
"github.com/jetsetilly/gopher2600/prefs"
|
||||||
|
"github.com/jetsetilly/gopher2600/recorder"
|
||||||
"github.com/jetsetilly/gopher2600/reflection"
|
"github.com/jetsetilly/gopher2600/reflection"
|
||||||
"github.com/jetsetilly/gopher2600/reflection/counter"
|
"github.com/jetsetilly/gopher2600/reflection/counter"
|
||||||
|
"github.com/jetsetilly/gopher2600/resources/unique"
|
||||||
"github.com/jetsetilly/gopher2600/rewind"
|
"github.com/jetsetilly/gopher2600/rewind"
|
||||||
"github.com/jetsetilly/gopher2600/setup"
|
"github.com/jetsetilly/gopher2600/setup"
|
||||||
"github.com/jetsetilly/gopher2600/tracker"
|
"github.com/jetsetilly/gopher2600/tracker"
|
||||||
|
@ -85,6 +87,10 @@ type Debugger struct {
|
||||||
// sure Close() is called on end
|
// sure Close() is called on end
|
||||||
loader *cartridgeloader.Loader
|
loader *cartridgeloader.Loader
|
||||||
|
|
||||||
|
// gameplay recorder/playback
|
||||||
|
recorder *recorder.Recorder
|
||||||
|
playback *recorder.Playback
|
||||||
|
|
||||||
// comparison emulator
|
// comparison emulator
|
||||||
comparison *comparison.Comparison
|
comparison *comparison.Comparison
|
||||||
|
|
||||||
|
@ -304,7 +310,7 @@ func NewDebugger(create CreateUserInterface, spec string, useSavekey bool, fpsCa
|
||||||
|
|
||||||
// create userinput/controllers handler
|
// create userinput/controllers handler
|
||||||
dbg.controllers = userinput.NewControllers()
|
dbg.controllers = userinput.NewControllers()
|
||||||
dbg.controllers.AddInputHandler(dbg.vcs)
|
dbg.controllers.AddInputHandler(dbg.vcs.Input)
|
||||||
|
|
||||||
// replace player 1 port with savekey
|
// replace player 1 port with savekey
|
||||||
if useSavekey {
|
if useSavekey {
|
||||||
|
@ -315,7 +321,7 @@ func NewDebugger(create CreateUserInterface, spec string, useSavekey bool, fpsCa
|
||||||
}
|
}
|
||||||
|
|
||||||
// create bot coordinator
|
// create bot coordinator
|
||||||
dbg.bots = wrangler.NewBots(dbg.vcs, dbg.vcs.TV)
|
dbg.bots = wrangler.NewBots(dbg.vcs.Input, dbg.vcs.TV)
|
||||||
|
|
||||||
// set up debugging interface to memory
|
// set up debugging interface to memory
|
||||||
dbg.dbgmem = &dbgmem.DbgMem{
|
dbg.dbgmem = &dbgmem.DbgMem{
|
||||||
|
@ -400,6 +406,12 @@ func NewDebugger(create CreateUserInterface, spec string, useSavekey bool, fpsCa
|
||||||
dbg.vcs.TV.SetFPSCap(fpsCap)
|
dbg.vcs.TV.SetFPSCap(fpsCap)
|
||||||
dbg.gui.SetFeature(gui.ReqMonitorSync, fpsCap)
|
dbg.gui.SetFeature(gui.ReqMonitorSync, fpsCap)
|
||||||
|
|
||||||
|
// initialise terminal
|
||||||
|
err = dbg.term.Initialise()
|
||||||
|
if err != nil {
|
||||||
|
return nil, curated.Errorf("debugger: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
return dbg, nil
|
return dbg, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -449,10 +461,13 @@ func (dbg *Debugger) setState(state emulation.State) {
|
||||||
// * see setState() comment, although debugger.SetFeature(ReqSetPause) will
|
// * see setState() comment, although debugger.SetFeature(ReqSetPause) will
|
||||||
// always be "noisy"
|
// always be "noisy"
|
||||||
func (dbg *Debugger) setStateQuiet(state emulation.State, quiet bool) {
|
func (dbg *Debugger) setStateQuiet(state emulation.State, quiet bool) {
|
||||||
// do not allow comparison emulation in the rewinding state. remove it if
|
|
||||||
// we ever enter the rewinding state
|
|
||||||
if state == emulation.Rewinding {
|
if state == emulation.Rewinding {
|
||||||
|
dbg.endPlayback()
|
||||||
|
dbg.endRecording()
|
||||||
dbg.endComparison()
|
dbg.endComparison()
|
||||||
|
|
||||||
|
// it is thought that bots are okay to enter the rewinding state. this
|
||||||
|
// might not be true in all future cases.
|
||||||
}
|
}
|
||||||
|
|
||||||
dbg.vcs.TV.SetEmulationState(state)
|
dbg.vcs.TV.SetEmulationState(state)
|
||||||
|
@ -487,6 +502,9 @@ func (dbg *Debugger) setMode(mode emulation.Mode) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// don't stop the recording, playback, comparison or bot sub-systems on
|
||||||
|
// change of mode
|
||||||
|
|
||||||
// if there is a halting condition that is not allowed in playmode (see
|
// if there is a halting condition that is not allowed in playmode (see
|
||||||
// targets type) then do not change the emulation mode
|
// targets type) then do not change the emulation mode
|
||||||
//
|
//
|
||||||
|
@ -565,10 +583,15 @@ func (dbg *Debugger) setMode(mode emulation.Mode) error {
|
||||||
|
|
||||||
// End cleans up any resources that may be dangling.
|
// End cleans up any resources that may be dangling.
|
||||||
func (dbg *Debugger) end() {
|
func (dbg *Debugger) end() {
|
||||||
|
dbg.endPlayback()
|
||||||
|
dbg.endRecording()
|
||||||
dbg.endComparison()
|
dbg.endComparison()
|
||||||
dbg.bots.Quit()
|
dbg.bots.Quit()
|
||||||
|
|
||||||
dbg.vcs.End()
|
dbg.vcs.End()
|
||||||
|
|
||||||
|
defer dbg.term.CleanUp()
|
||||||
|
|
||||||
// set ending state
|
// set ending state
|
||||||
err := dbg.gui.SetFeature(gui.ReqEnd)
|
err := dbg.gui.SetFeature(gui.ReqEnd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -582,57 +605,33 @@ func (dbg *Debugger) end() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Starts the main emulation sequence.
|
// StartInDebugMode starts the emulation with the debugger activated.
|
||||||
func (dbg *Debugger) Start(mode emulation.Mode, initScript string, cartload cartridgeloader.Loader, comparisonROM string, comparisonPrefs string) error {
|
func (dbg *Debugger) StartInDebugMode(initScript string, filename string, mapping string) error {
|
||||||
// do not allow comparison emulation inside the debugger. it's far too
|
// set running flag as early as possible
|
||||||
// complicated running two emulations that must be synced in the debugger
|
dbg.running = true
|
||||||
// loop
|
|
||||||
if mode == emulation.ModeDebugger && comparisonROM != "" {
|
|
||||||
return curated.Errorf("debugger: cannot run comparison emulation inside the debugger")
|
|
||||||
}
|
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
|
var cartload cartridgeloader.Loader
|
||||||
|
|
||||||
defer dbg.end()
|
if filename == "" {
|
||||||
err = dbg.start(mode, initScript, cartload, comparisonROM, comparisonPrefs)
|
cartload = cartridgeloader.Loader{}
|
||||||
if err != nil {
|
} else {
|
||||||
if curated.Has(err, terminal.UserQuit) {
|
cartload, err = cartridgeloader.NewLoader(filename, mapping)
|
||||||
return nil
|
if err != nil {
|
||||||
|
return curated.Errorf("debugger: %v", err)
|
||||||
}
|
}
|
||||||
return curated.Errorf("debugger: %v", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (dbg *Debugger) start(mode emulation.Mode, initScript string, cartload cartridgeloader.Loader, comparisonROM string, comparisonPrefs string) error {
|
|
||||||
// prepare user interface
|
|
||||||
err := dbg.term.Initialise()
|
|
||||||
if err != nil {
|
|
||||||
return curated.Errorf("debugger: %v", err)
|
|
||||||
}
|
|
||||||
defer dbg.term.CleanUp()
|
|
||||||
|
|
||||||
err = dbg.attachCartridge(cartload)
|
err = dbg.attachCartridge(cartload)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return curated.Errorf("debugger: %v", err)
|
return curated.Errorf("debugger: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = dbg.startComparison(comparisonROM, comparisonPrefs)
|
err = dbg.setMode(emulation.ModeDebugger)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return curated.Errorf("debugger: %v", err)
|
return curated.Errorf("debugger: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// set mode to the value requested in the function paramenters
|
|
||||||
err = dbg.setMode(mode)
|
|
||||||
if err != nil {
|
|
||||||
return curated.Errorf("debugger: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// debugger is running
|
|
||||||
dbg.running = true
|
|
||||||
|
|
||||||
// run initialisation script
|
|
||||||
if initScript != "" {
|
if initScript != "" {
|
||||||
scr, err := script.RescribeScript(initScript)
|
scr, err := script.RescribeScript(initScript)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
@ -647,6 +646,80 @@ func (dbg *Debugger) start(mode emulation.Mode, initScript string, cartload cart
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defer dbg.end()
|
||||||
|
err = dbg.run()
|
||||||
|
if err != nil {
|
||||||
|
if curated.Has(err, terminal.UserQuit) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return curated.Errorf("debugger: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// StartInPlaymode starts the emulation ready for game-play.
|
||||||
|
func (dbg *Debugger) StartInPlayMode(filename string, mapping string, record bool, comparisonROM string, comparisonPrefs string) error {
|
||||||
|
// set running flag as early as possible
|
||||||
|
dbg.running = true
|
||||||
|
|
||||||
|
var err error
|
||||||
|
var cartload cartridgeloader.Loader
|
||||||
|
|
||||||
|
if filename == "" {
|
||||||
|
cartload = cartridgeloader.Loader{}
|
||||||
|
} else {
|
||||||
|
cartload, err = cartridgeloader.NewLoader(filename, mapping)
|
||||||
|
if err != nil {
|
||||||
|
return curated.Errorf("debugger: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = recorder.IsPlaybackFile(filename)
|
||||||
|
if err != nil {
|
||||||
|
if !curated.Is(err, recorder.NotAPlaybackFile) {
|
||||||
|
return curated.Errorf("debugger: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = dbg.attachCartridge(cartload)
|
||||||
|
if err != nil {
|
||||||
|
return curated.Errorf("debugger: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if record {
|
||||||
|
dbg.startRecording(cartload.ShortName())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if record {
|
||||||
|
return curated.Errorf("debugger: cannot make a new recording using a playback file")
|
||||||
|
}
|
||||||
|
|
||||||
|
dbg.startPlayback(filename)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = dbg.startComparison(comparisonROM, comparisonPrefs)
|
||||||
|
if err != nil {
|
||||||
|
return curated.Errorf("debugger: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = dbg.setMode(emulation.ModePlay)
|
||||||
|
if err != nil {
|
||||||
|
return curated.Errorf("debugger: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer dbg.end()
|
||||||
|
err = dbg.run()
|
||||||
|
if err != nil {
|
||||||
|
if curated.Has(err, terminal.UserQuit) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return curated.Errorf("debugger: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dbg *Debugger) run() error {
|
||||||
// end script recording gracefully. this way we don't have to worry too
|
// end script recording gracefully. this way we don't have to worry too
|
||||||
// hard about script scribes
|
// hard about script scribes
|
||||||
defer func() {
|
defer func() {
|
||||||
|
@ -660,7 +733,7 @@ func (dbg *Debugger) start(mode emulation.Mode, initScript string, cartload cart
|
||||||
for dbg.running {
|
for dbg.running {
|
||||||
switch dbg.Mode() {
|
switch dbg.Mode() {
|
||||||
case emulation.ModePlay:
|
case emulation.ModePlay:
|
||||||
err = dbg.playLoop()
|
err := dbg.playLoop()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// if we ever encounter a cartridge ejected error in playmode
|
// if we ever encounter a cartridge ejected error in playmode
|
||||||
// then simply open up the ROM selector
|
// then simply open up the ROM selector
|
||||||
|
@ -685,7 +758,7 @@ func (dbg *Debugger) start(mode emulation.Mode, initScript string, cartload cart
|
||||||
return curated.Errorf("emulation state not supported on *start* of debugging loop: %s", dbg.State())
|
return curated.Errorf("emulation state not supported on *start* of debugging loop: %s", dbg.State())
|
||||||
}
|
}
|
||||||
|
|
||||||
err = dbg.inputLoop(dbg.term, false)
|
err := dbg.inputLoop(dbg.term, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return curated.Errorf("debugger: %v", err)
|
return curated.Errorf("debugger: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -707,7 +780,7 @@ func (dbg *Debugger) start(mode emulation.Mode, initScript string, cartload cart
|
||||||
|
|
||||||
// make sure any cartridge loader has been finished with
|
// make sure any cartridge loader has been finished with
|
||||||
if dbg.loader != nil {
|
if dbg.loader != nil {
|
||||||
err = dbg.loader.Close()
|
err := dbg.loader.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return curated.Errorf("debugger: %v", err)
|
return curated.Errorf("debugger: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -752,7 +825,9 @@ func (dbg *Debugger) reset(newCartridge bool) error {
|
||||||
// this is the glue that hold the cartridge and disassembly packages together.
|
// this is the glue that hold the cartridge and disassembly packages together.
|
||||||
// especially important is the repointing of the symbols table in the instance of dbgmem.
|
// especially important is the repointing of the symbols table in the instance of dbgmem.
|
||||||
func (dbg *Debugger) attachCartridge(cartload cartridgeloader.Loader) (e error) {
|
func (dbg *Debugger) attachCartridge(cartload cartridgeloader.Loader) (e error) {
|
||||||
// stop any existing comparison emulation and any existing bots
|
// stop optional sub-systems that shouldn't survive a new cartridge insertion
|
||||||
|
dbg.endPlayback()
|
||||||
|
dbg.endRecording()
|
||||||
dbg.endComparison()
|
dbg.endComparison()
|
||||||
dbg.bots.Quit()
|
dbg.bots.Quit()
|
||||||
|
|
||||||
|
@ -913,6 +988,63 @@ func (dbg *Debugger) attachCartridge(cartload cartridgeloader.Loader) (e error)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (dbg *Debugger) startRecording(cartShortName string) error {
|
||||||
|
recording := unique.Filename("recording", cartShortName)
|
||||||
|
|
||||||
|
var err error
|
||||||
|
|
||||||
|
dbg.recorder, err = recorder.NewRecorder(recording, dbg.vcs)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dbg *Debugger) endRecording() {
|
||||||
|
if dbg.recorder == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
dbg.recorder = nil
|
||||||
|
}()
|
||||||
|
|
||||||
|
err := dbg.recorder.End()
|
||||||
|
if err != nil {
|
||||||
|
logger.Logf("debugger", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dbg *Debugger) startPlayback(filename string) error {
|
||||||
|
plb, err := recorder.NewPlayback(filename)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = dbg.attachCartridge(plb.CartLoad)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = plb.AttachToVCSInput(dbg.vcs)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
dbg.playback = plb
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dbg *Debugger) endPlayback() {
|
||||||
|
if dbg.playback == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
dbg.playback = nil
|
||||||
|
dbg.vcs.Input.AttachPlayback(nil)
|
||||||
|
}
|
||||||
|
|
||||||
func (dbg *Debugger) startComparison(comparisonROM string, comparisonPrefs string) error {
|
func (dbg *Debugger) startComparison(comparisonROM string, comparisonPrefs string) error {
|
||||||
if comparisonROM == "" {
|
if comparisonROM == "" {
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -20,7 +20,6 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/jetsetilly/gopher2600/cartridgeloader"
|
|
||||||
"github.com/jetsetilly/gopher2600/debugger"
|
"github.com/jetsetilly/gopher2600/debugger"
|
||||||
"github.com/jetsetilly/gopher2600/debugger/terminal"
|
"github.com/jetsetilly/gopher2600/debugger/terminal"
|
||||||
"github.com/jetsetilly/gopher2600/emulation"
|
"github.com/jetsetilly/gopher2600/emulation"
|
||||||
|
@ -156,7 +155,7 @@ func TestDebugger_withNonExistantInitScript(t *testing.T) {
|
||||||
|
|
||||||
go trm.testSequence()
|
go trm.testSequence()
|
||||||
|
|
||||||
err = dbg.Start(emulation.ModeDebugger, "non_existent_script", cartridgeloader.Loader{}, "", "")
|
err = dbg.StartInDebugMode("", "", "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf(err.Error())
|
t.Fatalf(err.Error())
|
||||||
}
|
}
|
||||||
|
@ -179,7 +178,7 @@ func TestDebugger(t *testing.T) {
|
||||||
|
|
||||||
go trm.testSequence()
|
go trm.testSequence()
|
||||||
|
|
||||||
err = dbg.Start(emulation.ModeDebugger, "", cartridgeloader.Loader{}, "", "")
|
err = dbg.StartInDebugMode("", "", "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf(err.Error())
|
t.Fatalf(err.Error())
|
||||||
}
|
}
|
||||||
|
|
217
gopher2600.go
217
gopher2600.go
|
@ -44,8 +44,6 @@ import (
|
||||||
"github.com/jetsetilly/gopher2600/statsview"
|
"github.com/jetsetilly/gopher2600/statsview"
|
||||||
)
|
)
|
||||||
|
|
||||||
const defaultInitScript = "debuggerInit"
|
|
||||||
|
|
||||||
// communication between the main goroutine and the launch goroutine.
|
// communication between the main goroutine and the launch goroutine.
|
||||||
type mainSync struct {
|
type mainSync struct {
|
||||||
state chan stateRequest
|
state chan stateRequest
|
||||||
|
@ -267,145 +265,7 @@ func launch(sync *mainSync) {
|
||||||
sync.state <- stateRequest{req: reqQuit}
|
sync.state <- stateRequest{req: reqQuit}
|
||||||
}
|
}
|
||||||
|
|
||||||
// func play(md *modalflag.Modes, sync *mainSync) error {
|
const defaultInitScript = "debuggerInit"
|
||||||
// md.NewMode()
|
|
||||||
|
|
||||||
// mapping := md.AddString("mapping", "AUTO", "force use of cartridge mapping")
|
|
||||||
// spec := md.AddString("tv", "AUTO", "television specification: NTSC, PAL, PAL60")
|
|
||||||
// fullScreen := md.AddBool("fullscreen", false, "start in fullscreen mode")
|
|
||||||
// fpsCap := md.AddBool("fpscap", true, "cap fps to specification")
|
|
||||||
// record := md.AddBool("record", false, "record user input to a file")
|
|
||||||
// wav := md.AddString("wav", "", "record audio to wav file")
|
|
||||||
// patchFile := md.AddString("patch", "", "patch file to apply (cartridge args only)")
|
|
||||||
// hiscore := md.AddBool("hiscore", false, "contact hiscore server [EXPERIMENTAL]")
|
|
||||||
// log := md.AddBool("log", false, "echo debugging log to stdout")
|
|
||||||
// useSavekey := md.AddBool("savekey", false, "use savekey in player 1 port")
|
|
||||||
// multiload := md.AddInt("multiload", -1, "force multiload byte (supercharger only; 0 to 255)")
|
|
||||||
// profile := md.AddString("profile", "none", "run performance check with profiling: command separated CPU, MEM, TRACE or ALL")
|
|
||||||
|
|
||||||
// stats := &[]bool{false}[0]
|
|
||||||
// if statsview.Available() {
|
|
||||||
// stats = md.AddBool("statsview", false, fmt.Sprintf("run stats server (%s)", statsview.Address))
|
|
||||||
// }
|
|
||||||
|
|
||||||
// p, err := md.Parse()
|
|
||||||
// if err != nil || p != modalflag.ParseContinue {
|
|
||||||
// return err
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // set debugging log echo
|
|
||||||
// if *log {
|
|
||||||
// logger.SetEcho(os.Stdout)
|
|
||||||
// } else {
|
|
||||||
// logger.SetEcho(nil)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if *stats {
|
|
||||||
// statsview.Launch(os.Stdout)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// var cartload cartridgeloader.Loader
|
|
||||||
|
|
||||||
// switch len(md.RemainingArgs()) {
|
|
||||||
// case 0:
|
|
||||||
// // allow loading from file requester
|
|
||||||
|
|
||||||
// case 1:
|
|
||||||
// cartload, err = cartridgeloader.NewLoader(md.GetArg(0), *mapping)
|
|
||||||
// if err != nil {
|
|
||||||
// return err
|
|
||||||
// }
|
|
||||||
// defer cartload.Close()
|
|
||||||
|
|
||||||
// default:
|
|
||||||
// return fmt.Errorf("too many arguments for %s mode", md)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// tv, err := television.NewTelevision(*spec)
|
|
||||||
// if err != nil {
|
|
||||||
// return err
|
|
||||||
// }
|
|
||||||
// defer tv.End()
|
|
||||||
|
|
||||||
// // add wavwriter mixer if wav argument has been specified
|
|
||||||
// if *wav != "" {
|
|
||||||
// aw, err := wavwriter.New(*wav)
|
|
||||||
// if err != nil {
|
|
||||||
// return err
|
|
||||||
// }
|
|
||||||
// tv.AddAudioMixer(aw)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // create gui
|
|
||||||
// sync.state <- stateRequest{req: reqCreateGUI,
|
|
||||||
// args: guiCreate(func() (guiControl, error) {
|
|
||||||
// return sdlimgui.NewSdlImgui(tv)
|
|
||||||
// }),
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // wait for creator result
|
|
||||||
// var scr gui.GUI
|
|
||||||
// select {
|
|
||||||
// case g := <-sync.gui:
|
|
||||||
// scr = g.(gui.GUI)
|
|
||||||
|
|
||||||
// case err := <-sync.guiError:
|
|
||||||
// return err
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // set fps cap
|
|
||||||
// tv.SetFPSCap(*fpsCap)
|
|
||||||
// scr.SetFeature(gui.ReqMonitorSync, *fpsCap)
|
|
||||||
|
|
||||||
// // set full screen
|
|
||||||
// scr.SetFeature(gui.ReqFullScreen, *fullScreen)
|
|
||||||
|
|
||||||
// // turn off fallback ctrl-c handling. this so that the playmode can
|
|
||||||
// // end playback recordings gracefully
|
|
||||||
// sync.state <- stateRequest{req: reqNoIntSig}
|
|
||||||
|
|
||||||
// // check for profiling options
|
|
||||||
// o, err := performance.ParseProfileString(*profile)
|
|
||||||
// if err != nil {
|
|
||||||
// return err
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // set up a running function
|
|
||||||
// playLaunch := func() error {
|
|
||||||
// err = playmode.Play(tv, scr, *record, cartload, *patchFile, *hiscore, *useSavekey, *multiload)
|
|
||||||
// if err != nil {
|
|
||||||
// return err
|
|
||||||
// }
|
|
||||||
// return nil
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if o == performance.ProfileNone {
|
|
||||||
// err = playLaunch()
|
|
||||||
// if err != nil {
|
|
||||||
// return err
|
|
||||||
// }
|
|
||||||
// } else {
|
|
||||||
// // if profile generation has been requested then pass the
|
|
||||||
// // playLaunch() function prepared above, through the RunProfiler()
|
|
||||||
// // function
|
|
||||||
// err := performance.RunProfiler(o, "play", playLaunch)
|
|
||||||
// if err != nil {
|
|
||||||
// return err
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if *record {
|
|
||||||
// fmt.Println("! recording completed")
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // set ending state
|
|
||||||
// err = scr.SetFeature(gui.ReqEnd)
|
|
||||||
// if err != nil {
|
|
||||||
// return err
|
|
||||||
// }
|
|
||||||
|
|
||||||
// return nil
|
|
||||||
// }
|
|
||||||
|
|
||||||
func emulate(emulationMode emulation.Mode, md *modalflag.Modes, sync *mainSync) error {
|
func emulate(emulationMode emulation.Mode, md *modalflag.Modes, sync *mainSync) error {
|
||||||
md.NewMode()
|
md.NewMode()
|
||||||
|
@ -418,30 +278,49 @@ func emulate(emulationMode emulation.Mode, md *modalflag.Modes, sync *mainSync)
|
||||||
mapping := md.AddString("mapping", "AUTO", "force use of cartridge mapping")
|
mapping := md.AddString("mapping", "AUTO", "force use of cartridge mapping")
|
||||||
spec := md.AddString("tv", "AUTO", "television specification: NTSC, PAL, PAL60")
|
spec := md.AddString("tv", "AUTO", "television specification: NTSC, PAL, PAL60")
|
||||||
fpsCap := md.AddBool("fpscap", true, "cap fps to TV specification")
|
fpsCap := md.AddBool("fpscap", true, "cap fps to TV specification")
|
||||||
termType := md.AddString("term", "IMGUI", "terminal type to use in debug mode: IMGUI, COLOR, PLAIN")
|
|
||||||
initScript := md.AddString("initscript", defInitScript, "script to run on debugger start")
|
|
||||||
useSavekey := md.AddBool("savekey", false, "use savekey in player 1 port")
|
useSavekey := md.AddBool("savekey", false, "use savekey in player 1 port")
|
||||||
profile := md.AddString("profile", "none", "run performance check with profiling: command separated CPU, MEM, TRACE or ALL")
|
profile := md.AddString("profile", "none", "run performance check with profiling: command separated CPU, MEM, TRACE or ALL")
|
||||||
log := md.AddBool("log", false, "echo debugging log to stdout")
|
log := md.AddBool("log", false, "echo debugging log to stdout")
|
||||||
|
termType := md.AddString("term", "IMGUI", "terminal type to use in debug mode: IMGUI, COLOR, PLAIN")
|
||||||
|
|
||||||
// some arguments are mode specific
|
// wav := md.AddString("wav", "", "record audio to wav file")
|
||||||
|
// patchFile := md.AddString("patch", "", "patch file to apply (cartridge args only)")
|
||||||
|
// hiscore := md.AddBool("hiscore", false, "contact hiscore server [EXPERIMENTAL]")
|
||||||
|
// multiload := md.AddInt("multiload", -1, "force multiload byte (supercharger only; 0 to 255)")
|
||||||
|
|
||||||
|
// playmode specific arguments
|
||||||
var comparisonROM *string
|
var comparisonROM *string
|
||||||
var comparisonPrefs *string
|
var comparisonPrefs *string
|
||||||
|
var record *bool
|
||||||
if emulationMode == emulation.ModePlay {
|
if emulationMode == emulation.ModePlay {
|
||||||
comparisonROM = md.AddString("comparisonROM", "", "ROM to run in parallel for comparison")
|
comparisonROM = md.AddString("comparisonROM", "", "ROM to run in parallel for comparison")
|
||||||
comparisonPrefs = md.AddString("comparisonPrefs", "", "preferences for comparison emulation")
|
comparisonPrefs = md.AddString("comparisonPrefs", "", "preferences for comparison emulation")
|
||||||
|
record = md.AddBool("record", false, "record user input to a file")
|
||||||
}
|
}
|
||||||
|
|
||||||
stats := &[]bool{false}[0]
|
// debugger specific arguments
|
||||||
|
var initScript *string
|
||||||
|
if emulationMode == emulation.ModeDebugger {
|
||||||
|
initScript = md.AddString("initscript", defInitScript, "script to run on debugger start")
|
||||||
|
}
|
||||||
|
|
||||||
|
// statsview if available
|
||||||
|
var stats *bool
|
||||||
if statsview.Available() {
|
if statsview.Available() {
|
||||||
stats = md.AddBool("statsview", false, fmt.Sprintf("run stats server (%s)", statsview.Address))
|
stats = md.AddBool("statsview", false, fmt.Sprintf("run stats server (%s)", statsview.Address))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// parse arguments
|
||||||
p, err := md.Parse()
|
p, err := md.Parse()
|
||||||
if err != nil || p != modalflag.ParseContinue {
|
if err != nil || p != modalflag.ParseContinue {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// check remaining arguments
|
||||||
|
if len(md.RemainingArgs()) > 1 {
|
||||||
|
return fmt.Errorf("too many arguments for %s mode", md)
|
||||||
|
}
|
||||||
|
|
||||||
// set debugging log echo
|
// set debugging log echo
|
||||||
if *log {
|
if *log {
|
||||||
logger.SetEcho(os.Stdout)
|
logger.SetEcho(os.Stdout)
|
||||||
|
@ -449,7 +328,7 @@ func emulate(emulationMode emulation.Mode, md *modalflag.Modes, sync *mainSync)
|
||||||
logger.SetEcho(nil)
|
logger.SetEcho(nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
if *stats {
|
if stats != nil && *stats {
|
||||||
statsview.Launch(os.Stdout)
|
statsview.Launch(os.Stdout)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -460,6 +339,7 @@ func emulate(emulationMode emulation.Mode, md *modalflag.Modes, sync *mainSync)
|
||||||
// turning the emulation's interrupt handler off
|
// turning the emulation's interrupt handler off
|
||||||
sync.state <- stateRequest{req: reqNoIntSig}
|
sync.state <- stateRequest{req: reqNoIntSig}
|
||||||
|
|
||||||
|
// GUI create function
|
||||||
create := func(e emulation.Emulation) (gui.GUI, terminal.Terminal, error) {
|
create := func(e emulation.Emulation) (gui.GUI, terminal.Terminal, error) {
|
||||||
var term terminal.Terminal
|
var term terminal.Terminal
|
||||||
var scr gui.GUI
|
var scr gui.GUI
|
||||||
|
@ -512,23 +392,6 @@ func emulate(emulationMode emulation.Mode, md *modalflag.Modes, sync *mainSync)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
var cartload cartridgeloader.Loader
|
|
||||||
|
|
||||||
switch len(md.RemainingArgs()) {
|
|
||||||
case 0:
|
|
||||||
// allow launch with no ROM specified on command line
|
|
||||||
|
|
||||||
case 1:
|
|
||||||
// cartridge loader. note that there is no deferred cartload.Close(). the
|
|
||||||
// debugger type itself will handle this.
|
|
||||||
cartload, err = cartridgeloader.NewLoader(md.GetArg(0), *mapping)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("too many arguments for %s mode", md)
|
|
||||||
}
|
|
||||||
|
|
||||||
// check for profiling options
|
// check for profiling options
|
||||||
prf, err := performance.ParseProfileString(*profile)
|
prf, err := performance.ParseProfileString(*profile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -537,23 +400,21 @@ func emulate(emulationMode emulation.Mode, md *modalflag.Modes, sync *mainSync)
|
||||||
|
|
||||||
// set up a launch function
|
// set up a launch function
|
||||||
dbgLaunch := func() error {
|
dbgLaunch := func() error {
|
||||||
// check if comparison was defined and dereference if it was, otherwise
|
|
||||||
// comp is just the empty string
|
switch emulationMode {
|
||||||
var comp string
|
case emulation.ModeDebugger:
|
||||||
if comparisonROM != nil {
|
err := dbg.StartInDebugMode(*initScript, md.GetArg(0), *mapping)
|
||||||
comp = *comparisonROM
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
case emulation.ModePlay:
|
||||||
|
err := dbg.StartInPlayMode(md.GetArg(0), *mapping, *record, *comparisonROM, *comparisonPrefs)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// same for compPrefs
|
|
||||||
var compPrefs string
|
|
||||||
if comparisonPrefs != nil {
|
|
||||||
compPrefs = *comparisonPrefs
|
|
||||||
}
|
|
||||||
|
|
||||||
err := dbg.Start(emulationMode, *initScript, cartload, comp, compPrefs)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
52
hardware/input/doc.go
Normal file
52
hardware/input/doc.go
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
// 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/>.
|
||||||
|
|
||||||
|
// Pacakge input coordinates all the different types of input into the VCS.
|
||||||
|
// The types of input handled by the package include:
|
||||||
|
//
|
||||||
|
// 1) Immediate input from the user (see userinput package)
|
||||||
|
// 2) Playback for a previously recorded script (see recorder package)
|
||||||
|
// 3) Driven events from a driver emulation (see below))
|
||||||
|
// 4) Pushed events
|
||||||
|
//
|
||||||
|
// In addition it also coordinates the passing of input to other packages that
|
||||||
|
// need to know about an input event.
|
||||||
|
//
|
||||||
|
// 1) The RIOT ports (see Ports package)
|
||||||
|
// 2) Events to be recorded to a playback script (see recorder package)
|
||||||
|
// 3) Events to be driven to a passenger emulation (see below)
|
||||||
|
//
|
||||||
|
// The input package will handle impossible situations are return an error when
|
||||||
|
// appropriate. For example, it is not possible for an emulation to be a
|
||||||
|
// playback and a recorder at the same time.
|
||||||
|
//
|
||||||
|
// Points 1 in both lists is the normal type of input you would expect in an
|
||||||
|
// emultor that allows people to play games and as such, won't be discussed
|
||||||
|
// further.
|
||||||
|
//
|
||||||
|
// Points 2 in the lists is well covered by the recorder package.
|
||||||
|
//
|
||||||
|
// Points 3 in both lists above refer to driven events and driver & passenger
|
||||||
|
// emulations. This system is a way os synchronising two emulations such that
|
||||||
|
// the input of one drives the input of the other. The input package ensures
|
||||||
|
// that input events occur in bothe emulations at the same time - time being
|
||||||
|
// measure by the coordinates of the TV instances attached to the emulations.
|
||||||
|
//
|
||||||
|
// For an example of a driven emulation see the comparison package.
|
||||||
|
//
|
||||||
|
// Lastly, pushed events, as refered to in point 4, are events that have
|
||||||
|
// arrived from a different goroutine. For an example of pushed events see the
|
||||||
|
// various bot implementations in the bots package.
|
||||||
|
package input
|
87
hardware/input/driven.go
Normal file
87
hardware/input/driven.go
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
// 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 input
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/jetsetilly/gopher2600/curated"
|
||||||
|
"github.com/jetsetilly/gopher2600/hardware/riot/ports"
|
||||||
|
"github.com/jetsetilly/gopher2600/hardware/television/coords"
|
||||||
|
)
|
||||||
|
|
||||||
|
// handleDrivenEvents checks for input driven from a another emulation.
|
||||||
|
func (inp *Input) handleDrivenEvents() error {
|
||||||
|
if inp.checkForDriven {
|
||||||
|
ev := inp.drivenInputEvent
|
||||||
|
done := false
|
||||||
|
for !done {
|
||||||
|
c := inp.tv.GetCoords()
|
||||||
|
if coords.Equal(c, ev.Time) {
|
||||||
|
_, err := inp.ports.HandleInputEvent(ev.InputEvent)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else if coords.GreaterThan(c, ev.Time) {
|
||||||
|
return curated.Errorf("input: driven input seen too late. emulations not synced correctly.")
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case inp.drivenInputEvent = <-inp.fromDriver:
|
||||||
|
if inp.checkForDriven {
|
||||||
|
curated.Errorf("input: driven input received before previous input was processed")
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
done = true
|
||||||
|
inp.checkForDriven = false
|
||||||
|
}
|
||||||
|
|
||||||
|
ev = inp.drivenInputEvent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if inp.fromDriver != nil {
|
||||||
|
select {
|
||||||
|
case inp.drivenInputEvent = <-inp.fromDriver:
|
||||||
|
if inp.checkForDriven {
|
||||||
|
curated.Errorf("input: driven input received before previous input was processed")
|
||||||
|
}
|
||||||
|
inp.checkForDriven = true
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AttachPassenger should be called by an emulation that wants to be driven by another emulation.
|
||||||
|
func (inp *Input) AttachPassenger(driver chan ports.TimedInputEvent) error {
|
||||||
|
if inp.toPassenger != nil {
|
||||||
|
return curated.Errorf("input: attach passenger: emulation already defined as an input driver")
|
||||||
|
}
|
||||||
|
inp.fromDriver = driver
|
||||||
|
inp.setProcessFunc()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AttachDriver should be called by an emulation that is prepared to drive another emulation.
|
||||||
|
func (inp *Input) AttachDriver(passenger chan ports.TimedInputEvent) error {
|
||||||
|
if inp.fromDriver != nil {
|
||||||
|
return curated.Errorf("input: attach driver: emulation already defined as being an input passenger")
|
||||||
|
}
|
||||||
|
inp.toPassenger = passenger
|
||||||
|
return nil
|
||||||
|
}
|
142
hardware/input/input.go
Normal file
142
hardware/input/input.go
Normal file
|
@ -0,0 +1,142 @@
|
||||||
|
// 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 input
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/jetsetilly/gopher2600/curated"
|
||||||
|
"github.com/jetsetilly/gopher2600/hardware/riot/ports"
|
||||||
|
"github.com/jetsetilly/gopher2600/hardware/riot/ports/plugging"
|
||||||
|
"github.com/jetsetilly/gopher2600/hardware/television/coords"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TV defines the television functions required by the Input system.
|
||||||
|
type TV interface {
|
||||||
|
GetCoords() coords.TelevisionCoords
|
||||||
|
}
|
||||||
|
|
||||||
|
// Input handles all forms of input into the VCS.
|
||||||
|
type Input struct {
|
||||||
|
tv TV
|
||||||
|
ports *ports.Ports
|
||||||
|
|
||||||
|
playback EventPlayback
|
||||||
|
recorder EventRecorder
|
||||||
|
|
||||||
|
// events pushed onto the input queue
|
||||||
|
pushed chan ports.InputEvent
|
||||||
|
|
||||||
|
// the following fields all relate to driven input, for either the driver
|
||||||
|
// or for the passenger (the driven)
|
||||||
|
fromDriver chan ports.TimedInputEvent
|
||||||
|
toPassenger chan ports.TimedInputEvent
|
||||||
|
checkForDriven bool
|
||||||
|
drivenInputEvent ports.TimedInputEvent
|
||||||
|
|
||||||
|
// Process function should be called every VCS step
|
||||||
|
Process func() error
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewInput(tv TV, p *ports.Ports) *Input {
|
||||||
|
inp := &Input{
|
||||||
|
tv: tv,
|
||||||
|
ports: p,
|
||||||
|
pushed: make(chan ports.InputEvent, 64),
|
||||||
|
}
|
||||||
|
inp.setProcessFunc()
|
||||||
|
return inp
|
||||||
|
}
|
||||||
|
|
||||||
|
// Plumb a new ports instances into the Input.
|
||||||
|
func (inp *Input) Plumb(ports *ports.Ports) {
|
||||||
|
inp.ports = ports
|
||||||
|
}
|
||||||
|
|
||||||
|
// PeripheralID forwards a request of the PeripheralID of the PortID to VCS Ports.
|
||||||
|
func (inp *Input) PeripheralID(id plugging.PortID) plugging.PeripheralID {
|
||||||
|
return inp.ports.PeripheralID(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandleInputEvent forwards an input event to VCS Ports.
|
||||||
|
func (inp *Input) HandleInputEvent(ev ports.InputEvent) (bool, error) {
|
||||||
|
if inp.recorder != nil {
|
||||||
|
err := inp.recorder.RecordEvent(ports.TimedInputEvent{Time: inp.tv.GetCoords(), InputEvent: ev})
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handled, err := inp.ports.HandleInputEvent(ev)
|
||||||
|
if err != nil {
|
||||||
|
return handled, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// forward to passenger if one is defined
|
||||||
|
if handled && inp.toPassenger != nil {
|
||||||
|
select {
|
||||||
|
case inp.toPassenger <- ports.TimedInputEvent{Time: inp.tv.GetCoords(), InputEvent: ev}:
|
||||||
|
default:
|
||||||
|
return handled, curated.Errorf("input: passenger event queue is full: input dropped")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return handled, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (inp *Input) setProcessFunc() {
|
||||||
|
if inp.fromDriver != nil && inp.playback != nil {
|
||||||
|
inp.Process = func() error {
|
||||||
|
if err := inp.handlePushedEvents(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := inp.handlePlaybackEvents(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := inp.handleDrivenEvents(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if inp.fromDriver != nil {
|
||||||
|
inp.Process = func() error {
|
||||||
|
if err := inp.handlePushedEvents(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := inp.handleDrivenEvents(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if inp.playback != nil {
|
||||||
|
inp.Process = func() error {
|
||||||
|
if err := inp.handlePushedEvents(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := inp.handlePlaybackEvents(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
inp.Process = inp.handlePushedEvents
|
||||||
|
}
|
48
hardware/input/pushed.go
Normal file
48
hardware/input/pushed.go
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
// 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 input
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/jetsetilly/gopher2600/curated"
|
||||||
|
"github.com/jetsetilly/gopher2600/hardware/riot/ports"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PushEvent pushes an InputEvent onto the queue. Will drop the event and
|
||||||
|
// return an error if queue is full.
|
||||||
|
func (inp *Input) PushEvent(ev ports.InputEvent) error {
|
||||||
|
select {
|
||||||
|
case inp.pushed <- ev:
|
||||||
|
default:
|
||||||
|
return curated.Errorf("ports: pushed event queue is full: input dropped")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (inp *Input) handlePushedEvents() error {
|
||||||
|
done := false
|
||||||
|
for !done {
|
||||||
|
select {
|
||||||
|
case ev := <-inp.pushed:
|
||||||
|
_, err := inp.ports.HandleInputEvent(ev)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
done = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
104
hardware/input/recording.go
Normal file
104
hardware/input/recording.go
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
// 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 input
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/jetsetilly/gopher2600/curated"
|
||||||
|
"github.com/jetsetilly/gopher2600/hardware/riot/ports"
|
||||||
|
"github.com/jetsetilly/gopher2600/hardware/riot/ports/plugging"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Playback implementations feed controller Events to the device on request
|
||||||
|
// with the CheckInput() function.
|
||||||
|
//
|
||||||
|
// Intended for playback of controller events previously recorded to a file on
|
||||||
|
// disk but usable for many purposes I suspect. For example, AI control.
|
||||||
|
type EventPlayback interface {
|
||||||
|
// note the type restrictions on EventData in the type definition's
|
||||||
|
// commentary
|
||||||
|
GetPlayback() (ports.TimedInputEvent, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// EventRecorder implementations mirror an incoming event.
|
||||||
|
//
|
||||||
|
// Implementations should be able to handle being attached to more than one
|
||||||
|
// peripheral at once. The ID parameter of the EventRecord() function will help
|
||||||
|
// to differentiate between multiple devices.
|
||||||
|
type EventRecorder interface {
|
||||||
|
RecordEvent(ports.TimedInputEvent) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// AttachEventRecorder attaches an EventRecorder implementation.
|
||||||
|
func (inp *Input) AttachRecorder(r EventRecorder) error {
|
||||||
|
if inp.playback != nil {
|
||||||
|
return curated.Errorf("input: attach recorder: emulator already has a playback attached")
|
||||||
|
}
|
||||||
|
inp.recorder = r
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AttachPlayback attaches an EventPlayback implementation to the Input
|
||||||
|
// sub-system. EventPlayback can be nil in order to remove the playback.
|
||||||
|
func (inp *Input) AttachPlayback(pb EventPlayback) error {
|
||||||
|
if inp.recorder != nil {
|
||||||
|
return curated.Errorf("input: attach playback: emulator already has a recorder attached")
|
||||||
|
}
|
||||||
|
inp.playback = pb
|
||||||
|
inp.setProcessFunc()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// handlePlaybackEvents requests playback events from all attached and eligible peripherals.
|
||||||
|
func (inp *Input) handlePlaybackEvents() error {
|
||||||
|
if inp.playback == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// loop with GetPlayback() until we encounter a NoPortID or NoEvent
|
||||||
|
// condition. there might be more than one entry for a particular
|
||||||
|
// frame/scanline/horizpas state so we need to make sure we've processed
|
||||||
|
// them all.
|
||||||
|
//
|
||||||
|
// this happens in particular with recordings that were made of ROMs with
|
||||||
|
// panel setup configurations (see setup package) - where the switches are
|
||||||
|
// set when the TV state is at fr=0 sl=0 cl=0
|
||||||
|
morePlayback := true
|
||||||
|
for morePlayback {
|
||||||
|
ev, err := inp.playback.GetPlayback()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
morePlayback = ev.Port != plugging.PortUnplugged && ev.Ev != ports.NoEvent
|
||||||
|
if morePlayback {
|
||||||
|
_, err := inp.ports.HandleInputEvent(ev.InputEvent)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// forward to passenger if necessary
|
||||||
|
if inp.toPassenger != nil {
|
||||||
|
select {
|
||||||
|
case inp.toPassenger <- ev:
|
||||||
|
default:
|
||||||
|
return curated.Errorf("input: passenger event queue is full: input dropped")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -80,7 +80,7 @@ func (mem *Memory) Snapshot() *Memory {
|
||||||
return &n
|
return &n
|
||||||
}
|
}
|
||||||
|
|
||||||
// Plumb makes sure everythin is ship-shape after a rewind event.
|
// Plumb makes sure everything is ship-shape after a rewind event.
|
||||||
func (mem *Memory) Plumb(fromDifferentEmulation bool) {
|
func (mem *Memory) Plumb(fromDifferentEmulation bool) {
|
||||||
mem.Cart.Plumb(fromDifferentEmulation)
|
mem.Cart.Plumb(fromDifferentEmulation)
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,17 +16,12 @@
|
||||||
// Package ports represents the input/output parts of the VCS (the IO in
|
// Package ports represents the input/output parts of the VCS (the IO in
|
||||||
// RIOT).
|
// RIOT).
|
||||||
//
|
//
|
||||||
// Emulated peripherals are plugged into the VCS with the Plug() function.
|
|
||||||
// Input from "real" devices is handled by HandleEvent() which passes the event
|
// Input from "real" devices is handled by HandleEvent() which passes the event
|
||||||
// to peripherals in the specified PortID.
|
// to peripherals in the specified PortID.
|
||||||
//
|
//
|
||||||
// Emulations can share user input through the DrivenEvent mechanism. The driver
|
// HandleEvent() should probably no be called directly but instead only through
|
||||||
// emulation should call SynchroniseWithPassenger() and the passenger emulation
|
// the input package
|
||||||
// should call SynchroniseWithDriver().
|
|
||||||
//
|
//
|
||||||
// With the DrivenEvent mechanism, the driver sends events to the passenger.
|
// Emulated peripherals are plugged into the VCS with the Plug() function.
|
||||||
// Both emulations will receive the same user input at the same time, relative
|
// Plugging events can be monitored with the plugging.PlugMonitor interface.
|
||||||
// to television coordinates, so it is important that the driver is running
|
|
||||||
// ahead of the passenger at all time. See comparison package for model
|
|
||||||
// implementation.
|
|
||||||
package ports
|
package ports
|
||||||
|
|
|
@ -117,23 +117,3 @@ type TimedInputEvent struct {
|
||||||
Time coords.TelevisionCoords
|
Time coords.TelevisionCoords
|
||||||
InputEvent
|
InputEvent
|
||||||
}
|
}
|
||||||
|
|
||||||
// Playback implementations feed controller Events to the device on request
|
|
||||||
// with the CheckInput() function.
|
|
||||||
//
|
|
||||||
// Intended for playback of controller events previously recorded to a file on
|
|
||||||
// disk but usable for many purposes I suspect. For example, AI control.
|
|
||||||
type EventPlayback interface {
|
|
||||||
// note the type restrictions on EventData in the type definition's
|
|
||||||
// commentary
|
|
||||||
GetPlayback() (TimedInputEvent, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// EventRecorder implementations mirror an incoming event.
|
|
||||||
//
|
|
||||||
// Implementations should be able to handle being attached to more than one
|
|
||||||
// peripheral at once. The ID parameter of the EventRecord() function will help
|
|
||||||
// to differentiate between multiple devices.
|
|
||||||
type EventRecorder interface {
|
|
||||||
RecordEvent(TimedInputEvent) error
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,187 +0,0 @@
|
||||||
// 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 ports
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/jetsetilly/gopher2600/curated"
|
|
||||||
"github.com/jetsetilly/gopher2600/hardware/riot/ports/plugging"
|
|
||||||
"github.com/jetsetilly/gopher2600/hardware/television/coords"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (p *Ports) HandleInputEvents() error {
|
|
||||||
err := p.handlePushedEvents()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = p.handleDrivenEvents()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = p.handlePlaybackEvents()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Ports) handlePushedEvents() error {
|
|
||||||
done := false
|
|
||||||
for !done {
|
|
||||||
select {
|
|
||||||
case inp := <-p.pushed:
|
|
||||||
_, err := p.HandleInputEvent(inp)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
done = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// handleDrivenEvents checks for input driven from a another emulation.
|
|
||||||
func (p *Ports) handleDrivenEvents() error {
|
|
||||||
if p.checkForDriven {
|
|
||||||
inp := p.drivenInputData
|
|
||||||
done := false
|
|
||||||
for !done {
|
|
||||||
c := p.tv.GetCoords()
|
|
||||||
if coords.Equal(c, inp.Time) {
|
|
||||||
_, err := p.HandleInputEvent(inp.InputEvent)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else if coords.GreaterThan(c, inp.Time) {
|
|
||||||
return curated.Errorf("ports: driven input seen too late. emulations not synced correctly.")
|
|
||||||
} else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
select {
|
|
||||||
case p.drivenInputData = <-p.fromDriver:
|
|
||||||
if p.checkForDriven {
|
|
||||||
curated.Errorf("ports: driven input received before previous input was processed")
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
done = true
|
|
||||||
p.checkForDriven = false
|
|
||||||
}
|
|
||||||
|
|
||||||
inp = p.drivenInputData
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if p.fromDriver != nil {
|
|
||||||
select {
|
|
||||||
case p.drivenInputData = <-p.fromDriver:
|
|
||||||
if p.checkForDriven {
|
|
||||||
curated.Errorf("ports: driven input received before previous input was processed")
|
|
||||||
}
|
|
||||||
p.checkForDriven = true
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// handlePlaybackEvents requests playback events from all attached and eligible peripherals.
|
|
||||||
func (p *Ports) handlePlaybackEvents() error {
|
|
||||||
if p.playback == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// loop with GetPlayback() until we encounter a NoPortID or NoEvent
|
|
||||||
// condition. there might be more than one entry for a particular
|
|
||||||
// frame/scanline/horizpas state so we need to make sure we've processed
|
|
||||||
// them all.
|
|
||||||
//
|
|
||||||
// this happens in particular with recordings that were made of ROMs with
|
|
||||||
// panel setup configurations (see setup package) - where the switches are
|
|
||||||
// set when the TV state is at fr=0 sl=0 cl=0
|
|
||||||
morePlayback := true
|
|
||||||
for morePlayback {
|
|
||||||
inp, err := p.playback.GetPlayback()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
morePlayback = inp.Port != plugging.PortUnplugged && inp.Ev != NoEvent
|
|
||||||
if morePlayback {
|
|
||||||
_, err := p.HandleInputEvent(inp.InputEvent)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// HandleInputEvent should only be used from the same gorouine as the
|
|
||||||
// emulation. Events should be queued with QueueEvent() otherwise.
|
|
||||||
//
|
|
||||||
// Consider using HandleInputEvent() function in the VCS type rather than this
|
|
||||||
// function directly.
|
|
||||||
func (p *Ports) HandleInputEvent(inp InputEvent) (bool, error) {
|
|
||||||
var handled bool
|
|
||||||
var err error
|
|
||||||
|
|
||||||
switch inp.Port {
|
|
||||||
case plugging.PortPanel:
|
|
||||||
handled, err = p.Panel.HandleEvent(inp.Ev, inp.D)
|
|
||||||
case plugging.PortLeftPlayer:
|
|
||||||
handled, err = p.LeftPlayer.HandleEvent(inp.Ev, inp.D)
|
|
||||||
case plugging.PortRightPlayer:
|
|
||||||
handled, err = p.RightPlayer.HandleEvent(inp.Ev, inp.D)
|
|
||||||
}
|
|
||||||
|
|
||||||
// forward to passenger if one is defined
|
|
||||||
if handled && p.toPassenger != nil {
|
|
||||||
select {
|
|
||||||
case p.toPassenger <- TimedInputEvent{Time: p.tv.GetCoords(), InputEvent: inp}:
|
|
||||||
default:
|
|
||||||
return handled, curated.Errorf("ports: passenger event queue is full: input dropped")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// if error was because of an unhandled event then return without error
|
|
||||||
if err != nil {
|
|
||||||
return handled, curated.Errorf("ports: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// record event with the EventRecorder
|
|
||||||
for _, r := range p.recorder {
|
|
||||||
return handled, r.RecordEvent(TimedInputEvent{Time: p.tv.GetCoords(), InputEvent: inp})
|
|
||||||
}
|
|
||||||
|
|
||||||
return handled, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// QueueEvent pushes an InputEvent onto the queue. Will drop the event and
|
|
||||||
// return an error if queue is full.
|
|
||||||
func (p *Ports) QueueEvent(inp InputEvent) error {
|
|
||||||
select {
|
|
||||||
case p.pushed <- inp:
|
|
||||||
default:
|
|
||||||
return curated.Errorf("ports: pushed event queue is full: input dropped")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -23,14 +23,8 @@ import (
|
||||||
"github.com/jetsetilly/gopher2600/hardware/memory/addresses"
|
"github.com/jetsetilly/gopher2600/hardware/memory/addresses"
|
||||||
"github.com/jetsetilly/gopher2600/hardware/memory/bus"
|
"github.com/jetsetilly/gopher2600/hardware/memory/bus"
|
||||||
"github.com/jetsetilly/gopher2600/hardware/riot/ports/plugging"
|
"github.com/jetsetilly/gopher2600/hardware/riot/ports/plugging"
|
||||||
"github.com/jetsetilly/gopher2600/hardware/television/coords"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// TV defines the television functions required by the Ports type.
|
|
||||||
type TV interface {
|
|
||||||
GetCoords() coords.TelevisionCoords
|
|
||||||
}
|
|
||||||
|
|
||||||
// Input implements the input/output part of the RIOT (the IO in RIOT).
|
// Input implements the input/output part of the RIOT (the IO in RIOT).
|
||||||
type Ports struct {
|
type Ports struct {
|
||||||
riot bus.ChipBus
|
riot bus.ChipBus
|
||||||
|
@ -40,9 +34,6 @@ type Ports struct {
|
||||||
LeftPlayer Peripheral
|
LeftPlayer Peripheral
|
||||||
RightPlayer Peripheral
|
RightPlayer Peripheral
|
||||||
|
|
||||||
playback EventPlayback
|
|
||||||
recorder []EventRecorder
|
|
||||||
|
|
||||||
monitor plugging.PlugMonitor
|
monitor plugging.PlugMonitor
|
||||||
|
|
||||||
// local copies of key chip memory registers
|
// local copies of key chip memory registers
|
||||||
|
@ -84,21 +75,6 @@ type Ports struct {
|
||||||
//
|
//
|
||||||
// we use it to mux the Player0 and Player 1 nibbles into the single register
|
// we use it to mux the Player0 and Player 1 nibbles into the single register
|
||||||
swchaMux uint8
|
swchaMux uint8
|
||||||
|
|
||||||
// events pushed onto the input queue
|
|
||||||
pushed chan InputEvent
|
|
||||||
|
|
||||||
// the following fields all relate to driven input, for either the driver
|
|
||||||
// or for the passenger (the driven)
|
|
||||||
fromDriver chan TimedInputEvent
|
|
||||||
toPassenger chan TimedInputEvent
|
|
||||||
checkForDriven bool
|
|
||||||
drivenInputData TimedInputEvent
|
|
||||||
|
|
||||||
// the time of driven events are measured by television coordinates
|
|
||||||
//
|
|
||||||
// not used except to synchronise driver and passenger emulations
|
|
||||||
tv TV
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewPorts is the preferred method of initialisation of the Ports type.
|
// NewPorts is the preferred method of initialisation of the Ports type.
|
||||||
|
@ -106,11 +82,9 @@ func NewPorts(riotMem bus.ChipBus, tiaMem bus.ChipBus) *Ports {
|
||||||
p := &Ports{
|
p := &Ports{
|
||||||
riot: riotMem,
|
riot: riotMem,
|
||||||
tia: tiaMem,
|
tia: tiaMem,
|
||||||
recorder: make([]EventRecorder, 0),
|
|
||||||
swchaFromCPU: 0x00,
|
swchaFromCPU: 0x00,
|
||||||
swacnt: 0x00,
|
swacnt: 0x00,
|
||||||
latch: false,
|
latch: false,
|
||||||
pushed: make(chan InputEvent, 64),
|
|
||||||
}
|
}
|
||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
|
@ -267,45 +241,6 @@ func (p *Ports) Step() {
|
||||||
p.Panel.Step()
|
p.Panel.Step()
|
||||||
}
|
}
|
||||||
|
|
||||||
// SynchroniseWithDriver implies that the emulation will receive driven events
|
|
||||||
// from another emulation.
|
|
||||||
func (p *Ports) SynchroniseWithDriver(driver chan TimedInputEvent, tv TV) error {
|
|
||||||
if p.toPassenger != nil {
|
|
||||||
return curated.Errorf("ports: cannot sync with driver: emulation already defined as a driver of input")
|
|
||||||
}
|
|
||||||
if p.playback != nil {
|
|
||||||
return curated.Errorf("ports: cannot sync with driver: emulation is already receiving input from a playback")
|
|
||||||
}
|
|
||||||
p.tv = tv
|
|
||||||
p.fromDriver = driver
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SynchroniseWithPassenger connects the emulation to a second emulation (the
|
|
||||||
// passenger) to which user input events will be "driven".
|
|
||||||
func (p *Ports) SynchroniseWithPassenger(passenger chan TimedInputEvent, tv TV) error {
|
|
||||||
if p.fromDriver != nil {
|
|
||||||
return curated.Errorf("ports: cannot sync with passenger: emulation already defined as being driven")
|
|
||||||
}
|
|
||||||
p.tv = tv
|
|
||||||
p.toPassenger = passenger
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// AttachPlayback attaches an EventPlayback implementation.
|
|
||||||
func (p *Ports) AttachPlayback(b EventPlayback) error {
|
|
||||||
if p.fromDriver != nil {
|
|
||||||
return curated.Errorf("ports: cannot attach playback: emulation already defined as being driven")
|
|
||||||
}
|
|
||||||
p.playback = b
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// AttachEventRecorder attaches an EventRecorder implementation.
|
|
||||||
func (p *Ports) AttachEventRecorder(r EventRecorder) {
|
|
||||||
p.recorder = append(p.recorder, r)
|
|
||||||
}
|
|
||||||
|
|
||||||
// AttchPlugMonitor implements the plugging.Monitorable interface.
|
// AttchPlugMonitor implements the plugging.Monitorable interface.
|
||||||
func (p *Ports) AttachPlugMonitor(m plugging.PlugMonitor) {
|
func (p *Ports) AttachPlugMonitor(m plugging.PlugMonitor) {
|
||||||
p.monitor = m
|
p.monitor = m
|
||||||
|
@ -328,10 +263,7 @@ func (p *Ports) AttachPlugMonitor(m plugging.PlugMonitor) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// PeripheralID implements userinput.HandleInput interface.
|
// PeripheralID returns the ID of the peripheral in the identified port.
|
||||||
//
|
|
||||||
// Consider using PeripheralID() function in the VCS type rather than this
|
|
||||||
// function directly.
|
|
||||||
func (p *Ports) PeripheralID(id plugging.PortID) plugging.PeripheralID {
|
func (p *Ports) PeripheralID(id plugging.PortID) plugging.PeripheralID {
|
||||||
switch id {
|
switch id {
|
||||||
case plugging.PortPanel:
|
case plugging.PortPanel:
|
||||||
|
@ -376,3 +308,26 @@ func (p *Ports) WriteINPTx(inptx addresses.ChipRegister, data uint8) {
|
||||||
p.tia.ChipWrite(inptx, data)
|
p.tia.ChipWrite(inptx, data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HandleInputEvent forwards the InputEvent to the perupheral in the correct
|
||||||
|
// port. Returns true if the event was handled and false if not.
|
||||||
|
func (p *Ports) HandleInputEvent(inp InputEvent) (bool, error) {
|
||||||
|
var handled bool
|
||||||
|
var err error
|
||||||
|
|
||||||
|
switch inp.Port {
|
||||||
|
case plugging.PortPanel:
|
||||||
|
handled, err = p.Panel.HandleEvent(inp.Ev, inp.D)
|
||||||
|
case plugging.PortLeftPlayer:
|
||||||
|
handled, err = p.LeftPlayer.HandleEvent(inp.Ev, inp.D)
|
||||||
|
case plugging.PortRightPlayer:
|
||||||
|
handled, err = p.RightPlayer.HandleEvent(inp.Ev, inp.D)
|
||||||
|
}
|
||||||
|
|
||||||
|
// if error was because of an unhandled event then return without error
|
||||||
|
if err != nil {
|
||||||
|
return handled, curated.Errorf("ports: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return handled, nil
|
||||||
|
}
|
||||||
|
|
|
@ -50,7 +50,7 @@ func (vcs *VCS) Run(continueCheck func() (emulation.State, error)) error {
|
||||||
// see the equivalient videoCycle() in the VCS.Step() function for an
|
// see the equivalient videoCycle() in the VCS.Step() function for an
|
||||||
// explanation for what's going on here:
|
// explanation for what's going on here:
|
||||||
videoCycle := func() error {
|
videoCycle := func() error {
|
||||||
if err := vcs.RIOT.Ports.HandleInputEvents(); err != nil {
|
if err := vcs.Input.Process(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -57,7 +57,7 @@ func (vcs *VCS) Step(videoCycleCallback func() error) error {
|
||||||
// I don't believe any visual or audible artefacts of the VCS (undocumented
|
// I don't believe any visual or audible artefacts of the VCS (undocumented
|
||||||
// or not) rely on the details of the CPU-TIA relationship.
|
// or not) rely on the details of the CPU-TIA relationship.
|
||||||
videoCycle := func() error {
|
videoCycle := func() error {
|
||||||
if err := vcs.RIOT.Ports.HandleInputEvents(); err != nil {
|
if err := vcs.Input.Process(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,12 +19,12 @@ import (
|
||||||
"github.com/jetsetilly/gopher2600/cartridgeloader"
|
"github.com/jetsetilly/gopher2600/cartridgeloader"
|
||||||
"github.com/jetsetilly/gopher2600/curated"
|
"github.com/jetsetilly/gopher2600/curated"
|
||||||
"github.com/jetsetilly/gopher2600/hardware/cpu"
|
"github.com/jetsetilly/gopher2600/hardware/cpu"
|
||||||
|
"github.com/jetsetilly/gopher2600/hardware/input"
|
||||||
"github.com/jetsetilly/gopher2600/hardware/instance"
|
"github.com/jetsetilly/gopher2600/hardware/instance"
|
||||||
"github.com/jetsetilly/gopher2600/hardware/memory"
|
"github.com/jetsetilly/gopher2600/hardware/memory"
|
||||||
"github.com/jetsetilly/gopher2600/hardware/memory/addresses"
|
"github.com/jetsetilly/gopher2600/hardware/memory/addresses"
|
||||||
"github.com/jetsetilly/gopher2600/hardware/memory/cartridge"
|
"github.com/jetsetilly/gopher2600/hardware/memory/cartridge"
|
||||||
"github.com/jetsetilly/gopher2600/hardware/riot"
|
"github.com/jetsetilly/gopher2600/hardware/riot"
|
||||||
"github.com/jetsetilly/gopher2600/hardware/riot/ports"
|
|
||||||
"github.com/jetsetilly/gopher2600/hardware/riot/ports/controllers"
|
"github.com/jetsetilly/gopher2600/hardware/riot/ports/controllers"
|
||||||
"github.com/jetsetilly/gopher2600/hardware/riot/ports/panel"
|
"github.com/jetsetilly/gopher2600/hardware/riot/ports/panel"
|
||||||
"github.com/jetsetilly/gopher2600/hardware/riot/ports/plugging"
|
"github.com/jetsetilly/gopher2600/hardware/riot/ports/plugging"
|
||||||
|
@ -50,6 +50,8 @@ type VCS struct {
|
||||||
RIOT *riot.RIOT
|
RIOT *riot.RIOT
|
||||||
TIA *tia.TIA
|
TIA *tia.TIA
|
||||||
|
|
||||||
|
Input *input.Input
|
||||||
|
|
||||||
// The Clock defines the basic speed at which the the machine is runningt. This governs
|
// The Clock defines the basic speed at which the the machine is runningt. This governs
|
||||||
// the speed of the CPU, the RIOT and attached peripherals. The TIA runs at
|
// the speed of the CPU, the RIOT and attached peripherals. The TIA runs at
|
||||||
// exactly three times this speed.
|
// exactly three times this speed.
|
||||||
|
@ -90,6 +92,8 @@ func NewVCS(tv *television.Television) (*VCS, error) {
|
||||||
vcs.CPU = cpu.NewCPU(vcs.Instance, vcs.Mem)
|
vcs.CPU = cpu.NewCPU(vcs.Instance, vcs.Mem)
|
||||||
vcs.RIOT = riot.NewRIOT(vcs.Instance, vcs.Mem.RIOT, vcs.Mem.TIA)
|
vcs.RIOT = riot.NewRIOT(vcs.Instance, vcs.Mem.RIOT, vcs.Mem.TIA)
|
||||||
|
|
||||||
|
vcs.Input = input.NewInput(vcs.TV, vcs.RIOT.Ports)
|
||||||
|
|
||||||
vcs.TIA, err = tia.NewTIA(vcs.Instance, vcs.TV, vcs.Mem.TIA, vcs.RIOT.Ports, vcs.CPU)
|
vcs.TIA, err = tia.NewTIA(vcs.Instance, vcs.TV, vcs.Mem.TIA, vcs.RIOT.Ports, vcs.CPU)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -120,6 +124,21 @@ func (vcs *VCS) End() {
|
||||||
vcs.TV.End()
|
vcs.TV.End()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Plumb the various VCS sub-systems together after a rewind.
|
||||||
|
func (vcs *VCS) Plumb(fromDifferentEmulation bool) {
|
||||||
|
vcs.CPU.Plumb(vcs.Mem)
|
||||||
|
vcs.Mem.Plumb(fromDifferentEmulation)
|
||||||
|
vcs.RIOT.Plumb(vcs.Mem.RIOT, vcs.Mem.TIA)
|
||||||
|
vcs.TIA.Plumb(vcs.TV, vcs.Mem.TIA, vcs.RIOT.Ports, vcs.CPU)
|
||||||
|
|
||||||
|
// reset peripherals after new state has been plumbed. without this,
|
||||||
|
// controllers can feel odd if the newly plumbed state has left RIOT memory
|
||||||
|
// in a latched state
|
||||||
|
vcs.RIOT.Ports.ResetPeripherals()
|
||||||
|
|
||||||
|
vcs.Input.Plumb(vcs.RIOT.Ports)
|
||||||
|
}
|
||||||
|
|
||||||
// AttachCartridge to this VCS. While this function can be called directly it
|
// AttachCartridge to this VCS. While this function can be called directly it
|
||||||
// is advised that the setup package be used in most circumstances.
|
// is advised that the setup package be used in most circumstances.
|
||||||
func (vcs *VCS) AttachCartridge(cartload cartridgeloader.Loader) error {
|
func (vcs *VCS) AttachCartridge(cartload cartridgeloader.Loader) error {
|
||||||
|
@ -226,29 +245,7 @@ func (vcs *VCS) SetClockSpeed(tvSpec string) error {
|
||||||
// recorders and playback, and RIOT plug monitor.
|
// recorders and playback, and RIOT plug monitor.
|
||||||
func (vcs *VCS) DetatchEmulationExtras() {
|
func (vcs *VCS) DetatchEmulationExtras() {
|
||||||
vcs.TIA.Audio.SetTracker(nil)
|
vcs.TIA.Audio.SetTracker(nil)
|
||||||
vcs.RIOT.Ports.AttachEventRecorder(nil)
|
vcs.Input.AttachRecorder(nil)
|
||||||
vcs.RIOT.Ports.AttachPlayback(nil)
|
vcs.Input.AttachPlayback(nil)
|
||||||
vcs.RIOT.Ports.AttachPlugMonitor(nil)
|
vcs.RIOT.Ports.AttachPlugMonitor(nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandleInputEvent forwards an input event to VCS Ports.
|
|
||||||
//
|
|
||||||
// It's important that this function be used in preference to calling the
|
|
||||||
// HandleInputEvent() function in the RIOT.Ports directly. For rewind reasons
|
|
||||||
// it is likely that any direct reference to RIOT.Ports will grow stale.
|
|
||||||
func (vcs *VCS) HandleInputEvent(inp ports.InputEvent) (bool, error) {
|
|
||||||
return vcs.RIOT.Ports.HandleInputEvent(inp)
|
|
||||||
}
|
|
||||||
|
|
||||||
// PeripheralID forwards a request of the PeripheralID of the PortID to VCS
|
|
||||||
// Ports.
|
|
||||||
//
|
|
||||||
// See important comment in HandleInputEvent() above.
|
|
||||||
func (vcs *VCS) PeripheralID(id plugging.PortID) plugging.PeripheralID {
|
|
||||||
return vcs.RIOT.Ports.PeripheralID(id)
|
|
||||||
}
|
|
||||||
|
|
||||||
// QueueEvent forwards an input event to VCS Ports.
|
|
||||||
func (vcs *VCS) QueueEvent(inp ports.InputEvent) error {
|
|
||||||
return vcs.RIOT.Ports.QueueEvent(inp)
|
|
||||||
}
|
|
||||||
|
|
|
@ -13,12 +13,10 @@
|
||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Gopher2600. If not, see <https://www.gnu.org/licenses/>.
|
// along with Gopher2600. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
// Package recorder handles recording and playback of user input. The Recorder
|
// Package recorder handles recording and playback of input through the VCS
|
||||||
// type implements the riot.input.EventRecorder() interface. Once added as a
|
// input system. See input package.
|
||||||
// transcriber to the VCS port, it will record all user input to the specified
|
|
||||||
// file.
|
|
||||||
//
|
//
|
||||||
// To keep things simple, recording gameplay will use the VCS in it's default
|
// To keep things simple, recording gameplay will use the VCS in it's
|
||||||
// state. Future versions of the recorder fileformat will support localised
|
// normalised state. Future versions of the recorder fileformat will support
|
||||||
// preferences.
|
// localised preferences.
|
||||||
package recorder
|
package recorder
|
||||||
|
|
|
@ -74,6 +74,9 @@ func (plb Playback) EndFrame() (bool, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewPlayback is the preferred method of implementation for the Playback type.
|
// NewPlayback is the preferred method of implementation for the Playback type.
|
||||||
|
//
|
||||||
|
// The returned playback must be attached to the VCS input system (with
|
||||||
|
// AttachToVCSInput() function) for it it to be useful.
|
||||||
func NewPlayback(transcript string) (*Playback, error) {
|
func NewPlayback(transcript string) (*Playback, error) {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
|
@ -172,11 +175,11 @@ func NewPlayback(transcript string) (*Playback, error) {
|
||||||
return plb, nil
|
return plb, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// AttachToVCS attaches the playback instance (an implementation of the
|
// AttachToVCSInput attaches the playback instance (an implementation of the
|
||||||
// playback interface) to all the ports of the VCS, including the panel.
|
// playback interface) to all the input system of the VCS.
|
||||||
//
|
//
|
||||||
// Note that this will reset the VCS.
|
// Note that the VCS instance will be normalised as a result of this call.
|
||||||
func (plb *Playback) AttachToVCS(vcs *hardware.VCS) error {
|
func (plb *Playback) AttachToVCSInput(vcs *hardware.VCS) error {
|
||||||
// check we're working with correct information
|
// check we're working with correct information
|
||||||
if vcs == nil || vcs.TV == nil {
|
if vcs == nil || vcs.TV == nil {
|
||||||
return curated.Errorf("playback: no playback hardware available")
|
return curated.Errorf("playback: no playback hardware available")
|
||||||
|
@ -201,11 +204,8 @@ func (plb *Playback) AttachToVCS(vcs *hardware.VCS) error {
|
||||||
return curated.Errorf("playback: %v", err)
|
return curated.Errorf("playback: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// attach playback to all vcs ports
|
// attach playback to all VCS Input system
|
||||||
err = vcs.RIOT.Ports.AttachPlayback(plb)
|
vcs.Input.AttachPlayback(plb)
|
||||||
if err != nil {
|
|
||||||
return curated.Errorf("playback: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,7 +44,7 @@ type Recorder struct {
|
||||||
// type. Note that attaching of the Recorder to all the ports of the VCS
|
// type. Note that attaching of the Recorder to all the ports of the VCS
|
||||||
// (including the panel) is implicit in this function call.
|
// (including the panel) is implicit in this function call.
|
||||||
//
|
//
|
||||||
// Note that this will reset the VCS.
|
// Note that the VCS instance will be normalised as a result of this call.
|
||||||
func NewRecorder(transcript string, vcs *hardware.VCS) (*Recorder, error) {
|
func NewRecorder(transcript string, vcs *hardware.VCS) (*Recorder, error) {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
|
@ -67,8 +67,8 @@ func NewRecorder(transcript string, vcs *hardware.VCS) (*Recorder, error) {
|
||||||
return nil, curated.Errorf("recorder: %v", err)
|
return nil, curated.Errorf("recorder: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// attach recorder to vcs peripherals, including the panel
|
// attach recorder to vcs input system
|
||||||
vcs.RIOT.Ports.AttachEventRecorder(rec)
|
vcs.Input.AttachRecorder(rec)
|
||||||
|
|
||||||
// video digester for playback verification
|
// video digester for playback verification
|
||||||
rec.digest, err = digest.NewVideo(vcs.TV)
|
rec.digest, err = digest.NewVideo(vcs.TV)
|
||||||
|
|
|
@ -132,7 +132,7 @@ func (reg *PlaybackRegression) regress(newRegression bool, output io.Writer, msg
|
||||||
// function according to the current features of the recorder package and
|
// function according to the current features of the recorder package and
|
||||||
// the saved script
|
// the saved script
|
||||||
|
|
||||||
err = plb.AttachToVCS(vcs)
|
err = plb.AttachToVCSInput(vcs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, "", curated.Errorf("playback: %v", err)
|
return false, "", curated.Errorf("playback: %v", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -378,15 +378,8 @@ func Plumb(vcs *hardware.VCS, state *State, fromDifferentEmulation bool) {
|
||||||
vcs.RIOT = state.RIOT.Snapshot()
|
vcs.RIOT = state.RIOT.Snapshot()
|
||||||
vcs.TIA = state.TIA.Snapshot()
|
vcs.TIA = state.TIA.Snapshot()
|
||||||
|
|
||||||
vcs.CPU.Plumb(vcs.Mem)
|
// finish off plumbing process
|
||||||
vcs.Mem.Plumb(fromDifferentEmulation)
|
vcs.Plumb(fromDifferentEmulation)
|
||||||
vcs.RIOT.Plumb(vcs.Mem.RIOT, vcs.Mem.TIA)
|
|
||||||
vcs.TIA.Plumb(vcs.TV, vcs.Mem.TIA, vcs.RIOT.Ports, vcs.CPU)
|
|
||||||
|
|
||||||
// reset peripherals after new state has been plumbed. without this,
|
|
||||||
// controllers can feel odd if the newly plumbed state has left RIOT memory
|
|
||||||
// in a latched state
|
|
||||||
vcs.RIOT.Ports.ResetPeripherals()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// run from the supplied state until the cooridinates are reached.
|
// run from the supplied state until the cooridinates are reached.
|
||||||
|
|
|
@ -117,36 +117,36 @@ func (set PanelSetup) matchCartHash(hash string) bool {
|
||||||
func (set PanelSetup) apply(vcs *hardware.VCS) error {
|
func (set PanelSetup) apply(vcs *hardware.VCS) error {
|
||||||
if set.p0 {
|
if set.p0 {
|
||||||
inp := ports.InputEvent{Port: plugging.PortPanel, Ev: ports.PanelSetPlayer0Pro, D: true}
|
inp := ports.InputEvent{Port: plugging.PortPanel, Ev: ports.PanelSetPlayer0Pro, D: true}
|
||||||
if _, err := vcs.HandleInputEvent(inp); err != nil {
|
if _, err := vcs.Input.HandleInputEvent(inp); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
inp := ports.InputEvent{Port: plugging.PortPanel, Ev: ports.PanelSetPlayer0Pro, D: false}
|
inp := ports.InputEvent{Port: plugging.PortPanel, Ev: ports.PanelSetPlayer0Pro, D: false}
|
||||||
if _, err := vcs.HandleInputEvent(inp); err != nil {
|
if _, err := vcs.Input.HandleInputEvent(inp); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if set.p1 {
|
if set.p1 {
|
||||||
inp := ports.InputEvent{Port: plugging.PortPanel, Ev: ports.PanelSetPlayer1Pro, D: true}
|
inp := ports.InputEvent{Port: plugging.PortPanel, Ev: ports.PanelSetPlayer1Pro, D: true}
|
||||||
if _, err := vcs.HandleInputEvent(inp); err != nil {
|
if _, err := vcs.Input.HandleInputEvent(inp); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
inp := ports.InputEvent{Port: plugging.PortPanel, Ev: ports.PanelSetPlayer1Pro, D: false}
|
inp := ports.InputEvent{Port: plugging.PortPanel, Ev: ports.PanelSetPlayer1Pro, D: false}
|
||||||
if _, err := vcs.HandleInputEvent(inp); err != nil {
|
if _, err := vcs.Input.HandleInputEvent(inp); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if set.col {
|
if set.col {
|
||||||
inp := ports.InputEvent{Port: plugging.PortPanel, Ev: ports.PanelSetColor, D: true}
|
inp := ports.InputEvent{Port: plugging.PortPanel, Ev: ports.PanelSetColor, D: true}
|
||||||
if _, err := vcs.HandleInputEvent(inp); err != nil {
|
if _, err := vcs.Input.HandleInputEvent(inp); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
inp := ports.InputEvent{Port: plugging.PortPanel, Ev: ports.PanelSetColor, D: false}
|
inp := ports.InputEvent{Port: plugging.PortPanel, Ev: ports.PanelSetColor, D: false}
|
||||||
if _, err := vcs.HandleInputEvent(inp); err != nil {
|
if _, err := vcs.Input.HandleInputEvent(inp); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,12 +22,12 @@ import (
|
||||||
|
|
||||||
// HandleInput conceptualises data being sent to the console ports.
|
// HandleInput conceptualises data being sent to the console ports.
|
||||||
type HandleInput interface {
|
type HandleInput interface {
|
||||||
|
// PeripheralID identifies the device currently attached to the port.
|
||||||
|
PeripheralID(id plugging.PortID) plugging.PeripheralID
|
||||||
|
|
||||||
// HandleInputEvent forwards the Event and EventData to the device connected to the
|
// HandleInputEvent forwards the Event and EventData to the device connected to the
|
||||||
// specified PortID.
|
// specified PortID.
|
||||||
//
|
//
|
||||||
// Returns true if the port understood and handled the event.
|
// Returns true if the port understood and handled the event.
|
||||||
HandleInputEvent(ports.InputEvent) (bool, error)
|
HandleInputEvent(ports.InputEvent) (bool, error)
|
||||||
|
|
||||||
// PeripheralID identifies the device currently attached to the port.
|
|
||||||
PeripheralID(id plugging.PortID) plugging.PeripheralID
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue