refactor(vm, frontend): memory instructions, baseObject

- parse and map meory instructions
- use FrozenObjectFull to propogate  data out of VM to componates (far
  less wasm calls)
This commit is contained in:
Rachel Powers
2024-05-29 20:08:16 -07:00
parent c1f4cb23d4
commit 7b6909a323
17 changed files with 6235 additions and 409 deletions

View File

@@ -10,6 +10,10 @@ import type {
ObjectID,
TemplateDatabase,
FrozenObjectFull,
Class,
LogicSlotType,
SlotOccupantInfo,
ICState,
} from "ic10emu_wasm";
import { crc32, structuralEqual } from "utils";
import { LitElement, PropertyValueMap } from "lit";
@@ -24,20 +28,22 @@ export declare class VMObjectMixinInterface {
nameHash: number | null;
prefabName: string | null;
prefabHash: number | null;
fields: Map<LogicType, LogicField>;
slots: Slot[];
reagents: Map<number, number>;
connections: Connection[];
icIP: number;
icOpCount: number;
icState: string;
errors: ICError[];
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, string> | 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;
@@ -54,23 +60,34 @@ export type VMObjectMixinSubscription =
| "slots-count"
| "reagents"
| "connections"
| "memory"
| "ic"
| "active-ic"
| { field: LogicType }
| { slot: number }
| "visible-devices";
export interface VmObjectSlotInfo {
parent: ObjectID;
index: number;
name: string;
typ: Class;
logicFields: Map<LogicSlotType, LogicField>;
quantity: number;
occupant: FrozenObjectFull | undefined;
}
export const VMObjectMixin = <T extends Constructor<LitElement>>(
superClass: T,
) => {
class VMObjectMixinClass extends superClass {
private _deviceID: number;
get deviceID() {
return this._deviceID;
private _objectID: number;
get objectID() {
return this._objectID;
}
@property({ type: Number })
set deviceID(val: number) {
this._deviceID = val;
set objectID(val: number) {
this._objectID = val;
this.updateDevice();
}
@@ -93,40 +110,42 @@ export const VMObjectMixin = <T extends Constructor<LitElement>>(
@state() name: string | null = null;
@state() nameHash: number | null = null;
@state() prefabName: string | null;
@state() prefabHash: number | null;
@state() fields: Map<LogicType, LogicField>;
@state() slots: Slot[];
@state() reagents: Map<number, number>;
@state() connections: Connection[];
@state() icIP: number;
@state() icOpCount: number;
@state() icState: string;
@state() errors: ICError[];
@state() registers: number[] | null;
@state() memory: number[] | null;
@state() aliases: Map<string, Operand> | null;
@state() defines: Map<string, string> | null;
@state() numPins: number | null;
@state() pins: Map<number, ObjectID> | 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) => {
vm.addEventListener(
"vm-device-modified",
"vm-objects-modified",
this._handleDeviceModified.bind(this),
);
vm.addEventListener(
"vm-devices-update",
"vm-objects-update",
this._handleDevicesModified.bind(this),
);
vm.addEventListener(
"vm-device-id-change",
"vm-object-id-change",
this._handleDeviceIdChange.bind(this),
);
vm.addEventListener(
"vm-devices-removed",
"vm-objects-removed",
this._handleDevicesRemoved.bind(this),
);
});
@@ -137,19 +156,19 @@ export const VMObjectMixin = <T extends Constructor<LitElement>>(
disconnectedCallback(): void {
window.VM.get().then((vm) => {
vm.removeEventListener(
"vm-device-modified",
"vm-objects-modified",
this._handleDeviceModified.bind(this),
);
vm.removeEventListener(
"vm-devices-update",
"vm-objects-update",
this._handleDevicesModified.bind(this),
);
vm.removeEventListener(
"vm-device-id-change",
"vm-object-id-change",
this._handleDeviceIdChange.bind(this),
);
vm.removeEventListener(
"vm-devices-removed",
"vm-objects-removed",
this._handleDevicesRemoved.bind(this),
);
});
@@ -158,7 +177,7 @@ export const VMObjectMixin = <T extends Constructor<LitElement>>(
async _handleDeviceModified(e: CustomEvent) {
const id = e.detail;
const activeIcId = window.App.app.session.activeIC;
if (this.deviceID === id) {
if (this.objectID === id) {
this.updateDevice();
} else if (
id === activeIcId &&
@@ -168,7 +187,7 @@ export const VMObjectMixin = <T extends Constructor<LitElement>>(
this.requestUpdate();
} else if (this.objectSubscriptions.includes("visible-devices")) {
const visibleDevices = await window.VM.vm.visibleDeviceIds(
this.deviceID,
this.objectID,
);
if (visibleDevices.includes(id)) {
this.updateDevice();
@@ -180,7 +199,7 @@ export const VMObjectMixin = <T extends Constructor<LitElement>>(
async _handleDevicesModified(e: CustomEvent<number[]>) {
const activeIcId = window.App.app.session.activeIC;
const ids = e.detail;
if (ids.includes(this.deviceID)) {
if (ids.includes(this.objectID)) {
this.updateDevice();
if (this.objectSubscriptions.includes("visible-devices")) {
this.requestUpdate();
@@ -193,7 +212,7 @@ export const VMObjectMixin = <T extends Constructor<LitElement>>(
this.requestUpdate();
} else if (this.objectSubscriptions.includes("visible-devices")) {
const visibleDevices = await window.VM.vm.visibleDeviceIds(
this.deviceID,
this.objectID,
);
if (ids.some((id) => visibleDevices.includes(id))) {
this.updateDevice();
@@ -203,11 +222,11 @@ export const VMObjectMixin = <T extends Constructor<LitElement>>(
}
async _handleDeviceIdChange(e: CustomEvent<{ old: number; new: number }>) {
if (this.deviceID === e.detail.old) {
this.deviceID = e.detail.new;
if (this.objectID === e.detail.old) {
this.objectID = e.detail.new;
} else if (this.objectSubscriptions.includes("visible-devices")) {
const visibleDevices = await window.VM.vm.visibleDeviceIds(
this.deviceID,
this.objectID,
);
if (
visibleDevices.some(
@@ -227,48 +246,86 @@ export const VMObjectMixin = <T extends Constructor<LitElement>>(
}
updateDevice() {
this.obj = window.VM.vm.objects.get(this.deviceID)!;
this.obj = window.VM.vm.objects.get(this.objectID)!;
if (typeof this.obj === "undefined") {
return;
}
let newFields: Map<LogicType, LogicField> | null = null;
if (
this.objectSubscriptions.includes("slots") ||
this.objectSubscriptions.includes("slots-count")
this.objectSubscriptions.some(
(sub) =>
sub === "fields" || (typeof sub === "object" && "field" in sub),
)
) {
const slotsOccupantInfo = this.obj.obj_info.slots;
const logicValues =
this.obj.obj_info.logic_values ?? new Map<LogicType, number>();
const logicTemplate =
"logic" in this.obj.template ? this.obj.template.logic : null;
newFields = new Map(
Array.from(logicTemplate?.logic_types.entries() ?? []).map(
([lt, access]) => {
let field: LogicField = {
field_type: access,
value: logicValues.get(lt) ?? 0,
};
return [lt, field];
},
),
);
}
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 ?? new Map<number, SlotOccupantInfo>();
const slotsLogicValues =
this.obj.obj_info.slot_logic_values ??
new Map<number, Map<LogicSlotType, number>>();
const logicTemplate =
"logic" in this.obj.template ? this.obj.template.logic : null;
const slotsTemplate =
"slots" in this.obj.template ? this.obj.template.slots : [];
let slots: Slot[] | null = null;
if (slotsOccupantInfo.size !== 0) {
slots = slotsTemplate.map((template, index) => {
let slot = {
parent: this.obj.obj_info.id,
index: index,
name: template.name,
typ: template.typ,
readable_logic: Array.from(
logicTemplate?.logic_slot_types.get(index)?.entries() ?? [],
)
.filter(([_, val]) => val === "Read" || val === "ReadWrite")
.map(([key, _]) => key),
writeable_logic: Array.from(
logicTemplate?.logic_slot_types.get(index)?.entries() ?? [],
)
.filter(([_, val]) => val === "Write" || val === "ReadWrite")
.map(([key, _]) => key),
occupant: slotsOccupantInfo.get(index),
};
return slot;
});
}
if (!structuralEqual(this.slots, slots)) {
this.slots = slots;
}
newSlots = slotsTemplate.map((template, index) => {
const fieldEntryInfos = Array.from(
logicTemplate?.logic_slot_types.get(index).entries() ?? [],
);
const logicFields = new Map(
fieldEntryInfos.map(([slt, access]) => {
let field: LogicField = {
field_type: access,
value: slotsLogicValues.get(index)?.get(slt) ?? 0,
};
return [slt, 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) {
@@ -293,22 +350,20 @@ export const VMObjectMixin = <T extends Constructor<LitElement>>(
this.prefabHash = crc32(prefabName);
}
} else if (sub === "fields") {
const fields = this.obj.obj_info.logic_values ?? null;
const logicTemplate =
"logic" in this.obj.template ? this.obj.template.logic : null;
let logic_fields: Map<LogicType, LogicField> | null = null;
if (fields !== null) {
logic_fields = new Map();
for (const [lt, val] of fields) {
const access = logicTemplate?.logic_types.get(lt) ?? "Read";
logic_fields.set(lt, {
value: val,
field_type: access,
});
}
if (!structuralEqual(this.logicFields, newFields)) {
this.logicFields = newFields;
}
if (!structuralEqual(this.fields, logic_fields)) {
this.fields = logic_fields;
} 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;
@@ -367,10 +422,15 @@ export const VMObjectMixin = <T extends Constructor<LitElement>>(
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" ||
this.obj.obj_info.socketed_ic !== "undefined"
typeof this.obj.obj_info.socketed_ic !== "undefined"
) {
this.updateIC();
}
@@ -382,21 +442,19 @@ export const VMObjectMixin = <T extends Constructor<LitElement>>(
}
} else {
if ("field" in sub) {
const fields = this.obj.obj_info.logic_values;
if (this.fields.get(sub.field) !== fields.get(sub.field)) {
this.fields = fields;
if (this.logicFields.get(sub.field) !== newFields.get(sub.field)) {
this.logicFields = newFields;
}
} else if ("slot" in sub) {
const slots = this.obj.slots;
if (
typeof this.slots === "undefined" ||
this.slots.length < sub.slot
) {
this.slots = slots;
this.slots = newSlots;
} else if (
!structuralEqual(this.slots[sub.slot], slots[sub.slot])
!structuralEqual(this.slots[sub.slot], newSlots[sub.slot])
) {
this.slots = slots;
this.slots = newSlots;
}
}
}
@@ -404,41 +462,42 @@ export const VMObjectMixin = <T extends Constructor<LitElement>>(
}
updateIC() {
const ip = this.obj.ip!;
const ip = this.obj.obj_info.circuit?.instruction_pointer ?? null;
if (this.icIP !== ip) {
this.icIP = ip;
}
const opCount = this.obj.instructionCount!;
const opCount =
this.obj.obj_info.circuit?.yield_instruciton_count ?? null;
if (this.icOpCount !== opCount) {
this.icOpCount = opCount;
}
const state = this.obj.state!;
const state = this.obj.obj_info.circuit?.state ?? null;
if (this.icState !== state) {
this.icState = state;
}
const errors = this.obj.program?.errors ?? null;
const errors = this.obj.obj_info.compile_errors ?? null;
if (!structuralEqual(this.errors, errors)) {
this.errors = errors;
}
const registers = this.obj.registers ?? null;
const registers = this.obj.obj_info.circuit?.registers ?? null;
if (!structuralEqual(this.registers, registers)) {
this.registers = registers;
}
const stack = this.obj.stack ?? null;
if (!structuralEqual(this.memory, stack)) {
this.memory = stack;
}
const aliases = this.obj.aliases ?? null;
const aliases = this.obj.obj_info.circuit?.aliases ?? null;
if (!structuralEqual(this.aliases, aliases)) {
this.aliases = aliases;
}
const defines = this.obj.defines ?? null;
const defines = this.obj.obj_info.circuit?.defines ?? null;
if (!structuralEqual(this.defines, defines)) {
this.defines = defines;
}
const pins = this.obj.pins ?? null;
const pins = this.obj.obj_info.device_pins ?? new Map<number, number>();
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]));
}
}
}
@@ -492,16 +551,16 @@ export const VMActiveICMixin = <T extends Constructor<LitElement>>(
return VMActiveICMixinClass as Constructor<VMObjectMixinInterface> & T;
};
export declare class VMDeviceDBMixinInterface {
export declare class VMTemplateDBMixinInterface {
templateDB: TemplateDatabase;
_handleDeviceDBLoad(e: CustomEvent): void;
postDBSetUpdate(): void;
}
export const VMDeviceDBMixin = <T extends Constructor<LitElement>>(
export const VMTemplateDBMixin = <T extends Constructor<LitElement>>(
superClass: T,
) => {
class VMDeviceDBMixinClass extends superClass {
class VMTemplateDBMixinClass extends superClass {
connectedCallback(): void {
const root = super.connectedCallback();
window.VM.vm.addEventListener(
@@ -540,5 +599,5 @@ export const VMDeviceDBMixin = <T extends Constructor<LitElement>>(
}
}
return VMDeviceDBMixinClass as Constructor<VMDeviceDBMixinInterface> & T;
return VMTemplateDBMixinClass as Constructor<VMTemplateDBMixinInterface> & T;
};

View File

@@ -12,11 +12,11 @@ import { cache } from "lit/directives/cache.js";
import { default as uFuzzy } from "@leeoniya/ufuzzy";
import { when } from "lit/directives/when.js";
import { unsafeHTML } from "lit/directives/unsafe-html.js";
import { VMDeviceDBMixin } from "virtual_machine/base_device";
import { VMTemplateDBMixin } from "virtual_machine/base_device";
@customElement("vm-add-device-button")
export class VMAddDeviceButton extends VMDeviceDBMixin(BaseElement) {
export class VMAddDeviceButton extends VMTemplateDBMixin(BaseElement) {
static styles = [
...defaultCss,
css`

View File

@@ -1,7 +1,7 @@
import { html, css, HTMLTemplateResult } from "lit";
import { customElement, property, query, state } from "lit/decorators.js";
import { BaseElement, defaultCss } from "components";
import { VMDeviceDBMixin, VMObjectMixin } from "virtual_machine/base_device";
import { VMTemplateDBMixin, VMObjectMixin } from "virtual_machine/base_device";
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";
@@ -15,7 +15,9 @@ import { repeat } from "lit/directives/repeat.js";
export type CardTab = "fields" | "slots" | "reagents" | "networks" | "pins";
@customElement("vm-device-card")
export class VMDeviceCard extends VMDeviceDBMixin(VMObjectMixin(BaseElement)) {
export class VMDeviceCard extends VMTemplateDBMixin(
VMObjectMixin(BaseElement),
) {
image_err: boolean;
@property({ type: Boolean }) open: boolean;
@@ -141,7 +143,17 @@ export class VMDeviceCard extends VMDeviceDBMixin(VMObjectMixin(BaseElement)) {
badges.push(html`<sl-badge variant="primary" pill pulse>db</sl-badge>`);
}
const activeIc = window.VM.vm.activeIC;
activeIc?.pins?.forEach((id, index) => {
const numPins =
"device" in activeIc?.template
? activeIc.template.device.device_pins_length
: Math.max(
...Array.from(activeIc?.obj_info.device_pins?.keys() ?? [0]),
);
const pins = new Array(numPins)
.fill(true)
.map((_, index) => this.pins.get(index));
pins.forEach((id, index) => {
if (this.objectID == id) {
badges.push(
html`<sl-badge variant="success" pill>d${index}</sl-badge>`,
@@ -150,31 +162,71 @@ export class VMDeviceCard extends VMDeviceDBMixin(VMObjectMixin(BaseElement)) {
}, this);
return html`
<sl-tooltip content="${this.prefabName}">
<img class="image me-2" src="img/stationpedia/${this.prefabName}.png"
onerror="this.src = '${VMDeviceCard.transparentImg}'" />
<img
class="image me-2"
src="img/stationpedia/${this.prefabName}.png"
onerror="this.src = '${VMDeviceCard.transparentImg}'"
/>
</sl-tooltip>
<div class="header-name">
<sl-input id="vmDeviceCard${this.objectID}Id" class="device-id me-1" size="small" pill value=${this.objectID}
@sl-change=${this._handleChangeID}>
<sl-input
id="vmDeviceCard${this.objectID}Id"
class="device-id me-1"
size="small"
pill
value=${this.objectID.toString()}
@sl-change=${this._handleChangeID}
>
<span slot="prefix">Id</span>
<sl-copy-button slot="suffix" .value=${this.objectID}></sl-copy-button>
<sl-copy-button
slot="suffix"
.value=${this.objectID.toString()}
></sl-copy-button>
</sl-input>
<sl-input id="vmDeviceCard${this.objectID}Name" class="device-name me-1" size="small" pill
placeholder=${this.prefabName} value=${this.name} @sl-change=${this._handleChangeName}>
<sl-input
id="vmDeviceCard${this.objectID}Name"
class="device-name me-1"
size="small"
pill
placeholder=${this.prefabName}
value=${this.name}
@sl-change=${this._handleChangeName}
>
<span slot="prefix">Name</span>
<sl-copy-button slot="suffix" from="vmDeviceCard${this.objectID}Name.value"></sl-copy-button>
<sl-copy-button
slot="suffix"
from="vmDeviceCard${this.objectID}Name.value"
></sl-copy-button>
</sl-input>
<sl-input id="vmDeviceCard${this.objectID}NameHash" size="small" pill class="device-name-hash me-1"
value="${this.nameHash}" readonly>
<sl-input
id="vmDeviceCard${this.objectID}NameHash"
size="small"
pill
class="device-name-hash me-1"
value="${this.nameHash.toString()}"
readonly
>
<span slot="prefix">Hash</span>
<sl-copy-button slot="suffix" from="vmDeviceCard${this.objectID}NameHash.value"></sl-copy-button>
<sl-copy-button
slot="suffix"
from="vmDeviceCard${this.objectID}NameHash.value"
></sl-copy-button>
</sl-input>
${badges.map((badge) => badge)}
</div>
<div class="ms-auto mt-auto mb-auto me-2">
<sl-tooltip content=${thisIsActiveIc ? "Removing the selected Active IC is disabled" : "Remove Device" }>
<sl-icon-button class="remove-button" name="trash" label="Remove Device" ?disabled=${thisIsActiveIc}
@click=${this._handleDeviceRemoveButton}></sl-icon-button>
<sl-tooltip
content=${thisIsActiveIc
? "Removing the selected Active IC is disabled"
: "Remove Device"}
>
<sl-icon-button
class="remove-button"
name="trash"
label="Remove Device"
?disabled=${thisIsActiveIc}
@click=${this._handleDeviceRemoveButton}
></sl-icon-button>
</sl-tooltip>
</div>
`;
@@ -199,13 +251,14 @@ export class VMDeviceCard extends VMDeviceDBMixin(VMObjectMixin(BaseElement)) {
"slots",
html`
<div class="flex flex-row flex-wrap">
${repeat(this.slots,
(slot, index) => slot.typ + index.toString(),
(_slot, index) => html`
${repeat(
this.slots,
(slot, index) => slot.typ + index.toString(),
(_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>
`,
)}
)}
</div>
`,
);
@@ -219,15 +272,26 @@ export class VMDeviceCard extends VMDeviceDBMixin(VMObjectMixin(BaseElement)) {
const vmNetworks = window.VM.vm.networks;
const networks = this.connections.map((connection, index, _conns) => {
const conn =
typeof connection === "object" ? connection.CableNetwork : null;
typeof connection === "object" && "CableNetwork" in connection
? connection.CableNetwork
: null;
return html`
<sl-select hoist placement="top" clearable key=${index} value=${conn?.net} ?disabled=${conn===null}
@sl-change=${this._handleChangeConnection}>
<sl-select
hoist
placement="top"
clearable
key=${index}
value=${conn?.net}
?disabled=${conn === null}
@sl-change=${this._handleChangeConnection}
>
<span slot="prefix">Connection:${index} </span>
${vmNetworks.map(
(net) =>
html`<sl-option value=${net.toString()}>Network ${net}</sl-option>`,
)}
(net) =>
html`<sl-option value=${net.toString()}
>Network ${net}</sl-option
>`,
)}
<span slot="prefix"> ${conn?.typ} </span>
</sl-select>
`;
@@ -243,7 +307,7 @@ export class VMDeviceCard extends VMDeviceDBMixin(VMObjectMixin(BaseElement)) {
"pins",
html`<div class="pins">
<vm-device-pins .deviceID=${this.objectID}></vm-device-pins>
</div>`
</div>`,
);
}
@@ -295,7 +359,9 @@ export class VMDeviceCard extends VMDeviceDBMixin(VMObjectMixin(BaseElement)) {
<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.obj.pins}>Pins</sl-tab>
<sl-tab slot="nav" panel="pins" ?disabled=${!this.numPins}
>Pins</sl-tab
>
<sl-tab-panel name="fields" active>
${until(this.renderFields(), html`<sl-spinner></sl-spinner>`)}
@@ -309,21 +375,37 @@ export class VMDeviceCard extends VMDeviceDBMixin(VMObjectMixin(BaseElement)) {
<sl-tab-panel name="networks">
${until(this.renderNetworks(), html`<sl-spinner></sl-spinner>`)}
</sl-tab-panel>
<sl-tab-panel name="pins">${until(this.renderPins(), html`<sl-spinner></sl-spinner>`)} </sl-tab-panel>
<sl-tab-panel name="pins"
>${until(this.renderPins(), html`<sl-spinner></sl-spinner>`)}
</sl-tab-panel>
</sl-tab-group>
</ic10-details>
<sl-dialog class="remove-device-dialog" no-header @sl-request-close=${this._preventOverlayClose}>
<sl-dialog
class="remove-device-dialog"
no-header
@sl-request-close=${this._preventOverlayClose}
>
<div class="remove-dialog-body">
<img class="dialog-image mt-auto mb-auto me-2" src="img/stationpedia/${this.prefabName}.png"
onerror="this.src = '${VMDeviceCard.transparentImg}'" />
<img
class="dialog-image mt-auto mb-auto me-2"
src="img/stationpedia/${this.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>
</div>
</div>
<div slot="footer">
<sl-button variant="primary" autofocus @click=${this._closeRemoveDialog}>Close</sl-button>
<sl-button variant="danger" @click=${this._removeDialogRemove}>Remove</sl-button>
<sl-button
variant="primary"
autofocus
@click=${this._closeRemoveDialog}
>Close</sl-button
>
<sl-button variant="danger" @click=${this._removeDialogRemove}
>Remove</sl-button
>
</div>
</sl-dialog>
`;
@@ -387,5 +469,4 @@ export class VMDeviceCard extends VMDeviceDBMixin(VMObjectMixin(BaseElement)) {
);
this.updateDevice();
}
}

View File

@@ -151,11 +151,11 @@ export class VMDeviceList extends BaseElement {
for (const device_id of this.devices) {
const device = window.VM.vm.objects.get(device_id);
if (device) {
if (typeof device.name !== "undefined") {
datapoints.push([device.name, device.id]);
if (typeof device.obj_info.name !== "undefined") {
datapoints.push([device.obj_info.name, device.obj_info.id]);
}
if (typeof device.prefabName !== "undefined") {
datapoints.push([device.prefabName, device.id]);
if (typeof device.obj_info.prefab !== "undefined") {
datapoints.push([device.obj_info.prefab, device.obj_info.id]);
}
}
}

View File

@@ -1,20 +1,20 @@
import { html, css } from "lit";
import { customElement, property } from "lit/decorators.js";
import { BaseElement, defaultCss } from "components";
import { VMDeviceDBMixin, VMObjectMixin } from "virtual_machine/base_device";
import { VMTemplateDBMixin, VMObjectMixin } from "virtual_machine/base_device";
import { displayNumber, parseNumber } from "utils";
import type { LogicType } from "ic10emu_wasm";
import SlInput from "@shoelace-style/shoelace/dist/components/input/input.component.js";
@customElement("vm-device-fields")
export class VMDeviceSlot extends VMObjectMixin(VMDeviceDBMixin(BaseElement)) {
export class VMDeviceSlot extends VMObjectMixin(VMTemplateDBMixin(BaseElement)) {
constructor() {
super();
this.subscribe("fields");
}
render() {
const fields = Array.from(this.fields.entries());
const fields = Array.from(this.logicFields.entries());
const inputIdBase = `vmDeviceCard${this.objectID}Field`;
return html`
${fields.map(([name, field], _index, _fields) => {
@@ -34,7 +34,7 @@ export class VMDeviceSlot extends VMObjectMixin(VMDeviceDBMixin(BaseElement)) {
const val = parseNumber(input.value);
window.VM.get().then((vm) => {
if (!vm.setObjectField(this.objectID, field, val, true)) {
input.value = this.fields.get(field).value.toString();
input.value = this.logicFields.get(field).value.toString();
}
this.updateDevice();
});

View File

@@ -5,11 +5,11 @@ import "./add_device"
import "./slot_add_dialog"
import "./slot"
import { VmDeviceTemplate } from "./template";
import { VmObjectTemplate } from "./template";
import { VMDeviceCard } from "./card";
import { VMDeviceList } from "./device_list";
import { VMAddDeviceButton } from "./add_device";
import { VMSlotAddDialog } from "./slot_add_dialog";
export { VMDeviceCard, VmDeviceTemplate, VMDeviceList, VMAddDeviceButton, VMSlotAddDialog };
export { VMDeviceCard, VmObjectTemplate as VmDeviceTemplate, VMDeviceList, VMAddDeviceButton, VMSlotAddDialog };

View File

@@ -1,34 +1,42 @@
import { html, css } from "lit";
import { customElement, property } from "lit/decorators.js";
import { BaseElement, defaultCss } from "components";
import { VMDeviceDBMixin, VMObjectMixin } from "virtual_machine/base_device";
import { VMTemplateDBMixin, VMObjectMixin } from "virtual_machine/base_device";
import SlSelect from "@shoelace-style/shoelace/dist/components/select/select.component.js";
import { ObjectID } from "ic10emu_wasm";
@customElement("vm-device-pins")
export class VMDevicePins extends VMObjectMixin(VMDeviceDBMixin(BaseElement)) {
export class VMDevicePins extends VMObjectMixin(VMTemplateDBMixin(BaseElement)) {
constructor() {
super();
this.subscribe("ic", "visible-devices");
}
render() {
const pins = this.pins;
const visibleDevices = window.VM.vm.visibleDevices(this.objectID);
const pins = new Array(this.numPins ?? 0)
.fill(true)
.map((_, index) => this.pins.get(index));
const visibleDevices = (this.visibleDevices ?? []).map((id) => window.VM.vm.objects.get(id));
const pinsHtml = pins?.map(
(pin, index) =>
html`
<sl-select hoist placement="top" clearable key=${index} value=${pin} @sl-change=${this._handleChangePin}>
<span slot="prefix">d${index}</span>
${visibleDevices.map(
(device, _index) =>
html`
<sl-option value=${device.id}>
Device ${device.id} : ${device.name ?? device.prefabName}
</sl-option>
`,
html` <sl-select
hoist
placement="top"
clearable
key=${index}
value=${pin}
@sl-change=${this._handleChangePin}
>
<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>
`,
)}
</sl-select>`,
</sl-select>`,
);
return pinsHtml;
}
@@ -40,5 +48,4 @@ export class VMDevicePins extends VMObjectMixin(VMDeviceDBMixin(BaseElement)) {
window.VM.get().then((vm) => vm.setDevicePin(this.objectID, pin, val));
this.updateDevice();
}
}

View File

@@ -1,15 +1,19 @@
import { html, css } from "lit";
import { customElement, property} from "lit/decorators.js";
import { BaseElement, defaultCss } from "components";
import { VMDeviceDBMixin, VMObjectMixin } from "virtual_machine/base_device";
import { VMTemplateDBMixin, VMObjectMixin } from "virtual_machine/base_device";
import {
clamp,
crc32,
displayNumber,
parseNumber,
} from "utils";
import {
LogicField,
LogicSlotType,
SlotType,
SlotInfo,
Class as SlotType,
TemplateDatabase,
} from "ic10emu_wasm";
import SlInput from "@shoelace-style/shoelace/dist/components/input/input.component.js";
import { VMDeviceCard } from "./card";
@@ -21,7 +25,7 @@ export interface SlotModifyEvent {
}
@customElement("vm-device-slot")
export class VMDeviceSlot extends VMObjectMixin(VMDeviceDBMixin(BaseElement)) {
export class VMDeviceSlot extends VMObjectMixin(VMTemplateDBMixin(BaseElement)) {
private _slotIndex: number;
get slotIndex() {
@@ -72,8 +76,7 @@ export class VMDeviceSlot extends VMObjectMixin(VMDeviceDBMixin(BaseElement)) {
slotOccupantImg(): string {
const slot = this.slots[this.slotIndex];
if (typeof slot.occupant !== "undefined") {
const hashLookup = (this.templateDB ?? {}).names_by_hash ?? {};
const prefabName = hashLookup[slot.occupant.prefab_hash] ?? "UnknownHash";
const prefabName = slot.occupant.obj_info.prefab;
return `img/stationpedia/${prefabName}.png`;
} else {
return `img/stationpedia/SlotIcon_${slot.typ}.png`;
@@ -83,18 +86,16 @@ export class VMDeviceSlot extends VMObjectMixin(VMDeviceDBMixin(BaseElement)) {
slotOccupantPrefabName(): string {
const slot = this.slots[this.slotIndex];
if (typeof slot.occupant !== "undefined") {
const hashLookup = (this.templateDB ?? {}).names_by_hash ?? {};
const prefabName = hashLookup[slot.occupant.prefab_hash] ?? "UnknownHash";
const prefabName = slot.occupant.obj_info.prefab;
return prefabName;
} else {
return undefined;
}
}
slotOcccupantTemplate(): { name: string; typ: SlotType } | undefined {
if (this.templateDB) {
const entry = this.templateDB.db[this.prefabName];
return entry?.slots[this.slotIndex];
slotOcccupantTemplate(): SlotInfo | undefined {
if ("slots" in this.obj.template) {
return this.obj.template.slots[this.slotIndex];
} else {
return undefined;
}
@@ -139,10 +140,11 @@ export class VMDeviceSlot extends VMObjectMixin(VMDeviceDBMixin(BaseElement)) {
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.occupant.quantity}/${slot.occupant
.max_quantity}</small
>
<small>
${slot.quantity}/${"item" in slot.occupant.template
? slot.occupant.template.item.max_quantity
: 1}
</small>
</div>`,
)}
<div></div>
@@ -170,13 +172,20 @@ export class VMDeviceSlot extends VMObjectMixin(VMDeviceDBMixin(BaseElement)) {
? html` <sl-input
type="number"
size="small"
.value=${slot.occupant.quantity.toString()}
.value=${slot.quantity.toString()}
.min=${1}
.max=${slot.occupant.max_quantity}
.max=${"item" in slot.occupant.template
? slot.occupant.template.item.max_quantity
: 1}
@sl-change=${this._handleSlotQuantityChange}
>
<div slot="help-text">
<span>Max Quantity: ${slot.occupant.max_quantity}</span>
<span>
Max Quantity:
${"item" in slot.occupant.template
? slot.occupant.template.item.max_quantity
: 1}
</span>
</div>
</sl-input>`
: ""}
@@ -218,7 +227,13 @@ export class VMDeviceSlot extends VMObjectMixin(VMDeviceDBMixin(BaseElement)) {
_handleSlotQuantityChange(e: Event) {
const input = e.currentTarget as SlInput;
const slot = this.slots[this.slotIndex];
const val = clamp(input.valueAsNumber, 1, slot.occupant.max_quantity);
const val = clamp(
input.valueAsNumber,
1,
"item" in slot.occupant.template
? slot.occupant.template.item.max_quantity
: 1,
);
if (
!window.VM.vm.setObjectSlotField(
this.objectID,
@@ -228,15 +243,15 @@ export class VMDeviceSlot extends VMObjectMixin(VMDeviceDBMixin(BaseElement)) {
true,
)
) {
input.value = this.obj
.getSlotField(this.slotIndex, "Quantity")
.toString();
input.value = this.slots[this.slotIndex].quantity.toString();
}
}
renderFields() {
const inputIdBase = `vmDeviceSlot${this.objectID}Slot${this.slotIndex}Field`;
const _fields = this.obj.getSlotFields(this.slotIndex);
const _fields =
this.slots[this.slotIndex].logicFields ??
new Map<LogicSlotType, LogicField>();
const fields = Array.from(_fields.entries());
return html`
@@ -269,14 +284,23 @@ export class VMDeviceSlot extends VMObjectMixin(VMDeviceDBMixin(BaseElement)) {
let val = parseNumber(input.value);
if (field === "Quantity") {
const slot = this.slots[this.slotIndex];
val = clamp(input.valueAsNumber, 1, slot.occupant.max_quantity);
val = clamp(
input.valueAsNumber,
1,
"item" in slot.occupant.template
? slot.occupant.template.item.max_quantity
: 1,
);
}
window.VM.get().then((vm) => {
if (
!vm.setObjectSlotField(this.objectID, this.slotIndex, field, val, true)
) {
input.value = this.obj
.getSlotField(this.slotIndex, field)
input.value = (
this.slots[this.slotIndex].logicFields ??
new Map<LogicSlotType, LogicField>()
)
.get(field)
.toString();
}
this.updateDevice();

View File

@@ -1,7 +1,7 @@
import { html, css } from "lit";
import { customElement, property, query, state } from "lit/decorators.js";
import { BaseElement, defaultCss } from "components";
import { VMDeviceDBMixin } from "virtual_machine/base_device";
import { VMTemplateDBMixin } from "virtual_machine/base_device";
import type { DeviceDB, DeviceDBEntry } from "virtual_machine/device_db";
import SlInput from "@shoelace-style/shoelace/dist/components/input/input.component.js";
import SlDialog from "@shoelace-style/shoelace/dist/components/dialog/dialog.component.js";
@@ -11,7 +11,7 @@ import uFuzzy from "@leeoniya/ufuzzy";
import { LogicField, LogicSlotType, SlotOccupantTemplate } from "ic10emu_wasm";
@customElement("vm-slot-add-dialog")
export class VMSlotAddDialog extends VMDeviceDBMixin(BaseElement) {
export class VMSlotAddDialog extends VMTemplateDBMixin(BaseElement) {
static styles = [
...defaultCss,
css`

View File

@@ -1,26 +1,23 @@
import type {
Connection,
DeviceTemplate,
ObjectTemplate,
LogicField,
LogicType,
Slot,
SlotTemplate,
ConnectionCableNetwork,
} from "ic10emu_wasm";
import { html, css, HTMLTemplateResult } from "lit";
import { customElement, property, query, state } from "lit/decorators.js";
import { BaseElement, defaultCss } from "components";
import type { DeviceDB, DeviceDBEntry } from "virtual_machine/device_db";
import { connectionFromDeviceDBConnection } from "./dbutils";
import { displayNumber, parseNumber } from "utils";
import { crc32, displayNumber, parseNumber } from "utils";
import SlInput from "@shoelace-style/shoelace/dist/components/input/input.component.js";
import SlSelect from "@shoelace-style/shoelace/dist/components/select/select.component.js";
import { VMDeviceCard } from "./card";
import { VMDeviceDBMixin } from "virtual_machine/base_device";
import { VMTemplateDBMixin } from "virtual_machine/base_device";
@customElement("vm-device-template")
export class VmDeviceTemplate extends VMDeviceDBMixin(BaseElement) {
export class VmObjectTemplate extends VMTemplateDBMixin(BaseElement) {
static styles = [
...defaultCss,
@@ -56,14 +53,14 @@ export class VmDeviceTemplate extends VMDeviceDBMixin(BaseElement) {
@state() fields: { [key in LogicType]?: LogicField };
@state() slots: SlotTemplate[];
@state() template: DeviceTemplate;
@state() template: ObjectTemplate;
@state() device_id: number | undefined;
@state() device_name: string | undefined;
@state() connections: Connection[];
constructor() {
super();
this.templateDB = window.VM.vm.db;
this.templateDB = window.VM.vm.templateDB;
}
private _prefab_name: string;
@@ -78,27 +75,27 @@ export class VmDeviceTemplate extends VMDeviceDBMixin(BaseElement) {
this.setupState();
}
get dbDevice(): DeviceDBEntry {
return this.templateDB.db[this.prefab_name];
get dbTemplate(): ObjectTemplate {
return this.templateDB.get( crc32(this.prefab_name));
}
setupState() {
this.fields = Object.fromEntries(
Object.entries(this.dbDevice?.logic ?? {}).map(([lt, ft]) => {
const value = lt === "PrefabHash" ? this.dbDevice.hash : 0.0;
Object.entries(this.dbTemplate?.logic ?? {}).map(([lt, ft]) => {
const value = lt === "PrefabHash" ? this.dbTemplate.prefab.prefab_hash : 0.0;
return [lt, { field_type: ft, value } as LogicField];
}),
);
this.slots = (this.dbDevice?.slots ?? []).map(
this.slots = (this.dbTemplate?.slots ?? []).map(
(slot, _index) =>
({
typ: slot.typ,
}) as SlotTemplate,
);
const connections = Object.entries(this.dbDevice?.conn ?? {}).map(
const connections = Object.entries(this.dbTemplate?.conn ?? {}).map(
([index, conn]) =>
[index, connectionFromDeviceDBConnection(conn)] as const,
);
@@ -203,7 +200,7 @@ export class VmDeviceTemplate extends VMDeviceDBMixin(BaseElement) {
}
render() {
const device = this.dbDevice;
const device = this.dbTemplate;
return html`
<sl-card class="template-card">
<div class="header h-20 w-96" slot="header">

View File

@@ -116,8 +116,15 @@ class VirtualMachine extends (EventTarget as TypedEventTarget<VirtualMachinEvent
async updateObjects() {
let updateFlag = false;
const removedObjects = [];
const objectIds = await this.ic10vm.objects;
const frozenObjects = await this.ic10vm.freezeObjects(objectIds);
let objectIds;
let frozenObjects;
try {
objectIds = await this.ic10vm.objects;
frozenObjects = await this.ic10vm.freezeObjects(objectIds);
} catch (e) {
this.handleVmError(e);
return;
}
const updatedObjects = [];
for (const [index, id] of objectIds.entries()) {
@@ -270,7 +277,14 @@ class VirtualMachine extends (EventTarget as TypedEventTarget<VirtualMachinEvent
if (save) this.app.session.save();
}
updateDevice(id: number, save: boolean = true) {
async updateDevice(id: number, save: boolean = true) {
let frozen;
try {
frozen = await this.ic10vm.freezeObject(id);
this._objects.set(id, frozen);
} catch (e) {
this.handleVmError(e);
}
const device = this._objects.get(id);
this.dispatchEvent(
new CustomEvent("vm-device-modified", { detail: device.obj_info.id }),
@@ -528,7 +542,7 @@ class VirtualMachine extends (EventTarget as TypedEventTarget<VirtualMachinEvent
}
}
getPrograms() : [number, string][] {
getPrograms(): [number, string][] {
const programs: [number, string][] = Array.from(
this._circuitHolders.entries(),
).map(([id, ic]) => [id, ic.obj_info.source_code]);