Add UiEvent's. Make events follow types

This commit is contained in:
Alexander Bass 2024-02-16 03:44:20 -05:00
parent caf86de9b9
commit e2d3bae806
10 changed files with 219 additions and 109 deletions

Binary file not shown.

View file

@ -20,6 +20,7 @@
</div> </div>
<div id="memory"></div> <div id="memory"></div>
</div> </div>
<div id="instruction_explainer"></div>
<div id="printout"></div> <div id="printout"></div>
</div> </div>
<div id="controls_bar"> <div id="controls_bar">

View file

@ -1,5 +1,5 @@
import { CpuEvent, MemoryCellType } from "./events"; import { CpuEvent, CpuEventHandler, MemoryCellType, UiEvent, UiEventHandler } from "./events";
import { u8 } from "./etc"; import { byte_array_to_js_source, format_hex, u8 } from "./etc";
import { EventHandler } from "./eventHandler"; import { EventHandler } from "./eventHandler";
import { ConstParam, Instruction, ISA, MemorParam, RegisParam } from "./instructionSet"; import { ConstParam, Instruction, ISA, MemorParam, RegisParam } from "./instructionSet";
@ -17,21 +17,13 @@ export type ComputerState = {
}; };
export class Computer { export class Computer {
private memory: Uint8Array; private memory = new Uint8Array(256);
private program_counter: u8; private registers = new Uint8Array(8);
private registers: Uint8Array; private program_counter: u8 = 0;
private current_instr: TempInstrState | null; private current_instr: TempInstrState | null = null;
events: EventHandler<CpuEvent>; events: CpuEventHandler = new EventHandler<CpuEvent>() as CpuEventHandler;
constructor() { constructor() {
// 256 bytes for both program and general purpose memory.
this.memory = new Uint8Array(256);
// 8 registers
this.registers = new Uint8Array(8);
this.program_counter = 0;
this.current_instr = null;
this.events = new EventHandler<CpuEvent>();
// Add events // Add events
for (const [, e_type] of Object.entries(CpuEvent)) { for (const [, e_type] of Object.entries(CpuEvent)) {
this.events.register_event(e_type as CpuEvent); this.events.register_event(e_type as CpuEvent);
@ -47,8 +39,9 @@ export class Computer {
this.events.dispatch(CpuEvent.MemoryByteParsed, { this.events.dispatch(CpuEvent.MemoryByteParsed, {
type: MemoryCellType.InvalidInstruction, type: MemoryCellType.InvalidInstruction,
pos: this.program_counter, pos: this.program_counter,
code: current_byte,
}); });
console.log(`Invalid instruction: ${current_byte.toString(16).toUpperCase().padStart(2, "0")}`); console.log(`Invalid instruction: ${format_hex(current_byte)}`);
this.step_forward(); this.step_forward();
return; return;
} }
@ -62,6 +55,7 @@ export class Computer {
type: MemoryCellType.Instruction, type: MemoryCellType.Instruction,
pos: this.program_counter, pos: this.program_counter,
instr: parsed_instruction, instr: parsed_instruction,
code: current_byte,
}); });
} }
@ -80,7 +74,15 @@ export class Computer {
} else if (b instanceof MemorParam) { } else if (b instanceof MemorParam) {
a = MemoryCellType.Memory; a = MemoryCellType.Memory;
} }
this.events.dispatch(CpuEvent.MemoryByteParsed, { type: a, pos: this.program_counter, param: b }); if (a === undefined) {
throw new Error("Shouldn't");
}
this.events.dispatch(CpuEvent.MemoryByteParsed, {
type: a,
pos: this.program_counter,
code: current_byte,
param: b,
});
this.current_instr.params[this.current_instr.params_found] = current_byte; this.current_instr.params[this.current_instr.params_found] = current_byte;
this.current_instr.params_found += 1; this.current_instr.params_found += 1;
if (this.current_instr.params.length !== this.current_instr.params_found) { if (this.current_instr.params.length !== this.current_instr.params_found) {
@ -93,10 +95,10 @@ export class Computer {
noStep: function (): void { noStep: function (): void {
this.should_step = false; this.should_step = false;
}, },
event: this.events.dispatch.bind(this.events), dispatch: this.events.dispatch.bind(this.events),
}; };
this.current_instr.instr.execute(this, this.current_instr.params, execution_post_action_state); this.current_instr.instr.execute(this, this.current_instr.params, execution_post_action_state);
this.events.dispatch(CpuEvent.InstructionExecuted, { instr: this.current_instr.instr });
this.current_instr = null; this.current_instr = null;
if (execution_post_action_state.should_step) { if (execution_post_action_state.should_step) {
@ -136,9 +138,20 @@ export class Computer {
this.events.dispatch(CpuEvent.Reset, null); this.events.dispatch(CpuEvent.Reset, null);
} }
init_events(ui: UiEventHandler): void {
ui.listen(UiEvent.RequestCpuCycle, () => {
this.cycle();
});
}
load_memory(program: Array<u8>): void { load_memory(program: Array<u8>): void {
console.log(byte_array_to_js_source(program));
const max_loop = Math.min(this.memory.length, program.length); const max_loop = Math.min(this.memory.length, program.length);
for (let i = 0; i < max_loop; i++) { for (let i = 0; i < max_loop; i++) {
// Don't fire event if no change is made
if (this.memory[i] === program[i]) {
continue;
}
this.memory[i] = program[i]; this.memory[i] = program[i];
this.events.dispatch(CpuEvent.MemoryChanged, { address: i, value: program[i] }); this.events.dispatch(CpuEvent.MemoryChanged, { address: i, value: program[i] });
} }

View file

@ -4,6 +4,17 @@ export type u8 = number;
// Jquery lite // Jquery lite
export const $ = (s: string): HTMLElement => document.getElementById(s) as HTMLElement; export const $ = (s: string): HTMLElement => document.getElementById(s) as HTMLElement;
export const format_hex = (n: number): string => n.toString(16).toUpperCase().padStart(2, "0");
export const byte_array_to_js_source = (a: Array<u8>): string => {
let str = "[";
for (const b of a) {
str += `0x${format_hex(b)},`;
}
str += "]";
return str;
};
export function el(type: string, id?: string): HTMLElement { export function el(type: string, id?: string): HTMLElement {
const element = document.createElement(type); const element = document.createElement(type);
if (id === undefined) { if (id === undefined) {

View file

@ -1,18 +1,16 @@
export class Event<T> { export class Event<T> {
identifier: T; identifier: T;
callbacks: Array<(event_data: unknown) => void>; callbacks: Array<(event_data: unknown) => void> = [];
constructor(identifier: T) { constructor(identifier: T) {
this.identifier = identifier; this.identifier = identifier;
this.callbacks = [];
} }
} }
export class EventHandler<T> { export class EventHandler<T> {
events: Array<Event<T>>; events: Array<Event<T>> = [];
private sealed: boolean; private sealed: boolean;
constructor() { constructor() {
this.sealed = false; this.sealed = false;
this.events = [];
} }
seal(): void { seal(): void {
@ -38,7 +36,7 @@ export class EventHandler<T> {
callback(event_data); callback(event_data);
} }
} }
add_listener(identifier: T, callback: (event_data: unknown) => void): void { listen(identifier: T, callback: (event_data: unknown) => void): void {
if (!this.sealed) throw new Error("Event handler must be sealed before adding listener"); if (!this.sealed) throw new Error("Event handler must be sealed before adding listener");
const event = this.events.find((e) => e.identifier === identifier); const event = this.events.find((e) => e.identifier === identifier);
if (event === undefined) { if (event === undefined) {

View file

@ -1,8 +1,13 @@
import { u8 } from "./etc";
import { EventHandler } from "./eventHandler";
import { Instruction, ParameterType } from "./instructionSet";
export enum CpuEvent { export enum CpuEvent {
MemoryChanged, MemoryChanged,
RegisterChanged, RegisterChanged,
MemoryByteParsed,
ProgramCounterChanged, ProgramCounterChanged,
MemoryByteParsed,
InstructionExecuted,
Print, Print,
Reset, Reset,
} }
@ -14,3 +19,32 @@ export enum MemoryCellType {
Memory, Memory,
Constant, Constant,
} }
// Handily explained in https://www.cgjennings.ca/articles/typescript-events/
interface CpuEventMap {
[CpuEvent.MemoryChanged]: { address: u8; value: u8 };
[CpuEvent.RegisterChanged]: { register_no: u8; value: u8 };
[CpuEvent.ProgramCounterChanged]: { counter: u8 };
[CpuEvent.Reset]: null;
[CpuEvent.MemoryByteParsed]: { type: MemoryCellType; pos: u8; code: u8; param?: ParameterType; instr?: Instruction };
[CpuEvent.InstructionExecuted]: { instr: Instruction };
[CpuEvent.Print]: string;
}
export interface CpuEventHandler extends EventHandler<CpuEvent> {
listen<E extends keyof CpuEventMap>(type: E, listener: (ev: CpuEventMap[E]) => void): void;
dispatch<E extends keyof CpuEventMap>(type: E, data: CpuEventMap[E]): void;
}
export enum UiEvent {
RequestCpuCycle,
}
interface UiEventMap {
[UiEvent.RequestCpuCycle]: null;
}
export interface UiEventHandler extends EventHandler<UiEvent> {
listen<E extends keyof UiEventMap>(type: E, listener: (ev: UiEventMap[E]) => void): void;
dispatch<E extends keyof UiEventMap>(type: E, data: UiEventMap[E]): void;
}

View file

@ -5,8 +5,22 @@ import { generate_isa } from "./isaGenerator";
import { UI } from "./ui"; import { UI } from "./ui";
function main(): void { function main(): void {
// const program = [0x2f, 0x01, 0x01, 0x40, 0x00, 0x01, 0x21, 0x00, 0x02, 0x10, 0x00]; const program = [
const program = [0x2f, 0x00, 0x41, 0xfe, 0x00, 0x30, 0x00, 0x10, 0x03]; 0x2f, 0x00, 0xf0, 0x20, 0x07, 0x00, 0x50, 0x05, 0x06, 0x07, 0x11, 0x00, 0x05, 0xfe, 0x07, 0x30, 0x00, 0x10, 0x03,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x57,
0x6f, 0x72, 0x6c, 0x64, 0x21, 0x0a, 0x00, 0x00, 0x00,
];
const container = $("container"); const container = $("container");
if (container === null) { if (container === null) {
@ -14,9 +28,10 @@ function main(): void {
} }
const computer = new Computer(); const computer = new Computer();
const ui = new UI(container, computer.events); const ui = new UI(container);
ui.init_events(computer.events);
computer.load_memory(program); computer.load_memory(program);
ui.set_step_func(computer.cycle.bind(computer)); computer.init_events(ui.events);
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
(<any>window).comp = computer; (<any>window).comp = computer;

View file

@ -1,10 +1,10 @@
import { CpuEvent } from "./events"; import { CpuEvent, CpuEventHandler } from "./events";
import { u8 } from "./etc"; import { format_hex, u8 } from "./etc";
class ParameterType { export class ParameterType {
readonly description: string; readonly desc: string;
constructor(description: string) { constructor(description: string) {
this.description = description; this.desc = description;
} }
} }
@ -24,7 +24,7 @@ interface GenericComputer {
interface AfterExecutionComputerAction { interface AfterExecutionComputerAction {
// Does not step forward the program counter // Does not step forward the program counter
noStep: () => void; noStep: () => void;
event: (e: CpuEvent, data: unknown) => void; dispatch: CpuEventHandler["dispatch"];
} }
export interface Instruction { export interface Instruction {
@ -43,7 +43,7 @@ export class InstructionSet {
insertInstruction(hexCode: u8, instruction: Instruction): void { insertInstruction(hexCode: u8, instruction: Instruction): void {
if (this.instructions.has(hexCode)) { if (this.instructions.has(hexCode)) {
throw new Error(`Instruction "${hexCode.toString(16)}" already exists`); throw new Error(`Instruction "${format_hex(hexCode)}" already exists`);
} }
this.instructions.set(hexCode, instruction); this.instructions.set(hexCode, instruction);
} }
@ -75,17 +75,17 @@ ISA.insertInstruction(0x10, {
ISA.insertInstruction(0x20, { ISA.insertInstruction(0x20, {
name: "LoadToRegister", name: "LoadToRegister",
desc: "Sets the byte in register (P1) to be the contents of memory cell (P2)", desc: "Sets the byte in register (P1) to be the contents of memory cell at address in register (P2)",
params: [new RegisParam(""), new MemorParam("")], params: [new RegisParam("Set this register to"), new MemorParam("the byte held in this memory address")],
execute(c, p) { execute(c, p) {
const [register_no, mem_address] = p; const [register_no, mem_address] = p;
c.setRegister(register_no, c.getMemory(mem_address)); c.setRegister(register_no, c.getMemory(c.getRegister(mem_address)));
}, },
}); });
ISA.insertInstruction(0x21, { ISA.insertInstruction(0x21, {
name: "SaveToMemory", name: "SaveToMemory",
desc: "Writes the byte in register (P1) to the processing memory location (P2)", desc: "Writes the byte in register (P1) to the memory cell (P2)",
params: [new RegisParam(""), new MemorParam("")], params: [new RegisParam(""), new MemorParam("")],
execute(c, p) { execute(c, p) {
const [register_no, mem_address] = p; const [register_no, mem_address] = p;
@ -96,7 +96,7 @@ ISA.insertInstruction(0x21, {
ISA.insertInstruction(0x2f, { ISA.insertInstruction(0x2f, {
name: "AssignRegister", name: "AssignRegister",
desc: "Assigns constant value (P2) to register (P1)", desc: "Assigns constant value (P2) to register (P1)",
params: [new RegisParam(""), new ConstParam("")], params: [new RegisParam("Set this register"), new ConstParam("to this constant")],
execute(c, p) { execute(c, p) {
const [register_no, value] = p; const [register_no, value] = p;
c.setRegister(register_no, value); c.setRegister(register_no, value);
@ -104,9 +104,9 @@ ISA.insertInstruction(0x2f, {
}); });
ISA.insertInstruction(0x11, { ISA.insertInstruction(0x11, {
name: "GotoIfLowBit", name: "GotoIfLowBitHigh",
desc: "Moves the CPU instruction counter to the value in (P1) if the value in register (P2) has the lowest bit true", desc: "Moves the CPU instruction counter to the value in (P1) if the value in register (P2) has the lowest bit true",
params: [new ConstParam("new instruction counter location"), new RegisParam("Register to check")], params: [new ConstParam("Set program counter to this constant"), new RegisParam("if this register's 1 bit is set")],
execute(c, p, a) { execute(c, p, a) {
const [new_address, check_register_no] = p; const [new_address, check_register_no] = p;
if (c.getRegister(check_register_no) % 2 === 1) { if (c.getRegister(check_register_no) % 2 === 1) {
@ -150,24 +150,28 @@ ISA.insertInstruction(0x40, {
ISA.insertInstruction(0x50, { ISA.insertInstruction(0x50, {
name: "Equals", name: "Equals",
desc: "If byte in register (P1) equals byte in register (P2), set byte in register (P3) to 0x01", desc: "If byte in register (P2) equals byte in register (P3), set byte in register (P1) to 0x01",
params: [new RegisParam(""), new RegisParam(""), new RegisParam("")], params: [
new RegisParam("Set this register to be 0x01"),
new RegisParam("if this register and"),
new RegisParam("this register are equal (else 0x00)"),
],
execute(c, p) { execute(c, p) {
const [register_no_1, register_no_2, register_no_3] = p; const [register_no_1, register_no_2, register_no_3] = p;
const truth = c.getRegister(register_no_1) === c.getRegister(register_no_2) ? 0x01 : 0x00; const truth = c.getRegister(register_no_2) === c.getRegister(register_no_3) ? 0x01 : 0x00;
c.setRegister(register_no_3, truth); c.setRegister(register_no_1, truth);
}, },
}); });
ISA.insertInstruction(0xfe, { ISA.insertInstruction(0xfe, {
name: "PrintASCII", name: "PrintASCII",
desc: "Prints the ASCII byte in register (P1) to console", desc: "Prints the ASCII byte in register (P1) to console",
params: [new RegisParam("")], params: [new RegisParam("Register to print from")],
execute(c, p, a) { execute(c, p, a) {
const register_no = p[0]; const register_no = p[0];
const asciiByte = c.getRegister(register_no); const asciiByte = c.getRegister(register_no);
const char = String.fromCharCode(asciiByte); const char = String.fromCharCode(asciiByte);
a.event(CpuEvent.Print, { data: char }); a.dispatch(CpuEvent.Print, char);
}, },
}); });

View file

@ -1,4 +1,4 @@
import { u8 } from "./etc"; import { format_hex, u8 } from "./etc";
import { Instruction, InstructionSet } from "./instructionSet"; import { Instruction, InstructionSet } from "./instructionSet";
export function generate_isa(iset: InstructionSet): string { export function generate_isa(iset: InstructionSet): string {
@ -13,7 +13,7 @@ export function generate_isa(iset: InstructionSet): string {
max_instr_name_len = Math.max(max_instr_name_len, short_description.length); max_instr_name_len = Math.max(max_instr_name_len, short_description.length);
} }
for (const instruction of instructions) { for (const instruction of instructions) {
const hex_code = instruction[0].toString(16).toUpperCase().padStart(2, "0"); const hex_code = format_hex(instruction[0]);
const short_description = instruction[1].name.padEnd(max_instr_name_len, " "); const short_description = instruction[1].name.padEnd(max_instr_name_len, " ");
const parameter_count = instruction[1].params.length; const parameter_count = instruction[1].params.length;
const description = instruction[1].desc; const description = instruction[1].desc;

150
src/ui.ts
View file

@ -1,24 +1,33 @@
import { ComputerState } from "./computer"; import { ComputerState } from "./computer";
import { CpuEvent, MemoryCellType } from "./events"; import { CpuEvent, CpuEventHandler, MemoryCellType, UiEvent, UiEventHandler } from "./events";
import { $, el, u8 } from "./etc"; import { $, el, format_hex, u8 } from "./etc";
import { Instruction, ParameterType } from "./instructionSet";
import { EventHandler } from "./eventHandler"; import { EventHandler } from "./eventHandler";
export class UI { export class UI {
container: HTMLElement; container: HTMLElement;
program_memory: HTMLElement; program_memory: HTMLElement;
program_memory_cells: Array<HTMLElement>; program_memory_cells: Array<HTMLElement> = [];
registers: HTMLElement; registers: HTMLElement;
register_cells: Array<HTMLElement>; printout: HTMLElement;
step_func: null | (() => void); instruction_explainer: HTMLElement;
register_cells: Array<HTMLElement> = [];
program_counter: u8; instruction_parsing_addresses: Array<u8> = [];
program_counter: u8 = 0;
auto_running: boolean; auto_running: boolean;
constructor(parent: HTMLElement, cpu_events: EventHandler<CpuEvent>) {
events: UiEventHandler = new EventHandler<UiEvent>() as UiEventHandler;
constructor(parent: HTMLElement) {
for (const [, e_type] of Object.entries(UiEvent)) {
this.events.register_event(e_type as UiEvent);
}
this.events.seal();
this.container = parent; this.container = parent;
this.program_counter = 0; this.printout = $("printout");
this.instruction_explainer = $("instruction_explainer");
const program_mem = $("memory"); const program_mem = $("memory");
this.program_memory_cells = [];
for (let i = 0; i < 256; i++) { for (let i = 0; i < 256; i++) {
const mem_cell = el("div", `p_${i}`); const mem_cell = el("div", `p_${i}`);
mem_cell.textContent = "00"; mem_cell.textContent = "00";
@ -28,10 +37,9 @@ export class UI {
this.program_memory_cells[0].classList.add("div", "program_counter"); this.program_memory_cells[0].classList.add("div", "program_counter");
this.program_memory = program_mem; this.program_memory = program_mem;
this.register_cells = [];
const registers = $("registers"); const registers = $("registers");
for (let i = 0; i < 8; i++) { for (let i = 0; i < 8; i++) {
const reg_cell = el("div", `R_${i}`); const reg_cell = el("div", `r_${i}`);
reg_cell.textContent = "00"; reg_cell.textContent = "00";
registers.appendChild(reg_cell); registers.appendChild(reg_cell);
this.register_cells.push(reg_cell); this.register_cells.push(reg_cell);
@ -39,7 +47,6 @@ export class UI {
this.registers = registers; this.registers = registers;
this.step_func = null;
this.auto_running = false; this.auto_running = false;
const pp_button = $("pause_play_button"); const pp_button = $("pause_play_button");
if (pp_button === null) { if (pp_button === null) {
@ -54,48 +61,32 @@ export class UI {
pp_button.textContent = "Storp"; pp_button.textContent = "Storp";
} }
}); });
document.getElementById("step_button")?.addEventListener("click", () => { $("step_button")?.addEventListener("click", () => {
if (this.auto_running) { if (this.auto_running) {
this.stop_auto(); this.stop_auto();
} }
if (this.step_func === null) {
return;
}
this.step_func();
});
cpu_events.add_listener(CpuEvent.MemoryChanged, (e) => { this.events.dispatch(UiEvent.RequestCpuCycle, null);
const { address, value } = e as { address: u8; value: u8 };
this.program_memory_cells[address].textContent = value.toString(16).toUpperCase().padStart(2, "0");
}); });
cpu_events.add_listener(CpuEvent.RegisterChanged, (e) => { }
const { register_no, value } = e as { register_no: u8; value: u8 };
this.register_cells[register_no].textContent = value.toString(16).toUpperCase().padStart(2, "0"); init_events(cpu_events: CpuEventHandler): void {
cpu_events.listen(CpuEvent.MemoryChanged, ({ address, value }) => {
this.program_memory_cells[address].textContent = format_hex(value);
}); });
cpu_events.add_listener(CpuEvent.ProgramCounterChanged, (e) => { cpu_events.listen(CpuEvent.RegisterChanged, ({ register_no, value }) => {
const { counter } = e as { counter: u8 }; this.register_cells[register_no].textContent = format_hex(value);
});
cpu_events.listen(CpuEvent.ProgramCounterChanged, ({ counter }) => {
this.program_memory_cells[this.program_counter].classList.remove("program_counter"); this.program_memory_cells[this.program_counter].classList.remove("program_counter");
this.program_memory_cells[counter].classList.add("program_counter"); this.program_memory_cells[counter].classList.add("program_counter");
this.program_counter = counter; this.program_counter = counter;
}); });
cpu_events.add_listener(CpuEvent.Print, (e) => { cpu_events.listen(CpuEvent.Print, (char) => {
const { data } = e as { data: u8 }; this.printout.textContent = (this.printout.textContent ?? "") + char;
const printout = $("printout");
if (printout === null) {
throw new Error("Couldn't get printout");
}
printout.textContent = (printout.textContent ?? "") + data;
}); });
cpu_events.add_listener(CpuEvent.Reset, () => { cpu_events.listen(CpuEvent.Reset, () => {
this.stop_auto(); this.reset();
this.program_memory_cells.forEach((c) => {
c.className = "";
c.textContent = "00";
});
this.register_cells.forEach((r) => {
r.textContent = "00";
});
this.program_counter = 0;
}); });
const map: Map<MemoryCellType, string> = new Map(); const map: Map<MemoryCellType, string> = new Map();
@ -105,8 +96,8 @@ export class UI {
map.set(MemoryCellType.InvalidInstruction, "invalid_instruction"); map.set(MemoryCellType.InvalidInstruction, "invalid_instruction");
map.set(MemoryCellType.Memory, "memory"); map.set(MemoryCellType.Memory, "memory");
map.set(MemoryCellType.Register, "register"); map.set(MemoryCellType.Register, "register");
cpu_events.add_listener(CpuEvent.MemoryByteParsed, (e) => { cpu_events.listen(CpuEvent.MemoryByteParsed, (e) => {
const { type, pos } = e as { type: MemoryCellType; pos: u8 }; const { type, pos, code } = e;
const css_class = map.get(type); const css_class = map.get(type);
if (css_class === undefined) { if (css_class === undefined) {
throw new Error("Something went wrong"); throw new Error("Something went wrong");
@ -115,27 +106,74 @@ export class UI {
if (other_class === css_class) continue; if (other_class === css_class) continue;
this.program_memory_cells[pos].classList.remove(other_class); this.program_memory_cells[pos].classList.remove(other_class);
} }
if (type === MemoryCellType.Instruction) {
while (this.instruction_parsing_addresses.length > 0) {
const num = this.instruction_parsing_addresses.pop();
if (num === undefined) {
throw new Error("Shouldn't happen");
}
this.program_memory_cells[num].classList.remove("instruction_argument");
this.program_memory_cells[num].classList.remove("current_instruction");
}
this.instruction_explainer.innerHTML = "";
const { instr } = e as { instr: Instruction };
this.program_memory_cells[pos].classList.add("current_instruction");
this.instruction_parsing_addresses.push(pos);
const instr_box = el("div", "expl_box");
const instr_icon = el("span", "expl_icon");
instr_icon.classList.add(css_class);
instr_icon.setAttribute("title", css_class.toUpperCase());
instr_icon.textContent = format_hex(code);
const instr_box_text = el("span", "expl_text");
instr_box_text.textContent = `${instr.name}`;
instr_box.appendChild(instr_icon);
instr_box.appendChild(instr_box_text);
this.instruction_explainer.appendChild(instr_box);
} else if (type !== MemoryCellType.InvalidInstruction) {
const { param } = e as { param: ParameterType };
this.program_memory_cells[pos].classList.add("instruction_argument");
this.instruction_parsing_addresses.push(pos);
const instr_box = el("div", "expl_box");
const instr_icon = el("span", "expl_icon");
instr_icon.classList.add(css_class);
instr_icon.setAttribute("title", css_class.toUpperCase());
instr_icon.textContent = format_hex(code);
const instr_box_text = el("span", "expl_text");
instr_box_text.textContent = `${param.desc}`;
instr_box.appendChild(instr_icon);
instr_box.appendChild(instr_box_text);
this.instruction_explainer.appendChild(instr_box);
}
this.program_memory_cells[pos].classList.add(css_class); this.program_memory_cells[pos].classList.add(css_class);
}); });
cpu_events.listen(CpuEvent.InstructionExecuted, ({ instr }) => {});
}
reset(): void {
this.stop_auto();
this.program_memory_cells.forEach((c) => {
c.className = "";
c.textContent = "00";
});
this.register_cells.forEach((r) => {
r.textContent = "00";
});
this.program_counter = 0;
this.program_memory_cells[0].classList.add("program_counter");
this.printout.textContent = "";
} }
start_auto(speed: number = 200): void { start_auto(speed: number = 200): void {
if (this.step_func === null) {
return;
}
if (this.auto_running) { if (this.auto_running) {
return; return;
} }
this.auto_running = true; this.auto_running = true;
const loop = (): void => { const loop = (): void => {
if (this.step_func === null) {
this.auto_running = false;
return;
}
if (this.auto_running === false) { if (this.auto_running === false) {
return; return;
} }
this.step_func(); this.events.dispatch(UiEvent.RequestCpuCycle, null);
setTimeout(loop, speed); setTimeout(loop, speed);
// requestAnimationFrame(loop); // requestAnimationFrame(loop);
}; };
@ -146,10 +184,6 @@ export class UI {
this.auto_running = false; this.auto_running = false;
} }
set_step_func(f: () => void): void {
this.step_func = f;
}
state_update_event(state: ComputerState): void { state_update_event(state: ComputerState): void {
const current_instr = state.current_instruction; const current_instr = state.current_instruction;
if (current_instr !== null) { if (current_instr !== null) {