Skip to main content

sim_table_lazy/
lazy.rs

1//! The [`LazyTable`] object: a table whose entry values are produced on demand
2//! by [`ValueLoader`] closures and memoized after their first force, while
3//! satisfying the kernel table and object-encoding contracts.
4
5use std::{
6    collections::HashMap,
7    sync::{Arc, OnceLock, PoisonError, RwLock, RwLockReadGuard, RwLockWriteGuard},
8};
9
10use sim_kernel::{
11    Cx, Error, Expr, Object, ObjectEncode, ObjectEncoding, Result, Symbol, Table, Value,
12    id::CORE_TABLE_CLASS_ID, object::ClassRef,
13};
14
15use crate::citizen::lazy_table_class_symbol;
16
17/// A value loader, called at most once and memoized with its first result.
18pub type ValueLoader = Arc<dyn Fn(&mut Cx) -> Result<Value> + Send + Sync>;
19
20struct LazyEntry {
21    loader: ValueLoader,
22    cache: OnceLock<Result<Value>>,
23}
24
25impl LazyEntry {
26    fn eager(value: Value) -> Self {
27        let cache = OnceLock::new();
28        let _ = cache.set(Ok(value.clone()));
29        Self {
30            loader: Arc::new(move |_| Ok(value.clone())),
31            cache,
32        }
33    }
34
35    fn force(&self, cx: &mut Cx) -> Result<Value> {
36        if let Some(cached) = self.cache.get() {
37            return cached.clone();
38        }
39        let result = (self.loader)(cx);
40        let _ = self.cache.set(result.clone());
41        result
42    }
43}
44
45/// Table whose entry values are produced on demand by [`ValueLoader`] closures
46/// and memoized after their first force.
47///
48/// Each entry's loader runs at most once; the first result (value *or* error)
49/// is cached and returned for every later access. Metadata operations
50/// (`has`/`keys`/`len`) do not force loaders, while `get`/`del`/`entries` and
51/// encoding do. Implements the kernel [`Table`] contract and the
52/// object-encoding contracts, round-tripping through its
53/// [`LazyTableDescriptor`](crate::LazyTableDescriptor) citizen form. The entry
54/// map is guarded by an `RwLock`, so a `LazyTable` is shareable and mutable
55/// through a shared reference.
56pub struct LazyTable {
57    entries: RwLock<HashMap<Symbol, Arc<LazyEntry>>>,
58}
59
60impl Clone for LazyTable {
61    fn clone(&self) -> Self {
62        Self {
63            entries: RwLock::new(
64                self.entries
65                    .read()
66                    .unwrap_or_else(PoisonError::into_inner)
67                    .clone(),
68            ),
69        }
70    }
71}
72
73impl LazyTable {
74    fn read(&self) -> Result<RwLockReadGuard<'_, HashMap<Symbol, Arc<LazyEntry>>>> {
75        self.entries
76            .read()
77            .map_err(|_| Error::Eval("table/lazy lock poisoned".into()))
78    }
79
80    fn write(&self) -> Result<RwLockWriteGuard<'_, HashMap<Symbol, Arc<LazyEntry>>>> {
81        self.entries
82            .write()
83            .map_err(|_| Error::Eval("table/lazy lock poisoned".into()))
84    }
85
86    /// Construct an empty lazy table.
87    pub fn new() -> Self {
88        Self {
89            entries: RwLock::new(HashMap::new()),
90        }
91    }
92
93    /// Construct a lazy table whose entries are produced on demand by the given
94    /// loaders.
95    ///
96    /// Each loader runs at most once, on first access of its key, and its
97    /// result is memoized.
98    ///
99    /// # Examples
100    ///
101    /// ```
102    /// use std::sync::Arc;
103    /// use sim_kernel::{Cx, DefaultFactory, NoopEvalPolicy, Result, Symbol, Table, Value};
104    /// use sim_table_lazy::{LazyTable, ValueLoader};
105    ///
106    /// let mut cx = Cx::new(Arc::new(NoopEvalPolicy), Arc::new(DefaultFactory));
107    /// let loader: ValueLoader = Arc::new(|cx: &mut Cx| cx.factory().bool(true));
108    /// let table = LazyTable::with_loaders(vec![(Symbol::new("x"), loader)]);
109    ///
110    /// // Metadata does not force the loader.
111    /// assert!(table.has(&mut cx, Symbol::new("x")).unwrap());
112    /// // get forces it (once) and memoizes the result.
113    /// let forced = table.get(&mut cx, Symbol::new("x")).unwrap();
114    /// assert_eq!(forced, table.get(&mut cx, Symbol::new("x")).unwrap());
115    /// ```
116    pub fn with_loaders(pairs: Vec<(Symbol, ValueLoader)>) -> Self {
117        let entries = pairs
118            .into_iter()
119            .map(|(key, loader)| {
120                (
121                    key,
122                    Arc::new(LazyEntry {
123                        loader,
124                        cache: OnceLock::new(),
125                    }),
126                )
127            })
128            .collect();
129        Self {
130            entries: RwLock::new(entries),
131        }
132    }
133
134    /// Construct a lazy table pre-populated with already-computed values.
135    ///
136    /// Each entry is stored as an eager (pre-cached) loader, so accessing it
137    /// performs no further computation. Later entries with the same key
138    /// overwrite earlier ones.
139    pub fn with_entries(entries: Vec<(Symbol, Value)>) -> Self {
140        let entries = entries
141            .into_iter()
142            .map(|(key, value)| (key, Arc::new(LazyEntry::eager(value))))
143            .collect();
144        Self {
145            entries: RwLock::new(entries),
146        }
147    }
148
149    /// Insert (or replace) a lazily computed entry under `key`.
150    ///
151    /// The `loader` runs at most once, on first access of `key`, and its result
152    /// is memoized.
153    pub fn put_lazy(&self, key: Symbol, loader: ValueLoader) {
154        self.entries
155            .write()
156            .unwrap_or_else(PoisonError::into_inner)
157            .insert(
158                key,
159                Arc::new(LazyEntry {
160                    loader,
161                    cache: OnceLock::new(),
162                }),
163            );
164    }
165
166    fn descriptor_entries(&self, cx: &mut Cx) -> Result<Vec<(Symbol, Expr)>> {
167        let mut entries = self
168            .entries(cx)?
169            .into_iter()
170            .map(|(key, value)| Ok((key, value.object().as_expr(cx)?)))
171            .collect::<Result<Vec<_>>>()?;
172        entries.sort_by(|left, right| left.0.cmp(&right.0));
173        Ok(entries)
174    }
175}
176
177impl Default for LazyTable {
178    fn default() -> Self {
179        Self::new()
180    }
181}
182
183impl Object for LazyTable {
184    fn display(&self, _cx: &mut Cx) -> Result<String> {
185        Ok(format!("table/lazy[{}]", self.read()?.len()))
186    }
187
188    fn as_any(&self) -> &dyn std::any::Any {
189        self
190    }
191}
192
193impl sim_kernel::ObjectCompat for LazyTable {
194    fn class(&self, cx: &mut Cx) -> Result<ClassRef> {
195        let symbol = lazy_table_class_symbol();
196        if let Some(value) = cx.registry().class_by_symbol(&symbol) {
197            return Ok(value.clone());
198        }
199        let symbol = Symbol::qualified("core", "Table");
200        if let Some(value) = cx.registry().class_by_symbol(&symbol) {
201            return Ok(value.clone());
202        }
203        cx.factory().class_stub(CORE_TABLE_CLASS_ID, symbol)
204    }
205    fn as_expr(&self, cx: &mut Cx) -> Result<Expr> {
206        self.as_table_expr(cx)
207    }
208    fn truth(&self, _cx: &mut Cx) -> Result<bool> {
209        Ok(!self.read()?.is_empty())
210    }
211    fn as_table_impl(&self) -> Option<&dyn Table> {
212        Some(self)
213    }
214    fn as_object_encoder(&self) -> Option<&dyn ObjectEncode> {
215        Some(self)
216    }
217}
218
219impl ObjectEncode for LazyTable {
220    fn object_encoding(&self, cx: &mut Cx) -> Result<ObjectEncoding> {
221        Ok(ObjectEncoding::Constructor {
222            class: lazy_table_class_symbol(),
223            args: vec![
224                Expr::Symbol(Symbol::new("v0")),
225                sim_table_core::citizen_fields::entries::encode(&self.descriptor_entries(cx)?),
226            ],
227        })
228    }
229}
230
231impl sim_citizen::Citizen for LazyTable {
232    fn citizen_symbol() -> Symbol {
233        lazy_table_class_symbol()
234    }
235
236    fn citizen_version() -> u32 {
237        0
238    }
239
240    fn citizen_arity() -> usize {
241        1
242    }
243
244    fn citizen_fields() -> &'static [&'static str] {
245        &["entries"]
246    }
247}
248
249impl Table for LazyTable {
250    fn backend_symbol(&self) -> Symbol {
251        Symbol::qualified("table", "lazy")
252    }
253
254    fn get(&self, cx: &mut Cx, key: Symbol) -> Result<Value> {
255        match self.read()?.get(&key).cloned() {
256            Some(entry) => entry.force(cx),
257            None => cx.factory().nil(),
258        }
259    }
260
261    fn set(&self, _cx: &mut Cx, key: Symbol, value: Value) -> Result<()> {
262        self.write()?.insert(key, Arc::new(LazyEntry::eager(value)));
263        Ok(())
264    }
265
266    fn has(&self, _cx: &mut Cx, key: Symbol) -> Result<bool> {
267        Ok(self.read()?.contains_key(&key))
268    }
269
270    fn del(&self, cx: &mut Cx, key: Symbol) -> Result<Value> {
271        match self.write()?.remove(&key) {
272            Some(entry) => entry.force(cx),
273            None => cx.factory().nil(),
274        }
275    }
276
277    fn keys(&self, _cx: &mut Cx) -> Result<Vec<Symbol>> {
278        Ok(self.read()?.keys().cloned().collect())
279    }
280
281    fn entries(&self, cx: &mut Cx) -> Result<Vec<(Symbol, Value)>> {
282        let snapshot: Vec<(Symbol, Arc<LazyEntry>)> = self
283            .read()?
284            .iter()
285            .map(|(key, entry)| (key.clone(), entry.clone()))
286            .collect();
287        let mut out = Vec::with_capacity(snapshot.len());
288        for (key, entry) in snapshot {
289            out.push((key, entry.force(cx)?));
290        }
291        Ok(out)
292    }
293
294    fn len(&self, _cx: &mut Cx) -> Result<usize> {
295        Ok(self.read()?.len())
296    }
297
298    fn clear(&self, _cx: &mut Cx) -> Result<()> {
299        self.write()?.clear();
300        Ok(())
301    }
302}