Files
izapple2/apple2.go
T
2026-07-10 13:47:31 +02:00

178 lines
4.5 KiB
Go

package izapple2
import (
"fmt"
"sync/atomic"
"github.com/ivanizag/iz6502"
"github.com/ivanizag/izapple2/screen"
"github.com/ivanizag/izapple2/storage"
)
// Apple2 represents all the components and state of the emulated machine
type Apple2 struct {
Name string
cpu *iz6502.State
mmu *memoryManager
io *ioC0Page
video screen.VideoSource
cg *CharacterGenerator
cards [8]Card
tracers []executionTracer
tickerCards []cardTicker
irqRequests uint8 // Bitmask of the slots asserting the IRQ line
softVideoSwitch softVideoSwitchProvider
board string
isApple2e bool
hasLowerCase bool
isFourColors bool // An Apple II without the 6 color mod
usesMouse bool
commandChannel chan command
dmaActive bool
dmaSlot int
cycles uint64
cycleDurationNs float64 // Current speed. Inverse of the cpu clock in Ghz
fastRequestsCounter int32
cycleBreakpoint atomic.Uint64
breakPoint atomic.Bool
profile bool
paused atomic.Bool
cpuTrace bool
forceCaps bool
removableMediaDrives []drive
currentFreqMHz float64
}
// GetCards returns the array of inserted cards
func (a *Apple2) GetCards() [8]Card {
return a.cards
}
// A cardTicker is a card that needs to run logic on every instruction
type cardTicker interface {
tick()
}
func (a *Apple2) registerTickerCard(c cardTicker) {
a.tickerCards = append(a.tickerCards, c)
}
// requestIRQ sets or clears the IRQ request of a slot. The CPU IRQ line
// is asserted while any slot requests it. To be called only from the
// emulation goroutine.
func (a *Apple2) requestIRQ(slot int, asserted bool) {
if asserted {
a.irqRequests |= 1 << slot
} else {
a.irqRequests &^= 1 << slot
}
a.cpu.SetIRQ(a.irqRequests != 0)
}
// SetKeyboardProvider attaches an external keyboard provider
func (a *Apple2) SetKeyboardProvider(kb KeyboardProvider) {
a.io.setKeyboardProvider(kb)
}
// SetJoysticksProvider attaches an external joysticks provider
func (a *Apple2) SetJoysticksProvider(j JoysticksProvider) {
a.io.setJoysticksProvider(j)
}
// SetMouseProvider attaches an external mouse provider
func (a *Apple2) SetMouseProvider(m MouseProvider) {
a.io.setMouseProvider(m)
}
// UsesMouse returns true when the emulator uses the mouse
func (a *Apple2) UsesMouse() bool {
return a.usesMouse
}
// IsPaused returns true when emulator is paused
func (a *Apple2) IsPaused() bool {
return a.paused.Load()
}
func (a *Apple2) GetCycles() uint64 {
return a.cycles
}
func (a *Apple2) GetCurrentFreqMHz() float64 {
return a.currentFreqMHz
}
// GetClockMhz returns the configured CPU clock in MHz
func (a *Apple2) GetClockMhz() float64 {
if a.cycleDurationNs == 0 {
// Unlimited speed, report the standard clock
return CPUClockMhz
}
return 1000.0 / a.cycleDurationNs
}
// SetCycleBreakpoint sets a cycle number to pause the emulator. 0 to disable
func (a *Apple2) SetCycleBreakpoint(cycle uint64) {
a.cycleBreakpoint.Store(cycle)
a.breakPoint.Store(false)
}
func (a *Apple2) BreakPoint() bool {
return a.breakPoint.Load()
}
// IsProfiling returns true when profiling
func (a *Apple2) IsProfiling() bool {
return a.profile
}
// IsForceCaps returns true when all letters are forced to upper case
func (a *Apple2) IsForceCaps() bool {
return a.forceCaps
}
func (a *Apple2) GetCgPageInfo() (int, int) {
return a.cg.getPage(), a.cg.getPages()
}
func (a *Apple2) RequestFastMode() {
// Note: if the fastMode is shorter than maxWaitDuration, there won't be any gain.
atomic.AddInt32(&a.fastRequestsCounter, 1)
}
func (a *Apple2) ReleaseFastMode() {
atomic.AddInt32(&a.fastRequestsCounter, -1)
}
func (a *Apple2) registerRemovableMediaDrive(d drive) {
a.removableMediaDrives = append(a.removableMediaDrives, d)
}
// InsertDiskette inserts a diskette into a drive (for WASM/in-memory loading)
func (a *Apple2) InsertDiskette(unit int, diskette storage.Diskette, name string) error {
if unit < 0 || unit >= len(a.removableMediaDrives) {
return fmt.Errorf("unit %v not defined", unit)
}
// Type assert to access the diskette field
switch d := a.removableMediaDrives[unit].(type) {
case *cardDisk2Drive:
d.name = name
d.diskette = diskette
return nil
default:
// For other drive types (like sequencer), fall back to path-based loading
// This won't work in WASM for non-standard drive types
return fmt.Errorf("drive type %T does not support direct diskette insertion", d)
}
}
func (a *Apple2) GetVideoSource() screen.VideoSource {
return a.video
}