split out UI -> CPU signals
This commit is contained in:
parent
1f6a95c253
commit
49937af24e
|
@ -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 { byte_array_to_js_source, format_hex } from "./etc";
|
||||||
import { Instruction, ISA } from "./instructionSet";
|
import { Instruction, ISA } from "./instructionSet";
|
||||||
import { m256, u2, u3, u8 } from "./num";
|
import { m256, u2, u3, u8 } from "./num";
|
||||||
|
@ -28,14 +28,6 @@ export class Computer {
|
||||||
private current_instr: TempInstrState | null = null;
|
private current_instr: TempInstrState | null = null;
|
||||||
events: CpuEventHandler = new CpuEventHandler();
|
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 {
|
cycle(): void {
|
||||||
const current_byte = this.getMemorySilent(this.program_counter, 0);
|
const current_byte = this.getMemorySilent(this.program_counter, 0);
|
||||||
|
|
||||||
|
@ -102,6 +94,7 @@ export class Computer {
|
||||||
}
|
}
|
||||||
this.events.dispatch(CpuEvent.Cycle);
|
this.events.dispatch(CpuEvent.Cycle);
|
||||||
}
|
}
|
||||||
|
|
||||||
private getMemorySilent(address: u8, bank_override?: u2): u8 {
|
private getMemorySilent(address: u8, bank_override?: u2): u8 {
|
||||||
const bank = this.banks[bank_override ?? this.bank];
|
const bank = this.banks[bank_override ?? this.bank];
|
||||||
const value = bank[address] as u8;
|
const value = bank[address] as u8;
|
||||||
|
@ -116,8 +109,8 @@ export class Computer {
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
setMemory(address: u8, value: u8): void {
|
setMemory(address: u8, value: u8, bank?: u2): void {
|
||||||
this.banks[this.bank][address] = value;
|
this.banks[bank ?? this.bank][address] = value;
|
||||||
this.events.dispatch(CpuEvent.MemoryChanged, { address, bank: this.bank, value });
|
this.events.dispatch(CpuEvent.MemoryChanged, { address, bank: this.bank, value });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -173,12 +166,16 @@ export class Computer {
|
||||||
this.carry_flag = false;
|
this.carry_flag = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
init_events(ui: UiEventHandler): void {
|
init_events(ui: UiCpuSignalHandler): void {
|
||||||
ui.listen(UiEvent.RequestCpuCycle, (cycle_count) => {
|
ui.listen(UiCpuSignal.RequestCpuCycle, (cycle_count) => {
|
||||||
for (let i = 0; i < cycle_count; i++) this.cycle();
|
for (let i = 0; i < cycle_count; i++) this.cycle();
|
||||||
});
|
});
|
||||||
ui.listen(UiEvent.RequestMemoryChange, ({ address, value }) => this.setMemory(address, value));
|
ui.listen(UiCpuSignal.RequestMemoryChange, ({ address, bank, value }) => this.setMemory(address, value, bank));
|
||||||
ui.listen(UiEvent.RequestRegisterChange, ({ register_no, value }) => this.setRegister(register_no, value));
|
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<u8>): void {
|
load_memory(program: Array<u8>): void {
|
||||||
|
@ -195,8 +192,8 @@ export class Computer {
|
||||||
this.program_counter = 0;
|
this.program_counter = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
dump_memory(): Uint8Array {
|
dump_memory(): [Uint8Array, Uint8Array, Uint8Array, Uint8Array] {
|
||||||
return this.banks[0];
|
return this.banks;
|
||||||
}
|
}
|
||||||
|
|
||||||
private step_forward(): void {
|
private step_forward(): void {
|
||||||
|
|
|
@ -14,29 +14,13 @@ export class Event<T> {
|
||||||
|
|
||||||
export class EventHandler<T> {
|
export class EventHandler<T> {
|
||||||
events: Array<Event<T>> = [];
|
events: Array<Event<T>> = [];
|
||||||
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<T>(identifier);
|
|
||||||
this.events.push(event);
|
|
||||||
}
|
|
||||||
dispatch(identifier: T, event_data?: unknown): void {
|
dispatch(identifier: T, event_data?: unknown): void {
|
||||||
const event = this.events.find((e) => e.identifier === identifier);
|
const event = this.events.find((e) => e.identifier === identifier);
|
||||||
if (event === undefined) {
|
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) {
|
for (const callback of event.callbacks) {
|
||||||
callback(event_data);
|
callback(event_data);
|
||||||
|
@ -54,10 +38,13 @@ export class EventHandler<T> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
listen(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");
|
let event = this.events.find((e) => e.identifier === identifier);
|
||||||
const event = this.events.find((e) => e.identifier === identifier);
|
|
||||||
if (event === undefined) {
|
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);
|
event.callbacks.push(callback);
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,16 +22,13 @@ export enum CpuEvent {
|
||||||
Print,
|
Print,
|
||||||
Reset,
|
Reset,
|
||||||
Halt,
|
Halt,
|
||||||
// ClockStarted,
|
MemoryDumped,
|
||||||
// ClockStopped,
|
|
||||||
MemoryAccessed,
|
MemoryAccessed,
|
||||||
SwitchBank,
|
SwitchBank,
|
||||||
SetFlagCarry,
|
SetFlagCarry,
|
||||||
}
|
}
|
||||||
|
|
||||||
type VoidDataCpuEventList = CpuEvent.Halt | CpuEvent.Reset | CpuEvent.Cycle;
|
type VoidDataCpuEventList = CpuEvent.Halt | CpuEvent.Reset | CpuEvent.Cycle;
|
||||||
// | CpuEvent.ClockStarted
|
|
||||||
// | CpuEvent.ClockStopped;
|
|
||||||
|
|
||||||
interface CpuEventMap {
|
interface CpuEventMap {
|
||||||
[CpuEvent.MemoryChanged]: { address: u8; bank: u2; value: u8 };
|
[CpuEvent.MemoryChanged]: { address: u8; bank: u2; value: u8 };
|
||||||
|
@ -45,6 +42,7 @@ interface CpuEventMap {
|
||||||
[CpuEvent.SwitchBank]: { bank: u2 };
|
[CpuEvent.SwitchBank]: { bank: u2 };
|
||||||
[CpuEvent.Print]: string;
|
[CpuEvent.Print]: string;
|
||||||
[CpuEvent.SetFlagCarry]: boolean;
|
[CpuEvent.SetFlagCarry]: boolean;
|
||||||
|
[CpuEvent.MemoryDumped]: { memory: [Uint8Array, Uint8Array, Uint8Array, Uint8Array] };
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CpuEventHandler extends EventHandler<CpuEvent> {
|
export interface CpuEventHandler extends EventHandler<CpuEvent> {
|
||||||
|
@ -60,30 +58,51 @@ interface CpuEventHandlerConstructor {
|
||||||
|
|
||||||
export const CpuEventHandler = EventHandler<CpuEvent> as CpuEventHandlerConstructor;
|
export const CpuEventHandler = EventHandler<CpuEvent> 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<UiCpuSignal> {
|
||||||
|
listen<E extends VoidDataUiCpuSignalList>(type: E, listener: () => void): void;
|
||||||
|
dispatch<E extends VoidDataUiCpuSignalList>(type: E): void;
|
||||||
|
listen<E extends keyof UiCpuSignalMap>(type: E, listener: (ev: UiCpuSignalMap[E]) => void): void;
|
||||||
|
dispatch<E extends keyof UiCpuSignalMap>(type: E, data: UiCpuSignalMap[E]): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UICpuSignalHandlerConstructor {
|
||||||
|
new (): UiCpuSignalHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const UiCpuSignalHandler = EventHandler<UiCpuSignal> as UICpuSignalHandlerConstructor;
|
||||||
|
|
||||||
//
|
//
|
||||||
// Ui Event Handler Definition
|
// Ui Event Handler Definition
|
||||||
//
|
//
|
||||||
|
|
||||||
export enum UiEvent {
|
export enum UiEvent {
|
||||||
// Maybe move these into a UI -> CPU signal system?
|
|
||||||
RequestCpuCycle,
|
|
||||||
RequestMemoryChange,
|
|
||||||
RequestRegisterChange,
|
|
||||||
// Ui Events
|
|
||||||
EditOn,
|
EditOn,
|
||||||
EditOff,
|
EditOff,
|
||||||
ConsoleOn,
|
ChangeViewBank,
|
||||||
ConsoleOff,
|
|
||||||
ExplainerOn,
|
|
||||||
ExplainerOff,
|
|
||||||
VideoOn,
|
|
||||||
VideoOff,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface UiEventMap {
|
interface UiEventMap {
|
||||||
[UiEvent.RequestCpuCycle]: number;
|
[UiEvent.ChangeViewBank]: { bank: u2 };
|
||||||
[UiEvent.RequestMemoryChange]: { address: u8; value: u8 };
|
|
||||||
[UiEvent.RequestRegisterChange]: { register_no: u3; value: u8 };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type VoidDataUiEventList = UiEvent.EditOn | UiEvent.EditOff;
|
type VoidDataUiEventList = UiEvent.EditOn | UiEvent.EditOff;
|
||||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 4.3 KiB |
|
@ -7,25 +7,46 @@
|
||||||
<title>Virtual 8-Bit Computer</title>
|
<title>Virtual 8-Bit Computer</title>
|
||||||
</head>
|
</head>
|
||||||
<body id="root">
|
<body id="root">
|
||||||
|
<noscript>
|
||||||
|
This computer requires JavaScript. Your browser either doesn't support it, or you have it disabled.
|
||||||
|
</noscript>
|
||||||
<main>
|
<main>
|
||||||
<div id="grid">
|
<div id="grid">
|
||||||
<div id="title">VIRTUAL 8-BIT COMPUTER</div>
|
<div id="title">VIRTUAL 8-BIT COMPUTER</div>
|
||||||
<div id="registers"></div>
|
<div id="registers"></div>
|
||||||
<div id="labelcontainer">
|
<div id="labelcontainer">
|
||||||
<div id="registers_label">←REGISTERS</div>
|
<div id="registers_label">↯REGISTERS</div>
|
||||||
<div id="memory_label">MEMORY↯</div>
|
<div id="memory_label">MEMORY↯</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="memory"></div>
|
<div id="memory"></div>
|
||||||
|
|
||||||
<div id="controls_bar">
|
<div id="controls_bar">
|
||||||
<span id="controls_buttons"></span>
|
<span id="controls_buttons"></span>
|
||||||
<label for="binary_upload" class="button">Load Binary</label>
|
<span id="save_load_buttons"></span>
|
||||||
<input id="binary_upload" name="binary_upload" id="binary_upload" style="display: none" type="file" />
|
|
||||||
<button type="button" id="save_button">Save</button>
|
|
||||||
<button type="button" id="edit_button"></button>
|
<button type="button" id="edit_button"></button>
|
||||||
</div>
|
</div>
|
||||||
<span id="cycles"></span>
|
<span id="cycles"></span>
|
||||||
|
<div id="memory_bank_view">
|
||||||
|
<div id="bank_boxes">
|
||||||
|
<button class="nostyle">1</button>
|
||||||
|
<button class="nostyle selected">2</button>
|
||||||
|
<button class="nostyle">3</button>
|
||||||
|
<button class="nostyle">4</button>
|
||||||
</div>
|
</div>
|
||||||
|
<script>
|
||||||
|
const d = document.getElementById("bank_boxes");
|
||||||
|
const a = [...d.children];
|
||||||
|
for (const b of a) {
|
||||||
|
b.addEventListener("click", () => {
|
||||||
|
a.forEach((ab) => ab.classList.remove("selected"));
|
||||||
|
b.classList.add("selected");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
console.log(a);
|
||||||
|
</script>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div id="window_box">
|
<div id="window_box">
|
||||||
<div id="instruction_explainer"></div>
|
<div id="instruction_explainer"></div>
|
||||||
<div id="printout"></div>
|
<div id="printout"></div>
|
||||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 4.4 KiB |
Binary file not shown.
Before Width: | Height: | Size: 4.4 KiB |
41
src/index.ts
41
src/index.ts
|
@ -43,40 +43,12 @@ function main(): void {
|
||||||
const ui = new UI();
|
const ui = new UI();
|
||||||
ui.init_events(computer.events);
|
ui.init_events(computer.events);
|
||||||
computer.load_memory(program);
|
computer.load_memory(program);
|
||||||
computer.init_events(ui.events);
|
computer.init_events(ui.cpu_signaler);
|
||||||
window.comp = computer;
|
window.comp = computer;
|
||||||
window.ui = ui;
|
window.ui = ui;
|
||||||
|
|
||||||
$("ISA").textContent = generate_isa(ISA);
|
$("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<u8>;
|
|
||||||
computer.reset();
|
|
||||||
computer.load_memory(array);
|
|
||||||
} else {
|
|
||||||
console.log("not array");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
reader.readAsArrayBuffer(file);
|
|
||||||
});
|
|
||||||
let fire = false;
|
let fire = false;
|
||||||
window.firehose = (): void => {
|
window.firehose = (): void => {
|
||||||
if (fire === false) {
|
if (fire === false) {
|
||||||
|
@ -88,17 +60,6 @@ function main(): void {
|
||||||
console.error("Firehose already started");
|
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", () => {
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
|
|
24
src/num.ts
24
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 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;
|
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;
|
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 {
|
export function isU2(n: number): n is u2 {
|
||||||
if (n < 4 && n >= 0) {
|
if (n < 4 && n >= 0) {
|
||||||
return true;
|
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).
|
* Determines whether a number is a u3 type (unsigned 3-bit integer).
|
||||||
* Does not check for non integers
|
* Does not check for non integers
|
||||||
*
|
*
|
||||||
* @param n - Input number to be checked
|
* @param n Input number to be checked
|
||||||
* @returns true or false
|
* @returns true or false
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
export function isU3(n: number): n is u3 {
|
export function isU3(n: number): n is u3 {
|
||||||
if (n < 8 && n >= 0) {
|
if (n < 8 && n >= 0) {
|
||||||
|
@ -51,13 +63,13 @@ export function isU3(n: number): n is u3 {
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determines whether a number is a u4 type (unsigned 4-bit integer).
|
* Determines whether a number is a u4 type (unsigned 4-bit integer).
|
||||||
* Does not check for non integers
|
* Does not check for non integers
|
||||||
*
|
*
|
||||||
* @param n - Input number to be checked
|
* @param n Input number to be checked
|
||||||
* @returns true or false
|
* @returns true or false
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
export function isU4(n: number): n is u4 {
|
export function isU4(n: number): n is u4 {
|
||||||
if (n < 16 && n >= 0) {
|
if (n < 16 && n >= 0) {
|
||||||
|
@ -65,13 +77,13 @@ export function isU4(n: number): n is u4 {
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determines whether a number is a u8 type (unsigned 8-bit integer).
|
* Determines whether a number is a u8 type (unsigned 8-bit integer).
|
||||||
* Does not check for non integers
|
* Does not check for non integers
|
||||||
*
|
*
|
||||||
* @param n - Input number to be checked
|
* @param n Input number to be checked
|
||||||
* @returns true or false
|
* @returns true or false
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
export function isU8(n: number): n is u8 {
|
export function isU8(n: number): n is u8 {
|
||||||
if (n < 256 && n >= 0) {
|
if (n < 256 && n >= 0) {
|
||||||
|
|
|
@ -38,17 +38,6 @@ label.button:hover {
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
#controls_bar {
|
|
||||||
grid-area: buttons;
|
|
||||||
|
|
||||||
display: flex;
|
|
||||||
gap: 10px;
|
|
||||||
#controls_buttons {
|
|
||||||
display: flex;
|
|
||||||
gap: inherit;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type="range"] {
|
input[type="range"] {
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
-webkit-appearance: none;
|
-webkit-appearance: none;
|
||||||
|
|
|
@ -29,13 +29,6 @@
|
||||||
color: lightgray;
|
color: lightgray;
|
||||||
}
|
}
|
||||||
|
|
||||||
#labelcontainer {
|
|
||||||
column-gap: 18px;
|
|
||||||
font-size: 0.85em;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
user-select: none;
|
|
||||||
}
|
|
||||||
.celled_viewer {
|
.celled_viewer {
|
||||||
display: grid;
|
display: grid;
|
||||||
max-width: fit-content;
|
max-width: fit-content;
|
||||||
|
|
|
@ -33,13 +33,12 @@ main {
|
||||||
grid-template-columns: min-content min-content min-content;
|
grid-template-columns: min-content min-content min-content;
|
||||||
grid-template-rows: min-content min-content min-content;
|
grid-template-rows: min-content min-content min-content;
|
||||||
grid-template-areas:
|
grid-template-areas:
|
||||||
"cycles registers regmemlabel"
|
". regmemlabel . cycles "
|
||||||
"title memory memory"
|
". registers . bank "
|
||||||
". buttons buttons ";
|
"title memory memory memory"
|
||||||
|
". buttons buttons buttons ";
|
||||||
#memory {
|
#memory {
|
||||||
grid-area: memory;
|
grid-area: memory;
|
||||||
// grid-column: 2/4;
|
|
||||||
// grid-row: 2/6;
|
|
||||||
}
|
}
|
||||||
#window_box {
|
#window_box {
|
||||||
grid-area: windowbox;
|
grid-area: windowbox;
|
||||||
|
@ -47,12 +46,15 @@ main {
|
||||||
#registers {
|
#registers {
|
||||||
grid-area: registers;
|
grid-area: registers;
|
||||||
}
|
}
|
||||||
|
#memory_bank_view {
|
||||||
|
grid-area: bank;
|
||||||
|
}
|
||||||
#labelcontainer {
|
#labelcontainer {
|
||||||
grid-area: regmemlabel;
|
grid-area: regmemlabel;
|
||||||
}
|
}
|
||||||
#cycles {
|
#cycles {
|
||||||
grid-area: cycles;
|
grid-area: cycles;
|
||||||
text-align: left;
|
text-align: right;
|
||||||
align-self: center;
|
align-self: center;
|
||||||
font-size: 0.48em;
|
font-size: 0.48em;
|
||||||
user-select: none;
|
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 {
|
.invalid {
|
||||||
--color: var(--mem-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%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -14,22 +14,18 @@
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
max-width: 500px;
|
width: 500px;
|
||||||
.window {
|
.window {
|
||||||
overflow-y: hidden;
|
overflow-y: hidden;
|
||||||
position: relative;
|
position: relative;
|
||||||
border: 5px Solid var(--border);
|
border: 5px Solid var(--border);
|
||||||
border-bottom: unset;
|
border-bottom: unset;
|
||||||
&.collapsed {
|
|
||||||
.window_title {
|
.window_title {
|
||||||
// border-bottom: unset;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.window_title {
|
|
||||||
position: sticky;
|
|
||||||
user-select: none;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
|
position: sticky;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
user-select: none;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
font-size: 0.6em;
|
font-size: 0.6em;
|
||||||
color: lightgray;
|
color: lightgray;
|
||||||
|
@ -45,21 +41,24 @@
|
||||||
repeating-linear-gradient(to right, transparent, transparent 2px, transparent 2px, yellow 2px, yellow 4px);
|
repeating-linear-gradient(to right, transparent, transparent 2px, transparent 2px, yellow 2px, yellow 4px);
|
||||||
|
|
||||||
#text {
|
#text {
|
||||||
display: inline-block;
|
word-break: keep-all;
|
||||||
|
white-space: nowrap;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
height: 100%;
|
|
||||||
padding-inline: 10px;
|
padding-inline: 10px;
|
||||||
background-color: black;
|
background-color: black;
|
||||||
}
|
}
|
||||||
#collapse_button {
|
#collapse_button {
|
||||||
height: 23px !important;
|
height: 23px;
|
||||||
aspect-ratio: 1;
|
aspect-ratio: 1;
|
||||||
border: 2px solid white;
|
border: 2px solid yellow;
|
||||||
background-color: black;
|
background-color: black;
|
||||||
margin-right: 3px;
|
margin-right: 3px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.window.collapsed > :not(:first-child) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.window#tv {
|
.window#tv {
|
||||||
|
@ -71,11 +70,10 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
#instruction_explainer {
|
#instruction_explainer {
|
||||||
grid-area: explainer;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 5px;
|
gap: 5px;
|
||||||
height: 400px;
|
height: 300px;
|
||||||
#expl_box {
|
#expl_box {
|
||||||
padding-inline: 20px;
|
padding-inline: 20px;
|
||||||
padding-block-start: 10px;
|
padding-block-start: 10px;
|
||||||
|
@ -97,7 +95,6 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
#printout {
|
#printout {
|
||||||
grid-area: printout;
|
|
||||||
height: 500px;
|
height: 500px;
|
||||||
word-wrap: break-word;
|
word-wrap: break-word;
|
||||||
word-break: break-all;
|
word-break: break-all;
|
||||||
|
|
33
src/ui.ts
33
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 { $ } from "./etc";
|
||||||
import { InstructionExplainer } from "./ui/windows/instructionExplainer";
|
import { InstructionExplainer } from "./ui/windows/instructionExplainer";
|
||||||
import { MemoryView } from "./ui/memoryView";
|
import { MemoryView } from "./ui/memoryView";
|
||||||
import { frequencyIndicator } from "./ui/frequencyIndicator";
|
import { frequencyIndicator } from "./ui/frequencyIndicator";
|
||||||
import { RegisterView } from "./ui/registerView";
|
import { RegisterView } from "./ui/registerView";
|
||||||
import { Screen } from "./ui/windows/screen";
|
import { Screen } from "./ui/windows/screen";
|
||||||
import { EditButton } from "./ui/edit_button";
|
import { EditButton } from "./ui/editButton";
|
||||||
import { UiComponent, UiComponentConstructor } from "./ui/uiComponent";
|
import { UiComponent, UiComponentConstructor } from "./ui/uiComponent";
|
||||||
import { pausePlay } from "./ui/pausePlay";
|
import { pausePlay } from "./ui/pausePlay";
|
||||||
import { Printout } from "./ui/windows/printout";
|
import { Printout } from "./ui/windows/printout";
|
||||||
|
import { SaveLoad } from "./ui/saveLoad";
|
||||||
|
|
||||||
export class UI {
|
export class UI {
|
||||||
events: UiEventHandler = new UiEventHandler();
|
ui_events: UiEventHandler = new UiEventHandler();
|
||||||
|
cpu_signaler: UiCpuSignalHandler = new UiCpuSignalHandler();
|
||||||
private components: Array<UiComponent>;
|
private components: Array<UiComponent> = [];
|
||||||
|
|
||||||
constructor() {
|
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(MemoryView, $("memory"));
|
||||||
this.register_component(frequencyIndicator, $("cycles"));
|
this.register_component(frequencyIndicator, $("cycles"));
|
||||||
this.register_component(InstructionExplainer, $("instruction_explainer"));
|
this.register_component(InstructionExplainer, $("instruction_explainer"));
|
||||||
|
@ -31,15 +25,16 @@ export class UI {
|
||||||
this.register_component(Printout, $("printout"));
|
this.register_component(Printout, $("printout"));
|
||||||
this.register_component(EditButton, $("edit_button"));
|
this.register_component(EditButton, $("edit_button"));
|
||||||
this.register_component(pausePlay, $("controls_buttons"));
|
this.register_component(pausePlay, $("controls_buttons"));
|
||||||
|
this.register_component(SaveLoad, $("save_load_buttons"));
|
||||||
const pp_button = $("pause_play_button");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private register_component(ctor: UiComponentConstructor, e: HTMLElement): void {
|
private register_component(ctor: UiComponentConstructor, e: HTMLElement): void {
|
||||||
if (e === undefined) {
|
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);
|
console.log(ctor);
|
||||||
throw new Error("Could not find HTML element while registering UI component");
|
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);
|
this.components.push(component);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,14 +43,10 @@ export class UI {
|
||||||
this.reset();
|
this.reset();
|
||||||
});
|
});
|
||||||
|
|
||||||
for (const c of this.components) {
|
for (const c of this.components) if (c.init_cpu_events) c.init_cpu_events(cpu_events);
|
||||||
if (c.init_cpu_events) c.init_cpu_events(cpu_events);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
reset(): void {
|
reset(): void {
|
||||||
for (const c of this.components) {
|
for (const c of this.components) if (c.reset) c.reset();
|
||||||
c.reset();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,22 +8,18 @@ const HEX_CHARACTERS = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "
|
||||||
export class EditorContext {
|
export class EditorContext {
|
||||||
private list: Array<HTMLElement>;
|
private list: Array<HTMLElement>;
|
||||||
private width: number;
|
private width: number;
|
||||||
private height: number;
|
|
||||||
private enabled: boolean = false;
|
private enabled: boolean = false;
|
||||||
private current_cell_info: { left?: string; right?: string; old?: string };
|
private current_cell_info: { left?: string; right?: string; old?: string };
|
||||||
private edit_callback: (n: number, value: u8) => void;
|
private edit_callback: (n: number, value: u8) => void;
|
||||||
constructor(list: Array<HTMLElement>, width: number, height: number, callback: (n: number, value: u8) => void) {
|
constructor(list: Array<HTMLElement>, width: number, callback: (n: number, value: u8) => void) {
|
||||||
this.list = list;
|
this.list = list;
|
||||||
this.width = width;
|
this.width = width;
|
||||||
this.height = height;
|
|
||||||
this.edit_callback = callback;
|
this.edit_callback = callback;
|
||||||
this.current_cell_info = {};
|
this.current_cell_info = {};
|
||||||
|
|
||||||
for (const [i, cell] of this.list.entries()) {
|
for (const [i, cell] of this.list.entries()) {
|
||||||
cell.setAttribute("spellcheck", "false");
|
cell.setAttribute("spellcheck", "false");
|
||||||
cell.addEventListener("keydown", (e) => {
|
cell.addEventListener("keydown", (e) => this.keydown(e, i));
|
||||||
this.keydown(e, i);
|
|
||||||
});
|
|
||||||
cell.addEventListener("input", (e) => {
|
cell.addEventListener("input", (e) => {
|
||||||
const target = e.target as HTMLElement;
|
const target = e.target as HTMLElement;
|
||||||
if (target === null) return;
|
if (target === null) return;
|
||||||
|
|
|
@ -8,9 +8,9 @@ export class frequencyIndicator implements UiComponent {
|
||||||
private last_value: number = 0;
|
private last_value: number = 0;
|
||||||
private last_time: number = 0;
|
private last_time: number = 0;
|
||||||
events: UiEventHandler;
|
events: UiEventHandler;
|
||||||
constructor(element: HTMLElement, e: UiEventHandler) {
|
constructor(element: HTMLElement, events: UiEventHandler) {
|
||||||
this.element = element;
|
this.element = element;
|
||||||
this.events = e;
|
this.events = events;
|
||||||
this.start();
|
this.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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 { ParamType } from "../instructionSet";
|
||||||
import { u8 } from "../num.js";
|
import { u8 } from "../num.js";
|
||||||
import { UiComponent } from "./uiComponent";
|
import { UiComponent } from "./uiComponent";
|
||||||
import { CelledViewer } from "./celledViewer";
|
import { CelledViewer } from "./celledViewer";
|
||||||
import { EditorContext } from "./editableHex";
|
import { EditorContext } from "./editableHex";
|
||||||
|
|
||||||
type MemoryCell = {
|
|
||||||
el: HTMLDivElement;
|
|
||||||
};
|
|
||||||
|
|
||||||
export class MemoryView extends CelledViewer implements UiComponent {
|
export class MemoryView extends CelledViewer implements UiComponent {
|
||||||
program_counter: u8 = 0;
|
program_counter: u8 = 0;
|
||||||
last_accessed_cell: u8 | null = null;
|
last_accessed_cell: u8 | null = null;
|
||||||
events: UiEventHandler;
|
events: UiEventHandler;
|
||||||
constructor(element: HTMLElement, e: UiEventHandler) {
|
cpu_signals: UiCpuSignalHandler;
|
||||||
|
constructor(element: HTMLElement, events: UiEventHandler, cpu_signals: UiCpuSignalHandler) {
|
||||||
super(16, 16, element);
|
super(16, 16, element);
|
||||||
this.program_counter = 0;
|
this.program_counter = 0;
|
||||||
this.events = e;
|
this.events = events;
|
||||||
|
this.cpu_signals = cpu_signals;
|
||||||
|
|
||||||
const list = this.cells.map((c) => c.el);
|
const list = this.cells.map((c) => c.el);
|
||||||
const editor = new EditorContext(list, this.width, this.height, (i, value) => {
|
const editor = new EditorContext(list, this.width, (i, value) => {
|
||||||
this.events.dispatch(UiEvent.RequestMemoryChange, { address: i as u8, value });
|
this.cpu_signals.dispatch(UiCpuSignal.RequestMemoryChange, { address: i as u8, bank: 0, value });
|
||||||
});
|
});
|
||||||
this.events.listen(UiEvent.EditOn, () => {
|
this.events.listen(UiEvent.EditOn, () => {
|
||||||
editor.enable();
|
editor.enable();
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { el } from "../etc";
|
import { el } from "../etc";
|
||||||
import { UiEventHandler, UiEvent } from "../events";
|
import { UiEventHandler, UiEvent, CpuEventHandler, UiCpuSignalHandler, UiCpuSignal } from "../events";
|
||||||
import { UiComponent } from "./uiComponent";
|
import { UiComponent } from "./uiComponent";
|
||||||
|
|
||||||
const MAX_SLIDER = 1000;
|
const MAX_SLIDER = 1000;
|
||||||
|
@ -12,9 +12,12 @@ export class pausePlay implements UiComponent {
|
||||||
events: UiEventHandler;
|
events: UiEventHandler;
|
||||||
on: boolean = false;
|
on: boolean = false;
|
||||||
cycle_delay: number;
|
cycle_delay: number;
|
||||||
constructor(element: HTMLElement, events: UiEventHandler) {
|
cpu_signals: UiCpuSignalHandler;
|
||||||
|
|
||||||
|
constructor(element: HTMLElement, events: UiEventHandler, cpu_signals: UiCpuSignalHandler) {
|
||||||
this.element = element;
|
this.element = element;
|
||||||
this.events = events;
|
this.events = events;
|
||||||
|
this.cpu_signals = cpu_signals;
|
||||||
this.start_button = el("button", "pause_play_button");
|
this.start_button = el("button", "pause_play_button");
|
||||||
this.step_button = el("button", "step_button");
|
this.step_button = el("button", "step_button");
|
||||||
this.range = el("input", "speed_range");
|
this.range = el("input", "speed_range");
|
||||||
|
@ -72,7 +75,7 @@ export class pausePlay implements UiComponent {
|
||||||
if (this.on === false) {
|
if (this.on === false) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.events.dispatch(UiEvent.RequestCpuCycle, 1);
|
this.cpu_signals.dispatch(UiCpuSignal.RequestCpuCycle, 1);
|
||||||
setTimeout(loop, this.cycle_delay);
|
setTimeout(loop, this.cycle_delay);
|
||||||
};
|
};
|
||||||
loop();
|
loop();
|
||||||
|
@ -81,7 +84,7 @@ export class pausePlay implements UiComponent {
|
||||||
if (this.on) {
|
if (this.on) {
|
||||||
this.stop();
|
this.stop();
|
||||||
} else {
|
} else {
|
||||||
this.events.dispatch(UiEvent.RequestCpuCycle, 1);
|
this.cpu_signals.dispatch(UiCpuSignal.RequestCpuCycle, 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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 { u3 } from "../num";
|
||||||
import { CelledViewer } from "./celledViewer";
|
import { CelledViewer } from "./celledViewer";
|
||||||
import { EditorContext } from "./editableHex";
|
import { EditorContext } from "./editableHex";
|
||||||
|
@ -6,13 +6,15 @@ import { UiComponent } from "./uiComponent";
|
||||||
|
|
||||||
export class RegisterView extends CelledViewer implements UiComponent {
|
export class RegisterView extends CelledViewer implements UiComponent {
|
||||||
events: UiEventHandler;
|
events: UiEventHandler;
|
||||||
constructor(element: HTMLElement, e: UiEventHandler) {
|
cpu_signals: UiCpuSignalHandler;
|
||||||
|
constructor(element: HTMLElement, events: UiEventHandler, cpu_signals: UiCpuSignalHandler) {
|
||||||
super(8, 1, element);
|
super(8, 1, element);
|
||||||
this.events = e;
|
this.events = events;
|
||||||
|
this.cpu_signals = cpu_signals;
|
||||||
|
|
||||||
const list = this.cells.map((c) => c.el);
|
const list = this.cells.map((c) => c.el);
|
||||||
const editor = new EditorContext(list, this.width, this.height, (i, value) => {
|
const editor = new EditorContext(list, this.width, (i, value) => {
|
||||||
this.events.dispatch(UiEvent.RequestRegisterChange, { register_no: i as u3, value });
|
this.cpu_signals.dispatch(UiCpuSignal.RequestRegisterChange, { register_no: i as u3, value });
|
||||||
});
|
});
|
||||||
this.events.listen(UiEvent.EditOn, () => {
|
this.events.listen(UiEvent.EditOn, () => {
|
||||||
editor.enable();
|
editor.enable();
|
||||||
|
|
91
src/ui/saveLoad.ts
Normal file
91
src/ui/saveLoad.ts
Normal file
|
@ -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<u8>;
|
||||||
|
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();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,19 +3,28 @@
|
||||||
* @copyright Alexander Bass 2024
|
* @copyright Alexander Bass 2024
|
||||||
* @license GPL-3.0
|
* @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 {
|
export interface UiComponent {
|
||||||
element: HTMLElement;
|
element: HTMLElement;
|
||||||
/** Allows listening and emitting UiEvents*/
|
/** Allows listening and emitting UiEvents*/
|
||||||
events: UiEventHandler;
|
events: UiEventHandler;
|
||||||
|
/** Creating signals for the cpu to process */
|
||||||
|
cpu_signals?: UiCpuSignalHandler;
|
||||||
/** Completely reset the state of the component */
|
/** Completely reset the state of the component */
|
||||||
reset: () => void;
|
reset?: () => void;
|
||||||
soft_reset?: () => void;
|
soft_reset?: () => void;
|
||||||
/** Allows listening CPUEvents*/
|
/** Allows listening CPUEvents*/
|
||||||
init_cpu_events?: (c: CpuEventHandler) => void;
|
init_cpu_events?: (c: CpuEventHandler) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UiComponentConstructor {
|
export interface UiComponentConstructor {
|
||||||
new (el: HTMLElement, ue: UiEventHandler): UiComponent;
|
new (el: HTMLElement, ui_event_handler: UiEventHandler, cpu_signaler: UiCpuSignalHandler): UiComponent;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import { el } from "../etc";
|
import { el } from "../etc";
|
||||||
export abstract class WindowBox {
|
export abstract class WindowBox {
|
||||||
element: HTMLElement;
|
element: HTMLElement;
|
||||||
readonly title: string;
|
|
||||||
title_bar: HTMLElement;
|
title_bar: HTMLElement;
|
||||||
|
readonly title: string;
|
||||||
private resize: HTMLElement;
|
private resize: HTMLElement;
|
||||||
private collapse_button: HTMLButtonElement;
|
private collapse_button: HTMLButtonElement;
|
||||||
private collapsed: boolean = false;
|
private collapsed: boolean = false;
|
||||||
|
|
|
@ -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);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { el, format_hex } from "../../etc";
|
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 { Instruction, ParamType, ParameterType } from "../../instructionSet";
|
||||||
import { u8 } from "../../num";
|
import { u8 } from "../../num";
|
||||||
import { WindowBox } from "../windowBox";
|
import { WindowBox } from "../windowBox";
|
||||||
|
@ -7,9 +7,11 @@ import { UiComponent } from "../uiComponent";
|
||||||
|
|
||||||
export class InstructionExplainer extends WindowBox implements UiComponent {
|
export class InstructionExplainer extends WindowBox implements UiComponent {
|
||||||
events: UiEventHandler;
|
events: UiEventHandler;
|
||||||
constructor(element: HTMLElement, e: UiEventHandler) {
|
cpu_signals: UiCpuSignalHandler;
|
||||||
|
constructor(element: HTMLElement, events: UiEventHandler, cpu_signals: UiCpuSignalHandler) {
|
||||||
super(element, "Instruction Explainer");
|
super(element, "Instruction Explainer");
|
||||||
this.events = e;
|
this.cpu_signals = cpu_signals;
|
||||||
|
this.events = events;
|
||||||
}
|
}
|
||||||
add_instruction(instr: Instruction, pos: u8, byte: u8): void {
|
add_instruction(instr: Instruction, pos: u8, byte: u8): void {
|
||||||
this.reset();
|
this.reset();
|
||||||
|
|
|
@ -1,13 +1,15 @@
|
||||||
import { el } from "../../etc";
|
import { el } from "../../etc";
|
||||||
import { CpuEvent, CpuEventHandler, UiEventHandler } from "../../events";
|
import { CpuEvent, CpuEventHandler, UiCpuSignalHandler, UiEventHandler } from "../../events";
|
||||||
import { WindowBox } from "../windowBox";
|
import { WindowBox } from "../windowBox";
|
||||||
import { UiComponent } from "../uiComponent";
|
import { UiComponent } from "../uiComponent";
|
||||||
|
|
||||||
export class Printout extends WindowBox implements UiComponent {
|
export class Printout extends WindowBox implements UiComponent {
|
||||||
events: UiEventHandler;
|
events: UiEventHandler;
|
||||||
text_box: HTMLElement;
|
text_box: HTMLElement;
|
||||||
constructor(element: HTMLElement, events: UiEventHandler) {
|
cpu_signals: UiCpuSignalHandler;
|
||||||
|
constructor(element: HTMLElement, events: UiEventHandler, cpu_signals: UiCpuSignalHandler) {
|
||||||
super(element, "Printout");
|
super(element, "Printout");
|
||||||
|
this.cpu_signals = cpu_signals;
|
||||||
this.events = events;
|
this.events = events;
|
||||||
this.text_box = el("div", "printout_text");
|
this.text_box = el("div", "printout_text");
|
||||||
this.element.appendChild(this.text_box);
|
this.element.appendChild(this.text_box);
|
||||||
|
|
|
@ -22,6 +22,7 @@ export class Screen extends WindowBox implements UiComponent {
|
||||||
}
|
}
|
||||||
this.ctx = ctx;
|
this.ctx = ctx;
|
||||||
this.element.appendChild(this.screen);
|
this.element.appendChild(this.screen);
|
||||||
|
this.test_pattern();
|
||||||
}
|
}
|
||||||
|
|
||||||
private test_pattern(): void {
|
private test_pattern(): void {
|
||||||
|
|
Loading…
Reference in a new issue