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:
@@ -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<SlotLogicType, LogicField>,
|
||||
fields: BTreeMap<SlotLogicType, LogicField>,
|
||||
}
|
||||
|
||||
impl SlotOccupant {
|
||||
@@ -71,7 +71,7 @@ impl SlotOccupant {
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct SlotOccupantTemplate {
|
||||
pub id: Option<u32>,
|
||||
pub fields: HashMap<SlotLogicType, LogicField>,
|
||||
pub fields: BTreeMap<SlotLogicType, LogicField>,
|
||||
}
|
||||
|
||||
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<SlotLogicType, LogicField>) -> Self {
|
||||
pub fn with_fields(mut self, fields: BTreeMap<SlotLogicType, LogicField>) -> Self {
|
||||
self.fields.extend(fields);
|
||||
self
|
||||
}
|
||||
|
||||
/// chainable constructor
|
||||
pub fn get_fields(&self) -> HashMap<SlotLogicType, LogicField> {
|
||||
pub fn get_fields(&self) -> BTreeMap<SlotLogicType, LogicField> {
|
||||
let mut copy = self.fields.clone();
|
||||
copy.insert(
|
||||
SlotLogicType::PrefabHash,
|
||||
@@ -234,7 +234,7 @@ impl Slot {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_fields(&self) -> HashMap<SlotLogicType, LogicField> {
|
||||
pub fn get_fields(&self) -> BTreeMap<SlotLogicType, LogicField> {
|
||||
let mut copy = self
|
||||
.occupant
|
||||
.as_ref()
|
||||
@@ -546,10 +546,10 @@ pub struct Device {
|
||||
pub name_hash: Option<i32>,
|
||||
pub prefab: Option<Prefab>,
|
||||
pub slots: Vec<Slot>,
|
||||
pub reagents: HashMap<ReagentMode, HashMap<i32, f64>>,
|
||||
pub reagents: BTreeMap<ReagentMode, BTreeMap<i32, f64>>,
|
||||
pub ic: Option<u32>,
|
||||
pub connections: Vec<Connection>,
|
||||
fields: HashMap<LogicType, LogicField>,
|
||||
fields: BTreeMap<LogicType, LogicField>,
|
||||
}
|
||||
|
||||
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<LogicType, LogicField> {
|
||||
pub fn get_fields(&self, vm: &VM) -> BTreeMap<LogicType, LogicField> {
|
||||
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<HashMap<SlotLogicType, LogicField>, ICError> {
|
||||
) -> Result<BTreeMap<SlotLogicType, LogicField>, ICError> {
|
||||
let slot = self
|
||||
.slots
|
||||
.get(index as usize)
|
||||
@@ -908,9 +908,9 @@ pub struct DeviceTemplate {
|
||||
pub name: Option<String>,
|
||||
pub prefab_name: Option<String>,
|
||||
pub slots: Vec<SlotTemplate>,
|
||||
// pub reagents: HashMap<ReagentMode, HashMap<i32, f64>>,
|
||||
// pub reagents: BTreeMap<ReagentMode, BTreeMap<i32, f64>>,
|
||||
pub connections: Vec<Connection>,
|
||||
pub fields: HashMap<LogicType, LogicField>,
|
||||
pub fields: BTreeMap<LogicType, LogicField>,
|
||||
}
|
||||
|
||||
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,
|
||||
|
||||
@@ -29,7 +29,7 @@ pub mod generated {
|
||||
fn try_from(value: f64) -> Result<Self, <LogicType as TryFrom<f64>>::Error> {
|
||||
if let Some(lt) = LogicType::iter().find(|lt| {
|
||||
lt.get_str("value")
|
||||
.map(|val| val.parse::<u8>().unwrap() as f64 == value)
|
||||
.map(|val| val.parse::<u16>().unwrap() as f64 == value)
|
||||
.unwrap_or(false)
|
||||
}) {
|
||||
Ok(lt)
|
||||
|
||||
@@ -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<u16>,
|
||||
pub stack: RefCell<[f64; 512]>,
|
||||
pub aliases: RefCell<HashMap<String, grammar::Operand>>,
|
||||
pub defines: RefCell<HashMap<String, f64>>,
|
||||
pub aliases: RefCell<BTreeMap<String, grammar::Operand>>,
|
||||
pub defines: RefCell<BTreeMap<String, f64>>,
|
||||
pub pins: RefCell<[Option<u32>; 6]>,
|
||||
pub code: RefCell<String>,
|
||||
pub program: RefCell<Program>,
|
||||
@@ -210,8 +209,8 @@ pub struct FrozenIC {
|
||||
pub ic: u16,
|
||||
#[serde_as(as = "[_; 512]")]
|
||||
pub stack: [f64; 512],
|
||||
pub aliases: HashMap<String, grammar::Operand>,
|
||||
pub defines: HashMap<String, f64>,
|
||||
pub aliases: BTreeMap<String, grammar::Operand>,
|
||||
pub defines: BTreeMap<String, f64>,
|
||||
pub pins: [Option<u32>; 6],
|
||||
pub state: ICState,
|
||||
pub code: String,
|
||||
@@ -261,7 +260,7 @@ impl From<FrozenIC> for IC {
|
||||
pub struct Program {
|
||||
pub instructions: Vec<grammar::Instruction>,
|
||||
pub errors: Vec<ICError>,
|
||||
pub labels: HashMap<String, u32>,
|
||||
pub labels: BTreeMap<String, u32>,
|
||||
}
|
||||
|
||||
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<Self, ICError> {
|
||||
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<bool, LineError> {
|
||||
// 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
|
||||
}
|
||||
|
||||
@@ -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<u32, Rc<RefCell<interpreter::IC>>>,
|
||||
pub devices: HashMap<u32, Rc<RefCell<Device>>>,
|
||||
pub networks: HashMap<u32, Rc<RefCell<Network>>>,
|
||||
pub ics: BTreeMap<u32, Rc<RefCell<interpreter::IC>>>,
|
||||
pub devices: BTreeMap<u32, Rc<RefCell<Device>>>,
|
||||
pub networks: BTreeMap<u32, Rc<RefCell<Network>>>,
|
||||
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,
|
||||
|
||||
@@ -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<JsValue> {
|
||||
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")]
|
||||
|
||||
@@ -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<ic10emu::grammar::SlotLogicType, ic10emu::device::LogicField>,
|
||||
pub fields: BTreeMap<ic10emu::grammar::SlotLogicType, ic10emu::device::LogicField>,
|
||||
}
|
||||
|
||||
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<SlotOccupant>,
|
||||
pub fields: HashMap<ic10emu::grammar::SlotLogicType, ic10emu::device::LogicField>,
|
||||
pub fields: BTreeMap<ic10emu::grammar::SlotLogicType, ic10emu::device::LogicField>,
|
||||
}
|
||||
|
||||
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<Slot>);
|
||||
|
||||
impl<'a> FromIterator<&'a ic10emu::device::Slot> for Slots {
|
||||
fn from_iter<T: IntoIterator<Item = &'a ic10emu::device::Slot>>(iter: T) -> Self {
|
||||
Slots(iter.into_iter().map(|slot| slot.into()).collect_vec())
|
||||
}
|
||||
}
|
||||
|
||||
include!(concat!(env!("OUT_DIR"), "/ts_types.rs"));
|
||||
|
||||
// #[serde_as]
|
||||
|
||||
@@ -7,19 +7,19 @@ export interface LogicField {
|
||||
export type LogicFields = Map<LogicType, LogicField>;
|
||||
export type SlotLogicFields = Map<SlotLogicType, LogicField>;
|
||||
|
||||
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<string, Map<number, number>>;
|
||||
|
||||
|
||||
@@ -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