//! Thread-safe simulator statistics matching `Simulator.SimStats`. #![allow(clippy::missing_errors_doc)] // Result shapes are fixed by the compatibility map. use crate::Error; use std::fmt; use std::sync::atomic::{AtomicI32, AtomicI64, AtomicU32, Ordering}; fn csharp_convert_to_i32(value: f32) -> Option { let rounded = value.round_ties_even(); if !rounded.is_finite() || !(-2_147_483_648.0..2_147_483_648.0).contains(&rounded) { return None; } #[allow(clippy::cast_possible_truncation)] Some(rounded as i32) } macro_rules! integer_property { ($get:ident, $set:ident, $field:ident) => { #[must_use] pub fn $get(&self) -> i32 { self.$field.load(Ordering::Acquire) } pub fn $set(&mut self, value: i32) { self.$field.store(value, Ordering::Release); } }; } macro_rules! float_property { ($get:ident, $set:ident, $field:ident) => { #[must_use] pub fn $get(&self) -> f32 { f32::from_bits(self.$field.load(Ordering::Acquire)) } pub fn $set(&mut self, value: f32) { self.$field.store(value.to_bits(), Ordering::Release); } }; } #[derive(Default)] pub struct SimulatorSimStats { sent_packets: AtomicI64, recv_packets: AtomicI64, sent_bytes: AtomicI64, recv_bytes: AtomicI64, connect_time: AtomicI32, resent_packets: AtomicI32, received_resends: AtomicI32, dropped_packets: AtomicI32, sent_pings: AtomicI32, received_pongs: AtomicI32, incoming_bps: AtomicI32, outgoing_bps: AtomicI32, last_ping_sent: AtomicI32, last_ping_id: AtomicI32, last_lag: AtomicI32, missed_pings: AtomicI32, dilation: AtomicU32, fps: AtomicI32, physics_fps: AtomicU32, agent_updates: AtomicU32, frame_time: AtomicU32, net_time: AtomicU32, physics_time: AtomicU32, image_time: AtomicU32, script_time: AtomicU32, agent_time: AtomicU32, other_time: AtomicU32, objects: AtomicI32, scripted_objects: AtomicI32, agents: AtomicI32, child_agents: AtomicI32, active_scripts: AtomicI32, lslips: AtomicI32, inpps: AtomicI32, outpps: AtomicI32, pending_downloads: AtomicI32, pending_uploads: AtomicI32, virtual_size: AtomicI32, resident_size: AtomicI32, pending_local_uploads: AtomicI32, unacked_bytes: AtomicI32, } impl fmt::Debug for SimulatorSimStats { fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { formatter .debug_struct("SimulatorSimStats") .field("sent_packets", &self.sent_packets.load(Ordering::Relaxed)) .field("recv_packets", &self.recv_packets.load(Ordering::Relaxed)) .field("sent_bytes", &self.sent_bytes.load(Ordering::Relaxed)) .field("recv_bytes", &self.recv_bytes.load(Ordering::Relaxed)) .field( "dropped_packets", &self.dropped_packets.load(Ordering::Relaxed), ) .finish_non_exhaustive() } } impl SimulatorSimStats { pub fn new() -> Result { Ok(Self::default()) } pub fn add_recv_bytes(&self, value: i64) -> Result<(), Error> { self.recv_bytes.fetch_add(value, Ordering::SeqCst); Ok(()) } pub fn add_sent_bytes(&self, value: i64) -> Result<(), Error> { self.sent_bytes.fetch_add(value, Ordering::SeqCst); Ok(()) } pub fn get_and_increment_last_ping_id(&self) -> Result { Ok(self.last_ping_id.fetch_add(1, Ordering::SeqCst)) } pub fn get_connect_time(&self) -> Result { Ok(self.connect_time.load(Ordering::SeqCst)) } pub fn get_dropped_packets(&self) -> Result { Ok(self.dropped_packets.load(Ordering::SeqCst)) } pub fn get_incoming_bps(&self) -> Result { Ok(self.incoming_bps.load(Ordering::SeqCst)) } pub fn get_last_ping_sent(&self) -> Result { Ok(self.last_ping_sent.load(Ordering::SeqCst)) } pub fn get_outgoing_bps(&self) -> Result { Ok(self.outgoing_bps.load(Ordering::SeqCst)) } pub fn get_received_pongs(&self) -> Result { Ok(self.received_pongs.load(Ordering::SeqCst)) } pub fn get_received_resends(&self) -> Result { Ok(self.received_resends.load(Ordering::SeqCst)) } pub fn get_recv_bytes(&self) -> Result { Ok(self.recv_bytes.load(Ordering::SeqCst)) } pub fn get_recv_packets(&self) -> Result { Ok(self.recv_packets.load(Ordering::SeqCst)) } pub fn get_resent_packets(&self) -> Result { Ok(self.resent_packets.load(Ordering::SeqCst)) } pub fn get_sent_bytes(&self) -> Result { Ok(self.sent_bytes.load(Ordering::SeqCst)) } pub fn get_sent_packets(&self) -> Result { Ok(self.sent_packets.load(Ordering::SeqCst)) } pub fn get_sent_pings(&self) -> Result { Ok(self.sent_pings.load(Ordering::SeqCst)) } pub fn increment_dropped_packets(&self) -> Result<(), Error> { self.dropped_packets.fetch_add(1, Ordering::SeqCst); Ok(()) } pub fn increment_received_pongs(&self) -> Result<(), Error> { self.received_pongs.fetch_add(1, Ordering::SeqCst); Ok(()) } pub fn increment_received_resends(&self) -> Result<(), Error> { self.received_resends.fetch_add(1, Ordering::SeqCst); Ok(()) } pub fn increment_recv_packets(&self) -> Result<(), Error> { self.recv_packets.fetch_add(1, Ordering::SeqCst); Ok(()) } pub fn increment_resent_packets(&self) -> Result<(), Error> { self.resent_packets.fetch_add(1, Ordering::SeqCst); Ok(()) } pub fn increment_sent_packets(&self) -> Result<(), Error> { self.sent_packets.fetch_add(1, Ordering::SeqCst); Ok(()) } pub fn increment_sent_pings(&self) -> Result<(), Error> { self.sent_pings.fetch_add(1, Ordering::SeqCst); Ok(()) } pub fn set_connect_time(&self, value: i32) -> Result<(), Error> { self.connect_time.store(value, Ordering::SeqCst); Ok(()) } pub fn set_incoming_bps(&self, value: i32) -> Result<(), Error> { self.incoming_bps.store(value, Ordering::SeqCst); Ok(()) } pub fn set_last_ping_sent(&self, value: i32) -> Result<(), Error> { self.last_ping_sent.store(value, Ordering::SeqCst); Ok(()) } pub fn set_outgoing_bps(&self, value: i32) -> Result<(), Error> { self.outgoing_bps.store(value, Ordering::SeqCst); Ok(()) } pub(crate) fn record_pong(&self, tick_count: i32) { let sent = self.last_ping_sent.load(Ordering::Acquire); self.last_lag .store(tick_count.wrapping_sub(sent), Ordering::Release); self.received_pongs.fetch_add(1, Ordering::SeqCst); } pub(crate) fn apply_server_stat(&self, id: u32, value: f32) { let float = |field: &AtomicU32| field.store(value.to_bits(), Ordering::Release); let integer = |field: &AtomicI32| { if let Some(value) = csharp_convert_to_i32(value) { field.store(value, Ordering::Release); } }; match id { 0 => float(&self.dilation), 1 => integer(&self.fps), 2 => float(&self.physics_fps), 3 => float(&self.agent_updates), 4 => float(&self.frame_time), 5 => float(&self.net_time), 6 => float(&self.other_time), 7 => float(&self.physics_time), 8 => float(&self.agent_time), 9 => float(&self.image_time), 10 => float(&self.script_time), 11 => integer(&self.objects), 12 => integer(&self.scripted_objects), 13 => integer(&self.agents), 14 => integer(&self.child_agents), 15 => integer(&self.active_scripts), 16 => integer(&self.lslips), 17 => integer(&self.inpps), 18 => integer(&self.outpps), 19 => integer(&self.pending_downloads), 20 => integer(&self.pending_uploads), 21 => integer(&self.virtual_size), 22 => integer(&self.resident_size), 23 => integer(&self.pending_local_uploads), 24 => integer(&self.unacked_bytes), _ => {} } } integer_property!(active_scripts, set_active_scripts, active_scripts); float_property!(agent_time, set_agent_time, agent_time); float_property!(agent_updates, set_agent_updates, agent_updates); integer_property!(agents, set_agents, agents); integer_property!(child_agents, set_child_agents, child_agents); float_property!(dilation, set_dilation, dilation); integer_property!(fps, set_fps, fps); float_property!(frame_time, set_frame_time, frame_time); integer_property!(inpps, set_inpps, inpps); float_property!(image_time, set_image_time, image_time); integer_property!(lslips, set_lslips, lslips); integer_property!(last_lag, set_last_lag, last_lag); integer_property!(missed_pings, set_missed_pings, missed_pings); float_property!(net_time, set_net_time, net_time); integer_property!(outpps, set_outpps, outpps); integer_property!(objects, set_objects, objects); float_property!(other_time, set_other_time, other_time); integer_property!(pending_downloads, set_pending_downloads, pending_downloads); integer_property!( pending_local_uploads, set_pending_local_uploads, pending_local_uploads ); integer_property!(pending_uploads, set_pending_uploads, pending_uploads); float_property!(physics_fps, set_physics_fps, physics_fps); float_property!(physics_time, set_physics_time, physics_time); integer_property!(resident_size, set_resident_size, resident_size); float_property!(script_time, set_script_time, script_time); integer_property!(scripted_objects, set_scripted_objects, scripted_objects); integer_property!(unacked_bytes, set_unacked_bytes, unacked_bytes); integer_property!(virtual_size, set_virtual_size, virtual_size); } #[cfg(test)] mod tests { use super::*; use std::sync::Arc; use std::thread; #[test] fn counters_are_atomic_and_properties_preserve_float_bits() { let stats = Arc::new(SimulatorSimStats::new().unwrap()); let workers: Vec<_> = (0..4) .map(|_| { let stats = Arc::clone(&stats); thread::spawn(move || { for _ in 0..1_000 { stats.increment_recv_packets().unwrap(); stats.add_recv_bytes(7).unwrap(); } }) }) .collect(); for worker in workers { worker.join().unwrap(); } assert_eq!(stats.get_recv_packets().unwrap(), 4_000); assert_eq!(stats.get_recv_bytes().unwrap(), 28_000); assert_eq!(stats.get_and_increment_last_ping_id().unwrap(), 0); assert_eq!(stats.get_and_increment_last_ping_id().unwrap(), 1); let mut stats = Arc::try_unwrap(stats).ok().unwrap(); stats.set_dilation(f32::from_bits(0x7fc0_1234)); assert_eq!(stats.dilation().to_bits(), 0x7fc0_1234); stats.apply_server_stat(1, 44.5); assert_eq!(stats.fps(), 44); stats.apply_server_stat(1, 45.5); assert_eq!(stats.fps(), 46); stats.apply_server_stat(1, f32::NAN); assert_eq!(stats.fps(), 46); } }