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:
@@ -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>
|
||||
`;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user