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}