rename js folder to ts

This commit is contained in:
Rachel Powers
2024-04-08 12:27:26 -07:00
parent 41a9e6da86
commit ef0d2e6327
26 changed files with 0 additions and 0 deletions

View File

@@ -0,0 +1,165 @@
import { property, state } from "lit/decorators.js";
import { BaseElement } from "../components";
import {
DeviceRef,
Fields,
Reagents,
Slot,
Connection,
ICError,
Registers,
Stack,
Aliases,
Defines,
Pins,
} from "ic10emu_wasm";
import { structuralEqual } from "../utils";
export class VMBaseDevice extends BaseElement {
@property({ type: Number }) accessor deviceID: number;
@state() protected accessor device: DeviceRef;
@state() accessor name: string | null;
@state() accessor nameHash: number | null;
@state() accessor prefabName: string | null;
@state() accessor fields: Fields;
@state() accessor slots: Slot[];
@state() accessor reagents: Reagents;
@state() accessor connections: Connection[];
@state() accessor icIP: number;
@state() accessor icOpCount: number;
@state() accessor icState: string;
@state() accessor errors: ICError[];
@state() accessor registers: Registers | null;
@state() accessor stack: Stack | null;
@state() accessor aliases: Aliases | null;
@state() accessor defines: Defines | null;
@state() accessor pins: Pins | null;
constructor() {
super();
this.name = null;
this.nameHash = null;
}
connectedCallback(): void {
const root = super.connectedCallback();
this.device = window.VM!.devices.get(this.deviceID)!;
window.VM?.addEventListener(
"vm-device-modified",
this._handleDeviceModified.bind(this),
);
this.updateDevice();
return root;
}
_handleDeviceModified(e: CustomEvent) {
const id = e.detail;
if (this.deviceID === id) {
this.updateDevice();
}
}
updateDevice() {
const name = this.device.name ?? null;
if (this.name !== name) {
this.name = name;
}
const nameHash = this.device.nameHash ?? null;
if (this.nameHash !== nameHash) {
this.nameHash = nameHash;
}
const prefabName = this.device.prefabName ?? null;
if (this.prefabName !== prefabName) {
this.prefabName = prefabName;
}
const fields = this.device.fields;
if (!structuralEqual(this.fields, fields)) {
this.fields = fields;
}
const slots = this.device.slots;
if (!structuralEqual(this.slots, slots)) {
this.slots = slots;
}
const reagents = this.device.reagents;
if (!structuralEqual(this.reagents, reagents)) {
this.reagents = reagents;
}
const connections = this.device.connections;
if (!structuralEqual(this.connections, connections)) {
this.connections = connections;
}
this.updateIC();
}
updateIC() {
const ip = this.device.ip!;
if (this.icIP !== ip) {
this.icIP = ip;
}
const opCount = this.device.instructionCount!;
if (this.icOpCount !== opCount) {
this.icOpCount = opCount;
}
const state = this.device.state!;
if (this.icState !== state) {
this.icState = state;
}
const errors = this.device.program!.errors ?? null;
if (!structuralEqual(this.errors, errors)) {
this.errors = errors;
}
const registers = this.device.registers ?? null;
if (!structuralEqual(this.registers, registers)) {
this.registers = registers;
}
const stack = this.device.stack ?? null;
if (!structuralEqual(this.stack, stack)) {
this.stack = stack;
}
const aliases = this.device.aliases ?? null;
if (!structuralEqual(this.aliases, aliases)) {
this.aliases = aliases;
}
const defines = this.device.defines ?? null;
if (!structuralEqual(this.defines, defines)) {
this.defines = defines;
}
const pins = this.device.pins ?? null;
if(!structuralEqual(this.pins, pins)) {
this.pins = pins;
}
}
}
export class VMActiveIC extends VMBaseDevice {
constructor() {
super();
this.deviceID = window.App!.session.activeIC;
}
connectedCallback(): void {
const root = super.connectedCallback();
window.VM?.addEventListener(
"vm-run-ic",
this._handleDeviceModified.bind(this),
);
window.App?.session.addEventListener(
"session-active-ic",
this._handleActiveIC.bind(this),
);
this.updateIC();
return root;
}
_handleActiveIC(e: CustomEvent) {
const id = e.detail;
if (this.deviceID !== id) {
this.deviceID = id;
this.device = window.VM!.devices.get(this.deviceID)!;
}
this.updateDevice();
}
}

View File

@@ -0,0 +1,147 @@
import { html, css } from "lit";
import { customElement } from "lit/decorators.js";
import { defaultCss } from "../components";
import { VMActiveIC } from "./base_device";
import { structuralEqual } from "../utils";
import "@shoelace-style/shoelace/dist/components/card/card.js";
import "@shoelace-style/shoelace/dist/components/button-group/button-group.js";
import "@shoelace-style/shoelace/dist/components/button/button.js";
import "@shoelace-style/shoelace/dist/components/icon/icon.js";
import "@shoelace-style/shoelace/dist/components/tooltip/tooltip.js";
import "@shoelace-style/shoelace/dist/components/divider/divider.js";
@customElement("vm-ic-controls")
export class VMICControls extends VMActiveIC {
static styles = [
...defaultCss,
css`
:host {
}
.card {
margin-left: 1rem;
margin-right: 1rem;
margin-top: 0.5rem;
}
.controls {
display: flex;
flex-direction: row;
font-size: var(--sl-font-size-small);
}
.stats {
font-size: var(--sl-font-size-x-small);
}
.device-id {
margin-left: 2rem;
}
.button-group-toolbar sl-button-group:not(:last-of-type) {
margin-right: var(--sl-spacing-x-small);
}
sl-divider {
--spacing: 0.25rem;
}
`,
];
constructor() {
super();
this.deviceID = window.App!.session.activeIC;
}
protected render() {
return html`
<sl-card class="card">
<div class="controls" slot="header">
<sl-button-group>
<sl-tooltip
content="Run the active IC through one tick (128 operations)"
>
<sl-button
size="small"
variant="primary"
@click=${this._handleRunClick}
>
<span>Run</span>
<sl-icon name="play" label="Run" slot="prefix"></sl-icon>
</sl-button>
</sl-tooltip>
<sl-tooltip content="Run the active IC through a single operations">
<sl-button
size="small"
variant="success"
@click=${this._handleStepClick}
>
<span>Step</span>
<sl-icon
name="chevron-bar-right"
label="Step"
slot="prefix"
></sl-icon>
</sl-button>
</sl-tooltip>
<sl-tooltip content="Reset the active IC">
<sl-button
size="small"
variant="warning"
@click=${this._handleResetClick}
>
<span>Reset</span>
<sl-icon
name="arrow-clockwise"
label="Reset"
slot="prefix"
></sl-icon>
</sl-button>
</sl-tooltip>
</sl-button-group>
<div class="device-id">
Device:
${this.deviceID}${this.name ?? this.prefabName
? ` : ${this.name ?? this.prefabName}`
: ""}
</div>
</div>
<div class="stats">
<div class="hstack">
<span>Instruction Pointer</span>
<span class="ms-auto">${this.icIP}</span>
</div>
<sl-divider></sl-divider>
<div class="hstack">
<span>Last Run Operations Count</span>
<span class="ms-auto">${this.icOpCount}</span>
</div>
<sl-divider></sl-divider>
<div class="hstack">
<span>Last State</span>
<span class="ms-auto">${this.icState}</span>
</div>
<sl-divider></sl-divider>
<div class="vstack">
<span>Errors</span>
${this.errors.map(
(err) =>
html`<div class="hstack">
<span>
Line: ${err.ParseError.line} -
${err.ParseError.start}:${err.ParseError.end}
</span>
<span class="ms-auto">${err.ParseError.msg}</span>
</div>`,
)}
</div>
</div>
</sl-card>
`;
}
_handleRunClick() {
window.VM?.run();
}
_handleStepClick() {
window.VM?.step();
}
_handleResetClick() {
window.VM?.reset();
}
}

View File

@@ -0,0 +1,398 @@
import { Offcanvas } from "bootstrap";
import { VirtualMachine, VirtualMachineUI } from ".";
import { DeviceRef, VM } from "ic10emu_wasm";
class VMDeviceUI {
ui: VirtualMachineUI;
summary: HTMLDivElement;
canvasEl: HTMLDivElement;
deviceCountEl: HTMLElement;
canvas: Offcanvas;
private _deviceSummaryCards: Map<number, VMDeviceSummaryCard>;
private _offCanvaseCards: Map<
number,
{ col: HTMLElement; card: VMDeviceCard }
>;
constructor(ui: VirtualMachineUI) {
const that = this;
that.ui = ui;
this.summary = document.getElementById("vmDeviceSummary") as HTMLDivElement;
this.canvasEl = document.getElementById(
"vmDevicesOCBody",
) as HTMLDivElement;
this.deviceCountEl = document.getElementById("vmViewDeviceCount");
this.canvas = new Offcanvas(this.canvasEl);
this._deviceSummaryCards = new Map();
this._offCanvaseCards = new Map();
}
update(active_ic: DeviceRef) {
const devices = window.VM.devices;
this.deviceCountEl.textContent = `(${devices.size})`;
for (const [id, device] of devices) {
if (!this._deviceSummaryCards.has(id)) {
this._deviceSummaryCards.set(id, new VMDeviceSummaryCard(this, device));
}
if (!this._offCanvaseCards.has(id)) {
const col = document.createElement("div");
col.classList.add("col");
col.id = `${this.canvasEl.id}_col${id}`
this.canvasEl.appendChild(col);
this._offCanvaseCards.set(id, {
col,
card: new VMDeviceCard(this, col, device),
});
}
}
this._deviceSummaryCards.forEach((card, id, cards) => {
if (!devices.has(id)) {
card.destroy();
cards.delete(id);
} else {
card.update(active_ic);
}
}, this);
this._offCanvaseCards.forEach((card, id, cards) => {
if (!devices.has(id)) {
card.card.destroy();
card.col.remove();
cards.delete(id);
} else {
card.card.update(active_ic);
}
}, this);
}
}
class VMDeviceSummaryCard {
root: HTMLDivElement;
viewBtn: HTMLButtonElement;
deviceUI: VMDeviceUI;
device: DeviceRef;
badges: HTMLSpanElement[];
constructor(deviceUI: VMDeviceUI, device: DeviceRef) {
// const that = this;
this.deviceUI = deviceUI;
this.device = device;
this.root = document.createElement("div");
this.root.classList.add(
"hstack",
"gap-2",
"bg-light-subtle",
"border",
"border-secondary-subtle",
"rounded",
);
this.viewBtn = document.createElement("button");
this.viewBtn.type = "button";
this.viewBtn.classList.add("btn", "btn-success");
this.root.appendChild(this.viewBtn);
this.deviceUI.summary.appendChild(this.root);
this.badges = [];
this.update(window.VM.activeIC);
}
update(active_ic: DeviceRef) {
const that = this;
// clear previous badges
this.badges.forEach((badge) => badge.remove());
this.badges = [];
//update name
var deviceName = this.device.name ?? this.device.prefabName ?? "";
if (deviceName) {
deviceName = `: ${deviceName}`;
}
const btnTxt = `Device ${this.device.id}${deviceName}`;
this.viewBtn.textContent = btnTxt;
// regenerate badges
this.device.connections.forEach((conn, index) => {
if (typeof conn === "object") {
var badge = document.createElement("span");
badge.classList.add("badge", "text-bg-light");
badge.textContent = `Net ${index}:${conn.CableNetwork}`;
that.badges.push(badge);
that.root.appendChild(badge);
}
});
if (this.device.id === active_ic.id) {
var badge = document.createElement("span");
badge.classList.add("badge", "text-bg-success");
badge.textContent = "db";
that.badges.push(badge);
that.root.appendChild(badge);
}
active_ic.pins?.forEach((id, index) => {
if (that.device.id === id) {
var badge = document.createElement("span");
badge.classList.add("badge", "text-bg-success");
badge.textContent = `d${index}`;
that.badges.push(badge);
that.root.appendChild(badge);
}
});
}
destroy() {
this.root.remove();
}
}
class VMDeviceCard {
ui: VMDeviceUI;
container: HTMLElement;
device: DeviceRef;
root: HTMLDivElement;
nav: HTMLUListElement;
header: HTMLDivElement;
nameInput: HTMLInputElement;
nameHash: HTMLSpanElement;
body: HTMLDivElement;
badges: HTMLSpanElement[];
fieldsContainer: HTMLDivElement;
slotsContainer: HTMLDivElement;
pinsContainer: HTMLDivElement;
networksContainer: HTMLDivElement;
reagentsContainer: HTMLDivElement;
nav_id: string;
navTabs: { [key: string]: { li: HTMLLIElement; button: HTMLButtonElement } };
paneContainer: HTMLDivElement;
tabPanes: { [key: string]: HTMLElement };
image: HTMLImageElement;
image_err: boolean;
title: HTMLHeadingElement;
fieldEls: Map<string, VMDeviceField>;
constructor(ui: VMDeviceUI, container: HTMLElement, device: DeviceRef) {
this.ui = ui;
this.container = container;
this.device = device;
this.nav_id = `${this.container.id}_vmDeviceCard${this.device.id}`;
this.root = document.createElement("div");
this.root.classList.add("card");
this.header = document.createElement("div");
this.header.classList.add("card-header", "hstack");
this.image = document.createElement("img");
this.image_err = false;
this.image.src = `/img/stationpedia/${this.device.prefabName}.png`;
this.image.onerror = this.onImageErr;
this.image.width = 48;
this.image.classList.add("me-2");
this.header.appendChild(this.image);
this.title = document.createElement("h5");
this.title.textContent = `Device ${this.device.id} : ${this.device.prefabName ?? ""}`;
this.header.appendChild(this.title);
this.nameInput = document.createElement("input");
this.nameHash = document.createElement("span");
this.root.appendChild(this.header);
this.body = document.createElement("div");
this.body.classList.add("card-body");
this.root.appendChild(this.body);
this.nav = document.createElement("ul");
this.nav.classList.add("nav", "nav-tabs");
this.nav.role = "tablist";
this.nav.id = this.nav_id;
this.navTabs = {};
this.tabPanes = {};
this.body.appendChild(this.nav);
this.paneContainer = document.createElement("div");
this.paneContainer.id = `${this.nav_id}_tabs`;
this.body.appendChild(this.paneContainer);
this.badges = [];
this.fieldsContainer = document.createElement("div");
this.fieldsContainer.id = `${this.nav_id}_fields`;
this.fieldsContainer.classList.add("vstack");
this.fieldEls = new Map();
this.slotsContainer = document.createElement("div");
this.slotsContainer.id = `${this.nav_id}_slots`;
this.slotsContainer.classList.add("vstack");
this.reagentsContainer = document.createElement("div");
this.reagentsContainer.id = `${this.nav_id}_reagents`;
this.reagentsContainer.classList.add("vstack");
this.networksContainer = document.createElement("div");
this.networksContainer.id = `${this.nav_id}_networks`;
this.networksContainer.classList.add("vstack");
this.pinsContainer = document.createElement("div");
this.pinsContainer.id = `${this.nav_id}_pins`;
this.pinsContainer.classList.add("vstack");
this.addTab("Fields", this.fieldsContainer);
this.addTab("Slots", this.slotsContainer);
this.addTab("Networks", this.networksContainer);
this.update(window.VM.activeIC);
// do last to minimise reflows
this.container.appendChild(this.root);
}
onImageErr(e: Event) {
this.image_err = true;
console.log("Image load error", e);
}
addNav(name: string, target: string) {
if (!(name in this.navTabs)) {
var li = document.createElement("li");
li.classList.add("nav-item");
li.role = "presentation";
var button = document.createElement("button");
button.classList.add("nav-link");
if (!(Object.keys(this.navTabs).length > 0)) {
button.classList.add("active");
button.tabIndex = 0;
} else {
button.tabIndex = -1;
}
button.id = `${this.nav_id}_tab_${name}`;
button.setAttribute("data-bs-toggle", "tab");
button.setAttribute("data-bs-target", `#${target}`);
button.type = "button";
button.role = "tab";
button.setAttribute("aria-controls", target);
button.setAttribute(
"aria-selected",
Object.keys(this.navTabs).length > 0 ? "false" : "true",
);
button.textContent = name;
li.appendChild(button);
this.nav.appendChild(li);
this.navTabs[name] = { li, button };
return true;
}
return false;
}
removeNav(name: string) {
if (name in this.navTabs) {
this.navTabs[name].li.remove();
delete this.navTabs[name];
return true;
}
return false;
}
addTab(name: string, tab: HTMLElement) {
const paneName = `${this.nav_id}_pane_${name}`;
if (this.addNav(name, paneName)) {
if (name in this.tabPanes) {
this.tabPanes[name].remove();
}
const pane = document.createElement("div");
pane.classList.add("tap-pane", "fade");
if (!(Object.keys(this.tabPanes).length > 0)) {
pane.classList.add("show", "active");
}
pane.id = paneName;
pane.role = "tabpanel";
pane.setAttribute("aria-labelledby", `${this.nav_id}_tab_${name}`);
pane.tabIndex = 0;
this.paneContainer.appendChild(pane);
pane.appendChild(tab);
this.tabPanes[name] = tab;
}
}
removeTab(name: string) {
let result = this.removeNav(name);
if (name in this.tabPanes) {
this.tabPanes[name].remove();
delete this.tabPanes[name];
return true;
}
return result;
}
update(active_ic: DeviceRef) {
if (this.device.pins) {
this.addTab("Pins", this.pinsContainer);
} else {
this.removeTab("Pins");
}
// fields
for (const [name, _field] of this.device.fields) {
if (!this.fieldEls.has(name)) {
const field = new VMDeviceField(this.device, name, this, this.fieldsContainer);
this.fieldEls.set(name, field);
}
}
this.fieldEls.forEach((field, name, map) => {
if(!this.device.fields.has(name)) {
field.destroy();
map.delete(name);
} else {
field.update(active_ic);
}
}, this);
// TODO Reagents
}
destroy() {
this.root.remove();
}
}
class VMDeviceField {
container: HTMLElement;
card: VMDeviceCard;
device: DeviceRef;
field: string;
root: HTMLDivElement;
name: HTMLSpanElement;
fieldType: HTMLSpanElement;
input: HTMLInputElement;
constructor(device: DeviceRef, field: string, card: VMDeviceCard, container: HTMLElement) {
this.device = device;
this.field = field;
this.card = card;
this.container = container;
this.root = document.createElement('div');
this.root.classList.add("input-group", "input-group-sm");
this.name = document.createElement('span');
this.name.classList.add("input-group-text", "field_name");
this.name.textContent = this.field;
this.root.appendChild(this.name);
this.fieldType = document.createElement('span');
this.fieldType.classList.add("input-group-text", "field_type");
this.fieldType.textContent = device.fields.get(this.field)?.field_type;
this.root.appendChild(this.fieldType);
this.input = document.createElement('input');
this.input.type = "text";
this.input.value = this.device.fields.get(this.field)?.value.toString();
this.root.appendChild(this.input);
this.container.appendChild(this.root);
}
destroy () {
this.root.remove();
}
update(_active_ic: DeviceRef) {
this.input.value = this.device.fields.get(this.field)?.value.toString();
}
}
export { VMDeviceUI };

View File

@@ -0,0 +1,464 @@
import { DeviceRef, VM, init } from "ic10emu_wasm";
import "./base_device";
declare global {
interface Window {
VM?: VirtualMachine;
}
}
type DeviceDB = {
logic_enabled: string[];
slot_logic_enabled: string[];
devices: string[];
items: {
[key: string]: {
name: string;
hash: number;
desc: string;
logic?: { [key: string]: string };
slots?: { name: string; type: string }[];
modes?: { [key: string]: string };
conn?: { [key: string]: string[] };
};
};
};
class VirtualMachine extends EventTarget {
ic10vm: VM;
ui: VirtualMachineUI;
_devices: Map<number, DeviceRef>;
_ics: Map<number, DeviceRef>;
db: DeviceDB;
constructor() {
super();
const vm = init();
window.VM = this;
this.ic10vm = vm;
this._devices = new Map();
this._ics = new Map();
this.updateDevices();
this.updateCode();
}
get devices() {
return this._devices;
}
get ics() {
return this._ics;
}
get activeIC() {
return this._ics.get(window.App!.session.activeIC);
}
updateDevices() {
const device_ids = this.ic10vm.devices;
for (const id of device_ids) {
if (!this._devices.has(id)) {
this._devices.set(id, this.ic10vm.getDevice(id)!);
}
}
for (const id of this._devices.keys()) {
if (!device_ids.includes(id)) {
this._devices.get(id)!.free();
this._devices.delete(id);
}
}
const ics = this.ic10vm.ics;
for (const id of ics) {
if (!this._ics.has(id)) {
this._ics.set(id, this._devices.get(id)!);
}
}
for (const id of this._ics.keys()) {
if (!ics.includes(id)) {
this._ics.get(id)!.free();
this._ics.delete(id);
}
}
}
updateCode() {
const progs = window.App!.session.programs;
for (const id of progs.keys()) {
const attempt = Date.now().toString(16);
const ic = this._ics.get(id);
const prog = progs.get(id);
if (ic && prog) {
console.time(`CompileProgram_${id}_${attempt}`);
try {
this.ics.get(id)!.setCodeInvalid(progs.get(id)!);
const compiled = this.ics.get(id)?.program!;
window.App?.session.setProgramErrors(id, compiled.errors);
this.dispatchEvent(
new CustomEvent("vm-device-modified", { detail: id }),
);
} catch (e) {
console.log(e);
}
console.timeEnd(`CompileProgram_${id}_${attempt}`);
}
}
this.update();
}
step() {
const ic = this.activeIC;
if (ic) {
try {
ic.step(false);
} catch (e) {
console.log(e);
}
this.update();
this.dispatchEvent(
new CustomEvent("vm-run-ic", { detail: this.activeIC!.id }),
);
}
}
run() {
const ic = this.activeIC;
if (ic) {
try {
ic.run(false);
} catch (e) {
console.log(e);
}
this.update();
this.dispatchEvent(
new CustomEvent("vm-run-ic", { detail: this.activeIC!.id }),
);
}
}
reset() {
const ic = this.activeIC;
if (ic) {
ic.reset();
this.update();
}
}
update() {
this.updateDevices();
this.ic10vm.lastOperationModified.forEach((id, _index, _modifiedIds) => {
if (this.devices.has(id)) {
this.dispatchEvent(
new CustomEvent("vm-device-modified", { detail: id }),
);
}
}, this);
const ic = this.activeIC!;
window.App!.session.setActiveLine(window.App!.session.activeIC, ic.ip!);
}
setRegister(index: number, val: number) {
const ic = this.activeIC!;
try {
ic.setRegister(index, val);
this.dispatchEvent(
new CustomEvent("vm-device-modified", { detail: ic.id }),
);
} catch (e) {
console.log(e);
}
}
setStack(addr: number, val: number) {
const ic = this.activeIC!;
try {
ic!.setStack(addr, val);
this.dispatchEvent(
new CustomEvent("vm-device-modified", { detail: ic.id }),
);
} catch (e) {
console.log(e);
}
}
setupDeviceDatabase(db: DeviceDB) {
this.db = db;
console.log("Loaded Device Database", this.db);
}
}
class VirtualMachineUI {
vm: VirtualMachine;
state: VMStateUI;
registers: VMRegistersUI;
stack: VMStackUI;
constructor(vm: VirtualMachine) {
this.vm = vm;
this.state = new VMStateUI(this);
this.registers = new VMRegistersUI(this);
this.stack = new VMStackUI(this);
const that = this;
document.getElementById("vmControlRun")!.addEventListener(
"click",
(_event) => {
that.vm.run();
},
{ capture: true },
);
document.getElementById("vmControlStep")!.addEventListener(
"click",
(_event) => {
that.vm.step();
},
{ capture: true },
);
document.getElementById("vmControlReset")!.addEventListener(
"click",
(_event) => {
that.vm.reset();
},
{ capture: true },
);
}
update(ic: DeviceRef) {
this.state.update(ic);
this.registers.update(ic);
this.stack.update(ic);
}
}
class VMStateUI {
ui: VirtualMachineUI;
instructionPointer: HTMLElement;
instructionCounter: HTMLElement;
lastState: HTMLElement;
constructor(ui: VirtualMachineUI) {
this.ui = ui;
this.instructionPointer = document.getElementById("vmActiveICStateIP")!;
this.instructionCounter = document.getElementById("vmActiveICStateICount")!;
this.lastState = document.getElementById("vmActiveICStateLastRun")!;
}
update(ic: DeviceRef) {
if (ic) {
this.instructionPointer.innerText = ic.ip!.toString();
this.instructionCounter.innerText = ic.instructionCount!.toString();
this.lastState.innerText = ic.state!.toString();
}
}
}
class VMRegistersUI {
ui: VirtualMachineUI;
tbl: HTMLDivElement;
regCells: {
cell: HTMLDivElement;
nameLabel: HTMLSpanElement;
aliasesLabel: HTMLSpanElement;
input: HTMLInputElement;
}[];
default_aliases: Map<string, number>;
ic_aliases: Map<string, number>;
constructor(ui: VirtualMachineUI) {
const that = this;
this.ui = ui;
const regDom = document.getElementById("vmActiveRegisters")!;
this.tbl = document.createElement("div");
this.tbl.classList.add(
"d-flex",
"flex-wrap",
"justify-content-start",
"align-items-end",
);
this.regCells = [];
for (var i = 0; i < 18; i++) {
const container = document.createElement("div");
container.classList.add("vm_reg_cel", "align-that-stretch");
const cell = document.createElement("div");
cell.classList.add("input-group", "input-group-sm");
// cell.style.width = "30%";
const nameLabel = document.createElement("span");
nameLabel.innerText = `r${i}`;
nameLabel.classList.add("input-group-text");
cell.appendChild(nameLabel);
const input = document.createElement("input");
input.type = "text";
input.value = (0).toString();
input.dataset.index = i.toString();
cell.appendChild(input);
const aliasesLabel = document.createElement("span");
aliasesLabel.classList.add("input-group-text", "reg_label");
cell.appendChild(aliasesLabel);
this.regCells.push({
cell,
nameLabel,
aliasesLabel,
input,
});
container.appendChild(cell);
this.tbl.appendChild(container);
}
this.regCells.forEach((cell) => {
cell.input.addEventListener("change", that.onCellUpdate);
});
this.default_aliases = new Map([
["sp", 16],
["ra", 17],
]);
this.ic_aliases = new Map();
regDom.appendChild(this.tbl);
}
onCellUpdate(e: Event) {
let index: number;
let val: number;
let target = e.target as HTMLInputElement;
try {
index = parseInt(target.dataset.index!);
val = parseFloat(target.value);
} catch (e) {
// reset the edit
console.log(e);
window.VM!.update();
return;
}
window.VM!.setRegister(index, val);
}
update(ic: DeviceRef) {
if (ic) {
const registers = ic.registers;
if (registers) {
for (var i = 0; i < registers.length; i++) {
this.regCells[i].input.value = registers[i].toString();
}
}
const aliases = ic.aliases;
if (aliases) {
this.ic_aliases = new Map();
aliases.forEach((target, alias, _map) => {
if (
"RegisterSpec" in target &&
target.RegisterSpec.indirection == 0
) {
const index = target.RegisterSpec.target;
this.ic_aliases.set(alias, index);
}
});
}
}
// this.updateAliases();
}
updateAliases() {
const aliases = new Map([
...Array.from(this.default_aliases),
...Array.from(this.ic_aliases),
]);
const labels = new Map<number, string[]>();
for (const [alias, target] of aliases) {
if (labels.hasOwnProperty(target)) {
labels.get(target)!.push(alias);
} else {
labels.set(target, [alias]);
}
}
for (const [index, label_list] of labels) {
this.regCells[index].aliasesLabel.innerText = label_list.join(", ");
}
}
}
class VMStackUI {
ui: VirtualMachineUI;
tbl: HTMLDivElement;
stackCells: {
cell: HTMLDivElement;
nameLabel: HTMLSpanElement;
input: HTMLInputElement;
}[];
constructor(ui: VirtualMachineUI) {
this.ui = ui;
const stackDom = document.getElementById("vmActiveStack")!;
this.tbl = document.createElement("div");
this.tbl.classList.add(
"d-flex",
"flex-wrap",
"justify-content-start",
"align-items-end",
);
this.stackCells = [];
for (var i = 0; i < 512; i++) {
const container = document.createElement("div");
container.classList.add("vm_stack_cel", "align-that-stretch");
const cell = document.createElement("div");
cell.classList.add("input-group", "input-group-sm");
const nameLabel = document.createElement("span");
nameLabel.innerText = `${i}`;
nameLabel.classList.add("input-group-text");
cell.appendChild(nameLabel);
const input = document.createElement("input");
input.type = "text";
input.value = (0).toString();
input.dataset.index = i.toString();
cell.appendChild(input);
this.stackCells.push({
cell,
nameLabel,
input,
});
container.appendChild(cell);
this.tbl.appendChild(container);
}
this.stackCells.forEach((cell) => {
cell.input.addEventListener("change", this.onCellUpdate);
});
stackDom.appendChild(this.tbl);
}
onCellUpdate(e: Event) {
let index: number;
let val: number;
let target = e.target as HTMLInputElement;
try {
index = parseInt(target.dataset.index!);
val = parseFloat(target.value);
} catch (e) {
// reset the edit
window.VM!.update();
return;
}
window.VM!.setStack(index, val);
}
update(ic: DeviceRef) {
if (ic) {
const stack = ic.stack;
const sp = ic.registers![16];
if (stack) {
for (var i = 0; i < stack.length; i++) {
this.stackCells[i].input.value = stack[i].toString();
if (i == sp) {
this.stackCells[i].nameLabel.classList.add("stack_pointer");
} else {
this.stackCells[i].nameLabel.classList.remove("stack_pointer");
}
}
}
}
}
}
export { VirtualMachine, VirtualMachineUI, DeviceDB };

View File

@@ -0,0 +1,90 @@
import { html, css } from "lit";
import { customElement } from "lit/decorators.js";
import { defaultCss } from "../components";
import { VMActiveIC } from "./base_device";
import "@shoelace-style/shoelace/dist/components/card/card.js";
import "@shoelace-style/shoelace/dist/components/icon/icon.js";
import "@shoelace-style/shoelace/dist/components/tooltip/tooltip.js";
import "@shoelace-style/shoelace/dist/components/input/input.js";
import { RegisterSpec } from "ic10emu_wasm";
@customElement("vm-ic-registers")
export class VMICRegisters extends VMActiveIC {
static styles = [
...defaultCss,
css`
:host {
}
.card {
--padding: 0.5rem;
--sl-input-font-size-small: 0.75em
}
.card-body {
display: flex;
flex-flow: row wrap;
max-height: 8rem;
overflow-y: auto;
}
.reg-input {
width: 10rem;
}
`,
];
static defaultAliases: [string, number][] = [
["sp", 16],
["ra", 17],
];
constructor() {
super();
}
protected render() {
// const inputTypeFromVal = (val: number) => { if (val === Number.NEGATIVE_INFINITY || val === Number.POSITIVE_INFINITY || Number.isNaN(val)) { return "text"; } else { return "number"; } };
const displayVal = (val: number) => {
if (Number.POSITIVE_INFINITY === val) {
return "∞";
} else if (Number.NEGATIVE_INFINITY === val) {
return "-∞";
} else {
return val.toString();
}
};
const validation =
"[-+]?(([0-9]+(\\.[0-9]+)?([eE][+-]?[0-9]+)?)|((\\.[0-9]+)([eE][+-]?[0-9]+)?)|([iI][nN][fF][iI][nN][iI][tT][yY]))";
const registerAliases: [string, number][] = (
(
[...(this.aliases ?? [])].filter(
([_alias, target]) =>
"RegisterSpec" in target && target.RegisterSpec.indirection === 0,
) as [string, RegisterSpec][]
).map(([alias, target]) => [alias, target.RegisterSpec.target]) as [
string,
number,
][]
).concat(VMICRegisters.defaultAliases);
return html`
<sl-card class="card">
<div class="card-body">
${this.registers?.map((val, index) => {
const aliases = registerAliases
.filter(([_alias, target]) => index === target)
.map(([alias, _target]) => alias);
return html` <sl-input
type="text"
value="${displayVal(val)}"
pattern="${validation}"
size="small"
class="reg-input"
>
<span slot="prefix">r${index}</span>
<span slot="suffix">${aliases.join(", ")}</span>
</sl-input>`;
})}
</div>
</sl-card>
`;
}
}

View File

@@ -0,0 +1,82 @@
import { html, css } from "lit";
import { customElement } from "lit/decorators.js";
import { defaultCss } from "../components";
import { VMActiveIC } from "./base_device";
import "@shoelace-style/shoelace/dist/components/card/card.js";
import "@shoelace-style/shoelace/dist/components/icon/icon.js";
import "@shoelace-style/shoelace/dist/components/tooltip/tooltip.js";
import "@shoelace-style/shoelace/dist/components/input/input.js";
import { RegisterSpec } from "ic10emu_wasm";
@customElement("vm-ic-stack")
export class VMICStack extends VMActiveIC {
static styles = [
...defaultCss,
css`
:host {
}
.card {
--padding: 0.5rem;
--sl-input-font-size-small: 0.75em;
}
.card-body {
display: flex;
flex-flow: row wrap;
max-height: 15rem;
overflow-y: auto;
}
.stack-input {
width: 8rem;
}
.stack-pointer::part(prefix) {
background: rgb(121, 82, 179);
}
sl-input::part(prefix) {
padding-right: 0.25rem;
}
`,
];
constructor() {
super();
}
protected render() {
const displayVal = (val: number) => {
if (Number.POSITIVE_INFINITY === val) {
return "∞";
} else if (Number.NEGATIVE_INFINITY === val) {
return "-∞";
} else {
return val.toString();
}
};
const validation =
"[-+]?(([0-9]+(\\.[0-9]+)?([eE][+-]?[0-9]+)?)|((\\.[0-9]+)([eE][+-]?[0-9]+)?)|([iI][nN][fF][iI][nN][iI][tT][yY]))";
const sp = this.registers![16];
return html`
<sl-card class="card">
<div class="card-body">
${this.stack?.map((val, index) => {
return html` <sl-input
type="text"
value="${displayVal(val)}"
pattern="${validation}"
size="small"
class="stack-input ${sp === index ? "stack-pointer" : "" }"
>
<span
slot="prefix"
>
${index}
</span>
</sl-input>`;
})}
</div>
</sl-card>
`;
}
}

View File

@@ -0,0 +1,43 @@
import { HTMLTemplateResult, html, css } from "lit";
import { customElement, property, query } from "lit/decorators.js";
import { BaseElement, defaultCss } from "../components";
import "@shoelace-style/shoelace/dist/components/details/details.js";
import "./controls";
import "./registers";
import "./stack";
@customElement("vm-ui")
export class VMUI extends BaseElement {
static styles = [
...defaultCss,
css`
sl-details {
margin-left: 1rem;
margin-right: 1rem;
}
sl-details::part(header) {
padding: 0.3rem;
}
sl-details::part(content) {
padding: 0.5rem;
}
`,
];
constructor() {
super();
}
protected render() {
return html`
<vm-ic-controls></vm-ic-controls>
<sl-details summary="Registers">
<vm-ic-registers></vm-ic-registers>
</sl-details>
<sl-details summary="Stack">
<vm-ic-stack></vm-ic-stack>
</sl-details>
`;
}
}