diff --git a/ic10emu/src/errors.rs b/ic10emu/src/errors.rs index 085680e..21d061f 100644 --- a/ic10emu/src/errors.rs +++ b/ic10emu/src/errors.rs @@ -207,7 +207,7 @@ pub enum ICError { WriteOnlyField(String), #[error("device has no field '{0}'")] DeviceHasNoField(String), - #[error("device has not ic")] + #[error("device has no ic")] DeviceHasNoIC, #[error("unknown device '{0}'")] UnknownDeviceId(f64), diff --git a/ic10emu/src/interpreter.rs b/ic10emu/src/interpreter.rs index 3050af9..4db630a 100644 --- a/ic10emu/src/interpreter.rs +++ b/ic10emu/src/interpreter.rs @@ -185,9 +185,10 @@ impl Program { } } - pub fn get_line(&self, line: usize) -> Result<&Instruction, ICError> { + pub fn get_line(&self, line: usize) -> Result { self.instructions .get(line) + .cloned() .ok_or(ICError::InstructionPointerOutOfRange(line)) } } @@ -236,8 +237,8 @@ mod tests { println!("VM built"); let frozen_ic = FrozenObject { - obj_info: ObjectInfo::with_prefab(Prefab::Hash( - StationpediaPrefab::ItemIntegratedCircuit10 as i32, + obj_info: ObjectInfo::with_prefab(Prefab::Name( + StationpediaPrefab::ItemIntegratedCircuit10.to_string(), )), database_template: true, template: None, @@ -246,8 +247,8 @@ mod tests { println!("Adding IC"); let ic = vm.add_object_from_frozen(frozen_ic)?; let frozen_circuit_holder = FrozenObject { - obj_info: ObjectInfo::with_prefab(Prefab::Hash( - StationpediaPrefab::StructureCircuitHousing as i32, + obj_info: ObjectInfo::with_prefab(Prefab::Name( + StationpediaPrefab::StructureCircuitHousing.to_string(), )), database_template: true, template: None, diff --git a/ic10emu/src/vm.rs b/ic10emu/src/vm.rs index 9666617..d8ca053 100644 --- a/ic10emu/src/vm.rs +++ b/ic10emu/src/vm.rs @@ -6,7 +6,7 @@ use crate::{ interpreter::ICState, network::{CableConnectionType, CableNetwork, Connection, FrozenCableNetwork}, vm::object::{ - templates::{FrozenObject, Prefab}, + templates::{FrozenObject, FrozenObjectFull, Prefab}, traits::ParentSlotInfo, ObjectID, SlotOccupantInfo, VMObject, }, @@ -46,7 +46,7 @@ pub struct VM { /// list of object id's touched on the last operation operation_modified: RefCell>, - template_database: Option>, + template_database: RefCell>>, } #[derive(Debug, Default)] @@ -92,7 +92,7 @@ impl VM { network_id_space: RefCell::new(network_id_space), random: Rc::new(RefCell::new(crate::rand_mscorlib::Random::new())), operation_modified: RefCell::new(Vec::new()), - template_database: stationeers_data::build_prefab_database(), + template_database: RefCell::new(stationeers_data::build_prefab_database()), }); let default_network = VMObject::new(CableNetwork::new(default_network_key, vm.clone())); @@ -105,30 +105,41 @@ impl VM { /// get a random f64 value using a mscorlib rand PRNG /// (Stationeers, being written in .net, using mscorlib's rand) - pub fn random_f64(&self) -> f64 { + pub fn random_f64(self: &Rc) -> f64 { self.random.borrow_mut().next_f64() } /// Take ownership of an iterable the produces (prefab hash, ObjectTemplate) pairs and build a prefab /// database pub fn import_template_database( - &mut self, + self: &Rc, db: impl IntoIterator, ) { - self.template_database.replace(db.into_iter().collect()); + self.template_database + .borrow_mut() + .replace(db.into_iter().collect()); } /// Get a Object Template by either prefab name or hash - pub fn get_template(&self, prefab: Prefab) -> Option { + pub fn get_template(self: &Rc, prefab: Prefab) -> Option { let hash = match prefab { Prefab::Hash(hash) => hash, Prefab::Name(name) => const_crc32::crc32(name.as_bytes()) as i32, }; self.template_database + .borrow() .as_ref() .and_then(|db| db.get(&hash).cloned()) } + pub fn get_template_database(self: &Rc) -> BTreeMap { + self.template_database + .borrow() + .as_ref() + .cloned() + .unwrap_or_default() + } + /// Add an number of object to the VM state using Frozen Object strusts. /// See also `add_objects_frozen` /// Returns the built objects' IDs @@ -334,7 +345,7 @@ impl VM { .ok_or(VMError::UnknownId(id))?; { let mut obj_ref = obj.borrow_mut(); - if let Some(programmable) = obj_ref.as_mut_programmable() { + if let Some(programmable) = obj_ref.as_mut_source_code() { programmable.set_source_code(code)?; return Ok(true); } @@ -393,6 +404,72 @@ impl VM { Err(VMError::NoIC(id)) } + /// Get program code + /// Object Id is the programmable Id or the circuit holder's id + pub fn get_code(self: &Rc, id: ObjectID) -> Result { + let obj = self + .objects + .borrow() + .get(&id) + .cloned() + .ok_or(VMError::UnknownId(id))?; + { + let obj_ref = obj.borrow(); + if let Some(programmable) = obj_ref.as_source_code() { + return Ok(programmable.get_source_code()); + } + } + let ic_obj = { + let obj_ref = obj.borrow(); + if let Some(circuit_holder) = obj_ref.as_circuit_holder() { + circuit_holder.get_ic() + } else { + return Err(VMError::NotCircuitHolderOrProgrammable(id)); + } + }; + if let Some(ic_obj) = ic_obj { + let ic_obj_ref = ic_obj.borrow(); + if let Some(programmable) = ic_obj_ref.as_source_code() { + return Ok(programmable.get_source_code()); + } + return Err(VMError::NotProgrammable(*ic_obj_ref.get_id())); + } + Err(VMError::NoIC(id)) + } + + /// Get a vector of any errors compiling the source code + /// Object Id is the programmable Id or the circuit holder's id + pub fn get_compile_errors(self: &Rc, id: ObjectID) -> Result, VMError> { + let obj = self + .objects + .borrow() + .get(&id) + .cloned() + .ok_or(VMError::UnknownId(id))?; + { + let obj_ref = obj.borrow(); + if let Some(programmable) = obj_ref.as_source_code() { + return Ok(programmable.get_compile_errors()); + } + } + let ic_obj = { + let obj_ref = obj.borrow(); + if let Some(circuit_holder) = obj_ref.as_circuit_holder() { + circuit_holder.get_ic() + } else { + return Err(VMError::NotCircuitHolderOrProgrammable(id)); + } + }; + if let Some(ic_obj) = ic_obj { + let ic_obj_ref = ic_obj.borrow(); + if let Some(programmable) = ic_obj_ref.as_source_code() { + return Ok(programmable.get_compile_errors()); + } + return Err(VMError::NotProgrammable(*ic_obj_ref.get_id())); + } + Err(VMError::NoIC(id)) + } + /// Set register of integrated circuit /// Object Id is the circuit Id or the circuit holder's id pub fn set_register( @@ -1216,13 +1293,27 @@ impl VM { Ok(last) } - pub fn freeze_object(self: &Rc, id: ObjectID) -> Result { + pub fn freeze_object(self: &Rc, id: ObjectID) -> Result { let Some(obj) = self.objects.borrow().get(&id).cloned() else { return Err(VMError::UnknownId(id)); }; Ok(FrozenObject::freeze_object(&obj, self)?) } + pub fn freeze_objects( + self: &Rc, + ids: impl IntoIterator, + ) -> Result, VMError> { + ids.into_iter() + .map(|id| { + let Some(obj) = self.objects.borrow().get(&id).cloned() else { + return Err(VMError::UnknownId(id)); + }; + Ok(FrozenObject::freeze_object(&obj, self)?) + }) + .collect() + } + pub fn save_vm_state(self: &Rc) -> Result { Ok(FrozenVM { objects: self diff --git a/ic10emu/src/vm/object/stationpedia.rs b/ic10emu/src/vm/object/stationpedia.rs index fc1ac74..6eb2f23 100644 --- a/ic10emu/src/vm/object/stationpedia.rs +++ b/ic10emu/src/vm/object/stationpedia.rs @@ -1,9 +1,6 @@ use std::rc::Rc; -use stationeers_data::{ - enums::prefabs::StationpediaPrefab, - templates::{ObjectTemplate}, -}; +use stationeers_data::{enums::prefabs::StationpediaPrefab, templates::ObjectTemplate}; use crate::{ errors::TemplateError, @@ -29,10 +26,12 @@ pub fn object_from_frozen( vm: &Rc, ) -> Result, TemplateError> { #[allow(clippy::cast_possible_wrap)] - let hash = match &obj.prefab { - Some(Prefab::Hash(hash)) => *hash, - Some(Prefab::Name(name)) => const_crc32::crc32(name.as_bytes()) as i32, - None => return Ok(None), + let Some(hash) = obj + .prefab + .as_ref() + .map(|name| const_crc32::crc32(name.as_bytes()) as i32) + else { + return Ok(None); }; let prefab = StationpediaPrefab::from_repr(hash); @@ -84,7 +83,7 @@ pub fn object_from_frozen( .map(TryInto::try_into) .transpose() .map_err(|vec: Vec| TemplateError::MemorySize(vec.len(), 512))? - .unwrap_or( [0.0f64; 512]), + .unwrap_or([0.0f64; 512]), parent_slot: None, registers: obj .circuit @@ -92,7 +91,7 @@ pub fn object_from_frozen( .map(|circuit| circuit.registers.clone().try_into()) .transpose() .map_err(|vec: Vec| TemplateError::MemorySize(vec.len(), 18))? - .unwrap_or( [0.0f64; 18]), + .unwrap_or([0.0f64; 18]), ip: obj .circuit .as_ref() diff --git a/ic10emu/src/vm/object/stationpedia/structs/integrated_circuit.rs b/ic10emu/src/vm/object/stationpedia/structs/integrated_circuit.rs index 90aab2c..c4f0947 100644 --- a/ic10emu/src/vm/object/stationpedia/structs/integrated_circuit.rs +++ b/ic10emu/src/vm/object/stationpedia/structs/integrated_circuit.rs @@ -231,9 +231,12 @@ impl SourceCode for ItemIntegratedCircuit10 { fn get_source_code(&self) -> String { self.code.clone() } - fn get_line(&self, line: usize) -> Result<&Instruction, ICError> { + fn get_line(&self, line: usize) -> Result { self.program.get_line(line) } + fn get_compile_errors(&self) -> Vec { + self.program.errors.clone() + } } impl IntegratedCircuit for ItemIntegratedCircuit10 { diff --git a/ic10emu/src/vm/object/templates.rs b/ic10emu/src/vm/object/templates.rs index 9075312..16cbdd3 100644 --- a/ic10emu/src/vm/object/templates.rs +++ b/ic10emu/src/vm/object/templates.rs @@ -1,4 +1,4 @@ -use std::{collections::BTreeMap, rc::Rc, str::FromStr}; +use std::{collections::BTreeMap, fmt::Display, rc::Rc, str::FromStr}; use crate::{ errors::TemplateError, @@ -69,7 +69,7 @@ impl std::fmt::Display for Prefab { pub struct ObjectInfo { pub name: Option, pub id: Option, - pub prefab: Option, + pub prefab: Option, pub slots: Option>, pub damage: Option, pub device_pins: Option>, @@ -80,6 +80,7 @@ pub struct ObjectInfo { pub entity: Option, pub source_code: Option, pub circuit: Option, + pub socketed_ic: Option, } impl From<&VMObject> for ObjectInfo { @@ -88,7 +89,7 @@ impl From<&VMObject> for ObjectInfo { ObjectInfo { name: Some(obj_ref.get_name().value.clone()), id: Some(*obj_ref.get_id()), - prefab: Some(Prefab::Hash(obj_ref.get_prefab().hash)), + prefab: Some(obj_ref.get_prefab().value.clone()), slots: None, damage: None, device_pins: None, @@ -99,6 +100,7 @@ impl From<&VMObject> for ObjectInfo { entity: None, source_code: None, circuit: None, + socketed_ic: None, } } } @@ -106,10 +108,16 @@ impl From<&VMObject> for ObjectInfo { impl ObjectInfo { /// Build empty info with a prefab name pub fn with_prefab(prefab: Prefab) -> Self { + let prefab_name = match prefab { + Prefab::Name(name) => name, + Prefab::Hash(hash) => StationpediaPrefab::from_repr(hash) + .map(|sp| sp.to_string()) + .unwrap_or_default(), + }; ObjectInfo { name: None, id: None, - prefab: Some(prefab), + prefab: Some(prefab_name), slots: None, damage: None, device_pins: None, @@ -120,6 +128,7 @@ impl ObjectInfo { entity: None, source_code: None, circuit: None, + socketed_ic: None, } } @@ -151,6 +160,9 @@ impl ObjectInfo { if let Some(circuit) = interfaces.integrated_circuit { self.update_from_circuit(circuit); } + if let Some(circuit_holder) = interfaces.circuit_holder { + self.update_from_circuit_holder(circuit_holder); + } self } @@ -298,6 +310,24 @@ impl ObjectInfo { }); self } + + /// store socketed Ic Id + pub fn update_from_circuit_holder( + &mut self, + circuit_holder: CircuitHolderRef<'_>, + ) -> &mut Self { + if let Some(ic) = circuit_holder.get_ic() { + self.socketed_ic.replace(ic.get_id()); + } + self + } +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +#[cfg_attr(feature = "tsify", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))] +pub struct FrozenObjectFull { + pub obj_info: ObjectInfo, + pub template: ObjectTemplate, } #[derive(Debug, Clone, Deserialize, Serialize)] @@ -332,8 +362,9 @@ impl FrozenObject { .prefab .as_ref() .map(|prefab| { - vm.get_template(prefab.clone()) - .ok_or(TemplateError::NoTemplateForPrefab(prefab.clone())) + vm.get_template(Prefab::Name(prefab.clone())).ok_or( + TemplateError::NoTemplateForPrefab(Prefab::Name(prefab.clone())), + ) }) .transpose()? .ok_or(TemplateError::MissingPrefab) @@ -827,28 +858,24 @@ impl FrozenObject { } } - pub fn freeze_object(obj: &VMObject, vm: &Rc) -> Result { + pub fn freeze_object(obj: &VMObject, vm: &Rc) -> Result { let obj_ref = obj.borrow(); let interfaces = ObjectInterfaces::from_object(&*obj_ref); let mut obj_info: ObjectInfo = obj.into(); obj_info.update_from_interfaces(&interfaces); // if the template is known, omit it. else build it from interfaces - let mut database_template = false; let template = vm .get_template(Prefab::Hash(obj_ref.get_prefab().hash)) .map_or_else( - || Some(try_template_from_interfaces(&interfaces, obj)), + || try_template_from_interfaces(&interfaces, obj), |template| { - database_template = true; - Some(Ok(template)) + Ok(template) }, - ) - .transpose()?; + )?; - Ok(FrozenObject { + Ok(FrozenObjectFull { obj_info, template, - database_template, }) } diff --git a/ic10emu/src/vm/object/traits.rs b/ic10emu/src/vm/object/traits.rs index ccd20cb..c1172e0 100644 --- a/ic10emu/src/vm/object/traits.rs +++ b/ic10emu/src/vm/object/traits.rs @@ -143,7 +143,9 @@ tag_object_traits! { fn get_source_code(&self) -> String; /// Return the compiled instruction and it's operands at the indexed line in the source /// code. - fn get_line(&self, line: usize) -> Result<&Instruction, ICError>; + fn get_line(&self, line: usize) -> Result; + /// Return a vector of any errors encountered while compiling the source with `set_source_code_with_invalid` + fn get_compile_errors(&self) -> Vec; } pub trait CircuitHolder: Logicable + Storage { @@ -497,3 +499,51 @@ impl Debug for dyn Object { ) } } + +impl SourceCode for T { + fn get_line(&self, line: usize) -> Result { + let ic = self.get_ic().ok_or(ICError::DeviceHasNoIC)?; + let result = ic + .borrow() + .as_source_code() + .ok_or(ICError::DeviceHasNoIC)? + .get_line(line); + result.clone() + } + fn set_source_code(&mut self, code: &str) -> Result<(), ICError> { + self.get_ic() + .and_then(|obj| { + obj.borrow_mut() + .as_mut_source_code() + .map(|source| source.set_source_code(code)) + }) + .transpose()?; + Ok(()) + } + fn set_source_code_with_invalid(&mut self, code: &str) { + self.get_ic().and_then(|obj| { + obj.borrow_mut() + .as_mut_source_code() + .map(|source| source.set_source_code_with_invalid(code)) + }); + } + fn get_source_code(&self) -> String { + self.get_ic() + .and_then(|obj| { + obj.borrow() + .as_source_code() + .map(|source| source.get_source_code()) + }) + .unwrap_or_default() + } + fn get_compile_errors(&self) -> Vec { + self.get_ic() + .and_then(|obj| { + obj.borrow() + .as_source_code() + .map(|source| source.get_compile_errors()) + }) + .unwrap_or_default() + } +} + diff --git a/ic10emu_wasm/src/lib.rs b/ic10emu_wasm/src/lib.rs index 8afdf0f..ebd7c35 100644 --- a/ic10emu_wasm/src/lib.rs +++ b/ic10emu_wasm/src/lib.rs @@ -3,18 +3,21 @@ mod utils; // mod types; use ic10emu::{ - errors::VMError, + errors::{ICError, VMError}, vm::{ - object::{templates::FrozenObject, ObjectID, VMObject}, + object::{templates::{FrozenObject, FrozenObjectFull}, ObjectID}, FrozenVM, VM, }, }; use itertools::Itertools; use serde_derive::{Deserialize, Serialize}; -use stationeers_data::enums::script::{LogicSlotType, LogicType}; +use stationeers_data::{ + enums::script::{LogicSlotType, LogicType}, + templates::ObjectTemplate, +}; -use std::rc::Rc; +use std::{collections::BTreeMap, rc::Rc}; use wasm_bindgen::prelude::*; @@ -39,6 +42,28 @@ pub struct VMRef { vm: Rc, } +use tsify::Tsify; + +#[derive(Clone, Debug, Serialize, Deserialize, Tsify)] +#[tsify(into_wasm_abi, from_wasm_abi)] +pub struct TemplateDatabase(BTreeMap); + +impl IntoIterator for TemplateDatabase { + type Item = (i32, ObjectTemplate); + type IntoIter = std::collections::btree_map::IntoIter; + fn into_iter(self) -> Self::IntoIter { + self.0.into_iter() + } +} + +#[derive(Clone, Debug, Serialize, Deserialize, Tsify)] +#[tsify(into_wasm_abi, from_wasm_abi)] +pub struct FrozenObjects(Vec); + +#[derive(Clone, Debug, Serialize, Deserialize, Tsify)] +#[tsify(into_wasm_abi, from_wasm_abi)] +pub struct CompileErrors(Vec); + #[wasm_bindgen] impl VMRef { #[wasm_bindgen(constructor)] @@ -46,8 +71,18 @@ impl VMRef { VMRef { vm: VM::new() } } - #[wasm_bindgen(js_name = "addDeviceFromTemplate")] - pub fn add_device_from_template(&self, frozen: FrozenObject) -> Result { + #[wasm_bindgen(js_name = "importTemplateDatabase")] + pub fn import_template_database(&self, db: TemplateDatabase) { + self.vm.import_template_database(db); + } + + #[wasm_bindgen(js_name = "getTemplateDatabase")] + pub fn get_template_database(&self) -> TemplateDatabase { + TemplateDatabase(self.vm.get_template_database()) + } + + #[wasm_bindgen(js_name = "addObjectFromFrozen")] + pub fn add_object_from_frozen(&self, frozen: FrozenObject) -> Result { web_sys::console::log_2( &"(wasm) adding device".into(), &serde_wasm_bindgen::to_value(&frozen).unwrap(), @@ -55,14 +90,19 @@ impl VMRef { Ok(self.vm.add_object_from_frozen(frozen)?) } - #[wasm_bindgen(js_name = "getDevice")] - pub fn get_object(&self, id: ObjectID) -> Option { - self.vm.get_object(id) + // #[wasm_bindgen(js_name = "getDevice")] + // pub fn get_object(&self, id: ObjectID) -> Option { + // self.vm.get_object(id) + // } + + #[wasm_bindgen(js_name = "freezeObject")] + pub fn freeze_object(&self, id: ObjectID) -> Result { + Ok(self.vm.freeze_object(id)?) } - #[wasm_bindgen(js_name = "freezeDevice")] - pub fn freeze_object(&self, id: ObjectID) -> Result { - Ok(self.vm.freeze_object(id)?) + #[wasm_bindgen(js_name = "freezeObjects")] + pub fn freeze_objects(&self, ids: Vec) -> Result { + Ok(FrozenObjects(self.vm.freeze_objects(ids)?)) } #[wasm_bindgen(js_name = "setCode")] @@ -77,6 +117,18 @@ impl VMRef { Ok(self.vm.set_code_invalid(id, code)?) } + #[wasm_bindgen(js_name = "getCode")] + /// Set program code if it's valid + pub fn get_code(&self, id: ObjectID) -> Result { + Ok(self.vm.get_code(id)?) + } + + #[wasm_bindgen(js_name = "getCompileErrors")] + /// Set program code if it's valid + pub fn get_compiler_errors(&self, id: ObjectID) -> Result { + Ok(CompileErrors(self.vm.get_compile_errors(id)?)) + } + #[wasm_bindgen(js_name = "stepProgrammable")] pub fn step_programmable(&self, id: ObjectID, advance_ip_on_err: bool) -> Result<(), JsError> { Ok(self.vm.step_programmable(id, advance_ip_on_err)?) diff --git a/ic10emu_wasm/src/types.rs b/ic10emu_wasm/src/types.rs deleted file mode 100644 index 954f698..0000000 --- a/ic10emu_wasm/src/types.rs +++ /dev/null @@ -1,76 +0,0 @@ -#![allow(non_snake_case)] - -use std::collections::BTreeMap; - -use itertools::Itertools; -use serde_derive::{Deserialize, Serialize}; -use serde_with::serde_as; -use tsify::Tsify; -use wasm_bindgen::prelude::*; - -#[serde_as] -#[derive(Tsify, Serialize, Deserialize)] -#[tsify(into_wasm_abi, from_wasm_abi)] -pub struct Stack(#[serde_as(as = "[_; 512]")] pub [f64; 512]); - -#[serde_as] -#[derive(Tsify, Serialize, Deserialize)] -#[tsify(into_wasm_abi, from_wasm_abi)] -pub struct Registers(#[serde_as(as = "[_; 18]")] pub [f64; 18]); - -#[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: BTreeMap, -} - -impl From<&ic10emu::device::SlotOccupant> for SlotOccupant { - fn from(value: &ic10emu::device::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(), - } - } -} - -#[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: BTreeMap, -} - -impl From<&ic10emu::device::Slot> for Slot { - fn from(value: &ic10emu::device::Slot) -> Self { - Slot { - typ: value.typ, - occupant: value.occupant.as_ref().map(|occupant| occupant.into()), - fields: value.get_fields(), - } - } -} - -#[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")); diff --git a/ic10emu_wasm/src/types.ts b/ic10emu_wasm/src/types.ts deleted file mode 100644 index dade7d4..0000000 --- a/ic10emu_wasm/src/types.ts +++ /dev/null @@ -1,169 +0,0 @@ -export type MemoryAccess = "Read" | "Write" | "ReadWrite"; - -export interface LogicField { - field_type: MemoryAccess; - value: number; -} -export type LogicFields = Map; -export type SlotLogicFields = Map; - -export type Reagents = Map>; - -export interface ConnectionCableNetwork { - CableNetwork: { - net: number | undefined; - typ: string; - }; -} - -export type Connection = ConnectionCableNetwork | "Other"; - -export type RegisterSpec = { - readonly RegisterSpec: { - readonly indirection: number; - readonly target: number; - }; -}; -export type DeviceSpec = { - readonly DeviceSpec: { - readonly device: - | "Db" - | { readonly Numbered: number } - | { - readonly Indirect: { - readonly indirection: number; - readonly target: number; - }; - }; - }; - readonly connection: number | undefined; -}; -export type OperandLogicType = { readonly LogicType: string }; -export type OperandLogicSlotType = { readonly LogicSlotType: string }; -export type OperandBatchMode = { readonly BatchMode: string }; -export type OperandReagentMode = { readonly ReagentMode: string }; -export type Identifier = { readonly Identifier: { name: string } }; - -export type NumberFloat = { readonly Float: number }; -export type NumberBinary = { readonly Binary: BigInt }; -export type NumberHexadecimal = { readonly Hexadecimal: BigInt }; -export type NumberConstant = { readonly Constant: number }; -export type NumberString = { readonly String: string }; -export type NumberEnum = { readonly Enum: number }; - -export type NumberOperand = { - Number: - | NumberFloat - | NumberBinary - | NumberHexadecimal - | NumberConstant - | NumberString - | NumberEnum; -}; -export type Operand = - | RegisterSpec - | DeviceSpec - | NumberOperand - | OperandLogicType - | OperandLogicSlotType - | OperandBatchMode - | OperandReagentMode - | Identifier; - -export type Alias = RegisterSpec | DeviceSpec; - -export type Aliases = Map; - -export type Defines = Map; - -export type Pins = (number | undefined)[]; - -export interface Instruction { - readonly instruction: string; - readonly operands: Operand[]; -} - -export type ICError = { - readonly ParseError: { - readonly line: number; - readonly start: number; - readonly end: number; - readonly msg: string; - }; -}; - -export interface Program { - readonly instructions: Instruction[]; - readonly errors: ICError[]; - readonly labels: Map; -} - -export interface DeviceRef { - readonly fields: LogicFields; - readonly slots: Slot[]; - readonly reagents: Reagents; - readonly connections: Connection[]; - readonly aliases?: Aliases | undefined; - readonly defines?: Defines | undefined; - readonly pins?: Pins; - readonly program?: Program; - getSlotFields(slot: number): SlotLogicFields; - setField(field: LogicType, value: number, force: boolean): void; - setSlotField(slot: number, field: LogicSlotType, value: number, force: boolean): void; - getSlotField(slot: number, field: LogicSlotType): number; -} - -export interface SlotOccupantTemplate { - id?: number; - fields: { [key in LogicSlotType]?: LogicField }; -} - -export interface SlotTemplate { - typ: SlotType; - occupant?: SlotOccupantTemplate; -} - -export interface DeviceTemplate { - id?: number; - name?: string; - prefab_name?: string; - slots: SlotTemplate[]; - // reagents: { [key: string]: float} - connections: Connection[]; - fields: { [key in LogicType]?: LogicField }; -} - -export interface FrozenIC { - device: number; - id: number; - registers: number[]; - ip: number; - ic: number; - stack: number[]; - aliases: Aliases; - defines: Defines; - pins: Pins; - state: string; - code: string; -} - -export interface FrozenNetwork { - id: number; - devices: number[]; - power_only: number[]; - channels: number[]; -} - -export interface FrozenVM { - ics: FrozenIC[]; - devices: DeviceTemplate[]; - networks: FrozenNetwork[]; - default_network: number; -} - -export interface VMRef { - addDeviceFromTemplate(template: DeviceTemplate): number; - setSlotOccupant(id: number, index: number, template: SlotOccupantTemplate); - saveVMState(): FrozenVM; - restoreVMState(state: FrozenVM): void; -} diff --git a/www/package.json b/www/package.json index 6f0c503..29274c8 100644 --- a/www/package.json +++ b/www/package.json @@ -55,6 +55,7 @@ "bootstrap": "^5.3.3", "bson": "^6.6.0", "buffer": "^6.0.3", + "comlink": "^4.4.1", "crypto-browserify": "^3.12.0", "ic10emu_wasm": "file:../ic10emu_wasm/pkg", "ic10lsp_wasm": "file:../ic10lsp_wasm/pkg", diff --git a/www/pnpm-lock.yaml b/www/pnpm-lock.yaml index f9db843..0f5bb9f 100644 --- a/www/pnpm-lock.yaml +++ b/www/pnpm-lock.yaml @@ -32,6 +32,9 @@ dependencies: buffer: specifier: ^6.0.3 version: 6.0.3 + comlink: + specifier: ^4.4.1 + version: 4.4.1 crypto-browserify: specifier: ^3.12.0 version: 3.12.0 @@ -1634,6 +1637,10 @@ packages: resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} dev: true + /comlink@4.4.1: + resolution: {integrity: sha512-+1dlx0aY5Jo1vHy/tSsIGpSkN4tS9rZSW8FIhG0JH/crs9wwweswIo/POr451r7bZww3hFbPAKnTpimzL/mm4Q==} + dev: false + /commander@2.20.3: resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} dev: true diff --git a/www/src/ts/utils.ts b/www/src/ts/utils.ts index 9dcdbb7..386fe4c 100644 --- a/www/src/ts/utils.ts +++ b/www/src/ts/utils.ts @@ -13,7 +13,31 @@ export function docReady(fn: () => void) { } function isZeroNegative(zero: number) { - return Object.is(zero, -0) + return Object.is(zero, -0); +} + +function makeCRCTable() { + let c; + const crcTable = []; + for (let n = 0; n < 256; n++) { + c = n; + for (let k = 0; k < 8; k++) { + c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1; + } + crcTable[n] = c; + } + return crcTable; +} + +const crcTable = makeCRCTable(); + +// Signed crc32 for string +export function crc32(str: string): number { + let crc = 0 ^ -1; + for (let i = 0, len = str.length; i < len; i++) { + crc = (crc >>> 8) ^ crcTable[(crc ^ str.charCodeAt(i)) & 0xff]; + } + return crc ^ -1; } export function numberToString(n: number): string { @@ -68,10 +92,54 @@ export function fromJson(value: string): any { return JSON.parse(value, reviver); } + +export function compareMaps(map1: Map, map2: Map): boolean { + let testVal; + if (map1.size !== map2.size) { + return false; + } + for (let [key, val] of map1) { + testVal = map2.get(key); + if((testVal === undefined && !map2.has(key)) || !structuralEqual(val, testVal)) { + return false; + } + } + return true; +} + +export function compareArrays( array1: any[], array2: any[]): boolean { + if (array1.length !== array2.length) { + return false; + } + for ( let i = 0, len = array1.length; i < len; i++) { + if (!structuralEqual(array1[i], array2[i])) { + return false; + } + } + return true; +} + +export function compareStructuralObjects(a: object, b: object): boolean { + const aProps = new Map(Object.entries(a)); + const bProps = new Map(Object.entries(b)); + return compareMaps(aProps, bProps); +} export function structuralEqual(a: any, b: any): boolean { - const _a = JSON.stringify(a, replacer); - const _b = JSON.stringify(b, replacer); - return _a === _b; + const aType = typeof a; + const bType = typeof b; + if ( aType !== typeof bType) { + return false; + } + if (a instanceof Map && b instanceof Map) { + return compareMaps(a, b); + } + if (Array.isArray(a) && Array.isArray(b)) { + return compareArrays(a, b); + } + if (aType === "object" && bType === "object") { + return compareStructuralObjects(a, b); + } + return a !== b; } // probably not needed, fetch() exists now @@ -144,7 +212,7 @@ export async function saveFile(content: BlobPart) { } else { console.log("saving file via hidden link event"); var a = document.createElement("a"); - const date = new Date().valueOf().toString(16) ; + const date = new Date().valueOf().toString(16); a.download = `code_${date}.ic10`; a.href = window.URL.createObjectURL(blob); a.click(); @@ -243,6 +311,26 @@ export function parseIntWithHexOrBinary(s: string): number { return parseInt(s); } -export function clamp (val: number, min: number, max: number) { +export function clamp(val: number, min: number, max: number) { return Math.min(Math.max(val, min), max); } + +export type TypedEventTarget = { + new (): TypedEventTargetInterface; +}; + +interface TypedEventTargetInterface extends EventTarget { + addEventListener( + type: K, + callback: ( + event: EventMap[K] extends Event ? EventMap[K] : never, + ) => EventMap[K] extends Event ? void : never, + options?: boolean | AddEventListenerOptions, + ): void; + + addEventListener( + type: string, + callback: EventListenerOrEventListenerObject | null, + options?: EventListenerOptions | boolean, + ): void; +} diff --git a/www/src/ts/virtual_machine/base_device.ts b/www/src/ts/virtual_machine/base_device.ts index cdcae29..230da86 100644 --- a/www/src/ts/virtual_machine/base_device.ts +++ b/www/src/ts/virtual_machine/base_device.ts @@ -1,53 +1,51 @@ import { property, state } from "lit/decorators.js"; import type { - DeviceRef, - LogicFields, - Reagents, Slot, Connection, ICError, - Registers, - Stack, - Aliases, - Defines, - Pins, LogicType, + LogicField, + Operand, + ObjectID, + TemplateDatabase, + FrozenObjectFull, } from "ic10emu_wasm"; -import { structuralEqual } from "utils"; +import { crc32, structuralEqual } from "utils"; import { LitElement, PropertyValueMap } from "lit"; -import type { DeviceDB } from "./device_db"; type Constructor = new (...args: any[]) => T; -export declare class VMDeviceMixinInterface { - deviceID: number; - activeICId: number; - device: DeviceRef; +export declare class VMObjectMixinInterface { + objectID: ObjectID; + activeICId: ObjectID; + obj: FrozenObjectFull; name: string | null; nameHash: number | null; prefabName: string | null; - fields: LogicFields; + prefabHash: number | null; + fields: Map; slots: Slot[]; - reagents: Reagents; + reagents: Map; connections: Connection[]; icIP: number; icOpCount: number; icState: string; errors: ICError[]; - registers: Registers | null; - stack: Stack | null; - aliases: Aliases | null; - defines: Defines | null; - pins: Pins | null; + registers: number[] | null; + memory: number[] | null; + aliases: Map | null; + defines: Map | null; + numPins: number | null; + pins: Map | null; _handleDeviceModified(e: CustomEvent): void; updateDevice(): void; updateIC(): void; - subscribe(...sub: VMDeviceMixinSubscription[]): void; - unsubscribe(filter: (sub: VMDeviceMixinSubscription) => boolean): void; + subscribe(...sub: VMObjectMixinSubscription[]): void; + unsubscribe(filter: (sub: VMObjectMixinSubscription) => boolean): void; } -export type VMDeviceMixinSubscription = +export type VMObjectMixinSubscription = | "name" | "nameHash" | "prefabName" @@ -62,10 +60,10 @@ export type VMDeviceMixinSubscription = | { slot: number } | "visible-devices"; -export const VMDeviceMixin = >( +export const VMObjectMixin = >( superClass: T, ) => { - class VMDeviceMixinClass extends superClass { + class VMObjectMixinClass extends superClass { private _deviceID: number; get deviceID() { return this._deviceID; @@ -76,39 +74,41 @@ export const VMDeviceMixin = >( this.updateDevice(); } - @state() private deviceSubscriptions: VMDeviceMixinSubscription[] = []; + @state() private objectSubscriptions: VMObjectMixinSubscription[] = []; - subscribe(...sub: VMDeviceMixinSubscription[]) { - this.deviceSubscriptions = this.deviceSubscriptions.concat(sub); + subscribe(...sub: VMObjectMixinSubscription[]) { + this.objectSubscriptions = this.objectSubscriptions.concat(sub); } // remove subscripotions matching the filter - unsubscribe(filter: (sub: VMDeviceMixinSubscription) => boolean) { - this.deviceSubscriptions = this.deviceSubscriptions.filter( + unsubscribe(filter: (sub: VMObjectMixinSubscription) => boolean) { + this.objectSubscriptions = this.objectSubscriptions.filter( (sub) => !filter(sub), ); } - device: DeviceRef; + obj: FrozenObjectFull; @state() activeICId: number; @state() name: string | null = null; @state() nameHash: number | null = null; @state() prefabName: string | null; - @state() fields: LogicFields; + @state() prefabHash: number | null; + @state() fields: Map; @state() slots: Slot[]; - @state() reagents: Reagents; + @state() reagents: Map; @state() connections: Connection[]; @state() icIP: number; @state() icOpCount: number; @state() icState: string; @state() errors: ICError[]; - @state() registers: Registers | null; - @state() stack: Stack | null; - @state() aliases: Aliases | null; - @state() defines: Defines | null; - @state() pins: Pins | null; + @state() registers: number[] | null; + @state() memory: number[] | null; + @state() aliases: Map | null; + @state() defines: Map | null; + @state() numPins: number | null; + @state() pins: Map | null; connectedCallback(): void { const root = super.connectedCallback(); @@ -128,7 +128,7 @@ export const VMDeviceMixin = >( vm.addEventListener( "vm-devices-removed", this._handleDevicesRemoved.bind(this), - ) + ); }); this.updateDevice(); return root; @@ -151,23 +151,25 @@ export const VMDeviceMixin = >( vm.removeEventListener( "vm-devices-removed", this._handleDevicesRemoved.bind(this), - ) + ); }); } - _handleDeviceModified(e: CustomEvent) { + async _handleDeviceModified(e: CustomEvent) { const id = e.detail; const activeIcId = window.App.app.session.activeIC; if (this.deviceID === id) { this.updateDevice(); } else if ( id === activeIcId && - this.deviceSubscriptions.includes("active-ic") + this.objectSubscriptions.includes("active-ic") ) { this.updateDevice(); this.requestUpdate(); - } else if (this.deviceSubscriptions.includes("visible-devices")) { - const visibleDevices = window.VM.vm.visibleDeviceIds(this.deviceID); + } else if (this.objectSubscriptions.includes("visible-devices")) { + const visibleDevices = await window.VM.vm.visibleDeviceIds( + this.deviceID, + ); if (visibleDevices.includes(id)) { this.updateDevice(); this.requestUpdate(); @@ -175,116 +177,217 @@ export const VMDeviceMixin = >( } } - _handleDevicesModified(e: CustomEvent) { + async _handleDevicesModified(e: CustomEvent) { const activeIcId = window.App.app.session.activeIC; const ids = e.detail; if (ids.includes(this.deviceID)) { this.updateDevice(); - if (this.deviceSubscriptions.includes("visible-devices")) { + if (this.objectSubscriptions.includes("visible-devices")) { this.requestUpdate(); } } else if ( ids.includes(activeIcId) && - this.deviceSubscriptions.includes("active-ic") + this.objectSubscriptions.includes("active-ic") ) { this.updateDevice(); this.requestUpdate(); - } else if (this.deviceSubscriptions.includes("visible-devices")) { - const visibleDevices = window.VM.vm.visibleDeviceIds(this.deviceID); - if (ids.some( id => visibleDevices.includes(id))) { + } else if (this.objectSubscriptions.includes("visible-devices")) { + const visibleDevices = await window.VM.vm.visibleDeviceIds( + this.deviceID, + ); + if (ids.some((id) => visibleDevices.includes(id))) { this.updateDevice(); this.requestUpdate(); } } } - _handleDeviceIdChange(e: CustomEvent<{ old: number; new: number }>) { + async _handleDeviceIdChange(e: CustomEvent<{ old: number; new: number }>) { if (this.deviceID === e.detail.old) { this.deviceID = e.detail.new; - } else if (this.deviceSubscriptions.includes("visible-devices")) { - const visibleDevices = window.VM.vm.visibleDeviceIds(this.deviceID); - if (visibleDevices.some(id => id === e.detail.old || id === e.detail.new)) { - this.requestUpdate() + } else if (this.objectSubscriptions.includes("visible-devices")) { + const visibleDevices = await window.VM.vm.visibleDeviceIds( + this.deviceID, + ); + if ( + visibleDevices.some( + (id) => id === e.detail.old || id === e.detail.new, + ) + ) { + this.requestUpdate(); } } } _handleDevicesRemoved(e: CustomEvent) { const _ids = e.detail; - if (this.deviceSubscriptions.includes("visible-devices")) { - this.requestUpdate() + if (this.objectSubscriptions.includes("visible-devices")) { + this.requestUpdate(); } } updateDevice() { - this.device = window.VM.vm.devices.get(this.deviceID)!; + this.obj = window.VM.vm.objects.get(this.deviceID)!; - if (typeof this.device === "undefined") { + if (typeof this.obj === "undefined") { return; } - for (const sub of this.deviceSubscriptions) { + if ( + this.objectSubscriptions.includes("slots") || + this.objectSubscriptions.includes("slots-count") + ) { + const slotsOccupantInfo = this.obj.obj_info.slots; + const logicTemplate = + "logic" in this.obj.template ? this.obj.template.logic : null; + const slotsTemplate = + "slots" in this.obj.template ? this.obj.template.slots : []; + let slots: Slot[] | null = null; + if (slotsOccupantInfo.size !== 0) { + slots = slotsTemplate.map((template, index) => { + let slot = { + parent: this.obj.obj_info.id, + index: index, + name: template.name, + typ: template.typ, + readable_logic: Array.from( + logicTemplate?.logic_slot_types.get(index)?.entries() ?? [], + ) + .filter(([_, val]) => val === "Read" || val === "ReadWrite") + .map(([key, _]) => key), + writeable_logic: Array.from( + logicTemplate?.logic_slot_types.get(index)?.entries() ?? [], + ) + .filter(([_, val]) => val === "Write" || val === "ReadWrite") + .map(([key, _]) => key), + occupant: slotsOccupantInfo.get(index), + }; + return slot; + }); + } + + if (!structuralEqual(this.slots, slots)) { + this.slots = slots; + } + } + + for (const sub of this.objectSubscriptions) { if (typeof sub === "string") { if (sub == "name") { - const name = this.device.name ?? null; + const name = this.obj.obj_info.name ?? null; if (this.name !== name) { this.name = name; } } else if (sub === "nameHash") { - const nameHash = this.device.nameHash ?? null; + const nameHash = + typeof this.obj.obj_info.name !== "undefined" + ? crc32(this.obj.obj_info.name) + : null; if (this.nameHash !== nameHash) { this.nameHash = nameHash; } } else if (sub === "prefabName") { - const prefabName = this.device.prefabName ?? null; + const prefabName = this.obj.obj_info.prefab ?? null; if (this.prefabName !== prefabName) { this.prefabName = prefabName; + this.prefabHash = crc32(prefabName); } } else if (sub === "fields") { - const fields = this.device.fields; - if (!structuralEqual(this.fields, fields)) { - this.fields = fields; + const fields = this.obj.obj_info.logic_values ?? null; + const logicTemplate = + "logic" in this.obj.template ? this.obj.template.logic : null; + let logic_fields: Map | null = null; + if (fields !== null) { + logic_fields = new Map(); + for (const [lt, val] of fields) { + const access = logicTemplate?.logic_types.get(lt) ?? "Read"; + logic_fields.set(lt, { + value: val, + field_type: access, + }); + } } - } 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; + if (!structuralEqual(this.fields, logic_fields)) { + this.fields = logic_fields; } } else if (sub === "reagents") { - const reagents = this.device.reagents; + const reagents = this.obj.obj_info.reagents; if (!structuralEqual(this.reagents, reagents)) { this.reagents = reagents; } } else if (sub === "connections") { - const connections = this.device.connections; + const connectionsMap = this.obj.obj_info.connections ?? new Map(); + const connectionList = + "device" in this.obj.template + ? this.obj.template.device.connection_list + : []; + let connections: Connection[] | null = null; + if (connectionList.length !== 0) { + connections = connectionList.map((conn, index) => { + if (conn.typ === "Data") { + return { + CableNetwork: { + typ: "Data", + role: conn.role, + net: connectionsMap.get(index), + }, + }; + } else if (conn.typ === "Power") { + return { + CableNetwork: { + typ: "Power", + role: conn.role, + net: connectionsMap.get(index), + }, + }; + } else if (conn.typ === "PowerAndData") { + return { + CableNetwork: { + typ: "Data", + role: conn.role, + net: connectionsMap.get(index), + }, + }; + } else if (conn.typ === "Pipe") { + return { Pipe: { role: conn.role } }; + } else if (conn.typ === "Chute") { + return { Chute: { role: conn.role } }; + } else if (conn.typ === "Elevator") { + return { Elevator: { role: conn.role } }; + } else if (conn.typ === "LaunchPad") { + return { LaunchPad: { role: conn.role } }; + } else if (conn.typ === "LandingPad") { + return { LandingPad: { role: conn.role } }; + } else if (conn.typ === "PipeLiquid") { + return { PipeLiquid: { role: conn.role } }; + } + return "None"; + }); + } if (!structuralEqual(this.connections, connections)) { this.connections = connections; } } else if (sub === "ic") { - if (typeof this.device.ic !== "undefined") { + if ( + typeof this.obj.obj_info.circuit !== "undefined" || + this.obj.obj_info.socketed_ic !== "undefined" + ) { this.updateIC(); } } else if (sub === "active-ic") { const activeIc = window.VM.vm?.activeIC; - if (this.activeICId !== activeIc.id) { - this.activeICId = activeIc.id; + if (this.activeICId !== activeIc.obj_info.id) { + this.activeICId = activeIc.obj_info.id; } } } else { if ("field" in sub) { - const fields = this.device.fields; + const fields = this.obj.obj_info.logic_values; if (this.fields.get(sub.field) !== fields.get(sub.field)) { this.fields = fields; } } else if ("slot" in sub) { - const slots = this.device.slots; + const slots = this.obj.slots; if ( typeof this.slots === "undefined" || this.slots.length < sub.slot @@ -301,54 +404,54 @@ export const VMDeviceMixin = >( } updateIC() { - const ip = this.device.ip!; + const ip = this.obj.ip!; if (this.icIP !== ip) { this.icIP = ip; } - const opCount = this.device.instructionCount!; + const opCount = this.obj.instructionCount!; if (this.icOpCount !== opCount) { this.icOpCount = opCount; } - const state = this.device.state!; + const state = this.obj.state!; if (this.icState !== state) { this.icState = state; } - const errors = this.device.program?.errors ?? null; + const errors = this.obj.program?.errors ?? null; if (!structuralEqual(this.errors, errors)) { this.errors = errors; } - const registers = this.device.registers ?? null; + const registers = this.obj.registers ?? null; if (!structuralEqual(this.registers, registers)) { this.registers = registers; } - const stack = this.device.stack ?? null; - if (!structuralEqual(this.stack, stack)) { - this.stack = stack; + const stack = this.obj.stack ?? null; + if (!structuralEqual(this.memory, stack)) { + this.memory = stack; } - const aliases = this.device.aliases ?? null; + const aliases = this.obj.aliases ?? null; if (!structuralEqual(this.aliases, aliases)) { this.aliases = aliases; } - const defines = this.device.defines ?? null; + const defines = this.obj.defines ?? null; if (!structuralEqual(this.defines, defines)) { this.defines = defines; } - const pins = this.device.pins ?? null; + const pins = this.obj.pins ?? null; if (!structuralEqual(this.pins, pins)) { this.pins = pins; } } } - return VMDeviceMixinClass as Constructor & T; + return VMObjectMixinClass as Constructor & T; }; export const VMActiveICMixin = >( superClass: T, ) => { - class VMActiveICMixinClass extends VMDeviceMixin(superClass) { + class VMActiveICMixinClass extends VMObjectMixin(superClass) { constructor() { super(); - this.deviceID = window.App.app.session.activeIC; + this.objectID = window.App.app.session.activeIC; } connectedCallback(): void { @@ -378,19 +481,19 @@ export const VMActiveICMixin = >( _handleActiveIC(e: CustomEvent) { const id = e.detail; - if (this.deviceID !== id) { - this.deviceID = id; - this.device = window.VM.vm.devices.get(this.deviceID)!; + if (this.objectID !== id) { + this.objectID = id; + this.obj = window.VM.vm.objects.get(this.objectID)!; } this.updateDevice(); } } - return VMActiveICMixinClass as Constructor & T; + return VMActiveICMixinClass as Constructor & T; }; export declare class VMDeviceDBMixinInterface { - deviceDB: DeviceDB; + templateDB: TemplateDatabase; _handleDeviceDBLoad(e: CustomEvent): void; postDBSetUpdate(): void; } @@ -405,8 +508,8 @@ export const VMDeviceDBMixin = >( "vm-device-db-loaded", this._handleDeviceDBLoad.bind(this), ); - if (typeof window.VM.vm.db !== "undefined") { - this.deviceDB = window.VM.vm.db!; + if (typeof window.VM.vm.templateDB !== "undefined") { + this.templateDB = window.VM.vm.templateDB!; } return root; } @@ -419,20 +522,20 @@ export const VMDeviceDBMixin = >( } _handleDeviceDBLoad(e: CustomEvent) { - this.deviceDB = e.detail; + this.templateDB = e.detail; } - private _deviceDB: DeviceDB; + private _templateDB: TemplateDatabase; - get deviceDB(): DeviceDB { - return this._deviceDB; + get templateDB(): TemplateDatabase { + return this._templateDB; } postDBSetUpdate(): void { } @state() - set deviceDB(val: DeviceDB) { - this._deviceDB = val; + set templateDB(val: TemplateDatabase) { + this._templateDB = val; this.postDBSetUpdate(); } } diff --git a/www/src/ts/virtual_machine/controls.ts b/www/src/ts/virtual_machine/controls.ts index 067ebd1..549ded0 100644 --- a/www/src/ts/virtual_machine/controls.ts +++ b/www/src/ts/virtual_machine/controls.ts @@ -65,7 +65,7 @@ export class VMICControls extends VMActiveICMixin(BaseElement) { @query(".active-ic-select") activeICSelect: SlSelect; protected render() { - const ics = Array.from(window.VM.vm.ics); + const ics = Array.from(window.VM.vm.circuitHolders); return html`
@@ -116,7 +116,7 @@ export class VMICControls extends VMActiveICMixin(BaseElement) { hoist size="small" placement="bottom" - value="${this.deviceID}" + value="${this.objectID}" @sl-change=${this._handleChangeActiveIC} class="active-ic-select" > diff --git a/www/src/ts/virtual_machine/device/add_device.ts b/www/src/ts/virtual_machine/device/add_device.ts index e1f294e..96f76c1 100644 --- a/www/src/ts/virtual_machine/device/add_device.ts +++ b/www/src/ts/virtual_machine/device/add_device.ts @@ -41,10 +41,10 @@ export class VMAddDeviceButton extends VMDeviceDBMixin(BaseElement) { postDBSetUpdate(): void { this._structures = new Map( - Object.values(this.deviceDB.db) - .filter((entry) => this.deviceDB.structures.includes(entry.name), this) + Object.values(this.templateDB.db) + .filter((entry) => this.templateDB.structures.includes(entry.name), this) .filter( - (entry) => this.deviceDB.logic_enabled.includes(entry.name), + (entry) => this.templateDB.logic_enabled.includes(entry.name), this, ) .map((entry) => [entry.name, entry]), @@ -133,7 +133,7 @@ export class VMAddDeviceButton extends VMDeviceDBMixin(BaseElement) { } _handleDeviceDBLoad(e: CustomEvent) { - this.deviceDB = e.detail; + this.templateDB = e.detail; } @state() private page = 0; diff --git a/www/src/ts/virtual_machine/device/card.ts b/www/src/ts/virtual_machine/device/card.ts index 75d8b92..91465dd 100644 --- a/www/src/ts/virtual_machine/device/card.ts +++ b/www/src/ts/virtual_machine/device/card.ts @@ -1,7 +1,7 @@ import { html, css, HTMLTemplateResult } from "lit"; import { customElement, property, query, state } from "lit/decorators.js"; import { BaseElement, defaultCss } from "components"; -import { VMDeviceDBMixin, VMDeviceMixin } from "virtual_machine/base_device"; +import { VMDeviceDBMixin, VMObjectMixin } from "virtual_machine/base_device"; import SlSelect from "@shoelace-style/shoelace/dist/components/select/select.component.js"; import { parseIntWithHexOrBinary, parseNumber } from "utils"; import SlInput from "@shoelace-style/shoelace/dist/components/input/input.component.js"; @@ -15,7 +15,7 @@ import { repeat } from "lit/directives/repeat.js"; export type CardTab = "fields" | "slots" | "reagents" | "networks" | "pins"; @customElement("vm-device-card") -export class VMDeviceCard extends VMDeviceDBMixin(VMDeviceMixin(BaseElement)) { +export class VMDeviceCard extends VMDeviceDBMixin(VMObjectMixin(BaseElement)) { image_err: boolean; @property({ type: Boolean }) open: boolean; @@ -135,14 +135,14 @@ export class VMDeviceCard extends VMDeviceDBMixin(VMDeviceMixin(BaseElement)) { } renderHeader(): HTMLTemplateResult { - const thisIsActiveIc = this.activeICId === this.deviceID; + const thisIsActiveIc = this.activeICId === this.objectID; const badges: HTMLTemplateResult[] = []; if (thisIsActiveIc) { badges.push(html`db`); } const activeIc = window.VM.vm.activeIC; activeIc?.pins?.forEach((id, index) => { - if (this.deviceID == id) { + if (this.objectID == id) { badges.push( html`d${index}`, ); @@ -154,20 +154,20 @@ export class VMDeviceCard extends VMDeviceDBMixin(VMDeviceMixin(BaseElement)) { onerror="this.src = '${VMDeviceCard.transparentImg}'" />
- Id - + - Name - + - Hash - + ${badges.map((badge) => badge)}
@@ -183,7 +183,7 @@ export class VMDeviceCard extends VMDeviceDBMixin(VMDeviceMixin(BaseElement)) { renderFields() { return this.delayRenderTab( "fields", - html``, + html``, ); } @@ -202,7 +202,7 @@ export class VMDeviceCard extends VMDeviceDBMixin(VMDeviceMixin(BaseElement)) { ${repeat(this.slots, (slot, index) => slot.typ + index.toString(), (_slot, index) => html` - + `, )} @@ -242,7 +242,7 @@ export class VMDeviceCard extends VMDeviceDBMixin(VMDeviceMixin(BaseElement)) { return this.delayRenderTab( "pins", html`
- +
` ); } @@ -295,7 +295,7 @@ export class VMDeviceCard extends VMDeviceDBMixin(VMDeviceMixin(BaseElement)) { Slots Reagents Networks - Pins + Pins ${until(this.renderFields(), html``)} @@ -318,7 +318,7 @@ export class VMDeviceCard extends VMDeviceDBMixin(VMDeviceMixin(BaseElement)) { onerror="this.src = '${VMDeviceCard.transparentImg}'" />

Are you sure you want to remove this device?

- Id ${this.deviceID} : ${this.name ?? this.prefabName} + Id ${this.objectID} : ${this.name ?? this.prefabName}
@@ -350,12 +350,12 @@ export class VMDeviceCard extends VMDeviceDBMixin(VMDeviceMixin(BaseElement)) { const val = parseIntWithHexOrBinary(input.value); if (!isNaN(val)) { window.VM.get().then((vm) => { - if (!vm.changeDeviceID(this.deviceID, val)) { - input.value = this.deviceID.toString(); + if (!vm.changeDeviceID(this.objectID, val)) { + input.value = this.objectID.toString(); } }); } else { - input.value = this.deviceID.toString(); + input.value = this.objectID.toString(); } } @@ -363,7 +363,7 @@ export class VMDeviceCard extends VMDeviceDBMixin(VMDeviceMixin(BaseElement)) { const input = e.target as SlInput; const name = input.value.length === 0 ? undefined : input.value; window.VM.get().then((vm) => { - if (!vm.setDeviceName(this.deviceID, name)) { + if (!vm.setObjectName(this.objectID, name)) { input.value = this.name; } this.updateDevice(); @@ -375,7 +375,7 @@ export class VMDeviceCard extends VMDeviceDBMixin(VMDeviceMixin(BaseElement)) { _removeDialogRemove() { this.removeDialog.hide(); - window.VM.get().then((vm) => vm.removeDevice(this.deviceID)); + window.VM.get().then((vm) => vm.removeDevice(this.objectID)); } _handleChangeConnection(e: CustomEvent) { @@ -383,7 +383,7 @@ export class VMDeviceCard extends VMDeviceDBMixin(VMDeviceMixin(BaseElement)) { const conn = parseInt(select.getAttribute("key")!); const val = select.value ? parseInt(select.value as string) : undefined; window.VM.get().then((vm) => - vm.setDeviceConnection(this.deviceID, conn, val), + vm.setDeviceConnection(this.objectID, conn, val), ); this.updateDevice(); } diff --git a/www/src/ts/virtual_machine/device/device_list.ts b/www/src/ts/virtual_machine/device/device_list.ts index 04e0f64..871710f 100644 --- a/www/src/ts/virtual_machine/device/device_list.ts +++ b/www/src/ts/virtual_machine/device/device_list.ts @@ -43,7 +43,7 @@ export class VMDeviceList extends BaseElement { constructor() { super(); - this.devices = [...window.VM.vm.deviceIds]; + this.devices = [...window.VM.vm.objectIds]; } connectedCallback(): void { @@ -149,7 +149,7 @@ export class VMDeviceList extends BaseElement { if (this._filter) { const datapoints: [string, number][] = []; for (const device_id of this.devices) { - const device = window.VM.vm.devices.get(device_id); + const device = window.VM.vm.objects.get(device_id); if (device) { if (typeof device.name !== "undefined") { datapoints.push([device.name, device.id]); diff --git a/www/src/ts/virtual_machine/device/fields.ts b/www/src/ts/virtual_machine/device/fields.ts index e45c245..9592330 100644 --- a/www/src/ts/virtual_machine/device/fields.ts +++ b/www/src/ts/virtual_machine/device/fields.ts @@ -1,13 +1,13 @@ 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 { VMDeviceDBMixin, VMObjectMixin } 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)) { +export class VMDeviceSlot extends VMObjectMixin(VMDeviceDBMixin(BaseElement)) { constructor() { super(); this.subscribe("fields"); @@ -15,7 +15,7 @@ export class VMDeviceSlot extends VMDeviceMixin(VMDeviceDBMixin(BaseElement)) { render() { const fields = Array.from(this.fields.entries()); - const inputIdBase = `vmDeviceCard${this.deviceID}Field`; + const inputIdBase = `vmDeviceCard${this.objectID}Field`; return html` ${fields.map(([name, field], _index, _fields) => { return html` { - if (!vm.setDeviceField(this.deviceID, field, val, true)) { + if (!vm.setObjectField(this.objectID, field, val, true)) { input.value = this.fields.get(field).value.toString(); } this.updateDevice(); diff --git a/www/src/ts/virtual_machine/device/pins.ts b/www/src/ts/virtual_machine/device/pins.ts index 1bb5cba..12b4e04 100644 --- a/www/src/ts/virtual_machine/device/pins.ts +++ b/www/src/ts/virtual_machine/device/pins.ts @@ -2,11 +2,11 @@ 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 { VMDeviceDBMixin, VMObjectMixin } from "virtual_machine/base_device"; import SlSelect from "@shoelace-style/shoelace/dist/components/select/select.component.js"; @customElement("vm-device-pins") -export class VMDevicePins extends VMDeviceMixin(VMDeviceDBMixin(BaseElement)) { +export class VMDevicePins extends VMObjectMixin(VMDeviceDBMixin(BaseElement)) { constructor() { super(); this.subscribe("ic", "visible-devices"); @@ -14,7 +14,7 @@ export class VMDevicePins extends VMDeviceMixin(VMDeviceDBMixin(BaseElement)) { render() { const pins = this.pins; - const visibleDevices = window.VM.vm.visibleDevices(this.deviceID); + const visibleDevices = window.VM.vm.visibleDevices(this.objectID); const pinsHtml = pins?.map( (pin, index) => html` @@ -37,7 +37,7 @@ export class VMDevicePins extends VMDeviceMixin(VMDeviceDBMixin(BaseElement)) { const select = e.target as SlSelect; const pin = parseInt(select.getAttribute("key")!); const val = select.value ? parseInt(select.value as string) : undefined; - window.VM.get().then((vm) => vm.setDevicePin(this.deviceID, pin, val)); + window.VM.get().then((vm) => vm.setDevicePin(this.objectID, pin, val)); this.updateDevice(); } diff --git a/www/src/ts/virtual_machine/device/slot.ts b/www/src/ts/virtual_machine/device/slot.ts index fa16e0c..473223f 100644 --- a/www/src/ts/virtual_machine/device/slot.ts +++ b/www/src/ts/virtual_machine/device/slot.ts @@ -1,7 +1,7 @@ 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 { VMDeviceDBMixin, VMObjectMixin } from "virtual_machine/base_device"; import { clamp, displayNumber, @@ -21,7 +21,7 @@ export interface SlotModifyEvent { } @customElement("vm-device-slot") -export class VMDeviceSlot extends VMDeviceMixin(VMDeviceDBMixin(BaseElement)) { +export class VMDeviceSlot extends VMObjectMixin(VMDeviceDBMixin(BaseElement)) { private _slotIndex: number; get slotIndex() { @@ -72,7 +72,7 @@ export class VMDeviceSlot extends VMDeviceMixin(VMDeviceDBMixin(BaseElement)) { slotOccupantImg(): string { const slot = this.slots[this.slotIndex]; if (typeof slot.occupant !== "undefined") { - const hashLookup = (this.deviceDB ?? {}).names_by_hash ?? {}; + const hashLookup = (this.templateDB ?? {}).names_by_hash ?? {}; const prefabName = hashLookup[slot.occupant.prefab_hash] ?? "UnknownHash"; return `img/stationpedia/${prefabName}.png`; } else { @@ -83,7 +83,7 @@ export class VMDeviceSlot extends VMDeviceMixin(VMDeviceDBMixin(BaseElement)) { slotOccupantPrefabName(): string { const slot = this.slots[this.slotIndex]; if (typeof slot.occupant !== "undefined") { - const hashLookup = (this.deviceDB ?? {}).names_by_hash ?? {}; + const hashLookup = (this.templateDB ?? {}).names_by_hash ?? {}; const prefabName = hashLookup[slot.occupant.prefab_hash] ?? "UnknownHash"; return prefabName; } else { @@ -92,8 +92,8 @@ export class VMDeviceSlot extends VMDeviceMixin(VMDeviceDBMixin(BaseElement)) { } slotOcccupantTemplate(): { name: string; typ: SlotType } | undefined { - if (this.deviceDB) { - const entry = this.deviceDB.db[this.prefabName]; + if (this.templateDB) { + const entry = this.templateDB.db[this.prefabName]; return entry?.slots[this.slotIndex]; } else { return undefined; @@ -101,7 +101,7 @@ export class VMDeviceSlot extends VMDeviceMixin(VMDeviceDBMixin(BaseElement)) { } renderHeader() { - const inputIdBase = `vmDeviceSlot${this.deviceID}Slot${this.slotIndex}Head`; + const inputIdBase = `vmDeviceSlot${this.objectID}Slot${this.slotIndex}Head`; const slot = this.slots[this.slotIndex]; const slotImg = this.slotOccupantImg(); const img = html``; const template = this.slotOcccupantTemplate(); - const thisIsActiveIc = this.activeICId === this.deviceID; + const thisIsActiveIc = this.activeICId === this.objectID; const enableQuantityInput = false; @@ -202,7 +202,7 @@ export class VMDeviceSlot extends VMDeviceMixin(VMDeviceDBMixin(BaseElement)) { } _handleSlotOccupantRemove() { - window.VM.vm.removeDeviceSlotOccupant(this.deviceID, this.slotIndex); + window.VM.vm.removeSlotOccupant(this.objectID, this.slotIndex); } _handleSlotClick(_e: Event) { @@ -210,7 +210,7 @@ export class VMDeviceSlot extends VMDeviceMixin(VMDeviceDBMixin(BaseElement)) { new CustomEvent("device-modify-slot", { bubbles: true, composed: true, - detail: { deviceID: this.deviceID, slotIndex: this.slotIndex }, + detail: { deviceID: this.objectID, slotIndex: this.slotIndex }, }), ); } @@ -220,23 +220,23 @@ export class VMDeviceSlot extends VMDeviceMixin(VMDeviceDBMixin(BaseElement)) { const slot = this.slots[this.slotIndex]; const val = clamp(input.valueAsNumber, 1, slot.occupant.max_quantity); if ( - !window.VM.vm.setDeviceSlotField( - this.deviceID, + !window.VM.vm.setObjectSlotField( + this.objectID, this.slotIndex, "Quantity", val, true, ) ) { - input.value = this.device + input.value = this.obj .getSlotField(this.slotIndex, "Quantity") .toString(); } } renderFields() { - const inputIdBase = `vmDeviceSlot${this.deviceID}Slot${this.slotIndex}Field`; - const _fields = this.device.getSlotFields(this.slotIndex); + const inputIdBase = `vmDeviceSlot${this.objectID}Slot${this.slotIndex}Field`; + const _fields = this.obj.getSlotFields(this.slotIndex); const fields = Array.from(_fields.entries()); return html` @@ -273,9 +273,9 @@ export class VMDeviceSlot extends VMDeviceMixin(VMDeviceDBMixin(BaseElement)) { } window.VM.get().then((vm) => { if ( - !vm.setDeviceSlotField(this.deviceID, this.slotIndex, field, val, true) + !vm.setObjectSlotField(this.objectID, this.slotIndex, field, val, true) ) { - input.value = this.device + input.value = this.obj .getSlotField(this.slotIndex, field) .toString(); } diff --git a/www/src/ts/virtual_machine/device/slot_add_dialog.ts b/www/src/ts/virtual_machine/device/slot_add_dialog.ts index 393f101..1fa3dca 100644 --- a/www/src/ts/virtual_machine/device/slot_add_dialog.ts +++ b/www/src/ts/virtual_machine/device/slot_add_dialog.ts @@ -54,8 +54,8 @@ export class VMSlotAddDialog extends VMDeviceDBMixin(BaseElement) { postDBSetUpdate(): void { this._items = new Map( - Object.values(this.deviceDB.db) - .filter((entry) => this.deviceDB.items.includes(entry.name), this) + Object.values(this.templateDB.db) + .filter((entry) => this.templateDB.items.includes(entry.name), this) .map((entry) => [entry.name, entry]), ); this.setupSearch(); @@ -66,8 +66,8 @@ export class VMSlotAddDialog extends VMDeviceDBMixin(BaseElement) { setupSearch() { let filteredItemss = Array.from(this._items.values()); if( typeof this.deviceID !== "undefined" && typeof this.slotIndex !== "undefined") { - const device = window.VM.vm.devices.get(this.deviceID); - const dbDevice = this.deviceDB.db[device.prefabName] + const device = window.VM.vm.objects.get(this.deviceID); + const dbDevice = this.templateDB.db[device.prefabName] const slot = dbDevice.slots[this.slotIndex] const typ = slot.typ; @@ -157,17 +157,17 @@ export class VMSlotAddDialog extends VMDeviceDBMixin(BaseElement) { } _handleClickNone() { - window.VM.vm.removeDeviceSlotOccupant(this.deviceID, this.slotIndex); + window.VM.vm.removeSlotOccupant(this.deviceID, this.slotIndex); this.hide(); } _handleClickItem(e: Event) { const div = e.currentTarget as HTMLDivElement; const key = div.getAttribute("key"); - const entry = this.deviceDB.db[key]; - const device = window.VM.vm.devices.get(this.deviceID); - const dbDevice = this.deviceDB.db[device.prefabName] - const sorting = this.deviceDB.enums["SortingClass"][entry.item.sorting ?? "Default"] ?? 0; + const entry = this.templateDB.db[key]; + const device = window.VM.vm.objects.get(this.deviceID); + const dbDevice = this.templateDB.db[device.prefabName] + const sorting = this.templateDB.enums["SortingClass"][entry.item.sorting ?? "Default"] ?? 0; console.log("using entry", dbDevice); const fields: { [key in LogicSlotType]?: LogicField } = Object.fromEntries( Object.entries(dbDevice.slotlogic[this.slotIndex] ?? {}) @@ -175,7 +175,7 @@ export class VMSlotAddDialog extends VMDeviceDBMixin(BaseElement) { let slt = slt_s as LogicSlotType; let value = 0.0 if (slt === "FilterType") { - value = this.deviceDB.enums["GasType"][entry.item.filtertype] + value = this.templateDB.enums["GasType"][entry.item.filtertype] } const field: LogicField = { field_type, value}; return [slt, field]; @@ -189,7 +189,7 @@ export class VMSlotAddDialog extends VMDeviceDBMixin(BaseElement) { const template: SlotOccupantTemplate = { fields } - window.VM.vm.setDeviceSlotOccupant(this.deviceID, this.slotIndex, template); + window.VM.vm.setSlotOccupant(this.deviceID, this.slotIndex, template); this.hide(); } @@ -197,7 +197,7 @@ export class VMSlotAddDialog extends VMDeviceDBMixin(BaseElement) { @query(".device-search-input") searchInput: SlInput; render() { - const device = window.VM.vm.devices.get(this.deviceID); + const device = window.VM.vm.objects.get(this.deviceID); const name = device?.name ?? device?.prefabName ?? ""; const id = this.deviceID ?? 0; return html` diff --git a/www/src/ts/virtual_machine/device/template.ts b/www/src/ts/virtual_machine/device/template.ts index 711cb31..bd759ae 100644 --- a/www/src/ts/virtual_machine/device/template.ts +++ b/www/src/ts/virtual_machine/device/template.ts @@ -63,7 +63,7 @@ export class VmDeviceTemplate extends VMDeviceDBMixin(BaseElement) { constructor() { super(); - this.deviceDB = window.VM.vm.db; + this.templateDB = window.VM.vm.db; } private _prefab_name: string; @@ -79,7 +79,7 @@ export class VmDeviceTemplate extends VMDeviceDBMixin(BaseElement) { } get dbDevice(): DeviceDBEntry { - return this.deviceDB.db[this.prefab_name]; + return this.templateDB.db[this.prefab_name]; } setupState() { @@ -198,7 +198,7 @@ export class VmDeviceTemplate extends VMDeviceDBMixin(BaseElement) { } renderPins(): HTMLTemplateResult { - const device = this.deviceDB.db[this.prefab_name]; + const device = this.templateDB.db[this.prefab_name]; return html`
`; } diff --git a/www/src/ts/virtual_machine/device_db.ts b/www/src/ts/virtual_machine/device_db.ts deleted file mode 100644 index 770966c..0000000 --- a/www/src/ts/virtual_machine/device_db.ts +++ /dev/null @@ -1,95 +0,0 @@ -import { - LogicType, - LogicSlotType, - SortingClass, - SlotType, - MemoryAccess, - ReagentMode, - BatchMode, - ConnectionType, - ConnectionRole, -} from "ic10emu_wasm"; -export interface DeviceDBItem { - slotclass: SlotType; - sorting: SortingClass; - maxquantity?: number; - filtertype?: string; - consumable?: boolean; - ingredient?: boolean; - reagents?: { [key: string]: number }; -} - -export interface DeviceDBDevice { - states: DBStates; - reagents: boolean; - atmosphere: boolean; - pins?: number; -} - -export interface DeviceDBConnection { - typ: ConnectionType; - role: ConnectionRole; - name: string; -} - -export interface DeviceDBInstruction { - typ: string; - value: number; - desc: string; -} - -export interface DeviceDBMemory { - size: number; - sizeDisplay: string; - access: MemoryAccess - instructions?: { [key: string]: DeviceDBInstruction }; -} - -export type MemoryAccess = "Read" | "Write" | "ReadWrite" | "None"; - - -export interface DeviceDBEntry { - name: string; - hash: number; - title: string; - desc: string; - slots?: { name: string; typ: SlotType }[]; - logic?: { [key in LogicType]?: MemoryAccess }; - slotlogic?: { [key: number]: {[key in LogicSlotType]?: MemoryAccess } }; - modes?: { [key: number]: string }; - conn?: { [key: number]: DeviceDBConnection } - item?: DeviceDBItem; - device?: DeviceDBDevice; - transmitter: boolean; - receiver: boolean; - memory?: DeviceDBMemory; -} - -export interface DBStates { - activate: boolean; - color: boolean; - lock: boolean; - mode: boolean; - onoff: boolean; - open: boolean; -} - -export interface DeviceDBReagent { - Hash: number; - Unit: string; - Sources?: { [key: string]: number }; -} - -export interface DeviceDB { - logic_enabled: string[]; - slot_logic_enabled: string[]; - devices: string[]; - items: string[]; - structures: string[]; - db: { - [key: string]: DeviceDBEntry; - }; - names_by_hash: { [key: number]: string }; - reagents: { [key: string]: DeviceDBReagent }; - enums: { [key: string]: { [key: string]: number } }; -} diff --git a/www/src/ts/virtual_machine/index.ts b/www/src/ts/virtual_machine/index.ts index b487c51..5143235 100644 --- a/www/src/ts/virtual_machine/index.ts +++ b/www/src/ts/virtual_machine/index.ts @@ -1,18 +1,19 @@ -import { - DeviceRef, - DeviceTemplate, +import type { + ObjectTemplate, + FrozenObject, FrozenVM, LogicType, LogicSlotType, - SlotOccupantTemplate, - Slots, VMRef, - init, + TemplateDatabase, + FrozenCableNetwork, + FrozenObjectFull, } from "ic10emu_wasm"; -import { DeviceDB } from "./device_db"; +import * as Comlink from "comlink"; import "./base_device"; import "./device"; import { App } from "app"; +import { structuralEqual, TypedEventTarget } from "utils"; export interface ToastMessage { variant: "warning" | "danger" | "success" | "primary" | "neutral"; icon: string; @@ -21,156 +22,168 @@ export interface ToastMessage { id: string; } -export interface CacheDeviceRef extends DeviceRef { - dirty: boolean; +export interface VirtualMachinEventMap { + "vm-template-db-loaded": CustomEvent; + "vm-objects-update": CustomEvent; + "vm-objects-removed": CustomEvent; + "vm-objects-modified": CustomEvent; + "vm-run-ic": CustomEvent; + "vm-object-id-change": CustomEvent<{ old: number; new: number }>; } -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 as TypedEventTarget) { + ic10vm: Comlink.Remote; + templateDBPromise: Promise; + templateDB: TemplateDatabase; -class VirtualMachine extends EventTarget { - ic10vm: VMRef; - _devices: Map; - _ics: Map; + private _objects: Map; + private _circuitHolders: Map; + private _networks: Map; + private _default_network: number; - db: DeviceDB; - dbPromise: Promise<{ default: DeviceDB }>; + private vm_worker: Worker; private app: App; constructor(app: App) { super(); this.app = app; - const vm = init(); + this.vm_worker = new Worker("./vm_worker.ts"); + const vm = Comlink.wrap(this.vm_worker); + this.ic10vm = vm; window.VM.set(this); - this.ic10vm = vm; + this._objects = new Map(); + this._circuitHolders = new Map(); + this._networks = new Map(); - this._devices = new Map(); - this._ics = new Map(); + this.templateDBPromise = this.ic10vm.getTemplateDatabase(); - this.dbPromise = import("../../../data/database.json", { - assert: { type: "json" }, - }) as Promise<{ default: DeviceDB }>; + this.templateDBPromise.then((db) => this.setupTemplateDatabase(db)); - this.dbPromise.then((module) => - this.setupDeviceDatabase(module.default as DeviceDB), - ); - - this.updateDevices(); + this.updateObjects(); this.updateCode(); } - get devices() { - return this._devices; + get objects() { + return this._objects; } - get deviceIds() { - const ids = Array.from(this.ic10vm.devices); + get objectIds() { + const ids = Array.from(this._objects.keys()); ids.sort(); return ids; } - get ics() { - return this._ics; + get circuitHolders() { + return this._circuitHolders; } - get icIds() { - return Array.from(this.ic10vm.ics); - } - - get networks() { - return Array.from(this.ic10vm.networks); - } - - get defaultNetwork() { - return this.ic10vm.defaultNetwork; - } - - get activeIC() { - return this._ics.get(this.app.session.activeIC); - } - - visibleDevices(source: number) { - const ids = Array.from(this.ic10vm.visibleDevices(source)); - return ids.map((id, _index) => this._devices.get(id)!); - } - - visibleDeviceIds(source: number) { - const ids = Array.from(this.ic10vm.visibleDevices(source)); + get circuitHolderIds() { + const ids = Array.from(this._circuitHolders.keys()); + ids.sort(); return ids; } - updateDevices() { - var update_flag = false; - const removedDevices = []; - const device_ids = this.ic10vm.devices; - for (const id of device_ids) { - if (!this._devices.has(id)) { - this._devices.set(id, cachedDeviceRef(this.ic10vm.getDevice(id)!)); - update_flag = true; - } - } - for (const id of this._devices.keys()) { - if (!device_ids.includes(id)) { - this._devices.delete(id); - update_flag = true; - removedDevices.push(id); - } - } + get networks() { + const ids = Array.from(this._networks.keys()); + ids.sort(); + return ids; + } - 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); - update_flag = true; + get defaultNetwork() { + return this._default_network; + } + + get activeIC() { + return this._circuitHolders.get(this.app.session.activeIC); + } + + async visibleDevices(source: number) { + const visDevices = await this.ic10vm.visibleDevices(source); + const ids = Array.from(visDevices); + ids.sort(); + return ids.map((id, _index) => this._objects.get(id)!); + } + + async visibleDeviceIds(source: number) { + const visDevices = await this.ic10vm.visibleDevices(source); + const ids = Array.from(visDevices); + ids.sort(); + return ids; + } + + async updateObjects() { + let updateFlag = false; + const removedObjects = []; + const objectIds = await this.ic10vm.objects; + const frozenObjects = await this.ic10vm.freezeObjects(objectIds); + const updatedObjects = []; + + for (const [index, id] of objectIds.entries()) { + if (!this._objects.has(id)) { + this._objects.set(id, frozenObjects[index]); + updateFlag = true; + updatedObjects.push(id); + } else { + if (!structuralEqual(this._objects.get(id), frozenObjects[index])) { + this._objects.set(id, frozenObjects[index]); + updatedObjects.push(id); + updateFlag = true; } } } - for (const id of this._ics.keys()) { - if (!this._devices.has(id)) { - this._ics.delete(id); - update_flag = true; + for (const id of this._objects.keys()) { + if (!objectIds.includes(id)) { + this._objects.delete(id); + updateFlag = true; + removedObjects.push(id); } } - if (update_flag) { - const ids = Array.from(device_ids); + for (const [id, obj] of this._objects) { + if (typeof obj.obj_info.socketed_ic !== "undefined") { + if (!this._circuitHolders.has(id)) { + this._circuitHolders.set(id, obj); + updateFlag = true; + if (!updatedObjects.includes(id)) { + updatedObjects.push(id); + } + } + } else { + if (this._circuitHolders.has(id)) { + updateFlag = true; + if (!updatedObjects.includes(id)) { + updatedObjects.push(id); + } + this._circuitHolders.delete(id); + } + } + } + + for (const id of this._circuitHolders.keys()) { + if (!this._objects.has(id)) { + this._circuitHolders.delete(id); + updateFlag = true; + if (!removedObjects.includes(id)) { + removedObjects.push(id); + } + } + } + + if (updateFlag) { + const ids = Array.from(updatedObjects); ids.sort(); this.dispatchEvent( - new CustomEvent("vm-devices-update", { + new CustomEvent("vm-objects-update", { detail: ids, }), ); - if (removedDevices.length > 0) { + if (removedObjects.length > 0) { this.dispatchEvent( - new CustomEvent("vm-devices-removed", { - detail: removedDevices, + new CustomEvent("vm-objects-removed", { + detail: removedObjects, }), ); } @@ -178,20 +191,24 @@ class VirtualMachine extends EventTarget { } } - updateCode() { + async updateCode() { const progs = this.app.session.programs; for (const id of progs.keys()) { const attempt = Date.now().toString(16); - const ic = this._ics.get(id); + const circuitHolder = this._circuitHolders.get(id); const prog = progs.get(id); - if (ic && prog && ic.code !== prog) { + if ( + circuitHolder && + prog && + circuitHolder.obj_info.source_code !== prog + ) { try { console.time(`CompileProgram_${id}_${attempt}`); - this.ics.get(id)!.setCodeInvalid(progs.get(id)!); - const compiled = this.ics.get(id)?.program!; - this.app.session.setProgramErrors(id, compiled.errors); + await this.ic10vm.setCodeInvalid(id, progs.get(id)!); + const errors = await this.ic10vm.getCompileErrors(id); + this.app.session.setProgramErrors(id, errors); this.dispatchEvent( - new CustomEvent("vm-device-modified", { detail: id }), + new CustomEvent("vm-object-modified", { detail: id }), ); } catch (err) { this.handleVmError(err); @@ -203,65 +220,65 @@ class VirtualMachine extends EventTarget { this.update(false); } - step() { + async step() { const ic = this.activeIC; if (ic) { try { - ic.step(false); + await this.ic10vm.stepProgrammable(ic.obj_info.id, false); } catch (err) { this.handleVmError(err); } this.update(); this.dispatchEvent( - new CustomEvent("vm-run-ic", { detail: this.activeIC!.id }), + new CustomEvent("vm-run-ic", { detail: this.activeIC!.obj_info.id }), ); } } - run() { + async run() { const ic = this.activeIC; if (ic) { try { - ic.run(false); + await this.ic10vm.runProgrammable(ic.obj_info.id, false); } catch (err) { this.handleVmError(err); } this.update(); this.dispatchEvent( - new CustomEvent("vm-run-ic", { detail: this.activeIC!.id }), + new CustomEvent("vm-run-ic", { detail: this.activeIC!.obj_info.id }), ); } } - reset() { + async reset() { const ic = this.activeIC; if (ic) { - ic.reset(); - this.update(); + await this.ic10vm.resetProgrammable(ic.obj_info.id); + await this.update(); } } - update(save: boolean = true) { - this.updateDevices(); - this.ic10vm.lastOperationModified.forEach((id, _index, _modifiedIds) => { - if (this.devices.has(id)) { - this.dispatchEvent( - new CustomEvent("vm-device-modified", { detail: id }), - ); + async update(save: boolean = true) { + await this.updateObjects(); + const lastModified = await this.ic10vm.lastOperationModified; + lastModified.forEach((id, _index, _modifiedIds) => { + if (this.objects.has(id)) { + this.updateDevice(id, false); } }, this); - this.updateDevice(this.activeIC.id, save); + this.updateDevice(this.activeIC.obj_info.id, false); if (save) this.app.session.save(); } updateDevice(id: number, save: boolean = true) { - const device = this._devices.get(id); - device.dirty = true; + const device = this._objects.get(id); this.dispatchEvent( - new CustomEvent("vm-device-modified", { detail: device.id }), + new CustomEvent("vm-device-modified", { detail: device.obj_info.id }), ); - if (typeof device.ic !== "undefined") { - this.app.session.setActiveLine(device.id, device.ip!); + if (typeof device.obj_info.socketed_ic !== "undefined") { + const ic = this._objects.get(device.obj_info.socketed_ic); + const ip = ic.obj_info.circuit?.instruction_pointer; + this.app.session.setActiveLine(device.obj_info.id, ip); } if (save) this.app.session.save(); } @@ -278,15 +295,15 @@ class VirtualMachine extends EventTarget { this.dispatchEvent(new CustomEvent("vm-message", { detail: message })); } - changeDeviceID(oldID: number, newID: number): boolean { + async changeDeviceID(oldID: number, newID: number): Promise { try { - this.ic10vm.changeDeviceId(oldID, newID); + await this.ic10vm.changeDeviceId(oldID, newID); if (this.app.session.activeIC === oldID) { this.app.session.activeIC = newID; } - this.updateDevices(); + await this.updateObjects(); this.dispatchEvent( - new CustomEvent("vm-device-id-change", { + new CustomEvent("vm-object-id-change", { detail: { old: oldID, new: newID, @@ -301,11 +318,11 @@ class VirtualMachine extends EventTarget { } } - setRegister(index: number, val: number): boolean { + async setRegister(index: number, val: number): Promise { const ic = this.activeIC!; try { - ic.setRegister(index, val); - this.updateDevice(ic.id); + await this.ic10vm.setRegister(ic.obj_info.id, index, val); + this.updateDevice(ic.obj_info.id); return true; } catch (err) { this.handleVmError(err); @@ -313,11 +330,11 @@ class VirtualMachine extends EventTarget { } } - setStack(addr: number, val: number): boolean { + async setStack(addr: number, val: number): Promise { const ic = this.activeIC!; try { - ic!.setStack(addr, val); - this.updateDevice(ic.id); + await this.ic10vm.setMemory(ic.obj_info.id, addr, val); + this.updateDevice(ic.obj_info.id); return true; } catch (err) { this.handleVmError(err); @@ -325,14 +342,12 @@ class VirtualMachine extends EventTarget { } } - setDeviceName(id: number, name: string): boolean { - const device = this._devices.get(id); - if (device) { + async setObjectName(id: number, name: string): Promise { + const obj = this._objects.get(id); + if (obj) { try { - device.setName(name); - this.dispatchEvent( - new CustomEvent("vm-device-modified", { detail: id }), - ); + await this.ic10vm.setObjectName(obj.obj_info.id, name); + this.updateDevice(obj.obj_info.id); this.app.session.save(); return true; } catch (e) { @@ -342,18 +357,18 @@ class VirtualMachine extends EventTarget { return false; } - setDeviceField( + async setObjectField( id: number, field: LogicType, val: number, force?: boolean, - ): boolean { + ): Promise { force = force ?? false; - const device = this._devices.get(id); - if (device) { + const obj = this._objects.get(id); + if (obj) { try { - device.setField(field, val, force); - this.updateDevice(device.id); + await this.ic10vm.setLogicField(obj.obj_info.id, field, val, force); + this.updateDevice(obj.obj_info.id); return true; } catch (err) { this.handleVmError(err); @@ -362,19 +377,25 @@ class VirtualMachine extends EventTarget { return false; } - setDeviceSlotField( + async setObjectSlotField( id: number, slot: number, field: LogicSlotType, val: number, force?: boolean, - ): boolean { + ): Promise { force = force ?? false; - const device = this._devices.get(id); - if (device) { + const obj = this._objects.get(id); + if (obj) { try { - device.setSlotField(slot, field, val, force); - this.updateDevice(device.id); + await this.ic10vm.setSlotLogicField( + obj.obj_info.id, + field, + slot, + val, + force, + ); + this.updateDevice(obj.obj_info.id); return true; } catch (err) { this.handleVmError(err); @@ -383,16 +404,16 @@ class VirtualMachine extends EventTarget { return false; } - setDeviceConnection( + async setDeviceConnection( id: number, conn: number, val: number | undefined, - ): boolean { - const device = this._devices.get(id); + ): Promise { + const device = this._objects.get(id); if (typeof device !== "undefined") { try { - this.ic10vm.setDeviceConnection(id, conn, val); - this.updateDevice(device.id); + await this.ic10vm.setDeviceConnection(id, conn, val); + this.updateDevice(device.obj_info.id); return true; } catch (err) { this.handleVmError(err); @@ -401,12 +422,16 @@ class VirtualMachine extends EventTarget { return false; } - setDevicePin(id: number, pin: number, val: number | undefined): boolean { - const device = this._devices.get(id); + async setDevicePin( + id: number, + pin: number, + val: number | undefined, + ): Promise { + const device = this._objects.get(id); if (typeof device !== "undefined") { try { - this.ic10vm.setPin(id, pin, val); - this.updateDevice(device.id); + await this.ic10vm.setPin(id, pin, val); + this.updateDevice(device.obj_info.id); return true; } catch (err) { this.handleVmError(err); @@ -415,22 +440,23 @@ class VirtualMachine extends EventTarget { return false; } - setupDeviceDatabase(db: DeviceDB) { - this.db = db; - console.log("Loaded Device Database", this.db); + setupTemplateDatabase(db: TemplateDatabase) { + this.templateDB = db; + console.log("Loaded Template Database", this.templateDB); this.dispatchEvent( - new CustomEvent("vm-device-db-loaded", { detail: this.db }), + new CustomEvent("vm-template-db-loaded", { detail: this.templateDB }), ); } - addDeviceFromTemplate(template: DeviceTemplate): boolean { + async addObjectFromFrozen(frozen: FrozenObject): Promise { try { - console.log("adding device", template); - const id = this.ic10vm.addDeviceFromTemplate(template); - this._devices.set(id, cachedDeviceRef(this.ic10vm.getDevice(id)!)); - const device_ids = this.ic10vm.devices; + console.log("adding device", frozen); + const id = await this.ic10vm.addObjectFromFrozen(frozen); + const refrozen = await this.ic10vm.freezeObject(id); + this._objects.set(id, refrozen); + const device_ids = await this.ic10vm.objects; this.dispatchEvent( - new CustomEvent("vm-devices-update", { + new CustomEvent("vm-objects-update", { detail: Array.from(device_ids), }), ); @@ -442,10 +468,10 @@ class VirtualMachine extends EventTarget { } } - removeDevice(id: number): boolean { + async removeDevice(id: number): Promise { try { - this.ic10vm.removeDevice(id); - this.updateDevices(); + await this.ic10vm.removeDevice(id); + await this.updateObjects(); return true; } catch (err) { this.handleVmError(err); @@ -453,17 +479,18 @@ class VirtualMachine extends EventTarget { } } - setDeviceSlotOccupant( + async setSlotOccupant( id: number, index: number, - template: SlotOccupantTemplate, - ): boolean { - const device = this._devices.get(id); + frozen: FrozenObject, + quantity: number, + ): Promise { + const device = this._objects.get(id); if (typeof device !== "undefined") { try { - console.log("setting slot occupant", template); - this.ic10vm.setSlotOccupant(id, index, template); - this.updateDevice(device.id); + console.log("setting slot occupant", frozen); + await this.ic10vm.setSlotOccupant(id, index, frozen, quantity); + this.updateDevice(device.obj_info.id); return true; } catch (err) { this.handleVmError(err); @@ -472,12 +499,12 @@ class VirtualMachine extends EventTarget { return false; } - removeDeviceSlotOccupant(id: number, index: number): boolean { - const device = this._devices.get(id); + async removeSlotOccupant(id: number, index: number): Promise { + const device = this._objects.get(id); if (typeof device !== "undefined") { try { this.ic10vm.removeSlotOccupant(id, index); - this.updateDevice(device.id); + this.updateDevice(device.obj_info.id); return true; } catch (err) { this.handleVmError(err); @@ -486,25 +513,25 @@ class VirtualMachine extends EventTarget { return false; } - saveVMState(): FrozenVM { + async saveVMState(): Promise { return this.ic10vm.saveVMState(); } - restoreVMState(state: FrozenVM) { + async restoreVMState(state: FrozenVM) { try { - this.ic10vm.restoreVMState(state); - this._devices = new Map(); - this._ics = new Map(); - this.updateDevices(); + await this.ic10vm.restoreVMState(state); + this._objects = new Map(); + this._circuitHolders = new Map(); + await this.updateObjects(); } catch (e) { this.handleVmError(e); } } - getPrograms() { - const programs: [number, string][] = Array.from(this._ics.entries()).map( - ([id, ic]) => [id, ic.code], - ); + getPrograms() : [number, string][] { + const programs: [number, string][] = Array.from( + this._circuitHolders.entries(), + ).map(([id, ic]) => [id, ic.obj_info.source_code]); return programs; } } diff --git a/www/src/ts/virtual_machine/stack.ts b/www/src/ts/virtual_machine/stack.ts index 5d72b85..4adb81d 100644 --- a/www/src/ts/virtual_machine/stack.ts +++ b/www/src/ts/virtual_machine/stack.ts @@ -46,7 +46,7 @@ export class VMICStack extends VMActiveICMixin(BaseElement) { return html`
- ${this.stack?.map((val, index) => { + ${this.memory?.map((val, index) => { return html`
diff --git a/www/src/ts/virtual_machine/vm_worker.ts b/www/src/ts/virtual_machine/vm_worker.ts new file mode 100644 index 0000000..70615c8 --- /dev/null +++ b/www/src/ts/virtual_machine/vm_worker.ts @@ -0,0 +1,41 @@ +import { VMRef, init } from "ic10emu_wasm"; +import type { StationpediaPrefab, ObjectTemplate } from "ic10emu_wasm"; + +import * as Comlink from "comlink"; + +import * as json_database from "../../../data/database.json" with { type: "json" }; + +export interface PrefabDatabase { + prefabs: { [key in StationpediaPrefab]: ObjectTemplate}; + reagents: { + [key: string]: { + Hash: number; + Unit: string; + Sources?: { + [key in StationpediaPrefab]: number; + }; + }; + }; + prefabsByHash: { + [key: number]: StationpediaPrefab; + }; + structures: StationpediaPrefab[]; + devices: StationpediaPrefab[]; + items: StationpediaPrefab[]; + logicableItems: StationpediaPrefab[]; + suits: StationpediaPrefab[]; + circuitHolders: StationpediaPrefab[]; +} + +const prefab_database = json_database as unknown as PrefabDatabase; + +const vm: VMRef = init(); + +const template_database = new Map( + Object.entries(prefab_database.prefabsByHash).map(([hash, name]) => { + return [parseInt(hash), prefab_database.prefabs[name]]; + }), +); +vm.importTemplateDatabase(template_database); + +Comlink.expose(vm);