aimdb_core/buffer/
cfg.rs

1//! Buffer configuration types
2//!
3//! Defines the configuration enum for selecting buffer behavior per record type.
4
5use core::fmt;
6
7#[cfg(not(feature = "std"))]
8extern crate alloc;
9
10/// Buffer configuration for a record type
11///
12/// Selects buffering strategy: SPMC Ring (backlog), SingleLatest (state sync), or Mailbox (commands).
13///
14/// # Quick Selection Guide
15/// - **High-frequency data (>10 Hz)**: `SpmcRing` with tuned capacity
16/// - **State/config updates**: `SingleLatest` (only latest matters)
17/// - **Commands/one-shot events**: `Mailbox`
18///
19/// # Examples
20/// ```rust
21/// use aimdb_core::buffer::BufferCfg;
22///
23/// let telemetry = BufferCfg::SpmcRing { capacity: 2048 };  // High-freq data
24/// let config = BufferCfg::SingleLatest;                     // State sync
25/// let commands = BufferCfg::Mailbox;                        // Commands
26/// ```
27#[derive(Debug, Clone, PartialEq, Eq)]
28#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
29pub enum BufferCfg {
30    /// SPMC (Single Producer, Multiple Consumer) ring buffer
31    ///
32    /// Best for high-frequency data streams with bounded memory. Fast producers
33    /// can outrun slow consumers (lag detection). Oldest messages dropped on overflow.
34    ///
35    /// **Sizing:** `capacity = data_rate_hz × lag_seconds` (use power-of-2)
36    SpmcRing {
37        /// Maximum number of items in the buffer
38        capacity: usize,
39    },
40
41    /// Single latest value buffer (no backlog)
42    ///
43    /// Only most recent value is kept. Consumers always get latest state.
44    /// Intermediate updates are collapsed. Use when history doesn't matter.
45    SingleLatest,
46
47    /// Single-slot mailbox with overwrite
48    ///
49    /// New value overwrites old if not consumed. At-least-once delivery.
50    /// Use for commands where latest command wins.
51    Mailbox,
52}
53
54impl BufferCfg {
55    /// Validates the buffer configuration
56    ///
57    /// Returns `Err` if SPMC Ring capacity is 0.
58    pub fn validate(&self) -> Result<(), &'static str> {
59        match self {
60            BufferCfg::SpmcRing { capacity } => {
61                if *capacity == 0 {
62                    return Err("SPMC ring capacity must be > 0");
63                }
64                // Note: Non-power-of-2 is allowed but may have performance implications
65                // This is documented but not enforced
66                Ok(())
67            }
68            BufferCfg::SingleLatest | BufferCfg::Mailbox => Ok(()),
69        }
70    }
71
72    /// Returns a human-readable name for this buffer type
73    pub fn name(&self) -> &'static str {
74        match self {
75            BufferCfg::SpmcRing { .. } => "spmc_ring",
76            BufferCfg::SingleLatest => "single_latest",
77            BufferCfg::Mailbox => "mailbox",
78        }
79    }
80
81    /// Returns estimated memory overhead for this buffer type
82    ///
83    /// Approximation; varies by implementation and platform.
84    pub fn estimated_memory_bytes(&self, item_size: usize, consumer_count: usize) -> usize {
85        match self {
86            BufferCfg::SpmcRing { capacity } => {
87                // Buffer storage + per-consumer overhead
88                let buffer_size = capacity * item_size;
89                let consumer_overhead = consumer_count * 64; // Approximate
90                buffer_size + consumer_overhead
91            }
92            BufferCfg::SingleLatest => {
93                // Single slot + per-consumer overhead
94                let buffer_size = item_size + 8; // Option<T> overhead
95                let consumer_overhead = consumer_count * 16; // Watcher handles
96                buffer_size + consumer_overhead
97            }
98            BufferCfg::Mailbox => {
99                // Single slot + notify + per-consumer overhead
100                let buffer_size = item_size + 8; // Option<T>
101                let notify_overhead = 32; // Notify primitive
102                let consumer_overhead = consumer_count * 16; // Notifier handles
103                buffer_size + notify_overhead + consumer_overhead
104            }
105        }
106    }
107}
108
109impl Default for BufferCfg {
110    /// Returns the default buffer configuration: `SpmcRing { capacity: 1024 }`
111    fn default() -> Self {
112        BufferCfg::SpmcRing { capacity: 1024 }
113    }
114}
115
116impl fmt::Display for BufferCfg {
117    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
118        match self {
119            BufferCfg::SpmcRing { capacity } => write!(f, "SpmcRing(capacity={})", capacity),
120            BufferCfg::SingleLatest => write!(f, "SingleLatest"),
121            BufferCfg::Mailbox => write!(f, "Mailbox"),
122        }
123    }
124}
125
126#[cfg(test)]
127mod tests {
128    use super::*;
129
130    #[test]
131    fn test_buffer_cfg_validation() {
132        // Valid configurations
133        assert!(BufferCfg::SpmcRing { capacity: 1 }.validate().is_ok());
134        assert!(BufferCfg::SpmcRing { capacity: 1024 }.validate().is_ok());
135        assert!(BufferCfg::SingleLatest.validate().is_ok());
136        assert!(BufferCfg::Mailbox.validate().is_ok());
137
138        // Invalid configuration
139        assert!(BufferCfg::SpmcRing { capacity: 0 }.validate().is_err());
140    }
141
142    #[test]
143    fn test_buffer_cfg_default() {
144        let cfg = BufferCfg::default();
145        assert_eq!(cfg, BufferCfg::SpmcRing { capacity: 1024 });
146        assert!(cfg.validate().is_ok());
147    }
148
149    #[test]
150    fn test_buffer_cfg_names() {
151        assert_eq!(BufferCfg::SpmcRing { capacity: 100 }.name(), "spmc_ring");
152        assert_eq!(BufferCfg::SingleLatest.name(), "single_latest");
153        assert_eq!(BufferCfg::Mailbox.name(), "mailbox");
154    }
155
156    #[test]
157    #[cfg(feature = "std")]
158    fn test_buffer_cfg_display() {
159        assert_eq!(
160            format!("{}", BufferCfg::SpmcRing { capacity: 512 }),
161            "SpmcRing(capacity=512)"
162        );
163        assert_eq!(format!("{}", BufferCfg::SingleLatest), "SingleLatest");
164        assert_eq!(format!("{}", BufferCfg::Mailbox), "Mailbox");
165    }
166
167    #[test]
168    fn test_estimated_memory() {
169        // SPMC Ring with 100-byte items, 1024 capacity, 3 consumers
170        let cfg = BufferCfg::SpmcRing { capacity: 1024 };
171        let mem = cfg.estimated_memory_bytes(100, 3);
172        // Should be roughly 1024*100 + 3*64 = 102,400 + 192 = 102,592
173        assert!(mem > 102_000 && mem < 103_000);
174
175        // SingleLatest with 100-byte items, 3 consumers
176        let cfg = BufferCfg::SingleLatest;
177        let mem = cfg.estimated_memory_bytes(100, 3);
178        // Should be roughly 100 + 8 + 3*16 = 156
179        assert!(mem > 100 && mem < 200);
180
181        // Mailbox with 100-byte items, 3 consumers
182        let cfg = BufferCfg::Mailbox;
183        let mem = cfg.estimated_memory_bytes(100, 3);
184        // Should be roughly 100 + 8 + 32 + 3*16 = 188
185        assert!(mem > 140 && mem < 250);
186    }
187
188    #[test]
189    fn test_clone_and_equality() {
190        let cfg1 = BufferCfg::SpmcRing { capacity: 512 };
191        let cfg2 = cfg1.clone();
192        assert_eq!(cfg1, cfg2);
193
194        let cfg3 = BufferCfg::SingleLatest;
195        assert_ne!(cfg1, cfg3);
196    }
197}