Skip to main content

cranpose_core/
runtime_cell.rs

1//! A value a copyable handle reaches in the runtime, without snapshots.
2
3use std::{fmt, hash::Hash, marker::PhantomData, rc::Rc};
4
5use crate::runtime::{self, RuntimeHandle, RuntimeId, StateId};
6
7/// A value kept in a runtime, reached through a handle that copies like a
8/// [`MutableState`](crate::MutableState).
9///
10/// A state holder that is `Copy` keeps its shared, interior-mutable parts
11/// here: a read finds the value in the runtime's cells, with no snapshot
12/// records to walk and no scope subscribed. Nothing recomposes when the value
13/// changes, so what a composition reads goes in a `MutableState` instead.
14///
15/// The value is owned as a state is: made while a
16/// [`remember`](crate::remember) builds its value, it belongs to that slot
17/// and goes with it; made anywhere else, it lives as long as the runtime.
18///
19/// ```
20/// use std::cell::Cell;
21///
22/// use cranpose_core::{DefaultScheduler, Runtime, RuntimeCell, scheduler_ref};
23///
24/// let runtime = Runtime::new(scheduler_ref(DefaultScheduler));
25/// let count = RuntimeCell::with_runtime(Cell::new(0), runtime.handle());
26/// let copy = count;
27/// copy.with(|count| count.set(count.get() + 1));
28/// assert_eq!(count.with(Cell::get), 1);
29/// ```
30pub struct RuntimeCell<T: 'static> {
31    id: StateId,
32    runtime_id: RuntimeId,
33    _marker: PhantomData<fn() -> T>,
34}
35
36impl<T: 'static> RuntimeCell<T> {
37    /// Keeps `value` in the current runtime. Panics without one.
38    pub fn new(value: T) -> Self {
39        Self::with_runtime(value, crate::hooks::current_runtime("RuntimeCell::new"))
40    }
41
42    /// Keeps `value` in `runtime`.
43    pub fn with_runtime(value: T, runtime: RuntimeHandle) -> Self {
44        Self {
45            id: runtime.alloc_owned_plain(value),
46            runtime_id: runtime.id(),
47            _marker: PhantomData,
48        }
49    }
50
51    /// Whether the value still lives: its owner and its runtime keep it.
52    pub fn is_alive(&self) -> bool {
53        self.try_with(|_| ()).is_some()
54    }
55
56    /// A handle to the value, or `None` once it is gone. The handle keeps the
57    /// value alive.
58    pub fn try_get(&self) -> Option<Rc<T>> {
59        runtime::with_state_arena_by_id(self.runtime_id, |arena| arena.plain::<T>(self.id))?
60    }
61
62    /// A handle to the value. Panics once it is gone.
63    pub fn get(&self) -> Rc<T> {
64        self.try_get()
65            .unwrap_or_else(|| panic!("runtime cell {:?} is no longer alive", self.id))
66    }
67
68    /// Runs `f` on the value, or returns `None` once it is gone.
69    pub fn try_with<R>(&self, f: impl FnOnce(&T) -> R) -> Option<R> {
70        self.try_get().map(|value| f(&value))
71    }
72
73    /// Runs `f` on the value. Panics once it is gone.
74    pub fn with<R>(&self, f: impl FnOnce(&T) -> R) -> R {
75        f(&self.get())
76    }
77}
78
79impl<T: 'static> Clone for RuntimeCell<T> {
80    fn clone(&self) -> Self {
81        *self
82    }
83}
84
85impl<T: 'static> Copy for RuntimeCell<T> {}
86
87impl<T: 'static> PartialEq for RuntimeCell<T> {
88    fn eq(&self, other: &Self) -> bool {
89        self.id == other.id && self.runtime_id == other.runtime_id
90    }
91}
92
93impl<T: 'static> Eq for RuntimeCell<T> {}
94
95impl<T: 'static> Hash for RuntimeCell<T> {
96    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
97        self.id.hash(state);
98        self.runtime_id.hash(state);
99    }
100}
101
102impl<T: 'static> fmt::Debug for RuntimeCell<T> {
103    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
104        f.debug_struct("RuntimeCell")
105            .field("id", &self.id)
106            .field("runtime_id", &self.runtime_id)
107            .finish()
108    }
109}