Skip to main content

sim_lib_mutation/
cell.rs

1use std::sync::{Arc, RwLock};
2
3use sim_kernel::{Cx, Error, Object, ObjectCompat, Result, Value};
4
5use crate::standard_mutate_capability;
6
7/// A shared, mutable cell holding a single [`Value`].
8///
9/// The cell is the base mutation handle: a cloneable, reference-counted slot
10/// whose [`set`](Cell::set) requires [`standard_mutate_capability`]. Reads are
11/// always allowed; writes fail closed without the capability.
12///
13/// # Examples
14///
15/// ```
16/// use std::sync::Arc;
17/// use sim_kernel::{Cx, DefaultFactory, NoopEvalPolicy};
18/// use sim_lib_mutation::{Cell, standard_mutate_capability};
19///
20/// let mut cx = Cx::new(Arc::new(NoopEvalPolicy), Arc::new(DefaultFactory));
21/// let old = cx.factory().string("old".to_owned()).unwrap();
22/// let cell = Cell::new(old);
23///
24/// // Writing fails closed until the mutate capability is granted.
25/// let new = cx.factory().string("new".to_owned()).unwrap();
26/// assert!(cell.set(&mut cx, new).is_err());
27///
28/// cx.grant(standard_mutate_capability());
29/// let new = cx.factory().string("new".to_owned()).unwrap();
30/// cell.set(&mut cx, new).unwrap();
31/// ```
32#[sim_citizen_derive::non_citizen(
33    reason = "mutable cell handle; reconstruct from the current value plus mutation policy",
34    kind = "handle",
35    descriptor = "core/Expr"
36)]
37#[derive(Clone)]
38pub struct Cell {
39    value: Arc<RwLock<Value>>,
40}
41
42impl Cell {
43    /// Create a cell initialized to `value`.
44    pub fn new(value: Value) -> Self {
45        Self {
46            value: Arc::new(RwLock::new(value)),
47        }
48    }
49
50    /// Return a clone of the current value. Reads need no capability.
51    pub fn get(&self) -> Result<Value> {
52        Ok(self
53            .value
54            .read()
55            .map_err(|_| Error::PoisonedLock("mutation cell"))?
56            .clone())
57    }
58
59    /// Overwrite the cell with `value`, requiring [`standard_mutate_capability`].
60    pub fn set(&self, cx: &mut Cx, value: Value) -> Result<()> {
61        cx.require(&standard_mutate_capability())?;
62        *self
63            .value
64            .write()
65            .map_err(|_| Error::PoisonedLock("mutation cell"))? = value;
66        Ok(())
67    }
68}
69
70impl Object for Cell {
71    fn display(&self, _cx: &mut Cx) -> Result<String> {
72        Ok("#<mutation-cell>".to_owned())
73    }
74
75    fn as_any(&self) -> &dyn std::any::Any {
76        self
77    }
78}
79
80impl ObjectCompat for Cell {}
81
82/// A mutable box: a single-slot mutation handle over a [`Cell`].
83///
84/// Distinct runtime type from [`Cell`] so boxes and cells present as separate
85/// objects, but with the same capability-gated read/write behavior.
86#[sim_citizen_derive::non_citizen(
87    reason = "mutable box handle; reconstruct from the current value plus mutation policy",
88    kind = "handle",
89    descriptor = "core/Expr"
90)]
91#[derive(Clone)]
92pub struct MutableBox {
93    cell: Cell,
94}
95
96impl MutableBox {
97    /// Create a box initialized to `value`.
98    pub fn new(value: Value) -> Self {
99        Self {
100            cell: Cell::new(value),
101        }
102    }
103
104    /// Return a clone of the boxed value. Reads need no capability.
105    pub fn get(&self) -> Result<Value> {
106        self.cell.get()
107    }
108
109    /// Overwrite the boxed value, requiring [`standard_mutate_capability`].
110    pub fn set(&self, cx: &mut Cx, value: Value) -> Result<()> {
111        self.cell.set(cx, value)
112    }
113}
114
115impl Object for MutableBox {
116    fn display(&self, _cx: &mut Cx) -> Result<String> {
117        Ok("#<mutation-box>".to_owned())
118    }
119
120    fn as_any(&self) -> &dyn std::any::Any {
121        self
122    }
123}
124
125impl ObjectCompat for MutableBox {}
126
127/// Construct a [`Cell`] initialized to `value` and wrap it as a runtime [`Value`].
128pub fn cell_value(cx: &mut Cx, value: Value) -> Result<Value> {
129    cx.factory().opaque(Arc::new(Cell::new(value)))
130}
131
132/// Construct a [`MutableBox`] initialized to `value` and wrap it as a runtime [`Value`].
133pub fn mutable_box_value(cx: &mut Cx, value: Value) -> Result<Value> {
134    cx.factory().opaque(Arc::new(MutableBox::new(value)))
135}