import { ComponentChildren, h } from 'preact'; import { useCallback, useEffect, useRef, useState } from 'preact/hooks'; import cs from 'classnames'; import { Apple2 as Apple2Impl } from 'js/apple2'; import MMU from 'js/mmu'; import LanguageCard from 'js/cards/langcard'; import styles from './css/Memory.module.scss'; import debuggerStyles from './css/Debugger.module.scss'; /** * Encapsulates the read/write status of a bank */ interface ReadWrite { read: boolean; write: boolean; } /** * Encapsulates the read/write status of a language card */ interface LC extends ReadWrite { bank0: ReadWrite; bank1: ReadWrite; rom: ReadWrite; } /** * Encapsulates the read/write status of an aux/main memory bank. */ interface Bank extends ReadWrite { lc: LC; hires: ReadWrite; text: ReadWrite; zp: ReadWrite; } /** * Encapsulates the read/write status of aux main memory and rom banks. */ interface Banks { main: Bank; aux: Bank; io: ReadWrite; intcxrom: ReadWrite; } /** * Computes a language card state for an MMU aux or main bank. * * @param mmu MMU object * @param altzp Compute for main or aux bank * @returns LC read/write state */ const calcLC = (mmu: MMU, altzp: boolean) => { const read = mmu.readbsr && (mmu.altzp === altzp); const write = mmu.writebsr && (mmu.altzp === altzp); return { read, write, bank0: { read: read && !mmu.bank1, write: write && !mmu.bank1, }, bank1: { read: read && mmu.bank1, write: write && mmu.bank1, }, rom: { read: !mmu.readbsr, write: !mmu.writebsr, }, }; }; /** * Computes the hires aux or main read/write status. * * @param mmu MMU object * @param aux Compute for main or aux bank * @returns Hires pags read/write state */ const calcHires = (mmu: MMU, aux: boolean) => { const page2sel = mmu.hires && mmu._80store; return { read: page2sel ? mmu.page2 === aux : mmu.auxread === aux, write: page2sel ? mmu.page2 === aux : mmu.auxwrite === aux, }; }; /** * Computes the text aux or main read/write status. * * @param mmu MMU object * @param aux Compute for main or aux bank * @returns Text page read/write state */ const calcText = (mmu: MMU, aux: boolean) => { const page2sel = mmu._80store; return { read: page2sel ? mmu.page2 === aux : mmu.auxread === aux, write: page2sel ? mmu.page2 === aux : mmu.auxwrite === aux, }; }; /** * Creates read/write state from a flag * * @param flag Read/write flag * @returns A read/write state */ const readAndWrite = (flag: boolean) => { return { read: flag, write: flag, }; }; /** * Computes the aux or main bank read/write status. * * @param mmu MMU object * @param aux Compute for main or aux bank * @returns read/write state */ const calcBanks = (mmu: MMU): Banks => { return { main: { read: !mmu.auxread, write: !mmu.auxwrite, lc: calcLC(mmu, false), hires: calcHires(mmu, false), text: calcText(mmu, false), zp: readAndWrite(!mmu.altzp), }, aux: { read: mmu.auxread, write: mmu.auxwrite, lc: calcLC(mmu, true), hires: calcHires(mmu, true), text: calcText(mmu, true), zp: readAndWrite(mmu.altzp), }, io: readAndWrite(!mmu.intcxrom), intcxrom: readAndWrite(mmu.intcxrom), }; }; /** * Computes the read/write state of a language card. * * @param card The language card * @returns read/write state */ const calcLanguageCard = (card: LanguageCard): LC => { const read = card.readbsr; const write = card.writebsr; return { read, write, bank0: { read: read && !card.bsr2, write: write && !card.bsr2, }, bank1: { read: read && card.bsr2, write: write && card.bsr2, }, rom: { read: !card.readbsr, write: !card.writebsr, } }; }; /** * Computes the classes for a bank from read/write state. * * @param rw Read/write state * @returns Classes */ const rw = (rw: ReadWrite) => { return { [styles.read]: rw.read, [styles.write]: rw.write, [styles.inactive]: !rw.write && !rw.read, }; }; /** * Properties for LanguageCard component */ interface LanguageCardMapProps { lc: LC; children?: ComponentChildren; } /** * Language card state component use by both the MMU and LanguageCard * visualizations. * * @param lc LC state * @param children label component * @returns LanguageCard component */ const LanguageCardMap = ({ lc, children }: LanguageCardMapProps) => { return (
{children} LC
Bank 0
Bank 1
); }; /** * Legend of state colors. Green for read, red for write, blue for both, grey for * inactive. * * @returns Legend component */ const Legend = () => { return (
Read
Write
Read/Write
Inactive
); }; /** * Properties for the Memory component. */ export interface MemoryProps { apple2: Apple2Impl | undefined; } /** * Memory debugger component. Displays the active state of banks of * memory - aux, 80 column and language card depending up the machine. * * @param apple2 Apple2 object * @returns Memory component */ export const Memory = ({ apple2 }: MemoryProps) => { const animationRef = useRef(0); const [banks, setBanks] = useState(); const [lc, setLC] = useState(); const animate = useCallback(() => { if (apple2) { const mmu = apple2.getMMU(); if (mmu) { setBanks(calcBanks(mmu)); } else { const card = apple2.getIO().getSlot(0); if (card instanceof LanguageCard) { setLC(calcLanguageCard(card)); } } } animationRef.current = requestAnimationFrame(animate); }, [apple2]); useEffect(() => { animationRef.current = requestAnimationFrame(animate); return () => cancelAnimationFrame(animationRef.current); }, [animate]); if (banks) { return (
MMU
Aux Main
ROM
IO
CXROM
Aux Mem
Hires
Text/Lores
Stack/ZP
Main Mem
Hires
Text/Lores
Stack/ZP

); } else if (lc) { return (
Language Card
ROM

); } else { return null; } };