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:
Rachel Powers
2024-04-25 20:38:03 -07:00
parent 2480a08ada
commit cfa240c579
16 changed files with 427 additions and 212 deletions

View File

@@ -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) {