1use crate::Frame;
2
3#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
5#[must_use = "NetworkStats should be inspected or used after being queried"]
6pub struct NetworkStats {
7 pub send_queue_len: usize,
11 pub ping: u128,
13 pub kbps_sent: usize,
15
16 pub local_frames_behind: i32,
20 pub remote_frames_behind: i32,
24
25 pub last_compared_frame: Option<Frame>,
31
32 pub local_checksum: Option<u128>,
40
41 pub remote_checksum: Option<u128>,
49
50 pub checksums_match: Option<bool>,
62}
63
64impl NetworkStats {
65 pub fn new() -> Self {
67 Self::default()
68 }
69}
70
71impl std::fmt::Display for NetworkStats {
72 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
73 let Self {
75 send_queue_len,
76 ping,
77 kbps_sent,
78 local_frames_behind,
79 remote_frames_behind,
80 last_compared_frame,
81 local_checksum,
82 remote_checksum,
83 checksums_match,
84 } = self;
85
86 write!(
87 f,
88 "NetworkStats {{ ping: {}ms, queue: {}, kbps: {}, local_behind: {}, remote_behind: {}",
89 ping, send_queue_len, kbps_sent, local_frames_behind, remote_frames_behind
90 )?;
91
92 if last_compared_frame.is_some()
94 || local_checksum.is_some()
95 || remote_checksum.is_some()
96 || checksums_match.is_some()
97 {
98 write!(f, ", last_compared_frame: ")?;
99 match last_compared_frame {
100 Some(frame) => write!(f, "{}", frame.as_i32())?,
101 None => write!(f, "None")?,
102 }
103
104 write!(f, ", local_checksum: ")?;
105 match local_checksum {
106 Some(cs) => write!(f, "0x{:016x}", cs)?,
107 None => write!(f, "None")?,
108 }
109
110 write!(f, ", remote_checksum: ")?;
111 match remote_checksum {
112 Some(cs) => write!(f, "0x{:016x}", cs)?,
113 None => write!(f, "None")?,
114 }
115
116 write!(f, ", checksums_match: ")?;
117 match checksums_match {
118 Some(true) => write!(f, "true")?,
119 Some(false) => write!(f, "false")?,
120 None => write!(f, "None")?,
121 }
122 }
123
124 write!(f, " }}")
125 }
126}
127
128#[cfg(test)]
129#[allow(
130 clippy::panic,
131 clippy::unwrap_used,
132 clippy::expect_used,
133 clippy::indexing_slicing
134)]
135mod tests {
136 use super::*;
137
138 #[test]
139 fn test_network_stats_default() {
140 let stats = NetworkStats::default();
141 assert_eq!(stats.send_queue_len, 0);
142 assert_eq!(stats.ping, 0);
143 assert_eq!(stats.kbps_sent, 0);
144 assert_eq!(stats.local_frames_behind, 0);
145 assert_eq!(stats.remote_frames_behind, 0);
146 assert_eq!(stats.last_compared_frame, None);
147 assert_eq!(stats.local_checksum, None);
148 assert_eq!(stats.remote_checksum, None);
149 assert_eq!(stats.checksums_match, None);
150 }
151
152 #[test]
153 fn test_network_stats_new() {
154 let stats = NetworkStats::new();
155 assert_eq!(stats.send_queue_len, 0);
156 assert_eq!(stats.ping, 0);
157 assert_eq!(stats.kbps_sent, 0);
158 assert_eq!(stats.local_frames_behind, 0);
159 assert_eq!(stats.remote_frames_behind, 0);
160 assert_eq!(stats.last_compared_frame, None);
161 assert_eq!(stats.local_checksum, None);
162 assert_eq!(stats.remote_checksum, None);
163 assert_eq!(stats.checksums_match, None);
164 }
165
166 #[test]
167 fn test_network_stats_debug() {
168 let stats = NetworkStats {
169 send_queue_len: 5,
170 ping: 100,
171 kbps_sent: 50,
172 local_frames_behind: 2,
173 remote_frames_behind: -1,
174 last_compared_frame: None,
175 local_checksum: None,
176 remote_checksum: None,
177 checksums_match: None,
178 };
179 let debug = format!("{:?}", stats);
180 assert!(debug.contains("NetworkStats"));
181 assert!(debug.contains('5'));
182 assert!(debug.contains("100"));
183 assert!(debug.contains("50"));
184 }
185
186 #[test]
187 fn test_network_stats_clone() {
188 let stats = NetworkStats {
189 send_queue_len: 10,
190 ping: 50,
191 kbps_sent: 100,
192 local_frames_behind: 3,
193 remote_frames_behind: -2,
194 last_compared_frame: Some(Frame::new(42)),
195 local_checksum: Some(12345),
196 remote_checksum: Some(12345),
197 checksums_match: Some(true),
198 };
199 let cloned = stats;
200 assert_eq!(cloned.send_queue_len, 10);
201 assert_eq!(cloned.ping, 50);
202 assert_eq!(cloned.kbps_sent, 100);
203 assert_eq!(cloned.local_frames_behind, 3);
204 assert_eq!(cloned.remote_frames_behind, -2);
205 assert_eq!(cloned.last_compared_frame, Some(Frame::new(42)));
206 assert_eq!(cloned.local_checksum, Some(12345));
207 assert_eq!(cloned.remote_checksum, Some(12345));
208 assert_eq!(cloned.checksums_match, Some(true));
209 }
210
211 #[test]
212 fn test_network_stats_negative_frames_behind() {
213 let stats = NetworkStats {
214 send_queue_len: 0,
215 ping: 0,
216 kbps_sent: 0,
217 local_frames_behind: -5,
218 remote_frames_behind: 5,
219 last_compared_frame: None,
220 local_checksum: None,
221 remote_checksum: None,
222 checksums_match: None,
223 };
224 assert_eq!(stats.local_frames_behind, -5);
225 assert_eq!(stats.remote_frames_behind, 5);
226 }
227
228 #[test]
229 fn test_network_stats_checksum_fields() {
230 let stats = NetworkStats {
231 send_queue_len: 0,
232 ping: 0,
233 kbps_sent: 0,
234 local_frames_behind: 0,
235 remote_frames_behind: 0,
236 last_compared_frame: Some(Frame::new(100)),
237 local_checksum: Some(0xDEAD_BEEF),
238 remote_checksum: Some(0xCAFE_BABE),
239 checksums_match: Some(false),
240 };
241 assert_eq!(stats.last_compared_frame, Some(Frame::new(100)));
242 assert_eq!(stats.local_checksum, Some(0xDEAD_BEEF));
243 assert_eq!(stats.remote_checksum, Some(0xCAFE_BABE));
244 assert_eq!(stats.checksums_match, Some(false));
245 }
246
247 #[test]
252 fn test_network_stats_display_without_checksum() {
253 let stats = NetworkStats {
254 send_queue_len: 5,
255 ping: 100,
256 kbps_sent: 50,
257 local_frames_behind: 2,
258 remote_frames_behind: -1,
259 last_compared_frame: None,
260 local_checksum: None,
261 remote_checksum: None,
262 checksums_match: None,
263 };
264 let display = format!("{}", stats);
265 assert!(display.starts_with("NetworkStats {"));
266 assert!(display.contains("ping: 100ms"));
267 assert!(display.contains("queue: 5"));
268 assert!(display.contains("kbps: 50"));
269 assert!(display.contains("local_behind: 2"));
270 assert!(display.contains("remote_behind: -1"));
271 assert!(!display.contains("local_checksum"));
273 }
274
275 #[test]
276 fn test_network_stats_display_with_checksum() {
277 let stats = NetworkStats {
278 send_queue_len: 3,
279 ping: 50,
280 kbps_sent: 100,
281 local_frames_behind: 0,
282 remote_frames_behind: 0,
283 last_compared_frame: Some(Frame::new(42)),
284 local_checksum: Some(0xDEAD_BEEF_CAFE_BABE),
285 remote_checksum: Some(0x1234_5678_9ABC_DEF0),
286 checksums_match: Some(true),
287 };
288 let display = format!("{}", stats);
289 assert!(display.contains("ping: 50ms"));
290 assert!(display.contains("last_compared_frame: 42"));
291 assert!(display.contains("local_checksum: 0xdeadbeefcafebabe"));
292 assert!(display.contains("remote_checksum: 0x123456789abcdef0"));
293 assert!(display.contains("checksums_match: true"));
294 }
295
296 #[test]
297 fn test_network_stats_display_checksum_mismatch() {
298 let stats = NetworkStats {
299 send_queue_len: 0,
300 ping: 0,
301 kbps_sent: 0,
302 local_frames_behind: 0,
303 remote_frames_behind: 0,
304 last_compared_frame: Some(Frame::new(100)),
305 local_checksum: Some(0xAAAA),
306 remote_checksum: Some(0xBBBB),
307 checksums_match: Some(false),
308 };
309 let display = format!("{}", stats);
310 assert!(display.contains("checksums_match: false"));
311 }
312
313 #[test]
314 fn test_network_stats_display_partial_checksum() {
315 let stats = NetworkStats {
317 send_queue_len: 0,
318 ping: 0,
319 kbps_sent: 0,
320 local_frames_behind: 0,
321 remote_frames_behind: 0,
322 last_compared_frame: Some(Frame::new(50)),
323 local_checksum: None,
324 remote_checksum: None,
325 checksums_match: None,
326 };
327 let display = format!("{}", stats);
328 assert!(display.contains("last_compared_frame: 50"));
330 assert!(display.contains("local_checksum: None"));
331 }
332}