More headless commands
This commit is contained in:
parent
695eaa603b
commit
5b71a131f9
61
apple2.go
61
apple2.go
|
@ -2,6 +2,7 @@ package izapple2
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ivanizag/iz6502"
|
"github.com/ivanizag/iz6502"
|
||||||
|
@ -20,7 +21,9 @@ type Apple2 struct {
|
||||||
commandChannel chan int
|
commandChannel chan int
|
||||||
cycleDurationNs float64 // Current speed. Inverse of the cpu clock in Ghz
|
cycleDurationNs float64 // Current speed. Inverse of the cpu clock in Ghz
|
||||||
fastMode bool
|
fastMode bool
|
||||||
fastRequestsCounter int
|
fastRequestsCounter int32
|
||||||
|
cycleBreakpoint uint64
|
||||||
|
breakPoint bool
|
||||||
profile bool
|
profile bool
|
||||||
showSpeed bool
|
showSpeed bool
|
||||||
paused bool
|
paused bool
|
||||||
|
@ -45,15 +48,21 @@ const (
|
||||||
|
|
||||||
// Run starts the Apple2 emulation
|
// Run starts the Apple2 emulation
|
||||||
func (a *Apple2) Run() {
|
func (a *Apple2) Run() {
|
||||||
|
a.Start(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start the Apple2 emulation, can start paused
|
||||||
|
func (a *Apple2) Start(paused bool) {
|
||||||
// Start the processor
|
// Start the processor
|
||||||
a.cpu.Reset()
|
a.cpu.Reset()
|
||||||
referenceTime := time.Now()
|
referenceTime := time.Now()
|
||||||
speedReferenceTime := referenceTime
|
speedReferenceTime := referenceTime
|
||||||
speedReferenceCycles := uint64(0)
|
speedReferenceCycles := uint64(0)
|
||||||
|
|
||||||
|
a.paused = paused
|
||||||
|
|
||||||
for {
|
for {
|
||||||
// Run a 6502 step
|
// Run 6502 steps
|
||||||
if !a.paused {
|
if !a.paused {
|
||||||
for i := 0; i < cpuSpinLoops; i++ {
|
for i := 0; i < cpuSpinLoops; i++ {
|
||||||
// Conditional tracing
|
// Conditional tracing
|
||||||
|
@ -66,6 +75,12 @@ func (a *Apple2) Run() {
|
||||||
// Special tracing
|
// Special tracing
|
||||||
a.executionTrace()
|
a.executionTrace()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if a.cycleBreakpoint != 0 && a.cpu.GetCycles() >= a.cycleBreakpoint {
|
||||||
|
a.breakPoint = true
|
||||||
|
a.cycleBreakpoint = 0
|
||||||
|
a.paused = true
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
time.Sleep(200 * time.Millisecond)
|
time.Sleep(200 * time.Millisecond)
|
||||||
}
|
}
|
||||||
|
@ -78,7 +93,17 @@ func (a *Apple2) Run() {
|
||||||
switch command {
|
switch command {
|
||||||
case CommandKill:
|
case CommandKill:
|
||||||
return
|
return
|
||||||
case CommandPauseUnpauseEmulator:
|
case CommandPause:
|
||||||
|
if !a.paused {
|
||||||
|
a.paused = true
|
||||||
|
}
|
||||||
|
case CommandStart:
|
||||||
|
if a.paused {
|
||||||
|
a.paused = false
|
||||||
|
referenceTime = time.Now()
|
||||||
|
speedReferenceTime = referenceTime
|
||||||
|
}
|
||||||
|
case CommandPauseUnpause:
|
||||||
a.paused = !a.paused
|
a.paused = !a.paused
|
||||||
referenceTime = time.Now()
|
referenceTime = time.Now()
|
||||||
speedReferenceTime = referenceTime
|
speedReferenceTime = referenceTime
|
||||||
|
@ -134,6 +159,20 @@ func (a *Apple2) IsPaused() bool {
|
||||||
return a.paused
|
return a.paused
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *Apple2) GetCycles() uint64 {
|
||||||
|
return a.cpu.GetCycles()
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetCycleBreakpoint sets a cycle number to pause the emulator. 0 to disable
|
||||||
|
func (a *Apple2) SetCycleBreakpoint(cycle uint64) {
|
||||||
|
a.cycleBreakpoint = cycle
|
||||||
|
a.breakPoint = false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Apple2) BreakPoint() bool {
|
||||||
|
return a.breakPoint
|
||||||
|
}
|
||||||
|
|
||||||
func (a *Apple2) setProfiling(value bool) {
|
func (a *Apple2) setProfiling(value bool) {
|
||||||
a.profile = value
|
a.profile = value
|
||||||
}
|
}
|
||||||
|
@ -168,8 +207,12 @@ const (
|
||||||
CommandKill
|
CommandKill
|
||||||
// CommandReset executes a 6502 reset
|
// CommandReset executes a 6502 reset
|
||||||
CommandReset
|
CommandReset
|
||||||
// CommandPauseUnpauseEmulator allows the Pause button to freeze the emulator for a coffee break
|
// CommandPauseUnpause allows the Pause button to freeze the emulator for a coffee break
|
||||||
CommandPauseUnpauseEmulator
|
CommandPauseUnpause
|
||||||
|
// CommandPause pauses the emulator
|
||||||
|
CommandPause
|
||||||
|
// CommandStart restarts the emulator
|
||||||
|
CommandStart
|
||||||
)
|
)
|
||||||
|
|
||||||
// SendCommand enqueues a command to the emulator thread
|
// SendCommand enqueues a command to the emulator thread
|
||||||
|
@ -201,16 +244,16 @@ func (a *Apple2) executeCommand(command int) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Apple2) requestFastMode() {
|
func (a *Apple2) RequestFastMode() {
|
||||||
// Note: if the fastMode is shorter than maxWaitDuration, there won't be any gain.
|
// Note: if the fastMode is shorter than maxWaitDuration, there won't be any gain.
|
||||||
if a.fastMode {
|
if a.fastMode {
|
||||||
a.fastRequestsCounter++
|
atomic.AddInt32(&a.fastRequestsCounter, 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Apple2) releaseFastMode() {
|
func (a *Apple2) ReleaseFastMode() {
|
||||||
if a.fastMode {
|
if a.fastMode {
|
||||||
a.fastRequestsCounter--
|
atomic.AddInt32(&a.fastRequestsCounter, -1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -145,7 +145,7 @@ func (c *CardDisk2) softSwitchQ4(value bool) {
|
||||||
if !value && c.power {
|
if !value && c.power {
|
||||||
// Turn off
|
// Turn off
|
||||||
c.power = false
|
c.power = false
|
||||||
c.a.releaseFastMode()
|
c.a.ReleaseFastMode()
|
||||||
drive := &c.drive[c.selected]
|
drive := &c.drive[c.selected]
|
||||||
if drive.diskette != nil {
|
if drive.diskette != nil {
|
||||||
drive.diskette.PowerOff(c.a.cpu.GetCycles())
|
drive.diskette.PowerOff(c.a.cpu.GetCycles())
|
||||||
|
@ -153,7 +153,7 @@ func (c *CardDisk2) softSwitchQ4(value bool) {
|
||||||
} else if value && !c.power {
|
} else if value && !c.power {
|
||||||
// Turn on
|
// Turn on
|
||||||
c.power = true
|
c.power = true
|
||||||
c.a.requestFastMode()
|
c.a.RequestFastMode()
|
||||||
drive := &c.drive[c.selected]
|
drive := &c.drive[c.selected]
|
||||||
if drive.diskette != nil {
|
if drive.diskette != nil {
|
||||||
drive.diskette.PowerOn(c.a.cpu.GetCycles())
|
drive.diskette.PowerOn(c.a.cpu.GetCycles())
|
||||||
|
|
|
@ -89,9 +89,9 @@ func (c *CardFastChip) setSpeed(a *Apple2, value uint8) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if newAccelerated {
|
if newAccelerated {
|
||||||
a.requestFastMode()
|
a.RequestFastMode()
|
||||||
} else {
|
} else {
|
||||||
a.releaseFastMode()
|
a.ReleaseFastMode()
|
||||||
}
|
}
|
||||||
c.accelerated = newAccelerated
|
c.accelerated = newAccelerated
|
||||||
}
|
}
|
||||||
|
|
|
@ -135,7 +135,7 @@ func (k *keyboard) putKey(keyEvent *fyne.KeyEvent) {
|
||||||
fmt.Println("Saving snapshot")
|
fmt.Println("Saving snapshot")
|
||||||
}
|
}
|
||||||
//case fyne.KeyPause:
|
//case fyne.KeyPause:
|
||||||
// k.s.a.SendCommand(izapple2.CommandPauseUnpauseEmulator)
|
// k.s.a.SendCommand(izapple2.CommandPauseUnpause)
|
||||||
}
|
}
|
||||||
|
|
||||||
if result != 0 {
|
if result != 0 {
|
||||||
|
|
|
@ -21,7 +21,7 @@ func buildCommandToolbar(s *state, icon fyne.Resource, command int) widget.Toolb
|
||||||
func buildToolbar(s *state) *widget.Toolbar {
|
func buildToolbar(s *state) *widget.Toolbar {
|
||||||
tb := widget.NewToolbar()
|
tb := widget.NewToolbar()
|
||||||
tb.Append(buildCommandToolbar(s, resourceRestartSvg, izapple2.CommandReset))
|
tb.Append(buildCommandToolbar(s, resourceRestartSvg, izapple2.CommandReset))
|
||||||
tb.Append(buildCommandToolbar(s, resourcePauseSvg, izapple2.CommandPauseUnpauseEmulator))
|
tb.Append(buildCommandToolbar(s, resourcePauseSvg, izapple2.CommandPauseUnpause))
|
||||||
tb.Append(buildCommandToolbar(s, resourceFastForwardSvg, izapple2.CommandToggleSpeed))
|
tb.Append(buildCommandToolbar(s, resourceFastForwardSvg, izapple2.CommandToggleSpeed))
|
||||||
tb.Append(widget.NewToolbarSeparator())
|
tb.Append(widget.NewToolbarSeparator())
|
||||||
tb.Append(newToolbarScreen(s))
|
tb.Append(newToolbarScreen(s))
|
||||||
|
|
|
@ -131,7 +131,7 @@ func (k *sdlKeyboard) putKey(keyEvent *sdl.KeyboardEvent) {
|
||||||
fmt.Println("Saving snapshot 'snapshot.png'")
|
fmt.Println("Saving snapshot 'snapshot.png'")
|
||||||
}
|
}
|
||||||
case sdl.K_PAUSE:
|
case sdl.K_PAUSE:
|
||||||
k.a.SendCommand(izapple2.CommandPauseUnpauseEmulator)
|
k.a.SendCommand(izapple2.CommandPauseUnpause)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Missing values 91 to 95. Usually control for [\]^_
|
// Missing values 91 to 95. Usually control for [\]^_
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"image/gif"
|
"image/gif"
|
||||||
"os"
|
"os"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -17,11 +18,11 @@ func main() {
|
||||||
fe := &headLessFrontend{}
|
fe := &headLessFrontend{}
|
||||||
fe.keyChannel = make(chan uint8, 200)
|
fe.keyChannel = make(chan uint8, 200)
|
||||||
a.SetKeyboardProvider(fe)
|
a.SetKeyboardProvider(fe)
|
||||||
go a.Run()
|
go a.Start(true /*paused*/)
|
||||||
|
|
||||||
inReader := bufio.NewReader(os.Stdin)
|
inReader := bufio.NewReader(os.Stdin)
|
||||||
running := true
|
done := false
|
||||||
for running {
|
for !done {
|
||||||
fmt.Print("* ")
|
fmt.Print("* ")
|
||||||
text, err := inReader.ReadString('\n')
|
text, err := inReader.ReadString('\n')
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -31,18 +32,65 @@ func main() {
|
||||||
parts := strings.Split(text, " ")
|
parts := strings.Split(text, " ")
|
||||||
command := strings.ToLower(parts[0])
|
command := strings.ToLower(parts[0])
|
||||||
switch command {
|
switch command {
|
||||||
case "exit":
|
|
||||||
a.SendCommand(izapple2.CommandKill)
|
|
||||||
running = false
|
|
||||||
|
|
||||||
case "pts":
|
// General commands
|
||||||
fallthrough
|
case "quit":
|
||||||
case "printtextscreen":
|
a.SendCommand(izapple2.CommandKill)
|
||||||
|
done = true
|
||||||
|
case "help":
|
||||||
|
fmt.Print(help)
|
||||||
|
|
||||||
|
// Emulation control commands
|
||||||
|
case "start":
|
||||||
|
a.SendCommand(izapple2.CommandStart)
|
||||||
|
spinWait(func() bool { return !a.IsPaused() })
|
||||||
|
case "pause":
|
||||||
|
a.SendCommand(izapple2.CommandPause)
|
||||||
|
spinWait(func() bool { return a.IsPaused() })
|
||||||
|
case "run":
|
||||||
|
if len(parts) != 2 {
|
||||||
|
fmt.Printf("Usage: run <cycles>\n")
|
||||||
|
} else if cycles, err := strconv.Atoi(parts[1]); err != nil {
|
||||||
|
fmt.Printf("Usage: run <cycles>\n")
|
||||||
|
} else if !a.IsPaused() {
|
||||||
|
fmt.Printf("Emulation is already running\n")
|
||||||
|
} else {
|
||||||
|
a.RequestFastMode()
|
||||||
|
a.SetCycleBreakpoint(a.GetCycles() + uint64(cycles)*1000)
|
||||||
|
a.SendCommand(izapple2.CommandStart)
|
||||||
|
spinWait(func() bool { return a.BreakPoint() })
|
||||||
|
a.ReleaseFastMode()
|
||||||
|
}
|
||||||
|
case "cycle":
|
||||||
|
fmt.Printf("%v\n", a.GetCycles())
|
||||||
|
case "reset":
|
||||||
|
a.SendCommand(izapple2.CommandReset)
|
||||||
|
|
||||||
|
// Keyboard related commands
|
||||||
|
case "key":
|
||||||
|
if len(parts) < 2 {
|
||||||
|
fmt.Println("Usage: key <number>")
|
||||||
|
} else if code, err := strconv.Atoi(parts[1]); err != nil {
|
||||||
|
fmt.Println("Usage: key <number>")
|
||||||
|
} else {
|
||||||
|
fe.putKey(uint8(code))
|
||||||
|
}
|
||||||
|
case "type":
|
||||||
|
text := strings.Join(parts[1:], " ")
|
||||||
|
for _, char := range text {
|
||||||
|
fe.putKey(uint8(char))
|
||||||
|
}
|
||||||
|
case "enter":
|
||||||
|
fe.putKey(13)
|
||||||
|
case "clearkeys":
|
||||||
|
fe.clearKeyQueue()
|
||||||
|
|
||||||
|
//Screen related commands
|
||||||
|
case "text":
|
||||||
fmt.Print(izapple2.DumpTextModeAnsi(a))
|
fmt.Print(izapple2.DumpTextModeAnsi(a))
|
||||||
|
|
||||||
case "ss":
|
// Old:
|
||||||
fallthrough
|
case "png":
|
||||||
case "savescreen":
|
|
||||||
err := screen.SaveSnapshot(a, screen.ScreenModeNTSC, "snapshot.png")
|
err := screen.SaveSnapshot(a, screen.ScreenModeNTSC, "snapshot.png")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Error saving screen: %v.\n.", err)
|
fmt.Printf("Error saving screen: %v.\n.", err)
|
||||||
|
@ -50,9 +98,7 @@ func main() {
|
||||||
fmt.Println("Saving screen 'snapshot.png'")
|
fmt.Println("Saving screen 'snapshot.png'")
|
||||||
}
|
}
|
||||||
|
|
||||||
case "ssm":
|
case "pngm":
|
||||||
fallthrough
|
|
||||||
case "savescreenmono":
|
|
||||||
err := screen.SaveSnapshot(a, screen.ScreenModePlain, "snapshot.png")
|
err := screen.SaveSnapshot(a, screen.ScreenModePlain, "snapshot.png")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Error saving screen: %v.\n.", err)
|
fmt.Printf("Error saving screen: %v.\n.", err)
|
||||||
|
@ -60,59 +106,72 @@ func main() {
|
||||||
fmt.Println("Saving screen 'snapshot.png'")
|
fmt.Println("Saving screen 'snapshot.png'")
|
||||||
}
|
}
|
||||||
|
|
||||||
case "k":
|
|
||||||
fallthrough
|
|
||||||
case "key":
|
|
||||||
if len(parts) < 2 {
|
|
||||||
fmt.Println("No key specified.")
|
|
||||||
} else {
|
|
||||||
key := uint8(parts[1][0])
|
|
||||||
fe.keyChannel <- key
|
|
||||||
}
|
|
||||||
|
|
||||||
case "ks":
|
|
||||||
fallthrough
|
|
||||||
case "keys":
|
|
||||||
text := strings.Join(parts[1:], " ")
|
|
||||||
for _, char := range text {
|
|
||||||
fe.keyChannel <- uint8(char)
|
|
||||||
}
|
|
||||||
|
|
||||||
case "kr":
|
|
||||||
text := strings.Join(parts[1:], " ")
|
|
||||||
for _, char := range text {
|
|
||||||
fe.keyChannel <- uint8(char)
|
|
||||||
}
|
|
||||||
fe.keyChannel <- 13
|
|
||||||
|
|
||||||
case "r":
|
|
||||||
fallthrough
|
|
||||||
case "return":
|
|
||||||
fe.keyChannel <- 13
|
|
||||||
|
|
||||||
case "gif":
|
case "gif":
|
||||||
SaveGif(a, "snapshot.gif")
|
SaveGif(a, "snapshot.gif")
|
||||||
|
|
||||||
case "help":
|
|
||||||
fmt.Print(`
|
|
||||||
Available commands:
|
|
||||||
Exit: Stops the emulator and quits
|
|
||||||
PrintTextScreen or pts: Prints the text mode screen
|
|
||||||
PrintTextScreen, pts: Prints the text mode screen
|
|
||||||
SaveScreen or ss: Saves the screen with NTSC colors to "snapshot.png"
|
|
||||||
SaveScreenMono or ssm: Saves the monochromatic screen to "snapshot.png"
|
|
||||||
Key or k: Sends a key to the emulator
|
|
||||||
Keys or ks: Sends a string to the emulator
|
|
||||||
Return or r: Sends a return to the emulator
|
|
||||||
GIF or gif: Captures a GIF animation
|
|
||||||
Help: Prints this help
|
|
||||||
`)
|
|
||||||
default:
|
default:
|
||||||
fmt.Println("Unknown command.")
|
fmt.Println("Unknown command.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var help = `
|
||||||
|
General commands:
|
||||||
|
quit
|
||||||
|
Quits
|
||||||
|
help
|
||||||
|
Prints this help
|
||||||
|
|
||||||
|
Emulation control commands:
|
||||||
|
start
|
||||||
|
Runs the emulator
|
||||||
|
stop
|
||||||
|
Stops the emulator
|
||||||
|
run <cycles>
|
||||||
|
Runs the emulator for <cycles> thousand cycles at full speed. Waits until completed.
|
||||||
|
cycle
|
||||||
|
Prints the current cycle count
|
||||||
|
reset
|
||||||
|
Sends a reset to the emulator
|
||||||
|
|
||||||
|
Keyboard related commands:
|
||||||
|
key <key>
|
||||||
|
Queues the key to the emulator. <key> is a decimal number from 0 to 127.
|
||||||
|
type <string>
|
||||||
|
Queues the string characters to the emulator. No quotes for the argument, it can have spaces.
|
||||||
|
enter
|
||||||
|
Queues the enter key to the emulator. Alias for "key 13".
|
||||||
|
clearkeys
|
||||||
|
Clears the key queue.
|
||||||
|
|
||||||
|
Screen related commands:
|
||||||
|
text
|
||||||
|
Prints the text mode screen.
|
||||||
|
* png <filename>
|
||||||
|
Stores the active screen to <filename> in PNG format as NTSC color.
|
||||||
|
* pngm <filename>
|
||||||
|
Same as "png" in monochrome.
|
||||||
|
* gif <filename> <seconds> <delay>
|
||||||
|
Stores the running screen to <filename> in GIF format during <seconds> with a <delay> per frame
|
||||||
|
in 100ths of a second as NTSC color.
|
||||||
|
If the emulators is stopped. It is run at full speed during <seconds> and the stopped again.
|
||||||
|
* gifm <filename> <seconds> <delay>
|
||||||
|
Same as "gif" in monochrome.
|
||||||
|
|
||||||
|
`
|
||||||
|
|
||||||
|
/*
|
||||||
|
TODO:
|
||||||
|
floppy related commands: load disk....
|
||||||
|
joystick related commands: set paddle and button state, dump state
|
||||||
|
*/
|
||||||
|
|
||||||
|
func spinWait(f func() bool) {
|
||||||
|
for !f() {
|
||||||
|
time.Sleep(time.Millisecond * 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func SaveGif(a *izapple2.Apple2, filename string) error {
|
func SaveGif(a *izapple2.Apple2, filename string) error {
|
||||||
animation := gif.GIF{}
|
animation := gif.GIF{}
|
||||||
|
|
||||||
|
@ -169,3 +228,18 @@ func (fe *headLessFrontend) GetKey(strobed bool) (key uint8, ok bool) {
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (fe *headLessFrontend) putKey(key uint8) {
|
||||||
|
fe.keyChannel <- key
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fe *headLessFrontend) clearKeyQueue() {
|
||||||
|
empty := false
|
||||||
|
for !empty {
|
||||||
|
select {
|
||||||
|
case <-fe.keyChannel:
|
||||||
|
default:
|
||||||
|
empty = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue