use std::{
collections::BTreeMap,
sync::{Arc, RwLock},
};
use sim_kernel::{Cx, Error, Expr, Object, ObjectCompat, Result, Value};
use crate::{RuntimeKey, RuntimeKeyPolicy, standard_mutate_capability};
#[sim_citizen_derive::non_citizen(
reason = "mutable runtime-keyed table handle; reconstruct from entries plus key policy",
kind = "handle",
descriptor = "core/Expr"
)]
pub struct MutableRuntimeTable<P> {
policy: P,
entries: RwLock<BTreeMap<RuntimeKey, Value>>,
}
impl<P: RuntimeKeyPolicy> MutableRuntimeTable<P> {
pub fn new(policy: P) -> Self {
Self {
policy,
entries: RwLock::new(BTreeMap::new()),
}
}
pub fn with_entries(cx: &mut Cx, policy: P, entries: Vec<(Value, Value)>) -> Result<Self> {
let table = Self::new(policy);
let mut keyed = BTreeMap::new();
for (key, value) in entries {
keyed.insert(table.key_for_write(cx, &key)?, value);
}
*table.write_entries()? = keyed;
Ok(table)
}
pub fn policy(&self) -> &P {
&self.policy
}
pub fn get(&self, cx: &mut Cx, key: &Value) -> Result<Option<Value>> {
let Some(key) = self.policy.key_for(cx, key)? else {
return Ok(None);
};
self.get_runtime_key(&key)
}
pub fn get_runtime_key(&self, key: &RuntimeKey) -> Result<Option<Value>> {
Ok(self.read_entries()?.get(key).cloned())
}
pub fn set(&self, cx: &mut Cx, key: Value, value: Value) -> Result<()> {
let key = self.key_for_write(cx, &key)?;
self.set_runtime_key(cx, key, value)
}
pub fn set_runtime_key(&self, cx: &mut Cx, key: RuntimeKey, value: Value) -> Result<()> {
cx.require(&standard_mutate_capability())?;
self.write_entries()?.insert(key, value);
Ok(())
}
pub fn del(&self, cx: &mut Cx, key: &Value) -> Result<Option<Value>> {
let Some(key) = self.policy.key_for(cx, key)? else {
return Ok(None);
};
self.del_runtime_key(cx, &key)
}
pub fn del_runtime_key(&self, cx: &mut Cx, key: &RuntimeKey) -> Result<Option<Value>> {
cx.require(&standard_mutate_capability())?;
Ok(self.write_entries()?.remove(key))
}
pub fn entries_in_key_order(&self) -> Result<Vec<(RuntimeKey, Value)>> {
Ok(self
.read_entries()?
.iter()
.map(|(key, value)| (key.clone(), value.clone()))
.collect())
}
pub fn len(&self) -> Result<usize> {
Ok(self.read_entries()?.len())
}
pub fn is_empty(&self) -> Result<bool> {
Ok(self.len()? == 0)
}
pub fn clear(&self, cx: &mut Cx) -> Result<()> {
cx.require(&standard_mutate_capability())?;
self.write_entries()?.clear();
Ok(())
}
fn key_for_write(&self, cx: &mut Cx, key: &Value) -> Result<RuntimeKey> {
self.policy
.key_for(cx, key)?
.ok_or_else(|| Error::Eval("runtime table key is not allowed by policy".to_owned()))
}
fn read_entries(&self) -> Result<std::sync::RwLockReadGuard<'_, BTreeMap<RuntimeKey, Value>>> {
self.entries
.read()
.map_err(|_| Error::PoisonedLock("runtime-keyed mutation table"))
}
fn write_entries(
&self,
) -> Result<std::sync::RwLockWriteGuard<'_, BTreeMap<RuntimeKey, Value>>> {
self.entries
.write()
.map_err(|_| Error::PoisonedLock("runtime-keyed mutation table"))
}
}
impl<P: RuntimeKeyPolicy + 'static> Object for MutableRuntimeTable<P> {
fn display(&self, _cx: &mut Cx) -> Result<String> {
Ok(format!("#<runtime-mutation-table {}>", self.len()?))
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
}
impl<P: RuntimeKeyPolicy + 'static> ObjectCompat for MutableRuntimeTable<P> {
fn as_expr(&self, cx: &mut Cx) -> Result<Expr> {
Ok(Expr::Map(
self.entries_in_key_order()?
.into_iter()
.map(|(key, value)| Ok((key.as_expr(), value.object().as_expr(cx)?)))
.collect::<Result<Vec<_>>>()?,
))
}
fn truth(&self, _cx: &mut Cx) -> Result<bool> {
Ok(!self.is_empty()?)
}
}
pub fn mutable_runtime_table<P>(
cx: &mut Cx,
policy: P,
entries: Vec<(Value, Value)>,
) -> Result<Value>
where
P: RuntimeKeyPolicy + 'static,
{
let table = MutableRuntimeTable::with_entries(cx, policy, entries)?;
cx.factory().opaque(Arc::new(table))
}
pub fn mutable_runtime_table_value<P>(value: &Value) -> Result<&MutableRuntimeTable<P>>
where
P: RuntimeKeyPolicy + 'static,
{
value
.object()
.downcast_ref::<MutableRuntimeTable<P>>()
.ok_or(Error::TypeMismatch {
expected: "runtime-keyed mutation table",
found: "non-table",
})
}