Skip to main content

cljrs_value/collections/
transient_map.rs

1/*
2 * transient_map.rs -- implementation of transient maps.
3 * Copyright (C) 2026 Casey Marshall
4 *
5 * This file is licensed under the Eclipse Public License, Version 1,
6 * the same license as Clojure.
7 */
8
9use super::Snapshot;
10use crate::hash::{hash_combine_ordered, hash_combine_unordered};
11use crate::{ClojureHash, PersistentHashMap, Value, ValueError, ValueResult};
12use std::sync::{
13    Mutex,
14    atomic::{AtomicBool, AtomicU64, Ordering},
15};
16
17#[derive(Debug)]
18pub struct TransientMap {
19    map: Mutex<PersistentHashMap>,
20    persisted: Mutex<bool>,
21    revision: AtomicU64,
22    keys_may_realize: AtomicBool,
23    #[cfg(feature = "no-gc")]
24    initial: std::cell::RefCell<crate::publish::ValueLease>,
25}
26
27impl TransientMap {
28    pub fn new() -> TransientMap {
29        TransientMap {
30            map: Mutex::new(PersistentHashMap::empty()),
31            persisted: Mutex::new(false),
32            revision: AtomicU64::new(0),
33            #[cfg(feature = "no-gc")]
34            initial: Default::default(),
35            keys_may_realize: AtomicBool::new(false),
36        }
37    }
38
39    pub fn new_from_map(map: &PersistentHashMap) -> TransientMap {
40        TransientMap {
41            map: Mutex::new(map.clone()),
42            persisted: Mutex::new(false),
43            revision: AtomicU64::new(0),
44            #[cfg(feature = "no-gc")]
45            initial: std::cell::RefCell::new(crate::publish::ValueLease::for_trace(
46                map,
47                &Default::default(),
48            )),
49            keys_may_realize: AtomicBool::new(!map.keys_inert()),
50        }
51    }
52
53    fn update<T>(
54        &self,
55        key: &Value,
56        fast: impl FnOnce(&mut PersistentHashMap) -> T,
57        operation: impl Fn(&PersistentHashMap) -> (PersistentHashMap, T),
58    ) -> ValueResult<T> {
59        {
60            let mut collection = self.map.lock().unwrap();
61            if *self.persisted.lock().unwrap() {
62                return Err(ValueError::TransientAlreadyPersisted);
63            }
64            if !self.keys_may_realize.load(Ordering::Relaxed) && crate::realization::is_inert(key) {
65                let result = fast(&mut collection);
66                self.revision.fetch_add(1, Ordering::Relaxed);
67                return Ok(result);
68            }
69        }
70        loop {
71            let (snapshot, revision) = {
72                let map = self.map.lock().unwrap();
73                if *self.persisted.lock().unwrap() {
74                    return Err(ValueError::TransientAlreadyPersisted);
75                }
76                (
77                    Snapshot::new(map.clone()),
78                    self.revision.load(Ordering::Relaxed),
79                )
80            };
81            let (candidate, result) = operation(snapshot.get());
82            let mut map = self.map.lock().unwrap();
83            if *self.persisted.lock().unwrap() {
84                return Err(ValueError::TransientAlreadyPersisted);
85            }
86            if self.revision.load(Ordering::Relaxed) != revision {
87                continue;
88            }
89            *map = candidate;
90            if !crate::realization::is_inert(key) {
91                self.keys_may_realize.store(true, Ordering::Relaxed);
92            }
93            self.revision.fetch_add(1, Ordering::Relaxed);
94            return Ok(result);
95        }
96    }
97    pub fn assoc(&self, key: Value, value: Value) -> ValueResult<()> {
98        #[cfg(feature = "no-gc")]
99        let (key, _key_owner) = crate::publish::prepare_for_owner(key, &Default::default());
100        #[cfg(feature = "no-gc")]
101        let (value, _value_owner) = crate::publish::prepare_for_owner(value, &Default::default());
102        let entry = self.update(
103            &key,
104            |map| {
105                map.assoc_mut(key.clone(), value.clone());
106                map.storage_entry(&key).unwrap()
107            },
108            |map| {
109                let candidate = map.assoc(key.clone(), value.clone());
110                let entry = candidate.storage_entry(&key).unwrap();
111                (candidate, entry)
112            },
113        )?;
114        #[cfg(feature = "no-gc")]
115        if !cljrs_gc::alloc_ctx::invocation_is_active() {
116            let lease = cljrs_gc::region::RegionLease::for_values(&[entry.1, value]);
117            cljrs_gc::region::AllocationOwner::for_address(self as *const _ as usize).set_slot(
118                self as *const _ as usize,
119                entry.0,
120                &lease,
121            );
122        }
123        #[cfg(not(feature = "no-gc"))]
124        let _ = entry;
125        Ok(())
126    }
127    pub fn dissoc(&self, key: &Value) -> ValueResult<()> {
128        let entry = self.update(
129            key,
130            |map| {
131                let entry = map.storage_entry(key);
132                map.dissoc_mut(key);
133                entry
134            },
135            |map| (map.dissoc(key), map.storage_entry(key)),
136        )?;
137        #[cfg(feature = "no-gc")]
138        if let Some((slot, _)) = entry {
139            crate::publish::publish_for_slot(Value::Nil, self as *const _ as usize, slot);
140        }
141        #[cfg(not(feature = "no-gc"))]
142        let _ = entry;
143        Ok(())
144    }
145
146    pub fn persistent(&self) -> ValueResult<PersistentHashMap> {
147        let map = self.map.lock().unwrap();
148        let mut persisted = self.persisted.lock().unwrap();
149        if *persisted {
150            return Err(ValueError::TransientAlreadyPersisted);
151        }
152        *persisted = true;
153        let result = map.clone();
154        #[cfg(feature = "no-gc")]
155        {
156            let address = self as *const _ as usize;
157            if cljrs_gc::region::AllocationOwner::for_address(address).is_permanent() {
158                // Rust stack containers do not have a containing region to lease.
159                cljrs_gc::region::RegionLease::for_value(&result).protect_read();
160            } else {
161                cljrs_gc::region::RegionLease::for_address(address).protect_read();
162            }
163        }
164        Ok(result)
165    }
166
167    fn lookup<T>(&self, key: &Value, operation: impl Fn(&PersistentHashMap) -> T) -> T {
168        let map = self.map.lock().unwrap();
169        if !self.keys_may_realize.load(Ordering::Relaxed) && crate::realization::is_inert(key) {
170            return operation(&map);
171        }
172        let snapshot = Snapshot::new(map.clone());
173        drop(map);
174        operation(snapshot.get())
175    }
176    pub fn find(&self, key: &Value) -> Option<(Value, Value)> {
177        self.lookup(key, |map| {
178            map.get(key).map(|value| {
179                #[cfg(feature = "no-gc")]
180                cljrs_gc::region::RegionLease::for_values(&[key.clone(), value.clone()])
181                    .protect_read();
182                (key.clone(), value.clone())
183            })
184        })
185    }
186    pub fn get(&self, key: &Value) -> Option<Value> {
187        self.lookup(key, |map| {
188            let value = map.get(key).cloned();
189            #[cfg(feature = "no-gc")]
190            if let Some(value) = &value {
191                cljrs_gc::region::RegionLease::for_value(value).protect_read();
192            }
193            value
194        })
195    }
196    pub fn contains_key(&self, key: &Value) -> bool {
197        self.lookup(key, |map| map.contains_key(key))
198    }
199
200    pub fn count(&self) -> usize {
201        let map = self.map.lock().unwrap();
202        map.count()
203    }
204}
205
206impl Clone for TransientMap {
207    fn clone(&self) -> Self {
208        Self {
209            map: Mutex::new(self.map.lock().unwrap().clone()),
210            persisted: Mutex::new(*self.persisted.lock().unwrap()),
211            revision: AtomicU64::new(0),
212            #[cfg(feature = "no-gc")]
213            initial: std::cell::RefCell::new(crate::publish::ValueLease::for_trace(
214                &self.map.lock().unwrap().clone(),
215                &Default::default(),
216            )),
217            keys_may_realize: AtomicBool::new(self.keys_may_realize.load(Ordering::Relaxed)),
218        }
219    }
220}
221
222impl ClojureHash for TransientMap {
223    fn clojure_hash(&self) -> u32 {
224        let mut hash: u32 = 0;
225        let snapshot = Snapshot::new(self.map.lock().unwrap().clone());
226        for (k, v) in snapshot.get().iter() {
227            hash = hash_combine_unordered(
228                hash,
229                hash_combine_ordered(k.clojure_hash(), v.clojure_hash()),
230            );
231        }
232        hash
233    }
234}
235
236impl cljrs_gc::Trace for TransientMap {
237    #[cfg(feature = "no-gc")]
238    fn init_ownership(&self) {
239        let address = self as *const _ as usize;
240        let owner = cljrs_gc::region::AllocationOwner::for_address(address);
241        let lease = self.initial.borrow().reparent(&owner);
242        *self.initial.borrow_mut() = lease;
243    }
244    #[cfg(feature = "no-gc")]
245    fn trace(&self, _: &mut cljrs_gc::MarkVisitor) {}
246    #[cfg(not(feature = "no-gc"))]
247    fn trace(&self, visitor: &mut cljrs_gc::MarkVisitor) {
248        {
249            let map = self.map.lock().unwrap();
250            for (k, v) in map.iter() {
251                k.trace(visitor);
252                v.trace(visitor);
253            }
254        }
255    }
256
257    fn gc_size_extra(&self) -> usize {
258        let map = self.map.lock().unwrap();
259        map.full_size_extra()
260    }
261}
262
263impl Default for TransientMap {
264    fn default() -> Self {
265        Self::new()
266    }
267}
268
269impl Drop for TransientMap {
270    fn drop(&mut self) {
271        crate::publish::clear_slots(self as *const _ as usize);
272    }
273}