refactor(vm): use From<T> in freeze object
This commit is contained in:
@@ -41,8 +41,10 @@ pub enum VMError {
|
||||
|
||||
#[derive(Error, Debug, Serialize, Deserialize)]
|
||||
pub enum TemplateError {
|
||||
#[error("")]
|
||||
NonConformingObject(ObjectID)
|
||||
#[error("object id {0} has a non conforming set of interfaces")]
|
||||
NonConformingObject(ObjectID),
|
||||
#[error("ObjectID {0} is missing fomr the VM")]
|
||||
MissingVMObject(ObjectID),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
|
||||
@@ -105,21 +105,21 @@ pub enum ConnectionRole {
|
||||
|
||||
impl Connection {
|
||||
#[allow(dead_code)]
|
||||
pub fn from_info(typ: ConnectionType, role: ConnectionRole) -> Self {
|
||||
pub fn from_info(typ: ConnectionType, role: ConnectionRole, net: Option<ObjectID>) -> Self {
|
||||
match typ {
|
||||
ConnectionType::None => Self::None,
|
||||
ConnectionType::Data => Self::CableNetwork {
|
||||
net: None,
|
||||
net,
|
||||
typ: CableConnectionType::Data,
|
||||
role,
|
||||
},
|
||||
ConnectionType::Power => Self::CableNetwork {
|
||||
net: None,
|
||||
net,
|
||||
typ: CableConnectionType::Power,
|
||||
role,
|
||||
},
|
||||
ConnectionType::PowerAndData => Self::CableNetwork {
|
||||
net: None,
|
||||
net,
|
||||
typ: CableConnectionType::PowerAndData,
|
||||
role,
|
||||
},
|
||||
|
||||
@@ -3,16 +3,16 @@ pub mod instructions;
|
||||
pub mod object;
|
||||
|
||||
use crate::{
|
||||
device::{Device, DeviceTemplate, SlotOccupant, SlotOccupantTemplate},
|
||||
device::Device,
|
||||
errors::{ICError, VMError},
|
||||
interpreter::{self, FrozenIC, ICState, IC},
|
||||
network::{CableConnectionType, CableNetwork, Connection, FrozenNetwork},
|
||||
interpreter::ICState,
|
||||
network::{CableConnectionType, CableNetwork, Connection, ConnectionRole, FrozenNetwork},
|
||||
vm::{
|
||||
enums::script_enums::{LogicBatchMethod as BatchMode, LogicSlotType, LogicType},
|
||||
enums::script_enums::{LogicBatchMethod, LogicSlotType, LogicType},
|
||||
object::{
|
||||
templates::ObjectTemplate,
|
||||
traits::{Object, ParentSlotInfo},
|
||||
BoxedObject, ObjectID, VMObject,
|
||||
ObjectID, VMObject,
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -27,15 +27,15 @@ use serde_derive::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct VM {
|
||||
pub objects: BTreeMap<ObjectID, VMObject>,
|
||||
pub objects: RefCell<BTreeMap<ObjectID, VMObject>>,
|
||||
pub circuit_holders: RefCell<Vec<ObjectID>>,
|
||||
pub program_holders: RefCell<Vec<ObjectID>>,
|
||||
pub networks: BTreeMap<ObjectID, VMObject>,
|
||||
pub default_network_key: ObjectID,
|
||||
pub networks: RefCell<BTreeMap<ObjectID, VMObject>>,
|
||||
pub default_network_key: RefCell<ObjectID>,
|
||||
pub wireless_transmitters: RefCell<Vec<ObjectID>>,
|
||||
pub wireless_receivers: RefCell<Vec<ObjectID>>,
|
||||
id_space: IdSpace,
|
||||
network_id_space: IdSpace,
|
||||
id_space: RefCell<IdSpace>,
|
||||
network_id_space: RefCell<IdSpace>,
|
||||
random: Rc<RefCell<crate::rand_mscorlib::Random>>,
|
||||
|
||||
/// list of object id's touched on the last operation
|
||||
@@ -60,48 +60,48 @@ pub struct VMTransation {
|
||||
pub wireless_receivers: Vec<ObjectID>,
|
||||
pub id_space: IdSpace,
|
||||
pub networks: BTreeMap<ObjectID, VMTransationNetwork>,
|
||||
}
|
||||
|
||||
impl Default for VM {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
vm: Rc<VM>,
|
||||
}
|
||||
|
||||
impl VM {
|
||||
pub fn new() -> Self {
|
||||
pub fn new() -> Rc<Self> {
|
||||
let id_space = IdSpace::default();
|
||||
let mut network_id_space = IdSpace::default();
|
||||
let default_network_key = network_id_space.next();
|
||||
let default_network = VMObject::new(CableNetwork::new(default_network_key));
|
||||
let mut networks = BTreeMap::new();
|
||||
networks.insert(default_network_key, default_network);
|
||||
let networks = BTreeMap::new();
|
||||
|
||||
let mut vm = VM {
|
||||
objects: BTreeMap::new(),
|
||||
let mut vm = Rc::new(VM {
|
||||
objects: RefCell::new(BTreeMap::new()),
|
||||
circuit_holders: RefCell::new(Vec::new()),
|
||||
program_holders: RefCell::new(Vec::new()),
|
||||
networks,
|
||||
default_network_key,
|
||||
networks: RefCell::new(networks),
|
||||
default_network_key: RefCell::new(default_network_key),
|
||||
wireless_transmitters: RefCell::new(Vec::new()),
|
||||
wireless_receivers: RefCell::new(Vec::new()),
|
||||
id_space,
|
||||
network_id_space,
|
||||
id_space: RefCell::new(id_space),
|
||||
network_id_space: RefCell::new(network_id_space),
|
||||
random: Rc::new(RefCell::new(crate::rand_mscorlib::Random::new())),
|
||||
operation_modified: RefCell::new(Vec::new()),
|
||||
};
|
||||
});
|
||||
|
||||
let default_network = VMObject::new(CableNetwork::new(default_network_key), vm.clone());
|
||||
vm.networks.borrow_mut().insert(default_network_key, default_network);
|
||||
|
||||
vm
|
||||
}
|
||||
|
||||
pub fn add_device_from_template(&mut self, template: ObjectTemplate) -> Result<u32, VMError> {
|
||||
pub fn add_device_from_template(
|
||||
self: &Rc<Self>,
|
||||
template: ObjectTemplate,
|
||||
) -> Result<u32, VMError> {
|
||||
let mut transaction = VMTransation::new(self);
|
||||
|
||||
let obj_id = transaction.add_device_from_template(template)?;
|
||||
|
||||
let transation_ids = transaction.id_space.in_use_ids();
|
||||
self.id_space.use_new_ids(&transation_ids);
|
||||
self.id_space.borrow_mut().use_new_ids(&transation_ids);
|
||||
|
||||
self.objects.extend(transaction.objects);
|
||||
self.objects.borrow_mut().extend(transaction.objects);
|
||||
self.wireless_transmitters
|
||||
.borrow_mut()
|
||||
.extend(transaction.wireless_transmitters);
|
||||
@@ -117,6 +117,7 @@ impl VM {
|
||||
for (net_id, trans_net) in transaction.networks.into_iter() {
|
||||
let net = self
|
||||
.networks
|
||||
.borrow()
|
||||
.get(&net_id)
|
||||
.expect(&format!(
|
||||
"desync between vm and transation networks: {net_id}"
|
||||
@@ -135,39 +136,43 @@ impl VM {
|
||||
Ok(obj_id)
|
||||
}
|
||||
|
||||
pub fn add_network(&mut self) -> u32 {
|
||||
let next_id = self.network_id_space.next();
|
||||
pub fn add_network(self: &Rc<Self>) -> u32 {
|
||||
let next_id = self.network_id_space.borrow_mut().next();
|
||||
self.networks
|
||||
.insert(next_id, VMObject::new(CableNetwork::new(next_id)));
|
||||
.borrow_mut()
|
||||
.insert(next_id, VMObject::new(CableNetwork::new(next_id), self.clone()));
|
||||
next_id
|
||||
}
|
||||
|
||||
pub fn get_default_network(&self) -> VMObject {
|
||||
pub fn get_default_network(self: &Rc<Self>) -> VMObject {
|
||||
self.networks
|
||||
.get(&self.default_network_key)
|
||||
.borrow()
|
||||
.get(&*self.default_network_key.borrow())
|
||||
.cloned()
|
||||
.expect("default network not present")
|
||||
}
|
||||
|
||||
pub fn get_network(&self, id: u32) -> Option<VMObject> {
|
||||
self.networks.get(&id).cloned()
|
||||
pub fn get_network(self: &Rc<Self>, id: u32) -> Option<VMObject> {
|
||||
self.networks.borrow().get(&id).cloned()
|
||||
}
|
||||
|
||||
/// iterate over all object borrowing them mutably, never call unless VM is not currently
|
||||
/// stepping
|
||||
pub fn change_device_id(&mut self, old_id: u32, new_id: u32) -> Result<(), VMError> {
|
||||
if self.id_space.has_id(&new_id) {
|
||||
pub fn change_device_id(self: &Rc<Self>, old_id: u32, new_id: u32) -> Result<(), VMError> {
|
||||
if self.id_space.borrow().has_id(&new_id) {
|
||||
return Err(VMError::IdInUse(new_id));
|
||||
}
|
||||
let obj = self
|
||||
.objects
|
||||
.borrow_mut()
|
||||
.remove(&old_id)
|
||||
.ok_or(VMError::UnknownId(old_id))?;
|
||||
self.id_space.use_id(new_id)?;
|
||||
self.id_space.borrow_mut().use_id(new_id)?;
|
||||
obj.borrow_mut().set_id(new_id);
|
||||
self.objects.insert(new_id, obj);
|
||||
self.objects.borrow_mut().insert(new_id, obj);
|
||||
|
||||
self.objects
|
||||
.borrow_mut()
|
||||
.iter_mut()
|
||||
.filter_map(|(_obj_id, obj)| obj.borrow_mut().as_mut_device())
|
||||
.for_each(|device| {
|
||||
@@ -194,7 +199,7 @@ impl VM {
|
||||
*id = new_id
|
||||
}
|
||||
});
|
||||
self.networks.iter().for_each(|(_net_id, net)| {
|
||||
self.networks.borrow().iter().for_each(|(_net_id, net)| {
|
||||
let net_ref = net
|
||||
.borrow_mut()
|
||||
.as_mut_network()
|
||||
@@ -206,14 +211,15 @@ impl VM {
|
||||
net_ref.add_power(new_id);
|
||||
}
|
||||
});
|
||||
self.id_space.free_id(old_id);
|
||||
self.id_space.borrow_mut().free_id(old_id);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Set program code if it's valid
|
||||
pub fn set_code(&self, id: u32, code: &str) -> Result<bool, VMError> {
|
||||
pub fn set_code(self: &Rc<Self>, id: u32, code: &str) -> Result<bool, VMError> {
|
||||
let programmable = self
|
||||
.objects
|
||||
.borrow()
|
||||
.get(&id)
|
||||
.ok_or(VMError::UnknownId(id))?
|
||||
.borrow_mut()
|
||||
@@ -224,9 +230,10 @@ impl VM {
|
||||
}
|
||||
|
||||
/// Set program code and translate invalid lines to Nop, collecting errors
|
||||
pub fn set_code_invalid(&self, id: u32, code: &str) -> Result<bool, VMError> {
|
||||
pub fn set_code_invalid(self: &Rc<Self>, id: u32, code: &str) -> Result<bool, VMError> {
|
||||
let programmable = self
|
||||
.objects
|
||||
.borrow()
|
||||
.get(&id)
|
||||
.ok_or(VMError::UnknownId(id))?
|
||||
.borrow_mut()
|
||||
@@ -237,13 +244,14 @@ impl VM {
|
||||
}
|
||||
|
||||
/// returns a list of device ids modified in the last operations
|
||||
pub fn last_operation_modified(&self) -> Vec<u32> {
|
||||
pub fn last_operation_modified(self: &Rc<Self>) -> Vec<u32> {
|
||||
self.operation_modified.borrow().clone()
|
||||
}
|
||||
|
||||
pub fn step_programmable(&self, id: u32, advance_ip_on_err: bool) -> Result<(), VMError> {
|
||||
pub fn step_programmable(self: &Rc<Self>, id: u32, advance_ip_on_err: bool) -> Result<(), VMError> {
|
||||
let programmable = self
|
||||
.objects
|
||||
.borrow()
|
||||
.get(&id)
|
||||
.ok_or(VMError::UnknownId(id))?
|
||||
.borrow_mut()
|
||||
@@ -256,9 +264,10 @@ impl VM {
|
||||
}
|
||||
|
||||
/// returns true if executed 128 lines, false if returned early.
|
||||
pub fn run_programmable(&self, id: u32, ignore_errors: bool) -> Result<bool, VMError> {
|
||||
pub fn run_programmable(self: &Rc<Self>, id: u32, ignore_errors: bool) -> Result<bool, VMError> {
|
||||
let programmable = self
|
||||
.objects
|
||||
.borrow()
|
||||
.get(&id)
|
||||
.ok_or(VMError::UnknownId(id))?
|
||||
.borrow_mut()
|
||||
@@ -284,13 +293,14 @@ impl VM {
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
pub fn set_modified(&self, id: ObjectID) {
|
||||
pub fn set_modified(self: &Rc<Self>, id: ObjectID) {
|
||||
self.operation_modified.borrow_mut().push(id);
|
||||
}
|
||||
|
||||
pub fn reset_programmable(&self, id: ObjectID) -> Result<bool, VMError> {
|
||||
pub fn reset_programmable(self: &Rc<Self>, id: ObjectID) -> Result<bool, VMError> {
|
||||
let programmable = self
|
||||
.objects
|
||||
.borrow()
|
||||
.get(&id)
|
||||
.ok_or(VMError::UnknownId(id))?
|
||||
.borrow_mut()
|
||||
@@ -300,17 +310,18 @@ impl VM {
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
pub fn get_object(&self, id: ObjectID) -> Option<VMObject> {
|
||||
self.objects.get(&id).cloned()
|
||||
pub fn get_object(self: &Rc<Self>, id: ObjectID) -> Option<VMObject> {
|
||||
self.objects.borrow().get(&id).cloned()
|
||||
}
|
||||
|
||||
pub fn batch_device(
|
||||
&self,
|
||||
self: &Rc<Self>,
|
||||
source: ObjectID,
|
||||
prefab_hash: f64,
|
||||
name: Option<f64>,
|
||||
) -> impl Iterator<Item = &VMObject> {
|
||||
) -> impl Iterator<Item = VMObject> {
|
||||
self.objects
|
||||
.borrow()
|
||||
.iter()
|
||||
.filter(move |(id, device)| {
|
||||
device.borrow().as_device().is_some_and(|device| {
|
||||
@@ -322,9 +333,12 @@ impl VM {
|
||||
&& self.devices_on_same_network(&[source, **id])
|
||||
})
|
||||
.map(|(_, d)| d)
|
||||
.cloned()
|
||||
.collect::<Vec<_>>()
|
||||
.into_iter()
|
||||
}
|
||||
|
||||
pub fn get_device_same_network(&self, source: ObjectID, other: ObjectID) -> Option<VMObject> {
|
||||
pub fn get_device_same_network(self: &Rc<Self>, source: ObjectID, other: ObjectID) -> Option<VMObject> {
|
||||
if self.devices_on_same_network(&[source, other]) {
|
||||
self.get_object(other)
|
||||
} else {
|
||||
@@ -332,8 +346,8 @@ impl VM {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_network_channel(&self, id: u32, channel: usize) -> Result<f64, ICError> {
|
||||
let network = self.networks.get(&id).ok_or(ICError::BadNetworkId(id))?;
|
||||
pub fn get_network_channel(self: &Rc<Self>, id: u32, channel: usize) -> Result<f64, ICError> {
|
||||
let network = self.networks.borrow().get(&id).ok_or(ICError::BadNetworkId(id))?;
|
||||
if !(0..8).contains(&channel) {
|
||||
Err(ICError::ChannelIndexOutOfRange(channel))
|
||||
} else {
|
||||
@@ -349,12 +363,12 @@ impl VM {
|
||||
}
|
||||
|
||||
pub fn set_network_channel(
|
||||
&self,
|
||||
self: &Rc<Self>,
|
||||
id: ObjectID,
|
||||
channel: usize,
|
||||
val: f64,
|
||||
) -> Result<(), ICError> {
|
||||
let network = self.networks.get(&(id)).ok_or(ICError::BadNetworkId(id))?;
|
||||
let network = self.networks.borrow().get(&(id)).ok_or(ICError::BadNetworkId(id))?;
|
||||
if !(0..8).contains(&channel) {
|
||||
Err(ICError::ChannelIndexOutOfRange(channel))
|
||||
} else {
|
||||
@@ -369,8 +383,8 @@ impl VM {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn devices_on_same_network(&self, ids: &[ObjectID]) -> bool {
|
||||
for net in self.networks.values() {
|
||||
pub fn devices_on_same_network(self: &Rc<Self>, ids: &[ObjectID]) -> bool {
|
||||
for net in self.networks.borrow().values() {
|
||||
if net
|
||||
.borrow()
|
||||
.as_network()
|
||||
@@ -384,8 +398,8 @@ impl VM {
|
||||
}
|
||||
|
||||
/// return a vector with the device ids the source id can see via it's connected networks
|
||||
pub fn visible_devices(&self, source: ObjectID) -> Vec<ObjectID> {
|
||||
self.networks
|
||||
pub fn visible_devices(self: &Rc<Self>, source: ObjectID) -> Vec<ObjectID> {
|
||||
self.networks.borrow()
|
||||
.values()
|
||||
.filter_map(|net| {
|
||||
let net_ref = net.borrow().as_network().expect("non-network network");
|
||||
@@ -398,12 +412,12 @@ impl VM {
|
||||
.concat()
|
||||
}
|
||||
|
||||
pub fn set_pin(&self, id: u32, pin: usize, val: Option<ObjectID>) -> Result<bool, VMError> {
|
||||
let Some(obj) = self.objects.get(&id) else {
|
||||
pub fn set_pin(self: &Rc<Self>, id: u32, pin: usize, val: Option<ObjectID>) -> Result<bool, VMError> {
|
||||
let Some(obj) = self.objects.borrow().get(&id) else {
|
||||
return Err(VMError::UnknownId(id));
|
||||
};
|
||||
if let Some(other_device) = val {
|
||||
if !self.objects.contains_key(&other_device) {
|
||||
if !self.objects.borrow().contains_key(&other_device) {
|
||||
return Err(VMError::UnknownId(other_device));
|
||||
}
|
||||
if !self.devices_on_same_network(&[id, other_device]) {
|
||||
@@ -425,12 +439,12 @@ impl VM {
|
||||
}
|
||||
|
||||
pub fn set_device_connection(
|
||||
&self,
|
||||
self: &Rc<Self>,
|
||||
id: ObjectID,
|
||||
connection: usize,
|
||||
target_net: Option<ObjectID>,
|
||||
) -> Result<bool, VMError> {
|
||||
let Some(obj) = self.objects.get(&id) else {
|
||||
let Some(obj) = self.objects.borrow().get(&id) else {
|
||||
return Err(VMError::UnknownId(id));
|
||||
};
|
||||
let Some(device) = obj.borrow_mut().as_mut_device() else {
|
||||
@@ -443,19 +457,20 @@ impl VM {
|
||||
}
|
||||
|
||||
// scope this borrow
|
||||
let Connection::CableNetwork { net, typ } = &connections[connection] else {
|
||||
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 let Some(network) = self.networks.borrow().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
|
||||
typ: other_typ,
|
||||
..
|
||||
} if other_net == net && (
|
||||
!matches!(typ, CableConnectionType::Power) ||
|
||||
matches!(other_typ, CableConnectionType::Data | CableConnectionType::PowerAndData))
|
||||
@@ -487,12 +502,13 @@ impl VM {
|
||||
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) {
|
||||
if let Some(network) = self.networks.borrow().get(&target_net) {
|
||||
match typ {
|
||||
CableConnectionType::Power => {
|
||||
network
|
||||
@@ -518,12 +534,12 @@ impl VM {
|
||||
}
|
||||
|
||||
pub fn remove_device_from_network(
|
||||
&self,
|
||||
self: &Rc<Self>,
|
||||
id: ObjectID,
|
||||
network_id: ObjectID,
|
||||
) -> Result<bool, VMError> {
|
||||
if let Some(network) = self.networks.get(&network_id) {
|
||||
let Some(obj) = self.objects.get(&id) else {
|
||||
if let Some(network) = self.networks.borrow().get(&network_id) {
|
||||
let Some(obj) = self.objects.borrow().get(&id) else {
|
||||
return Err(VMError::UnknownId(id));
|
||||
};
|
||||
let Some(device) = obj.borrow_mut().as_mut_device() else {
|
||||
@@ -549,7 +565,7 @@ impl VM {
|
||||
}
|
||||
|
||||
pub fn set_batch_device_field(
|
||||
&self,
|
||||
self: &Rc<Self>,
|
||||
source: ObjectID,
|
||||
prefab: f64,
|
||||
typ: LogicType,
|
||||
@@ -558,7 +574,7 @@ impl VM {
|
||||
) -> Result<(), ICError> {
|
||||
self.batch_device(source, prefab, None)
|
||||
.map(|device| {
|
||||
self.set_modified(*device.borrow().get_id());
|
||||
self.set_modified(device.borrow().get_id());
|
||||
device
|
||||
.borrow_mut()
|
||||
.as_mut_device()
|
||||
@@ -570,7 +586,7 @@ impl VM {
|
||||
}
|
||||
|
||||
pub fn set_batch_device_slot_field(
|
||||
&self,
|
||||
self: &Rc<Self>,
|
||||
source: ObjectID,
|
||||
prefab: f64,
|
||||
index: f64,
|
||||
@@ -580,7 +596,7 @@ impl VM {
|
||||
) -> Result<(), ICError> {
|
||||
self.batch_device(source, prefab, None)
|
||||
.map(|device| {
|
||||
self.set_modified(*device.borrow().get_id());
|
||||
self.set_modified(device.borrow().get_id());
|
||||
device
|
||||
.borrow_mut()
|
||||
.as_mut_device()
|
||||
@@ -592,7 +608,7 @@ impl VM {
|
||||
}
|
||||
|
||||
pub fn set_batch_name_device_field(
|
||||
&self,
|
||||
self: &Rc<Self>,
|
||||
source: ObjectID,
|
||||
prefab: f64,
|
||||
name: f64,
|
||||
@@ -602,7 +618,7 @@ impl VM {
|
||||
) -> Result<(), ICError> {
|
||||
self.batch_device(source, prefab, Some(name))
|
||||
.map(|device| {
|
||||
self.set_modified(*device.borrow().get_id());
|
||||
self.set_modified(device.borrow().get_id());
|
||||
device
|
||||
.borrow_mut()
|
||||
.as_mut_device()
|
||||
@@ -614,11 +630,11 @@ impl VM {
|
||||
}
|
||||
|
||||
pub fn get_batch_device_field(
|
||||
&self,
|
||||
self: &Rc<Self>,
|
||||
source: ObjectID,
|
||||
prefab: f64,
|
||||
typ: LogicType,
|
||||
mode: BatchMode,
|
||||
mode: LogicBatchMethod,
|
||||
) -> Result<f64, ICError> {
|
||||
let samples = self
|
||||
.batch_device(source, prefab, None)
|
||||
@@ -636,12 +652,12 @@ impl VM {
|
||||
}
|
||||
|
||||
pub fn get_batch_name_device_field(
|
||||
&self,
|
||||
self: &Rc<Self>,
|
||||
source: ObjectID,
|
||||
prefab: f64,
|
||||
name: f64,
|
||||
typ: LogicType,
|
||||
mode: BatchMode,
|
||||
mode: LogicBatchMethod,
|
||||
) -> Result<f64, ICError> {
|
||||
let samples = self
|
||||
.batch_device(source, prefab, Some(name))
|
||||
@@ -659,13 +675,13 @@ impl VM {
|
||||
}
|
||||
|
||||
pub fn get_batch_name_device_slot_field(
|
||||
&self,
|
||||
self: &Rc<Self>,
|
||||
source: ObjectID,
|
||||
prefab: f64,
|
||||
name: f64,
|
||||
index: f64,
|
||||
typ: LogicSlotType,
|
||||
mode: BatchMode,
|
||||
mode: LogicBatchMethod,
|
||||
) -> Result<f64, ICError> {
|
||||
let samples = self
|
||||
.batch_device(source, prefab, Some(name))
|
||||
@@ -683,12 +699,12 @@ impl VM {
|
||||
}
|
||||
|
||||
pub fn get_batch_device_slot_field(
|
||||
&self,
|
||||
self: &Rc<Self>,
|
||||
source: ObjectID,
|
||||
prefab: f64,
|
||||
index: f64,
|
||||
typ: LogicSlotType,
|
||||
mode: BatchMode,
|
||||
mode: LogicBatchMethod,
|
||||
) -> Result<f64, ICError> {
|
||||
let samples = self
|
||||
.batch_device(source, prefab, None)
|
||||
@@ -705,15 +721,15 @@ impl VM {
|
||||
Ok(mode.apply(&samples))
|
||||
}
|
||||
|
||||
pub fn remove_object(&mut self, id: ObjectID) -> Result<(), VMError> {
|
||||
let Some(obj) = self.objects.remove(&id) else {
|
||||
pub fn remove_object(self: &Rc<Self>, id: ObjectID) -> Result<(), VMError> {
|
||||
let Some(obj) = self.objects.borrow_mut().remove(&id) else {
|
||||
return Err(VMError::UnknownId(id));
|
||||
};
|
||||
|
||||
if let Some(device) = obj.borrow().as_device() {
|
||||
for conn in device.connection_list().iter() {
|
||||
if let Connection::CableNetwork { net: Some(net), .. } = conn {
|
||||
if let Some(network) = self.networks.get(&net) {
|
||||
if let Some(network) = self.networks.borrow().get(&net) {
|
||||
network
|
||||
.borrow_mut()
|
||||
.as_mut_network()
|
||||
@@ -726,7 +742,7 @@ impl VM {
|
||||
self.circuit_holders.borrow_mut().retain(|a| *a != id);
|
||||
}
|
||||
}
|
||||
self.id_space.free_id(id);
|
||||
self.id_space.borrow_mut().free_id(id);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -735,13 +751,13 @@ impl VM {
|
||||
/// does not clean up previous object
|
||||
/// returns the id of any former occupant
|
||||
pub fn set_slot_occupant(
|
||||
&mut self,
|
||||
self: &Rc<Self>,
|
||||
id: ObjectID,
|
||||
index: usize,
|
||||
target: Option<ObjectID>,
|
||||
quantity: u32,
|
||||
) -> Result<Option<ObjectID>, VMError> {
|
||||
let Some(obj) = self.objects.get(&id) else {
|
||||
let Some(obj) = self.objects.borrow().get(&id) else {
|
||||
return Err(VMError::UnknownId(id));
|
||||
};
|
||||
let Some(storage) = obj.borrow_mut().as_mut_storage() else {
|
||||
@@ -751,7 +767,7 @@ impl VM {
|
||||
.get_slot_mut(index)
|
||||
.ok_or(ICError::SlotIndexOutOfRange(index as f64))?;
|
||||
if let Some(target) = target {
|
||||
let Some(item_obj) = self.objects.get(&target) else {
|
||||
let Some(item_obj) = self.objects.borrow().get(&target) else {
|
||||
return Err(VMError::UnknownId(id));
|
||||
};
|
||||
let Some(item) = item_obj.borrow_mut().as_mut_item() else {
|
||||
@@ -776,11 +792,11 @@ impl VM {
|
||||
|
||||
/// returns former occupant id if any
|
||||
pub fn remove_slot_occupant(
|
||||
&mut self,
|
||||
self: &Rc<Self>,
|
||||
id: ObjectID,
|
||||
index: usize,
|
||||
) -> Result<Option<ObjectID>, VMError> {
|
||||
let Some(obj) = self.objects.get(&id) else {
|
||||
let Some(obj) = self.objects.borrow().get(&id) else {
|
||||
return Err(VMError::UnknownId(id));
|
||||
};
|
||||
let Some(storage) = obj.borrow_mut().as_mut_storage() else {
|
||||
@@ -795,7 +811,7 @@ impl VM {
|
||||
Ok(last)
|
||||
}
|
||||
|
||||
pub fn save_vm_state(&self) -> FrozenVM {
|
||||
pub fn save_vm_state(self: &Rc<Self>) -> FrozenVM {
|
||||
FrozenVM {
|
||||
ics: self
|
||||
.circuit_holders
|
||||
@@ -816,7 +832,7 @@ impl VM {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn restore_vm_state(&mut self, state: FrozenVM) -> Result<(), VMError> {
|
||||
pub fn restore_vm_state(self: &Rc<Self>, state: FrozenVM) -> Result<(), VMError> {
|
||||
self.circuit_holders.clear();
|
||||
self.devices.clear();
|
||||
self.networks.clear();
|
||||
@@ -868,20 +884,22 @@ impl VM {
|
||||
}
|
||||
|
||||
impl VMTransation {
|
||||
pub fn new(vm: &VM) -> Self {
|
||||
pub fn new(vm: &Rc<VM>) -> Self {
|
||||
VMTransation {
|
||||
objects: BTreeMap::new(),
|
||||
circuit_holders: Vec::new(),
|
||||
program_holders: Vec::new(),
|
||||
default_network_key: vm.default_network_key,
|
||||
default_network_key: *vm.default_network_key.borrow(),
|
||||
wireless_transmitters: Vec::new(),
|
||||
wireless_receivers: Vec::new(),
|
||||
id_space: vm.id_space.clone(),
|
||||
id_space: vm.id_space.borrow().clone(),
|
||||
networks: vm
|
||||
.networks
|
||||
.borrow()
|
||||
.keys()
|
||||
.map(|net_id| (*net_id, VMTransationNetwork::default()))
|
||||
.collect(),
|
||||
vm: vm.clone()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -902,7 +920,7 @@ impl VMTransation {
|
||||
self.id_space.next()
|
||||
};
|
||||
|
||||
let obj = template.build(obj_id);
|
||||
let obj = template.build(obj_id, self.vm);
|
||||
|
||||
if let Some(storage) = obj.borrow_mut().as_mut_storage() {
|
||||
for (slot_index, occupant_template) in
|
||||
@@ -935,15 +953,16 @@ impl VMTransation {
|
||||
if let Connection::CableNetwork {
|
||||
net: Some(net_id),
|
||||
typ,
|
||||
role: ConnectionRole::None,
|
||||
} = conn
|
||||
{
|
||||
if let Some(net) = self.networks.get_mut(net_id) {
|
||||
if let Some(net) = self.networks.get_mut(&net_id) {
|
||||
match typ {
|
||||
CableConnectionType::Power => net.power_only.push(obj_id),
|
||||
_ => net.objects.push(obj_id),
|
||||
}
|
||||
} else {
|
||||
return Err(VMError::InvalidNetwork(*net_id));
|
||||
return Err(VMError::InvalidNetwork(net_id));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -955,19 +974,21 @@ impl VMTransation {
|
||||
}
|
||||
}
|
||||
|
||||
impl BatchMode {
|
||||
impl LogicBatchMethod {
|
||||
pub fn apply(&self, samples: &[f64]) -> f64 {
|
||||
match self {
|
||||
BatchMode::Sum => samples.iter().sum(),
|
||||
LogicBatchMethod::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,
|
||||
LogicBatchMethod::Average => {
|
||||
samples.iter().copied().sum::<f64>() / samples.len() as f64
|
||||
}
|
||||
// Game uses a default of Positive INFINITY for Minimum
|
||||
BatchMode::Minimum => *samples
|
||||
LogicBatchMethod::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
|
||||
LogicBatchMethod::Maximum => *samples
|
||||
.iter()
|
||||
.max_by(|a, b| a.partial_cmp(b).unwrap())
|
||||
.unwrap_or(&f64::NEG_INFINITY),
|
||||
@@ -1063,10 +1084,9 @@ impl IdSpace {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct FrozenVM {
|
||||
pub objects Vec<ObjectTemplate>,
|
||||
pub objects: Vec<ObjectTemplate>,
|
||||
pub circuit_holders: Vec<ObjectID>,
|
||||
pub program_holders: Vec<ObjectID>,
|
||||
pub default_network_key: ObjectID,
|
||||
@@ -1077,7 +1097,6 @@ pub struct FrozenVM {
|
||||
|
||||
impl FrozenVM {
|
||||
pub fn from_vm(vm: &VM) -> Self {
|
||||
let objects = vm.objects.iter().map()
|
||||
|
||||
let objects = vm.objects.iter().map();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
use std::{cell::RefCell, ops::{Deref, DerefMut}, rc::Rc, str::FromStr};
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
ops::{Deref, DerefMut},
|
||||
rc::Rc,
|
||||
str::FromStr,
|
||||
};
|
||||
|
||||
use macro_rules_attribute::derive;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
@@ -12,39 +17,55 @@ pub mod traits;
|
||||
|
||||
use traits::Object;
|
||||
|
||||
use crate::vm::enums::{basic_enums::Class as SlotClass, script_enums::LogicSlotType};
|
||||
use crate::vm::{
|
||||
enums::{basic_enums::Class as SlotClass, script_enums::LogicSlotType},
|
||||
VM,
|
||||
};
|
||||
|
||||
use super::enums::prefabs::StationpediaPrefab;
|
||||
|
||||
pub type ObjectID = u32;
|
||||
pub type BoxedObject = Rc<RefCell<dyn Object<ID = ObjectID>>>;
|
||||
pub type BoxedObject = Rc<RefCell<dyn Object>>;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct VMObject(BoxedObject);
|
||||
pub struct VMObject {
|
||||
obj: BoxedObject,
|
||||
vm: Rc<VM>,
|
||||
}
|
||||
|
||||
impl Deref for VMObject {
|
||||
type Target = BoxedObject;
|
||||
|
||||
#[inline(always)]
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
&self.obj
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for VMObject {
|
||||
|
||||
#[inline(always)]
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
&mut self.obj
|
||||
}
|
||||
}
|
||||
|
||||
impl VMObject {
|
||||
pub fn new<T>(val: T) -> Self
|
||||
pub fn new<T>(val: T, vm: Rc<VM>) -> Self
|
||||
where
|
||||
T: Object<ID = ObjectID> + 'static,
|
||||
T: Object + 'static,
|
||||
{
|
||||
VMObject(Rc::new(RefCell::new(val)))
|
||||
VMObject {
|
||||
obj: Rc::new(RefCell::new(val)),
|
||||
vm,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_vm(&mut self, vm: Rc<VM>) {
|
||||
self.vm = vm;
|
||||
}
|
||||
|
||||
pub fn get_vm(&self) -> &Rc<VM> {
|
||||
&self.vm
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -76,7 +76,7 @@ pub struct GenericLogicableDeviceMemoryReadable {
|
||||
pub prefab: Name,
|
||||
#[custom(object_name)]
|
||||
pub name: Name,
|
||||
pub small_grid: bool
|
||||
pub small_grid: bool,
|
||||
pub slots: Vec<Slot>,
|
||||
pub fields: BTreeMap<LogicType, LogicField>,
|
||||
pub modes: Option<BTreeMap<u32, String>>,
|
||||
|
||||
@@ -21,7 +21,7 @@ pub trait GWStructure {
|
||||
fn small_grid(&self) -> bool;
|
||||
}
|
||||
|
||||
impl<T: GWStructure> Structure for T {
|
||||
impl<T: GWStructure + Object> Structure for T {
|
||||
fn is_small_grid(&self) -> bool {
|
||||
self.small_grid()
|
||||
}
|
||||
|
||||
@@ -1,33 +1,32 @@
|
||||
macro_rules! object_trait {
|
||||
(@intf {$trait_name:ident $trt:ident}) => {
|
||||
(@intf {$trt:ident}) => {
|
||||
paste::paste! {
|
||||
#[allow(missing_docs, unused)]
|
||||
pub type [<$trt Ref>]<'a, T> = &'a dyn $trt<ID = <T as $trait_name>::ID>;
|
||||
pub type [<$trt Ref>]<'a> = &'a dyn $trt;
|
||||
#[allow(missing_docs, unused)]
|
||||
pub type [<$trt RefMut>]<'a, T> = &'a mut dyn $trt<ID = <T as $trait_name>::ID>;
|
||||
pub type [<$trt RefMut>]<'a> = &'a mut dyn $trt;
|
||||
}
|
||||
};
|
||||
(@body $trait_name:ident { $($trt:ident),* }; ) => {
|
||||
type ID: std::cmp::Ord + std::cmp::Eq + std::hash::Hash;
|
||||
fn get_id(&self) -> &Self::ID;
|
||||
fn set_id(&mut self, id: Self::ID);
|
||||
fn get_id(&self) -> crate::vm::object::ObjectID;
|
||||
fn set_id(&mut self, id: crate::vm::object::ObjectID);
|
||||
fn get_prefab(&self) -> &crate::vm::object::Name;
|
||||
fn get_mut_prefab(&mut self) -> &mut crate::vm::object::Name;
|
||||
fn get_name(&self) -> &crate::vm::object::Name;
|
||||
fn get_mut_name(&mut self) -> &mut crate::vm::object::Name;
|
||||
fn type_name(&self) -> &str;
|
||||
fn as_object(&self) -> &dyn $trait_name<ID = Self::ID>;
|
||||
fn as_mut_object(&mut self) -> &mut dyn $trait_name<ID = Self::ID>;
|
||||
fn as_object(&self) -> &dyn $trait_name;
|
||||
fn as_mut_object(&mut self) -> &mut dyn $trait_name;
|
||||
|
||||
paste::paste! {
|
||||
$(
|
||||
#[inline(always)]
|
||||
fn [<as_ $trt:snake>](&self) -> Option<[<$trt Ref>]<Self>> {
|
||||
fn [<as_ $trt:snake>](&self) -> Option<[<$trt Ref>]> {
|
||||
None
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn [<as_mut_ $trt:snake>](&mut self) -> Option<[<$trt RefMut>]<Self>> {
|
||||
fn [<as_mut_ $trt:snake>](&mut self) -> Option<[<$trt RefMut>]> {
|
||||
None
|
||||
}
|
||||
)*
|
||||
@@ -35,15 +34,15 @@ macro_rules! object_trait {
|
||||
};
|
||||
(@intf_struct $trait_name:ident { $($trt:ident),* };) => {
|
||||
paste::paste! {
|
||||
pub struct [<$trait_name Interfaces>]<'a, T: $trait_name> {
|
||||
pub struct [<$trait_name Interfaces>]<'a> {
|
||||
$(
|
||||
pub [<$trt:snake>]: Option<[<$trt Ref>]<'a, T>>,
|
||||
pub [<$trt:snake>]: Option<[<$trt Ref>]<'a>>,
|
||||
)*
|
||||
}
|
||||
|
||||
impl<'a, T: $trait_name> [<$trait_name Interfaces>]<'a, T> {
|
||||
impl<'a> [<$trait_name Interfaces>]<'a> {
|
||||
|
||||
pub fn [<from_ $trait_name:snake>](obj: &'a dyn $trait_name<ID = T::ID>) -> [<$trait_name Interfaces>]<'a, T> {
|
||||
pub fn [<from_ $trait_name:snake>](obj: &'a dyn $trait_name) -> [<$trait_name Interfaces>]<'a> {
|
||||
[<$trait_name Interfaces>] {
|
||||
$(
|
||||
[<$trt:snake>]: obj.[<as_ $trt:snake>](),
|
||||
@@ -56,7 +55,7 @@ macro_rules! object_trait {
|
||||
};
|
||||
( $trait_name:ident $(: $($bound:tt)* )? {$($trt:ident),*}) => {
|
||||
$(
|
||||
$crate::vm::object::macros::object_trait!{@intf {$trait_name $trt}}
|
||||
$crate::vm::object::macros::object_trait!{@intf {$trt}}
|
||||
)*
|
||||
|
||||
|
||||
@@ -82,13 +81,12 @@ macro_rules! ObjectInterface {
|
||||
@name $name_field:ident: $name_typ:ty;
|
||||
} => {
|
||||
impl $trait_name for $struct {
|
||||
type ID = $id_typ;
|
||||
|
||||
fn get_id(&self) -> &Self::ID {
|
||||
&self.$id_field
|
||||
fn get_id(&self) -> crate::vm::object::ObjectID {
|
||||
self.$id_field
|
||||
}
|
||||
|
||||
fn set_id(&mut self, id: Self::ID) {
|
||||
fn set_id(&mut self, id: crate::vm::object::ObjectID) {
|
||||
self.$id_field = id;
|
||||
}
|
||||
|
||||
@@ -113,23 +111,23 @@ macro_rules! ObjectInterface {
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn as_object(&self) -> &dyn $trait_name<ID = Self::ID> {
|
||||
fn as_object(&self) -> &dyn $trait_name {
|
||||
self
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn as_mut_object(&mut self) -> &mut dyn $trait_name<ID = Self::ID> {
|
||||
fn as_mut_object(&mut self) -> &mut dyn $trait_name {
|
||||
self
|
||||
}
|
||||
|
||||
paste::paste!{$(
|
||||
#[inline(always)]
|
||||
fn [<as_ $trt:snake>](&self) -> Option<[<$trt Ref>]<Self>> {
|
||||
fn [<as_ $trt:snake>](&self) -> Option<[<$trt Ref>]> {
|
||||
Some(self)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn [<as_mut_ $trt:snake>](&mut self) -> Option<[<$trt RefMut>]<Self>> {
|
||||
fn [<as_mut_ $trt:snake>](&mut self) -> Option<[<$trt RefMut>]> {
|
||||
Some(self)
|
||||
}
|
||||
)*}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
use crate::vm::enums::prefabs::StationpediaPrefab;
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::vm::{enums::prefabs::StationpediaPrefab, VM};
|
||||
use crate::vm::object::VMObject;
|
||||
|
||||
use super::templates::ObjectTemplate;
|
||||
@@ -7,12 +9,12 @@ use super::ObjectID;
|
||||
pub mod structs;
|
||||
|
||||
#[allow(unused)]
|
||||
pub fn object_from_prefab_template(template: &ObjectTemplate, id: ObjectID) -> Option<VMObject> {
|
||||
pub fn object_from_prefab_template(template: &ObjectTemplate, id: ObjectID, vm: &Rc<VM>) -> Option<VMObject> {
|
||||
let prefab = StationpediaPrefab::from_repr(template.prefab_info().prefab_hash);
|
||||
match prefab {
|
||||
Some(StationpediaPrefab::ItemIntegratedCircuit10) => {
|
||||
Some(VMObject::new(structs::ItemIntegratedCircuit10))
|
||||
}
|
||||
// Some(StationpediaPrefab::ItemIntegratedCircuit10) => {
|
||||
// Some(VMObject::new(structs::ItemIntegratedCircuit10))
|
||||
// }
|
||||
// Some(StationpediaPrefab::StructureCircuitHousing) => Some()
|
||||
// Some(StationpediaPrefab::StructureRocketCircuitHousing) => Some()
|
||||
_ => None,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -75,14 +75,14 @@ tag_object_traits! {
|
||||
pub trait CircuitHolder: Logicable + Storage {
|
||||
fn clear_error(&mut self);
|
||||
fn set_error(&mut self, state: i32);
|
||||
fn get_logicable_from_index(&self, device: usize, vm: &VM) -> Option<LogicableRef<Self>>;
|
||||
fn get_logicable_from_index_mut(&self, device: usize, vm: &VM) -> Option<LogicableRefMut<Self>>;
|
||||
fn get_logicable_from_id(&self, device: ObjectID, vm: &VM) -> Option<LogicableRef<Self>>;
|
||||
fn get_logicable_from_id_mut(&self, device: ObjectID, vm: &VM) -> Option<LogicableRefMut<Self>>;
|
||||
fn get_logicable_from_index(&self, device: usize, vm: &VM) -> Option<LogicableRef>;
|
||||
fn get_logicable_from_index_mut(&self, device: usize, vm: &VM) -> Option<LogicableRefMut>;
|
||||
fn get_logicable_from_id(&self, device: ObjectID, vm: &VM) -> Option<LogicableRef>;
|
||||
fn get_logicable_from_id_mut(&self, device: ObjectID, vm: &VM) -> Option<LogicableRefMut>;
|
||||
fn get_source_code(&self) -> String;
|
||||
fn set_source_code(&self, code: String);
|
||||
fn get_batch(&self) -> Vec<LogicableRef<Self>>;
|
||||
fn get_batch_mut(&self) -> Vec<LogicableRefMut<Self>>;
|
||||
fn get_batch(&self) -> Vec<LogicableRef>;
|
||||
fn get_batch_mut(&self) -> Vec<LogicableRefMut>;
|
||||
fn get_ic(&self) -> Option<ObjectID>;
|
||||
}
|
||||
|
||||
@@ -99,7 +99,7 @@ tag_object_traits! {
|
||||
}
|
||||
|
||||
pub trait IntegratedCircuit: Logicable + MemoryWritable + SourceCode + Item {
|
||||
fn get_circuit_holder(&self, vm: &VM) -> Option<CircuitHolderRef<Self>>;
|
||||
fn get_circuit_holder(&self, vm: &VM) -> Option<CircuitHolderRef>;
|
||||
fn get_instruction_pointer(&self) -> f64;
|
||||
fn set_next_instruction(&mut self, next_instruction: f64);
|
||||
fn set_next_instruction_relative(&mut self, offset: f64) {
|
||||
@@ -189,7 +189,7 @@ tag_object_traits! {
|
||||
|
||||
}
|
||||
|
||||
impl<T: Debug> Debug for dyn Object<ID = T> {
|
||||
impl Debug for dyn Object {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
|
||||
Reference in New Issue
Block a user