Skip to main content

subetha_cxc/
control_table.rs

1//! The atomic control table: the lock-free bridge between the slow
2//! sensor/controller loop and the fast per-packet data path.
3//!
4//! A controller running on its own cadence (sensor polling, loss
5//! estimation) publishes its decisions here with Relaxed stores. The
6//! hot path reads a single field with one Relaxed load and branches to
7//! the minimal coding work for the current level - no locks, no
8//! syscalls, no allocation. This is what makes the adaptive machinery
9//! "consulted as needed", never run per packet.
10//!
11//! All fields are `u8` so each read/write is a single atomic
12//! instruction. Relaxed ordering is correct here: the control values
13//! are advisory tuning knobs, not data that gates memory safety, so the
14//! hot path tolerates reading a value one tick stale.
15
16use std::sync::atomic::{AtomicU8, Ordering};
17
18/// Coding escalation level read on the hot path.
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20#[repr(u8)]
21pub enum CodingLevel {
22    /// memcpy passthrough - clean link, zero ECC work.
23    Passthrough = 0,
24    /// inter-packet erasure FEC only.
25    Fec = 1,
26    /// FEC plus transmit interleaving for burst tolerance.
27    Interleave = 2,
28    /// adds intra-packet bad-FCS salvage.
29    Salvage = 3,
30}
31
32impl CodingLevel {
33    /// Map a raw control byte to a level (saturating at `Salvage`).
34    #[inline]
35    pub fn from_u8(v: u8) -> Self {
36        match v {
37            0 => CodingLevel::Passthrough,
38            1 => CodingLevel::Fec,
39            2 => CodingLevel::Interleave,
40            _ => CodingLevel::Salvage,
41        }
42    }
43}
44
45/// Lock-free tuning knobs shared between the controller and the data
46/// path. Cheap to construct; share via `Arc`.
47#[derive(Debug)]
48pub struct ControlTable {
49    level: AtomicU8,
50    parity_r: AtomicU8,
51    interleave_depth: AtomicU8,
52    inner_fec: AtomicU8,
53    tower_depth: AtomicU8,
54}
55
56impl Default for ControlTable {
57    fn default() -> Self {
58        // Defaults match a clean small-LAN link: FEC on with r=2, no
59        // interleave, no salvage, no outer tower.
60        Self {
61            level: AtomicU8::new(CodingLevel::Fec as u8),
62            parity_r: AtomicU8::new(2),
63            interleave_depth: AtomicU8::new(1),
64            inner_fec: AtomicU8::new(0),
65            tower_depth: AtomicU8::new(0),
66        }
67    }
68}
69
70impl ControlTable {
71    /// A fresh table at the clean-link defaults.
72    pub fn new() -> Self {
73        Self::default()
74    }
75
76    // --- hot-path reads (one Relaxed load each) ---
77
78    /// Current coding escalation level.
79    #[inline]
80    pub fn level(&self) -> CodingLevel {
81        CodingLevel::from_u8(self.level.load(Ordering::Relaxed))
82    }
83
84    /// Current FEC parity shards per block.
85    #[inline]
86    pub fn parity_r(&self) -> u8 {
87        self.parity_r.load(Ordering::Relaxed)
88    }
89
90    /// Current interleave depth (1 = no interleaving).
91    #[inline]
92    pub fn interleave_depth(&self) -> u8 {
93        self.interleave_depth.load(Ordering::Relaxed).max(1)
94    }
95
96    /// Whether the intra-packet salvage code is engaged.
97    #[inline]
98    pub fn inner_fec(&self) -> bool {
99        self.inner_fec.load(Ordering::Relaxed) != 0
100    }
101
102    /// Outer-tower rung depth (0 = block code only).
103    #[inline]
104    pub fn tower_depth(&self) -> u8 {
105        self.tower_depth.load(Ordering::Relaxed)
106    }
107
108    // --- controller-side writes (Relaxed stores) ---
109
110    /// Publish a new coding level.
111    pub fn set_level(&self, level: CodingLevel) {
112        self.level.store(level as u8, Ordering::Relaxed);
113    }
114
115    /// Publish a new parity count.
116    pub fn set_parity_r(&self, r: u8) {
117        self.parity_r.store(r, Ordering::Relaxed);
118    }
119
120    /// Publish a new interleave depth (clamped to at least 1).
121    pub fn set_interleave_depth(&self, d: u8) {
122        self.interleave_depth.store(d.max(1), Ordering::Relaxed);
123    }
124
125    /// Engage or disengage the intra-packet salvage code.
126    pub fn set_inner_fec(&self, on: bool) {
127        self.inner_fec.store(on as u8, Ordering::Relaxed);
128    }
129
130    /// Publish a new outer-tower rung depth.
131    pub fn set_tower_depth(&self, d: u8) {
132        self.tower_depth.store(d, Ordering::Relaxed);
133    }
134}
135
136#[cfg(test)]
137mod tests {
138    use super::*;
139    use std::sync::Arc;
140
141    #[test]
142    fn defaults_are_clean_link() {
143        let t = ControlTable::new();
144        assert_eq!(t.level(), CodingLevel::Fec);
145        assert_eq!(t.parity_r(), 2);
146        assert_eq!(t.interleave_depth(), 1);
147        assert!(!t.inner_fec());
148        assert_eq!(t.tower_depth(), 0);
149    }
150
151    #[test]
152    fn writes_are_visible_to_reads() {
153        let t = ControlTable::new();
154        t.set_level(CodingLevel::Interleave);
155        t.set_parity_r(4);
156        t.set_interleave_depth(8);
157        t.set_inner_fec(true);
158        t.set_tower_depth(1);
159        assert_eq!(t.level(), CodingLevel::Interleave);
160        assert_eq!(t.parity_r(), 4);
161        assert_eq!(t.interleave_depth(), 8);
162        assert!(t.inner_fec());
163        assert_eq!(t.tower_depth(), 1);
164    }
165
166    #[test]
167    fn interleave_depth_floor_is_one() {
168        let t = ControlTable::new();
169        t.set_interleave_depth(0);
170        assert_eq!(t.interleave_depth(), 1);
171    }
172
173    #[test]
174    fn shared_across_threads() {
175        // Controller thread writes; data-path thread reads. The read
176        // must always see a valid level (never a torn value).
177        let t = Arc::new(ControlTable::new());
178        let writer = {
179            let t = Arc::clone(&t);
180            std::thread::spawn(move || {
181                for i in 0..10_000u32 {
182                    t.set_level(CodingLevel::from_u8((i % 4) as u8));
183                }
184            })
185        };
186        let mut seen = 0u32;
187        for _ in 0..10_000 {
188            // black_box forces the load so the tear-test is real.
189            if std::hint::black_box(t.level()) == CodingLevel::Passthrough {
190                seen += 1;
191            }
192        }
193        writer.join().unwrap();
194        // `seen` is observed, not asserted to a value (it is a race);
195        // the point is no torn read panicked the `from_u8` match.
196        std::hint::black_box(seen);
197    }
198}