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), sim_kernel::HandleSeed::new(0xb7c0_5082_0987_e6bd));
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}