use std::{
collections::HashMap,
sync::{Arc, OnceLock, PoisonError, RwLock, RwLockReadGuard, RwLockWriteGuard},
};
use sim_kernel::{
Cx, Error, Expr, Object, ObjectEncode, ObjectEncoding, Result, Symbol, Table, Value,
id::CORE_TABLE_CLASS_ID, object::ClassRef,
};
use crate::citizen::lazy_table_class_symbol;
pub type ValueLoader = Arc<dyn Fn(&mut Cx) -> Result<Value> + Send + Sync>;
struct LazyEntry {
loader: ValueLoader,
cache: OnceLock<Result<Value>>,
}
impl LazyEntry {
fn eager(value: Value) -> Self {
let cache = OnceLock::new();
let _ = cache.set(Ok(value.clone()));
Self {
loader: Arc::new(move |_| Ok(value.clone())),
cache,
}
}
fn force(&self, cx: &mut Cx) -> Result<Value> {
if let Some(cached) = self.cache.get() {
return cached.clone();
}
let result = (self.loader)(cx);
let _ = self.cache.set(result.clone());
result
}
}
pub struct LazyTable {
entries: RwLock<HashMap<Symbol, Arc<LazyEntry>>>,
}
impl Clone for LazyTable {
fn clone(&self) -> Self {
Self {
entries: RwLock::new(
self.entries
.read()
.unwrap_or_else(PoisonError::into_inner)
.clone(),
),
}
}
}
impl LazyTable {
fn read(&self) -> Result<RwLockReadGuard<'_, HashMap<Symbol, Arc<LazyEntry>>>> {
self.entries
.read()
.map_err(|_| Error::Eval("table/lazy lock poisoned".into()))
}
fn write(&self) -> Result<RwLockWriteGuard<'_, HashMap<Symbol, Arc<LazyEntry>>>> {
self.entries
.write()
.map_err(|_| Error::Eval("table/lazy lock poisoned".into()))
}
pub fn new() -> Self {
Self {
entries: RwLock::new(HashMap::new()),
}
}
pub fn with_loaders(pairs: Vec<(Symbol, ValueLoader)>) -> Self {
let entries = pairs
.into_iter()
.map(|(key, loader)| {
(
key,
Arc::new(LazyEntry {
loader,
cache: OnceLock::new(),
}),
)
})
.collect();
Self {
entries: RwLock::new(entries),
}
}
pub fn with_entries(entries: Vec<(Symbol, Value)>) -> Self {
let entries = entries
.into_iter()
.map(|(key, value)| (key, Arc::new(LazyEntry::eager(value))))
.collect();
Self {
entries: RwLock::new(entries),
}
}
pub fn put_lazy(&self, key: Symbol, loader: ValueLoader) {
self.entries
.write()
.unwrap_or_else(PoisonError::into_inner)
.insert(
key,
Arc::new(LazyEntry {
loader,
cache: OnceLock::new(),
}),
);
}
fn descriptor_entries(&self, cx: &mut Cx) -> Result<Vec<(Symbol, Expr)>> {
let mut entries = self
.entries(cx)?
.into_iter()
.map(|(key, value)| Ok((key, value.object().as_expr(cx)?)))
.collect::<Result<Vec<_>>>()?;
entries.sort_by(|left, right| left.0.cmp(&right.0));
Ok(entries)
}
}
impl Default for LazyTable {
fn default() -> Self {
Self::new()
}
}
impl Object for LazyTable {
fn display(&self, _cx: &mut Cx) -> Result<String> {
Ok(format!("table/lazy[{}]", self.read()?.len()))
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
}
impl sim_kernel::ObjectCompat for LazyTable {
fn class(&self, cx: &mut Cx) -> Result<ClassRef> {
let symbol = lazy_table_class_symbol();
if let Some(value) = cx.registry().class_by_symbol(&symbol) {
return Ok(value.clone());
}
let symbol = Symbol::qualified("core", "Table");
if let Some(value) = cx.registry().class_by_symbol(&symbol) {
return Ok(value.clone());
}
cx.factory().class_stub(CORE_TABLE_CLASS_ID, symbol)
}
fn as_expr(&self, cx: &mut Cx) -> Result<Expr> {
self.as_table_expr(cx)
}
fn truth(&self, _cx: &mut Cx) -> Result<bool> {
Ok(!self.read()?.is_empty())
}
fn as_table_impl(&self) -> Option<&dyn Table> {
Some(self)
}
fn as_object_encoder(&self) -> Option<&dyn ObjectEncode> {
Some(self)
}
}
impl ObjectEncode for LazyTable {
fn object_encoding(&self, cx: &mut Cx) -> Result<ObjectEncoding> {
Ok(ObjectEncoding::Constructor {
class: lazy_table_class_symbol(),
args: vec![
Expr::Symbol(Symbol::new("v0")),
sim_table_core::citizen_fields::entries::encode(&self.descriptor_entries(cx)?),
],
})
}
}
impl sim_citizen::Citizen for LazyTable {
fn citizen_symbol() -> Symbol {
lazy_table_class_symbol()
}
fn citizen_version() -> u32 {
0
}
fn citizen_arity() -> usize {
1
}
fn citizen_fields() -> &'static [&'static str] {
&["entries"]
}
}
impl Table for LazyTable {
fn backend_symbol(&self) -> Symbol {
Symbol::qualified("table", "lazy")
}
fn get(&self, cx: &mut Cx, key: Symbol) -> Result<Value> {
match self.read()?.get(&key).cloned() {
Some(entry) => entry.force(cx),
None => cx.factory().nil(),
}
}
fn set(&self, _cx: &mut Cx, key: Symbol, value: Value) -> Result<()> {
self.write()?.insert(key, Arc::new(LazyEntry::eager(value)));
Ok(())
}
fn has(&self, _cx: &mut Cx, key: Symbol) -> Result<bool> {
Ok(self.read()?.contains_key(&key))
}
fn del(&self, cx: &mut Cx, key: Symbol) -> Result<Value> {
match self.write()?.remove(&key) {
Some(entry) => entry.force(cx),
None => cx.factory().nil(),
}
}
fn keys(&self, _cx: &mut Cx) -> Result<Vec<Symbol>> {
Ok(self.read()?.keys().cloned().collect())
}
fn entries(&self, cx: &mut Cx) -> Result<Vec<(Symbol, Value)>> {
let snapshot: Vec<(Symbol, Arc<LazyEntry>)> = self
.read()?
.iter()
.map(|(key, entry)| (key.clone(), entry.clone()))
.collect();
let mut out = Vec::with_capacity(snapshot.len());
for (key, entry) in snapshot {
out.push((key, entry.force(cx)?));
}
Ok(out)
}
fn len(&self, _cx: &mut Cx) -> Result<usize> {
Ok(self.read()?.len())
}
fn clear(&self, _cx: &mut Cx) -> Result<()> {
self.write()?.clear();
Ok(())
}
}