From cfa240c5794817ce4221cdac8be2e96e320edf5c Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Thu, 25 Apr 2024 20:38:03 -0700 Subject: [PATCH] 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> --- ic10emu/src/device.rs | 32 ++-- ic10emu/src/grammar.rs | 2 +- ic10emu/src/interpreter.rs | 40 +++-- ic10emu/src/vm.rs | 14 +- ic10emu_wasm/src/lib.rs | 27 ++-- ic10emu_wasm/src/types.rs | 26 +++- ic10emu_wasm/src/types.ts | 26 ++-- www/src/ts/presets/demo.ts | 15 +- www/src/ts/virtual_machine/base_device.ts | 154 +++++++++++++++----- www/src/ts/virtual_machine/controls.ts | 6 + www/src/ts/virtual_machine/device/card.ts | 95 +++++------- www/src/ts/virtual_machine/device/fields.ts | 42 ++++++ www/src/ts/virtual_machine/device/slot.ts | 98 +++++++++---- www/src/ts/virtual_machine/index.ts | 60 ++++++-- www/src/ts/virtual_machine/registers.ts | 1 + www/src/ts/virtual_machine/stack.ts | 1 + 16 files changed, 427 insertions(+), 212 deletions(-) create mode 100644 www/src/ts/virtual_machine/device/fields.ts diff --git a/ic10emu/src/device.rs b/ic10emu/src/device.rs index e8f4f68..205a984 100644 --- a/ic10emu/src/device.rs +++ b/ic10emu/src/device.rs @@ -4,7 +4,7 @@ use crate::{ network::{CableConnectionType, Connection}, vm::VM, }; -use std::{collections::HashMap, ops::Deref}; +use std::{collections::BTreeMap, ops::Deref}; use itertools::Itertools; @@ -32,7 +32,7 @@ pub struct SlotOccupant { pub max_quantity: u32, pub sorting_class: SortingClass, pub damage: f64, - fields: HashMap, + fields: BTreeMap, } impl SlotOccupant { @@ -71,7 +71,7 @@ impl SlotOccupant { #[derive(Debug, Clone, Serialize, Deserialize)] pub struct SlotOccupantTemplate { pub id: Option, - pub fields: HashMap, + pub fields: BTreeMap, } impl SlotOccupant { @@ -83,7 +83,7 @@ impl SlotOccupant { max_quantity: 1, damage: 0.0, sorting_class: SortingClass::Default, - fields: HashMap::new(), + fields: BTreeMap::new(), } } @@ -106,13 +106,13 @@ impl SlotOccupant { } /// chainable constructor - pub fn with_fields(mut self, fields: HashMap) -> Self { + pub fn with_fields(mut self, fields: BTreeMap) -> Self { self.fields.extend(fields); self } /// chainable constructor - pub fn get_fields(&self) -> HashMap { + pub fn get_fields(&self) -> BTreeMap { let mut copy = self.fields.clone(); copy.insert( SlotLogicType::PrefabHash, @@ -234,7 +234,7 @@ impl Slot { } } - pub fn get_fields(&self) -> HashMap { + pub fn get_fields(&self) -> BTreeMap { let mut copy = self .occupant .as_ref() @@ -546,10 +546,10 @@ pub struct Device { pub name_hash: Option, pub prefab: Option, pub slots: Vec, - pub reagents: HashMap>, + pub reagents: BTreeMap>, pub ic: Option, pub connections: Vec, - fields: HashMap, + fields: BTreeMap, } impl Device { @@ -559,9 +559,9 @@ impl Device { name: None, name_hash: None, prefab: None, - fields: HashMap::new(), + fields: BTreeMap::new(), slots: Vec::new(), - reagents: HashMap::new(), + reagents: BTreeMap::new(), ic: None, connections: vec![Connection::CableNetwork { net: None, @@ -617,7 +617,7 @@ impl Device { device } - pub fn get_fields(&self, vm: &VM) -> HashMap { + pub fn get_fields(&self, vm: &VM) -> BTreeMap { let mut copy = self.fields.clone(); if let Some(ic_id) = &self.ic { let ic = vm.ics.get(ic_id).expect("our own ic to exist").borrow(); @@ -819,7 +819,7 @@ impl Device { &self, index: f64, vm: &VM, - ) -> Result, ICError> { + ) -> Result, ICError> { let slot = self .slots .get(index as usize) @@ -908,9 +908,9 @@ pub struct DeviceTemplate { pub name: Option, pub prefab_name: Option, pub slots: Vec, - // pub reagents: HashMap>, + // pub reagents: BTreeMap>, pub connections: Vec, - pub fields: HashMap, + pub fields: BTreeMap, } impl Device { @@ -959,7 +959,7 @@ impl Device { prefab: template.prefab_name.map(|name| Prefab::new(&name)), slots, // reagents: template.reagents, - reagents: HashMap::new(), + reagents: BTreeMap::new(), ic, connections: template.connections, fields, diff --git a/ic10emu/src/grammar.rs b/ic10emu/src/grammar.rs index 744956e..3e6f4a1 100644 --- a/ic10emu/src/grammar.rs +++ b/ic10emu/src/grammar.rs @@ -29,7 +29,7 @@ pub mod generated { fn try_from(value: f64) -> Result>::Error> { if let Some(lt) = LogicType::iter().find(|lt| { lt.get_str("value") - .map(|val| val.parse::().unwrap() as f64 == value) + .map(|val| val.parse::().unwrap() as f64 == value) .unwrap_or(false) }) { Ok(lt) diff --git a/ic10emu/src/interpreter.rs b/ic10emu/src/interpreter.rs index c0a1f65..d3bd663 100644 --- a/ic10emu/src/interpreter.rs +++ b/ic10emu/src/interpreter.rs @@ -2,7 +2,7 @@ use core::f64; use serde::{Deserialize, Serialize}; use std::{cell::{Cell, RefCell}, ops::Deref, string::ToString}; use std::{ - collections::{HashMap, HashSet}, + collections::{BTreeMap, HashSet}, error::Error, fmt::Display, u32, @@ -13,8 +13,7 @@ use itertools::Itertools; use time::format_description; use crate::{ - grammar::{self, ParseError}, - vm::VM, + device::SlotType, grammar::{self, LogicType, ParseError, SlotLogicType}, vm::VM }; use serde_with::serde_as; @@ -190,8 +189,8 @@ pub struct IC { /// Instruction Count since last yield pub ic: Cell, pub stack: RefCell<[f64; 512]>, - pub aliases: RefCell>, - pub defines: RefCell>, + pub aliases: RefCell>, + pub defines: RefCell>, pub pins: RefCell<[Option; 6]>, pub code: RefCell, pub program: RefCell, @@ -210,8 +209,8 @@ pub struct FrozenIC { pub ic: u16, #[serde_as(as = "[_; 512]")] pub stack: [f64; 512], - pub aliases: HashMap, - pub defines: HashMap, + pub aliases: BTreeMap, + pub defines: BTreeMap, pub pins: [Option; 6], pub state: ICState, pub code: String, @@ -261,7 +260,7 @@ impl From for IC { pub struct Program { pub instructions: Vec, pub errors: Vec, - pub labels: HashMap, + pub labels: BTreeMap, } impl Default for Program { @@ -275,14 +274,14 @@ impl Program { Program { instructions: Vec::new(), errors: Vec::new(), - labels: HashMap::new(), + labels: BTreeMap::new(), } } pub fn try_from_code(code: &str) -> Result { let parse_tree = grammar::parse(code)?; let mut labels_set = HashSet::new(); - let mut labels = HashMap::new(); + let mut labels = BTreeMap::new(); let errors = Vec::new(); let instructions = parse_tree .into_iter() @@ -320,7 +319,7 @@ impl Program { pub fn from_code_with_invalid(code: &str) -> Self { let parse_tree = grammar::parse_with_invlaid(code); let mut labels_set = HashSet::new(); - let mut labels = HashMap::new(); + let mut labels = BTreeMap::new(); let mut errors = Vec::new(); let instructions = parse_tree .into_iter() @@ -380,8 +379,8 @@ impl IC { pins: RefCell::new([None; 6]), program: RefCell::new(Program::new()), code: RefCell::new(String::new()), - aliases: RefCell::new(HashMap::new()), - defines: RefCell::new(HashMap::new()), + aliases: RefCell::new(BTreeMap::new()), + defines: RefCell::new(BTreeMap::new()), state: RefCell::new(ICState::Start), } } @@ -391,8 +390,8 @@ impl IC { self.ic.replace(0); self.registers.replace([0.0; 18]); self.stack.replace([0.0; 512]); - self.aliases.replace(HashMap::new()); - self.defines.replace(HashMap::new()); + self.aliases.replace(BTreeMap::new()); + self.defines.replace(BTreeMap::new()); self.state.replace(ICState::Start); } @@ -522,6 +521,16 @@ impl IC { } } + pub fn propgate_line_number(&self, vm: &VM) { + if let Some(device) = vm.devices.get(&self.device) { + let mut device_ref = device.borrow_mut(); + let _ = device_ref.set_field(LogicType::LineNumber, self.ip.get() as f64, vm, true); + if let Some(slot) = device_ref.slots.iter_mut().find(|slot| slot.typ == SlotType::ProgrammableChip) { + let _ = slot.set_field(SlotLogicType::LineNumber, self.ip.get() as f64, true); + } + } + } + /// processes one line of the contained program pub fn step(&self, vm: &VM, advance_ip_on_err: bool) -> Result { // TODO: handle sleep @@ -2552,6 +2561,7 @@ impl IC { if result.is_ok() || advance_ip_on_err { self.ic.set(self.ic.get() + 1); self.set_ip(next_ip); + self.propgate_line_number(vm); } result } diff --git a/ic10emu/src/vm.rs b/ic10emu/src/vm.rs index 72a391c..d7819ce 100644 --- a/ic10emu/src/vm.rs +++ b/ic10emu/src/vm.rs @@ -6,7 +6,7 @@ use crate::{ }; use std::{ cell::RefCell, - collections::{HashMap, HashSet}, + collections::{BTreeMap, HashSet}, rc::Rc, }; @@ -40,9 +40,9 @@ pub enum VMError { #[derive(Debug)] pub struct VM { - pub ics: HashMap>>, - pub devices: HashMap>>, - pub networks: HashMap>>, + pub ics: BTreeMap>>, + pub devices: BTreeMap>>, + pub networks: BTreeMap>>, pub default_network: u32, id_space: IdSpace, network_id_space: IdSpace, @@ -64,12 +64,12 @@ impl VM { let mut network_id_space = IdSpace::default(); let default_network_key = network_id_space.next(); let default_network = Rc::new(RefCell::new(Network::new(default_network_key))); - let mut networks = HashMap::new(); + let mut networks = BTreeMap::new(); networks.insert(default_network_key, default_network); let mut vm = VM { - ics: HashMap::new(), - devices: HashMap::new(), + ics: BTreeMap::new(), + devices: BTreeMap::new(), networks, default_network: default_network_key, id_space: id_gen, diff --git a/ic10emu_wasm/src/lib.rs b/ic10emu_wasm/src/lib.rs index 571b795..4204bb2 100644 --- a/ic10emu_wasm/src/lib.rs +++ b/ic10emu_wasm/src/lib.rs @@ -13,6 +13,7 @@ use types::{Registers, Stack}; use std::{cell::RefCell, rc::Rc, str::FromStr}; use itertools::Itertools; +// use std::iter::FromIterator; // use itertools::Itertools; use wasm_bindgen::prelude::*; @@ -86,17 +87,9 @@ impl DeviceRef { serde_wasm_bindgen::to_value(&self.device.borrow().get_fields(&self.vm.borrow())).unwrap() } - #[wasm_bindgen(getter, skip_typescript)] - pub fn slots(&self) -> Vec { - self.device - .borrow() - .slots - .iter() - .map(|slot| { - let flat_slot: types::Slot = slot.into(); - serde_wasm_bindgen::to_value(&flat_slot).unwrap() - }) - .collect_vec() + #[wasm_bindgen(getter)] + pub fn slots(&self) -> types::Slots { + types::Slots::from_iter(self.device.borrow().slots.iter()) } #[wasm_bindgen(getter, skip_typescript)] @@ -490,9 +483,17 @@ impl VMRef { } #[wasm_bindgen(js_name = "setSlotOccupant", skip_typescript)] - pub fn set_slot_occupant(&self, id: u32, index: usize, template: JsValue) -> Result<(), JsError> { + pub fn set_slot_occupant( + &self, + id: u32, + index: usize, + template: JsValue, + ) -> Result<(), JsError> { let template: SlotOccupantTemplate = serde_wasm_bindgen::from_value(template)?; - Ok(self.vm.borrow_mut().set_slot_occupant(id, index, template)?) + Ok(self + .vm + .borrow_mut() + .set_slot_occupant(id, index, template)?) } #[wasm_bindgen(js_name = "removeSlotOccupant")] diff --git a/ic10emu_wasm/src/types.rs b/ic10emu_wasm/src/types.rs index 09f4fb9..5bb3bb8 100644 --- a/ic10emu_wasm/src/types.rs +++ b/ic10emu_wasm/src/types.rs @@ -1,8 +1,9 @@ #![allow(non_snake_case)] // use std::collections::BTreeMap; -use std::collections::HashMap; +use std::collections::BTreeMap; +use itertools::Itertools; use serde::{Deserialize, Serialize}; use serde_with::serde_as; use tsify::Tsify; @@ -18,14 +19,16 @@ pub struct Stack(#[serde_as(as = "[_; 512]")] pub [f64; 512]); #[tsify(into_wasm_abi, from_wasm_abi)] pub struct Registers(#[serde_as(as = "[_; 18]")] pub [f64; 18]); -#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde_as] +#[derive(Tsify, Debug, Clone, Serialize, Deserialize)] +#[tsify(into_wasm_abi, from_wasm_abi)] pub struct SlotOccupant { pub id: u32, pub prefab_hash: i32, pub quantity: u32, pub max_quantity: u32, pub damage: f64, - pub fields: HashMap, + pub fields: BTreeMap, } impl From<&ic10emu::device::SlotOccupant> for SlotOccupant { @@ -41,11 +44,13 @@ impl From<&ic10emu::device::SlotOccupant> for SlotOccupant { } } -#[derive(Debug, Default, Serialize, Deserialize)] +#[serde_as] +#[derive(Tsify, Debug, Clone, Default, Serialize, Deserialize)] +#[tsify(into_wasm_abi, from_wasm_abi)] pub struct Slot { pub typ: ic10emu::device::SlotType, pub occupant: Option, - pub fields: HashMap, + pub fields: BTreeMap, } impl From<&ic10emu::device::Slot> for Slot { @@ -58,6 +63,17 @@ impl From<&ic10emu::device::Slot> for Slot { } } +#[serde_as] +#[derive(Tsify, Debug, Clone, Serialize, Deserialize)] +#[tsify(into_wasm_abi, from_wasm_abi)] +pub struct Slots(pub Vec); + +impl<'a> FromIterator<&'a ic10emu::device::Slot> for Slots { + fn from_iter>(iter: T) -> Self { + Slots(iter.into_iter().map(|slot| slot.into()).collect_vec()) + } +} + include!(concat!(env!("OUT_DIR"), "/ts_types.rs")); // #[serde_as] diff --git a/ic10emu_wasm/src/types.ts b/ic10emu_wasm/src/types.ts index 96d0589..b2e0a08 100644 --- a/ic10emu_wasm/src/types.ts +++ b/ic10emu_wasm/src/types.ts @@ -7,19 +7,19 @@ export interface LogicField { export type LogicFields = Map; export type SlotLogicFields = Map; -export interface SlotOccupant { - readonly id: number; - readonly prefab_hash: number; - readonly quantity: number; - readonly max_quantity: number; - readonly damage: number; - readonly fields: SlotLogicFields; -} -export interface Slot { - readonly typ: SlotType; - readonly occupant: SlotOccupant | undefined; - readonly fields: SlotLogicFields; -} +// export interface SlotOccupant { +// readonly id: number; +// readonly prefab_hash: number; +// readonly quantity: number; +// readonly max_quantity: number; +// readonly damage: number; +// readonly fields: SlotLogicFields; +// } +// export interface Slot { +// readonly typ: SlotType; +// readonly occupant: SlotOccupant | undefined; +// readonly fields: SlotLogicFields; +// } export type Reagents = Map>; diff --git a/www/src/ts/presets/demo.ts b/www/src/ts/presets/demo.ts index 8d7e147..dd817e4 100644 --- a/www/src/ts/presets/demo.ts +++ b/www/src/ts/presets/demo.ts @@ -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: [ diff --git a/www/src/ts/virtual_machine/base_device.ts b/www/src/ts/virtual_machine/base_device.ts index 0f4b571..1a11cf8 100644 --- a/www/src/ts/virtual_machine/base_device.ts +++ b/www/src/ts/virtual_machine/base_device.ts @@ -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 = 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 = >( superClass: T, ) => { @@ -57,8 +75,23 @@ export const VMDeviceMixin = >( 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 = >( this._handleDevicesModified.bind(this), ), ); - } _handleDeviceModified(e: CustomEvent) { @@ -115,49 +147,93 @@ export const VMDeviceMixin = >( 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) { + 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 = >( 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 = >( window.VM.vm.removeEventListener( "vm-device-db-loaded", this._handleDeviceDBLoad.bind(this), - ) + ); } _handleDeviceDBLoad(e: CustomEvent) { diff --git a/www/src/ts/virtual_machine/controls.ts b/www/src/ts/virtual_machine/controls.ts index c376be2..067ebd1 100644 --- a/www/src/ts/virtual_machine/controls.ts +++ b/www/src/ts/virtual_machine/controls.ts @@ -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` diff --git a/www/src/ts/virtual_machine/device/card.ts b/www/src/ts/virtual_machine/device/card.ts index 4f92367..6d9a04d 100644 --- a/www/src/ts/virtual_machine/device/card.ts +++ b/www/src/ts/virtual_machine/device/card.ts @@ -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`db`); } + 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` - ${name} - - ${field.field_type} - `; - })} - `); + return this.delayRenderTab( + "fields", + html``, + ); } _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` -
- ${repeat( - this.slots, - (_slot, index) => index, - (_slot, index) => html` - - - `, - )} -
- `); + return this.delayRenderTab( + "slots", + html` +
+ ${repeat(this.slots, + (slot, index) => slot.typ + index.toString(), + (_slot, index) => html` + + + `, + )} +
+ `, + ); } renderReagents() { @@ -242,7 +231,10 @@ export class VMDeviceCard extends VMDeviceDBMixin(VMDeviceMixin(BaseElement)) { `; }); - return this.delayRenderTab("networks", html`
${networks}
`); + return this.delayRenderTab( + "networks", + html`
${networks}
`, + ); } 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(); } diff --git a/www/src/ts/virtual_machine/device/fields.ts b/www/src/ts/virtual_machine/device/fields.ts new file mode 100644 index 0000000..e45c245 --- /dev/null +++ b/www/src/ts/virtual_machine/device/fields.ts @@ -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` + ${name} + + ${field.field_type} + `; + })} + `; + } + + _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(); + }); + } +} diff --git a/www/src/ts/virtual_machine/device/slot.ts b/www/src/ts/virtual_machine/device/slot.ts index 2be3d6f..d03c592 100644 --- a/www/src/ts/virtual_machine/device/slot.ts +++ b/www/src/ts/virtual_machine/device/slot.ts @@ -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)) {
${this.slotIndex}
@@ -127,7 +138,7 @@ export class VMDeviceSlot extends VMDeviceMixin(VMDeviceDBMixin(BaseElement)) { () => html`
${slot.occupant.quantity}/${slot.occupant @@ -156,22 +167,32 @@ export class VMDeviceSlot extends VMDeviceMixin(VMDeviceDBMixin(BaseElement)) { typeof slot.occupant !== "undefined", () => html`
- ${ enableQuantityInput ? html` - +
+ Max Quantity: ${slot.occupant.max_quantity} +
+
` + : ""} + -
- Max Quantity: ${slot.occupant.max_quantity} -
- ` : "" } - - +
`, @@ -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` - +
${this.renderHeader()}
@@ -263,4 +296,5 @@ export class VMDeviceSlot extends VMDeviceMixin(VMDeviceDBMixin(BaseElement)) {
`; } + } diff --git a/www/src/ts/virtual_machine/index.ts b/www/src/ts/virtual_machine/index.ts index dff3210..7bb335d 100644 --- a/www/src/ts/virtual_machine/index.ts +++ b/www/src/ts/virtual_machine/index.ts @@ -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(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; + _devices: Map; _ics: Map; 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); diff --git a/www/src/ts/virtual_machine/registers.ts b/www/src/ts/virtual_machine/registers.ts index 19aa885..6abae1b 100644 --- a/www/src/ts/virtual_machine/registers.ts +++ b/www/src/ts/virtual_machine/registers.ts @@ -40,6 +40,7 @@ export class VMICRegisters extends VMActiveICMixin(BaseElement) { constructor() { super(); + this.subscribe("ic", "active-ic") } protected render() { diff --git a/www/src/ts/virtual_machine/stack.ts b/www/src/ts/virtual_machine/stack.ts index 68417f2..5d72b85 100644 --- a/www/src/ts/virtual_machine/stack.ts +++ b/www/src/ts/virtual_machine/stack.ts @@ -37,6 +37,7 @@ export class VMICStack extends VMActiveICMixin(BaseElement) { constructor() { super(); + this.subscribe("ic", "active-ic") } protected render() {