refactor to make navigation easier

This commit is contained in:
Rachel Powers
2024-04-18 15:03:17 -07:00
parent bd5695c415
commit 3f65360811
7 changed files with 2015 additions and 1997 deletions

927
ic10emu/src/device.rs Normal file
View File

@@ -0,0 +1,927 @@
use crate::{
grammar::{LogicType, ReagentMode, SlotLogicType}, interpreter::{ICError, ICState}, network::{CableConnectionType, Connection}, vm::VM
};
use std::collections::HashMap;
use serde::{Deserialize, Serialize};
use strum_macros::{AsRefStr, EnumIter, EnumString};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum FieldType {
Read,
Write,
ReadWrite,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LogicField {
pub field_type: FieldType,
pub value: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SlotOccupant {
pub id: u32,
pub prefab_hash: i32,
pub quantity: u32,
pub max_quantity: u32,
pub sorting_class: SortingClass,
pub damage: f64,
fields: HashMap<SlotLogicType, LogicField>,
}
impl SlotOccupant {
pub fn from_template<F>(template: SlotOccupantTemplate, id_fn: F) -> Self
where
F: FnOnce() -> u32,
{
let mut fields = template.fields;
SlotOccupant {
id: template.id.unwrap_or_else(id_fn),
prefab_hash: fields
.remove(&SlotLogicType::PrefabHash)
.map(|field| field.value as i32)
.unwrap_or(0),
quantity: fields
.remove(&SlotLogicType::Quantity)
.map(|field| field.value as u32)
.unwrap_or(1),
max_quantity: fields
.remove(&SlotLogicType::MaxQuantity)
.map(|field| field.value as u32)
.unwrap_or(1),
damage: fields
.remove(&SlotLogicType::Damage)
.map(|field| field.value)
.unwrap_or(0.0),
sorting_class: fields
.remove(&SlotLogicType::SortingClass)
.map(|field| (field.value as u32).into())
.unwrap_or(SortingClass::Default),
fields,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SlotOccupantTemplate {
pub id: Option<u32>,
pub fields: HashMap<SlotLogicType, LogicField>,
}
impl SlotOccupant {
pub fn new(id: u32, prefab_hash: i32) -> Self {
SlotOccupant {
id,
prefab_hash,
quantity: 1,
max_quantity: 1,
damage: 0.0,
sorting_class: SortingClass::Default,
fields: HashMap::new(),
}
}
/// chainable constructor
pub fn with_quantity(mut self, quantity: u32) -> Self {
self.quantity = quantity;
self
}
/// chainable constructor
pub fn with_max_quantity(mut self, max_quantity: u32) -> Self {
self.max_quantity = max_quantity;
self
}
/// chainable constructor
pub fn with_damage(mut self, damage: f64) -> Self {
self.damage = damage;
self
}
/// chainable constructor
pub fn with_fields(mut self, fields: HashMap<SlotLogicType, LogicField>) -> Self {
self.fields.extend(fields);
self
}
/// chainable constructor
pub fn get_fields(&self) -> HashMap<SlotLogicType, LogicField> {
self.fields.clone()
}
pub fn set_field(
&mut self,
field: SlotLogicType,
val: f64,
force: bool,
) -> Result<(), ICError> {
if let Some(logic) = self.fields.get_mut(&field) {
match logic.field_type {
FieldType::ReadWrite | FieldType::Write => {
logic.value = val;
Ok(())
}
_ => {
if force {
logic.value = val;
Ok(())
} else {
Err(ICError::ReadOnlyField(field.to_string()))
}
}
}
} else if force {
self.fields.insert(
field,
LogicField {
field_type: FieldType::ReadWrite,
value: val,
},
);
Ok(())
} else {
Err(ICError::ReadOnlyField(field.to_string()))
}
}
pub fn can_logic_read(&self, field: SlotLogicType) -> bool {
if let Some(logic) = self.fields.get(&field) {
matches!(logic.field_type, FieldType::Read | FieldType::ReadWrite)
} else {
false
}
}
pub fn can_logic_write(&self, field: SlotLogicType) -> bool {
if let Some(logic) = self.fields.get(&field) {
matches!(logic.field_type, FieldType::Write | FieldType::ReadWrite)
} else {
false
}
}
}
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct Slot {
pub typ: SlotType,
pub occupant: Option<SlotOccupant>,
}
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct SlotTemplate {
pub typ: SlotType,
pub occupant: Option<SlotOccupantTemplate>,
}
impl Slot {
pub fn new(typ: SlotType) -> Self {
Slot {
typ,
occupant: None,
}
}
pub fn with_occupant(typ: SlotType, occupant: SlotOccupant) -> Self {
Slot {
typ,
occupant: Some(occupant),
}
}
pub fn get_fields(&self) -> HashMap<SlotLogicType, LogicField> {
let mut copy = self
.occupant
.as_ref()
.map(|occupant| occupant.get_fields())
.unwrap_or_default();
copy.insert(
SlotLogicType::Occupied,
LogicField {
field_type: FieldType::Read,
value: if self.occupant.is_some() { 1.0 } else { 0.0 },
},
);
copy.insert(
SlotLogicType::OccupantHash,
LogicField {
field_type: FieldType::Read,
value: self
.occupant
.as_ref()
.map(|occupant| occupant.prefab_hash as f64)
.unwrap_or(0.0),
},
);
copy.insert(
SlotLogicType::Quantity,
LogicField {
field_type: FieldType::Read,
value: self
.occupant
.as_ref()
.map(|occupant| occupant.quantity as f64)
.unwrap_or(0.0),
},
);
copy.insert(
SlotLogicType::Damage,
LogicField {
field_type: FieldType::Read,
value: self
.occupant
.as_ref()
.map(|occupant| occupant.damage)
.unwrap_or(0.0),
},
);
copy.insert(
SlotLogicType::Class,
LogicField {
field_type: FieldType::Read,
value: self.typ as u32 as f64,
},
);
copy.insert(
SlotLogicType::MaxQuantity,
LogicField {
field_type: FieldType::Read,
value: self
.occupant
.as_ref()
.map(|occupant| occupant.max_quantity as f64)
.unwrap_or(0.0),
},
);
copy.insert(
SlotLogicType::PrefabHash,
LogicField {
field_type: FieldType::Read,
value: self
.occupant
.as_ref()
.map(|occupant| occupant.prefab_hash as f64)
.unwrap_or(0.0),
},
);
copy.insert(
SlotLogicType::SortingClass,
LogicField {
field_type: FieldType::Read,
value: self
.occupant
.as_ref()
.map(|occupant| occupant.sorting_class as u32 as f64)
.unwrap_or(0.0),
},
);
copy.insert(
SlotLogicType::ReferenceId,
LogicField {
field_type: FieldType::Read,
value: self
.occupant
.as_ref()
.map(|occupant| occupant.id as f64)
.unwrap_or(0.0),
},
);
copy
}
pub fn get_field(&self, field: SlotLogicType) -> f64 {
let fields = self.get_fields();
fields
.get(&field)
.map(|field| match field.field_type {
FieldType::Read | FieldType::ReadWrite => field.value,
_ => 0.0,
})
.unwrap_or(0.0)
}
pub fn can_logic_read(&self, field: SlotLogicType) -> bool {
match field {
SlotLogicType::Pressure | SlotLogicType::Temperature | SlotLogicType::Volume => {
matches!(
self.typ,
SlotType::GasCanister | SlotType::LiquidCanister | SlotType::LiquidBottle
)
}
SlotLogicType::Charge | SlotLogicType::ChargeRatio => {
matches!(self.typ, SlotType::Battery)
}
SlotLogicType::Open => matches!(
self.typ,
SlotType::Helmet | SlotType::Tool | SlotType::Appliance
),
SlotLogicType::Lock => matches!(self.typ, SlotType::Helmet),
SlotLogicType::FilterType => matches!(self.typ, SlotType::GasFilter),
_ => {
if let Some(occupant) = self.occupant.as_ref() {
occupant.can_logic_read(field)
} else {
false
}
}
}
}
pub fn can_logic_write(&self, field: SlotLogicType) -> bool {
match field {
SlotLogicType::Open => matches!(
self.typ,
SlotType::Helmet
| SlotType::GasCanister
| SlotType::LiquidCanister
| SlotType::LiquidBottle
),
SlotLogicType::On => matches!(
self.typ,
SlotType::Helmet | SlotType::Tool | SlotType::Appliance
),
SlotLogicType::Lock => matches!(self.typ, SlotType::Helmet),
_ => {
if let Some(occupant) = self.occupant.as_ref() {
occupant.can_logic_write(field)
} else {
false
}
}
}
}
pub fn set_field(
&mut self,
field: SlotLogicType,
val: f64,
force: bool,
) -> Result<(), ICError> {
if matches!(
field,
SlotLogicType::Occupied
| SlotLogicType::OccupantHash
| SlotLogicType::Quantity
| SlotLogicType::MaxQuantity
| SlotLogicType::Class
| SlotLogicType::PrefabHash
| SlotLogicType::SortingClass
| SlotLogicType::ReferenceId
) {
return Err(ICError::ReadOnlyField(field.to_string()));
}
if let Some(occupant) = self.occupant.as_mut() {
occupant.set_field(field, val, force)
} else {
Err(ICError::SlotNotOccupied)
}
}
}
#[derive(
Debug,
Default,
Clone,
Copy,
PartialEq,
Eq,
Hash,
strum_macros::Display,
EnumString,
EnumIter,
AsRefStr,
Serialize,
Deserialize,
)]
#[strum(serialize_all = "PascalCase")]
pub enum SortingClass {
#[default]
Default = 0,
Kits = 1,
Tools = 2,
Resources,
Food = 4,
Clothing,
Appliances,
Atmospherics,
Storage = 8,
Ores,
Ices,
}
impl From<u32> for SortingClass {
fn from(value: u32) -> Self {
match value {
1 => Self::Kits,
2 => Self::Tools,
3 => Self::Resources,
4 => Self::Food,
5 => Self::Clothing,
6 => Self::Appliances,
7 => Self::Atmospherics,
8 => Self::Storage,
9 => Self::Ores,
10 => Self::Ices,
_ => Self::Default,
}
}
}
#[derive(
Debug,
Default,
Clone,
Copy,
PartialEq,
Eq,
Hash,
strum_macros::Display,
EnumString,
EnumIter,
AsRefStr,
Serialize,
Deserialize,
)]
#[strum(serialize_all = "PascalCase")]
pub enum SlotType {
Helmet = 1,
Suit = 2,
Back,
GasFilter = 4,
GasCanister,
MotherBoard,
Circuitboard,
DataDisk = 8,
Organ,
Ore,
Plant,
Uniform,
Entity,
Battery,
Egg,
Belt = 16,
Tool,
Appliance,
Ingot,
Torpedo,
Cartridge,
AccessCard,
Magazine,
Circuit = 24,
Bottle,
ProgrammableChip,
Glasses,
CreditCard,
DirtCanister,
SensorProcessingUnit,
LiquidCanister,
LiquidBottle = 32,
Wreckage,
SoundCartridge,
DrillHead,
ScanningHead,
Flare,
Blocked,
#[default]
None = 0,
}
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct Prefab {
pub name: String,
pub hash: i32,
}
impl Prefab {
pub fn new(name: &str) -> Self {
Prefab {
name: name.to_owned(),
hash: const_crc32::crc32(name.as_bytes()) as i32,
}
}
}
#[derive(Debug, Default)]
pub struct Device {
pub id: u32,
pub name: Option<String>,
pub name_hash: Option<i32>,
pub prefab: Option<Prefab>,
pub slots: Vec<Slot>,
pub reagents: HashMap<ReagentMode, HashMap<i32, f64>>,
pub ic: Option<u32>,
pub connections: Vec<Connection>,
pub fields: HashMap<LogicType, LogicField>,
}
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct DeviceTemplate {
pub id: Option<u32>,
pub name: Option<String>,
pub prefab_name: Option<String>,
pub slots: Vec<SlotTemplate>,
// pub reagents: HashMap<ReagentMode, HashMap<i32, f64>>,
pub connections: Vec<Connection>,
pub fields: HashMap<LogicType, LogicField>,
}
impl Device {
pub fn new(id: u32) -> Self {
Device {
id,
name: None,
name_hash: None,
prefab: None,
fields: HashMap::new(),
slots: Vec::new(),
reagents: HashMap::new(),
ic: None,
connections: vec![Connection::CableNetwork {
net: None,
typ: CableConnectionType::default(),
}],
}
}
pub fn with_ic(id: u32, ic: u32) -> Self {
let mut device = Device::new(id);
device.ic = Some(ic);
device.connections = vec![
Connection::CableNetwork {
net: None,
typ: CableConnectionType::Data,
},
Connection::CableNetwork {
net: None,
typ: CableConnectionType::Power,
},
];
device.prefab = Some(Prefab::new("StructureCircuitHousing"));
device.fields.extend(vec![
(
LogicType::Setting,
LogicField {
field_type: FieldType::ReadWrite,
value: 0.0,
},
),
(
LogicType::RequiredPower,
LogicField {
field_type: FieldType::Read,
value: 0.0,
},
),
(
LogicType::PrefabHash,
LogicField {
field_type: FieldType::Read,
value: -128473777.0,
},
),
]);
device.slots.push(Slot::with_occupant(
SlotType::ProgrammableChip,
// -744098481 = ItemIntegratedCircuit10
SlotOccupant::new(ic, -744098481),
));
device
}
pub fn get_fields(&self, vm: &VM) -> HashMap<LogicType, LogicField> {
let mut copy = self.fields.clone();
if let Some(ic_id) = &self.ic {
let ic = vm.ics.get(ic_id).expect("our own ic to exist").borrow();
copy.insert(
LogicType::LineNumber,
LogicField {
field_type: FieldType::ReadWrite,
value: ic.ip as f64,
},
);
copy.insert(
LogicType::Error,
LogicField {
field_type: FieldType::Read,
value: match ic.state {
ICState::Error(_) => 1.0,
_ => 0.0,
},
},
);
}
if self.has_power_state() {
copy.insert(
LogicType::Power,
LogicField {
field_type: FieldType::Read,
value: if self.has_power_connection() {
1.0
} else {
0.0
},
},
);
}
copy.insert(
LogicType::ReferenceId,
LogicField {
field_type: FieldType::Read,
value: self.id as f64,
},
);
copy
}
pub fn get_network_id(&self, connection: usize) -> Result<u32, ICError> {
if connection >= self.connections.len() {
Err(ICError::ConnectionIndexOutOfRange(
connection,
self.connections.len(),
))
} else if let Connection::CableNetwork {
net: network_id, ..
} = self.connections[connection]
{
if let Some(network_id) = network_id {
Ok(network_id)
} else {
Err(ICError::NetworkNotConnected(connection))
}
} else {
Err(ICError::NotACableConnection(connection))
}
}
pub fn can_logic_read(&self, field: LogicType) -> bool {
match field {
LogicType::ReferenceId => true,
LogicType::LineNumber | LogicType::Error if self.ic.is_some() => true,
LogicType::Power if self.has_power_state() => true,
_ => {
if let Some(logic) = self.fields.get(&field) {
matches!(logic.field_type, FieldType::Read | FieldType::ReadWrite)
} else {
false
}
}
}
}
pub fn can_logic_write(&self, field: LogicType) -> bool {
match field {
LogicType::ReferenceId => false,
LogicType::LineNumber if self.ic.is_some() => true,
_ => {
if let Some(logic) = self.fields.get(&field) {
matches!(logic.field_type, FieldType::Write | FieldType::ReadWrite)
} else {
false
}
}
}
}
pub fn can_slot_logic_read(&self, field: SlotLogicType, slot: usize) -> bool {
if self.slots.is_empty() {
return false;
}
let Some(slot) = self.slots.get(slot) else {
return false;
};
slot.can_logic_read(field)
}
pub fn can_slot_logic_write(&self, field: SlotLogicType, slot: usize) -> bool {
if self.slots.is_empty() {
return false;
}
let Some(slot) = self.slots.get(slot) else {
return false;
};
slot.can_logic_write(field)
}
pub fn get_field(&self, typ: LogicType, vm: &VM) -> Result<f64, ICError> {
if typ == LogicType::LineNumber && self.ic.is_some() {
if let Ok(ic) = vm
.ics
.get(&self.ic.unwrap())
.ok_or_else(|| ICError::UnknownDeviceID(self.ic.unwrap() as f64))?
.try_borrow()
{
Ok(ic.ip as f64)
} else {
// HACK: the game succeeds in getting the correct line number
// when reading it's own IC, but we'll panic trying to do it here
// this is worked around in internal_step so just return 0 here
Ok(0.0)
}
} else if let Some(field) = self.get_fields(vm).get(&typ) {
if field.field_type == FieldType::Read || field.field_type == FieldType::ReadWrite {
Ok(field.value)
} else {
Err(ICError::WriteOnlyField(typ.to_string()))
}
} else {
Err(ICError::DeviceHasNoField(typ.to_string()))
}
}
pub fn set_field(
&mut self,
typ: LogicType,
val: f64,
vm: &VM,
force: bool,
) -> Result<(), ICError> {
if typ == LogicType::ReferenceId
|| (typ == LogicType::Error && self.ic.is_some())
|| (typ == LogicType::Power && self.has_power_state())
{
Err(ICError::ReadOnlyField(typ.to_string()))
} else if typ == LogicType::LineNumber && self.ic.is_some() {
// try borrow to set ip, we should only fail if the ic is in use aka is is *our* ic
// in game trying to set your own ic's LineNumber appears to be a Nop so this is fine.
if let Ok(mut ic) = vm
.ics
.get(&self.ic.unwrap())
.ok_or_else(|| ICError::UnknownDeviceID(self.ic.unwrap() as f64))?
.try_borrow_mut()
{
ic.ip = val as u32;
}
Ok(())
} else if let Some(field) = self.fields.get_mut(&typ) {
if field.field_type == FieldType::Write
|| field.field_type == FieldType::ReadWrite
|| force
{
field.value = val;
Ok(())
} else {
Err(ICError::ReadOnlyField(typ.to_string()))
}
} else if force {
self.fields.insert(
typ,
LogicField {
field_type: FieldType::ReadWrite,
value: val,
},
);
Ok(())
} else {
Err(ICError::DeviceHasNoField(typ.to_string()))
}
}
pub fn get_slot_field(&self, index: f64, typ: SlotLogicType, vm: &VM) -> Result<f64, ICError> {
let slot = self
.slots
.get(index as usize)
.ok_or(ICError::SlotIndexOutOfRange(index))?;
if slot.typ == SlotType::ProgrammableChip
&& slot.occupant.is_some()
&& self.ic.is_some()
&& typ == SlotLogicType::LineNumber
{
// try borrow to get ip, we should only fail if the ic is in us aka is is *our* ic
if let Ok(ic) = vm
.ics
.get(&self.ic.unwrap())
.ok_or_else(|| ICError::UnknownDeviceID(self.ic.unwrap() as f64))?
.try_borrow()
{
Ok(ic.ip as f64)
} else {
// HACK: the game succeeds in getting the correct line number
// when reading it's own IC, but we'll panic trying to do it here
// this is worked around in internal_step so just return 0 here
Ok(0.0)
}
} else {
Ok(slot.get_field(typ))
}
}
pub fn get_slot_fields(
&self,
index: f64,
vm: &VM,
) -> Result<HashMap<SlotLogicType, LogicField>, ICError> {
let slot = self
.slots
.get(index as usize)
.ok_or(ICError::SlotIndexOutOfRange(index))?;
let mut fields = slot.get_fields();
if slot.typ == SlotType::ProgrammableChip && slot.occupant.is_some() && self.ic.is_some() {
// try borrow to get ip, we should only fail if the ic is in us aka is is *our* ic
if let Ok(ic) = vm
.ics
.get(&self.ic.unwrap())
.ok_or_else(|| ICError::UnknownDeviceID(self.ic.unwrap() as f64))?
.try_borrow()
{
fields.insert(
SlotLogicType::LineNumber,
LogicField {
field_type: FieldType::ReadWrite,
value: ic.ip as f64,
},
);
} else {
// HACK: the game succeeds in getting the correct line number
// when reading it's own IC, but we'll panic trying to do it here
// this is worked around in internal_step so just return 0 here
fields.insert(
SlotLogicType::LineNumber,
LogicField {
field_type: FieldType::ReadWrite,
value: 0.0,
},
);
}
}
Ok(fields)
}
pub fn set_slot_field(
&mut self,
index: f64,
typ: SlotLogicType,
val: f64,
vm: &VM,
force: bool,
) -> Result<(), ICError> {
let slot = self
.slots
.get_mut(index as usize)
.ok_or(ICError::SlotIndexOutOfRange(index))?;
if slot.typ == SlotType::ProgrammableChip
&& slot.occupant.is_some()
&& self.ic.is_some()
&& typ == SlotLogicType::LineNumber
{
// try borrow to set ip, we shoudl only fail if the ic is in us aka is is *our* ic
// in game trying to set your own ic's LineNumber appears to be a Nop so this is fine.
if let Ok(mut ic) = vm
.ics
.get(&self.ic.unwrap())
.ok_or_else(|| ICError::UnknownDeviceID(self.ic.unwrap() as f64))?
.try_borrow_mut()
{
ic.ip = val as u32;
}
Ok(())
} else {
slot.set_field(typ, val, force)
}
}
pub fn get_slot(&self, index: f64) -> Result<&Slot, ICError> {
self.slots
.get(index as usize)
.ok_or(ICError::SlotIndexOutOfRange(index))
}
pub fn get_reagent(&self, rm: &ReagentMode, reagent: f64) -> f64 {
if let Some(mode) = self.reagents.get(rm) {
if let Some(val) = mode.get(&(reagent as i32)) {
return *val;
}
}
0.0
}
pub fn set_name(&mut self, name: &str) {
self.name_hash = Some(const_crc32::crc32(name.as_bytes()) as i32);
self.name = Some(name.to_owned());
}
pub fn has_power_state(&self) -> bool {
self.connections.iter().any(|conn| {
matches!(
conn,
Connection::CableNetwork {
typ: CableConnectionType::Power | CableConnectionType::PowerAndData,
..
}
)
})
}
pub fn has_power_connection(&self) -> bool {
self.connections.iter().any(|conn| {
matches!(
conn,
Connection::CableNetwork {
net: Some(_),
typ: CableConnectionType::Power | CableConnectionType::PowerAndData,
}
)
})
}
}

View File

@@ -12,7 +12,7 @@ use itertools::Itertools;
use time::format_description;
use crate::grammar::{self, ParseError};
use crate::{grammar::{self, ParseError}, vm::VM};
use thiserror::Error;
@@ -458,7 +458,7 @@ impl IC {
}
/// processes one line of the contained program
pub fn step(&mut self, vm: &crate::VM, advance_ip_on_err: bool) -> Result<bool, LineError> {
pub fn step(&mut self, vm: &VM, advance_ip_on_err: bool) -> Result<bool, LineError> {
// TODO: handle sleep
self.state = ICState::Running;
let line = self.ip;
@@ -472,7 +472,7 @@ impl IC {
}
}
fn internal_step(&mut self, vm: &crate::VM, advance_ip_on_err: bool) -> Result<(), ICError> {
fn internal_step(&mut self, vm: &VM, advance_ip_on_err: bool) -> Result<(), ICError> {
use grammar::*;
use ICError::*;
@@ -1793,7 +1793,7 @@ impl IC {
indirection,
target,
} = reg.as_register(this, inst, 1)?;
let val = vm.random.clone().borrow_mut().next_f64();
let val = vm.random_f64();
this.set_register(indirection, target, val)?;
Ok(())
}

File diff suppressed because it is too large Load Diff

177
ic10emu/src/network.rs Normal file
View File

@@ -0,0 +1,177 @@
use std::collections::HashSet;
use serde::{Deserialize, Serialize};
use thiserror::Error;
use itertools::Itertools;
#[derive(Debug, Default, Clone, Copy, Serialize, Deserialize)]
pub enum CableConnectionType {
Power,
Data,
#[default]
PowerAndData,
}
#[derive(Debug, Default, Clone, Copy, Serialize, Deserialize)]
pub enum Connection {
CableNetwork {
net: Option<u32>,
typ: CableConnectionType,
},
#[default]
Other,
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum ConnectionType {
Pipe,
Power,
Data,
Chute,
Elevator,
PipeLiquid,
LandingPad,
LaunchPad,
PowerAndData,
#[serde(other)]
#[default]
None,
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum ConnectionRole {
Input,
Input2,
Output,
Output2,
Waste,
#[serde(other)]
#[default]
None,
}
impl Connection {
#[allow(dead_code)]
fn from(typ: ConnectionType, _role: ConnectionRole) -> Self {
match typ {
ConnectionType::None
| ConnectionType::Chute
| ConnectionType::Pipe
| ConnectionType::Elevator
| ConnectionType::LandingPad
| ConnectionType::LaunchPad
| ConnectionType::PipeLiquid => Self::Other,
ConnectionType::Data => Self::CableNetwork {
net: None,
typ: CableConnectionType::Data,
},
ConnectionType::Power => Self::CableNetwork {
net: None,
typ: CableConnectionType::Power,
},
ConnectionType::PowerAndData => Self::CableNetwork {
net: None,
typ: CableConnectionType::PowerAndData,
},
}
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Network {
pub devices: HashSet<u32>,
pub power_only: HashSet<u32>,
pub channels: [f64; 8],
}
impl Default for Network {
fn default() -> Self {
Network {
devices: HashSet::new(),
power_only: HashSet::new(),
channels: [f64::NAN; 8],
}
}
}
#[derive(Debug, Error)]
pub enum NetworkError {
#[error("")]
ChannelIndexOutOfRange,
}
impl Network {
pub fn contains(&self, id: &u32) -> bool {
self.devices.contains(id) || self.power_only.contains(id)
}
pub fn contains_all(&self, ids: &[u32]) -> bool {
ids.iter().all(|id| self.contains(id))
}
pub fn contains_data(&self, id: &u32) -> bool {
self.devices.contains(id)
}
pub fn contains_all_data(&self, ids: &[u32]) -> bool {
ids.iter().all(|id| self.contains_data(id))
}
pub fn contains_power(&self, id: &u32) -> bool {
self.power_only.contains(id)
}
pub fn contains_all_power(&self, ids: &[u32]) -> bool {
ids.iter().all(|id| self.contains_power(id))
}
pub fn data_visible(&self, source: &u32) -> Vec<u32> {
if self.contains_data(source) {
self.devices
.iter()
.filter(|id| id != &source)
.copied()
.collect_vec()
} else {
Vec::new()
}
}
pub fn add_data(&mut self, id: u32) -> bool {
self.devices.insert(id)
}
pub fn add_power(&mut self, id: u32) -> bool {
self.power_only.insert(id)
}
pub fn remove_all(&mut self, id: u32) -> bool {
self.devices.remove(&id) || self.power_only.remove(&id)
}
pub fn remove_data(&mut self, id: u32) -> bool {
self.devices.remove(&id)
}
pub fn remove_power(&mut self, id: u32) -> bool {
self.devices.remove(&id)
}
pub fn set_channel(&mut self, chan: usize, val: f64) -> Result<f64, NetworkError> {
if chan > 7 {
Err(NetworkError::ChannelIndexOutOfRange)
} else {
let last = self.channels[chan];
self.channels[chan] = val;
Ok(last)
}
}
pub fn get_channel(&self, chan: usize) -> Result<f64, NetworkError> {
if chan > 7 {
Err(NetworkError::ChannelIndexOutOfRange)
} else {
Ok(self.channels[chan])
}
}
}

880
ic10emu/src/vm.rs Normal file
View File

@@ -0,0 +1,880 @@
use crate::{
grammar::{BatchMode, LogicType, SlotLogicType},
interpreter::{self, ICError, LineError},
device::{Device, DeviceTemplate, Prefab, Slot, SlotOccupant, SlotType},
network::{CableConnectionType, Connection, Network},
};
use std::{cell::RefCell, collections::{HashMap, HashSet}, rc::Rc};
use itertools::Itertools;
use serde::{Deserialize, Serialize};
use thiserror::Error;
#[derive(Error, Debug, Serialize, Deserialize)]
pub enum VMError {
#[error("device with id '{0}' does not exist")]
UnknownId(u32),
#[error("ic with id '{0}' does not exist")]
UnknownIcId(u32),
#[error("device with id '{0}' does not have a ic slot")]
NoIC(u32),
#[error("ic encountered an error: {0}")]
ICError(#[from] ICError),
#[error("ic encountered an error: {0}")]
LineError(#[from] LineError),
#[error("invalid network id {0}")]
InvalidNetwork(u32),
#[error("device {0} not visible to device {1} (not on the same networks)")]
DeviceNotVisible(u32, u32),
#[error("a device with id {0} already exists")]
IdInUse(u32),
#[error("device(s) with ids {0:?} already exist")]
IdsInUse(Vec<u32>),
#[error("atempt to use a set of id's with duplicates: id(s) {0:?} exsist more than once")]
DuplicateIds(Vec<u32>),
}
#[derive(Debug)]
pub struct VM {
pub ics: HashMap<u32, Rc<RefCell<interpreter::IC>>>,
pub devices: HashMap<u32, Rc<RefCell<Device>>>,
pub networks: HashMap<u32, Rc<RefCell<Network>>>,
pub default_network: u32,
id_space: IdSpace,
network_id_gen: IdSpace,
random: Rc<RefCell<crate::rand_mscorlib::Random>>,
/// list of device id's touched on the last operation
operation_modified: RefCell<Vec<u32>>,
}
impl Default for VM {
fn default() -> Self {
Self::new()
}
}
impl VM {
pub fn new() -> Self {
let id_gen = IdSpace::default();
let mut network_id_space = IdSpace::default();
let default_network = Rc::new(RefCell::new(Network::default()));
let mut networks = HashMap::new();
let default_network_key = network_id_space.next();
networks.insert(default_network_key, default_network);
let mut vm = VM {
ics: HashMap::new(),
devices: HashMap::new(),
networks,
default_network: default_network_key,
id_space: id_gen,
network_id_gen: network_id_space,
random: Rc::new(RefCell::new(crate::rand_mscorlib::Random::new())),
operation_modified: RefCell::new(Vec::new()),
};
let _ = vm.add_ic(None);
vm
}
fn new_device(&mut self) -> Device {
Device::new(self.id_space.next())
}
fn new_ic(&mut self) -> (Device, interpreter::IC) {
let id = self.id_space.next();
let ic_id = self.id_space.next();
let ic = interpreter::IC::new(ic_id, id);
let device = Device::with_ic(id, ic_id);
(device, ic)
}
pub fn random_f64(&self) -> f64 {
self.random.borrow_mut().next_f64()
}
pub fn add_device(&mut self, network: Option<u32>) -> Result<u32, VMError> {
if let Some(n) = &network {
if !self.networks.contains_key(n) {
return Err(VMError::InvalidNetwork(*n));
}
}
let mut device = self.new_device();
if let Some(first_network) = device.connections.iter_mut().find_map(|c| {
if let Connection::CableNetwork {
net,
typ: CableConnectionType::Data | CableConnectionType::PowerAndData,
} = c
{
Some(net)
} else {
None
}
}) {
first_network.replace(if let Some(network) = network {
network
} else {
self.default_network
});
}
let id = device.id;
let first_data_network = device
.connections
.iter()
.enumerate()
.find_map(|(index, conn)| match conn {
Connection::CableNetwork {
typ: CableConnectionType::Data | CableConnectionType::PowerAndData,
..
} => Some(index),
_ => None,
});
self.devices.insert(id, Rc::new(RefCell::new(device)));
if let Some(first_data_network) = first_data_network {
let _ = self.set_device_connection(
id,
first_data_network,
if let Some(network) = network {
Some(network)
} else {
Some(self.default_network)
},
);
}
Ok(id)
}
pub fn add_ic(&mut self, network: Option<u32>) -> Result<u32, VMError> {
if let Some(n) = &network {
if !self.networks.contains_key(n) {
return Err(VMError::InvalidNetwork(*n));
}
}
let (mut device, ic) = self.new_ic();
if let Some(first_network) = device.connections.iter_mut().find_map(|c| {
if let Connection::CableNetwork {
net,
typ: CableConnectionType::Data | CableConnectionType::PowerAndData,
} = c
{
Some(net)
} else {
None
}
}) {
first_network.replace(if let Some(network) = network {
network
} else {
self.default_network
});
}
let id = device.id;
let ic_id = ic.id;
let first_data_network = device
.connections
.iter()
.enumerate()
.find_map(|(index, conn)| match conn {
Connection::CableNetwork {
typ: CableConnectionType::Data | CableConnectionType::PowerAndData,
..
} => Some(index),
_ => None,
});
self.devices.insert(id, Rc::new(RefCell::new(device)));
self.ics.insert(ic_id, Rc::new(RefCell::new(ic)));
if let Some(first_data_network) = first_data_network {
let _ = self.set_device_connection(
id,
first_data_network,
if let Some(network) = network {
Some(network)
} else {
Some(self.default_network)
},
);
}
Ok(id)
}
pub fn add_device_from_template(&mut self, template: DeviceTemplate) -> Result<u32, VMError> {
for conn in &template.connections {
if let Connection::CableNetwork { net: Some(net), .. } = conn {
if !self.networks.contains_key(net) {
return Err(VMError::InvalidNetwork(*net));
}
}
}
// collect the id's this template wants to use
let mut to_use_ids = template
.slots
.iter()
.filter_map(|slot| slot.occupant.as_ref().and_then(|occupant| occupant.id))
.collect_vec();
let device_id = {
// attempt to use all the idea at once to error without needing to clean up.
if let Some(id) = &template.id {
to_use_ids.push(*id);
self.id_space.use_ids(&to_use_ids)?;
*id
} else {
self.id_space.use_ids(&to_use_ids)?;
self.id_space.next()
}
};
let name_hash = template
.name
.as_ref()
.map(|name| const_crc32::crc32(name.as_bytes()) as i32);
let slots = template
.slots
.into_iter()
.map(|slot| Slot {
typ: slot.typ,
occupant: slot
.occupant
.map(|occupant| SlotOccupant::from_template(occupant, || self.id_space.next())),
})
.collect_vec();
let ic = slots
.iter()
.find_map(|slot| {
if slot.typ == SlotType::ProgrammableChip && slot.occupant.is_some() {
Some(slot.occupant.clone()).flatten()
} else {
None
}
})
.map(|occupant| occupant.id);
if let Some(ic_id) = &ic {
let chip = interpreter::IC::new(*ic_id, device_id);
self.ics.insert(*ic_id, Rc::new(RefCell::new(chip)));
}
let fields = template.fields;
let device = Device {
id: device_id,
name: template.name,
name_hash,
prefab: template.prefab_name.map(|name| Prefab::new(&name)),
slots,
// reagents: template.reagents,
reagents: HashMap::new(),
ic,
connections: template.connections,
fields,
};
device.connections.iter().for_each(|conn| {
if let Connection::CableNetwork {
net: Some(net),
typ,
} = conn
{
if let Some(network) = self.networks.get(net) {
match typ {
CableConnectionType::Power => {
network.borrow_mut().add_power(device_id);
}
_ => {
network.borrow_mut().add_data(device_id);
}
}
}
}
});
self.devices
.insert(device_id, Rc::new(RefCell::new(device)));
Ok(device_id)
}
pub fn add_network(&mut self) -> u32 {
let next_id = self.network_id_gen.next();
self.networks
.insert(next_id, Rc::new(RefCell::new(Network::default())));
next_id
}
pub fn get_default_network(&self) -> Rc<RefCell<Network>> {
self.networks.get(&self.default_network).cloned().unwrap()
}
pub fn get_network(&self, id: u32) -> Option<Rc<RefCell<Network>>> {
self.networks.get(&id).cloned()
}
pub fn remove_ic(&mut self, id: u32) {
if self.ics.remove(&id).is_some() {
self.devices.remove(&id);
}
}
pub fn change_device_id(&mut self, old_id: u32, new_id: u32) -> Result<(), VMError> {
self.id_space.use_id(new_id)?;
let device = self
.devices
.remove(&old_id)
.ok_or(VMError::UnknownId(old_id))?;
device.borrow_mut().id = new_id;
self.devices.insert(new_id, device);
self.ics.iter().for_each(|(_id, ic)| {
if let Ok(mut ic_ref) = ic.try_borrow_mut() {
ic_ref.pins.iter_mut().for_each(|pin| {
if pin.is_some_and(|d| d == old_id) {
pin.replace(new_id);
}
})
}
});
self.networks.iter().for_each(|(_net_id, net)| {
if let Ok(mut net_ref) = net.try_borrow_mut() {
if net_ref.devices.remove(&old_id) {
net_ref.devices.insert(new_id);
}
}
});
self.id_space.free_id(old_id);
Ok(())
}
/// Set program code if it's valid
pub fn set_code(&self, id: u32, code: &str) -> Result<bool, VMError> {
let device = self
.devices
.get(&id)
.ok_or(VMError::UnknownId(id))?
.borrow();
let ic_id = *device.ic.as_ref().ok_or(VMError::NoIC(id))?;
let mut ic = self
.ics
.get(&ic_id)
.ok_or(VMError::UnknownIcId(ic_id))?
.borrow_mut();
let new_prog = interpreter::Program::try_from_code(code)?;
ic.program = new_prog;
ic.ip = 0;
ic.code = code.to_string();
Ok(true)
}
/// Set program code and translate invalid lines to Nop, collecting errors
pub fn set_code_invalid(&self, id: u32, code: &str) -> Result<bool, VMError> {
let device = self
.devices
.get(&id)
.ok_or(VMError::UnknownId(id))?
.borrow();
let ic_id = *device.ic.as_ref().ok_or(VMError::NoIC(id))?;
let mut ic = self
.ics
.get(&ic_id)
.ok_or(VMError::UnknownIcId(ic_id))?
.borrow_mut();
let new_prog = interpreter::Program::from_code_with_invalid(code);
ic.program = new_prog;
ic.ip = 0;
ic.code = code.to_string();
Ok(true)
}
/// returns a list of device ids modified in the last operations
pub fn last_operation_modified(&self) -> Vec<u32> {
self.operation_modified.borrow().clone()
}
pub fn step_ic(&self, id: u32, advance_ip_on_err: bool) -> Result<bool, VMError> {
self.operation_modified.borrow_mut().clear();
let ic_id = {
let device = self.devices.get(&id).ok_or(VMError::UnknownId(id))?;
let device_ref = device.borrow();
let ic_id = device_ref.ic.as_ref().ok_or(VMError::NoIC(id))?;
*ic_id
};
self.set_modified(id);
let ic = self
.ics
.get(&ic_id)
.ok_or(VMError::UnknownIcId(ic_id))?
.clone();
ic.borrow_mut().ic = 0;
let result = ic.borrow_mut().step(self, advance_ip_on_err)?;
Ok(result)
}
/// returns true if executed 128 lines, false if returned early.
pub fn run_ic(&self, id: u32, ignore_errors: bool) -> Result<bool, VMError> {
self.operation_modified.borrow_mut().clear();
let device = self.devices.get(&id).ok_or(VMError::UnknownId(id))?.clone();
let ic_id = *device.borrow().ic.as_ref().ok_or(VMError::NoIC(id))?;
let ic = self
.ics
.get(&ic_id)
.ok_or(VMError::UnknownIcId(ic_id))?
.clone();
ic.borrow_mut().ic = 0;
self.set_modified(id);
for _i in 0..128 {
if let Err(err) = ic.borrow_mut().step(self, ignore_errors) {
if !ignore_errors {
return Err(err.into());
}
}
if let interpreter::ICState::Yield = ic.borrow().state {
return Ok(false);
} else if let interpreter::ICState::Sleep(_then, _sleep_for) = ic.borrow().state {
return Ok(false);
}
}
ic.borrow_mut().state = interpreter::ICState::Yield;
Ok(true)
}
pub fn set_modified(&self, id: u32) {
self.operation_modified.borrow_mut().push(id);
}
pub fn reset_ic(&self, id: u32) -> Result<bool, VMError> {
let device = self.devices.get(&id).ok_or(VMError::UnknownId(id))?.clone();
let ic_id = *device.borrow().ic.as_ref().ok_or(VMError::NoIC(id))?;
let ic = self
.ics
.get(&ic_id)
.ok_or(VMError::UnknownIcId(ic_id))?
.clone();
ic.borrow_mut().ic = 0;
ic.borrow_mut().reset();
Ok(true)
}
pub fn get_device(&self, id: u32) -> Option<Rc<RefCell<Device>>> {
self.devices.get(&id).cloned()
}
pub fn batch_device(
&self,
source: u32,
prefab_hash: f64,
name: Option<f64>,
) -> impl Iterator<Item = &Rc<RefCell<Device>>> {
self.devices
.iter()
.filter(move |(id, device)| {
device
.borrow()
.fields
.get(&LogicType::PrefabHash)
.is_some_and(|f| f.value == prefab_hash)
&& (name.is_none()
|| name == device.borrow().name_hash.as_ref().map(|hash| *hash as f64))
&& self.devices_on_same_network(&[source, **id])
})
.map(|(_, d)| d)
}
pub fn get_device_same_network(&self, source: u32, other: u32) -> Option<Rc<RefCell<Device>>> {
if self.devices_on_same_network(&[source, other]) {
self.get_device(other)
} else {
None
}
}
pub fn get_network_channel(&self, id: u32, channel: usize) -> Result<f64, ICError> {
let network = self.networks.get(&id).ok_or(ICError::BadNetworkId(id))?;
if !(0..8).contains(&channel) {
Err(ICError::ChannelIndexOutOfRange(channel))
} else {
Ok(network.borrow().channels[channel])
}
}
pub fn set_network_channel(&self, id: u32, channel: usize, val: f64) -> Result<(), ICError> {
let network = self.networks.get(&(id)).ok_or(ICError::BadNetworkId(id))?;
if !(0..8).contains(&channel) {
Err(ICError::ChannelIndexOutOfRange(channel))
} else {
network.borrow_mut().channels[channel] = val;
Ok(())
}
}
pub fn devices_on_same_network(&self, ids: &[u32]) -> bool {
for net in self.networks.values() {
if net.borrow().contains_all_data(ids) {
return true;
}
}
false
}
/// return a vecter with the device ids the source id can see via it's connected networks
pub fn visible_devices(&self, source: u32) -> Vec<u32> {
self.networks
.values()
.filter_map(|net| {
if net.borrow().contains_data(&source) {
Some(net.borrow().data_visible(&source))
} else {
None
}
})
.concat()
}
pub fn set_pin(&self, id: u32, pin: usize, val: Option<u32>) -> Result<bool, VMError> {
let Some(device) = self.devices.get(&id) else {
return Err(VMError::UnknownId(id));
};
if let Some(other_device) = val {
if !self.devices.contains_key(&other_device) {
return Err(VMError::UnknownId(other_device));
}
if !self.devices_on_same_network(&[id, other_device]) {
return Err(VMError::DeviceNotVisible(other_device, id));
}
}
if !(0..6).contains(&pin) {
Err(ICError::PinIndexOutOfRange(pin).into())
} else {
let Some(ic_id) = device.borrow().ic else {
return Err(VMError::NoIC(id));
};
self.ics.get(&ic_id).unwrap().borrow_mut().pins[pin] = val;
Ok(true)
}
}
pub fn set_device_connection(
&self,
id: u32,
connection: usize,
target_net: Option<u32>,
) -> Result<bool, VMError> {
let Some(device) = self.devices.get(&id) else {
return Err(VMError::UnknownId(id));
};
if connection >= device.borrow().connections.len() {
let conn_len = device.borrow().connections.len();
return Err(ICError::ConnectionIndexOutOfRange(connection, conn_len).into());
}
{
// scope this borrow
let connections = &device.borrow().connections;
let Connection::CableNetwork { net, typ } = &connections[connection] else {
return Err(ICError::NotACableConnection(connection).into());
};
// remove from current network
if let Some(net) = net {
if let Some(network) = self.networks.get(net) {
// if there is no other connection to this network
if connections
.iter()
.filter(|conn| {
matches!(conn, Connection::CableNetwork {
net: Some(other_net),
typ: other_typ
} if other_net == net && (
!matches!(typ, CableConnectionType::Power) ||
matches!(other_typ, CableConnectionType::Data | CableConnectionType::PowerAndData))
)
})
.count()
== 1
{
match typ {
CableConnectionType::Power => {
network.borrow_mut().remove_power(id);
}
_ => {
network.borrow_mut().remove_data(id);
}
}
}
}
}
}
let mut device_ref = device.borrow_mut();
let connections = &mut device_ref.connections;
let Connection::CableNetwork {
ref mut net,
ref typ,
} = connections[connection]
else {
return Err(ICError::NotACableConnection(connection).into());
};
if let Some(target_net) = target_net {
if let Some(network) = self.networks.get(&target_net) {
match typ {
CableConnectionType::Power => {
network.borrow_mut().add_power(id);
}
_ => {
network.borrow_mut().add_data(id);
}
}
} else {
return Err(VMError::InvalidNetwork(target_net));
}
}
*net = target_net;
Ok(true)
}
pub fn remove_device_from_network(&self, id: u32, network_id: u32) -> Result<bool, VMError> {
if let Some(network) = self.networks.get(&network_id) {
let Some(device) = self.devices.get(&id) else {
return Err(VMError::UnknownId(id));
};
let mut device_ref = device.borrow_mut();
for conn in device_ref.connections.iter_mut() {
if let Connection::CableNetwork { net, .. } = conn {
if net.is_some_and(|id| id == network_id) {
*net = None;
}
}
}
network.borrow_mut().remove_all(id);
Ok(true)
} else {
Err(VMError::InvalidNetwork(network_id))
}
}
pub fn set_batch_device_field(
&self,
source: u32,
prefab: f64,
typ: LogicType,
val: f64,
write_readonly: bool,
) -> Result<(), ICError> {
self.batch_device(source, prefab, None)
.map(|device| {
self.set_modified(device.borrow().id);
device
.borrow_mut()
.set_field(typ, val, self, write_readonly)
})
.try_collect()
}
pub fn set_batch_device_slot_field(
&self,
source: u32,
prefab: f64,
index: f64,
typ: SlotLogicType,
val: f64,
write_readonly: bool,
) -> Result<(), ICError> {
self.batch_device(source, prefab, None)
.map(|device| {
self.set_modified(device.borrow().id);
device
.borrow_mut()
.set_slot_field(index, typ, val, self, write_readonly)
})
.try_collect()
}
pub fn set_batch_name_device_field(
&self,
source: u32,
prefab: f64,
name: f64,
typ: LogicType,
val: f64,
write_readonly: bool,
) -> Result<(), ICError> {
self.batch_device(source, prefab, Some(name))
.map(|device| {
self.set_modified(device.borrow().id);
device
.borrow_mut()
.set_field(typ, val, self, write_readonly)
})
.try_collect()
}
pub fn get_batch_device_field(
&self,
source: u32,
prefab: f64,
typ: LogicType,
mode: BatchMode,
) -> Result<f64, ICError> {
let samples = self
.batch_device(source, prefab, None)
.map(|device| device.borrow_mut().get_field(typ, self))
.filter_ok(|val| !val.is_nan())
.collect::<Result<Vec<_>, ICError>>()?;
Ok(mode.apply(&samples))
}
pub fn get_batch_name_device_field(
&self,
source: u32,
prefab: f64,
name: f64,
typ: LogicType,
mode: BatchMode,
) -> Result<f64, ICError> {
let samples = self
.batch_device(source, prefab, Some(name))
.map(|device| device.borrow_mut().get_field(typ, self))
.filter_ok(|val| !val.is_nan())
.collect::<Result<Vec<_>, ICError>>()?;
Ok(mode.apply(&samples))
}
pub fn get_batch_name_device_slot_field(
&self,
source: u32,
prefab: f64,
name: f64,
index: f64,
typ: SlotLogicType,
mode: BatchMode,
) -> Result<f64, ICError> {
let samples = self
.batch_device(source, prefab, Some(name))
.map(|device| device.borrow().get_slot_field(index, typ, self))
.filter_ok(|val| !val.is_nan())
.collect::<Result<Vec<_>, ICError>>()?;
Ok(mode.apply(&samples))
}
pub fn get_batch_device_slot_field(
&self,
source: u32,
prefab: f64,
index: f64,
typ: SlotLogicType,
mode: BatchMode,
) -> Result<f64, ICError> {
let samples = self
.batch_device(source, prefab, None)
.map(|device| device.borrow().get_slot_field(index, typ, self))
.filter_ok(|val| !val.is_nan())
.collect::<Result<Vec<_>, ICError>>()?;
Ok(mode.apply(&samples))
}
pub fn remove_device(&mut self, id: u32) -> Result<(), VMError> {
let Some(device) = self.devices.remove(&id) else {
return Err(VMError::UnknownId(id));
};
for conn in device.borrow().connections.iter() {
if let Connection::CableNetwork { net: Some(net), .. } = conn {
if let Some(network) = self.networks.get(net) {
network.borrow_mut().remove_all(id);
}
}
}
if let Some(ic_id) = device.borrow().ic {
let _ = self.ics.remove(&ic_id);
}
self.id_space.free_id(id);
Ok(())
}
}
impl BatchMode {
pub fn apply(&self, samples: &[f64]) -> f64 {
match self {
BatchMode::Sum => samples.iter().sum(),
// Both c-charp and rust return NaN for 0.0/0.0 so we're good here
BatchMode::Average => samples.iter().copied().sum::<f64>() / samples.len() as f64,
// Game uses a default of Positive INFINITY for Minimum
BatchMode::Minimum => *samples
.iter()
.min_by(|a, b| a.partial_cmp(b).unwrap())
.unwrap_or(&f64::INFINITY),
// Game uses default of NEG_INFINITY for Maximum
BatchMode::Maximum => *samples
.iter()
.max_by(|a, b| a.partial_cmp(b).unwrap())
.unwrap_or(&f64::NEG_INFINITY),
}
}
}
#[derive(Debug)]
struct IdSpace {
next: u32,
in_use: HashSet<u32>,
}
impl Default for IdSpace {
fn default() -> Self {
IdSpace::new()
}
}
impl IdSpace {
pub fn new() -> Self {
IdSpace {
next: 1,
in_use: HashSet::new(),
}
}
pub fn next(&mut self) -> u32 {
let val = self.next;
self.next += 1;
self.in_use.insert(val);
val
}
pub fn use_id(&mut self, id: u32) -> Result<(), VMError> {
if self.in_use.contains(&id) {
Err(VMError::IdInUse(id))
} else {
self.in_use.insert(id);
Ok(())
}
}
pub fn use_ids<'a, I>(&mut self, ids: I) -> Result<(), VMError>
where
I: IntoIterator<Item = &'a u32> + std::marker::Copy,
{
let mut to_use: HashSet<u32> = HashSet::new();
let mut duplicates: HashSet<u32> = HashSet::new();
let all_uniq = ids.into_iter().copied().all(|id| {
if to_use.insert(id) {
true
} else {
duplicates.insert(id);
false
}
});
if !all_uniq {
return Err(VMError::DuplicateIds(duplicates.into_iter().collect_vec()));
}
let invalid = self.in_use.intersection(&to_use).copied().collect_vec();
if !invalid.is_empty() {
return Err(VMError::IdsInUse(invalid));
}
self.in_use.extend(ids);
self.next = self.in_use.iter().max().unwrap_or(&0) + 1;
Ok(())
}
pub fn free_id(&mut self, id: u32) {
self.in_use.remove(&id);
}
}

View File

@@ -4,7 +4,8 @@ mod types;
use ic10emu::{
grammar::{LogicType, SlotLogicType},
DeviceTemplate,
device::{Device, DeviceTemplate},
vm::{VMError, VM},
};
use serde::{Deserialize, Serialize};
use types::{Registers, Stack};
@@ -22,8 +23,8 @@ extern "C" {
#[wasm_bindgen]
pub struct DeviceRef {
device: Rc<RefCell<ic10emu::Device>>,
vm: Rc<RefCell<ic10emu::VM>>,
device: Rc<RefCell<Device>>,
vm: Rc<RefCell<VM>>,
}
use thiserror::Error;
@@ -38,7 +39,7 @@ pub enum BindingError {
#[wasm_bindgen]
impl DeviceRef {
fn from_device(device: Rc<RefCell<ic10emu::Device>>, vm: Rc<RefCell<ic10emu::VM>>) -> Self {
fn from_device(device: Rc<RefCell<Device>>, vm: Rc<RefCell<VM>>) -> Self {
DeviceRef { device, vm }
}
@@ -255,12 +256,12 @@ impl DeviceRef {
.borrow()
.ic
.as_ref()
.ok_or(ic10emu::VMError::NoIC(self.device.borrow().id))?;
.ok_or(VMError::NoIC(self.device.borrow().id))?;
let vm_borrow = self.vm.borrow();
let ic = vm_borrow
.ics
.get(&ic_id)
.ok_or(ic10emu::VMError::NoIC(self.device.borrow().id))?;
.ok_or(VMError::NoIC(self.device.borrow().id))?;
let result = ic.borrow_mut().set_register(0, index, val)?;
Ok(result)
}
@@ -272,12 +273,12 @@ impl DeviceRef {
.borrow()
.ic
.as_ref()
.ok_or(ic10emu::VMError::NoIC(self.device.borrow().id))?;
.ok_or(VMError::NoIC(self.device.borrow().id))?;
let vm_borrow = self.vm.borrow();
let ic = vm_borrow
.ics
.get(&ic_id)
.ok_or(ic10emu::VMError::NoIC(self.device.borrow().id))?;
.ok_or(VMError::NoIC(self.device.borrow().id))?;
let result = ic.borrow_mut().poke(address, val)?;
Ok(result)
}
@@ -344,16 +345,16 @@ impl DeviceRef {
#[wasm_bindgen]
#[derive(Debug)]
pub struct VM {
vm: Rc<RefCell<ic10emu::VM>>,
pub struct VMRef {
vm: Rc<RefCell<VM>>,
}
#[wasm_bindgen]
impl VM {
impl VMRef {
#[wasm_bindgen(constructor)]
pub fn new() -> Self {
VM {
vm: Rc::new(RefCell::new(ic10emu::VM::new())),
VMRef {
vm: Rc::new(RefCell::new(VM::new())),
}
}
@@ -472,16 +473,16 @@ impl VM {
}
}
impl Default for VM {
impl Default for VMRef {
fn default() -> Self {
Self::new()
}
}
#[wasm_bindgen]
pub fn init() -> VM {
pub fn init() -> VMRef {
utils::set_panic_hook();
let vm = VM::new();
let vm = VMRef::new();
log!("Hello from ic10emu!");
vm
}

View File

@@ -22,11 +22,11 @@ pub struct SlotOccupant {
pub quantity: u32,
pub max_quantity: u32,
pub damage: f64,
pub fields: HashMap<ic10emu::grammar::SlotLogicType, ic10emu::LogicField>,
pub fields: HashMap<ic10emu::grammar::SlotLogicType, ic10emu::device::LogicField>,
}
impl From<&ic10emu::SlotOccupant> for SlotOccupant {
fn from(value: &ic10emu::SlotOccupant) -> Self {
impl From<&ic10emu::device::SlotOccupant> for SlotOccupant {
fn from(value: &ic10emu::device::SlotOccupant) -> Self {
SlotOccupant {
id: value.id,
prefab_hash: value.prefab_hash,
@@ -40,13 +40,13 @@ impl From<&ic10emu::SlotOccupant> for SlotOccupant {
#[derive(Debug, Default, Serialize, Deserialize)]
pub struct Slot {
pub typ: ic10emu::SlotType,
pub typ: ic10emu::device::SlotType,
pub occupant: Option<SlotOccupant>,
pub fields: HashMap<ic10emu::grammar::SlotLogicType, ic10emu::LogicField>,
pub fields: HashMap<ic10emu::grammar::SlotLogicType, ic10emu::device::LogicField>,
}
impl From<&ic10emu::Slot> for Slot {
fn from(value: &ic10emu::Slot) -> Self {
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()),