Skip to main content

dualcache_ff/static_cache/
static_cache.rs

1use core::hash::{BuildHasher, Hash, Hasher};
2use ahash::RandomState;
3
4/// A single cache slot in the direct-mapped StaticDualCache.
5/// Utilizes a lightweight spinlock and an UnsafeCell to manage
6/// exclusive mutable access to the underlying optional key-value pair.
7#[derive(Debug)]
8pub struct CacheSlot<K, V> {
9    lock: core::sync::atomic::AtomicBool,
10    data: core::cell::UnsafeCell<Option<(K, V)>>,
11}
12
13// UnsafeCell is not Sync by default. We manually implement Send/Sync
14// because the slot access is guarded by the atomic spinlock.
15unsafe impl<K: Send, V: Send> Send for CacheSlot<K, V> {}
16unsafe impl<K: Sync, V: Sync> Sync for CacheSlot<K, V> {}
17
18impl<K, V> CacheSlot<K, V> {
19    /// Create a new empty cache slot.
20    #[inline]
21    pub const fn new() -> Self {
22        Self {
23            lock: core::sync::atomic::AtomicBool::new(false),
24            data: core::cell::UnsafeCell::new(None),
25        }
26    }
27}
28
29/// A zero-allocation, completely `alloc`-free static concurrent cache.
30/// Suitable for ultra-constrained bare-metal environments (e.g. IoT, ESP32-C6).
31///
32/// Uses direct-mapped layout with const-generic capacity `N` and individual slot-level spinlocks.
33#[derive(Debug)]
34pub struct StaticDualCache<K, V, const N: usize, S = RandomState> {
35    hasher: S,
36    slots: [CacheSlot<K, V>; N],
37}
38
39// StaticDualCache implements Send/Sync if its components do.
40unsafe impl<K: Send, V: Send, const N: usize, S: Send> Send for StaticDualCache<K, V, N, S> {}
41unsafe impl<K: Sync, V: Sync, const N: usize, S: Sync> Sync for StaticDualCache<K, V, N, S> {}
42
43impl<K, V, const N: usize> StaticDualCache<K, V, N, RandomState> {
44    /// Create a new StaticDualCache instance with a default hasher.
45    #[inline]
46    pub fn new(_config: crate::Config) -> Self {
47        Self {
48            hasher: RandomState::new(),
49            slots: [const { CacheSlot::new() }; N],
50        }
51    }
52
53    /// Exposes API parity with `DualCacheFF::new_headless`
54    #[inline]
55    pub fn new_headless(_config: crate::Config) -> (Self, ()) {
56        (
57            Self::new(_config),
58            (),
59        )
60    }
61}
62
63impl<K, V, const N: usize, S> StaticDualCache<K, V, N, S>
64where
65    K: Hash + Eq + Clone,
66    V: Clone,
67    S: BuildHasher,
68{
69    /// Create a new StaticDualCache instance with a custom hasher.
70    #[inline]
71    pub fn with_hasher(hasher: S) -> Self {
72        Self {
73            hasher,
74            slots: [const { CacheSlot::new() }; N],
75        }
76    }
77
78    #[inline(always)]
79    fn get_slot_idx(&self, key: &K) -> usize {
80        let mut s = self.hasher.build_hasher();
81        key.hash(&mut s);
82        (s.finish() as usize) % N
83    }
84
85    /// Retrieve a cloned value associated with the key if it exists in the cache slot.
86    pub fn get(&self, key: &K) -> Option<V> {
87        let idx = self.get_slot_idx(key);
88        let slot = &self.slots[idx];
89
90        // Spin-lock the slot
91        while slot.lock.compare_exchange_weak(
92            false,
93            true,
94            core::sync::atomic::Ordering::Acquire,
95            core::sync::atomic::Ordering::Relaxed,
96        ).is_err() {
97            core::hint::spin_loop();
98        }
99
100        // Access UnsafeCell safely while holding the lock
101        let data = unsafe { &*slot.data.get() };
102        let res = match data {
103            Some((k, v)) if k == key => Some(v.clone()),
104            _ => None,
105        };
106
107        // Unlock
108        slot.lock.store(false, core::sync::atomic::Ordering::Release);
109
110        res
111    }
112
113    /// Insert or update a key-value pair in the mapped cache slot.
114    pub fn insert(&self, key: K, value: V) {
115        let idx = self.get_slot_idx(&key);
116        let slot = &self.slots[idx];
117
118        // Spin-lock the slot
119        while slot.lock.compare_exchange_weak(
120            false,
121            true,
122            core::sync::atomic::Ordering::Acquire,
123            core::sync::atomic::Ordering::Relaxed,
124        ).is_err() {
125            core::hint::spin_loop();
126        }
127
128        // Access UnsafeCell safely while holding the lock
129        let data = unsafe { &mut *slot.data.get() };
130        *data = Some((key, value));
131
132        // Unlock
133        slot.lock.store(false, core::sync::atomic::Ordering::Release);
134    }
135
136    /// Remove a key-value pair if the key matches the mapped slot.
137    pub fn remove(&self, key: &K) {
138        let idx = self.get_slot_idx(key);
139        let slot = &self.slots[idx];
140
141        // Spin-lock the slot
142        while slot.lock.compare_exchange_weak(
143            false,
144            true,
145            core::sync::atomic::Ordering::Acquire,
146            core::sync::atomic::Ordering::Relaxed,
147        ).is_err() {
148            core::hint::spin_loop();
149        }
150
151        // Access UnsafeCell safely while holding the lock
152        let data = unsafe { &mut *slot.data.get() };
153        if let Some((k, _)) = data {
154            if k == key {
155                *data = None;
156            }
157        }
158
159        // Unlock
160        slot.lock.store(false, core::sync::atomic::Ordering::Release);
161    }
162
163    /// Clear all cached slots.
164    pub fn clear(&self) {
165        for slot in &self.slots {
166            // Spin-lock the slot
167            while slot.lock.compare_exchange_weak(
168                false,
169                true,
170                core::sync::atomic::Ordering::Acquire,
171                core::sync::atomic::Ordering::Relaxed,
172            ).is_err() {
173                core::hint::spin_loop();
174            }
175
176            // Clear the data inside
177            let data = unsafe { &mut *slot.data.get() };
178            *data = None;
179
180            // Unlock
181            slot.lock.store(false, core::sync::atomic::Ordering::Release);
182        }
183    }
184
185    /// Synchronize the cache (no-op for static cache parity).
186    #[inline(always)]
187    pub fn sync(&self) {}
188}