348 lines
12 KiB
Rust
348 lines
12 KiB
Rust
//! 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<i32> {
|
|
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<Self, Error> {
|
|
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<i32, Error> {
|
|
Ok(self.last_ping_id.fetch_add(1, Ordering::SeqCst))
|
|
}
|
|
|
|
pub fn get_connect_time(&self) -> Result<i32, Error> {
|
|
Ok(self.connect_time.load(Ordering::SeqCst))
|
|
}
|
|
|
|
pub fn get_dropped_packets(&self) -> Result<i32, Error> {
|
|
Ok(self.dropped_packets.load(Ordering::SeqCst))
|
|
}
|
|
|
|
pub fn get_incoming_bps(&self) -> Result<i32, Error> {
|
|
Ok(self.incoming_bps.load(Ordering::SeqCst))
|
|
}
|
|
|
|
pub fn get_last_ping_sent(&self) -> Result<i32, Error> {
|
|
Ok(self.last_ping_sent.load(Ordering::SeqCst))
|
|
}
|
|
|
|
pub fn get_outgoing_bps(&self) -> Result<i32, Error> {
|
|
Ok(self.outgoing_bps.load(Ordering::SeqCst))
|
|
}
|
|
|
|
pub fn get_received_pongs(&self) -> Result<i32, Error> {
|
|
Ok(self.received_pongs.load(Ordering::SeqCst))
|
|
}
|
|
|
|
pub fn get_received_resends(&self) -> Result<i32, Error> {
|
|
Ok(self.received_resends.load(Ordering::SeqCst))
|
|
}
|
|
|
|
pub fn get_recv_bytes(&self) -> Result<i64, Error> {
|
|
Ok(self.recv_bytes.load(Ordering::SeqCst))
|
|
}
|
|
|
|
pub fn get_recv_packets(&self) -> Result<i64, Error> {
|
|
Ok(self.recv_packets.load(Ordering::SeqCst))
|
|
}
|
|
|
|
pub fn get_resent_packets(&self) -> Result<i32, Error> {
|
|
Ok(self.resent_packets.load(Ordering::SeqCst))
|
|
}
|
|
|
|
pub fn get_sent_bytes(&self) -> Result<i64, Error> {
|
|
Ok(self.sent_bytes.load(Ordering::SeqCst))
|
|
}
|
|
|
|
pub fn get_sent_packets(&self) -> Result<i64, Error> {
|
|
Ok(self.sent_packets.load(Ordering::SeqCst))
|
|
}
|
|
|
|
pub fn get_sent_pings(&self) -> Result<i32, Error> {
|
|
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);
|
|
}
|
|
}
|