From d50eabe594579a3349e559a4d09daa31921ea335 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Sat, 13 Apr 2024 00:45:39 -0700 Subject: [PATCH] make slots account for having occupents. return logic fields that are deived form the device or slot occupant --- ic10emu/src/grammar.rs | 2 +- ic10emu/src/interpreter.rs | 36 +- ic10emu/src/lib.rs | 568 ++++++++++++++++++++------- ic10emu_wasm/src/lib.rs | 133 ++++--- ic10emu_wasm/src/types.rs | 44 ++- ic10emu_wasm/src/types.ts | 73 ++-- www/src/ts/virtual_machine/device.ts | 55 +-- www/src/ts/virtual_machine/index.ts | 16 +- 8 files changed, 660 insertions(+), 267 deletions(-) diff --git a/ic10emu/src/grammar.rs b/ic10emu/src/grammar.rs index 6b7a015..9131e3d 100644 --- a/ic10emu/src/grammar.rs +++ b/ic10emu/src/grammar.rs @@ -424,7 +424,7 @@ impl Operand { ic: &interpreter::IC, inst: InstructionOp, index: u32, - ) -> Result<(Option, Option), interpreter::ICError> { + ) -> Result<(Option, Option), interpreter::ICError> { match self.translate_alias(ic) { Operand::DeviceSpec(DeviceSpec { device, connection }) => match device { Device::Db => Ok((Some(ic.device), connection)), diff --git a/ic10emu/src/interpreter.rs b/ic10emu/src/interpreter.rs index c942ae4..d19681e 100644 --- a/ic10emu/src/interpreter.rs +++ b/ic10emu/src/interpreter.rs @@ -12,7 +12,9 @@ use itertools::Itertools; use time::format_description; -use crate::grammar::{self, ParseError}; +use crate::{ + grammar::{self, ParseError}, +}; use thiserror::Error; @@ -169,15 +171,17 @@ impl Display for ICState { #[derive(Debug)] pub struct IC { - pub device: u16, - pub id: u16, + pub device: u32, + pub id: u32, pub registers: [f64; 18], + /// Instruction Pointer pub ip: u32, + /// Instruction Count since last yield pub ic: u16, pub stack: [f64; 512], pub aliases: HashMap, pub defines: HashMap, - pub pins: [Option; 6], + pub pins: [Option; 6], pub code: String, pub program: Program, pub state: ICState, @@ -295,7 +299,7 @@ impl Program { } impl IC { - pub fn new(id: u16, device: u16) -> Self { + pub fn new(id: u32, device: u32) -> Self { IC { device, id, @@ -2111,7 +2115,7 @@ impl IC { if device_id >= u16::MAX as f64 || device_id < u16::MIN as f64 { return Err(DeviceIndexOutOfRange(device_id)); } - let device = vm.get_device_same_network(this.device, device_id as u16); + let device = vm.get_device_same_network(this.device, device_id as u32); match device { Some(device) => match device.borrow().ic.as_ref() { Some(ic_id) => { @@ -2119,7 +2123,7 @@ impl IC { let val = val.as_value(this, inst, 3)?; let mut ic = vm.ics.get(ic_id).unwrap().borrow_mut(); ic.poke(addr, val)?; - vm.set_modified(device_id as u16); + vm.set_modified(device_id as u32); Ok(()) } None => Err(DeviceHasNoIC), @@ -2153,7 +2157,7 @@ impl IC { match device { Some(device) => { let val = val.as_value(this, inst, 1)?; - device.borrow_mut().set_field(lt, val)?; + device.borrow_mut().set_field(lt, val, vm)?; vm.set_modified(device_id); Ok(()) } @@ -2168,13 +2172,13 @@ impl IC { if device_id >= u16::MAX as f64 || device_id < u16::MIN as f64 { return Err(DeviceIndexOutOfRange(device_id)); } - let device = vm.get_device_same_network(this.device, device_id as u16); + let device = vm.get_device_same_network(this.device, device_id as u32); match device { Some(device) => { let lt = lt.as_logic_type(this, inst, 2)?; let val = val.as_value(this, inst, 3)?; - device.borrow_mut().set_field(lt, val)?; - vm.set_modified(device_id as u16); + device.borrow_mut().set_field(lt, val, vm)?; + vm.set_modified(device_id as u32); Ok(()) } None => Err(UnknownDeviceID(device_id)), @@ -2193,7 +2197,7 @@ impl IC { let index = index.as_value(this, inst, 2)?; let slt = slt.as_slot_logic_type(this, inst, 3)?; let val = val.as_value(this, inst, 4)?; - device.borrow_mut().set_slot_field(index, slt, val)?; + device.borrow_mut().set_slot_field(index, slt, val, vm)?; vm.set_modified(device_id); Ok(()) } @@ -2261,7 +2265,7 @@ impl IC { let device = vm.get_device_same_network(this.device, device_id); match device { Some(device) => { - let val = device.borrow().get_field(lt)?; + let val = device.borrow().get_field(lt, vm)?; this.set_register(indirection, target, val)?; Ok(()) } @@ -2280,11 +2284,11 @@ impl IC { if device_id >= u16::MAX as f64 || device_id < u16::MIN as f64 { return Err(DeviceIndexOutOfRange(device_id)); } - let device = vm.get_device_same_network(this.device, device_id as u16); + let device = vm.get_device_same_network(this.device, device_id as u32); match device { Some(device) => { let lt = lt.as_logic_type(this, inst, 3)?; - let val = device.borrow().get_field(lt)?; + let val = device.borrow().get_field(lt, vm)?; this.set_register(indirection, target, val)?; Ok(()) } @@ -2307,7 +2311,7 @@ impl IC { Some(device) => { let index = index.as_value(this, inst, 3)?; let slt = slt.as_slot_logic_type(this, inst, 4)?; - let val = device.borrow().get_slot_field(index, slt)?; + let val = device.borrow().get_slot_field(index, slt, vm)?; this.set_register(indirection, target, val)?; Ok(()) } diff --git a/ic10emu/src/lib.rs b/ic10emu/src/lib.rs index c208fed..afbfd8e 100644 --- a/ic10emu/src/lib.rs +++ b/ic10emu/src/lib.rs @@ -19,19 +19,19 @@ use thiserror::Error; #[derive(Error, Debug, Serialize, Deserialize)] pub enum VMError { #[error("device with id '{0}' does not exist")] - UnknownId(u16), + UnknownId(u32), #[error("ic with id '{0}' does not exist")] - UnknownIcId(u16), + UnknownIcId(u32), #[error("device with id '{0}' does not have a ic slot")] - NoIC(u16), + NoIC(u32), #[error("ic encountered an error: {0}")] ICError(#[from] ICError), #[error("ic encountered an error: {0}")] LineError(#[from] LineError), #[error("invalid network id {0}")] - InvalidNetwork(u16), + InvalidNetwork(u32), #[error("device {0} not visible to device {1} (not on the same networks)")] - DeviceNotVisible(u16, u16), + DeviceNotVisible(u32, u32), } #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] @@ -47,18 +47,188 @@ pub struct LogicField { pub value: f64, } +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct SlotOccupant { + pub id: u32, + pub prefab_hash: i32, + pub quantity: u32, + pub max_quantity: u32, + pub damage: f64, + fields: HashMap, +} + +impl SlotOccupant { + pub fn new(id: u32, prefab_hash: i32) -> Self { + SlotOccupant { + id, + prefab_hash, + quantity: 1, + max_quantity: 1, // FIXME: need a good way to set a better default + damage: 0.0, + fields: HashMap::new(), + } + } + + /// chainable constructor + pub fn with_quantity(mut self, quantity: u32) -> Self { + self.quantity = quantity; + self + } + + /// chainable constructor + pub fn with_max_quantity(mut self, max_quantity: u32) -> Self { + self.max_quantity = max_quantity; + self + } + + /// chainable constructor + pub fn with_damage(mut self, damage: f64) -> Self { + self.damage = damage; + self + } + + /// chainable constructor + pub fn with_fields(mut self, fields: HashMap) -> Self { + self.fields.extend(fields); + self + } + + /// chainable constructor + pub fn get_fields(&self) -> HashMap { + self.fields.clone() + } + + /// slot field operations don't fail + pub fn set_field(&mut self, field: SlotLogicType, val: f64) { + if let Some(logic) = self.fields.get_mut(&field) { + match logic.field_type { + FieldType::ReadWrite | FieldType::Write => logic.value = val, + _ => {} + } + } + } +} + #[derive(Debug, Default, Serialize, Deserialize)] pub struct Slot { pub typ: SlotType, - // FIXME: this actualy needs to be an "Occupant" field - // where the Occupant is an items with a - // quantity, PrefabName/Hash, fields, etc - pub fields: HashMap, + pub occupant: Option, +} + +impl Slot { + pub fn new(typ: SlotType) -> Self { + Slot { + typ, + occupant: None, + } + } + pub fn with_occupant(typ: SlotType, occupant: SlotOccupant) -> Self { + Slot { + typ, + occupant: Some(occupant), + } + } + + pub fn get_fields(&self) -> HashMap { + let mut copy = self + .occupant + .as_ref() + .map(|occupant| occupant.get_fields()) + .unwrap_or_default(); + copy.insert( + SlotLogicType::ReferenceId, + LogicField { + field_type: FieldType::Read, + value: self + .occupant + .as_ref() + .map(|occupant| occupant.id as f64) + .unwrap_or(0.0), + }, + ); + copy.insert( + SlotLogicType::Occupied, + LogicField { + field_type: FieldType::Read, + value: if self.occupant.is_some() { 1.0 } else { 0.0 }, + }, + ); + copy.insert( + SlotLogicType::OccupantHash, + LogicField { + field_type: FieldType::Read, + value: self + .occupant + .as_ref() + .map(|occupant| occupant.prefab_hash as f64) + .unwrap_or(0.0), + }, + ); + copy.insert( + SlotLogicType::PrefabHash, + LogicField { + field_type: FieldType::Read, + value: self + .occupant + .as_ref() + .map(|occupant| occupant.prefab_hash as f64) + .unwrap_or(0.0), + }, + ); + copy.insert( + SlotLogicType::Quantity, + LogicField { + field_type: FieldType::Read, + value: self + .occupant + .as_ref() + .map(|occupant| occupant.quantity as f64) + .unwrap_or(0.0), + }, + ); + copy.insert( + SlotLogicType::MaxQuantity, + LogicField { + field_type: FieldType::Read, + value: self + .occupant + .as_ref() + .map(|occupant| occupant.max_quantity as f64) + .unwrap_or(0.0), + }, + ); + copy.insert( + SlotLogicType::Class, + LogicField { + field_type: FieldType::Read, + value: self.typ as u32 as f64, + }, + ); + copy + } + + /// the game returns 0.0 for all slotlogic types if they are not set + pub fn get_field(&self, field: SlotLogicType) -> f64 { + let fields = self.get_fields(); + fields + .get(&field) + .map(|field| match field.field_type { + FieldType::Read | FieldType::ReadWrite => field.value, + _ => 0.0, + }) + .unwrap_or(0.0) + } + + pub fn set_field(&mut self, field: SlotLogicType, val: f64) { + if let Some(occupant) = self.occupant.as_mut() { + occupant.set_field(field, val); + } + } } #[derive(Debug, Default, Clone, Copy, Serialize, Deserialize)] pub enum Connection { - CableNetwork(Option), + CableNetwork(Option), #[default] Other, } @@ -120,41 +290,49 @@ impl Connection { } #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] + pub enum SlotType { - AccessCard, - Appliance, + Helmet = 1, + Suit = 2, Back, - Battery, - Blocked, - Bottle, - Cartridge, - Circuitboard, - CreditCard, - DataDisk, - DrillHead, - Egg, - Entity, - Flare, + GasFilter = 4, GasCanister, - GasFilter, - Helmet, - Ingot, - LiquidBottle, - LiquidCanister, - Magazine, - Ore, + MotherBoard, + Circuitboard, + DataDisk = 8, Organ, + Ore, Plant, - ProgramableChip, - ScanningHead, - SensorProcessingUnit, - SoundCartridge, - Suit, + Uniform, + Entity, + Battery, + Egg, + Belt = 16, Tool, + Appliance, + Ingot, Torpedo, + Cartridge, + AccessCard, + Magazine, + Circuit = 24, + Bottle, + ProgramableChip, + Glasses, + CreditCard, + DirtCanister, + SensorProcessingUnit, + LiquidCanister, + LiquidBottle = 32, + Wreckage, + SoundCartridge, + DrillHead, + ScanningHead, + Flare, + Blocked, #[default] #[serde(other)] - None, + None = 0, } #[derive(Debug, Default, Clone, Serialize, Deserialize)] @@ -165,49 +343,69 @@ pub struct SlotTemplate { #[derive(Debug, Default)] pub struct Device { - pub id: u16, + pub id: u32, pub name: Option, pub name_hash: Option, - pub fields: HashMap, pub prefab_name: Option, pub prefab_hash: Option, pub slots: Vec, pub reagents: HashMap>, - pub ic: Option, + pub ic: Option, pub connections: Vec, + fields: HashMap, } #[derive(Debug, Serialize, Deserialize)] pub struct Network { - pub devices: HashSet, + pub devices: HashSet, pub channels: [f64; 8], } #[derive(Debug, Default)] struct IdSequenceGenerator { - next: u16, + next: u32, } impl IdSequenceGenerator { - pub fn next(&mut self) -> u16 { + pub fn next(&mut self) -> u32 { let val = self.next; self.next += 1; val } + + pub fn next_free<'a, I>(&mut self, in_use: I) -> u32 + where + I: IntoIterator, + { + let sorted_in_use = in_use.into_iter().sorted(); + let mut last = None; + for val in sorted_in_use.into_iter() { + if val > &self.next && last.is_some_and(|last| (val - last) > 1) { + break; + } + last = Some(val); + } + if let Some(last) = last { + self.next = u32::max(*last, self.next) + 1; + self.next + } else { + self.next() + } + } } #[derive(Debug)] pub struct VM { - pub ics: HashMap>>, - pub devices: HashMap>>, - pub networks: HashMap>>, - pub default_network: u16, + pub ics: HashMap>>, + pub devices: HashMap>>, + pub networks: HashMap>>, + pub default_network: u32, id_gen: IdSequenceGenerator, network_id_gen: IdSequenceGenerator, random: Rc>, /// list of device id's touched on the last operation - operation_modified: RefCell>, + operation_modified: RefCell>, } impl Default for Network { @@ -226,15 +424,15 @@ pub enum NetworkError { } impl Network { - pub fn contains(&self, ids: &[u16]) -> bool { + pub fn contains(&self, ids: &[u32]) -> bool { ids.iter().all(|id| self.devices.contains(id)) } - pub fn add(&mut self, id: u16) -> bool { + pub fn add(&mut self, id: u32) -> bool { self.devices.insert(id) } - pub fn remove(&mut self, id: u16) -> bool { + pub fn remove(&mut self, id: u32) -> bool { self.devices.remove(&id) } @@ -258,7 +456,7 @@ impl Network { } impl Device { - pub fn new(id: u16) -> Self { + pub fn new(id: u32) -> Self { let mut device = Device { id, name: None, @@ -281,7 +479,7 @@ impl Device { device } - pub fn with_ic(id: u16, ic: u16) -> Self { + pub fn with_ic(id: u32, ic: u32) -> Self { let mut device = Device::new(id); device.ic = Some(ic); device.connections = vec![Connection::CableNetwork(None), Connection::Other]; @@ -345,30 +543,31 @@ impl Device { }, ), ]); - device.slots.push(Slot { - typ: SlotType::ProgramableChip, - fields: HashMap::from([ - ( - SlotLogicType::PrefabHash, - LogicField { - field_type: FieldType::Read, - value: -744098481.0, - }, - ), - ( - SlotLogicType::LineNumber, - LogicField { - field_type: FieldType::Read, - value: 0.0, - }, - ), - ]), - }); + device.slots.push(Slot::with_occupant( + SlotType::ProgramableChip, + // -744098481 = ItemIntegratedCircuit10 + SlotOccupant::new(ic, -744098481), + )); device } - pub fn get_network_id(&self, connection: usize) -> Result { + pub fn get_fields(&self, vm: &VM) -> HashMap { + 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(); + copy.insert( + LogicType::LineNumber, + LogicField { + field_type: FieldType::ReadWrite, + value: ic.ip as f64, + }, + ); + } + copy + } + + pub fn get_network_id(&self, connection: usize) -> Result { if connection >= self.connections.len() { Err(ICError::ConnectionIndexOutOfRange( connection, @@ -385,9 +584,21 @@ impl Device { } } - // FIXME: this needs some logic to link some special fields like "LineNumber" to the chip - pub fn get_field(&self, typ: grammar::LogicType) -> Result { - if let Some(field) = self.fields.get(&typ) { + pub fn get_field(&self, typ: LogicType, vm: &VM) -> Result { + if typ == LogicType::LineNumber && self.ic.is_some() { + if let Ok(ic) = vm + .ics + .get(&self.ic.unwrap()) + .ok_or_else(|| ICError::UnknownDeviceID(self.ic.unwrap() as f64))? + .try_borrow() + { + Ok(ic.ip as f64) + } else { + // FIXME: the game succeeds in getting the correct line number + // when reading it's own IC + Ok(0.0) + } + } else if let Some(field) = self.fields.get(&typ) { if field.field_type == FieldType::Read || field.field_type == FieldType::ReadWrite { Ok(field.value) } else { @@ -398,9 +609,20 @@ impl Device { } } - // FIXME: this needs some logic to link some special fields like "LineNumber" to the chip - pub fn set_field(&mut self, typ: grammar::LogicType, val: f64) -> Result<(), ICError> { - if let Some(field) = self.fields.get_mut(&typ) { + pub fn set_field(&mut self, typ: LogicType, val: f64, vm: &VM) -> Result<(), ICError> { + if typ == LogicType::LineNumber && self.ic.is_some() { + // try borrow to set ip, we shoudl only fail if the ic is in us aka is is *our* ic + // in game trying to set your own ic's LineNumber appears to be a Nop so this is fine. + if let Ok(mut ic) = vm + .ics + .get(&self.ic.unwrap()) + .ok_or_else(|| ICError::UnknownDeviceID(self.ic.unwrap() as f64))? + .try_borrow_mut() + { + ic.ip = val as u32; + } + Ok(()) + } else if let Some(field) = self.fields.get_mut(&typ) { if field.field_type == FieldType::Write || field.field_type == FieldType::ReadWrite { field.value = val; Ok(()) @@ -412,50 +634,113 @@ impl Device { } } - // FIXME: this needs to work with slot Occupants, see `Slot` decl - pub fn get_slot_field(&self, index: f64, typ: grammar::SlotLogicType) -> Result { - if let Some(field) = self + pub fn get_slot_field(&self, index: f64, typ: SlotLogicType, vm: &VM) -> Result { + let slot = self .slots .get(index as usize) - .ok_or(ICError::SlotIndexOutOfRange(index))? - .fields - .get(&typ) + .ok_or(ICError::SlotIndexOutOfRange(index))?; + if slot.typ == SlotType::ProgramableChip + && slot.occupant.is_some() + && self.ic.is_some() + && typ == SlotLogicType::LineNumber { - if field.field_type == FieldType::Read || field.field_type == FieldType::ReadWrite { - Ok(field.value) + // try borrow to get ip, we should only fail if the ic is in us aka is is *our* ic + if let Ok(ic) = vm + .ics + .get(&self.ic.unwrap()) + .ok_or_else(|| ICError::UnknownDeviceID(self.ic.unwrap() as f64))? + .try_borrow() + { + Ok(ic.ip as f64) } else { - Err(ICError::WriteOnlyField(typ.to_string())) + // FIXME: the game succeeds in getting the correct line number + // when reading it's own IC + Ok(0.0) } } else { - Err(ICError::DeviceHasNoField(typ.to_string())) + Ok(slot.get_field(typ)) } } - // FIXME: this needs to work with slot Occupants, see `Slot` decl + pub fn get_slot_fields( + &self, + index: f64, + vm: &VM, + ) -> Result, ICError> { + let slot = self + .slots + .get(index as usize) + .ok_or(ICError::SlotIndexOutOfRange(index))?; + let mut fields = slot.get_fields(); + if slot.typ == SlotType::ProgramableChip && slot.occupant.is_some() && self.ic.is_some() { + // try borrow to get ip, we should only fail if the ic is in us aka is is *our* ic + if let Ok(ic) = vm + .ics + .get(&self.ic.unwrap()) + .ok_or_else(|| ICError::UnknownDeviceID(self.ic.unwrap() as f64))? + .try_borrow() + { + fields.insert( + SlotLogicType::LineNumber, + LogicField { + field_type: FieldType::ReadWrite, + value: ic.ip as f64, + }, + ); + } else { + // FIXME: the game succeeds in getting the correct line number + // when reading it's own IC + fields.insert( + SlotLogicType::LineNumber, + LogicField { + field_type: FieldType::ReadWrite, + value: 0.0, + }, + ); + } + } + Ok(fields) + } + pub fn set_slot_field( &mut self, index: f64, typ: grammar::SlotLogicType, val: f64, + vm: &VM, ) -> Result<(), ICError> { - if let Some(field) = self + let slot = self .slots .get_mut(index as usize) - .ok_or(ICError::SlotIndexOutOfRange(index))? - .fields - .get_mut(&typ) + .ok_or(ICError::SlotIndexOutOfRange(index))?; + if slot.typ == SlotType::ProgramableChip + && slot.occupant.is_some() + && self.ic.is_some() + && typ == SlotLogicType::LineNumber { - if field.field_type == FieldType::Write || field.field_type == FieldType::ReadWrite { - field.value = val; - Ok(()) - } else { - Err(ICError::ReadOnlyField(typ.to_string())) + // try borrow to set ip, we shoudl only fail if the ic is in us aka is is *our* ic + // in game trying to set your own ic's LineNumber appears to be a Nop so this is fine. + if let Ok(mut ic) = vm + .ics + .get(&self.ic.unwrap()) + .ok_or_else(|| ICError::UnknownDeviceID(self.ic.unwrap() as f64))? + .try_borrow_mut() + { + ic.ip = val as u32; } + Ok(()) } else { - Err(ICError::DeviceHasNoField(typ.to_string())) + slot.set_field(typ, val); + Ok(()) } } + pub fn get_slot(&self, index: f64) -> Result<&Slot, ICError> { + self.slots + .get(index as usize) + .ok_or(ICError::SlotIndexOutOfRange(index)) + } + pub fn get_reagent(&self, rm: &ReagentMode, reagent: f64) -> f64 { if let Some(mode) = self.reagents.get(rm) { if let Some(val) = mode.get(&(reagent as i32)) { @@ -505,13 +790,18 @@ impl VM { } fn new_ic(&mut self) -> (Device, interpreter::IC) { - let id = self.id_gen.next(); - let ic = interpreter::IC::new(id, id); - let device = Device::with_ic(id, id); + let id = self + .id_gen + .next_free(self.devices.keys().chain(self.ics.keys())); + let ic_id = self + .id_gen + .next_free(self.devices.keys().chain(self.ics.keys())); + let ic = interpreter::IC::new(ic_id, id); + let device = Device::with_ic(id, ic_id); (device, ic) } - pub fn add_device(&mut self, network: Option) -> Result { + pub fn add_device(&mut self, network: Option) -> Result { if let Some(n) = &network { if !self.networks.contains_key(n) { return Err(VMError::InvalidNetwork(*n)); @@ -556,7 +846,7 @@ impl VM { Ok(id) } - pub fn add_ic(&mut self, network: Option) -> Result { + pub fn add_ic(&mut self, network: Option) -> Result { if let Some(n) = &network { if !self.networks.contains_key(n) { return Err(VMError::InvalidNetwork(*n)); @@ -602,7 +892,7 @@ impl VM { Ok(id) } - pub fn add_network(&mut self) -> u16 { + pub fn add_network(&mut self) -> u32 { let next_id = self.network_id_gen.next(); self.networks .insert(next_id, Rc::new(RefCell::new(Network::default()))); @@ -613,18 +903,18 @@ impl VM { self.networks.get(&self.default_network).cloned().unwrap() } - pub fn get_network(&self, id: u16) -> Option>> { + pub fn get_network(&self, id: u32) -> Option>> { self.networks.get(&id).cloned() } - pub fn remove_ic(&mut self, id: u16) { + pub fn remove_ic(&mut self, id: u32) { if self.ics.remove(&id).is_some() { self.devices.remove(&id); } } /// Set program code if it's valid - pub fn set_code(&self, id: u16, code: &str) -> Result { + pub fn set_code(&self, id: u32, code: &str) -> Result { let device = self .devices .get(&id) @@ -644,7 +934,7 @@ impl VM { } /// Set program code and translate invalid lines to Nop, collecting errors - pub fn set_code_invalid(&self, id: u16, code: &str) -> Result { + pub fn set_code_invalid(&self, id: u32, code: &str) -> Result { let device = self .devices .get(&id) @@ -664,11 +954,11 @@ impl VM { } /// returns a list of device ids modified in the last operations - pub fn last_operation_modified(&self) -> Vec { + pub fn last_operation_modified(&self) -> Vec { self.operation_modified.borrow().clone() } - pub fn step_ic(&self, id: u16, advance_ip_on_err: bool) -> Result { + pub fn step_ic(&self, id: u32, advance_ip_on_err: bool) -> Result { self.operation_modified.borrow_mut().clear(); let device = self.devices.get(&id).ok_or(VMError::UnknownId(id))?.clone(); let ic_id = *device.borrow().ic.as_ref().ok_or(VMError::NoIC(id))?; @@ -684,7 +974,7 @@ impl VM { } /// returns true if executed 128 lines, false if returned early. - pub fn run_ic(&self, id: u16, ignore_errors: bool) -> Result { + pub fn run_ic(&self, id: u32, ignore_errors: bool) -> Result { self.operation_modified.borrow_mut().clear(); let device = self.devices.get(&id).ok_or(VMError::UnknownId(id))?.clone(); let ic_id = *device.borrow().ic.as_ref().ok_or(VMError::NoIC(id))?; @@ -711,11 +1001,11 @@ impl VM { Ok(true) } - pub fn set_modified(&self, id: u16) { + pub fn set_modified(&self, id: u32) { self.operation_modified.borrow_mut().push(id); } - pub fn reset_ic(&self, id: u16) -> Result { + pub fn reset_ic(&self, id: u32) -> Result { let device = self.devices.get(&id).ok_or(VMError::UnknownId(id))?.clone(); let ic_id = *device.borrow().ic.as_ref().ok_or(VMError::NoIC(id))?; let ic = self @@ -728,13 +1018,13 @@ impl VM { Ok(true) } - pub fn get_device(&self, id: u16) -> Option>> { + pub fn get_device(&self, id: u32) -> Option>> { self.devices.get(&id).cloned() } pub fn batch_device( &self, - source: u16, + source: u32, prefab_hash: f64, name: Option, ) -> impl Iterator>> { @@ -752,7 +1042,7 @@ impl VM { .map(|(_, d)| d) } - pub fn get_device_same_network(&self, source: u16, other: u16) -> Option>> { + pub fn get_device_same_network(&self, source: u32, other: u32) -> Option>> { if self.devices_on_same_network(&[source, other]) { self.get_device(other) } else { @@ -763,7 +1053,7 @@ impl VM { pub fn get_network_channel(&self, id: usize, channel: usize) -> Result { let network = self .networks - .get(&(id as u16)) + .get(&(id as u32)) .ok_or(ICError::BadNetworkId(id as u32))?; Ok(network.borrow().channels[channel]) } @@ -771,13 +1061,13 @@ impl VM { pub fn set_network_channel(&self, id: usize, channel: usize, val: f64) -> Result<(), ICError> { let network = self .networks - .get(&(id as u16)) + .get(&(id as u32)) .ok_or(ICError::BadNetworkId(id as u32))?; network.borrow_mut().channels[channel] = val; Ok(()) } - pub fn devices_on_same_network(&self, ids: &[u16]) -> bool { + pub fn devices_on_same_network(&self, ids: &[u32]) -> bool { for net in self.networks.values() { if net.borrow().contains(ids) { return true; @@ -787,7 +1077,7 @@ impl VM { } /// return a vecter with the device ids the source id can see via it's connected netowrks - pub fn visible_devices(&self, source: u16) -> Vec { + pub fn visible_devices(&self, source: u32) -> Vec { self.networks .values() .filter_map(|net| { @@ -807,7 +1097,7 @@ impl VM { .concat() } - pub fn set_pin(&self, id: u16, pin: usize, val: Option) -> Result { + pub fn set_pin(&self, id: u32, pin: usize, val: Option) -> Result { let Some(device) = self.devices.get(&id) else { return Err(VMError::UnknownId(id)); }; @@ -832,8 +1122,8 @@ impl VM { pub fn add_device_to_network( &self, - id: u16, - network_id: u16, + id: u32, + network_id: u32, connection: usize, ) -> Result { if let Some(network) = self.networks.get(&network_id) { @@ -858,7 +1148,7 @@ impl VM { } } - pub fn remove_device_from_network(&self, id: u16, network_id: u16) -> Result { + pub fn remove_device_from_network(&self, id: u32, network_id: u32) -> Result { if let Some(network) = self.networks.get(&network_id) { let Some(device) = self.devices.get(&id) else { return Err(VMError::UnknownId(id)); @@ -881,7 +1171,7 @@ impl VM { pub fn set_batch_device_field( &self, - source: u16, + source: u32, prefab: f64, typ: LogicType, val: f64, @@ -889,14 +1179,14 @@ impl VM { self.batch_device(source, prefab, None) .map(|device| { self.set_modified(device.borrow().id); - device.borrow_mut().set_field(typ, val) + device.borrow_mut().set_field(typ, val, self) }) .try_collect() } pub fn set_batch_device_slot_field( &self, - source: u16, + source: u32, prefab: f64, index: f64, typ: SlotLogicType, @@ -905,14 +1195,14 @@ impl VM { self.batch_device(source, prefab, None) .map(|device| { self.set_modified(device.borrow().id); - device.borrow_mut().set_slot_field(index, typ, val) + device.borrow_mut().set_slot_field(index, typ, val, self) }) .try_collect() } pub fn set_batch_name_device_field( &self, - source: u16, + source: u32, prefab: f64, name: f64, typ: LogicType, @@ -921,21 +1211,21 @@ impl VM { self.batch_device(source, prefab, Some(name)) .map(|device| { self.set_modified(device.borrow().id); - device.borrow_mut().set_field(typ, val) + device.borrow_mut().set_field(typ, val, self) }) .try_collect() } pub fn get_batch_device_field( &self, - source: u16, + source: u32, prefab: f64, typ: LogicType, mode: BatchMode, ) -> Result { let samples = self .batch_device(source, prefab, None) - .map(|device| device.borrow_mut().get_field(typ)) + .map(|device| device.borrow_mut().get_field(typ, self)) .filter_ok(|val| !val.is_nan()) .collect::, ICError>>()?; Ok(mode.apply(&samples)) @@ -943,7 +1233,7 @@ impl VM { pub fn get_batch_name_device_field( &self, - source: u16, + source: u32, prefab: f64, name: f64, typ: LogicType, @@ -951,7 +1241,7 @@ impl VM { ) -> Result { let samples = self .batch_device(source, prefab, Some(name)) - .map(|device| device.borrow_mut().get_field(typ)) + .map(|device| device.borrow_mut().get_field(typ, self)) .filter_ok(|val| !val.is_nan()) .collect::, ICError>>()?; Ok(mode.apply(&samples)) @@ -959,7 +1249,7 @@ impl VM { pub fn get_batch_name_device_slot_field( &self, - source: u16, + source: u32, prefab: f64, name: f64, index: f64, @@ -968,7 +1258,7 @@ impl VM { ) -> Result { let samples = self .batch_device(source, prefab, Some(name)) - .map(|device| device.borrow().get_slot_field(index, typ)) + .map(|device| device.borrow().get_slot_field(index, typ, self)) .filter_ok(|val| !val.is_nan()) .collect::, ICError>>()?; Ok(mode.apply(&samples)) @@ -976,7 +1266,7 @@ impl VM { pub fn get_batch_device_slot_field( &self, - source: u16, + source: u32, prefab: f64, index: f64, typ: SlotLogicType, @@ -984,7 +1274,7 @@ impl VM { ) -> Result { let samples = self .batch_device(source, prefab, None) - .map(|device| device.borrow().get_slot_field(index, typ)) + .map(|device| device.borrow().get_slot_field(index, typ, self)) .filter_ok(|val| !val.is_nan()) .collect::, ICError>>()?; Ok(mode.apply(&samples)) diff --git a/ic10emu_wasm/src/lib.rs b/ic10emu_wasm/src/lib.rs index 490135a..9e0ecb2 100644 --- a/ic10emu_wasm/src/lib.rs +++ b/ic10emu_wasm/src/lib.rs @@ -43,10 +43,15 @@ impl DeviceRef { } #[wasm_bindgen(getter)] - pub fn id(&self) -> u16 { + pub fn id(&self) -> u32 { self.device.borrow().id } + #[wasm_bindgen(getter)] + pub fn ic(&self) -> Option { + self.device.borrow().ic + } + #[wasm_bindgen(getter)] pub fn name(&self) -> Option { self.device.borrow().name.clone() @@ -69,12 +74,20 @@ impl DeviceRef { #[wasm_bindgen(getter, skip_typescript)] pub fn fields(&self) -> JsValue { - serde_wasm_bindgen::to_value(&self.device.borrow().fields).unwrap() + serde_wasm_bindgen::to_value(&self.device.borrow().get_fields(&self.vm.borrow())).unwrap() } #[wasm_bindgen(getter, skip_typescript)] - pub fn slots(&self) -> JsValue { - serde_wasm_bindgen::to_value(&self.device.borrow().slots).unwrap() + 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, skip_typescript)] @@ -267,38 +280,37 @@ impl DeviceRef { } #[wasm_bindgen(js_name = "setField")] - pub fn set_field(&self, field: &str, value: f64) -> Result { + pub fn set_field(&self, field: &str, value: f64) -> Result<(), JsError> { let logic_typ = LogicType::from_str(field)?; let mut device_ref = self.device.borrow_mut(); - let logic_field = device_ref - .fields - .get_mut(&logic_typ) - .ok_or_else(|| BindingError::InvalidEnumVariant(field.to_owned()))?; - let last = logic_field.value; - logic_field.value = value; - Ok(last) + device_ref.set_field(logic_typ, value, &self.vm.borrow())?; + Ok(()) } #[wasm_bindgen(js_name = "setSlotField")] - pub fn set_slot_field(&self, slot: usize, field: &str, value: f64) -> Result { + pub fn set_slot_field(&self, slot: f64, field: &str, value: f64) -> Result<(), JsError> { let logic_typ = SlotLogicType::from_str(field)?; let mut device_ref = self.device.borrow_mut(); - let slots_len = device_ref.slots.len(); - let slot = device_ref - .slots - .get_mut(slot) - .ok_or(BindingError::OutOfBounds(slot, slots_len))?; - let logic_field = slot - .fields - .get_mut(&logic_typ) - .ok_or_else(|| BindingError::InvalidEnumVariant(field.to_owned()))?; - let last = logic_field.value; - logic_field.value = value; - Ok(last) + device_ref.set_slot_field(slot, logic_typ, value, &self.vm.borrow())?; + Ok(()) + } + + #[wasm_bindgen(js_name = "getSlotField")] + pub fn get_slot_field(&self, slot: f64, field: &str) -> Result { + let logic_typ = SlotLogicType::from_str(field)?; + let device_ref = self.device.borrow_mut(); + Ok(device_ref.get_slot_field(slot, logic_typ, &self.vm.borrow())?) + } + + #[wasm_bindgen(js_name = "getSlotFields", skip_typescript)] + pub fn get_slot_fields(&self, slot: f64) -> Result { + let device_ref = self.device.borrow_mut(); + let fields = device_ref.get_slot_fields(slot, &self.vm.borrow())?; + Ok(serde_wasm_bindgen::to_value(&fields).unwrap()) } #[wasm_bindgen(js_name = "setConnection")] - pub fn set_connection(&self, conn: usize, net: Option) -> Result<(), JsError> { + pub fn set_connection(&self, conn: usize, net: Option) -> Result<(), JsError> { let mut device_ref = self.device.borrow_mut(); let conn_len = device_ref.connections.len(); let conn_ref = device_ref @@ -315,19 +327,29 @@ impl DeviceRef { } #[wasm_bindgen(js_name = "addDeviceToNetwork")] - pub fn add_device_to_network(&self, network_id: u16, connection: usize) -> Result { + pub fn add_device_to_network( + &self, + network_id: u32, + connection: usize, + ) -> Result { let id = self.device.borrow().id; - Ok(self.vm.borrow().add_device_to_network(id, network_id, connection)?) + Ok(self + .vm + .borrow() + .add_device_to_network(id, network_id, connection)?) } #[wasm_bindgen(js_name = "removeDeviceFromNetwork")] - pub fn remove_device_from_network(&self, network_id: u16) -> Result { + pub fn remove_device_from_network(&self, network_id: u32) -> Result { let id = self.device.borrow().id; - Ok(self.vm.borrow().remove_device_from_network(id, network_id)?) + Ok(self + .vm + .borrow() + .remove_device_from_network(id, network_id)?) } #[wasm_bindgen(js_name = "setPin")] - pub fn set_pin(&self, pin: usize, val: Option) -> Result { + pub fn set_pin(&self, pin: usize, val: Option) -> Result { let id = self.device.borrow().id; Ok(self.vm.borrow().set_pin(id, pin, val)?) } @@ -349,89 +371,98 @@ impl VM { } #[wasm_bindgen(js_name = "addDevice")] - pub fn add_device(&self, network: Option) -> Result { + pub fn add_device(&self, network: Option) -> Result { Ok(self.vm.borrow_mut().add_device(network)?) } #[wasm_bindgen(js_name = "getDevice")] - pub fn get_device(&self, id: u16) -> Option { + pub fn get_device(&self, id: u32) -> Option { let device = self.vm.borrow().get_device(id); device.map(|d| DeviceRef::from_device(d.clone(), self.vm.clone())) } #[wasm_bindgen(js_name = "setCode")] /// Set program code if it's valid - pub fn set_code(&self, id: u16, code: &str) -> Result { + pub fn set_code(&self, id: u32, code: &str) -> Result { Ok(self.vm.borrow().set_code(id, code)?) } #[wasm_bindgen(js_name = "setCodeInvalid")] /// Set program code and translate invalid lines to Nop, collecting errors - pub fn set_code_invalid(&self, id: u16, code: &str) -> Result { + pub fn set_code_invalid(&self, id: u32, code: &str) -> Result { Ok(self.vm.borrow().set_code_invalid(id, code)?) } #[wasm_bindgen(js_name = "stepIC")] - pub fn step_ic(&self, id: u16, advance_ip_on_err: bool) -> Result { + pub fn step_ic(&self, id: u32, advance_ip_on_err: bool) -> Result { Ok(self.vm.borrow().step_ic(id, advance_ip_on_err)?) } #[wasm_bindgen(js_name = "runIC")] - pub fn run_ic(&self, id: u16, ignore_errors: bool) -> Result { + pub fn run_ic(&self, id: u32, ignore_errors: bool) -> Result { Ok(self.vm.borrow().run_ic(id, ignore_errors)?) } #[wasm_bindgen(js_name = "resetIC")] - pub fn reset_ic(&self, id: u16) -> Result { + pub fn reset_ic(&self, id: u32) -> Result { Ok(self.vm.borrow().reset_ic(id)?) } #[wasm_bindgen(getter, js_name = "defaultNetwork")] - pub fn default_network(&self) -> u16 { + pub fn default_network(&self) -> u32 { self.vm.borrow().default_network } #[wasm_bindgen(getter)] - pub fn devices(&self) -> Vec { + pub fn devices(&self) -> Vec { self.vm.borrow().devices.keys().copied().collect_vec() } #[wasm_bindgen(getter)] - pub fn networks(&self) -> Vec { + pub fn networks(&self) -> Vec { self.vm.borrow().networks.keys().copied().collect_vec() } #[wasm_bindgen(getter)] - pub fn ics(&self) -> Vec { + pub fn ics(&self) -> Vec { self.vm.borrow().ics.keys().copied().collect_vec() } #[wasm_bindgen(getter, js_name = "lastOperationModified")] - pub fn last_operation_modified(&self) -> Vec { + pub fn last_operation_modified(&self) -> Vec { self.vm.borrow().last_operation_modified() } #[wasm_bindgen(js_name = "visibleDevices")] - pub fn visible_devices(&self, source: u16) -> Vec { + pub fn visible_devices(&self, source: u32) -> Vec { self.vm.borrow().visible_devices(source) } #[wasm_bindgen(js_name = "addDeviceToNetwork")] - pub fn add_device_to_network(&self, id: u16, network_id: u16, connection: usize) -> Result { - Ok(self.vm.borrow().add_device_to_network(id, network_id, connection)?) + pub fn add_device_to_network( + &self, + id: u32, + network_id: u32, + connection: usize, + ) -> Result { + Ok(self + .vm + .borrow() + .add_device_to_network(id, network_id, connection)?) } #[wasm_bindgen(js_name = "removeDeviceFromNetwork")] - pub fn remove_device_from_network(&self, id: u16, network_id: u16) -> Result { - Ok(self.vm.borrow().remove_device_from_network(id, network_id)?) + pub fn remove_device_from_network(&self, id: u32, network_id: u32) -> Result { + Ok(self + .vm + .borrow() + .remove_device_from_network(id, network_id)?) } #[wasm_bindgen(js_name = "setPin")] - pub fn set_pin(&self, id: u16, pin: usize, val: Option) -> Result { + pub fn set_pin(&self, id: u32, pin: usize, val: Option) -> Result { Ok(self.vm.borrow().set_pin(id, pin, val)?) } - - } impl Default for VM { diff --git a/ic10emu_wasm/src/types.rs b/ic10emu_wasm/src/types.rs index c659553..57f2ef1 100644 --- a/ic10emu_wasm/src/types.rs +++ b/ic10emu_wasm/src/types.rs @@ -1,3 +1,4 @@ +use std::collections::HashMap; use serde::{Deserialize, Serialize}; use serde_with::serde_as; @@ -14,4 +15,45 @@ 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]); -include!(concat!(env!("OUT_DIR"), "/ts_types.rs")); \ No newline at end of file +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct SlotOccupant { + pub id: u32, + pub prefab_hash: i32, + pub quantity: u32, + pub max_quantity: u32, + pub damage: f64, + pub fields: HashMap, +} + +impl From<&ic10emu::SlotOccupant> for SlotOccupant { + fn from(value: &ic10emu::SlotOccupant) -> Self { + SlotOccupant { + id: value.id, + prefab_hash: value.prefab_hash, + quantity: value.quantity, + max_quantity: value.max_quantity, + damage: value.damage, + fields: value.get_fields(), + } + } +} + +#[derive(Debug, Default, Serialize, Deserialize)] +pub struct Slot { + pub typ: ic10emu::SlotType, + pub occupant: Option, + pub fields: HashMap, +} + +impl From<&ic10emu::Slot> for Slot { + fn from(value: &ic10emu::Slot) -> Self { + Slot { + typ: value.typ, + occupant: value.occupant.as_ref().map(|occupant| occupant.into()), + fields: value.get_fields(), + } + } +} + +include!(concat!(env!("OUT_DIR"), "/ts_types.rs")); + diff --git a/ic10emu_wasm/src/types.ts b/ic10emu_wasm/src/types.ts index 12f6b2f..707c25a 100644 --- a/ic10emu_wasm/src/types.ts +++ b/ic10emu_wasm/src/types.ts @@ -40,9 +40,18 @@ export type SlotType = | "Torpedo" | "None"; +export interface SlotOccupant { + readonly id: number; + readonly prefab_hash: number; + readonly quantity: number; + readonly max_quantity: number; + readonly damage: number; + readonly fields: Fields; +} export interface Slot { - typ: SlotType; - fields: Fields; + readonly typ: SlotType; + readonly occupant: SlotOccupant | undefined; + readonly fields: Fields; } export type Reagents = Map>; @@ -50,29 +59,37 @@ export type Reagents = Map>; export type Connection = { CableNetwork: number } | "Other"; export type RegisterSpec = { - RegisterSpec: { indirection: number; target: number }; + readonly RegisterSpec: { + readonly indirection: number; + readonly target: number; + }; }; export type DeviceSpec = { - DeviceSpec: { - device: + readonly DeviceSpec: { + readonly device: | "Db" - | { Numbered: number } - | { Indirect: { indirection: number; target: number } }; + | { readonly Numbered: number } + | { + readonly Indirect: { + readonly indirection: number; + readonly target: number; + }; + }; }; - connection: number | undefined; + readonly connection: number | undefined; }; -export type LogicType = { LogicType: string }; -export type SlotLogicType = { SlotLogicType: string }; -export type BatchMode = { BatchMode: string }; -export type ReagentMode = { ReagentMode: string }; -export type Identifier = { Identifier: { name: string } }; +export type LogicType = { readonly LogicType: string }; +export type SlotLogicType = { readonly SlotLogicType: string }; +export type BatchMode = { readonly BatchMode: string }; +export type ReagentMode = { readonly ReagentMode: string }; +export type Identifier = { readonly Identifier: { name: string } }; -export type NumberFloat = { Float: number }; -export type NumberBinary = { Binary: number }; -export type NumberHexadecimal = { Hexadecimal: number }; -export type NumberConstant = { Constant: number }; -export type NumberString = { String: string }; -export type NumberEnum = { Enum: number }; +export type NumberFloat = { readonly Float: number }; +export type NumberBinary = { readonly Binary: number }; +export type NumberHexadecimal = { readonly Hexadecimal: number }; +export type NumberConstant = { readonly Constant: number }; +export type NumberString = { readonly String: string }; +export type NumberEnum = { readonly Enum: number }; export type NumberOperand = { Number: @@ -102,18 +119,23 @@ export type Defines = Map; export type Pins = (number | undefined)[]; export interface Instruction { - instruction: string; - operands: Operand[]; + readonly instruction: string; + readonly operands: Operand[]; } export type ICError = { - ParseError: { line: number; start: number; end: number; msg: string }; + readonly ParseError: { + readonly line: number; + readonly start: number; + readonly end: number; + readonly msg: string; + }; }; export interface Program { - instructions: Instruction[]; - errors: ICError[]; - labels: Map; + readonly instructions: Instruction[]; + readonly errors: ICError[]; + readonly labels: Map; } export interface DeviceRef { @@ -125,4 +147,5 @@ export interface DeviceRef { readonly defines?: Defines | undefined; readonly pins?: Pins; readonly program?: Program; + getSlotFields(slot: number): Fields; } diff --git a/www/src/ts/virtual_machine/device.ts b/www/src/ts/virtual_machine/device.ts index 165e67c..b2fc437 100644 --- a/www/src/ts/virtual_machine/device.ts +++ b/www/src/ts/virtual_machine/device.ts @@ -90,7 +90,7 @@ export class VMDeviceCard extends VMDeviceMixin(BaseElement) { --sl-color-primary-600: var(--sl-color-purple-600); } sl-tab::part(base) { - padding: var(--sl-spacing-small) var(--sl-spacing-medium) + padding: var(--sl-spacing-small) var(--sl-spacing-medium); } sl-tab-group::part(base) { height: 16rem; @@ -169,7 +169,7 @@ export class VMDeviceCard extends VMDeviceMixin(BaseElement) { } renderFields(): HTMLTemplateResult { - const fields = Array.from(this.fields); + const fields = Array.from(this.fields.entries()); const inputIdBase = `vmDeviceCard${this.deviceID}Field`; return html` ${fields.map(([name, field], _index, _fields) => { @@ -192,7 +192,8 @@ export class VMDeviceCard extends VMDeviceMixin(BaseElement) { } renderSlot(slot: Slot, slotIndex: number): HTMLTemplateResult { - const fields = Array.from(slot.fields); + const _fields = this.device.getSlotFields(slotIndex); + const fields = Array.from(_fields.entries()); const inputIdBase = `vmDeviceCard${this.deviceID}Slot${slotIndex}Field`; return html` @@ -615,7 +616,7 @@ export class VmDeviceTemplate extends BaseElement { ...defaultCss, css` .template-card { - --padding: var(--sl-spacing-small) + --padding: var(--sl-spacing-small); } .image { width: 3rem; @@ -630,7 +631,7 @@ export class VmDeviceTemplate extends BaseElement { overflow-y: auto; } sl-tab::part(base) { - padding: var(--sl-spacing-small) var(--sl-spacing-medium) + padding: var(--sl-spacing-small) var(--sl-spacing-medium); } sl-tab-group::part(base) { height: 14rem; @@ -676,10 +677,7 @@ export class VmDeviceTemplate extends BaseElement { const fields = device.logic ? Object.entries(device.logic) : []; return html` ${fields.map(([name, field_type], _index, _fields) => { - return html` + return html` ${name} ${field_type} `; @@ -688,7 +686,6 @@ export class VmDeviceTemplate extends BaseElement { } renderSlot(slot: Slot, slotIndex: number): HTMLTemplateResult { - const fields = Array.from(slot.fields); return html` `; } @@ -722,28 +719,32 @@ export class VmDeviceTemplate extends BaseElement { @onerr=${this.onImageErr} /> -
+
${device.name} ${device.hash}
- Add + Add
-
- - - Fields - Slots - Reagents - Networks - Pins - - ${this.renderFields()} - ${this.renderSlots()} - ${this.renderReagents()} - ${this.renderNetworks()} - ${this.renderPins()} - +
+ + Fields + Slots + Reagents + Networks + Pins + ${this.renderFields()} + ${this.renderSlots()} + ${this.renderReagents()} + ${this.renderNetworks()} + ${this.renderPins()} +
`; diff --git a/www/src/ts/virtual_machine/index.ts b/www/src/ts/virtual_machine/index.ts index e4ecf06..bdfc220 100644 --- a/www/src/ts/virtual_machine/index.ts +++ b/www/src/ts/virtual_machine/index.ts @@ -85,20 +85,22 @@ class VirtualMachine extends EventTarget { } } - const ics = this.ic10vm.ics; - for (const id of ics) { - if (!this._ics.has(id)) { - this._ics.set(id, this._devices.get(id)!); - update_flag = true; + for (const [id, device] of this._devices) { + if (typeof device.ic !== "undefined") { + if (!this._ics.has(id)) { + this._ics.set(id, device); + update_flag = true; + } } } + for (const id of this._ics.keys()) { - if (!ics.includes(id)) { - this._ics.get(id)!.free(); + if (!this._devices.has(id)) { this._ics.delete(id); update_flag = true; } } + if (update_flag) { this.dispatchEvent( new CustomEvent("vm-devices-update", { detail: device_ids }),