Skip to main content

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/// Buffer configuration for a record type
8///
9/// Selects buffering strategy: SPMC Ring (backlog), SingleLatest (state sync), or Mailbox (commands).
10///
11/// # Quick Selection Guide
12/// - **High-frequency data (>10 Hz)**: `SpmcRing` with tuned capacity
13/// - **State/config updates**: `SingleLatest` (only latest matters)
14/// - **Commands/one-shot events**: `Mailbox`
15///
16/// # Examples
17/// ```rust
18/// use aimdb_core::buffer::BufferCfg;
19///
20/// let telemetry = BufferCfg::SpmcRing { capacity: 2048 };  // High-freq data
21/// let config = BufferCfg::SingleLatest;                     // State sync
22/// let commands = BufferCfg::Mailbox;                        // Commands
23/// ```
24#[derive(Debug, Clone, PartialEq, Eq)]
25#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
26pub enum BufferCfg {
27    /// SPMC (Single Producer, Multiple Consumer) ring buffer
28    ///
29    /// Best for high-frequency data streams with bounded memory. Fast producers
30    /// can outrun slow consumers (lag detection). Oldest messages dropped on overflow.
31    ///
32    /// **Sizing:** `capacity = data_rate_hz × lag_seconds` (use power-of-2).
33    /// The Tokio adapter's broadcast backend rounds capacity up to the next
34    /// power of two, so the effective capacity can exceed the configured
35    /// value; metrics report the configured capacity.
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    ///
46    /// A reader that subscribes after a value has already been produced
47    /// observes that value once, as if it had been pushed after subscription
48    /// — a late subscriber to state wants the current state, not just future
49    /// changes. If nothing has been produced yet, the reader waits. This
50    /// fresh-subscriber rule is identical across all runtime adapters (tokio,
51    /// embassy, and wasm) and is enforced by the shared buffer contract suite.
52    SingleLatest,
53
54    /// Single-slot mailbox with overwrite
55    ///
56    /// New value overwrites old if not consumed: delivery is at-most-once
57    /// per value — an unconsumed value is lost on overwrite. Use for
58    /// commands where the latest command wins.
59    ///
60    /// With multiple subscribers, consumption is competitive: each value is
61    /// taken by exactly one subscriber, nondeterministically. Unlike
62    /// `SpmcRing` and `SingleLatest`, a mailbox does not fan out.
63    Mailbox,
64}
65
66impl BufferCfg {
67    /// Validates the buffer configuration
68    ///
69    /// Returns `Err` if SPMC Ring capacity is 0.
70    pub fn validate(&self) -> Result<(), &'static str> {
71        match self {
72            BufferCfg::SpmcRing { capacity } => {
73                if *capacity == 0 {
74                    return Err("SPMC ring capacity must be > 0");
75                }
76                // Note: Non-power-of-2 is allowed but may have performance implications
77                // This is documented but not enforced
78                Ok(())
79            }
80            BufferCfg::SingleLatest | BufferCfg::Mailbox => Ok(()),
81        }
82    }
83
84    /// Returns a human-readable name for this buffer type
85    pub fn name(&self) -> &'static str {
86        match self {
87            BufferCfg::SpmcRing { .. } => "spmc_ring",
88            BufferCfg::SingleLatest => "single_latest",
89            BufferCfg::Mailbox => "mailbox",
90        }
91    }
92}
93
94impl Default for BufferCfg {
95    /// Returns the default buffer configuration: `SpmcRing { capacity: 1024 }`
96    fn default() -> Self {
97        BufferCfg::SpmcRing { capacity: 1024 }
98    }
99}
100
101impl fmt::Display for BufferCfg {
102    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
103        match self {
104            BufferCfg::SpmcRing { capacity } => write!(f, "SpmcRing(capacity={})", capacity),
105            BufferCfg::SingleLatest => write!(f, "SingleLatest"),
106            BufferCfg::Mailbox => write!(f, "Mailbox"),
107        }
108    }
109}
110
111#[cfg(test)]
112mod tests {
113    use super::*;
114
115    #[test]
116    fn test_buffer_cfg_validation() {
117        // Valid configurations
118        assert!(BufferCfg::SpmcRing { capacity: 1 }.validate().is_ok());
119        assert!(BufferCfg::SpmcRing { capacity: 1024 }.validate().is_ok());
120        assert!(BufferCfg::SingleLatest.validate().is_ok());
121        assert!(BufferCfg::Mailbox.validate().is_ok());
122
123        // Invalid configuration
124        assert!(BufferCfg::SpmcRing { capacity: 0 }.validate().is_err());
125    }
126
127    #[test]
128    fn test_buffer_cfg_default() {
129        let cfg = BufferCfg::default();
130        assert_eq!(cfg, BufferCfg::SpmcRing { capacity: 1024 });
131        assert!(cfg.validate().is_ok());
132    }
133
134    #[test]
135    fn test_buffer_cfg_names() {
136        assert_eq!(BufferCfg::SpmcRing { capacity: 100 }.name(), "spmc_ring");
137        assert_eq!(BufferCfg::SingleLatest.name(), "single_latest");
138        assert_eq!(BufferCfg::Mailbox.name(), "mailbox");
139    }
140
141    #[test]
142    #[cfg(feature = "std")]
143    fn test_buffer_cfg_display() {
144        assert_eq!(
145            format!("{}", BufferCfg::SpmcRing { capacity: 512 }),
146            "SpmcRing(capacity=512)"
147        );
148        assert_eq!(format!("{}", BufferCfg::SingleLatest), "SingleLatest");
149        assert_eq!(format!("{}", BufferCfg::Mailbox), "Mailbox");
150    }
151
152    #[test]
153    fn test_clone_and_equality() {
154        let cfg1 = BufferCfg::SpmcRing { capacity: 512 };
155        let cfg2 = cfg1.clone();
156        assert_eq!(cfg1, cfg2);
157
158        let cfg3 = BufferCfg::SingleLatest;
159        assert_ne!(cfg1, cfg3);
160    }
161}