netpulse/state.rs
1// src/state.rs — Shared application state
2//
3// AppState is the single source of truth shared between:
4// - Prober tasks (write: push ProbeResult, update seq/last_rtt)
5// - TUI renderer (read: sparkline data, stats, status)
6// - Stream exporter (read: stats snapshots)
7//
8// Wrapped in Arc<Mutex<>> for safe cross-task sharing.
9// Lock contention is minimal — probers write once per interval (≥ 100ms),
10// TUI reads once per render frame (~100ms).
11
12use crate::stats::ring_buffer::RingBuffer;
13use std::collections::HashMap;
14use std::sync::{Arc, Mutex};
15
16/// Per-target probe state.
17pub struct TargetState {
18 /// Original display name (e.g., "google.com" or "8.8.8.8")
19 pub display_name: String,
20 /// Resolved IP address used for ICMP probing
21 pub resolved_addr: String,
22 /// Ring buffer of recent probe results
23 pub buffer: RingBuffer,
24 /// Total probes sent (used as sequence counter)
25 pub seq: u64,
26 /// RTT of the most recent probe in microseconds; None = loss
27 pub last_rtt_us: Option<u64>,
28 /// Latest responder IP address (used primarily in trace mode)
29 pub last_ip: Option<String>,
30}
31
32impl TargetState {
33 pub fn new(display_name: &str, resolved_addr: &str, window: usize) -> Self {
34 Self {
35 display_name: display_name.to_string(),
36 resolved_addr: resolved_addr.to_string(),
37 buffer: RingBuffer::new(window),
38 seq: 0,
39 last_rtt_us: None,
40 last_ip: None,
41 }
42 }
43
44 pub fn new_hop(window: usize) -> Self {
45 Self {
46 display_name: String::new(),
47 resolved_addr: String::new(),
48 buffer: RingBuffer::new(window),
49 seq: 0,
50 last_rtt_us: None,
51 last_ip: None,
52 }
53 }
54}
55
56/// Shared application state: one entry per monitored target.
57pub type AppState = Arc<Mutex<HashMap<String, TargetState>>>;
58
59/// Create a new empty AppState.
60pub fn new_app_state() -> AppState {
61 Arc::new(Mutex::new(HashMap::new()))
62}