ferroui_base/utilities/
ref_countable.rs1use std::sync::atomic::{AtomicI32, Ordering};
2use std::sync::{Arc, Mutex};
3
4pub struct RefCountable;
6
7impl RefCountable {
8 pub fn create<T: ?Sized + 'static>(item: Arc<T>, release: impl FnOnce() + Send + 'static) -> RefCounted<T> {
11 RefCounted { item: Mutex::new(Some(item)), counter: Arc::new(RefCounter::new(Box::new(release))) }
12 }
13}
14
15struct RefCounter {
16 release: Mutex<Option<Box<dyn FnOnce() + Send>>>,
17 refs: AtomicI32,
18}
19
20impl RefCounter {
21 fn new(release: Box<dyn FnOnce() + Send>) -> Self {
22 Self { release: Mutex::new(Some(release)), refs: AtomicI32::new(1) }
23 }
24
25 fn try_add_ref(&self) -> bool {
26 let mut old = self.refs.load(Ordering::SeqCst);
27 loop {
28 if old == 0 {
29 return false;
30 }
31 match self.refs.compare_exchange(old, old + 1, Ordering::SeqCst, Ordering::SeqCst) {
32 Ok(_) => return true,
33 Err(current) => old = current,
34 }
35 }
36 }
37
38 fn release(&self) {
39 let old = self.refs.fetch_sub(1, Ordering::SeqCst);
40 if old == 1 {
41 let release = self.release.lock().unwrap().take();
42 if let Some(release) = release {
43 release();
44 }
45 }
46 }
47
48 fn ref_count(&self) -> i32 {
49 self.refs.load(Ordering::SeqCst)
50 }
51}
52
53pub struct RefCounted<T: ?Sized + 'static> {
62 item: Mutex<Option<Arc<T>>>,
63 counter: Arc<RefCounter>,
64}
65
66impl<T: ?Sized + 'static> RefCounted<T> {
67 pub fn dispose(&self) {
69 let item = self.item.lock().unwrap().take();
70 if item.is_some() {
71 self.counter.release();
72 }
73 }
74
75 pub fn item(&self) -> Arc<T> {
77 let item = self.item.lock().unwrap().clone();
79 item.unwrap_or_else(|| Self::disposed())
80 }
81
82 pub fn clone_ref(&self) -> RefCounted<T> {
85 self.clone_as(|item| item)
86 }
87
88 pub fn clone_as<TResult: ?Sized + 'static>(&self, cast: impl FnOnce(Arc<T>) -> Arc<TResult>) -> RefCounted<TResult> {
91 let item = self.item.lock().unwrap().clone();
94 let Some(item) = item else { Self::disposed() };
95
96 if !self.counter.try_add_ref() {
99 Self::disposed();
100 }
101
102 RefCounted { item: Mutex::new(Some(cast(item))), counter: self.counter.clone() }
103 }
104
105 pub fn is_alive(&self) -> bool {
107 self.item.lock().unwrap().is_some()
108 }
109
110 pub fn ref_count(&self) -> i32 {
113 if !self.is_alive() {
114 Self::disposed();
115 }
116 self.counter.ref_count()
117 }
118
119 fn disposed() -> ! {
120 panic!("Cannot access a disposed object: RefCounted<{}>", std::any::type_name::<T>())
121 }
122}
123
124impl<T: ?Sized + 'static> Drop for RefCounted<T> {
125 fn drop(&mut self) {
126 self.dispose();
127 }
128}
129
130#[cfg(test)]
131mod tests {
132 use super::*;
134
135 #[test]
136 fn releases_when_the_last_reference_is_disposed() {
137 let released = Arc::new(AtomicI32::new(0));
138 let r = released.clone();
139 let first = RefCountable::create(Arc::new(5), move || {
140 r.fetch_add(1, Ordering::SeqCst);
141 });
142 assert_eq!(1, first.ref_count());
143
144 let second = first.clone_ref();
145 assert_eq!(2, first.ref_count());
146 assert_eq!(5, *second.item());
147
148 first.dispose();
149 first.dispose();
150 assert!(!first.is_alive());
151 assert!(second.is_alive());
152 assert_eq!(1, second.ref_count());
153 assert_eq!(0, released.load(Ordering::SeqCst));
154
155 let third = second.clone_as(|item| item as Arc<dyn std::any::Any + Send + Sync>);
156 second.dispose();
157 assert_eq!(0, released.load(Ordering::SeqCst));
158 drop(third);
159 assert_eq!(1, released.load(Ordering::SeqCst));
160 }
161
162 #[test]
163 #[should_panic(expected = "disposed")]
164 fn item_of_a_disposed_reference_panics() {
165 let reference = RefCountable::create(Arc::new(1), || {});
166 reference.dispose();
167 reference.item();
168 }
169
170 #[test]
171 #[should_panic(expected = "disposed")]
172 fn cloning_a_disposed_reference_panics() {
173 let reference = RefCountable::create(Arc::new(1), || {});
174 reference.dispose();
175 reference.clone_ref();
176 }
177}