diff --git a/src/computer.ts b/src/computer.ts index b446b0b..79b6621 100644 --- a/src/computer.ts +++ b/src/computer.ts @@ -1,4 +1,4 @@ -import { CpuEvent, CpuEventHandler, UiEvent, UiEventHandler } from "./events"; +import { CpuEvent, CpuEventHandler, UiCpuSignal, UiCpuSignalHandler, UiEvent, UiEventHandler } from "./events"; import { byte_array_to_js_source, format_hex } from "./etc"; import { Instruction, ISA } from "./instructionSet"; import { m256, u2, u3, u8 } from "./num"; @@ -28,14 +28,6 @@ export class Computer { private current_instr: TempInstrState | null = null; events: CpuEventHandler = new CpuEventHandler(); - constructor() { - // Add events - for (const [, e_type] of Object.entries(CpuEvent)) { - this.events.register_event(e_type as CpuEvent); - } - this.events.seal(); - } - cycle(): void { const current_byte = this.getMemorySilent(this.program_counter, 0); @@ -102,6 +94,7 @@ export class Computer { } this.events.dispatch(CpuEvent.Cycle); } + private getMemorySilent(address: u8, bank_override?: u2): u8 { const bank = this.banks[bank_override ?? this.bank]; const value = bank[address] as u8; @@ -116,8 +109,8 @@ export class Computer { return value; } - setMemory(address: u8, value: u8): void { - this.banks[this.bank][address] = value; + setMemory(address: u8, value: u8, bank?: u2): void { + this.banks[bank ?? this.bank][address] = value; this.events.dispatch(CpuEvent.MemoryChanged, { address, bank: this.bank, value }); } @@ -173,12 +166,16 @@ export class Computer { this.carry_flag = false; } - init_events(ui: UiEventHandler): void { - ui.listen(UiEvent.RequestCpuCycle, (cycle_count) => { + init_events(ui: UiCpuSignalHandler): void { + ui.listen(UiCpuSignal.RequestCpuCycle, (cycle_count) => { for (let i = 0; i < cycle_count; i++) this.cycle(); }); - ui.listen(UiEvent.RequestMemoryChange, ({ address, value }) => this.setMemory(address, value)); - ui.listen(UiEvent.RequestRegisterChange, ({ register_no, value }) => this.setRegister(register_no, value)); + ui.listen(UiCpuSignal.RequestMemoryChange, ({ address, bank, value }) => this.setMemory(address, value, bank)); + ui.listen(UiCpuSignal.RequestRegisterChange, ({ register_no, value }) => this.setRegister(register_no, value)); + ui.listen(UiCpuSignal.RequestMemoryDump, () => + this.events.dispatch(CpuEvent.MemoryDumped, { memory: this.dump_memory() }) + ); + ui.listen(UiCpuSignal.RequestCpuReset, () => this.reset()); } load_memory(program: Array): void { @@ -195,8 +192,8 @@ export class Computer { this.program_counter = 0; } - dump_memory(): Uint8Array { - return this.banks[0]; + dump_memory(): [Uint8Array, Uint8Array, Uint8Array, Uint8Array] { + return this.banks; } private step_forward(): void { diff --git a/src/eventHandler.ts b/src/eventHandler.ts index 4db7dbf..4ca7c98 100644 --- a/src/eventHandler.ts +++ b/src/eventHandler.ts @@ -14,29 +14,13 @@ export class Event { export class EventHandler { events: Array> = []; - private sealed: boolean; - constructor() { - this.sealed = false; - } - seal(): void { - if (this.sealed) { - throw new Error("Already Sealed"); - } - this.sealed = true; - } - - register_event(identifier: T): void { - if (this.sealed) { - throw new Error("Can't add event to sealed event handler"); - } - const event = new Event(identifier); - this.events.push(event); - } dispatch(identifier: T, event_data?: unknown): void { const event = this.events.find((e) => e.identifier === identifier); if (event === undefined) { - throw new Error("Event not found"); + // throw new Error("Event not found"); + console.log(`Event for ${identifier} was dispatched without any listeners. Data:`, event_data); + return; } for (const callback of event.callbacks) { callback(event_data); @@ -54,10 +38,13 @@ export class EventHandler { }); } listen(identifier: T, callback: (event_data: unknown) => void): void { - if (!this.sealed) throw new Error("Event handler must be sealed before adding listener"); - const event = this.events.find((e) => e.identifier === identifier); + let event = this.events.find((e) => e.identifier === identifier); if (event === undefined) { - throw new Error("No event found given identifier"); + // If no event found, create it. + // Type system is used to verify that events are valid. + // If this were plain JS, a registerEvent method would likely be better to avoid listening to events that will never exist. + event = new Event(identifier); + this.events.push(event); } event.callbacks.push(callback); } diff --git a/src/events.ts b/src/events.ts index 68e184b..2bd52eb 100644 --- a/src/events.ts +++ b/src/events.ts @@ -22,16 +22,13 @@ export enum CpuEvent { Print, Reset, Halt, - // ClockStarted, - // ClockStopped, + MemoryDumped, MemoryAccessed, SwitchBank, SetFlagCarry, } type VoidDataCpuEventList = CpuEvent.Halt | CpuEvent.Reset | CpuEvent.Cycle; -// | CpuEvent.ClockStarted -// | CpuEvent.ClockStopped; interface CpuEventMap { [CpuEvent.MemoryChanged]: { address: u8; bank: u2; value: u8 }; @@ -45,6 +42,7 @@ interface CpuEventMap { [CpuEvent.SwitchBank]: { bank: u2 }; [CpuEvent.Print]: string; [CpuEvent.SetFlagCarry]: boolean; + [CpuEvent.MemoryDumped]: { memory: [Uint8Array, Uint8Array, Uint8Array, Uint8Array] }; } export interface CpuEventHandler extends EventHandler { @@ -60,30 +58,51 @@ interface CpuEventHandlerConstructor { export const CpuEventHandler = EventHandler as CpuEventHandlerConstructor; +// +// Ui -> CPU Signaler definition +// + +export enum UiCpuSignal { + RequestCpuCycle, + RequestMemoryChange, + RequestRegisterChange, + RequestCpuReset, + RequestMemoryDump, +} + +type VoidDataUiCpuSignalList = UiCpuSignal.RequestCpuReset | UiCpuSignal.RequestMemoryDump; + +interface UiCpuSignalMap { + [UiCpuSignal.RequestCpuCycle]: number; + [UiCpuSignal.RequestMemoryChange]: { address: u8; bank: u2; value: u8 }; + [UiCpuSignal.RequestRegisterChange]: { register_no: u3; value: u8 }; +} + +export interface UiCpuSignalHandler extends EventHandler { + listen(type: E, listener: () => void): void; + dispatch(type: E): void; + listen(type: E, listener: (ev: UiCpuSignalMap[E]) => void): void; + dispatch(type: E, data: UiCpuSignalMap[E]): void; +} + +interface UICpuSignalHandlerConstructor { + new (): UiCpuSignalHandler; +} + +export const UiCpuSignalHandler = EventHandler as UICpuSignalHandlerConstructor; + // // Ui Event Handler Definition // export enum UiEvent { - // Maybe move these into a UI -> CPU signal system? - RequestCpuCycle, - RequestMemoryChange, - RequestRegisterChange, - // Ui Events EditOn, EditOff, - ConsoleOn, - ConsoleOff, - ExplainerOn, - ExplainerOff, - VideoOn, - VideoOff, + ChangeViewBank, } interface UiEventMap { - [UiEvent.RequestCpuCycle]: number; - [UiEvent.RequestMemoryChange]: { address: u8; value: u8 }; - [UiEvent.RequestRegisterChange]: { register_no: u3; value: u8 }; + [UiEvent.ChangeViewBank]: { bank: u2 }; } type VoidDataUiEventList = UiEvent.EditOn | UiEvent.EditOff; diff --git a/src/include/explainer.png b/src/include/explainer.png deleted file mode 100644 index 7ed74b8..0000000 Binary files a/src/include/explainer.png and /dev/null differ diff --git a/src/include/index.html b/src/include/index.html index 58ad3e9..cd39acf 100644 --- a/src/include/index.html +++ b/src/include/index.html @@ -7,25 +7,46 @@ Virtual 8-Bit Computer +
VIRTUAL 8-BIT COMPUTER
-
←REGISTERS
+
↯REGISTERS
MEMORY↯
- - - +
+
+
+ + + + +
+ +
+
diff --git a/src/include/texout.png b/src/include/texout.png deleted file mode 100644 index 7b8aab3..0000000 Binary files a/src/include/texout.png and /dev/null differ diff --git a/src/include/tv.png b/src/include/tv.png deleted file mode 100644 index 7c0aba0..0000000 Binary files a/src/include/tv.png and /dev/null differ diff --git a/src/index.ts b/src/index.ts index 65f9d24..7eef404 100644 --- a/src/index.ts +++ b/src/index.ts @@ -43,40 +43,12 @@ function main(): void { const ui = new UI(); ui.init_events(computer.events); computer.load_memory(program); - computer.init_events(ui.events); + computer.init_events(ui.cpu_signaler); window.comp = computer; window.ui = ui; $("ISA").textContent = generate_isa(ISA); - $("binary_upload").addEventListener("change", (e) => { - const t = e.target; - if (t === null) { - return; - } - - const file: File | undefined = (t as HTMLInputElement).files?.[0]; - if (file === undefined) { - console.log("No files attribute on file input"); - return; - } - const reader = new FileReader(); - console.log(file); - reader.addEventListener("load", (e) => { - if (e.target !== null) { - const data = e.target.result; - if (data instanceof ArrayBuffer) { - const view = new Uint8Array(data); - const array = [...view] as Array; - computer.reset(); - computer.load_memory(array); - } else { - console.log("not array"); - } - } - }); - reader.readAsArrayBuffer(file); - }); let fire = false; window.firehose = (): void => { if (fire === false) { @@ -88,17 +60,6 @@ function main(): void { console.error("Firehose already started"); } }; - - $("save_button").addEventListener("click", () => { - const memory = computer.dump_memory(); - const blob = new Blob([memory], { type: "application/octet-stream" }); - const url = URL.createObjectURL(blob); - - const link = document.createElement("a"); - link.href = url; - link.download = "bin.bin"; - link.click(); - }); } document.addEventListener("DOMContentLoaded", () => { diff --git a/src/num.ts b/src/num.ts index fa348ca..96adb27 100644 --- a/src/num.ts +++ b/src/num.ts @@ -28,8 +28,21 @@ export type u2 = 0 | 1 | 2 | 3; export type u3 = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7; export type u4 = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15; +/** + * Takes the input number and returns it modulus 256. Converts to `u8` + * + * @param number + * @returns number mod 256 (u8) + */ export const m256 = (number: number): u8 => (number % 256) as u8; +/** + * Determines whether a number is a u2 type (unsigned 2-bit integer). + * Does not check for non integers + * + * @param n Input number to be checked + * @returns true or false + */ export function isU2(n: number): n is u2 { if (n < 4 && n >= 0) { return true; @@ -41,9 +54,8 @@ export function isU2(n: number): n is u2 { * Determines whether a number is a u3 type (unsigned 3-bit integer). * Does not check for non integers * - * @param n - Input number to be checked + * @param n Input number to be checked * @returns true or false - * */ export function isU3(n: number): n is u3 { if (n < 8 && n >= 0) { @@ -51,13 +63,13 @@ export function isU3(n: number): n is u3 { } return false; } + /** * Determines whether a number is a u4 type (unsigned 4-bit integer). * Does not check for non integers * - * @param n - Input number to be checked + * @param n Input number to be checked * @returns true or false - * */ export function isU4(n: number): n is u4 { if (n < 16 && n >= 0) { @@ -65,13 +77,13 @@ export function isU4(n: number): n is u4 { } return false; } + /** * Determines whether a number is a u8 type (unsigned 8-bit integer). * Does not check for non integers * - * @param n - Input number to be checked + * @param n Input number to be checked * @returns true or false - * */ export function isU8(n: number): n is u8 { if (n < 256 && n >= 0) { diff --git a/src/style/buttons.scss b/src/style/buttons.scss index 74b34d4..7d922ae 100644 --- a/src/style/buttons.scss +++ b/src/style/buttons.scss @@ -38,17 +38,6 @@ label.button:hover { color: white; } -#controls_bar { - grid-area: buttons; - - display: flex; - gap: 10px; - #controls_buttons { - display: flex; - gap: inherit; - } -} - input[type="range"] { background-color: transparent; -webkit-appearance: none; diff --git a/src/style/memory_registers.scss b/src/style/memory_registers.scss index 8052540..8472844 100644 --- a/src/style/memory_registers.scss +++ b/src/style/memory_registers.scss @@ -29,13 +29,6 @@ color: lightgray; } -#labelcontainer { - column-gap: 18px; - font-size: 0.85em; - display: flex; - align-items: center; - user-select: none; -} .celled_viewer { display: grid; max-width: fit-content; diff --git a/src/style/style.scss b/src/style/style.scss index 64cd08d..3ff17a1 100644 --- a/src/style/style.scss +++ b/src/style/style.scss @@ -33,13 +33,12 @@ main { grid-template-columns: min-content min-content min-content; grid-template-rows: min-content min-content min-content; grid-template-areas: - "cycles registers regmemlabel" - "title memory memory" - ". buttons buttons "; + ". regmemlabel . cycles " + ". registers . bank " + "title memory memory memory" + ". buttons buttons buttons "; #memory { grid-area: memory; - // grid-column: 2/4; - // grid-row: 2/6; } #window_box { grid-area: windowbox; @@ -47,12 +46,15 @@ main { #registers { grid-area: registers; } + #memory_bank_view { + grid-area: bank; + } #labelcontainer { grid-area: regmemlabel; } #cycles { grid-area: cycles; - text-align: left; + text-align: right; align-self: center; font-size: 0.48em; user-select: none; @@ -66,6 +68,45 @@ main { } } +#labelcontainer { + column-gap: 18px; + font-size: 0.75em; + display: flex; + flex-direction: row; + align-items: center; + user-select: none; +} + +#memory_bank_view { + display: flex; + #bank_boxes { + display: flex; + align-items: flex-end; + $pad: 8px; + $border-width: 5px; + $border: $border-width solid var(--border); + button { + border-top: $border; + border-bottom: unset; + padding-inline: $pad + $border-width; + } + button:first-child { + border-left: $border; + padding-left: $pad; + } + button:last-child { + padding-right: $pad; + border-right: $border; + } + button.selected { + color: lightgray; + padding-block: 5px; + padding-inline: $pad; + border-inline: $border; + } + } +} + .invalid { --color: var(--mem-invalid); } @@ -91,3 +132,38 @@ div#main.editor { } } } + +#controls_bar { + grid-area: buttons; + + display: flex; + gap: 10px; + #controls_buttons { + display: flex; + gap: inherit; + justify-content: inherit; + } + #save_load_buttons { + display: flex; + gap: inherit; + justify-content: inherit; + } +} + +#edit_button { + aspect-ratio: 1; + + display: flex; + justify-content: center; + align-content: center; + img { + min-height: 30px; + min-width: 30px; + &:hover { + filter: grayscale(100%) brightness(500%); + } + } + &.on img { + filter: grayscale(100%) brightness(500%); + } +} diff --git a/src/style/windows.scss b/src/style/windows.scss index dc4b24d..a28c9fc 100644 --- a/src/style/windows.scss +++ b/src/style/windows.scss @@ -14,22 +14,18 @@ flex-direction: column; gap: 10px; margin-left: 10px; - max-width: 500px; + width: 500px; .window { overflow-y: hidden; position: relative; border: 5px Solid var(--border); border-bottom: unset; - &.collapsed { - .window_title { - // border-bottom: unset; - } - } + .window_title { - position: sticky; - user-select: none; display: flex; + position: sticky; align-items: center; + user-select: none; justify-content: space-between; font-size: 0.6em; color: lightgray; @@ -45,21 +41,24 @@ repeating-linear-gradient(to right, transparent, transparent 2px, transparent 2px, yellow 2px, yellow 4px); #text { - display: inline-block; + word-break: keep-all; + white-space: nowrap; text-align: center; - height: 100%; padding-inline: 10px; background-color: black; } #collapse_button { - height: 23px !important; + height: 23px; aspect-ratio: 1; - border: 2px solid white; + border: 2px solid yellow; background-color: black; margin-right: 3px; } } } + .window.collapsed > :not(:first-child) { + display: none; + } } .window#tv { @@ -71,11 +70,10 @@ } #instruction_explainer { - grid-area: explainer; display: flex; flex-direction: column; gap: 5px; - height: 400px; + height: 300px; #expl_box { padding-inline: 20px; padding-block-start: 10px; @@ -97,7 +95,6 @@ } #printout { - grid-area: printout; height: 500px; word-wrap: break-word; word-break: break-all; diff --git a/src/ui.ts b/src/ui.ts index d29a03b..03659a9 100644 --- a/src/ui.ts +++ b/src/ui.ts @@ -1,28 +1,22 @@ -import { CpuEvent, CpuEventHandler, UiEvent, UiEventHandler } from "./events"; +import { CpuEvent, CpuEventHandler, UiCpuSignalHandler, UiEvent, UiEventHandler } from "./events"; import { $ } from "./etc"; import { InstructionExplainer } from "./ui/windows/instructionExplainer"; import { MemoryView } from "./ui/memoryView"; import { frequencyIndicator } from "./ui/frequencyIndicator"; import { RegisterView } from "./ui/registerView"; import { Screen } from "./ui/windows/screen"; -import { EditButton } from "./ui/edit_button"; +import { EditButton } from "./ui/editButton"; import { UiComponent, UiComponentConstructor } from "./ui/uiComponent"; import { pausePlay } from "./ui/pausePlay"; import { Printout } from "./ui/windows/printout"; +import { SaveLoad } from "./ui/saveLoad"; export class UI { - events: UiEventHandler = new UiEventHandler(); - - private components: Array; + ui_events: UiEventHandler = new UiEventHandler(); + cpu_signaler: UiCpuSignalHandler = new UiCpuSignalHandler(); + private components: Array = []; constructor() { - for (const [, e_type] of Object.entries(UiEvent)) { - this.events.register_event(e_type as UiEvent); - } - this.events.seal(); - - this.components = []; - this.register_component(MemoryView, $("memory")); this.register_component(frequencyIndicator, $("cycles")); this.register_component(InstructionExplainer, $("instruction_explainer")); @@ -31,15 +25,16 @@ export class UI { this.register_component(Printout, $("printout")); this.register_component(EditButton, $("edit_button")); this.register_component(pausePlay, $("controls_buttons")); - - const pp_button = $("pause_play_button"); + this.register_component(SaveLoad, $("save_load_buttons")); } + private register_component(ctor: UiComponentConstructor, e: HTMLElement): void { if (e === undefined) { + // shouldn't be able to happen, but I sometimes let the type system slide when getting elements from the DOM. console.log(ctor); throw new Error("Could not find HTML element while registering UI component"); } - const component = new ctor(e, this.events); + const component = new ctor(e, this.ui_events, this.cpu_signaler); this.components.push(component); } @@ -48,14 +43,10 @@ export class UI { this.reset(); }); - for (const c of this.components) { - if (c.init_cpu_events) c.init_cpu_events(cpu_events); - } + for (const c of this.components) if (c.init_cpu_events) c.init_cpu_events(cpu_events); } reset(): void { - for (const c of this.components) { - c.reset(); - } + for (const c of this.components) if (c.reset) c.reset(); } } diff --git a/src/ui/edit_button.ts b/src/ui/editButton.ts similarity index 100% rename from src/ui/edit_button.ts rename to src/ui/editButton.ts diff --git a/src/ui/editableHex.ts b/src/ui/editableHex.ts index 32f7fb1..8e8b894 100644 --- a/src/ui/editableHex.ts +++ b/src/ui/editableHex.ts @@ -8,22 +8,18 @@ const HEX_CHARACTERS = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", " export class EditorContext { private list: Array; private width: number; - private height: number; private enabled: boolean = false; private current_cell_info: { left?: string; right?: string; old?: string }; private edit_callback: (n: number, value: u8) => void; - constructor(list: Array, width: number, height: number, callback: (n: number, value: u8) => void) { + constructor(list: Array, width: number, callback: (n: number, value: u8) => void) { this.list = list; this.width = width; - this.height = height; this.edit_callback = callback; this.current_cell_info = {}; for (const [i, cell] of this.list.entries()) { cell.setAttribute("spellcheck", "false"); - cell.addEventListener("keydown", (e) => { - this.keydown(e, i); - }); + cell.addEventListener("keydown", (e) => this.keydown(e, i)); cell.addEventListener("input", (e) => { const target = e.target as HTMLElement; if (target === null) return; diff --git a/src/ui/frequencyIndicator.ts b/src/ui/frequencyIndicator.ts index a0f7c53..b1b4bf8 100644 --- a/src/ui/frequencyIndicator.ts +++ b/src/ui/frequencyIndicator.ts @@ -8,9 +8,9 @@ export class frequencyIndicator implements UiComponent { private last_value: number = 0; private last_time: number = 0; events: UiEventHandler; - constructor(element: HTMLElement, e: UiEventHandler) { + constructor(element: HTMLElement, events: UiEventHandler) { this.element = element; - this.events = e; + this.events = events; this.start(); } diff --git a/src/ui/memoryView.ts b/src/ui/memoryView.ts index 9db61dd..6609709 100644 --- a/src/ui/memoryView.ts +++ b/src/ui/memoryView.ts @@ -1,26 +1,24 @@ -import { CpuEvent, CpuEventHandler, UiEvent, UiEventHandler } from "../events"; +import { CpuEvent, CpuEventHandler, UiCpuSignal, UiCpuSignalHandler, UiEvent, UiEventHandler } from "../events"; import { ParamType } from "../instructionSet"; import { u8 } from "../num.js"; import { UiComponent } from "./uiComponent"; import { CelledViewer } from "./celledViewer"; import { EditorContext } from "./editableHex"; -type MemoryCell = { - el: HTMLDivElement; -}; - export class MemoryView extends CelledViewer implements UiComponent { program_counter: u8 = 0; last_accessed_cell: u8 | null = null; events: UiEventHandler; - constructor(element: HTMLElement, e: UiEventHandler) { + cpu_signals: UiCpuSignalHandler; + constructor(element: HTMLElement, events: UiEventHandler, cpu_signals: UiCpuSignalHandler) { super(16, 16, element); this.program_counter = 0; - this.events = e; + this.events = events; + this.cpu_signals = cpu_signals; const list = this.cells.map((c) => c.el); - const editor = new EditorContext(list, this.width, this.height, (i, value) => { - this.events.dispatch(UiEvent.RequestMemoryChange, { address: i as u8, value }); + const editor = new EditorContext(list, this.width, (i, value) => { + this.cpu_signals.dispatch(UiCpuSignal.RequestMemoryChange, { address: i as u8, bank: 0, value }); }); this.events.listen(UiEvent.EditOn, () => { editor.enable(); diff --git a/src/ui/pausePlay.ts b/src/ui/pausePlay.ts index 66312f8..e8ce91e 100644 --- a/src/ui/pausePlay.ts +++ b/src/ui/pausePlay.ts @@ -1,5 +1,5 @@ import { el } from "../etc"; -import { UiEventHandler, UiEvent } from "../events"; +import { UiEventHandler, UiEvent, CpuEventHandler, UiCpuSignalHandler, UiCpuSignal } from "../events"; import { UiComponent } from "./uiComponent"; const MAX_SLIDER = 1000; @@ -12,9 +12,12 @@ export class pausePlay implements UiComponent { events: UiEventHandler; on: boolean = false; cycle_delay: number; - constructor(element: HTMLElement, events: UiEventHandler) { + cpu_signals: UiCpuSignalHandler; + + constructor(element: HTMLElement, events: UiEventHandler, cpu_signals: UiCpuSignalHandler) { this.element = element; this.events = events; + this.cpu_signals = cpu_signals; this.start_button = el("button", "pause_play_button"); this.step_button = el("button", "step_button"); this.range = el("input", "speed_range"); @@ -72,7 +75,7 @@ export class pausePlay implements UiComponent { if (this.on === false) { return; } - this.events.dispatch(UiEvent.RequestCpuCycle, 1); + this.cpu_signals.dispatch(UiCpuSignal.RequestCpuCycle, 1); setTimeout(loop, this.cycle_delay); }; loop(); @@ -81,7 +84,7 @@ export class pausePlay implements UiComponent { if (this.on) { this.stop(); } else { - this.events.dispatch(UiEvent.RequestCpuCycle, 1); + this.cpu_signals.dispatch(UiCpuSignal.RequestCpuCycle, 1); } } diff --git a/src/ui/registerView.ts b/src/ui/registerView.ts index 024079b..b80251e 100644 --- a/src/ui/registerView.ts +++ b/src/ui/registerView.ts @@ -1,4 +1,4 @@ -import { CpuEvent, CpuEventHandler, UiEvent, UiEventHandler } from "../events"; +import { CpuEvent, CpuEventHandler, UiCpuSignal, UiCpuSignalHandler, UiEvent, UiEventHandler } from "../events"; import { u3 } from "../num"; import { CelledViewer } from "./celledViewer"; import { EditorContext } from "./editableHex"; @@ -6,13 +6,15 @@ import { UiComponent } from "./uiComponent"; export class RegisterView extends CelledViewer implements UiComponent { events: UiEventHandler; - constructor(element: HTMLElement, e: UiEventHandler) { + cpu_signals: UiCpuSignalHandler; + constructor(element: HTMLElement, events: UiEventHandler, cpu_signals: UiCpuSignalHandler) { super(8, 1, element); - this.events = e; + this.events = events; + this.cpu_signals = cpu_signals; const list = this.cells.map((c) => c.el); - const editor = new EditorContext(list, this.width, this.height, (i, value) => { - this.events.dispatch(UiEvent.RequestRegisterChange, { register_no: i as u3, value }); + const editor = new EditorContext(list, this.width, (i, value) => { + this.cpu_signals.dispatch(UiCpuSignal.RequestRegisterChange, { register_no: i as u3, value }); }); this.events.listen(UiEvent.EditOn, () => { editor.enable(); diff --git a/src/ui/saveLoad.ts b/src/ui/saveLoad.ts new file mode 100644 index 0000000..d4baeca --- /dev/null +++ b/src/ui/saveLoad.ts @@ -0,0 +1,91 @@ +import { el } from "../etc"; +import { UiEventHandler, CpuEventHandler, CpuEvent, UiCpuSignalHandler, UiCpuSignal } from "../events"; +import { u2, u8, m256 } from "../num"; +import { UiComponent } from "./uiComponent"; + +export class SaveLoad implements UiComponent { + element: HTMLElement; + events: UiEventHandler; + save_button: HTMLButtonElement; + binary_upload: HTMLInputElement; + cpu_signals: UiCpuSignalHandler; + + constructor(element: HTMLElement, events: UiEventHandler, cpu_signals: UiCpuSignalHandler) { + this.element = element; + this.events = events; + this.cpu_signals = cpu_signals; + this.save_button = el("button", "save_button"); + this.binary_upload = el("input", "binary_upload"); + this.binary_upload.type = "file"; + this.binary_upload.name = "binary_upload"; + this.binary_upload.style.display = "none"; + const label = el("label"); + this.save_button.textContent = "Save"; + label.textContent = "Load Binary"; + label.classList.add("button"); + label.setAttribute("for", "binary_upload"); + + this.element.appendChild(this.binary_upload); + this.element.appendChild(label); + this.element.appendChild(this.save_button); + this.save_button.addEventListener("click", () => { + this.cpu_signals.dispatch(UiCpuSignal.RequestMemoryDump); + }); + this.binary_upload.addEventListener("change", (e) => { + this.upload_changed(e); + }); + } + + private upload_changed(e: Event): void { + const t = e.target; + if (t === null) { + return; + } + + const file: File | undefined = (t as HTMLInputElement).files?.[0]; + if (file === undefined) { + console.log("No files attribute on file input"); + return; + } + const reader = new FileReader(); + console.log(file); + reader.addEventListener("load", (e) => { + if (e.target !== null) { + const data = e.target.result; + if (data instanceof ArrayBuffer) { + const view = new Uint8Array(data); + const array = [...view] as Array; + this.cpu_signals.dispatch(UiCpuSignal.RequestCpuReset); + for (const [i, v] of array.entries()) { + const address = m256(i); + const bank = Math.floor(i / 256) as u2; + this.cpu_signals.dispatch(UiCpuSignal.RequestMemoryChange, { address, bank, value: v }); + } + } else { + console.log("not array"); + } + } + }); + reader.readAsArrayBuffer(file); + } + + // eslint-disable-next-line class-methods-use-this + init_cpu_events(e: CpuEventHandler): void { + e.listen(CpuEvent.MemoryDumped, ({ memory }) => { + const flattened = new Uint8Array(256 * memory.length); + for (let x = 0; x < 4; x++) { + for (let y = 0; y < 256; x++) { + flattened[256 * x + y] = memory[x][y]; + } + } + const blob = new Blob([flattened], { type: "application/octet-stream" }); + const url = URL.createObjectURL(blob); + + const link = document.createElement("a"); + link.href = url; + link.download = "bin.bin"; + link.click(); + link.remove(); + }); + } +} diff --git a/src/ui/uiComponent.ts b/src/ui/uiComponent.ts index d970ea2..add22e7 100644 --- a/src/ui/uiComponent.ts +++ b/src/ui/uiComponent.ts @@ -3,19 +3,28 @@ * @copyright Alexander Bass 2024 * @license GPL-3.0 */ -import { CpuEventHandler, UiEventHandler } from "../events"; +import { CpuEventHandler, UiCpuSignalHandler, UiEventHandler } from "../events"; + +// A UiComponent represents one DOM element and its contents. +// A UiComponent reacts to events to change its state, and creates events when it wants to communicate with other UiComponents, or with the CPU. +// These event/signal handlers are available to each UiComponent: +// - UiEventHandler: dispatch/listen to events created as a result of Ui actions +// - CpuEventHandler: listen to events created as a result of CPU actions +// - UiCpuEventSignaler: dispatch signals to request actions from the CPU export interface UiComponent { element: HTMLElement; /** Allows listening and emitting UiEvents*/ events: UiEventHandler; + /** Creating signals for the cpu to process */ + cpu_signals?: UiCpuSignalHandler; /** Completely reset the state of the component */ - reset: () => void; + reset?: () => void; soft_reset?: () => void; /** Allows listening CPUEvents*/ init_cpu_events?: (c: CpuEventHandler) => void; } export interface UiComponentConstructor { - new (el: HTMLElement, ue: UiEventHandler): UiComponent; + new (el: HTMLElement, ui_event_handler: UiEventHandler, cpu_signaler: UiCpuSignalHandler): UiComponent; } diff --git a/src/ui/windowBox.ts b/src/ui/windowBox.ts index 32415d6..5e0ce6e 100644 --- a/src/ui/windowBox.ts +++ b/src/ui/windowBox.ts @@ -1,8 +1,8 @@ import { el } from "../etc"; export abstract class WindowBox { element: HTMLElement; - readonly title: string; title_bar: HTMLElement; + readonly title: string; private resize: HTMLElement; private collapse_button: HTMLButtonElement; private collapsed: boolean = false; diff --git a/src/ui/windows/bankIndicator.ts b/src/ui/windows/bankIndicator.ts deleted file mode 100644 index 3109465..0000000 --- a/src/ui/windows/bankIndicator.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { UiEventHandler, CpuEventHandler, CpuEvent } from "../../events"; -import { u1, u2 } from "../../num"; -import { UiComponent } from "../uiComponent"; - -class BankIndicator implements UiComponent { - element: HTMLElement; - events: UiEventHandler; - constructor(element: HTMLElement, events: UiEventHandler) { - this.element = element; - this.events = events; - } - - reset(): void {} - - select_bank(bank_no: u2): void {} - - init_cpu_events(c: CpuEventHandler): void { - c.listen(CpuEvent.SwitchBank, ({ bank }) => { - this.select_bank(bank); - }); - } -} diff --git a/src/ui/windows/instructionExplainer.ts b/src/ui/windows/instructionExplainer.ts index a83d334..bd342aa 100644 --- a/src/ui/windows/instructionExplainer.ts +++ b/src/ui/windows/instructionExplainer.ts @@ -1,5 +1,5 @@ import { el, format_hex } from "../../etc"; -import { CpuEvent, CpuEventHandler, UiEventHandler } from "../../events"; +import { CpuEvent, CpuEventHandler, UiCpuSignalHandler, UiEventHandler } from "../../events"; import { Instruction, ParamType, ParameterType } from "../../instructionSet"; import { u8 } from "../../num"; import { WindowBox } from "../windowBox"; @@ -7,9 +7,11 @@ import { UiComponent } from "../uiComponent"; export class InstructionExplainer extends WindowBox implements UiComponent { events: UiEventHandler; - constructor(element: HTMLElement, e: UiEventHandler) { + cpu_signals: UiCpuSignalHandler; + constructor(element: HTMLElement, events: UiEventHandler, cpu_signals: UiCpuSignalHandler) { super(element, "Instruction Explainer"); - this.events = e; + this.cpu_signals = cpu_signals; + this.events = events; } add_instruction(instr: Instruction, pos: u8, byte: u8): void { this.reset(); diff --git a/src/ui/windows/printout.ts b/src/ui/windows/printout.ts index f24e0bc..2975cd8 100644 --- a/src/ui/windows/printout.ts +++ b/src/ui/windows/printout.ts @@ -1,13 +1,15 @@ import { el } from "../../etc"; -import { CpuEvent, CpuEventHandler, UiEventHandler } from "../../events"; +import { CpuEvent, CpuEventHandler, UiCpuSignalHandler, UiEventHandler } from "../../events"; import { WindowBox } from "../windowBox"; import { UiComponent } from "../uiComponent"; export class Printout extends WindowBox implements UiComponent { events: UiEventHandler; text_box: HTMLElement; - constructor(element: HTMLElement, events: UiEventHandler) { + cpu_signals: UiCpuSignalHandler; + constructor(element: HTMLElement, events: UiEventHandler, cpu_signals: UiCpuSignalHandler) { super(element, "Printout"); + this.cpu_signals = cpu_signals; this.events = events; this.text_box = el("div", "printout_text"); this.element.appendChild(this.text_box); diff --git a/src/ui/windows/screen.ts b/src/ui/windows/screen.ts index 058f7dc..f63da2e 100644 --- a/src/ui/windows/screen.ts +++ b/src/ui/windows/screen.ts @@ -22,6 +22,7 @@ export class Screen extends WindowBox implements UiComponent { } this.ctx = ctx; this.element.appendChild(this.screen); + this.test_pattern(); } private test_pattern(): void {