Skip to main content

radiate_core/domain/sync/
cell.rs

1use std::{
2    cell::UnsafeCell,
3    fmt::{Debug, Formatter},
4    ops::Deref,
5    sync::atomic::{AtomicUsize, Ordering},
6};
7struct ArcInner<T> {
8    value: UnsafeCell<T>,
9    ref_count: AtomicUsize,
10}
11
12pub struct MutCell<T> {
13    inner: *const ArcInner<T>,
14    consumed: bool,
15}
16
17// Ensure MutCell<T> is safe to send/sync if T is
18unsafe impl<T: Send> Send for MutCell<T> {}
19unsafe impl<T: Sync> Sync for MutCell<T> {}
20
21impl<T> MutCell<T> {
22    pub fn new(value: T) -> Self {
23        Self {
24            inner: Box::into_raw(Box::new(ArcInner {
25                value: UnsafeCell::new(value),
26                ref_count: AtomicUsize::new(1),
27            })),
28            consumed: false,
29        }
30    }
31
32    pub fn is_unique(&self) -> bool {
33        // SAFETY: We're only reading the ref_count
34        unsafe { (*self.inner).ref_count.load(Ordering::Acquire) == 1 }
35    }
36
37    pub fn is_shared(&self) -> bool {
38        !self.is_unique()
39    }
40
41    pub fn strong_count(&self) -> usize {
42        // SAFETY: We're only reading the ref_count
43        unsafe { (*self.inner).ref_count.load(Ordering::Acquire) }
44    }
45
46    // There isn't much of a reason to implement the raw trait here. They would do the same thing.
47    // This code should be used very carefully anyways.
48    #[allow(clippy::should_implement_trait)]
49    pub fn borrow(&self) -> &T {
50        // SAFETY: This is inherently unsafe because we don't know if there exists a mutable
51        // reference to the inner value elsewhere.
52        //
53        // We assume that the caller has ensured that there are no mutable references
54        // to the inner value when calling this method. So straight up - make sure that you don't have
55        // any mutable references to the inner value when calling this method.
56        assert!(!self.consumed, "Cannot access consumed MutCell");
57        unsafe { &*(*self.inner).value.get() }
58    }
59
60    // There isn't much of a reason to implement the raw trait here. They would do the same thing.
61    // This code should be used very carefully anyways.
62    #[allow(clippy::should_implement_trait)]
63    pub fn borrow_mut(&mut self) -> &mut T {
64        assert!(self.is_unique(), "Cannot mutably borrow shared MutCell");
65        unsafe { &mut *(*self.inner).value.get() }
66    }
67
68    pub fn into_inner(mut self) -> T
69    where
70        T: Clone,
71    {
72        // SAFETY: If there is more than one reference to the
73        // inner value, we will clone it and decrement the ref count.
74        // If there is only one reference, we will consume the inner value and
75        // drop the inner box.
76        unsafe {
77            if (*self.inner).ref_count.load(Ordering::Acquire) == 1 {
78                self.consumed = true;
79                std::sync::atomic::fence(Ordering::SeqCst);
80                let boxed = Box::from_raw(self.inner as *mut ArcInner<T>);
81                boxed.value.into_inner()
82            } else {
83                let clone = (*(*self.inner).value.get()).clone();
84                (*self.inner).ref_count.fetch_sub(1, Ordering::Release);
85                clone
86            }
87        }
88    }
89}
90
91impl<T> Clone for MutCell<T> {
92    fn clone(&self) -> Self {
93        // SAFETY: We are only incrementing the ref_count here - no mutable access is done.
94        unsafe {
95            (*self.inner).ref_count.fetch_add(1, Ordering::Relaxed);
96        }
97        Self {
98            inner: self.inner,
99            consumed: false,
100        }
101    }
102}
103
104impl<T> Drop for MutCell<T> {
105    fn drop(&mut self) {
106        if self.consumed {
107            return;
108        }
109
110        // SAFETY: We are decrementing the ref_count here - no mutable access is done.
111        // If the ref_count reaches zero, we can safely drop the inner value.
112        unsafe {
113            if (*self.inner).ref_count.fetch_sub(1, Ordering::Release) == 1 {
114                std::sync::atomic::fence(Ordering::Acquire);
115                drop(Box::from_raw(self.inner as *mut ArcInner<T>));
116            }
117        }
118    }
119}
120
121impl<T> Deref for MutCell<T> {
122    type Target = T;
123    fn deref(&self) -> &Self::Target {
124        self.borrow()
125    }
126}
127
128impl<T: PartialEq> PartialEq for MutCell<T> {
129    fn eq(&self, other: &Self) -> bool {
130        self.borrow() == other.borrow()
131    }
132}
133
134impl<T: PartialOrd> PartialOrd for MutCell<T> {
135    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
136        self.borrow().partial_cmp(other.borrow())
137    }
138}
139
140impl<T> From<T> for MutCell<T> {
141    fn from(value: T) -> Self {
142        Self::new(value)
143    }
144}
145
146impl<T: Debug> Debug for MutCell<T> {
147    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
148        write!(f, "{:?}", self.borrow())
149    }
150}
151
152#[cfg(test)]
153mod tests {
154    use super::*;
155
156    #[test]
157    fn mutcell_basic_clone_and_mutation_updated() {
158        let mut cell = MutCell::new(5);
159        assert_eq!(*cell, 5);
160
161        // Mutate the cell (unique, so this is allowed)
162        *cell.borrow_mut() = 10;
163        assert_eq!(*cell, 10);
164
165        // Cloning happens only after mutation:
166        let cell2 = cell.clone();
167        // Now, getting a mutable reference from either cell will panic because it's no longer unique.
168        // Instead, test that both views see the update:
169        assert_eq!(*cell2, 10);
170    }
171
172    #[test]
173    fn mutcell_into_inner_unique() {
174        let cell = MutCell::new(String::from("hello"));
175        let inner = cell.into_inner();
176        assert_eq!(inner, "hello");
177    }
178
179    #[test]
180    fn mutcell_into_inner_clone_when_multiple() {
181        let cell = MutCell::new(String::from("hello"));
182        let cell2 = cell.clone();
183
184        let inner = cell.into_inner();
185        assert_eq!(inner, "hello");
186
187        // Drop cell2 to avoid leak
188        drop(cell2);
189    }
190
191    #[test]
192    fn mutcell_partial_eq_and_ord() {
193        let cell1 = MutCell::new(10);
194        let cell2 = MutCell::new(20);
195        let cell3 = MutCell::new(10);
196
197        assert!(cell1 == cell3);
198        assert!(cell1 != cell2);
199        assert!(cell1 < cell2);
200        assert!(cell2 > cell3);
201    }
202
203    #[test]
204    fn mutcell_is_unique_and_shared() {
205        let cell = MutCell::new(42);
206        assert!(cell.is_unique());
207
208        let cell2 = cell.clone();
209
210        assert!(cell.is_shared());
211        assert!(cell2.is_shared());
212        assert!(!cell.is_unique());
213        assert!(!cell2.is_unique());
214        assert_eq!(*cell, 42);
215        assert_eq!(*cell2, 42);
216        assert!(cell.borrow() == cell2.borrow());
217    }
218
219    #[test]
220    fn mut_cell_drop() {
221        let cell = MutCell::new(42);
222        {
223            let _cell2 = cell.clone();
224            assert!(cell.is_shared());
225        } // _cell2 goes out of scope, ref count should decrease
226
227        assert!(cell.is_unique());
228        drop(cell); // Should not panic
229    }
230
231    #[test]
232    fn mut_cell_deref() {
233        let mut cell = MutCell::new(42);
234        assert_eq!(*cell, 42);
235        let mut_ref = cell.borrow_mut();
236        *mut_ref = 100;
237        assert_eq!(*cell, 100);
238    }
239}