Files
ic10emu/www/src/ts/virtual_machine/index.ts
2024-04-28 12:53:13 -07:00

438 lines
9.9 KiB
TypeScript

import {
DeviceRef,
DeviceTemplate,
FrozenVM,
LogicType,
SlotLogicType,
SlotOccupantTemplate,
VMRef,
init,
} from "ic10emu_wasm";
import { DeviceDB } from "./device_db";
import "./base_device";
import "./device";
import { App } from "app";
export interface ToastMessage {
variant: "warning" | "danger" | "success" | "primary" | "neutral";
icon: string;
title: string;
msg: string;
id: string;
}
class VirtualMachine extends EventTarget {
ic10vm: VMRef;
_devices: Map<number, DeviceRef>;
_ics: Map<number, DeviceRef>;
db: DeviceDB;
dbPromise: Promise<{ default: DeviceDB }>;
private app: App;
constructor(app: App) {
super();
this.app = app;
const vm = init();
window.VM.set(this);
this.ic10vm = vm;
this._devices = new Map();
this._ics = new Map();
this.dbPromise = import("../../../data/database.json", {
assert: { type: "json" },
}) as Promise<{ default: DeviceDB }>;
this.dbPromise.then((module) =>
this.setupDeviceDatabase(module.default as DeviceDB),
);
this.updateDevices();
this.updateCode();
}
get devices() {
return this._devices;
}
get deviceIds() {
const ids = Array.from(this.ic10vm.devices);
ids.sort();
return ids;
}
get ics() {
return this._ics;
}
get icIds() {
return Array.from(this.ic10vm.ics);
}
get networks() {
return Array.from(this.ic10vm.networks);
}
get defaultNetwork() {
return this.ic10vm.defaultNetwork;
}
get activeIC() {
return this._ics.get(this.app.session.activeIC);
}
visibleDevices(source: number) {
const ids = Array.from(this.ic10vm.visibleDevices(source));
return ids.map((id, _index) => this._devices.get(id)!);
}
updateDevices() {
var update_flag = false;
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)!);
update_flag = true;
}
}
for (const id of this._devices.keys()) {
if (!device_ids.includes(id)) {
this._devices.delete(id);
update_flag = true;
}
}
for (const [id, device] of this._devices) {
if (typeof device.ic !== "undefined") {
if (!this._ics.has(id)) {
this._ics.set(id, device);
update_flag = true;
}
}
}
for (const id of this._ics.keys()) {
if (!this._devices.has(id)) {
this._ics.delete(id);
update_flag = true;
}
}
if (update_flag) {
const ids = Array.from(device_ids);
ids.sort();
this.dispatchEvent(
new CustomEvent("vm-devices-update", {
detail: ids,
}),
);
this.app.session.save();
}
}
updateCode() {
const progs = this.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 && ic.code !== prog) {
try {
console.time(`CompileProgram_${id}_${attempt}`);
this.ics.get(id)!.setCodeInvalid(progs.get(id)!);
const compiled = this.ics.get(id)?.program!;
this.app.session.setProgramErrors(id, compiled.errors);
this.dispatchEvent(
new CustomEvent("vm-device-modified", { detail: id }),
);
} catch (err) {
this.handleVmError(err);
} finally {
console.timeEnd(`CompileProgram_${id}_${attempt}`);
}
}
}
this.update(false);
}
step() {
const ic = this.activeIC;
if (ic) {
try {
ic.step(false);
} catch (err) {
this.handleVmError(err);
}
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 (err) {
this.handleVmError(err);
}
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(save: boolean = true) {
this.updateDevices();
this.ic10vm.lastOperationModified.forEach((id, _index, _modifiedIds) => {
if (this.devices.has(id)) {
this.dispatchEvent(
new CustomEvent("vm-device-modified", { detail: id }),
);
}
}, this);
this.updateDevice(this.activeIC, save);
if (save) this.app.session.save();
}
updateDevice(device: DeviceRef, save: boolean = true) {
this.dispatchEvent(
new CustomEvent("vm-device-modified", { detail: device.id }),
);
if (typeof device.ic !== "undefined") {
this.app.session.setActiveLine(device.id, device.ip!);
}
if (save) this.app.session.save();
}
handleVmError(err: Error) {
console.log("Error in Virtual Machine", err);
const message: ToastMessage = {
variant: "danger",
icon: "bug",
title: `Error in Virtual Machine ${err.name}`,
msg: err.message,
id: Date.now().toString(16),
};
this.dispatchEvent(new CustomEvent("vm-message", { detail: message }));
}
changeDeviceId(old_id: number, new_id: number): boolean {
try {
this.ic10vm.changeDeviceId(old_id, new_id);
this.updateDevices();
if (this.app.session.activeIC === old_id) {
this.app.session.activeIC = new_id;
}
return true;
} catch (err) {
this.handleVmError(err);
return false;
}
}
setRegister(index: number, val: number): boolean {
const ic = this.activeIC!;
try {
ic.setRegister(index, val);
this.updateDevice(ic);
return true;
} catch (err) {
this.handleVmError(err);
return false;
}
}
setStack(addr: number, val: number): boolean {
const ic = this.activeIC!;
try {
ic!.setStack(addr, val);
this.updateDevice(ic);
return true;
} catch (err) {
this.handleVmError(err);
return false;
}
}
setDeviceName(id: number, name: string): boolean {
const device = this._devices.get(id);
if (device) {
try {
device.setName(name);
this.dispatchEvent(
new CustomEvent("vm-device-modified", { detail: id }),
);
this.app.session.save();
return true;
} catch (e) {
this.handleVmError(e);
}
}
return false;
}
setDeviceField(
id: number,
field: LogicType,
val: number,
force?: boolean,
): boolean {
force = force ?? false;
const device = this._devices.get(id);
if (device) {
try {
device.setField(field, val, force);
this.updateDevice(device);
return true;
} catch (err) {
this.handleVmError(err);
}
}
return false;
}
setDeviceSlotField(
id: number,
slot: number,
field: SlotLogicType,
val: number,
force?: boolean,
): boolean {
force = force ?? false;
const device = this._devices.get(id);
if (device) {
try {
device.setSlotField(slot, field, val, false);
this.updateDevice(device);
return true;
} catch (err) {
this.handleVmError(err);
}
}
return false;
}
setDeviceConnection(
id: number,
conn: number,
val: number | undefined,
): boolean {
const device = this._devices.get(id);
if (typeof device !== "undefined") {
try {
this.ic10vm.setDeviceConnection(id, conn, val);
this.updateDevice(device);
return true;
} catch (err) {
this.handleVmError(err);
}
}
return false;
}
setDevicePin(id: number, pin: number, val: number | undefined): boolean {
const device = this._devices.get(id);
if (typeof device !== "undefined") {
try {
this.ic10vm.setPin(id, pin, val);
this.updateDevice(device);
return true;
} catch (err) {
this.handleVmError(err);
}
}
return false;
}
setupDeviceDatabase(db: DeviceDB) {
this.db = db;
console.log("Loaded Device Database", this.db);
this.dispatchEvent(
new CustomEvent("vm-device-db-loaded", { detail: this.db }),
);
}
addDeviceFromTemplate(template: DeviceTemplate): boolean {
try {
console.log("adding device", template);
const id = this.ic10vm.addDeviceFromTemplate(template);
this._devices.set(id, this.ic10vm.getDevice(id)!);
const device_ids = this.ic10vm.devices;
this.dispatchEvent(
new CustomEvent("vm-devices-update", {
detail: Array.from(device_ids),
}),
);
this.app.session.save();
return true;
} catch (err) {
this.handleVmError(err);
return false;
}
}
removeDevice(id: number): boolean {
try {
this.ic10vm.removeDevice(id);
this.updateDevices();
return true;
} catch (err) {
this.handleVmError(err);
return false;
}
}
setDeviceSlotOccupant(id: number, index: number, template: SlotOccupantTemplate): boolean {
const device = this._devices.get(id);
if (typeof device !== "undefined") {
try {
console.log("setting slot occupant", template);
this.ic10vm.setSlotOccupant(id, index, template);
this.updateDevice(device);
return true;
} catch (err) {
this.handleVmError(err);
}
}
return false;
}
saveVMState(): FrozenVM {
return this.ic10vm.saveVMState();
}
restoreVMState(state: FrozenVM) {
try {
this.ic10vm.restoreVMState(state);
this._devices = new Map();
this._ics = new Map();
this.updateDevices();
} catch (e) {
this.handleVmError(e);
}
}
getPrograms() {
const programs: [number, string][] = Array.from(this._ics.entries()).map(
([id, ic]) => [id, ic.code],
);
return programs;
}
}
export { VirtualMachine };