mirror of
https://github.com/ivanizag/izapple2.git
synced 2024-11-16 19:08:37 +00:00
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 (
|
||||
"fmt"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/ivanizag/iz6502"
|
||||
@ -20,7 +21,9 @@ type Apple2 struct {
|
||||
commandChannel chan int
|
||||
cycleDurationNs float64 // Current speed. Inverse of the cpu clock in Ghz
|
||||
fastMode bool
|
||||
fastRequestsCounter int
|
||||
fastRequestsCounter int32
|
||||
cycleBreakpoint uint64
|
||||
breakPoint bool
|
||||
profile bool
|
||||
showSpeed bool
|
||||
paused bool
|
||||
@ -45,15 +48,21 @@ const (
|
||||
|
||||
// Run starts the Apple2 emulation
|
||||
func (a *Apple2) Run() {
|
||||
a.Start(false)
|
||||
}
|
||||
|
||||
// Start the Apple2 emulation, can start paused
|
||||
func (a *Apple2) Start(paused bool) {
|
||||
// Start the processor
|
||||
a.cpu.Reset()
|
||||
referenceTime := time.Now()
|
||||
speedReferenceTime := referenceTime
|
||||
speedReferenceCycles := uint64(0)
|
||||
|
||||
a.paused = paused
|
||||
|
||||
for {
|
||||
// Run a 6502 step
|
||||
// Run 6502 steps
|
||||
if !a.paused {
|
||||
for i := 0; i < cpuSpinLoops; i++ {
|
||||
// Conditional tracing
|
||||
@ -66,6 +75,12 @@ func (a *Apple2) Run() {
|
||||
// Special tracing
|
||||
a.executionTrace()
|
||||
}
|
||||
|
||||
if a.cycleBreakpoint != 0 && a.cpu.GetCycles() >= a.cycleBreakpoint {
|
||||
a.breakPoint = true
|
||||
a.cycleBreakpoint = 0
|
||||
a.paused = true
|
||||
}
|
||||
} else {
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
}
|
||||
@ -78,7 +93,17 @@ func (a *Apple2) Run() {
|
||||
switch command {
|
||||
case CommandKill:
|
||||
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
|
||||
referenceTime = time.Now()
|
||||
speedReferenceTime = referenceTime
|
||||
@ -134,6 +159,20 @@ func (a *Apple2) IsPaused() bool {
|
||||
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) {
|
||||
a.profile = value
|
||||
}
|
||||
@ -168,8 +207,12 @@ const (
|
||||
CommandKill
|
||||
// CommandReset executes a 6502 reset
|
||||
CommandReset
|
||||
// CommandPauseUnpauseEmulator allows the Pause button to freeze the emulator for a coffee break
|
||||
CommandPauseUnpauseEmulator
|
||||
// CommandPauseUnpause allows the Pause button to freeze the emulator for a coffee break
|
||||
CommandPauseUnpause
|
||||
// CommandPause pauses the emulator
|
||||
CommandPause
|
||||
// CommandStart restarts the emulator
|
||||
CommandStart
|
||||
)
|
||||
|
||||
// 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.
|
||||
if a.fastMode {
|
||||
a.fastRequestsCounter++
|
||||
atomic.AddInt32(&a.fastRequestsCounter, 1)
|
||||
}
|
||||
}
|
||||
|
||||
func (a *Apple2) releaseFastMode() {
|
||||
func (a *Apple2) ReleaseFastMode() {
|
||||
if a.fastMode {
|
||||
a.fastRequestsCounter--
|
||||
atomic.AddInt32(&a.fastRequestsCounter, -1)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -145,7 +145,7 @@ func (c *CardDisk2) softSwitchQ4(value bool) {
|
||||
if !value && c.power {
|
||||
// Turn off
|
||||
c.power = false
|
||||
c.a.releaseFastMode()
|
||||
c.a.ReleaseFastMode()
|
||||
drive := &c.drive[c.selected]
|
||||
if drive.diskette != nil {
|
||||
drive.diskette.PowerOff(c.a.cpu.GetCycles())
|
||||
@ -153,7 +153,7 @@ func (c *CardDisk2) softSwitchQ4(value bool) {
|
||||
} else if value && !c.power {
|
||||
// Turn on
|
||||
c.power = true
|
||||
c.a.requestFastMode()
|
||||
c.a.RequestFastMode()
|
||||
drive := &c.drive[c.selected]
|
||||
if drive.diskette != nil {
|
||||
drive.diskette.PowerOn(c.a.cpu.GetCycles())
|
||||
|
@ -89,9 +89,9 @@ func (c *CardFastChip) setSpeed(a *Apple2, value uint8) {
|
||||
return
|
||||
}
|
||||
if newAccelerated {
|
||||
a.requestFastMode()
|
||||
a.RequestFastMode()
|
||||
} else {
|
||||
a.releaseFastMode()
|
||||
a.ReleaseFastMode()
|
||||
}
|
||||
c.accelerated = newAccelerated
|
||||
}
|
||||
|
@ -135,7 +135,7 @@ func (k *keyboard) putKey(keyEvent *fyne.KeyEvent) {
|
||||
fmt.Println("Saving snapshot")
|
||||
}
|
||||
//case fyne.KeyPause:
|
||||
// k.s.a.SendCommand(izapple2.CommandPauseUnpauseEmulator)
|
||||
// k.s.a.SendCommand(izapple2.CommandPauseUnpause)
|
||||
}
|
||||
|
||||
if result != 0 {
|
||||
|
@ -21,7 +21,7 @@ func buildCommandToolbar(s *state, icon fyne.Resource, command int) widget.Toolb
|
||||
func buildToolbar(s *state) *widget.Toolbar {
|
||||
tb := widget.NewToolbar()
|
||||
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(widget.NewToolbarSeparator())
|
||||
tb.Append(newToolbarScreen(s))
|
||||
|
@ -131,7 +131,7 @@ func (k *sdlKeyboard) putKey(keyEvent *sdl.KeyboardEvent) {
|
||||
fmt.Println("Saving snapshot 'snapshot.png'")
|
||||
}
|
||||
case sdl.K_PAUSE:
|
||||
k.a.SendCommand(izapple2.CommandPauseUnpauseEmulator)
|
||||
k.a.SendCommand(izapple2.CommandPauseUnpause)
|
||||
}
|
||||
|
||||
// Missing values 91 to 95. Usually control for [\]^_
|
||||
|
@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"image/gif"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@ -17,11 +18,11 @@ func main() {
|
||||
fe := &headLessFrontend{}
|
||||
fe.keyChannel = make(chan uint8, 200)
|
||||
a.SetKeyboardProvider(fe)
|
||||
go a.Run()
|
||||
go a.Start(true /*paused*/)
|
||||
|
||||
inReader := bufio.NewReader(os.Stdin)
|
||||
running := true
|
||||
for running {
|
||||
done := false
|
||||
for !done {
|
||||
fmt.Print("* ")
|
||||
text, err := inReader.ReadString('\n')
|
||||
if err != nil {
|
||||
@ -31,18 +32,65 @@ func main() {
|
||||
parts := strings.Split(text, " ")
|
||||
command := strings.ToLower(parts[0])
|
||||
switch command {
|
||||
case "exit":
|
||||
a.SendCommand(izapple2.CommandKill)
|
||||
running = false
|
||||
|
||||
case "pts":
|
||||
fallthrough
|
||||
case "printtextscreen":
|
||||
// General commands
|
||||
case "quit":
|
||||
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))
|
||||
|
||||
case "ss":
|
||||
fallthrough
|
||||
case "savescreen":
|
||||
// Old:
|
||||
case "png":
|
||||
err := screen.SaveSnapshot(a, screen.ScreenModeNTSC, "snapshot.png")
|
||||
if err != nil {
|
||||
fmt.Printf("Error saving screen: %v.\n.", err)
|
||||
@ -50,9 +98,7 @@ func main() {
|
||||
fmt.Println("Saving screen 'snapshot.png'")
|
||||
}
|
||||
|
||||
case "ssm":
|
||||
fallthrough
|
||||
case "savescreenmono":
|
||||
case "pngm":
|
||||
err := screen.SaveSnapshot(a, screen.ScreenModePlain, "snapshot.png")
|
||||
if err != nil {
|
||||
fmt.Printf("Error saving screen: %v.\n.", err)
|
||||
@ -60,59 +106,72 @@ func main() {
|
||||
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":
|
||||
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:
|
||||
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 {
|
||||
animation := gif.GIF{}
|
||||
|
||||
@ -169,3 +228,18 @@ func (fe *headLessFrontend) GetKey(strobed bool) (key uint8, ok bool) {
|
||||
}
|
||||
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
Block a user