1use starnix_sync::{ExpandoLock, LockDepMutex};
6use std::any::{Any, TypeId};
7use std::collections::BTreeMap;
8use std::marker::{Send, Sync};
9use std::ops::Deref;
10use std::sync::Arc;
11
12#[derive(Debug)]
16struct ExpandoSlot {
17 value: Arc<dyn Any + Send + Sync>,
18}
19
20impl ExpandoSlot {
21 fn new(value: Arc<dyn Any + Send + Sync>) -> Self {
22 ExpandoSlot { value }
23 }
24
25 fn downcast<T: Any + Send + Sync>(&self) -> Option<Arc<T>> {
26 self.value.clone().downcast::<T>().ok()
27 }
28}
29
30#[derive(Debug, Default)]
39pub struct Expando {
40 properties: LockDepMutex<BTreeMap<TypeId, ExpandoSlot>, ExpandoLock>,
41}
42
43impl Expando {
44 pub fn get<T: Any + Send + Sync + Default + 'static>(&self) -> Arc<T> {
49 self.get_or_init(T::default)
50 }
51
52 pub fn get_or_init<T: Any + Send + Sync + 'static>(&self, init: impl FnOnce() -> T) -> Arc<T> {
58 self.get_or_try_init::<T, ()>(|| Ok(init())).expect("infallible initializer")
59 }
60
61 pub fn get_or_try_init<T: Any + Send + Sync + 'static, E>(
67 &self,
68 try_init: impl FnOnce() -> Result<T, E>,
69 ) -> Result<Arc<T>, E> {
70 let type_id = TypeId::of::<T>();
71
72 if let Some(slot) = self.properties.lock().get(&type_id) {
75 assert_eq!(type_id, slot.value.deref().type_id());
76 return Ok(slot.downcast().expect("downcast of expando slot was successful"));
77 }
78
79 let newly_init = Arc::new(try_init()?);
81
82 let mut properties = self.properties.lock();
84 let slot = properties.entry(type_id).or_insert_with(|| ExpandoSlot::new(newly_init));
85 assert_eq!(type_id, slot.value.deref().type_id());
86 Ok(slot.downcast().expect("downcast of expando slot was successful"))
87 }
88
89 pub fn peek<T: Any + Send + Sync + 'static>(&self) -> Option<Arc<T>> {
92 let properties = self.properties.lock();
93 let type_id = TypeId::of::<T>();
94 let slot = properties.get(&type_id)?;
95 assert_eq!(type_id, slot.value.deref().type_id());
96 Some(slot.downcast().expect("downcast of expando slot was successful"))
97 }
98
99 pub fn remove<T: Any + Send + Sync + 'static>(&self) -> Option<Arc<T>> {
101 let mut properties = self.properties.lock();
102 let type_id = TypeId::of::<T>();
103 let slot = properties.remove(&type_id)?;
104 assert_eq!(type_id, slot.value.deref().type_id());
105 Some(slot.downcast().expect("downcast of expando slot was successful"))
106 }
107}
108
109#[cfg(test)]
110mod tests {
111 use super::*;
112 use starnix_sync::Mutex;
113
114 #[derive(Debug, Default)]
115 struct MyStruct {
116 counter: Mutex<i32>,
117 }
118
119 #[test]
120 fn basic_test() {
121 let expando = Expando::default();
122 let first = expando.get::<MyStruct>();
123 assert_eq!(*first.counter.lock(), 0);
124 *first.counter.lock() += 1;
125 let second = expando.get::<MyStruct>();
126 assert_eq!(*second.counter.lock(), 1);
127 }
128
129 #[test]
130 fn user_initializer() {
131 let expando = Expando::default();
132 let first = expando.get_or_init(|| String::from("hello"));
133 assert_eq!(first.as_str(), "hello");
134 let second = expando.get_or_init(|| String::from("world"));
135 assert_eq!(
136 second.as_str(),
137 "hello",
138 "expando must have preserved value from original initializer"
139 );
140 assert_eq!(Arc::as_ptr(&first), Arc::as_ptr(&second));
141 }
142
143 #[test]
144 fn nested_user_initializer() {
145 let expando = Expando::default();
146 let first = expando.get_or_init(|| expando.get::<u32>().to_string());
147 assert_eq!(first.as_str(), "0");
148 let second = expando.get_or_init(|| expando.get::<u32>().to_string());
149 assert_eq!(Arc::as_ptr(&first), Arc::as_ptr(&second));
150 }
151
152 #[test]
153 fn failed_init_can_be_retried() {
154 let expando = Expando::default();
155 let failed = expando.get_or_try_init::<String, String>(|| Err(String::from("oops")));
156 assert_eq!(failed.unwrap_err().as_str(), "oops");
157
158 let succeeded = expando.get_or_try_init::<String, String>(|| Ok(String::from("hurray")));
159 assert_eq!(succeeded.unwrap().as_str(), "hurray");
160 }
161
162 #[test]
163 fn peek_works() {
164 let expando = Expando::default();
165 assert_eq!(expando.peek::<String>(), None);
166 let from_init = expando.get_or_init(|| String::from("hello"));
167 let from_peek = expando.peek::<String>().unwrap();
168 assert_eq!(from_peek.as_str(), "hello");
169 assert_eq!(Arc::as_ptr(&from_init), Arc::as_ptr(&from_peek));
170 }
171
172 #[test]
173 fn remove_works() {
174 let expando = Expando::default();
175 assert_eq!(expando.peek::<String>(), None);
176 let from_init = expando.get_or_init(|| String::from("hello"));
177 let removed = expando.remove::<String>().unwrap();
178 assert_eq!(removed.as_str(), "hello");
179 assert_eq!(Arc::as_ptr(&from_init), Arc::as_ptr(&removed));
180 assert_eq!(expando.peek::<String>(), None);
181 }
182}