438 lines
9.9 KiB
TypeScript
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 };
|