refactor(frontend): use new frozen object to fill values for baseObject mixin, proxy vm to webworker to avoid hogging main thread

This commit is contained in:
Rachel Powers
2024-05-29 00:27:28 -07:00
parent e4167efc20
commit c1f4cb23d4
27 changed files with 946 additions and 796 deletions

View File

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

View File

@@ -185,9 +185,10 @@ impl Program {
}
}
pub fn get_line(&self, line: usize) -> Result<&Instruction, ICError> {
pub fn get_line(&self, line: usize) -> Result<Instruction, ICError> {
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,

View File

@@ -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<Vec<ObjectID>>,
template_database: Option<BTreeMap<i32, ObjectTemplate>>,
template_database: RefCell<Option<BTreeMap<i32, ObjectTemplate>>>,
}
#[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<Self>) -> 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<Self>,
db: impl IntoIterator<Item = (i32, ObjectTemplate)>,
) {
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<ObjectTemplate> {
pub fn get_template(self: &Rc<Self>, prefab: Prefab) -> Option<ObjectTemplate> {
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<Self>) -> BTreeMap<i32, ObjectTemplate> {
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<Self>, id: ObjectID) -> Result<String, 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_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<Self>, id: ObjectID) -> Result<Vec<ICError>, 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<Self>, id: ObjectID) -> Result<FrozenObject, VMError> {
pub fn freeze_object(self: &Rc<Self>, id: ObjectID) -> Result<FrozenObjectFull, VMError> {
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<Self>,
ids: impl IntoIterator<Item = ObjectID>,
) -> Result<Vec<FrozenObjectFull>, 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<Self>) -> Result<FrozenVM, TemplateError> {
Ok(FrozenVM {
objects: self

View File

@@ -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<VM>,
) -> Result<Option<VMObject>, 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<f64>| 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<f64>| TemplateError::MemorySize(vec.len(), 18))?
.unwrap_or( [0.0f64; 18]),
.unwrap_or([0.0f64; 18]),
ip: obj
.circuit
.as_ref()

View File

@@ -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<Instruction, ICError> {
self.program.get_line(line)
}
fn get_compile_errors(&self) -> Vec<ICError> {
self.program.errors.clone()
}
}
impl IntegratedCircuit for ItemIntegratedCircuit10 {

View File

@@ -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<String>,
pub id: Option<ObjectID>,
pub prefab: Option<Prefab>,
pub prefab: Option<String>,
pub slots: Option<BTreeMap<u32, SlotOccupantInfo>>,
pub damage: Option<f32>,
pub device_pins: Option<BTreeMap<u32, ObjectID>>,
@@ -80,6 +80,7 @@ pub struct ObjectInfo {
pub entity: Option<EntityInfo>,
pub source_code: Option<String>,
pub circuit: Option<ICInfo>,
pub socketed_ic: Option<ObjectID>,
}
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<VM>) -> Result<Self, TemplateError> {
pub fn freeze_object(obj: &VMObject, vm: &Rc<VM>) -> Result<FrozenObjectFull, TemplateError> {
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,
})
}

View File

@@ -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<Instruction, ICError>;
/// Return a vector of any errors encountered while compiling the source with `set_source_code_with_invalid`
fn get_compile_errors(&self) -> Vec<ICError>;
}
pub trait CircuitHolder: Logicable + Storage {
@@ -497,3 +499,51 @@ impl Debug for dyn Object {
)
}
}
impl<T: CircuitHolder> SourceCode for T {
fn get_line(&self, line: usize) -> Result<Instruction, ICError> {
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<ICError> {
self.get_ic()
.and_then(|obj| {
obj.borrow()
.as_source_code()
.map(|source| source.get_compile_errors())
})
.unwrap_or_default()
}
}

View File

@@ -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<VM>,
}
use tsify::Tsify;
#[derive(Clone, Debug, Serialize, Deserialize, Tsify)]
#[tsify(into_wasm_abi, from_wasm_abi)]
pub struct TemplateDatabase(BTreeMap<i32, ObjectTemplate>);
impl IntoIterator for TemplateDatabase {
type Item = (i32, ObjectTemplate);
type IntoIter = std::collections::btree_map::IntoIter<i32, ObjectTemplate>;
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<FrozenObjectFull>);
#[derive(Clone, Debug, Serialize, Deserialize, Tsify)]
#[tsify(into_wasm_abi, from_wasm_abi)]
pub struct CompileErrors(Vec<ICError>);
#[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<ObjectID, JsError> {
#[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<ObjectID, JsError> {
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<VMObject> {
self.vm.get_object(id)
// #[wasm_bindgen(js_name = "getDevice")]
// pub fn get_object(&self, id: ObjectID) -> Option<VMObject> {
// self.vm.get_object(id)
// }
#[wasm_bindgen(js_name = "freezeObject")]
pub fn freeze_object(&self, id: ObjectID) -> Result<FrozenObjectFull, JsError> {
Ok(self.vm.freeze_object(id)?)
}
#[wasm_bindgen(js_name = "freezeDevice")]
pub fn freeze_object(&self, id: ObjectID) -> Result<FrozenObject, JsError> {
Ok(self.vm.freeze_object(id)?)
#[wasm_bindgen(js_name = "freezeObjects")]
pub fn freeze_objects(&self, ids: Vec<ObjectID>) -> Result<FrozenObjects, JsError> {
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<String, JsError> {
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<CompileErrors, JsError> {
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)?)

View File

@@ -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<ic10emu::grammar::LogicSlotType, ic10emu::device::LogicField>,
}
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<SlotOccupant>,
pub fields: BTreeMap<ic10emu::grammar::LogicSlotType, ic10emu::device::LogicField>,
}
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<Slot>);
impl<'a> FromIterator<&'a ic10emu::device::Slot> for Slots {
fn from_iter<T: IntoIterator<Item = &'a ic10emu::device::Slot>>(iter: T) -> Self {
Slots(iter.into_iter().map(|slot| slot.into()).collect_vec())
}
}
include!(concat!(env!("OUT_DIR"), "/ts_types.rs"));

View File

@@ -1,169 +0,0 @@
export type MemoryAccess = "Read" | "Write" | "ReadWrite";
export interface LogicField {
field_type: MemoryAccess;
value: number;
}
export type LogicFields = Map<LogicType, LogicField>;
export type SlotLogicFields = Map<LogicSlotType, LogicField>;
export type Reagents = Map<string, Map<number, number>>;
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<string, Alias>;
export type Defines = Map<string, number>;
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<string, number>;
}
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;
}

View File

@@ -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",

7
www/pnpm-lock.yaml generated
View File

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

View File

@@ -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<any, any>, map2: Map<any, any>): 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<EventMap extends object> = {
new (): TypedEventTargetInterface<EventMap>;
};
interface TypedEventTargetInterface<EventMap> extends EventTarget {
addEventListener<K extends keyof EventMap>(
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;
}

View File

@@ -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<T = {}> = 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<LogicType, LogicField>;
slots: Slot[];
reagents: Reagents;
reagents: Map<number, number>;
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<string, Operand> | null;
defines: Map<string, string> | null;
numPins: number | null;
pins: Map<number, ObjectID> | 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 = <T extends Constructor<LitElement>>(
export const VMObjectMixin = <T extends Constructor<LitElement>>(
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 = <T extends Constructor<LitElement>>(
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<LogicType, LogicField>;
@state() slots: Slot[];
@state() reagents: Reagents;
@state() reagents: Map<number, number>;
@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<string, Operand> | null;
@state() defines: Map<string, string> | null;
@state() numPins: number | null;
@state() pins: Map<number, ObjectID> | null;
connectedCallback(): void {
const root = super.connectedCallback();
@@ -128,7 +128,7 @@ export const VMDeviceMixin = <T extends Constructor<LitElement>>(
vm.addEventListener(
"vm-devices-removed",
this._handleDevicesRemoved.bind(this),
)
);
});
this.updateDevice();
return root;
@@ -151,23 +151,25 @@ export const VMDeviceMixin = <T extends Constructor<LitElement>>(
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 = <T extends Constructor<LitElement>>(
}
}
_handleDevicesModified(e: CustomEvent<number[]>) {
async _handleDevicesModified(e: CustomEvent<number[]>) {
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<number[]>) {
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<LogicType, LogicField> | 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 = <T extends Constructor<LitElement>>(
}
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<VMDeviceMixinInterface> & T;
return VMObjectMixinClass as Constructor<VMObjectMixinInterface> & T;
};
export const VMActiveICMixin = <T extends Constructor<LitElement>>(
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 = <T extends Constructor<LitElement>>(
_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<VMDeviceMixinInterface> & T;
return VMActiveICMixinClass as Constructor<VMObjectMixinInterface> & T;
};
export declare class VMDeviceDBMixinInterface {
deviceDB: DeviceDB;
templateDB: TemplateDatabase;
_handleDeviceDBLoad(e: CustomEvent): void;
postDBSetUpdate(): void;
}
@@ -405,8 +508,8 @@ export const VMDeviceDBMixin = <T extends Constructor<LitElement>>(
"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 = <T extends Constructor<LitElement>>(
}
_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();
}
}

View File

@@ -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`
<sl-card class="card">
<div class="controls" slot="header">
@@ -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"
>

View File

@@ -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;

View File

@@ -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`<sl-badge variant="primary" pill pulse>db</sl-badge>`);
}
const activeIc = window.VM.vm.activeIC;
activeIc?.pins?.forEach((id, index) => {
if (this.deviceID == id) {
if (this.objectID == id) {
badges.push(
html`<sl-badge variant="success" pill>d${index}</sl-badge>`,
);
@@ -154,20 +154,20 @@ export class VMDeviceCard extends VMDeviceDBMixin(VMDeviceMixin(BaseElement)) {
onerror="this.src = '${VMDeviceCard.transparentImg}'" />
</sl-tooltip>
<div class="header-name">
<sl-input id="vmDeviceCard${this.deviceID}Id" class="device-id me-1" size="small" pill value=${this.deviceID}
<sl-input id="vmDeviceCard${this.objectID}Id" class="device-id me-1" size="small" pill value=${this.objectID}
@sl-change=${this._handleChangeID}>
<span slot="prefix">Id</span>
<sl-copy-button slot="suffix" .value=${this.deviceID}></sl-copy-button>
<sl-copy-button slot="suffix" .value=${this.objectID}></sl-copy-button>
</sl-input>
<sl-input id="vmDeviceCard${this.deviceID}Name" class="device-name me-1" size="small" pill
<sl-input id="vmDeviceCard${this.objectID}Name" class="device-name me-1" size="small" pill
placeholder=${this.prefabName} value=${this.name} @sl-change=${this._handleChangeName}>
<span slot="prefix">Name</span>
<sl-copy-button slot="suffix" from="vmDeviceCard${this.deviceID}Name.value"></sl-copy-button>
<sl-copy-button slot="suffix" from="vmDeviceCard${this.objectID}Name.value"></sl-copy-button>
</sl-input>
<sl-input id="vmDeviceCard${this.deviceID}NameHash" size="small" pill class="device-name-hash me-1"
<sl-input id="vmDeviceCard${this.objectID}NameHash" size="small" pill class="device-name-hash me-1"
value="${this.nameHash}" readonly>
<span slot="prefix">Hash</span>
<sl-copy-button slot="suffix" from="vmDeviceCard${this.deviceID}NameHash.value"></sl-copy-button>
<sl-copy-button slot="suffix" from="vmDeviceCard${this.objectID}NameHash.value"></sl-copy-button>
</sl-input>
${badges.map((badge) => badge)}
</div>
@@ -183,7 +183,7 @@ export class VMDeviceCard extends VMDeviceDBMixin(VMDeviceMixin(BaseElement)) {
renderFields() {
return this.delayRenderTab(
"fields",
html`<vm-device-fields .deviceID=${this.deviceID}></vm-device-fields>`,
html`<vm-device-fields .deviceID=${this.objectID}></vm-device-fields>`,
);
}
@@ -202,7 +202,7 @@ export class VMDeviceCard extends VMDeviceDBMixin(VMDeviceMixin(BaseElement)) {
${repeat(this.slots,
(slot, index) => slot.typ + index.toString(),
(_slot, index) => html`
<vm-device-slot .deviceID=${this.deviceID} .slotIndex=${index} class-"flex flex-row max-w-lg mr-2 mb-2">
<vm-device-slot .deviceID=${this.objectID} .slotIndex=${index} class-"flex flex-row max-w-lg mr-2 mb-2">
</vm-device-slot>
`,
)}
@@ -242,7 +242,7 @@ export class VMDeviceCard extends VMDeviceDBMixin(VMDeviceMixin(BaseElement)) {
return this.delayRenderTab(
"pins",
html`<div class="pins">
<vm-device-pins .deviceID=${this.deviceID}></vm-device-pins>
<vm-device-pins .deviceID=${this.objectID}></vm-device-pins>
</div>`
);
}
@@ -295,7 +295,7 @@ export class VMDeviceCard extends VMDeviceDBMixin(VMDeviceMixin(BaseElement)) {
<sl-tab slot="nav" panel="slots">Slots</sl-tab>
<sl-tab slot="nav" panel="reagents" disabled>Reagents</sl-tab>
<sl-tab slot="nav" panel="networks">Networks</sl-tab>
<sl-tab slot="nav" panel="pins" ?disabled=${!this.device.pins}>Pins</sl-tab>
<sl-tab slot="nav" panel="pins" ?disabled=${!this.obj.pins}>Pins</sl-tab>
<sl-tab-panel name="fields" active>
${until(this.renderFields(), html`<sl-spinner></sl-spinner>`)}
@@ -318,7 +318,7 @@ export class VMDeviceCard extends VMDeviceDBMixin(VMDeviceMixin(BaseElement)) {
onerror="this.src = '${VMDeviceCard.transparentImg}'" />
<div class="flex-g">
<p><strong>Are you sure you want to remove this device?</strong></p>
<span>Id ${this.deviceID} : ${this.name ?? this.prefabName}</span>
<span>Id ${this.objectID} : ${this.name ?? this.prefabName}</span>
</div>
</div>
<div slot="footer">
@@ -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();
}

View File

@@ -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]);

View File

@@ -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` <sl-input id="${inputIdBase}${name}" key="${name}" value="${displayNumber(field.value)}" size="small"
@@ -33,7 +33,7 @@ export class VMDeviceSlot extends VMDeviceMixin(VMDeviceDBMixin(BaseElement)) {
const field = input.getAttribute("key")! as LogicType;
const val = parseNumber(input.value);
window.VM.get().then((vm) => {
if (!vm.setDeviceField(this.deviceID, field, val, true)) {
if (!vm.setObjectField(this.objectID, field, val, true)) {
input.value = this.fields.get(field).value.toString();
}
this.updateDevice();

View File

@@ -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();
}

View File

@@ -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`<img
@@ -111,7 +111,7 @@ export class VMDeviceSlot extends VMDeviceMixin(VMDeviceDBMixin(BaseElement)) {
/>`;
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<SlotModifyEvent>("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();
}

View File

@@ -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`

View File

@@ -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`<div class="pins"></div>`;
}

View File

@@ -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 } };
}

View File

@@ -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<TemplateDatabase>;
"vm-objects-update": CustomEvent<number[]>;
"vm-objects-removed": CustomEvent<number[]>;
"vm-objects-modified": CustomEvent<number>;
"vm-run-ic": CustomEvent<number>;
"vm-object-id-change": CustomEvent<{ old: number; new: number }>;
}
function cachedDeviceRef(ref: DeviceRef) {
let slotsDirty = true;
let cachedSlots: Slots = undefined;
return new Proxy<DeviceRef>(ref, {
get(target, prop, receiver) {
if (prop === "slots") {
if (typeof cachedSlots === undefined || slotsDirty) {
cachedSlots = target.slots;
slotsDirty = false;
}
return cachedSlots;
} else if (prop === "dirty") {
return slotsDirty;
}
return Reflect.get(target, prop, receiver);
},
set(target, prop, value) {
if (prop === "dirty") {
slotsDirty = value;
return true;
}
return Reflect.set(target, prop, value);
},
}) as CacheDeviceRef;
}
class VirtualMachine extends (EventTarget as TypedEventTarget<VirtualMachinEventMap>) {
ic10vm: Comlink.Remote<VMRef>;
templateDBPromise: Promise<TemplateDatabase>;
templateDB: TemplateDatabase;
class VirtualMachine extends EventTarget {
ic10vm: VMRef;
_devices: Map<number, CacheDeviceRef>;
_ics: Map<number, DeviceRef>;
private _objects: Map<number, FrozenObjectFull>;
private _circuitHolders: Map<number, FrozenObjectFull>;
private _networks: Map<number, FrozenCableNetwork>;
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<VMRef>(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<boolean> {
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<boolean> {
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<boolean> {
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<boolean> {
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<boolean> {
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<boolean> {
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<boolean> {
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<boolean> {
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<boolean> {
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<boolean> {
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<boolean> {
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<boolean> {
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<FrozenVM> {
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;
}
}

View File

@@ -46,7 +46,7 @@ export class VMICStack extends VMActiveICMixin(BaseElement) {
return html`
<sl-card class="card">
<div class="card-body">
${this.stack?.map((val, index) => {
${this.memory?.map((val, index) => {
return html`
<sl-tooltip placement="left">
<div slot="content">

View File

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