mirror of
https://github.com/ivanizag/izapple2.git
synced 2026-10-22 00:02:27 +00:00
178 lines
4.5 KiB
Go
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
|
|
}
|