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}
24
25impl TransientMap {
26    pub fn new() -> TransientMap {
27        TransientMap {
28            map: Mutex::new(PersistentHashMap::empty()),
29            persisted: Mutex::new(false),
30            revision: AtomicU64::new(0),
31            keys_may_realize: AtomicBool::new(false),
32        }
33    }
34
35    pub fn new_from_map(map: &PersistentHashMap) -> TransientMap {
36        TransientMap {
37            map: Mutex::new(map.clone()),
38            persisted: Mutex::new(false),
39            revision: AtomicU64::new(0),
40            keys_may_realize: AtomicBool::new(
41                !(map.iter().all(|(key, _)| crate::realization::is_inert(key))),
42            ),
43        }
44    }
45
46    fn update(
47        &self,
48        key: &Value,
49        fast: impl FnOnce(&mut PersistentHashMap),
50        operation: impl Fn(&PersistentHashMap) -> PersistentHashMap,
51    ) -> ValueResult<()> {
52        {
53            let mut collection = self.map.lock().unwrap();
54            if *self.persisted.lock().unwrap() {
55                return Err(ValueError::TransientAlreadyPersisted);
56            }
57            if !self.keys_may_realize.load(Ordering::Relaxed) && crate::realization::is_inert(key) {
58                fast(&mut collection);
59                self.revision.fetch_add(1, Ordering::Relaxed);
60                return Ok(());
61            }
62        }
63        loop {
64            let (snapshot, revision) = {
65                let map = self.map.lock().unwrap();
66                if *self.persisted.lock().unwrap() {
67                    return Err(ValueError::TransientAlreadyPersisted);
68                }
69                (
70                    Snapshot::new(map.clone()),
71                    self.revision.load(Ordering::Relaxed),
72                )
73            };
74            let candidate = operation(snapshot.get());
75            let mut map = self.map.lock().unwrap();
76            if *self.persisted.lock().unwrap() {
77                return Err(ValueError::TransientAlreadyPersisted);
78            }
79            if self.revision.load(Ordering::Relaxed) != revision {
80                continue;
81            }
82            *map = candidate;
83            if !crate::realization::is_inert(key) {
84                self.keys_may_realize.store(true, Ordering::Relaxed);
85            }
86            self.revision.fetch_add(1, Ordering::Relaxed);
87            return Ok(());
88        }
89    }
90    pub fn assoc(&self, key: Value, value: Value) -> ValueResult<()> {
91        self.update(
92            &key,
93            |map| map.assoc_mut(key.clone(), value.clone()),
94            |map| map.assoc(key.clone(), value.clone()),
95        )
96    }
97    pub fn dissoc(&self, key: &Value) -> ValueResult<()> {
98        self.update(key, |map| map.dissoc_mut(key), |map| map.dissoc(key))
99    }
100
101    pub fn persistent(&self) -> ValueResult<PersistentHashMap> {
102        let map = self.map.lock().unwrap();
103        let mut persisted = self.persisted.lock().unwrap();
104        if *persisted {
105            return Err(ValueError::TransientAlreadyPersisted);
106        }
107        *persisted = true;
108        Ok(map.clone())
109    }
110
111    fn lookup<T>(&self, key: &Value, operation: impl Fn(&PersistentHashMap) -> T) -> T {
112        let map = self.map.lock().unwrap();
113        if !self.keys_may_realize.load(Ordering::Relaxed) && crate::realization::is_inert(key) {
114            return operation(&map);
115        }
116        let snapshot = Snapshot::new(map.clone());
117        drop(map);
118        operation(snapshot.get())
119    }
120    pub fn find(&self, key: &Value) -> Option<(Value, Value)> {
121        self.lookup(key, |map| {
122            map.get(key).map(|value| (key.clone(), value.clone()))
123        })
124    }
125    pub fn get(&self, key: &Value) -> Option<Value> {
126        self.lookup(key, |map| map.get(key).cloned())
127    }
128    pub fn contains_key(&self, key: &Value) -> bool {
129        self.lookup(key, |map| map.contains_key(key))
130    }
131
132    pub fn count(&self) -> usize {
133        let map = self.map.lock().unwrap();
134        map.count()
135    }
136}
137
138impl Clone for TransientMap {
139    fn clone(&self) -> Self {
140        Self {
141            map: Mutex::new(self.map.lock().unwrap().clone()),
142            persisted: Mutex::new(*self.persisted.lock().unwrap()),
143            revision: AtomicU64::new(0),
144            keys_may_realize: AtomicBool::new(self.keys_may_realize.load(Ordering::Relaxed)),
145        }
146    }
147}
148
149impl ClojureHash for TransientMap {
150    fn clojure_hash(&self) -> u32 {
151        let mut hash: u32 = 0;
152        let snapshot = Snapshot::new(self.map.lock().unwrap().clone());
153        for (k, v) in snapshot.get().iter() {
154            hash = hash_combine_unordered(
155                hash,
156                hash_combine_ordered(k.clojure_hash(), v.clojure_hash()),
157            );
158        }
159        hash
160    }
161}
162
163impl cljrs_gc::Trace for TransientMap {
164    fn trace(&self, visitor: &mut cljrs_gc::MarkVisitor) {
165        {
166            let map = self.map.lock().unwrap();
167            for (k, v) in map.iter() {
168                k.trace(visitor);
169                v.trace(visitor);
170            }
171        }
172    }
173
174    fn gc_size_extra(&self) -> usize {
175        let map = self.map.lock().unwrap();
176        map.full_size_extra()
177    }
178}
179
180impl Default for TransientMap {
181    fn default() -> Self {
182        Self::new()
183    }
184}