import { h, JSX } from 'preact'; import cs from 'classnames'; import { useCallback, useEffect, useRef, useState } from 'preact/hooks'; import { Apple2 as Apple2Impl } from '../../apple2'; import { ControlButton } from '../ControlButton'; import { FileChooser } from '../FileChooser'; import { loadLocalBinaryFile } from '../util/files'; import { spawn } from '../util/promises'; import { toHex } from 'js/util'; import styles from './css/CPU.module.css'; import debuggerStyles from './css/Debugger.module.css'; export interface CPUProps { apple2: Apple2Impl | undefined; } interface DebugData { memory: string; registers: string; running: boolean; stack: string; trace: string; zeroPage: string; } const CIDERPRESS_EXTENSION = /#([0-9a-f]{2})([0-9a-f]{4})$/i; const VALID_PAGE = /^[0-9A-F]{1,2}$/i; const VALID_ADDRESS = /^[0-9A-F]{1,4}$/i; const ERROR_ICON = (
); export const CPU = ({ apple2 }: CPUProps) => { const debug = apple2?.getDebugger(); const [data, setData] = useState({ running: true, registers: '', stack: '', trace: '', zeroPage: '', memory: '', }); const [memoryPage, setMemoryPage] = useState('08'); const [loadAddress, setLoadAddress] = useState('0800'); const [run, setRun] = useState(true); const animationRef = useRef(0); const animate = useCallback(() => { if (debug) { setData({ registers: debug.dumpRegisters(), running: debug.isRunning(), stack: debug.getStack(38), trace: debug.getTrace(16), zeroPage: debug.dumpPage(0), memory: debug.dumpPage(parseInt(memoryPage, 16) || 0) }); } animationRef.current = requestAnimationFrame(animate); }, [debug, memoryPage]); useEffect(() => { animationRef.current = requestAnimationFrame(animate); return () => cancelAnimationFrame(animationRef.current); }, [animate]); const doPause = useCallback(() => { apple2?.stop(); }, [apple2]); const doRun = useCallback(() => { apple2?.run(); }, [apple2]); const doStep = useCallback(() => { debug?.step(); }, [debug]); const doLoadAddress = useCallback((event: JSX.TargetedEvent) => { setLoadAddress(event.currentTarget.value); }, []); const doRunCheck = useCallback((event: JSX.TargetedEvent) => { setRun(event.currentTarget.checked); }, []); const doMemoryPage = useCallback((event: JSX.TargetedEvent) => { setMemoryPage(event.currentTarget.value); }, []); const doChooseFile = useCallback((handles: FileSystemFileHandle[]) => { if (debug && handles.length === 1) { spawn(async () => { const file = await handles[0].getFile(); let atAddress = parseInt(loadAddress, 16) || 0x800; const matches = file.name.match(CIDERPRESS_EXTENSION); if (matches && matches.length === 3) { const [, , aux] = matches; atAddress = parseInt(aux, 16); } await loadLocalBinaryFile(file, atAddress, debug); setLoadAddress(toHex(atAddress, 4)); if (run) { debug?.runAt(atAddress); } }); } }, [debug, loadAddress, run]); const { memory, registers, running, stack, trace, zeroPage } = data; const memoryPageValid = VALID_PAGE.test(memoryPage); const loadAddressValid = VALID_ADDRESS.test(loadAddress); return (
Controls
{running ? ( ) : ( )}
Registers
                        {registers}
                    
Trace
                        {trace}
                    
ZP
                        {zeroPage}
                    
Stack
                        {stack}
                    

Memory Page: $ {memoryPageValid ? null : ERROR_ICON}
                    {memory}
                

Load File: $ {loadAddressValid ? null : ERROR_ICON} {' '} Run
); };