perf: performance improvments
- switch to BTreeMap for consistant ordering of fields (less UI updates) - cache calls to expensive getters in the vm via witha Proxy on DeviceRefs - have DeviceMixin explicitly subscribe to device property changes to limit updates - split fields into seperate componate to avoid rerender of other components - speedup ic10emu_wasm DeviceRef::get_slots by only calling serde once. Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com>
This commit is contained in:
@@ -124,7 +124,20 @@ export const demoVMState: VMState = {
|
||||
},
|
||||
},
|
||||
],
|
||||
fields: {},
|
||||
fields: {
|
||||
"PrefabHash": {
|
||||
field_type: "Read",
|
||||
value: -128473777,
|
||||
},
|
||||
"Setting": {
|
||||
field_type: "ReadWrite",
|
||||
value: 0,
|
||||
},
|
||||
"RequiredPower": {
|
||||
field_type: "Read",
|
||||
value: 0,
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
networks: [
|
||||
|
||||
@@ -12,6 +12,7 @@ import type {
|
||||
Aliases,
|
||||
Defines,
|
||||
Pins,
|
||||
LogicType,
|
||||
} from "ic10emu_wasm";
|
||||
import { structuralEqual } from "utils";
|
||||
import { LitElement, PropertyValueMap } from "lit";
|
||||
@@ -21,6 +22,7 @@ type Constructor<T = {}> = new (...args: any[]) => T;
|
||||
|
||||
export declare class VMDeviceMixinInterface {
|
||||
deviceID: number;
|
||||
activeICId: number;
|
||||
device: DeviceRef;
|
||||
name: string | null;
|
||||
nameHash: number | null;
|
||||
@@ -41,8 +43,24 @@ export declare class VMDeviceMixinInterface {
|
||||
_handleDeviceModified(e: CustomEvent): void;
|
||||
updateDevice(): void;
|
||||
updateIC(): void;
|
||||
subscribe(...sub: VMDeviceMixinSubscription[]): void;
|
||||
unsubscribe(filter: (sub: VMDeviceMixinSubscription) => boolean): void;
|
||||
}
|
||||
|
||||
export type VMDeviceMixinSubscription =
|
||||
| "name"
|
||||
| "nameHash"
|
||||
| "prefabName"
|
||||
| "fields"
|
||||
| "slots"
|
||||
| "slots-count"
|
||||
| "reagents"
|
||||
| "connections"
|
||||
| "ic"
|
||||
| "active-ic"
|
||||
| { field: LogicType }
|
||||
| { slot: number };
|
||||
|
||||
export const VMDeviceMixin = <T extends Constructor<LitElement>>(
|
||||
superClass: T,
|
||||
) => {
|
||||
@@ -57,8 +75,23 @@ export const VMDeviceMixin = <T extends Constructor<LitElement>>(
|
||||
this.updateDevice();
|
||||
}
|
||||
|
||||
@state() private deviceSubscriptions: VMDeviceMixinSubscription[] = [];
|
||||
|
||||
subscribe(...sub: VMDeviceMixinSubscription[]) {
|
||||
this.deviceSubscriptions = this.deviceSubscriptions.concat(sub);
|
||||
}
|
||||
|
||||
// remove subscripotions matching the filter
|
||||
unsubscribe(filter: (sub: VMDeviceMixinSubscription) => boolean) {
|
||||
this.deviceSubscriptions = this.deviceSubscriptions.filter(
|
||||
(sub) => !filter(sub),
|
||||
);
|
||||
}
|
||||
|
||||
device: DeviceRef;
|
||||
|
||||
@state() activeICId: number;
|
||||
|
||||
@state() name: string | null = null;
|
||||
@state() nameHash: number | null = null;
|
||||
@state() prefabName: string | null;
|
||||
@@ -107,7 +140,6 @@ export const VMDeviceMixin = <T extends Constructor<LitElement>>(
|
||||
this._handleDevicesModified.bind(this),
|
||||
),
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
_handleDeviceModified(e: CustomEvent) {
|
||||
@@ -115,49 +147,93 @@ export const VMDeviceMixin = <T extends Constructor<LitElement>>(
|
||||
const activeIc = window.VM.vm.activeIC;
|
||||
if (this.deviceID === id) {
|
||||
this.updateDevice();
|
||||
} else if (id === activeIc.id) {
|
||||
this.requestUpdate();
|
||||
} else if (id === activeIc.id && this.deviceSubscriptions.includes("active-ic")) {
|
||||
this.updateDevice();
|
||||
}
|
||||
}
|
||||
|
||||
_handleDevicesModified(e: CustomEvent) {
|
||||
_handleDevicesModified(e: CustomEvent<number[]>) {
|
||||
const activeIc = window.VM.vm.activeIC;
|
||||
const ids = e.detail;
|
||||
this.requestUpdate();
|
||||
if (ids.includes(this.deviceID)) {
|
||||
this.updateDevice()
|
||||
} else if (ids.includes(activeIc.id) && this.deviceSubscriptions.includes("active-ic")) {
|
||||
this.updateDevice();
|
||||
}
|
||||
}
|
||||
|
||||
updateDevice() {
|
||||
this.device = window.VM.vm.devices.get(this.deviceID)!;
|
||||
|
||||
const name = this.device.name ?? null;
|
||||
if (this.name !== name) {
|
||||
this.name = name;
|
||||
}
|
||||
const nameHash = this.device.nameHash ?? null;
|
||||
if (this.nameHash !== nameHash) {
|
||||
this.nameHash = nameHash;
|
||||
}
|
||||
const prefabName = this.device.prefabName ?? null;
|
||||
if (this.prefabName !== prefabName) {
|
||||
this.prefabName = prefabName;
|
||||
}
|
||||
const fields = this.device.fields;
|
||||
if (!structuralEqual(this.fields, fields)) {
|
||||
this.fields = fields;
|
||||
}
|
||||
const slots = this.device.slots;
|
||||
if (!structuralEqual(this.slots, slots)) {
|
||||
this.slots = slots;
|
||||
}
|
||||
const reagents = this.device.reagents;
|
||||
if (!structuralEqual(this.reagents, reagents)) {
|
||||
this.reagents = reagents;
|
||||
}
|
||||
const connections = this.device.connections;
|
||||
if (!structuralEqual(this.connections, connections)) {
|
||||
this.connections = connections;
|
||||
}
|
||||
if (typeof this.device.ic !== "undefined") {
|
||||
this.updateIC();
|
||||
for (const sub of this.deviceSubscriptions) {
|
||||
if (typeof sub === "string") {
|
||||
if (sub == "name") {
|
||||
const name = this.device.name ?? null;
|
||||
if (this.name !== name) {
|
||||
this.name = name;
|
||||
}
|
||||
} else if (sub === "nameHash") {
|
||||
const nameHash = this.device.nameHash ?? null;
|
||||
if (this.nameHash !== nameHash) {
|
||||
this.nameHash = nameHash;
|
||||
}
|
||||
} else if (sub === "prefabName") {
|
||||
const prefabName = this.device.prefabName ?? null;
|
||||
if (this.prefabName !== prefabName) {
|
||||
this.prefabName = prefabName;
|
||||
}
|
||||
} else if (sub === "fields") {
|
||||
const fields = this.device.fields;
|
||||
if (!structuralEqual(this.fields, fields)) {
|
||||
this.fields = fields;
|
||||
}
|
||||
} else if (sub === "slots") {
|
||||
const slots = this.device.slots;
|
||||
if (!structuralEqual(this.slots, slots)) {
|
||||
this.slots = slots;
|
||||
}
|
||||
} else if (sub === "slots-count") {
|
||||
const slots = this.device.slots;
|
||||
if (typeof this.slots === "undefined") {
|
||||
this.slots = slots;
|
||||
} else if (this.slots.length !== slots.length) {
|
||||
this.slots = slots;
|
||||
}
|
||||
} else if (sub === "reagents") {
|
||||
const reagents = this.device.reagents;
|
||||
if (!structuralEqual(this.reagents, reagents)) {
|
||||
this.reagents = reagents;
|
||||
}
|
||||
} else if (sub === "connections") {
|
||||
const connections = this.device.connections;
|
||||
if (!structuralEqual(this.connections, connections)) {
|
||||
this.connections = connections;
|
||||
}
|
||||
} else if (sub === "ic") {
|
||||
if (typeof this.device.ic !== "undefined") {
|
||||
this.updateIC();
|
||||
}
|
||||
} else if (sub === "active-ic") {
|
||||
const activeIc = window.VM.vm?.activeIC;
|
||||
if (this.activeICId !== activeIc.id) {
|
||||
this.activeICId = activeIc.id;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if ( "field" in sub ) {
|
||||
const fields = this.device.fields;
|
||||
if (this.fields.get(sub.field) !== fields.get(sub.field)) {
|
||||
this.fields = fields;
|
||||
}
|
||||
} else if ( "slot" in sub) {
|
||||
const slots = this.device.slots;
|
||||
if (typeof this.slots === "undefined" || this.slots.length < sub.slot) {
|
||||
this.slots = slots;
|
||||
} else if (!structuralEqual(this.slots[sub.slot], slots[sub.slot])) {
|
||||
this.slots = slots;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -224,10 +300,12 @@ export const VMActiveICMixin = <T extends Constructor<LitElement>>(
|
||||
return root;
|
||||
}
|
||||
|
||||
|
||||
disconnectedCallback(): void {
|
||||
window.VM.get().then((vm) =>
|
||||
vm.removeEventListener("vm-run-ic", this._handleDeviceModified.bind(this)),
|
||||
vm.removeEventListener(
|
||||
"vm-run-ic",
|
||||
this._handleDeviceModified.bind(this),
|
||||
),
|
||||
);
|
||||
window.App.app.session.removeEventListener(
|
||||
"session-active-ic",
|
||||
@@ -274,7 +352,7 @@ export const VMDeviceDBMixin = <T extends Constructor<LitElement>>(
|
||||
window.VM.vm.removeEventListener(
|
||||
"vm-device-db-loaded",
|
||||
this._handleDeviceDBLoad.bind(this),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
_handleDeviceDBLoad(e: CustomEvent) {
|
||||
|
||||
@@ -7,6 +7,12 @@ import SlSelect from "@shoelace-style/shoelace/dist/components/select/select.js"
|
||||
|
||||
@customElement("vm-ic-controls")
|
||||
export class VMICControls extends VMActiveICMixin(BaseElement) {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.subscribe("ic", "active-ic")
|
||||
}
|
||||
|
||||
static styles = [
|
||||
...defaultCss,
|
||||
css`
|
||||
|
||||
@@ -3,19 +3,11 @@ import { customElement, property, query, state } from "lit/decorators.js";
|
||||
import { BaseElement, defaultCss } from "components";
|
||||
import { VMDeviceDBMixin, VMDeviceMixin } from "virtual_machine/base_device";
|
||||
import SlSelect from "@shoelace-style/shoelace/dist/components/select/select.component.js";
|
||||
import { displayNumber, parseIntWithHexOrBinary, parseNumber } from "utils";
|
||||
import {
|
||||
LogicType,
|
||||
Slot,
|
||||
SlotLogicType,
|
||||
SlotOccupant,
|
||||
SlotType,
|
||||
} from "ic10emu_wasm";
|
||||
import { parseIntWithHexOrBinary, parseNumber } from "utils";
|
||||
import SlInput from "@shoelace-style/shoelace/dist/components/input/input.component.js";
|
||||
import SlDialog from "@shoelace-style/shoelace/dist/components/dialog/dialog.component.js";
|
||||
import "./slot";
|
||||
import { when } from "lit/directives/when.js";
|
||||
import { cache } from "lit/directives/cache.js";
|
||||
import "./fields";
|
||||
import { until } from "lit/directives/until.js";
|
||||
import { repeat } from "lit/directives/repeat.js";
|
||||
|
||||
@@ -30,6 +22,16 @@ export class VMDeviceCard extends VMDeviceDBMixin(VMDeviceMixin(BaseElement)) {
|
||||
constructor() {
|
||||
super();
|
||||
this.open = false;
|
||||
this.subscribe(
|
||||
"prefabName",
|
||||
"name",
|
||||
"nameHash",
|
||||
"reagents",
|
||||
"slots-count",
|
||||
"reagents",
|
||||
"connections",
|
||||
"active-ic",
|
||||
);
|
||||
}
|
||||
|
||||
static styles = [
|
||||
@@ -72,13 +74,6 @@ export class VMDeviceCard extends VMDeviceDBMixin(VMDeviceMixin(BaseElement)) {
|
||||
.device-name-hash::part(input) {
|
||||
width: 7rem;
|
||||
}
|
||||
.slot-header.image {
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
border: var(--sl-panel-border-width) solid var(--sl-panel-border-color);
|
||||
border-radius: var(--sl-border-radius-medium);
|
||||
background-color: var(--sl-color-neutral-0);
|
||||
}
|
||||
sl-divider {
|
||||
--spacing: 0.25rem;
|
||||
}
|
||||
@@ -139,12 +134,12 @@ export class VMDeviceCard extends VMDeviceDBMixin(VMDeviceMixin(BaseElement)) {
|
||||
}
|
||||
|
||||
renderHeader(): HTMLTemplateResult {
|
||||
const activeIc = window.VM.vm.activeIC;
|
||||
const thisIsActiveIc = activeIc.id === this.deviceID;
|
||||
const thisIsActiveIc = this.activeICId === this.deviceID;
|
||||
const badges: HTMLTemplateResult[] = [];
|
||||
if (this.deviceID == activeIc?.id) {
|
||||
if (thisIsActiveIc) {
|
||||
badges.push(html`<sl-badge variant="primary" pill pulse>db</sl-badge>`);
|
||||
}
|
||||
const activeIc = window.VM.vm.activeIC;
|
||||
activeIc?.pins?.forEach((id, index) => {
|
||||
if (this.deviceID == id) {
|
||||
badges.push(
|
||||
@@ -185,18 +180,10 @@ export class VMDeviceCard extends VMDeviceDBMixin(VMDeviceMixin(BaseElement)) {
|
||||
}
|
||||
|
||||
renderFields() {
|
||||
const fields = Array.from(this.fields.entries());
|
||||
const inputIdBase = `vmDeviceCard${this.deviceID}Field`;
|
||||
return this.delayRenderTab("fields", html`
|
||||
${fields.map(([name, field], _index, _fields) => {
|
||||
return html` <sl-input id="${inputIdBase}${name}" key="${name}" value="${displayNumber(field.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>
|
||||
</sl-input>`;
|
||||
})}
|
||||
`);
|
||||
return this.delayRenderTab(
|
||||
"fields",
|
||||
html`<vm-device-fields .deviceID=${this.deviceID}></vm-device-fields>`,
|
||||
);
|
||||
}
|
||||
|
||||
_onSlotImageErr(e: Event) {
|
||||
@@ -207,18 +194,20 @@ export class VMDeviceCard extends VMDeviceDBMixin(VMDeviceMixin(BaseElement)) {
|
||||
"data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" as const;
|
||||
|
||||
async renderSlots() {
|
||||
return this.delayRenderTab("slots", html`
|
||||
<div class="flex flex-row flex-wrap">
|
||||
${repeat(
|
||||
this.slots,
|
||||
(_slot, index) => index,
|
||||
(_slot, index) => html`
|
||||
<vm-device-slot .deviceID=${this.deviceID} .slotIndex=${index} class-"flex flex-row max-w-lg mr-2 mb-2">
|
||||
</vm-device-slot>
|
||||
`,
|
||||
)}
|
||||
</div>
|
||||
`);
|
||||
return this.delayRenderTab(
|
||||
"slots",
|
||||
html`
|
||||
<div class="flex flex-row flex-wrap">
|
||||
${repeat(this.slots,
|
||||
(slot, index) => slot.typ + index.toString(),
|
||||
(_slot, index) => html`
|
||||
<vm-device-slot .deviceID=${this.deviceID} .slotIndex=${index} class-"flex flex-row max-w-lg mr-2 mb-2">
|
||||
</vm-device-slot>
|
||||
`,
|
||||
)}
|
||||
</div>
|
||||
`,
|
||||
);
|
||||
}
|
||||
|
||||
renderReagents() {
|
||||
@@ -242,7 +231,10 @@ export class VMDeviceCard extends VMDeviceDBMixin(VMDeviceMixin(BaseElement)) {
|
||||
</sl-select>
|
||||
`;
|
||||
});
|
||||
return this.delayRenderTab("networks", html`<div class="networks">${networks}</div>`);
|
||||
return this.delayRenderTab(
|
||||
"networks",
|
||||
html`<div class="networks">${networks}</div>`,
|
||||
);
|
||||
}
|
||||
|
||||
renderPins() {
|
||||
@@ -303,7 +295,6 @@ export class VMDeviceCard extends VMDeviceDBMixin(VMDeviceMixin(BaseElement)) {
|
||||
this.tabResolves[name].resolver(this.tabResolves[name].result);
|
||||
this.tabsShown.push(name);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
render(): HTMLTemplateResult {
|
||||
@@ -389,18 +380,6 @@ export class VMDeviceCard extends VMDeviceDBMixin(VMDeviceMixin(BaseElement)) {
|
||||
this.updateDevice();
|
||||
});
|
||||
}
|
||||
|
||||
_handleChangeField(e: CustomEvent) {
|
||||
const input = e.target as SlInput;
|
||||
const field = input.getAttribute("key")! as LogicType;
|
||||
const val = parseNumber(input.value);
|
||||
window.VM.get().then((vm) => {
|
||||
if (!vm.setDeviceField(this.deviceID, field, val, true)) {
|
||||
input.value = this.fields.get(field).value.toString();
|
||||
}
|
||||
this.updateDevice();
|
||||
});
|
||||
}
|
||||
_handleDeviceRemoveButton(_e: Event) {
|
||||
this.removeDialog.show();
|
||||
}
|
||||
|
||||
42
www/src/ts/virtual_machine/device/fields.ts
Normal file
42
www/src/ts/virtual_machine/device/fields.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import { html, css } from "lit";
|
||||
import { customElement, property } from "lit/decorators.js";
|
||||
import { BaseElement, defaultCss } from "components";
|
||||
import { VMDeviceDBMixin, VMDeviceMixin } 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 VMDeviceMixin(VMDeviceDBMixin(BaseElement)) {
|
||||
constructor() {
|
||||
super();
|
||||
this.subscribe("fields");
|
||||
}
|
||||
|
||||
render() {
|
||||
const fields = Array.from(this.fields.entries());
|
||||
const inputIdBase = `vmDeviceCard${this.deviceID}Field`;
|
||||
return html`
|
||||
${fields.map(([name, field], _index, _fields) => {
|
||||
return html` <sl-input id="${inputIdBase}${name}" key="${name}" value="${displayNumber(field.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>
|
||||
</sl-input>`;
|
||||
})}
|
||||
`;
|
||||
}
|
||||
|
||||
_handleChangeField(e: CustomEvent) {
|
||||
const input = e.target as SlInput;
|
||||
const field = input.getAttribute("key")! as LogicType;
|
||||
const val = parseNumber(input.value);
|
||||
window.VM.get().then((vm) => {
|
||||
if (!vm.setDeviceField(this.deviceID, field, val, true)) {
|
||||
input.value = this.fields.get(field).value.toString();
|
||||
}
|
||||
this.updateDevice();
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,19 +1,17 @@
|
||||
import { html, css } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators.js";
|
||||
import { customElement, property} from "lit/decorators.js";
|
||||
import { BaseElement, defaultCss } from "components";
|
||||
import { VMDeviceDBMixin, VMDeviceMixin } from "virtual_machine/base_device";
|
||||
import type { DeviceDB, DeviceDBEntry } from "virtual_machine/device_db";
|
||||
import SlSelect from "@shoelace-style/shoelace/dist/components/select/select.component.js";
|
||||
import { clamp, displayNumber, parseIntWithHexOrBinary, parseNumber } from "utils";
|
||||
import {
|
||||
LogicType,
|
||||
Slot,
|
||||
clamp,
|
||||
displayNumber,
|
||||
parseNumber,
|
||||
} from "utils";
|
||||
import {
|
||||
SlotLogicType,
|
||||
SlotOccupant,
|
||||
SlotType,
|
||||
} from "ic10emu_wasm";
|
||||
import SlInput from "@shoelace-style/shoelace/dist/components/input/input.component.js";
|
||||
import SlDialog from "@shoelace-style/shoelace/dist/components/dialog/dialog.component.js";
|
||||
import { VMDeviceCard } from "./card";
|
||||
import { when } from "lit/directives/when.js";
|
||||
|
||||
@@ -24,10 +22,23 @@ export interface SlotModifyEvent {
|
||||
|
||||
@customElement("vm-device-slot")
|
||||
export class VMDeviceSlot extends VMDeviceMixin(VMDeviceDBMixin(BaseElement)) {
|
||||
@property({ type: Number }) slotIndex: number;
|
||||
private _slotIndex: number;
|
||||
|
||||
get slotIndex() {
|
||||
return this._slotIndex;
|
||||
}
|
||||
|
||||
@property({ type: Number })
|
||||
set slotIndex(val: number) {
|
||||
this._slotIndex = val;
|
||||
this.unsubscribe((sub) => typeof sub === "object" && "slot" in sub);
|
||||
this.subscribe({ slot: val });
|
||||
}
|
||||
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.subscribe("active-ic");
|
||||
}
|
||||
|
||||
static styles = [
|
||||
@@ -101,7 +112,7 @@ export class VMDeviceSlot extends VMDeviceMixin(VMDeviceDBMixin(BaseElement)) {
|
||||
const template = this.slotOcccupantTemplate();
|
||||
|
||||
const activeIc = window.VM.vm.activeIC;
|
||||
const thisIsActiveIc = activeIc.id === this.deviceID;
|
||||
const thisIsActiveIc = this.activeICId === this.deviceID;
|
||||
|
||||
const enableQuantityInput = false;
|
||||
|
||||
@@ -109,13 +120,13 @@ export class VMDeviceSlot extends VMDeviceMixin(VMDeviceDBMixin(BaseElement)) {
|
||||
<div class="flex flex-row me-2">
|
||||
<div
|
||||
class="relative shrink-0 border border-neutral-200/40 rounded-lg p-1
|
||||
hover:ring-2 hover:ring-purple-500 hover:ring-offset-1
|
||||
hover:ring-offset-purple-500 cursor-pointer me-2"
|
||||
hover:ring-2 hover:ring-purple-500 hover:ring-offset-1
|
||||
hover:ring-offset-purple-500 cursor-pointer me-2"
|
||||
@click=${this._handleSlotClick}
|
||||
>
|
||||
<div
|
||||
class="absolute top-0 left-0 ml-1 mt-1 text-xs
|
||||
text-neutral-200/90 font-mono bg-neutral-500/40 rounded pl-1 pr-1"
|
||||
text-neutral-200/90 font-mono bg-neutral-500/40 rounded pl-1 pr-1"
|
||||
>
|
||||
<small>${this.slotIndex}</small>
|
||||
</div>
|
||||
@@ -127,7 +138,7 @@ export class VMDeviceSlot extends VMDeviceMixin(VMDeviceDBMixin(BaseElement)) {
|
||||
() =>
|
||||
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"
|
||||
text-neutral-200/90 font-mono bg-neutral-500/40 rounded pl-1 pr-1"
|
||||
>
|
||||
<small
|
||||
>${slot.occupant.quantity}/${slot.occupant
|
||||
@@ -156,22 +167,32 @@ export class VMDeviceSlot extends VMDeviceMixin(VMDeviceDBMixin(BaseElement)) {
|
||||
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.occupant.quantity.toString()}
|
||||
.min=${1}
|
||||
.max=${slot.occupant.max_quantity}
|
||||
@sl-change=${this._handleSlotQuantityChange}
|
||||
${enableQuantityInput
|
||||
? html` <sl-input
|
||||
type="number"
|
||||
size="small"
|
||||
.value=${slot.occupant.quantity.toString()}
|
||||
.min=${1}
|
||||
.max=${slot.occupant.max_quantity}
|
||||
@sl-change=${this._handleSlotQuantityChange}
|
||||
>
|
||||
<div slot="help-text">
|
||||
<span>Max Quantity: ${slot.occupant.max_quantity}</span>
|
||||
</div>
|
||||
</sl-input>`
|
||||
: ""}
|
||||
<sl-tooltip
|
||||
content=${thisIsActiveIc && slot.typ === "ProgrammableChip"
|
||||
? "Removing the selected Active IC is disabled"
|
||||
: "Remove Occupant"}
|
||||
>
|
||||
<div slot="help-text">
|
||||
<span>Max Quantity: ${slot.occupant.max_quantity}</span>
|
||||
</div>
|
||||
</sl-input>` : "" }
|
||||
<sl-tooltip content=${thisIsActiveIc ? "Removing the selected Active IC is disabled" : "Remove Occupant" }>
|
||||
<sl-icon-button class="clear-occupant" name="x-octagon" label="Remove" ?disabled=${thisIsActiveIc}
|
||||
@click=${this._handleSlotOccupantRemove}></sl-icon-button>
|
||||
<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>
|
||||
`,
|
||||
@@ -199,8 +220,18 @@ export class VMDeviceSlot extends VMDeviceMixin(VMDeviceDBMixin(BaseElement)) {
|
||||
const input = e.currentTarget as SlInput;
|
||||
const slot = this.slots[this.slotIndex];
|
||||
const val = clamp(input.valueAsNumber, 1, slot.occupant.max_quantity);
|
||||
if (!window.VM.vm.setDeviceSlotField(this.deviceID, this.slotIndex, "Quantity", val, true)) {
|
||||
input.value = this.device.getSlotField(this.slotIndex, "Quantity").toString();
|
||||
if (
|
||||
!window.VM.vm.setDeviceSlotField(
|
||||
this.deviceID,
|
||||
this.slotIndex,
|
||||
"Quantity",
|
||||
val,
|
||||
true,
|
||||
)
|
||||
) {
|
||||
input.value = this.device
|
||||
.getSlotField(this.slotIndex, "Quantity")
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -255,7 +286,9 @@ export class VMDeviceSlot extends VMDeviceMixin(VMDeviceDBMixin(BaseElement)) {
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<ic10-details class="slot-card">
|
||||
<ic10-details
|
||||
class="slot-card"
|
||||
>
|
||||
<div class="slot-header w-full" slot="summary">
|
||||
${this.renderHeader()}
|
||||
</div>
|
||||
@@ -263,4 +296,5 @@ export class VMDeviceSlot extends VMDeviceMixin(VMDeviceDBMixin(BaseElement)) {
|
||||
</ic10-details>
|
||||
`;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
LogicType,
|
||||
SlotLogicType,
|
||||
SlotOccupantTemplate,
|
||||
Slots,
|
||||
VMRef,
|
||||
init,
|
||||
} from "ic10emu_wasm";
|
||||
@@ -20,9 +21,39 @@ export interface ToastMessage {
|
||||
id: string;
|
||||
}
|
||||
|
||||
export interface CacheDeviceRef extends DeviceRef {
|
||||
dirty: boolean
|
||||
}
|
||||
|
||||
function cachedDeviceRef(ref: DeviceRef) {
|
||||
let slotsDirty = true;
|
||||
let cachedSlots: Slots = undefined;
|
||||
return new Proxy<DeviceRef>(ref, {
|
||||
get(target, prop, receiver) {
|
||||
if (prop === "slots") {
|
||||
if (typeof cachedSlots === undefined || slotsDirty) {
|
||||
cachedSlots = target.slots;
|
||||
slotsDirty = false;
|
||||
}
|
||||
return cachedSlots;
|
||||
} else if (prop === "dirty") {
|
||||
return slotsDirty;
|
||||
}
|
||||
return Reflect.get(target, prop, receiver);
|
||||
},
|
||||
set(target, prop, value) {
|
||||
if (prop === "dirty") {
|
||||
slotsDirty = value
|
||||
return true;
|
||||
}
|
||||
return Reflect.set(target, prop, value)
|
||||
}
|
||||
}) as CacheDeviceRef
|
||||
}
|
||||
|
||||
class VirtualMachine extends EventTarget {
|
||||
ic10vm: VMRef;
|
||||
_devices: Map<number, DeviceRef>;
|
||||
_devices: Map<number, CacheDeviceRef>;
|
||||
_ics: Map<number, DeviceRef>;
|
||||
|
||||
db: DeviceDB;
|
||||
@@ -93,7 +124,7 @@ class VirtualMachine extends EventTarget {
|
||||
const device_ids = this.ic10vm.devices;
|
||||
for (const id of device_ids) {
|
||||
if (!this._devices.has(id)) {
|
||||
this._devices.set(id, this.ic10vm.getDevice(id)!);
|
||||
this._devices.set(id, cachedDeviceRef(this.ic10vm.getDevice(id)!));
|
||||
update_flag = true;
|
||||
}
|
||||
}
|
||||
@@ -105,6 +136,7 @@ class VirtualMachine extends EventTarget {
|
||||
}
|
||||
|
||||
for (const [id, device] of this._devices) {
|
||||
device.dirty = true;
|
||||
if (typeof device.ic !== "undefined") {
|
||||
if (!this._ics.has(id)) {
|
||||
this._ics.set(id, device);
|
||||
@@ -204,11 +236,13 @@ class VirtualMachine extends EventTarget {
|
||||
);
|
||||
}
|
||||
}, this);
|
||||
this.updateDevice(this.activeIC, save);
|
||||
this.updateDevice(this.activeIC.id, save);
|
||||
if (save) this.app.session.save();
|
||||
}
|
||||
|
||||
updateDevice(device: DeviceRef, save: boolean = true) {
|
||||
updateDevice(id: number, save: boolean = true) {
|
||||
const device = this._devices.get(id);
|
||||
device.dirty = true;
|
||||
this.dispatchEvent(
|
||||
new CustomEvent("vm-device-modified", { detail: device.id }),
|
||||
);
|
||||
@@ -248,7 +282,7 @@ class VirtualMachine extends EventTarget {
|
||||
const ic = this.activeIC!;
|
||||
try {
|
||||
ic.setRegister(index, val);
|
||||
this.updateDevice(ic);
|
||||
this.updateDevice(ic.id);
|
||||
return true;
|
||||
} catch (err) {
|
||||
this.handleVmError(err);
|
||||
@@ -260,7 +294,7 @@ class VirtualMachine extends EventTarget {
|
||||
const ic = this.activeIC!;
|
||||
try {
|
||||
ic!.setStack(addr, val);
|
||||
this.updateDevice(ic);
|
||||
this.updateDevice(ic.id);
|
||||
return true;
|
||||
} catch (err) {
|
||||
this.handleVmError(err);
|
||||
@@ -296,7 +330,7 @@ class VirtualMachine extends EventTarget {
|
||||
if (device) {
|
||||
try {
|
||||
device.setField(field, val, force);
|
||||
this.updateDevice(device);
|
||||
this.updateDevice(device.id);
|
||||
return true;
|
||||
} catch (err) {
|
||||
this.handleVmError(err);
|
||||
@@ -317,7 +351,7 @@ class VirtualMachine extends EventTarget {
|
||||
if (device) {
|
||||
try {
|
||||
device.setSlotField(slot, field, val, force);
|
||||
this.updateDevice(device);
|
||||
this.updateDevice(device.id);
|
||||
return true;
|
||||
} catch (err) {
|
||||
this.handleVmError(err);
|
||||
@@ -335,7 +369,7 @@ class VirtualMachine extends EventTarget {
|
||||
if (typeof device !== "undefined") {
|
||||
try {
|
||||
this.ic10vm.setDeviceConnection(id, conn, val);
|
||||
this.updateDevice(device);
|
||||
this.updateDevice(device.id);
|
||||
return true;
|
||||
} catch (err) {
|
||||
this.handleVmError(err);
|
||||
@@ -349,7 +383,7 @@ class VirtualMachine extends EventTarget {
|
||||
if (typeof device !== "undefined") {
|
||||
try {
|
||||
this.ic10vm.setPin(id, pin, val);
|
||||
this.updateDevice(device);
|
||||
this.updateDevice(device.id);
|
||||
return true;
|
||||
} catch (err) {
|
||||
this.handleVmError(err);
|
||||
@@ -370,7 +404,7 @@ class VirtualMachine extends EventTarget {
|
||||
try {
|
||||
console.log("adding device", template);
|
||||
const id = this.ic10vm.addDeviceFromTemplate(template);
|
||||
this._devices.set(id, this.ic10vm.getDevice(id)!);
|
||||
this._devices.set(id, cachedDeviceRef(this.ic10vm.getDevice(id)!));
|
||||
const device_ids = this.ic10vm.devices;
|
||||
this.dispatchEvent(
|
||||
new CustomEvent("vm-devices-update", {
|
||||
@@ -402,7 +436,7 @@ class VirtualMachine extends EventTarget {
|
||||
try {
|
||||
console.log("setting slot occupant", template);
|
||||
this.ic10vm.setSlotOccupant(id, index, template);
|
||||
this.updateDevice(device);
|
||||
this.updateDevice(device.id);
|
||||
return true;
|
||||
} catch (err) {
|
||||
this.handleVmError(err);
|
||||
@@ -416,7 +450,7 @@ class VirtualMachine extends EventTarget {
|
||||
if (typeof device !== "undefined") {
|
||||
try {
|
||||
this.ic10vm.removeSlotOccupant(id, index);
|
||||
this.updateDevice(device);
|
||||
this.updateDevice(device.id);
|
||||
return true;
|
||||
} catch (err) {
|
||||
this.handleVmError(err);
|
||||
|
||||
@@ -40,6 +40,7 @@ export class VMICRegisters extends VMActiveICMixin(BaseElement) {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.subscribe("ic", "active-ic")
|
||||
}
|
||||
|
||||
protected render() {
|
||||
|
||||
@@ -37,6 +37,7 @@ export class VMICStack extends VMActiveICMixin(BaseElement) {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.subscribe("ic", "active-ic")
|
||||
}
|
||||
|
||||
protected render() {
|
||||
|
||||
Reference in New Issue
Block a user