overlay_map/lib.rs
1//! A two-layered map structure where the foreground is mutable and the
2//! background is preserved.
3//!
4//! OverlayMap lets you insert values without cloning, while keeping a single
5//! layer of historical context. Each key has a current value (foreground) and
6//! may have a previous value (background), which is automatically managed
7//! during updates.
8//!
9//! ```rust
10//! use overlay_map::OverlayMap;
11//!
12//! #[derive(Debug, Clone, PartialEq)]
13//! struct QuantumBit {
14//! collapsed: bool,
15//! value: Option<bool>,
16//! }
17//!
18//! /// Simulates measurement collapse
19//! fn collapse(mut qbit: QuantumBit) -> QuantumBit {
20//! qbit.collapsed = true;
21//! qbit.value = Some(rand::random());
22//! qbit
23//! }
24//!
25//! fn main() {
26//! let mut qstate = OverlayMap::<&str, QuantumBit>::new();
27//!
28//! // Push an uncollapsed qubit
29//! qstate.push(
30//! "qbit_1",
31//! QuantumBit {
32//! collapsed: false,
33//! value: None,
34//! },
35//! );
36//!
37//! // Observe the qubit: only collapse if it's not already
38//! let did_observe = qstate.push_if(&"qbit_1", |current| {
39//! if current.collapsed {
40//! None // already collapsed, don't change
41//! } else {
42//! Some(collapse(current.clone())) // clone *only* if needed
43//! }
44//! });
45//!
46//! println!("Was observed? {}", did_observe);
47//! println!("After observation: {:?}", qstate.fg(&"qbit_1"));
48//! println!("Before observation: {:?}", qstate.bg(&"qbit_1"));
49//! }
50//! ```
51
52use std::{
53 collections::{HashMap, hash_map::RandomState},
54 hash::{BuildHasher, Hash},
55 mem::MaybeUninit,
56};
57
58/// A two-layered map where each key has a mutable foreground and an optional
59/// background value.
60///
61/// When inserting a new value for a key, the previous value (if any) is
62/// automatically moved to the background. Background values are preserved but
63/// not cloned.
64///
65/// This map is not thread-safe for mutation. It may be shared across threads
66/// for read-only access.
67#[derive(Debug, Default)]
68pub struct OverlayMap<K, V, S = RandomState>
69where
70 K: Eq + Hash,
71{
72 map: HashMap<K, Entry<V>, S>,
73}
74
75unsafe impl<K, V, S> Sync for OverlayMap<K, V, S>
76where
77 K: Eq + Hash + Sync,
78 S: Sync,
79{
80}
81
82impl<K, V, S> OverlayMap<K, V, S>
83where
84 K: Eq + Hash,
85 S: BuildHasher + Default,
86{
87 /// Creates a new, empty `OverlayMap` with the default hasher.
88 pub fn new() -> Self {
89 Self::with_hasher(Default::default())
90 }
91
92 /// Creates an empty `OverlayMap` with the specified capacity and default hasher.
93 pub fn with_capacity(capacity: usize) -> Self {
94 Self::with_capacity_and_hasher(capacity, Default::default())
95 }
96
97 /// Creates an empty `OverlayMap` that will use the given hasher.
98 pub fn with_hasher(hasher: S) -> Self {
99 Self {
100 map: HashMap::with_hasher(hasher),
101 }
102 }
103
104 /// Creates an empty `OverlayMap` with the specified capacity and hasher.
105 pub fn with_capacity_and_hasher(capacity: usize, hasher: S) -> Self {
106 Self {
107 map: HashMap::with_capacity_and_hasher(capacity, hasher),
108 }
109 }
110
111 /// Number of unique keys in the map.
112 pub fn len(&self) -> usize {
113 self.map.len()
114 }
115
116 /// Check if the map is empty.
117 pub fn is_empty(&self) -> bool {
118 self.map.is_empty()
119 }
120
121 /// Get an immutable reference to the value associated with the key.
122 ///
123 /// Returns `None` if the key was not found in the map.
124 pub fn fg(&self, key: &K) -> Option<&V> {
125 self.map.get(key).map(|entry| entry.fg())
126 }
127
128 /// Get an immutable reference to the value associated with the key in the background layer.
129 ///
130 /// Returns `None` if the key was not found in the background layer.
131 pub fn bg(&self, key: &K) -> Option<&V> {
132 self.map.get(key).and_then(|entry| entry.bg())
133 }
134
135 /// Push a value into the foreground layer, preserving the previous value in
136 /// the background.
137 ///
138 /// If the key was already present, the current foreground is moved to the
139 /// background slot, and the new value becomes the new foreground. No
140 /// cloning occurs. The old background value is dropped if it was present.
141 ///
142 /// Returns `true` if there was already a foreground value (i.e. a
143 /// background now exists).
144 pub fn push(&mut self, key: K, value: V) -> bool {
145 if let Some(entry) = self.map.get_mut(&key) {
146 entry.push(value);
147 true
148 } else {
149 self.map.insert(key, Entry::new(value));
150 false
151 }
152 }
153
154 /// Conditionally push a new value into the foreground based on the current
155 /// value.
156 ///
157 /// If the key exists, the current foreground value is passed to the
158 /// predicate. If the predicate returns `Some(new_val)`, the new value is
159 /// pushed and the old one is preserved in the background. If it returns
160 /// `None`, nothing is changed.
161 ///
162 /// Returns `true` if a new value was pushed.
163 pub fn push_if<F>(&mut self, key: &K, predicate: F) -> bool
164 where
165 F: FnOnce(&V) -> Option<V>,
166 {
167 let entry = match self.map.get_mut(key) {
168 Some(e) => e,
169 None => return false,
170 };
171
172 match predicate(entry.fg()) {
173 Some(new) => {
174 entry.push(new);
175 true
176 }
177 None => false,
178 }
179 }
180
181 /// Overlay multiple values onto the map.
182 ///
183 /// Each key-value pair is pushed into the foreground layer. If the key was
184 /// already present, the existing foreground value is moved to the
185 /// background. This does not clone or retain old values beyond the
186 /// background layer.
187 ///
188 /// Returns the number of keys that already existed (i.e. pushes that
189 /// replaced a foreground).
190 pub fn overlay<I>(&mut self, iter: I) -> usize
191 where
192 I: IntoIterator<Item = (K, V)>,
193 {
194 let mut replaced = 0;
195
196 for (key, val) in iter {
197 replaced += self.push(key, val) as usize;
198 }
199
200 replaced
201 }
202}
203
204const SLOT0_PRESENT: u8 = 1 << 0;
205const SLOT1_PRESENT: u8 = 1 << 1;
206const FG_SLOT: u8 = 1 << 2;
207
208/// Internal value container for a key in `OverlayMap`, holding up to two
209/// versions.
210///
211/// Each `Entry` stores a foreground value and optionally a background value.
212/// Used to track changes without cloning.
213#[derive(Debug)]
214struct Entry<T> {
215 bits: u8,
216 slots: [MaybeUninit<T>; 2],
217}
218
219impl<T> Entry<T> {
220 fn new(val: T) -> Self {
221 Self {
222 bits: SLOT0_PRESENT,
223 slots: [MaybeUninit::new(val), MaybeUninit::uninit()],
224 }
225 }
226
227 #[inline]
228 fn fg_index(&self) -> usize {
229 ((self.bits & FG_SLOT) >> 2) as usize
230 }
231
232 #[inline]
233 fn bg_index(&self) -> usize {
234 self.fg_index() ^ 1
235 }
236
237 fn fg(&self) -> &T {
238 let idx = self.fg_index();
239 unsafe { self.slots[idx].assume_init_ref() }
240 }
241
242 fn bg(&self) -> Option<&T> {
243 let idx = self.bg_index();
244 if self.is_slot_present(idx) {
245 Some(unsafe { self.slots[idx].assume_init_ref() })
246 } else {
247 None
248 }
249 }
250
251 #[inline]
252 fn is_slot_present(&self, idx: usize) -> bool {
253 (self.bits & (1 << idx)) != 0
254 }
255
256 /// Moves the current foreground value to the background slot, dropping any
257 /// previous background.
258 ///
259 /// This operation updates only internal bits and memory slots. The value
260 /// itself is not cloned or moved in memory. If a background value already
261 /// exists, it is dropped before being replaced.
262 #[inline]
263 fn push_fg_to_bg(&mut self) {
264 let bgi = self.bg_index();
265
266 // If the background slot is present, drop it to avoid memory leaks
267 if self.is_slot_present(bgi) {
268 unsafe {
269 self.slots[bgi].assume_init_drop();
270 }
271 }
272
273 // Flip the foreground/background logical mapping
274 self.bits |= 1 << bgi;
275 self.bits ^= FG_SLOT;
276 }
277
278 /// Insert a new value into the foreground, pushing the old one into the
279 /// background.
280 ///
281 /// This is called after `push_fg_to_bg` flips the active slot.
282 #[inline]
283 fn push(&mut self, val: T) {
284 self.push_fg_to_bg();
285 let idx = self.fg_index();
286 self.slots[idx] = MaybeUninit::new(val);
287 self.bits |= 1 << idx;
288 }
289}
290
291impl<V> Drop for Entry<V> {
292 fn drop(&mut self) {
293 if (self.bits & SLOT0_PRESENT) != 0 {
294 unsafe { self.slots[0].assume_init_drop() };
295 }
296
297 if (self.bits & SLOT1_PRESENT) != 0 {
298 unsafe { self.slots[1].assume_init_drop() };
299 }
300 }
301}
302
303#[cfg(test)]
304mod tests {
305 use super::*;
306
307 #[test]
308 fn push_and_get_foreground() {
309 let mut map = OverlayMap::<&str, i32>::new();
310 assert!(map.fg(&"key").is_none());
311 map.push("key", 42);
312 assert_eq!(*map.fg(&"key").expect("Entry was just pushed"), 42);
313 }
314
315 #[test]
316 fn push_and_get_background() {
317 let mut map = OverlayMap::<&str, i32>::new();
318 assert!(map.fg(&"key").is_none());
319 map.push("key", 99);
320 assert_eq!(*map.fg(&"key").expect("Entry was just pushed"), 99);
321 }
322
323 #[test]
324 fn push_if_no_change_foreground() {
325 let mut map = OverlayMap::<&str, i32>::new();
326 map.push("key", 100);
327
328 // Try swap but do nothing
329 map.push_if(&"key", |old| {
330 if *old == 100 {
331 None // no change
332 } else {
333 Some(999)
334 }
335 });
336
337 // Still foreground = 100, no background
338 assert_eq!(*map.fg(&"key").expect("Entry should still exist"), 100);
339 }
340
341 #[test]
342 fn push_if_foreground_to_background() {
343 let mut map = OverlayMap::<&str, i32>::new();
344 map.push("key", 50);
345 map.push_if(&"key", |old| if *old == 50 { Some(123) } else { None });
346 assert_eq!(*map.fg(&"key").expect("Entry should still exist"), 123);
347 assert_eq!(
348 *map.bg(&"key").expect("Old value not found in background"),
349 50
350 );
351 }
352
353 #[test]
354 fn push_if_missing_key() {
355 let mut map = OverlayMap::<&str, i32>::new();
356 map.push_if(&"missing", |_| Some(999));
357 assert!(map.fg(&"missing").is_none());
358 }
359
360 #[test]
361 fn overlay_test() {
362 // Initialize an OverlayMap with:
363 // - "fg_key" in foreground
364 // - "bg_key" in background
365 // - "absent_key" absent
366 let mut map = OverlayMap::<&str, i32>::new();
367 map.push("fg_key", 10);
368 map.push("bg_key", 20);
369
370 // Prepare updates:
371 // - "fg_key" -> 100
372 // - "bg_key" -> 200
373 // - "none_key" -> 300 (was absent in map)
374 let updates = vec![("fg_key", 100), ("bg_key", 200), ("none_key", 300)];
375
376 // Perform the merge
377 map.overlay(updates);
378
379 // Check that "fg_key" was in foreground, so old value (10) moved to background.
380 // New value (100) should be in foreground.
381 match map.fg(&"fg_key") {
382 Some(val) => assert_eq!(*val, 100),
383 _ => panic!("Expected 'fg_key' = 100 in foreground"),
384 }
385 match map.bg(&"fg_key") {
386 Some(val) => assert_eq!(*val, 10),
387 None => panic!("Expected old 'fg_key' value = 10 in background"),
388 }
389
390 // Check that "none_key" was absent, so it is now in the foreground with 300
391 match map.fg(&"none_key") {
392 Some(val) => assert_eq!(*val, 300),
393 _ => panic!("Expected 'none_key' = 300 in foreground"),
394 }
395 // It shouldn't exist in background
396 assert!(map.bg(&"none_key").is_none());
397 }
398}