Skip to main content

dualcache_ff/
config.rs

1#[derive(Debug, Clone, Copy)]
2pub struct Config {
3    pub capacity: usize,
4    pub t1_slots: usize,
5    pub t2_slots: usize,
6    /// TTL duration in epoch ticks (one tick ≈ 100 ms in std mode).
7    pub duration: u32,
8    pub threads: usize,
9    /// Daemon poll interval in **microseconds** (1 000–10 000 µs = 1–10 ms).
10    /// Controls the latency vs. idle-CPU trade-off.
11    /// Lower = faster hit-signal delivery, higher CPU when idle.
12    /// Higher = more efficient idle, but hotter TLS buffers may stall longer.
13    pub poll_us: u64,
14    /// TLS flush threshold in **daemon ticks**.
15    /// A Worker forces a TLS buffer flush when it detects that the Daemon
16    /// tick counter has advanced by at least this many ticks since the last
17    /// flush, regardless of buffer fill level.
18    ///
19    /// Rule of thumb: `flush_tick_threshold ≈ 1ms / poll_us`.
20    /// For poll_us = 1 000 µs (1 ms): threshold = 1.
21    /// For poll_us = 5 000 µs (5 ms): threshold = 1 (flush every 5 ms).
22    pub flush_tick_threshold: u64,
23}
24
25impl Config {
26    /// Budget-based constructor: specify RAM and TTL, the engine picks sizes.
27    pub fn with_memory_budget(ram_mb: usize, duration: u32) -> Self {
28        // Assume total overhead per item is ~128 bytes
29        let raw_capacity = (ram_mb * 1024 * 1024) / 128;
30        let capacity = raw_capacity.next_power_of_two().max(256);
31
32        Self {
33            capacity,
34            // T1 fits in L1 cache: max 2048 × 8-byte pointers = 16 KB
35            t1_slots: 2048,
36            // T2 intercepts warm data: 20% of capacity (80/20 rule)
37            t2_slots: (capacity / 5).next_power_of_two().max(4096),
38            duration,
39            #[cfg(feature = "std")]
40            threads: std::thread::available_parallelism()
41                .map(|p| p.get())
42                .unwrap_or(16),
43            #[cfg(not(feature = "std"))]
44            threads: 8,
45            poll_us: 1_000,
46            flush_tick_threshold: 1,
47        }
48    }
49
50    /// Expert constructor with explicit physical-law assertions.
51    pub fn new_expert(
52        capacity: usize,
53        t1_slots: usize,
54        t2_slots: usize,
55        duration: u32,
56        threads: usize,
57    ) -> Self {
58        // Physical Law 1: Bitmask routing requires powers of two
59        assert!(capacity.is_power_of_two(), "Capacity MUST be a power of two");
60        assert!(t1_slots.is_power_of_two(), "T1 slots MUST be a power of two");
61        assert!(t2_slots.is_power_of_two(), "T2 slots MUST be a power of two");
62
63        // Physical Law 2: T1 absolutely cannot exceed L1 cache
64        assert!(
65            t1_slots <= 4096,
66            "T1 size exceeds L1 Cache physical limits! Max slots: 4096"
67        );
68
69        Self {
70            capacity,
71            t1_slots,
72            t2_slots,
73            duration,
74            threads,
75            poll_us: 1_000,
76            flush_tick_threshold: 1,
77        }
78    }
79
80    /// Builder: set Daemon poll interval (1 000–10 000 µs).
81    pub fn with_poll_us(mut self, poll_us: u64) -> Self {
82        let clamped = if poll_us < 1_000 {
83            1_000
84        } else if poll_us > 10_000 {
85            10_000
86        } else {
87            poll_us
88        };
89        self.poll_us = clamped;
90        self
91    }
92
93    /// Builder: set TLS flush threshold in daemon ticks.
94    pub fn with_flush_tick_threshold(mut self, ticks: u64) -> Self {
95        self.flush_tick_threshold = if ticks < 1 { 1 } else { ticks };
96        self
97    }
98}