refactor(ui): start using signals

smaller DOM updates  & easier data update paths.
fix sl-select problems with force update on signal sub

Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com>
This commit is contained in:
Rachel Powers
2024-08-16 23:40:17 -07:00
parent 3da16d1f03
commit 70833e0b00
11 changed files with 4368 additions and 3589 deletions

223
rust-testing.ipynb Normal file
View File

@@ -0,0 +1,223 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": 20,
"metadata": {},
"outputs": [],
"source": [
":dep stationeers_data = { path = \"./stationeers_data\" }\n",
":dep const-crc32 = \"1.3.0\"\n",
":dep color-eyre\n",
":dep serde_path_to_error\n",
":dep serde_ignored\n",
":dep serde\n",
":dep serde_json\n"
]
},
{
"cell_type": "code",
"execution_count": 21,
"metadata": {},
"outputs": [],
"source": [
"use color_eyre::eyre;\n",
"pub fn parse_value<'a, T: serde::Deserialize<'a>>(\n",
" jd: impl serde::Deserializer<'a>,\n",
") -> Result<T, color_eyre::Report> {\n",
" let mut track = serde_path_to_error::Track::new();\n",
" let path = serde_path_to_error::Deserializer::new(jd, &mut track);\n",
" let mut fun = |path: serde_ignored::Path| {\n",
" eprintln!(\"Found ignored key: {path}\");\n",
" };\n",
" serde_ignored::deserialize(path, &mut fun).map_err(|e| {\n",
" eyre::eyre!(\n",
" \"path: {track} | error = {e}\",\n",
" track = track.path().to_string(),\n",
" )\n",
" })\n",
"}"
]
},
{
"cell_type": "code",
"execution_count": 25,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"{7274344: StructureLogicDevice(StructureLogicDeviceTemplate { prefab: PrefabInfo { prefab_name: \"StructureAutoMinerSmall\", prefab_hash: 7274344, desc: \"The <link=Recurso><color=#0080FFFF>Recurso</color></link> SquareDig autominer is a structure that when built will mine a vertical 2x2 shaft until it hits bedrock. The autominer can be connected to a chute system, and is controllable by a logic network. Note that the autominer outputs more <link=OrePage><color=#0080FFFF>ore</color></link> than a conventional <link=ThingItemMiningDrill><color=green>Mining Drill</color></link> over the same area.\", name: \"Autominer (Small)\" }, structure: StructureInfo { small_grid: true }, thermal_info: None, internal_atmo_info: None, logic: LogicInfo { logic_slot_types: {0: {}, 1: {}}, logic_types: {Power: Read, Open: ReadWrite, Error: Read, Activate: ReadWrite, On: ReadWrite, RequiredPower: Read, ClearMemory: Write, ExportCount: Read, ImportCount: Read, PrefabHash: Read, ReferenceId: Read, NameHash: Read}, modes: None, transmission_receiver: false, wireless_logic: false, circuit_holder: false }, slots: [SlotInfo { name: \"Import\", typ: None }, SlotInfo { name: \"Export\", typ: None }], device: DeviceInfo { connection_list: [ConnectionInfo { typ: Chute, role: Input }, ConnectionInfo { typ: Chute, role: Output }, ConnectionInfo { typ: Data, role: None }, ConnectionInfo { typ: Power, role: None }], device_pins_length: None, has_activate_state: true, has_atmosphere: false, has_color_state: false, has_lock_state: false, has_mode_state: false, has_on_off_state: true, has_open_state: true, has_reagents: false } }), 111280987: ItemLogic(ItemLogicTemplate { prefab: PrefabInfo { prefab_name: \"ItemTerrainManipulator\", prefab_hash: 111280987, desc: \"0.Mode0\\n1.Mode1\", name: \"Terrain Manipulator\" }, item: ItemInfo { consumable: false, filter_type: None, ingredient: false, max_quantity: 1, reagents: None, slot_class: Tool, sorting_class: Default }, thermal_info: None, internal_atmo_info: None, logic: LogicInfo { logic_slot_types: {0: {Occupied: Read, OccupantHash: Read, Quantity: Read, Damage: Read, Charge: Read, ChargeRatio: Read, Class: Read, MaxQuantity: Read, ReferenceId: Read}, 1: {Occupied: Read, OccupantHash: Read, Quantity: Read, Damage: Read, Class: Read, MaxQuantity: Read, ReferenceId: Read}}, logic_types: {Power: Read, Mode: ReadWrite, Error: Read, Activate: ReadWrite, On: ReadWrite, ReferenceId: Read}, modes: Some({0: \"Mode0\", 1: \"Mode1\"}), transmission_receiver: false, wireless_logic: false, circuit_holder: false }, slots: [SlotInfo { name: \"Battery\", typ: Battery }, SlotInfo { name: \"Dirt Canister\", typ: Ore }] })}\n"
]
}
],
"source": [
"let entries = r#\"\n",
"{\n",
"\"7274344\": {\n",
" \"templateType\": \"StructureLogicDevice\",\n",
" \"prefab\": {\n",
" \"prefab_name\": \"StructureAutoMinerSmall\",\n",
" \"prefab_hash\": 7274344,\n",
" \"desc\": \"The <link=Recurso><color=#0080FFFF>Recurso</color></link> SquareDig autominer is a structure that when built will mine a vertical 2x2 shaft until it hits bedrock. The autominer can be connected to a chute system, and is controllable by a logic network. Note that the autominer outputs more <link=OrePage><color=#0080FFFF>ore</color></link> than a conventional <link=ThingItemMiningDrill><color=green>Mining Drill</color></link> over the same area.\",\n",
" \"name\": \"Autominer (Small)\"\n",
" },\n",
" \"structure\": {\n",
" \"small_grid\": true\n",
" },\n",
" \"logic\": {\n",
" \"logic_slot_types\": {\n",
" \"0\": {},\n",
" \"1\": {}\n",
" },\n",
" \"logic_types\": {\n",
" \"Power\": \"Read\",\n",
" \"Open\": \"ReadWrite\",\n",
" \"Error\": \"Read\",\n",
" \"Activate\": \"ReadWrite\",\n",
" \"On\": \"ReadWrite\",\n",
" \"RequiredPower\": \"Read\",\n",
" \"ClearMemory\": \"Write\",\n",
" \"ExportCount\": \"Read\",\n",
" \"ImportCount\": \"Read\",\n",
" \"PrefabHash\": \"Read\",\n",
" \"ReferenceId\": \"Read\",\n",
" \"NameHash\": \"Read\"\n",
" },\n",
" \"transmission_receiver\": false,\n",
" \"wireless_logic\": false,\n",
" \"circuit_holder\": false\n",
" },\n",
" \"slots\": [\n",
" {\n",
" \"name\": \"Import\",\n",
" \"typ\": \"None\"\n",
" },\n",
" {\n",
" \"name\": \"Export\",\n",
" \"typ\": \"None\"\n",
" }\n",
" ],\n",
" \"device\": {\n",
" \"connection_list\": [\n",
" {\n",
" \"typ\": \"Chute\",\n",
" \"role\": \"Input\"\n",
" },\n",
" {\n",
" \"typ\": \"Chute\",\n",
" \"role\": \"Output\"\n",
" },\n",
" {\n",
" \"typ\": \"Data\",\n",
" \"role\": \"None\"\n",
" },\n",
" {\n",
" \"typ\": \"Power\",\n",
" \"role\": \"None\"\n",
" }\n",
" ],\n",
" \"has_activate_state\": true,\n",
" \"has_atmosphere\": false,\n",
" \"has_color_state\": false,\n",
" \"has_lock_state\": false,\n",
" \"has_mode_state\": false,\n",
" \"has_on_off_state\": true,\n",
" \"has_open_state\": true,\n",
" \"has_reagents\": false\n",
" }\n",
" },\n",
" \"111280987\": {\n",
" \"templateType\": \"ItemLogic\",\n",
" \"prefab\": {\n",
" \"prefab_name\": \"ItemTerrainManipulator\",\n",
" \"prefab_hash\": 111280987,\n",
" \"desc\": \"0.Mode0\\n1.Mode1\",\n",
" \"name\": \"Terrain Manipulator\"\n",
" },\n",
" \"item\": {\n",
" \"consumable\": false,\n",
" \"ingredient\": false,\n",
" \"max_quantity\": 1,\n",
" \"slot_class\": \"Tool\",\n",
" \"sorting_class\": \"Default\"\n",
" },\n",
" \"logic\": {\n",
" \"logic_slot_types\": {\n",
" \"0\": {\n",
" \"Occupied\": \"Read\",\n",
" \"OccupantHash\": \"Read\",\n",
" \"Quantity\": \"Read\",\n",
" \"Damage\": \"Read\",\n",
" \"Charge\": \"Read\",\n",
" \"ChargeRatio\": \"Read\",\n",
" \"Class\": \"Read\",\n",
" \"MaxQuantity\": \"Read\",\n",
" \"ReferenceId\": \"Read\"\n",
" },\n",
" \"1\": {\n",
" \"Occupied\": \"Read\",\n",
" \"OccupantHash\": \"Read\",\n",
" \"Quantity\": \"Read\",\n",
" \"Damage\": \"Read\",\n",
" \"Class\": \"Read\",\n",
" \"MaxQuantity\": \"Read\",\n",
" \"ReferenceId\": \"Read\"\n",
" }\n",
" },\n",
" \"logic_types\": {\n",
" \"Power\": \"Read\",\n",
" \"Mode\": \"ReadWrite\",\n",
" \"Error\": \"Read\",\n",
" \"Activate\": \"ReadWrite\",\n",
" \"On\": \"ReadWrite\",\n",
" \"ReferenceId\": \"Read\"\n",
" },\n",
" \"modes\": {\n",
" \"0\": \"Mode0\",\n",
" \"1\": \"Mode1\"\n",
" },\n",
" \"transmission_receiver\": false,\n",
" \"wireless_logic\": false,\n",
" \"circuit_holder\": false\n",
" },\n",
" \"slots\": [\n",
" {\n",
" \"name\": \"Battery\",\n",
" \"typ\": \"Battery\"\n",
" },\n",
" {\n",
" \"name\": \"Dirt Canister\",\n",
" \"typ\": \"Ore\"\n",
" }\n",
" ]\n",
" }\n",
"}\n",
"\"#;\n",
"use std::collections::BTreeMap;\n",
"use stationeers_data::templates::ObjectTemplate;\n",
"let parsed_db: BTreeMap<i32, ObjectTemplate> =\n",
" parse_value(&mut serde_json::Deserializer::from_str(entries))?;\n",
"println!(\"{parsed_db:?}\");"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Rust",
"language": "rust",
"name": "rust"
},
"language_info": {
"codemirror_mode": "rust",
"file_extension": ".rs",
"mimetype": "text/rust",
"name": "rust",
"pygment_lexer": "rust",
"version": ""
}
},
"nbformat": 4,
"nbformat_minor": 2
}

View File

@@ -54,6 +54,8 @@
"brnez",
"Circuitboard",
"codegen",
"Comlink",
"datapoints",
"Depressurising",
"deviceslength",
"endpos",
@@ -62,13 +64,16 @@
"hardwrap",
"hashables",
"hstack",
"idxs",
"infile",
"jetpack",
"Keybind",
"labelledby",
"lbns",
"logicable",
"LogicSlotType",
"logicslottypes",
"LogicSlotTypes",
"logictype",
"logictypes",
"lparen",
@@ -82,6 +87,7 @@
"pedia",
"pinf",
"popperjs",
"preact",
"preproc",
"Pressurising",
"putd",
@@ -103,8 +109,6 @@
"slotclass",
"slotlogic",
"slotlogicable",
"LogicSlotType",
"LogicSlotTypes",
"slottype",
"sltz",
"snan",

View File

@@ -25,12 +25,12 @@
"homepage": "https://github.com/ryex/ic10emu#readme",
"devDependencies": {
"@oneidentity/zstd-js": "^1.0.3",
"@rsbuild/core": "^0.6.4",
"@rsbuild/plugin-image-compress": "^0.6.4",
"@rsbuild/plugin-type-check": "^0.6.4",
"@rspack/cli": "^0.6.2",
"@rspack/core": "^0.6.2",
"@swc/helpers": "^0.5.10",
"@rsbuild/core": "^0.7.10",
"@rsbuild/plugin-image-compress": "^0.7.10",
"@rsbuild/plugin-type-check": "^0.7.10",
"@rspack/cli": "^0.7.5",
"@rspack/core": "^0.7.5",
"@swc/helpers": "^0.5.12",
"@types/ace": "^0.0.52",
"@types/bootstrap": "^5.2.10",
"@types/wicg-file-system-access": "^2023.10.5",
@@ -38,34 +38,35 @@
"lit-scss-loader": "^2.0.1",
"mini-css-extract-plugin": "^2.9.0",
"postcss-loader": "^8.1.1",
"sass": "^1.75.0",
"tailwindcss": "^3.4.3",
"sass": "^1.77.8",
"tailwindcss": "^3.4.10",
"ts-lit-plugin": "^2.0.2",
"ts-loader": "^9.5.1",
"typescript": "^5.4.5",
"typescript": "^5.5.4",
"typescript-lit-html-plugin": "^0.9.0"
},
"dependencies": {
"@leeoniya/ufuzzy": "^1.0.14",
"@lit/context": "^1.1.1",
"@lit-labs/preact-signals": "^1.0.2",
"@lit/context": "^1.1.2",
"@popperjs/core": "^2.11.8",
"@shoelace-style/shoelace": "^2.15.0",
"ace-builds": "^1.33.0",
"ace-linters": "^1.2.0",
"@shoelace-style/shoelace": "^2.16.0",
"ace-builds": "^1.35.4",
"ace-linters": "^1.2.3",
"bootstrap": "^5.3.3",
"bson": "^6.6.0",
"bson": "^6.8.0",
"buffer": "^6.0.3",
"comlink": "^4.4.1",
"crypto-browserify": "^3.12.0",
"ic10emu_wasm": "file:../ic10emu_wasm/pkg",
"ic10lsp_wasm": "file:../ic10lsp_wasm/pkg",
"ic10emu_wasm": "file:..\\ic10emu_wasm\\pkg",
"ic10lsp_wasm": "file:..\\ic10lsp_wasm\\pkg",
"idb": "^8.0.0",
"jquery": "^3.7.1",
"lit": "^3.1.3",
"lit": "^3.2.0",
"lzma-web": "^3.0.1",
"marked": "^12.0.2",
"marked": "^14.0.0",
"stream-browserify": "^3.0.0",
"uuid": "^9.0.1",
"uuid": "^10.0.0",
"vm-browserify": "^1.1.2"
}
}

6286
www/pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -14,58 +14,15 @@ import type {
LogicSlotType,
SlotOccupantInfo,
ICState,
ObjectTemplate,
} from "ic10emu_wasm";
import { crc32, structuralEqual } from "utils";
import { LitElement, PropertyValueMap } from "lit";
type Constructor<T = {}> = new (...args: any[]) => T;
export declare class VMObjectMixinInterface {
objectID: ObjectID;
activeICId: ObjectID;
obj: FrozenObjectFull;
name: string | null;
nameHash: number | null;
prefabName: string | null;
prefabHash: number | null;
logicFields: Map<LogicType, LogicField> | null;
slots: VmObjectSlotInfo[] | null;
slotsCount: number | null;
reagents: Map<number, number> | null;
connections: Connection[] | null;
icIP: number | null;
icOpCount: number | null;
icState: string | null;
errors: ICError[] | null;
registers: number[] | null;
memory: number[] | null;
aliases: Map<string, Operand> | null;
defines: Map<string, number> | null;
numPins: number | null;
pins: Map<number, ObjectID> | null;
visibleDevices: ObjectID[] | null;
_handleDeviceModified(e: CustomEvent): void;
updateDevice(): void;
updateIC(): void;
subscribe(...sub: VMObjectMixinSubscription[]): void;
unsubscribe(filter: (sub: VMObjectMixinSubscription) => boolean): void;
}
export type VMObjectMixinSubscription =
| "name"
| "nameHash"
| "prefabName"
| "fields"
| "slots"
| "slots-count"
| "reagents"
| "connections"
| "memory"
| "ic"
| "active-ic"
| { field: LogicType }
| { slot: number }
| "visible-devices";
import {
computed,
} from '@lit-labs/preact-signals';
import type { Signal } from '@lit-labs/preact-signals';
export interface VmObjectSlotInfo {
parent: ObjectID;
@@ -74,9 +31,309 @@ export interface VmObjectSlotInfo {
typ: Class;
logicFields: Map<LogicSlotType, LogicField>;
quantity: number;
occupant: FrozenObjectFull | undefined;
occupant: ComputedObjectSignals | null;
}
export class ComputedObjectSignals {
obj: Signal<FrozenObjectFull>;
id: Signal<number>;
template: Signal<ObjectTemplate>;
name: Signal<string | null>;
nameHash: Signal<number | null>;
prefabName: Signal<string | null>;
prefabHash: Signal<number | null>;
displayName: Signal<string>;
logicFields: Signal<Map<LogicType, LogicField> | null>;
slots: Signal<VmObjectSlotInfo[] | null>;
slotsCount: Signal<number | null>;
reagents: Signal<Map<number, number> | null>;
connections: Signal<Connection[] | null>;
visibleDevices: Signal<ComputedObjectSignals[]>;
memory: Signal<number[] | null>;
icIP: Signal<number | null>;
icOpCount: Signal<number | null>;
icState: Signal<ICState | null>;
errors: Signal<ICError[] | null>;
registers: Signal<number[] | null>;
aliases: Signal<Map<string, Operand> | null>;
defines: Signal<Map<string, number> | null>;
numPins: Signal<number | null>;
pins: Signal<Map<number, ObjectID> | null>;
constructor(obj: Signal<FrozenObjectFull>) {
this.obj = obj
this.id = computed(() => { return this.obj.value.obj_info.id; });
this.template = computed(() => { return this.obj.value.template; });
this.name = computed(() => { return this.obj.value.obj_info.name; });
this.nameHash = computed(() => { return this.name.value !== "undefined" ? crc32(this.name.value) : null; });
this.prefabName = computed(() => { return this.obj.value.obj_info.prefab; });
this.prefabHash = computed(() => { return this.obj.value.obj_info.prefab_hash; });
this.displayName = computed(() => { return this.obj.value.obj_info.name ?? this.obj.value.obj_info.prefab; });
this.logicFields = computed(() => {
const obj_info = this.obj.value.obj_info;
const template = this.obj.value.template;
const logicValues =
obj_info.logic_values != null
? (new Map(Object.entries(obj_info.logic_values)) as Map<
LogicType,
number
>)
: null;
const logicTemplate =
"logic" in template ? template.logic : null;
return new Map(
Array.from(Object.entries(logicTemplate?.logic_types) ?? []).map(
([lt, access]) => {
let field: LogicField = {
field_type: access,
value: logicValues.get(lt as LogicType) ?? 0,
};
return [lt as LogicType, field];
},
),
)
});
this.slots = computed(() => {
const obj_info = this.obj.value.obj_info;
const template = this.obj.value.template;
const slotsOccupantInfo =
obj_info.slots != null
? new Map(
Object.entries(obj_info.slots).map(([key, val]) => [
parseInt(key),
val,
]),
)
: null;
const slotsLogicValues =
obj_info.slot_logic_values != null
? new Map<number, Map<LogicSlotType, number>>(
Object.entries(obj_info.slot_logic_values).map(
([index, values]) => [
parseInt(index),
new Map(Object.entries(values)) as Map<
LogicSlotType,
number
>,
],
),
)
: null;
const logicTemplate =
"logic" in template ? template.logic : null;
const slotsTemplate =
"slots" in template ? template.slots : [];
return slotsTemplate.map((template, index) => {
const fieldEntryInfos = Array.from(
Object.entries(logicTemplate?.logic_slot_types[index]) ?? [],
);
const logicFields = new Map(
fieldEntryInfos.map(([slt, access]) => {
let field: LogicField = {
field_type: access,
value:
slotsLogicValues.get(index)?.get(slt as LogicSlotType) ?? 0,
};
return [slt as LogicSlotType, field];
}),
);
let occupantInfo = slotsOccupantInfo.get(index);
let occupant =
typeof occupantInfo !== "undefined"
? globalObjectSignalMap.get(occupantInfo.id) ?? null
: null;
let slot: VmObjectSlotInfo = {
parent: obj_info.id,
index: index,
name: template.name,
typ: template.typ,
logicFields: logicFields,
occupant: occupant,
quantity: occupantInfo?.quantity ?? 0,
};
return slot;
});
});
this.slotsCount = computed(() => {
const slotsTemplate =
"slots" in this.obj.value.template ? this.obj.value.template.slots : [];
return slotsTemplate.length;
});
this.reagents = computed(() => {
const reagents =
this.obj.value.obj_info.reagents != null
? new Map(
Object.entries(this.obj.value.obj_info.reagents).map(
([key, val]) => [parseInt(key), val],
),
)
: null;
return reagents;
});
this.connections = computed(() => {
const obj_info = this.obj.value.obj_info;
const template = this.obj.value.template;
const connectionsMap =
obj_info.connections != null
? new Map(
Object.entries(obj_info.connections).map(
([key, val]) => [parseInt(key), val],
),
)
: null;
const connectionList =
"device" in template
? template.device.connection_list
: [];
let connections: Connection[] | null = null;
if (connectionList.length !== 0) {
connections = connectionList.map((conn, index) => {
if (conn.typ === "Data") {
return {
CableNetwork: {
typ: "Data",
role: conn.role,
net: connectionsMap.get(index),
},
};
} else if (conn.typ === "Power") {
return {
CableNetwork: {
typ: "Power",
role: conn.role,
net: connectionsMap.get(index),
},
};
} else if (conn.typ === "PowerAndData") {
return {
CableNetwork: {
typ: "Data",
role: conn.role,
net: connectionsMap.get(index),
},
};
} else if (conn.typ === "Pipe") {
return { Pipe: { role: conn.role } };
} else if (conn.typ === "Chute") {
return { Chute: { role: conn.role } };
} else if (conn.typ === "Elevator") {
return { Elevator: { role: conn.role } };
} else if (conn.typ === "LaunchPad") {
return { LaunchPad: { role: conn.role } };
} else if (conn.typ === "LandingPad") {
return { LandingPad: { role: conn.role } };
} else if (conn.typ === "PipeLiquid") {
return { PipeLiquid: { role: conn.role } };
}
return "None";
});
}
return connections;
});
this.visibleDevices = computed(() => {
return this.obj.value.obj_info.visible_devices.map((id) => globalObjectSignalMap.get(id))
});
this.memory = computed(() => {
return this.obj.value.obj_info.memory ?? null;
});
this.icIP = computed(() => {
return this.obj.value.obj_info.circuit?.instruction_pointer ?? null;
});
this.icOpCount = computed(() => {
return this.obj.value.obj_info.circuit?.yield_instruction_count ?? null;
});
this.icState = computed(() => {
return this.obj.value.obj_info.circuit?.state ?? null;
});
this.errors = computed(() => {
return this.obj.value.obj_info.compile_errors ?? null;
});
this.registers = computed(() => {
return this.obj.value.obj_info.circuit?.registers ?? null;
});
this.aliases = computed(() => {
const aliases = this.obj.value.obj_info.circuit?.aliases ?? null;
return aliases != null ? new Map(Object.entries(aliases)) : null;
});
this.defines = computed(() => {
const defines = this.obj.value.obj_info.circuit?.defines ?? null;
return defines != null ? new Map(Object.entries(defines)) : null;
});
this.pins = computed(() => {
const pins = this.obj.value.obj_info.device_pins;
return pins != null ? new Map(Object.entries(pins).map(([key, val]) => [parseInt(key), val])) : null;
});
this.numPins = computed(() => {
return "device" in this.obj.value.template
? this.obj.value.template.device.device_pins_length
: Math.max(...Array.from(this.pins.value?.keys() ?? [0]));
});
}
}
class ObjectComputedSignalMap extends Map {
get(id: ObjectID): ComputedObjectSignals {
if (!this.has(id)) {
const obj = window.VM.vm.objects.get(id)
if (typeof obj !== "undefined") {
this.set(id, new ComputedObjectSignals(obj));
}
}
return super.get(id);
}
set(id: ObjectID, value: ComputedObjectSignals): this {
super.set(id, value);
return this
}
}
export const globalObjectSignalMap = new ObjectComputedSignalMap();
type Constructor<T = {}> = new (...args: any[]) => T;
export declare class VMObjectMixinInterface {
objectID: ObjectID;
activeICId: ObjectID;
objectSignals: ComputedObjectSignals | null;
_handleDeviceModified(e: CustomEvent): void;
updateDevice(): void;
subscribe(...sub: VMObjectMixinSubscription[]): void;
unsubscribe(filter: (sub: VMObjectMixinSubscription) => boolean): void;
}
export type VMObjectMixinSubscription =
| "active-ic"
| "visible-devices";
export const VMObjectMixin = <T extends Constructor<LitElement>>(
superClass: T,
) => {
@@ -104,31 +361,10 @@ export const VMObjectMixin = <T extends Constructor<LitElement>>(
);
}
obj: FrozenObjectFull;
@state() objectSignals: ComputedObjectSignals | null;
@state() activeICId: number;
@state() name: string | null = null;
@state() nameHash: number | null = null;
@state() prefabName: string | null = null;
@state() prefabHash: number | null = null;
@state() logicFields: Map<LogicType, LogicField> | null = null;
@state() slots: VmObjectSlotInfo[] | null = null;
@state() slotsCount: number | null = null;
@state() reagents: Map<number, number> | null = null;
@state() connections: Connection[] | null = null;
@state() icIP: number | null = null;
@state() icOpCount: number | null = null;
@state() icState: ICState | null = null;
@state() errors: ICError[] | null = null;
@state() registers: number[] | null = null;
@state() memory: number[] | null = null;
@state() aliases: Map<string, Operand> | null = null;
@state() defines: Map<string, number> | null = null;
@state() numPins: number | null = null;
@state() pins: Map<number, ObjectID> | null = null;
@state() visibleDevices: ObjectID[] | null = null;
connectedCallback(): void {
const root = super.connectedCallback();
window.VM.get().then((vm) => {
@@ -246,315 +482,20 @@ export const VMObjectMixin = <T extends Constructor<LitElement>>(
}
updateDevice() {
this.obj = window.VM.vm.objects.get(this.objectID)!;
const newObjSignals = globalObjectSignalMap.get(this.objectID);
if (newObjSignals !== this.objectSignals) {
this.objectSignals = newObjSignals
}
if (typeof this.obj === "undefined") {
if (typeof this.objectSignals === "undefined") {
return;
}
let newFields: Map<LogicType, LogicField> | null = null;
if (
this.objectSubscriptions.some(
(sub) =>
sub === "fields" || (typeof sub === "object" && "field" in sub),
)
) {
const logicValues =
this.obj.obj_info.logic_values != null
? (new Map(Object.entries(this.obj.obj_info.logic_values)) as Map<
LogicType,
number
>)
: null;
const logicTemplate =
"logic" in this.obj.template ? this.obj.template.logic : null;
newFields = new Map(
Array.from(Object.entries(logicTemplate?.logic_types) ?? []).map(
([lt, access]) => {
let field: LogicField = {
field_type: access,
value: logicValues.get(lt as LogicType) ?? 0,
};
return [lt as LogicType, field];
},
),
);
}
// other updates needed
const visibleDevices = this.obj.obj_info.visible_devices ?? [];
if (!structuralEqual(this.visibleDevices, visibleDevices)) {
this.visibleDevices = visibleDevices;
}
let newSlots: VmObjectSlotInfo[] | null = null;
if (
this.objectSubscriptions.some(
(sub) =>
sub === "slots" || (typeof sub === "object" && "slot" in sub),
)
) {
const slotsOccupantInfo =
this.obj.obj_info.slots != null
? new Map(
Object.entries(this.obj.obj_info.slots).map(([key, val]) => [
parseInt(key),
val,
]),
)
: null;
const slotsLogicValues =
this.obj.obj_info.slot_logic_values != null
? new Map<number, Map<LogicSlotType, number>>(
Object.entries(this.obj.obj_info.slot_logic_values).map(
([index, values]) => [
parseInt(index),
new Map(Object.entries(values)) as Map<
LogicSlotType,
number
>,
],
),
)
: null;
const logicTemplate =
"logic" in this.obj.template ? this.obj.template.logic : null;
const slotsTemplate =
"slots" in this.obj.template ? this.obj.template.slots : [];
newSlots = slotsTemplate.map((template, index) => {
const fieldEntryInfos = Array.from(
Object.entries(logicTemplate?.logic_slot_types[index]) ?? [],
);
const logicFields = new Map(
fieldEntryInfos.map(([slt, access]) => {
let field: LogicField = {
field_type: access,
value:
slotsLogicValues.get(index)?.get(slt as LogicSlotType) ?? 0,
};
return [slt as LogicSlotType, field];
}),
);
let occupantInfo = slotsOccupantInfo.get(index);
let occupant =
typeof occupantInfo !== "undefined"
? window.VM.vm.objects.get(occupantInfo.id)
: null;
let slot: VmObjectSlotInfo = {
parent: this.obj.obj_info.id,
index: index,
name: template.name,
typ: template.typ,
logicFields: logicFields,
occupant: occupant,
quantity: occupantInfo?.quantity ?? 0,
};
return slot;
});
}
for (const sub of this.objectSubscriptions) {
if (typeof sub === "string") {
if (sub == "name") {
const name = this.obj.obj_info.name ?? null;
if (this.name !== name) {
this.name = name;
}
} else if (sub === "nameHash") {
const nameHash =
typeof this.obj.obj_info.name !== "undefined"
? crc32(this.obj.obj_info.name)
: null;
if (this.nameHash !== nameHash) {
this.nameHash = nameHash;
}
} else if (sub === "prefabName") {
const prefabName = this.obj.obj_info.prefab ?? null;
if (this.prefabName !== prefabName) {
this.prefabName = prefabName;
this.prefabHash = crc32(prefabName);
}
} else if (sub === "fields") {
if (!structuralEqual(this.logicFields, newFields)) {
this.logicFields = newFields;
}
} else if (sub === "slots") {
if (!structuralEqual(this.slots, newSlots)) {
this.slots = newSlots;
this.slotsCount = newSlots.length;
}
} else if (sub === "slots-count") {
const slotsTemplate =
"slots" in this.obj.template ? this.obj.template.slots : [];
const slotsCount = slotsTemplate.length;
if (this.slotsCount !== slotsCount) {
this.slotsCount = slotsCount;
}
} else if (sub === "reagents") {
const reagents =
this.obj.obj_info.reagents != null
? new Map(
Object.entries(this.obj.obj_info.reagents).map(
([key, val]) => [parseInt(key), val],
),
)
: null;
if (!structuralEqual(this.reagents, reagents)) {
this.reagents = reagents;
}
} else if (sub === "connections") {
const connectionsMap =
this.obj.obj_info.connections != null
? new Map(
Object.entries(this.obj.obj_info.connections).map(
([key, val]) => [parseInt(key), val],
),
)
: null;
const connectionList =
"device" in this.obj.template
? this.obj.template.device.connection_list
: [];
let connections: Connection[] | null = null;
if (connectionList.length !== 0) {
connections = connectionList.map((conn, index) => {
if (conn.typ === "Data") {
return {
CableNetwork: {
typ: "Data",
role: conn.role,
net: connectionsMap.get(index),
},
};
} else if (conn.typ === "Power") {
return {
CableNetwork: {
typ: "Power",
role: conn.role,
net: connectionsMap.get(index),
},
};
} else if (conn.typ === "PowerAndData") {
return {
CableNetwork: {
typ: "Data",
role: conn.role,
net: connectionsMap.get(index),
},
};
} else if (conn.typ === "Pipe") {
return { Pipe: { role: conn.role } };
} else if (conn.typ === "Chute") {
return { Chute: { role: conn.role } };
} else if (conn.typ === "Elevator") {
return { Elevator: { role: conn.role } };
} else if (conn.typ === "LaunchPad") {
return { LaunchPad: { role: conn.role } };
} else if (conn.typ === "LandingPad") {
return { LandingPad: { role: conn.role } };
} else if (conn.typ === "PipeLiquid") {
return { PipeLiquid: { role: conn.role } };
}
return "None";
});
}
if (!structuralEqual(this.connections, connections)) {
this.connections = connections;
}
} else if (sub === "memory") {
const stack = this.obj.obj_info.memory ?? null;
if (!structuralEqual(this.memory, stack)) {
this.memory = stack;
}
} else if (sub === "ic") {
if (
typeof this.obj.obj_info.circuit !== "undefined" ||
typeof this.obj.obj_info.socketed_ic !== "undefined"
) {
this.updateIC();
}
} else if (sub === "active-ic") {
const activeIc = window.VM.vm?.activeIC;
if (this.activeICId !== activeIc.obj_info.id) {
this.activeICId = activeIc.obj_info.id;
}
}
} else {
if ("field" in sub) {
if (this.logicFields.get(sub.field) !== newFields.get(sub.field)) {
this.logicFields = newFields;
}
} else if ("slot" in sub) {
if (
typeof this.slots === "undefined" ||
this.slots.length < sub.slot
) {
this.slots = newSlots;
} else if (
!structuralEqual(this.slots[sub.slot], newSlots[sub.slot])
) {
this.slots = newSlots;
}
}
}
}
}
updateIC() {
const ip = this.obj.obj_info.circuit?.instruction_pointer ?? null;
if (this.icIP !== ip) {
this.icIP = ip;
}
const opCount =
this.obj.obj_info.circuit?.yield_instruction_count ?? null;
if (this.icOpCount !== opCount) {
this.icOpCount = opCount;
}
const state = this.obj.obj_info.circuit?.state ?? null;
if (this.icState !== state) {
this.icState = state;
}
const errors = this.obj.obj_info.compile_errors ?? null;
if (!structuralEqual(this.errors, errors)) {
this.errors = errors;
}
const registers = this.obj.obj_info.circuit?.registers ?? null;
if (!structuralEqual(this.registers, registers)) {
this.registers = registers;
}
const aliases =
this.obj.obj_info.circuit?.aliases != null
? new Map(Object.entries(this.obj.obj_info.circuit.aliases))
: null;
if (!structuralEqual(this.aliases, aliases)) {
this.aliases = aliases;
}
const defines =
this.obj.obj_info.circuit?.defines != null
? new Map(
Object.entries(this.obj.obj_info.circuit.defines),
// .map(([key, val]) => [])
)
: null;
if (!structuralEqual(this.defines, defines)) {
this.defines = new Map(defines);
}
const pins =
this.obj.obj_info.device_pins != null
? new Map(
Object.entries(this.obj.obj_info.device_pins).map(
([key, val]) => [parseInt(key), val],
),
)
: null;
if (!structuralEqual(this.pins, pins)) {
this.pins = pins;
this.numPins =
"device" in this.obj.template
? this.obj.template.device.device_pins_length
: Math.max(...Array.from(this.pins?.keys() ?? [0]));
}
}
}
return VMObjectMixinClass as Constructor<VMObjectMixinInterface> & T;
};
@@ -596,7 +537,6 @@ export const VMActiveICMixin = <T extends Constructor<LitElement>>(
const id = e.detail;
if (this.objectID !== id) {
this.objectID = id;
this.obj = window.VM.vm.objects.get(this.objectID)!;
}
this.updateDevice();
}
@@ -618,7 +558,7 @@ export const VMTemplateDBMixin = <T extends Constructor<LitElement>>(
connectedCallback(): void {
const root = super.connectedCallback();
window.VM.vm.addEventListener(
"vm-device-db-loaded",
"vm-template-db-loaded",
this._handleDeviceDBLoad.bind(this),
);
if (typeof window.VM.vm.templateDB !== "undefined") {
@@ -644,7 +584,7 @@ export const VMTemplateDBMixin = <T extends Constructor<LitElement>>(
return this._templateDB;
}
postDBSetUpdate(): void {}
postDBSetUpdate(): void { }
@state()
set templateDB(val: TemplateDatabase) {

View File

@@ -13,7 +13,7 @@ import { unsafeHTML } from "lit/directives/unsafe-html.js";
import { VMTemplateDBMixin } from "virtualMachine/baseDevice";
import { LogicInfo, ObjectTemplate, StructureInfo } from "ic10emu_wasm";
type LogicableStrucutureTemplate = Extract<
type LogicableStructureTemplate = Extract<
ObjectTemplate,
{ structure: StructureInfo; logic: LogicInfo }
>;
@@ -38,7 +38,7 @@ export class VMAddDeviceButton extends VMTemplateDBMixin(BaseElement) {
@query("sl-drawer") drawer: SlDrawer;
@query(".device-search-input") searchInput: SlInput;
private _structures: Map<string, LogicableStrucutureTemplate> = new Map();
private _structures: Map<string, LogicableStructureTemplate> = new Map();
private _datapoints: [string, string][] = [];
private _haystack: string[] = [];
@@ -48,10 +48,10 @@ export class VMAddDeviceButton extends VMTemplateDBMixin(BaseElement) {
if ("structure" in template && "logic" in template) {
return [[template.prefab.prefab_name, template]] as [
string,
LogicableStrucutureTemplate,
LogicableStructureTemplate,
][];
} else {
return [] as [string, LogicableStrucutureTemplate][];
return [] as [string, LogicableStructureTemplate][];
}
}),
);
@@ -84,7 +84,7 @@ export class VMAddDeviceButton extends VMTemplateDBMixin(BaseElement) {
}
private _searchResults: {
entry: LogicableStrucutureTemplate;
entry: LogicableStructureTemplate;
haystackEntry: string;
ranges: number[];
}[] = [];
@@ -132,7 +132,7 @@ export class VMAddDeviceButton extends VMTemplateDBMixin(BaseElement) {
super.connectedCallback();
window.VM.get().then((vm) =>
vm.addEventListener(
"vm-device-db-loaded",
"vm-template-db-loaded",
this._handleDeviceDBLoad.bind(this),
),
);

View File

@@ -1,7 +1,8 @@
import { html, css, HTMLTemplateResult } from "lit";
import { customElement, property, query, state } from "lit/decorators.js";
import { watch, SignalWatcher, computed } from '@lit-labs/preact-signals';
import { BaseElement, defaultCss } from "components";
import { VMTemplateDBMixin, VMObjectMixin } from "virtualMachine/baseDevice";
import { VMTemplateDBMixin, VMObjectMixin, globalObjectSignalMap } from "virtualMachine/baseDevice";
import SlSelect from "@shoelace-style/shoelace/dist/components/select/select.component.js";
import { parseIntWithHexOrBinary, parseNumber } from "utils";
import SlInput from "@shoelace-style/shoelace/dist/components/input/input.component.js";
@@ -16,7 +17,7 @@ export type CardTab = "fields" | "slots" | "reagents" | "networks" | "pins";
@customElement("vm-device-card")
export class VMDeviceCard extends VMTemplateDBMixin(
VMObjectMixin(BaseElement),
VMObjectMixin(SignalWatcher(BaseElement)),
) {
image_err: boolean;
@@ -26,13 +27,6 @@ export class VMDeviceCard extends VMTemplateDBMixin(
super();
this.open = false;
this.subscribe(
"prefabName",
"name",
"nameHash",
"reagents",
"slots-count",
"reagents",
"connections",
"active-ic",
);
}
@@ -142,23 +136,12 @@ export class VMDeviceCard extends VMTemplateDBMixin(
if (thisIsActiveIc) {
badges.push(html`<sl-badge variant="primary" pill pulse>db</sl-badge>`);
}
const activeIc = window.VM.vm.activeIC;
const activeIc = globalObjectSignalMap.get(this.activeICId);
const numPins =
"device" in activeIc?.template
? activeIc.template.device.device_pins_length
: Math.max(
...Array.from(
activeIc?.obj_info.device_pins != null
? Object.keys(activeIc?.obj_info.device_pins).map((key) =>
parseInt(key),
)
: [0],
),
);
const numPins = activeIc.numPins.value;
const pins = new Array(numPins)
.fill(true)
.map((_, index) => this.pins.get(index));
.map((_, index) => this.objectSignals.pins.value.get(index));
pins.forEach((id, index) => {
if (this.objectID == id) {
badges.push(
@@ -167,10 +150,10 @@ export class VMDeviceCard extends VMTemplateDBMixin(
}
}, this);
return html`
<sl-tooltip content="${this.prefabName}">
<sl-tooltip content="${watch(this.objectSignals.prefabName)}">
<img
class="image me-2"
src="img/stationpedia/${this.prefabName}.png"
src="img/stationpedia/${watch(this.objectSignals.prefabName)}.png"
onerror="this.src = '${VMDeviceCard.transparentImg}'"
/>
</sl-tooltip>
@@ -194,8 +177,8 @@ export class VMDeviceCard extends VMTemplateDBMixin(
class="device-name me-1"
size="small"
pill
placeholder=${this.prefabName}
value=${this.name}
placeholder=${watch(this.objectSignals.prefabName)}
value=${watch(this.objectSignals.name)}
@sl-change=${this._handleChangeName}
>
<span slot="prefix">Name</span>
@@ -209,7 +192,7 @@ export class VMDeviceCard extends VMTemplateDBMixin(
size="small"
pill
class="device-name-hash me-1"
value="${this.nameHash.toString()}"
value="${watch(this.objectSignals.nameHash)}"
readonly
>
<span slot="prefix">Hash</span>
@@ -257,9 +240,7 @@ export class VMDeviceCard extends VMTemplateDBMixin(
"slots",
html`
<div class="flex flex-row flex-wrap">
${repeat(
this.slots,
(slot, index) => slot.typ + index.toString(),
${repeat(Array(this.objectSignals.slotsCount),
(_slot, index) => html`
<vm-device-slot .deviceID=${this.objectID} .slotIndex=${index} class-"flex flex-row max-w-lg mr-2 mb-2">
</vm-device-slot>
@@ -276,7 +257,7 @@ export class VMDeviceCard extends VMTemplateDBMixin(
renderNetworks() {
const vmNetworks = window.VM.vm.networks;
const networks = this.connections.map((connection, index, _conns) => {
const networks = this.objectSignals.connections.value.map((connection, index, _conns) => {
const conn =
typeof connection === "object" && "CableNetwork" in connection
? connection.CableNetwork
@@ -357,6 +338,8 @@ export class VMDeviceCard extends VMTemplateDBMixin(
}
render(): HTMLTemplateResult {
const disablePins = computed(() => {return !this.objectSignals.numPins.value;});
const displayName = computed(() => { return this.objectSignals.name.value ?? this.objectSignals.prefabName.value})
return html`
<ic10-details class="device-card" ?open=${this.open}>
<div class="header" slot="summary">${this.renderHeader()}</div>
@@ -365,7 +348,7 @@ export class VMDeviceCard extends VMTemplateDBMixin(
<sl-tab slot="nav" panel="slots">Slots</sl-tab>
<sl-tab slot="nav" panel="reagents" disabled>Reagents</sl-tab>
<sl-tab slot="nav" panel="networks">Networks</sl-tab>
<sl-tab slot="nav" panel="pins" ?disabled=${!this.numPins}
<sl-tab slot="nav" panel="pins" ?disabled=${watch(disablePins)}
>Pins</sl-tab
>
@@ -394,12 +377,12 @@ export class VMDeviceCard extends VMTemplateDBMixin(
<div class="remove-dialog-body">
<img
class="dialog-image mt-auto mb-auto me-2"
src="img/stationpedia/${this.prefabName}.png"
src="img/stationpedia/${watch(this.objectSignals.prefabName)}.png"
onerror="this.src = '${VMDeviceCard.transparentImg}'"
/>
<div class="flex-g">
<p><strong>Are you sure you want to remove this device?</strong></p>
<span>Id ${this.objectID} : ${this.name ?? this.prefabName}</span>
<span>Id ${this.objectID} : ${watch(displayName)}</span>
</div>
</div>
<div slot="footer">
@@ -452,7 +435,7 @@ export class VMDeviceCard extends VMTemplateDBMixin(
const name = input.value.length === 0 ? undefined : input.value;
window.VM.get().then((vm) => {
if (!vm.setObjectName(this.objectID, name)) {
input.value = this.name;
input.value = this.objectSignals.name.value;
}
this.updateDevice();
});

View File

@@ -5,26 +5,46 @@ import { VMTemplateDBMixin, VMObjectMixin } from "virtualMachine/baseDevice";
import { displayNumber, parseNumber } from "utils";
import type { LogicType } from "ic10emu_wasm";
import SlInput from "@shoelace-style/shoelace/dist/components/input/input.component.js";
import { computed, Signal, watch } from "@lit-labs/preact-signals";
@customElement("vm-device-fields")
export class VMDeviceSlot extends VMObjectMixin(VMTemplateDBMixin(BaseElement)) {
constructor() {
super();
this.subscribe("fields");
this.setupSignals();
}
setupSignals() {
this.logicFieldNames = computed(() => {
return Array.from(this.objectSignals.logicFields.value.keys());
});
}
logicFieldNames: Signal<LogicType[]>;
render() {
const fields = Array.from(this.logicFields.entries());
const inputIdBase = `vmDeviceCard${this.objectID}Field`;
return html`
${fields.map(([name, field], _index, _fields) => {
return html` <sl-input id="${inputIdBase}${name}" key="${name}" value="${displayNumber(field.value)}" size="small"
const fieldsHtml = computed(() => {
return this.logicFieldNames.value.map((name) => {
const field = computed(() => {
return this.objectSignals.logicFields.value.get(name);
});
const typ = computed(() => {
return field.value.field_type;
});
const value = computed(() => {
return displayNumber(field.value.value);
});
return html` <sl-input id="${inputIdBase}${name}" key="${name}" value="${watch(value)}" size="small"
@sl-change=${this._handleChangeField}>
<span slot="prefix">${name}</span>
<sl-copy-button slot="suffix" from="${inputIdBase}${name}.value"></sl-copy-button>
<span slot="suffix">${field.field_type}</span>
<span slot="suffix">${watch(typ)}</span>
</sl-input>`;
})}
})
});
return html`
${watch(fieldsHtml)}
`;
}
@@ -34,7 +54,7 @@ export class VMDeviceSlot extends VMObjectMixin(VMTemplateDBMixin(BaseElement))
const val = parseNumber(input.value);
window.VM.get().then((vm) => {
if (!vm.setObjectField(this.objectID, field, val, true)) {
input.value = this.logicFields.get(field).value.toString();
input.value = this.objectSignals.logicFields.value.get(field).value.toString();
}
this.updateDevice();
});

View File

@@ -4,22 +4,30 @@ import { BaseElement, defaultCss } from "components";
import { VMTemplateDBMixin, VMObjectMixin } from "virtualMachine/baseDevice";
import SlSelect from "@shoelace-style/shoelace/dist/components/select/select.component.js";
import { ObjectID } from "ic10emu_wasm";
import { effect, watch } from "@lit-labs/preact-signals";
import { SlOption } from "@shoelace-style/shoelace";
@customElement("vm-device-pins")
export class VMDevicePins extends VMObjectMixin(VMTemplateDBMixin(BaseElement)) {
constructor() {
super();
this.subscribe("ic", "visible-devices");
// this.subscribe("visible-devices");
}
render() {
const pins = new Array(this.numPins ?? 0)
const pins = new Array(this.objectSignals.numPins.value ?? 0)
.fill(true)
.map((_, index) => this.pins.get(index));
const visibleDevices = (this.visibleDevices ?? []).map((id) => window.VM.vm.objects.get(id));
.map((_, index) => this.objectSignals.pins.value.get(index));
const visibleDevices = (this.objectSignals.visibleDevices.value ?? []);
const forceSelectUpdate = () => {
const slSelect = this.renderRoot.querySelector("sl-select") as SlSelect;
if (slSelect != null) {
slSelect.handleValueChange();
}
};
const pinsHtml = pins?.map(
(pin, index) =>
html` <sl-select
(pin, index) => {
return html` <sl-select
hoist
placement="top"
clearable
@@ -29,14 +37,24 @@ export class VMDevicePins extends VMObjectMixin(VMTemplateDBMixin(BaseElement))
>
<span slot="prefix">d${index}</span>
${visibleDevices.map(
(device, _index) => html`
<sl-option value=${device.obj_info.id.toString()}>
Device ${device.obj_info.id} :
${device.obj_info.name ?? device.obj_info.prefab}
</sl-option>
`,
(device, _index) => {
device.id.subscribe((id: ObjectID) => {
forceSelectUpdate();
});
device.displayName.subscribe((_: string) => {
forceSelectUpdate();
});
return html`
<sl-option value=${watch(device.id)}>
Device ${watch(device.id)} :
${watch(device.displayName)}
</sl-option>
`
}
)}
</sl-select>`,
</sl-select>`;
}
);
return pinsHtml;
}

View File

@@ -1,7 +1,7 @@
import { html, css } from "lit";
import { customElement, property} from "lit/decorators.js";
import { customElement, property } from "lit/decorators.js";
import { BaseElement, defaultCss } from "components";
import { VMTemplateDBMixin, VMObjectMixin } from "virtualMachine/baseDevice";
import { VMTemplateDBMixin, VMObjectMixin, VmObjectSlotInfo, ComputedObjectSignals } from "virtualMachine/baseDevice";
import {
clamp,
crc32,
@@ -18,6 +18,7 @@ import {
import SlInput from "@shoelace-style/shoelace/dist/components/input/input.component.js";
import { VMDeviceCard } from "./card";
import { when } from "lit/directives/when.js";
import { computed, signal, Signal, SignalWatcher, watch } from "@lit-labs/preact-signals";
export interface SlotModifyEvent {
deviceID: number;
@@ -25,24 +26,29 @@ export interface SlotModifyEvent {
}
@customElement("vm-device-slot")
export class VMDeviceSlot extends VMObjectMixin(VMTemplateDBMixin(BaseElement)) {
private _slotIndex: number;
export class VMDeviceSlot extends VMObjectMixin(VMTemplateDBMixin(SignalWatcher(BaseElement))) {
private _slotIndex: Signal<number>;
slotSignal: Signal<VmObjectSlotInfo>;
get slotIndex() {
return this._slotIndex;
return this._slotIndex.value;
}
@property({ type: Number })
set slotIndex(val: number) {
this._slotIndex = val;
this.unsubscribe((sub) => typeof sub === "object" && "slot" in sub);
this.subscribe({ slot: val });
this._slotIndex.value = val;
}
constructor() {
super();
this.subscribe("active-ic", "prefabName");
this._slotIndex = signal(0);
this.subscribe("active-ic");
this.slotSignal = computed(() => {
const index = this._slotIndex.value;
return this.objectSignals.slots.value[index];
});
this.setupSignals();
}
static styles = [
@@ -73,49 +79,158 @@ export class VMDeviceSlot extends VMObjectMixin(VMTemplateDBMixin(BaseElement))
`,
];
slotOccupantImg(): string {
const slot = this.slots[this.slotIndex];
if (typeof slot.occupant !== "undefined") {
const prefabName = slot.occupant.obj_info.prefab;
return `img/stationpedia/${prefabName}.png`;
} else {
return `img/stationpedia/SlotIcon_${slot.typ}.png`;
}
setupSignals() {
this.slotOccupant = computed(() => {
const slot = this.slotSignal.value ?? null;
return slot?.occupant ?? null;
});
this.slotFieldTypes = computed(() => {
return Array.from(this.slotSignal.value?.logicFields.keys() ?? []) ;
});
this.slotOccupantImg = computed(() => {
const slot = this.slotSignal.value ?? null;
if (slot != null && slot.occupant != null) {
const prefabName = slot.occupant.prefabName;
return `img/stationpedia/${watch(prefabName)}.png`;
} else {
return `img/stationpedia/SlotIcon_${slot.typ}.png`;
}
});
this.slotOccupantPrefabName = computed(() => {
const slot = this.slotSignal.value ?? null;
if (slot != null && slot.occupant != null) {
const prefabName = slot.occupant.prefabName.value;
return prefabName;
} else {
return null;
}
});
this.slotOccupantTemplate = computed(() => {
if (this.objectSignals != null && "slots" in this.objectSignals.template.value) {
return this.objectSignals.template.value.slots[this.slotIndex];
} else {
return null;
}
});
}
slotOccupantPrefabName(): string {
const slot = this.slots[this.slotIndex];
if (typeof slot.occupant !== "undefined") {
const prefabName = slot.occupant.obj_info.prefab;
return prefabName;
} else {
return undefined;
}
}
slotOcccupantTemplate(): SlotInfo | undefined {
if ("slots" in this.obj.template) {
return this.obj.template.slots[this.slotIndex];
} else {
return undefined;
}
}
slotOccupant: Signal<ComputedObjectSignals | null>;
slotFieldTypes: Signal<LogicSlotType[]>;
slotOccupantImg: Signal<string>;
slotOccupantPrefabName: Signal<string | null>;
slotOccupantTemplate: Signal<SlotInfo | null>;
renderHeader() {
const inputIdBase = `vmDeviceSlot${this.objectID}Slot${this.slotIndex}Head`;
const slot = this.slots[this.slotIndex];
const slotImg = this.slotOccupantImg();
// const slot = this.slotSignal.value;
const slotImg = this.slotOccupantImg;
const img = html`<img
class="w-10 h-10"
src="${slotImg}"
src="${watch(slotImg)}"
onerror="this.src = '${VMDeviceCard.transparentImg}'"
/>`;
const template = this.slotOcccupantTemplate();
const thisIsActiveIc = this.activeICId === this.objectID;
const template = this.slotOccupantTemplate;
const templateName = computed(() => {
return template.value?.name ?? null;
});
const slotTyp = computed(() => {
return this.slotSignal.value.typ;
})
const enableQuantityInput = false;
const quantity = computed(() => {
const slot = this.slotSignal.value;
return slot.quantity;
});
const maxQuantity = computed(() => {
const slotOccupant = this.slotSignal.value.occupant;
const template = slotOccupant?.template.value ?? null;
if (template != null && "item" in template) {
return template.item.max_quantity;
} else {
return 1;
}
});
const slotDisplayName = computed(() => {
return this.slotOccupantPrefabName.value ?? this.slotSignal.value.typ;
});
const tooltipContent = computed(() => {
return this.activeICId === this.objectID && slotTyp.value === "ProgrammableChip"
? "Removing the selected Active IC is disabled"
: "Remove Occupant"
})
const removeDisabled = computed(() => {
return this.activeICId === this.objectID && slotTyp.value === "ProgrammableChip"
});
const quantityContent = computed(() => {
if (this.slotOccupant.value != null) {
return html`
<div
class="absolute bottom-0 right-0 mr-1 mb-1 text-xs
text-neutral-200/90 font-mono bg-neutral-500/40 rounded pl-1 pr-1"
>
<small>
${watch(quantity)}/${watch(maxQuantity)}
</small>
</div>`
} else {
return null
}
});
const slotName = computed(() => {
if(this.slotOccupant.value != null) {
return html` <span> ${watch(this.slotOccupantPrefabName)} </span> `
} else {
html` <span> ${watch(templateName)} </span> `
}
});
const inputContent = computed(() => {
if (this.slotOccupant.value != null) {
return html`
<div class="quantity-input ms-auto pl-2 mt-auto mb-auto me-2">
${enableQuantityInput
? html`<sl-input
type="number"
size="small"
.value=${watch(quantity)}
.min=${1}
.max=${watch(maxQuantity)}
@sl-change=${this._handleSlotQuantityChange}
>
<div slot="help-text">
<span>
Max Quantity:
${watch(maxQuantity)}
</span>
</div>
</sl-input>`
: ""}
<sl-tooltip
content=${watch(tooltipContent)}
>
<sl-icon-button
class="clear-occupant"
name="x-octagon"
label="Remove"
?disabled=${watch(removeDisabled)}
@click=${this._handleSlotOccupantRemove}
></sl-icon-button>
</sl-tooltip>
</div>
`
} else {
return null;
}
});
return html`
<div class="flex flex-row me-2">
<div
@@ -130,82 +245,24 @@ export class VMDeviceSlot extends VMObjectMixin(VMTemplateDBMixin(BaseElement))
>
<small>${this.slotIndex}</small>
</div>
<sl-tooltip content="${this.slotOccupantPrefabName() ?? slot.typ}">
<sl-tooltip content="${watch(slotDisplayName)}">
${img}
</sl-tooltip>
${when(
typeof slot.occupant !== "undefined",
() =>
html`<div
class="absolute bottom-0 right-0 mr-1 mb-1 text-xs
text-neutral-200/90 font-mono bg-neutral-500/40 rounded pl-1 pr-1"
>
<small>
${slot.quantity}/${"item" in slot.occupant.template
? slot.occupant.template.item.max_quantity
: 1}
</small>
</div>`,
)}
${watch(quantityContent)}
<div></div>
</div>
<div class="flex flex-col justify-end">
<div class="text-sm mt-auto mb-auto">
${when(
typeof slot.occupant !== "undefined",
() => html` <span> ${this.slotOccupantPrefabName()} </span> `,
() => html` <span> ${template?.name} </span> `,
)}
${watch(slotName)}
</div>
<div class="text-neutral-400 text-xs mt-auto flex flex-col mb-1">
<div>
<strong class="mt-auto mb-auto">Type:</strong
><span class="p-1">${slot.typ}</span>
><span class="p-1">${watch(slotTyp)}</span>
</div>
</div>
</div>
${when(
typeof slot.occupant !== "undefined",
() => html`
<div class="quantity-input ms-auto pl-2 mt-auto mb-auto me-2">
${enableQuantityInput
? html` <sl-input
type="number"
size="small"
.value=${slot.quantity.toString()}
.min=${1}
.max=${"item" in slot.occupant.template
? slot.occupant.template.item.max_quantity
: 1}
@sl-change=${this._handleSlotQuantityChange}
>
<div slot="help-text">
<span>
Max Quantity:
${"item" in slot.occupant.template
? slot.occupant.template.item.max_quantity
: 1}
</span>
</div>
</sl-input>`
: ""}
<sl-tooltip
content=${thisIsActiveIc && slot.typ === "ProgrammableChip"
? "Removing the selected Active IC is disabled"
: "Remove Occupant"}
>
<sl-icon-button
class="clear-occupant"
name="x-octagon"
label="Remove"
?disabled=${thisIsActiveIc && slot.typ === "ProgrammableChip"}
@click=${this._handleSlotOccupantRemove}
></sl-icon-button>
</sl-tooltip>
</div>
`,
() => html``,
)}
${watch(inputContent)}
</div>
`;
}
@@ -226,12 +283,12 @@ export class VMDeviceSlot extends VMObjectMixin(VMTemplateDBMixin(BaseElement))
_handleSlotQuantityChange(e: Event) {
const input = e.currentTarget as SlInput;
const slot = this.slots[this.slotIndex];
const slot = this.slotSignal.value;
const val = clamp(
input.valueAsNumber,
1,
"item" in slot.occupant.template
? slot.occupant.template.item.max_quantity
"item" in slot.occupant.template.value
? slot.occupant.template.value.item.max_quantity
: 1,
);
if (
@@ -243,25 +300,33 @@ export class VMDeviceSlot extends VMObjectMixin(VMTemplateDBMixin(BaseElement))
true,
)
) {
input.value = this.slots[this.slotIndex].quantity.toString();
input.value = this.slotSignal.value.quantity.toString();
}
}
renderFields() {
const inputIdBase = `vmDeviceSlot${this.objectID}Slot${this.slotIndex}Field`;
const _fields =
this.slots[this.slotIndex].logicFields ??
new Map<LogicSlotType, LogicField>();
const fields = Array.from(_fields.entries());
return html`
<div class="slot-fields">
${fields.map(
([name, field], _index, _fields) => html`
const fields = computed(() => {
const slot = this.slotSignal.value;
const _fields =
slot.logicFields??
new Map<LogicSlotType, LogicField>();
return this.slotFieldTypes.value.map(
(name, _index, _types) => {
const slotField = computed(() => {
return this.slotSignal.value.logicFields.get(name);
});
const fieldValue = computed(() => {
return displayNumber(slotField.value.value);
})
const fieldAccessType = computed(() => {
return slotField.value.field_type;
})
return html`
<sl-input
id="${inputIdBase}${name}"
key="${name}"
value="${displayNumber(field.value)}"
value="${watch(fieldValue)}"
size="small"
@sl-change=${this._handleChangeSlotField}
>
@@ -270,10 +335,16 @@ export class VMDeviceSlot extends VMObjectMixin(VMTemplateDBMixin(BaseElement))
slot="suffix"
from="${inputIdBase}${name}.value"
></sl-copy-button>
<span slot="suffix">${field.field_type}</span>
<span slot="suffix">${watch(fieldAccessType)}</span>
</sl-input>
`,
)}
`
}
)
});
return html`
<div class="slot-fields">
${watch(fields)}
</div>
`;
}
@@ -283,12 +354,12 @@ export class VMDeviceSlot extends VMObjectMixin(VMTemplateDBMixin(BaseElement))
const field = input.getAttribute("key")! as LogicSlotType;
let val = parseNumber(input.value);
if (field === "Quantity") {
const slot = this.slots[this.slotIndex];
const slot = this.slotSignal.value;
val = clamp(
input.valueAsNumber,
1,
"item" in slot.occupant.template
? slot.occupant.template.item.max_quantity
"item" in slot.occupant.template.value
? slot.occupant.template.value.item.max_quantity
: 1,
);
}
@@ -297,7 +368,7 @@ export class VMDeviceSlot extends VMObjectMixin(VMTemplateDBMixin(BaseElement))
!vm.setObjectSlotField(this.objectID, this.slotIndex, field, val, true)
) {
input.value = (
this.slots[this.slotIndex].logicFields ??
this.slotSignal.value.logicFields ??
new Map<LogicSlotType, LogicField>()
)
.get(field)

View File

@@ -22,6 +22,12 @@ export interface ToastMessage {
msg: string;
id: string;
}
import {
signal,
computed,
effect,
} from '@lit-labs/preact-signals';
import type { Signal } from '@lit-labs/preact-signals';
export interface VirtualMachineEventMap {
"vm-template-db-loaded": CustomEvent<TemplateDatabase>;
@@ -40,10 +46,12 @@ class VirtualMachine extends TypedEventTarget<VirtualMachineEventMap>() {
templateDBPromise: Promise<TemplateDatabase>;
templateDB: TemplateDatabase;
private _objects: Map<number, FrozenObjectFull>;
private _circuitHolders: Map<number, FrozenObjectFull>;
private _networks: Map<number, FrozenCableNetwork>;
private _default_network: number;
private _vmState: Signal<FrozenVM>;
private _objects: Map<number, Signal<FrozenObjectFull>>;
private _circuitHolders: Map<number, Signal<FrozenObjectFull>>;
private _networks: Map<number, Signal<FrozenCableNetwork>>;
private _default_network: Signal<number>;
private vm_worker: Worker;
@@ -61,6 +69,9 @@ class VirtualMachine extends TypedEventTarget<VirtualMachineEventMap>() {
}
async setupVM() {
this.templateDBPromise = this.ic10vm.getTemplateDatabase();
this.templateDBPromise.then((db) => this.setupTemplateDatabase(db));
this.vm_worker = new Worker(new URL("./vmWorker.ts", import.meta.url));
const loaded = (w: Worker) =>
new Promise((r) => w.addEventListener("message", r, { once: true }));
@@ -68,17 +79,21 @@ class VirtualMachine extends TypedEventTarget<VirtualMachineEventMap>() {
console.info("VM Worker loaded");
const vm = Comlink.wrap<VMRef>(this.vm_worker);
this.ic10vm = vm;
this._vmState.value = await this.ic10vm.saveVMState();
window.VM.set(this);
this.templateDBPromise = this.ic10vm.getTemplateDatabase();
effect(() => {
this.updateObjects(this._vmState.value);
this.updateNetworks(this._vmState.value);
});
this.templateDBPromise.then((db) => this.setupTemplateDatabase(db));
this.updateObjects();
this.updateNetworks();
this.updateCode();
}
get state() {
return this._vmState;
}
get objects() {
return this._objects;
}
@@ -113,41 +128,42 @@ class VirtualMachine extends TypedEventTarget<VirtualMachineEventMap>() {
return this._circuitHolders.get(this.app.session.activeIC);
}
async visibleDevices(source: number) {
const visDevices = await this.ic10vm.visibleDevices(source);
const ids = Array.from(visDevices);
ids.sort();
return ids.map((id, _index) => this._objects.get(id)!);
async visibleDevices(source: number): Promise<Signal<FrozenObjectFull>[]> {
try {
const visDevices = await this.ic10vm.visibleDevices(source);
const ids = Array.from(visDevices);
ids.sort();
return ids.map((id, _index) => this._objects.get(id)!);
} catch (err) {
this.handleVmError(err);
}
}
async visibleDeviceIds(source: number) {
async visibleDeviceIds(source: number): Promise<number[]> {
const visDevices = await this.ic10vm.visibleDevices(source);
const ids = Array.from(visDevices);
ids.sort();
return ids;
}
async updateNetworks() {
async updateNetworks(state: FrozenVM) {
let updateFlag = false;
const removedNetworks = [];
let networkIds: Uint32Array;
let frozenNetworks: FrozenCableNetwork[];
try {
networkIds = await this.ic10vm.networks;
frozenNetworks = await this.ic10vm.freezeNetworks(networkIds);
} catch (e) {
this.handleVmError(e);
return;
}
const networkIds: ObjectID[] = [];
const frozenNetworks: FrozenCableNetwork[] = state.networks;
const updatedNetworks: ObjectID[] = [];
for (const [index, id] of networkIds.entries()) {
for (const [index, net] of frozenNetworks.entries()) {
const id = net.id;
networkIds.push(id);
if (!this._networks.has(id)) {
this._networks.set(id, frozenNetworks[index]);
this._networks.set(id, signal(net));
updateFlag = true;
updatedNetworks.push(id);
} else {
if (!structuralEqual(this._networks.get(id), frozenNetworks[index])) {
this._networks.set(id, frozenNetworks[index]);
const mappedNet = this._networks.get(id);
if (!structuralEqual(mappedNet.peek(), net)) {
mappedNet.value = net;
updatedNetworks.push(id);
updateFlag = true;
}
@@ -173,28 +189,24 @@ class VirtualMachine extends TypedEventTarget<VirtualMachineEventMap>() {
}
}
async updateObjects() {
let updateFlag = false;
async updateObjects(state: FrozenVM) {
const removedObjects = [];
let objectIds: Uint32Array;
let frozenObjects: FrozenObjectFull[];
try {
objectIds = await this.ic10vm.objects;
frozenObjects = await this.ic10vm.freezeObjects(objectIds);
} catch (e) {
this.handleVmError(e);
return;
}
const frozenObjects = state.objects;
const objectIds: ObjectID[] = [];
const updatedObjects: ObjectID[] = [];
let updateFlag = false;
for (const [index, id] of objectIds.entries()) {
for (const [index, obj] of frozenObjects.entries()) {
const id = obj.obj_info.id;
objectIds.push(id);
if (!this._objects.has(id)) {
this._objects.set(id, frozenObjects[index]);
this._objects.set(id, signal(obj));
updateFlag = true;
updatedObjects.push(id);
} else {
if (!structuralEqual(this._objects.get(id), frozenObjects[index])) {
this._objects.set(id, frozenObjects[index]);
const mappedObject = this._objects.get(id);
if (!structuralEqual(obj, mappedObject.peek())) {
mappedObject.value = obj;
updatedObjects.push(id);
updateFlag = true;
}
@@ -210,7 +222,7 @@ class VirtualMachine extends TypedEventTarget<VirtualMachineEventMap>() {
}
for (const [id, obj] of this._objects) {
if (typeof obj.obj_info.socketed_ic !== "undefined") {
if (typeof obj.peek().obj_info.socketed_ic !== "undefined") {
if (!this._circuitHolders.has(id)) {
this._circuitHolders.set(id, obj);
updateFlag = true;
@@ -259,7 +271,7 @@ class VirtualMachine extends TypedEventTarget<VirtualMachineEventMap>() {
if (
circuitHolder &&
prog &&
circuitHolder.obj_info.source_code !== prog
circuitHolder.peek().obj_info.source_code !== prog
) {
try {
console.time(`CompileProgram_${id}_${attempt}`);
@@ -281,12 +293,12 @@ class VirtualMachine extends TypedEventTarget<VirtualMachineEventMap>() {
const ic = this.activeIC;
if (ic) {
try {
await this.ic10vm.stepProgrammable(ic.obj_info.id, false);
await this.ic10vm.stepProgrammable(ic.peek().obj_info.id, false);
} catch (err) {
this.handleVmError(err);
}
this.update();
this.dispatchCustomEvent("vm-run-ic", this.activeIC!.obj_info.id);
this.dispatchCustomEvent("vm-run-ic", this.activeIC!.peek().obj_info.id);
}
}
@@ -294,55 +306,30 @@ class VirtualMachine extends TypedEventTarget<VirtualMachineEventMap>() {
const ic = this.activeIC;
if (ic) {
try {
await this.ic10vm.runProgrammable(ic.obj_info.id, false);
await this.ic10vm.runProgrammable(ic.peek().obj_info.id, false);
} catch (err) {
this.handleVmError(err);
}
this.update();
this.dispatchCustomEvent("vm-run-ic", this.activeIC!.obj_info.id);
this.dispatchCustomEvent("vm-run-ic", this.activeIC!.peek().obj_info.id);
}
}
async reset() {
const ic = this.activeIC;
if (ic) {
await this.ic10vm.resetProgrammable(ic.obj_info.id);
await this.ic10vm.resetProgrammable(ic.peek().obj_info.id);
await this.update();
}
}
async update(save: boolean = true) {
await this.updateObjects();
await this.updateNetworks();
const lastModified = await this.ic10vm.lastOperationModified;
lastModified.forEach((id, _index, _modifiedIds) => {
if (this.objects.has(id)) {
this.updateObject(id, false);
}
}, this);
const activeIC = this.activeIC;
if (activeIC != null) {
this.updateObject(activeIC.obj_info.id, false);
}
if (save) this.app.session.save();
}
async updateObject(id: number, save: boolean = true) {
let frozen;
try {
frozen = await this.ic10vm.freezeObject(id);
this._objects.set(id, frozen);
} catch (e) {
this.handleVmError(e);
this._vmState.value = await this.ic10vm.saveVMState();
if (save) this.app.session.save();
} catch (err) {
this.handleVmError(err);
}
const device = this._objects.get(id);
this.dispatchCustomEvent("vm-object-modified", device.obj_info.id);
if (typeof device.obj_info.socketed_ic !== "undefined") {
const ic = this._objects.get(device.obj_info.socketed_ic);
const ip = ic.obj_info.circuit?.instruction_pointer;
this.app.session.setActiveLine(device.obj_info.id, ip);
}
if (save) this.app.session.save();
}
handleVmError(err: Error) {
@@ -359,7 +346,7 @@ class VirtualMachine extends TypedEventTarget<VirtualMachineEventMap>() {
// return the data connected oject ids for a network
networkDataDevices(network: ObjectID): number[] {
return this._networks.get(network)?.devices ?? [];
return this._networks.get(network)?.peek().devices ?? [];
}
async changeObjectID(oldID: number, newID: number): Promise<boolean> {
@@ -368,7 +355,7 @@ class VirtualMachine extends TypedEventTarget<VirtualMachineEventMap>() {
if (this.app.session.activeIC === oldID) {
this.app.session.activeIC = newID;
}
await this.updateObjects();
await this.update();
this.dispatchCustomEvent("vm-object-id-change", {
old: oldID,
new: newID,
@@ -383,25 +370,29 @@ class VirtualMachine extends TypedEventTarget<VirtualMachineEventMap>() {
async setRegister(index: number, val: number): Promise<boolean> {
const ic = this.activeIC!;
try {
await this.ic10vm.setRegister(ic.obj_info.id, index, val);
this.updateObject(ic.obj_info.id);
if (ic) {
try {
await this.ic10vm.setRegister(ic.peek().obj_info.id, index, val);
} catch (err) {
this.handleVmError(err);
return false;
}
await this.update();
return true;
} catch (err) {
this.handleVmError(err);
return false;
}
}
async setStack(addr: number, val: number): Promise<boolean> {
const ic = this.activeIC!;
try {
await this.ic10vm.setMemory(ic.obj_info.id, addr, val);
this.updateObject(ic.obj_info.id);
if (ic) {
try {
await this.ic10vm.setMemory(ic.peek().obj_info.id, addr, val);
} catch (err) {
this.handleVmError(err);
return false;
}
await this.update();
return true;
} catch (err) {
this.handleVmError(err);
return false;
}
}
@@ -409,13 +400,13 @@ class VirtualMachine extends TypedEventTarget<VirtualMachineEventMap>() {
const obj = this._objects.get(id);
if (obj) {
try {
await this.ic10vm.setObjectName(obj.obj_info.id, name);
this.updateObject(obj.obj_info.id);
this.app.session.save();
return true;
await this.ic10vm.setObjectName(obj.peek().obj_info.id, name);
} catch (e) {
this.handleVmError(e);
return false;
}
await this.update();
return true;
}
return false;
}
@@ -430,12 +421,13 @@ class VirtualMachine extends TypedEventTarget<VirtualMachineEventMap>() {
const obj = this._objects.get(id);
if (obj) {
try {
await this.ic10vm.setLogicField(obj.obj_info.id, field, val, force);
this.updateObject(obj.obj_info.id);
return true;
await this.ic10vm.setLogicField(obj.peek().obj_info.id, field, val, force);
} catch (err) {
this.handleVmError(err);
return false;
}
await this.update();
return true;
}
return false;
}
@@ -452,17 +444,18 @@ class VirtualMachine extends TypedEventTarget<VirtualMachineEventMap>() {
if (obj) {
try {
await this.ic10vm.setSlotLogicField(
obj.obj_info.id,
obj.peek().obj_info.id,
field,
slot,
val,
force,
);
this.updateObject(obj.obj_info.id);
return true;
} catch (err) {
this.handleVmError(err);
return false;
}
await this.update();
return true;
}
return false;
}
@@ -476,11 +469,12 @@ class VirtualMachine extends TypedEventTarget<VirtualMachineEventMap>() {
if (typeof device !== "undefined") {
try {
await this.ic10vm.setDeviceConnection(id, conn, val);
this.updateObject(device.obj_info.id);
return true;
} catch (err) {
this.handleVmError(err);
return false;
}
await this.update();
return true;
}
return false;
}
@@ -494,11 +488,12 @@ class VirtualMachine extends TypedEventTarget<VirtualMachineEventMap>() {
if (typeof device !== "undefined") {
try {
await this.ic10vm.setPin(id, pin, val);
this.updateObject(device.obj_info.id);
return true;
} catch (err) {
this.handleVmError(err);
return false;
}
await this.update();
return true;
}
return false;
}
@@ -513,11 +508,7 @@ class VirtualMachine extends TypedEventTarget<VirtualMachineEventMap>() {
try {
console.log("adding device", frozen);
const id = await this.ic10vm.addObjectFrozen(frozen);
const refrozen = await this.ic10vm.freezeObject(id);
this._objects.set(id, refrozen);
const device_ids = await this.ic10vm.objects;
this.dispatchCustomEvent("vm-objects-update", Array.from(device_ids));
this.app.session.save();
await this.update();
return id;
} catch (err) {
this.handleVmError(err);
@@ -531,13 +522,7 @@ class VirtualMachine extends TypedEventTarget<VirtualMachineEventMap>() {
try {
console.log("adding devices", frozenObjects);
const ids = await this.ic10vm.addObjectsFrozen(frozenObjects);
const refrozen = await this.ic10vm.freezeObjects(ids);
ids.forEach((id, index) => {
this._objects.set(id, refrozen[index]);
});
const device_ids = await this.ic10vm.objects;
this.dispatchCustomEvent("vm-objects-update", Array.from(device_ids));
this.app.session.save();
await this.update();
return Array.from(ids);
} catch (err) {
this.handleVmError(err);
@@ -548,12 +533,12 @@ class VirtualMachine extends TypedEventTarget<VirtualMachineEventMap>() {
async removeDevice(id: number): Promise<boolean> {
try {
await this.ic10vm.removeDevice(id);
await this.updateObjects();
return true;
} catch (err) {
this.handleVmError(err);
return false;
}
await this.update();
return true;
}
async setSlotOccupant(
@@ -567,7 +552,7 @@ class VirtualMachine extends TypedEventTarget<VirtualMachineEventMap>() {
try {
console.log("setting slot occupant", frozen);
await this.ic10vm.setSlotOccupant(id, index, frozen, quantity);
this.updateObject(device.obj_info.id);
await this.update();
return true;
} catch (err) {
this.handleVmError(err);
@@ -580,8 +565,8 @@ class VirtualMachine extends TypedEventTarget<VirtualMachineEventMap>() {
const device = this._objects.get(id);
if (typeof device !== "undefined") {
try {
this.ic10vm.removeSlotOccupant(id, index);
this.updateObject(device.obj_info.id);
await this.ic10vm.removeSlotOccupant(id, index);
await this.update();
return true;
} catch (err) {
this.handleVmError(err);
@@ -591,7 +576,7 @@ class VirtualMachine extends TypedEventTarget<VirtualMachineEventMap>() {
}
async saveVMState(): Promise<FrozenVM> {
return this.ic10vm.saveVMState();
return await this.ic10vm.saveVMState();
}
async restoreVMState(state: FrozenVM) {
@@ -599,7 +584,7 @@ class VirtualMachine extends TypedEventTarget<VirtualMachineEventMap>() {
await this.ic10vm.restoreVMState(state);
this._objects = new Map();
this._circuitHolders = new Map();
await this.updateObjects();
await this.update();
} catch (e) {
this.handleVmError(e);
}
@@ -608,7 +593,7 @@ class VirtualMachine extends TypedEventTarget<VirtualMachineEventMap>() {
getPrograms(): [number, string][] {
const programs: [number, string][] = Array.from(
this._circuitHolders.entries(),
).map(([id, ic]) => [id, ic.obj_info.source_code]);
).map(([id, ic]) => [id, ic.peek().obj_info.source_code]);
return programs;
}
}