dynp/property_collection.rs
1use std::any::{Any, TypeId};
2use std::collections::HashMap;
3use std::fmt::Debug;
4use crate::property::Property;
5
6/// Dynamic collection of properties.
7#[derive(Debug)]
8pub struct PropertyCollection {
9 properties: HashMap<TypeId, Box<dyn Any>>,
10}
11
12impl PropertyCollection {
13 pub fn new() -> Self {
14 Self {
15 properties: HashMap::new(),
16 }
17 }
18
19 /// Retrieves a reference to a property of a specific type from the PropertyCollection.
20 ///
21 /// # Returns
22 /// - `Some(&Property<T>)` if the property of type `T` exists and its type matches `T`.
23 /// - `None` if the property is not found or the property assigned to the TypeId of `T` does not
24 /// match `T`.
25 #[inline]
26 fn get_property<T: Any>(&self) -> Option<&Property<T>> {
27 // Get the TypeId of the specified type T
28 let type_id = TypeId::of::<T>();
29
30 // Attempt to find the property by its TypeId
31 if let Some(property) = self.properties.get(&type_id) {
32 // Attempt to downcast the property to the specified type T
33 property
34 .downcast_ref::<Property<T>>()
35 } else {
36 // Property not found
37 None
38 }
39 }
40
41 /// Retrieves a mutable reference to a property of a specific type from the PropertyCollection.
42 ///
43 /// # Returns
44 /// - `Some(&mut Property<T>)` if the property of type `T` exists and its type matches `T`.
45 /// - `None` if the property is not found or the property assigned to the TypeId of `T` does not
46 /// match `T`.
47 #[inline]
48 fn get_property_mut<T: Any>(&mut self) -> Option<&mut Property<T>> {
49 let type_id = TypeId::of::<T>();
50 if let Some(property) = self.properties.get_mut(&type_id) {
51 property
52 .downcast_mut::<Property<T>>()
53 } else {
54 None
55 }
56 }
57
58 /// Retrieves the value of a property of a specific type from the PropertyCollection.
59 ///
60 /// # Type Parameters
61 /// - `T`: The type of the property to retrieve. It should be a type that implements the `Any`
62 ///
63 /// # Returns
64 /// - `Some(&T)` if the property of type `T` exists.
65 /// - `None` if the property is not found.
66 pub fn get<T: Any>(&self) -> Option<&T> {
67 if let Some(property) = self.get_property::<T>() {
68 property.get()
69 } else {
70 None
71 }
72 }
73
74 /// Assigns the value to a property or adds a new property with the specified value.
75 ///
76 /// This function assigns the provided `value` to an existing property of the specified type `T`
77 /// if it already exists in the collection. If no property of that type exists, a new property of
78 /// that type is added to the collection with the specified `value`.
79 ///
80 /// # Type Parameters
81 /// - `T`: The type of the property to assign. It should be a type that implements the `Any`
82 ///
83 /// # Parameters
84 /// - `value`: The value of type `T` to assign to the property.
85 pub fn assign<T: Any>(&mut self, value: T)
86 {
87 if let Some(property) = self.get_property_mut::<T>() {
88 // if the property was found, just assign the new value
89 property.assign(value);
90 } else {
91 // if the property was not found, add it to the collection
92 self.properties.insert(
93 TypeId::of::<T>(),
94 Box::new(Property::new(value))
95 );
96 }
97 }
98
99 /// Checks if a property of a specified type exists in the collection.
100 ///
101 /// # Returns
102 /// * `true` - If a property of the specified type `T` exists in the collection.
103 /// * `false` - If no property of the specified type `T` is found in the collection.
104 pub fn contains<T: Any>(&self) -> bool {
105 self.get_property::<T>().is_some()
106 }
107
108 /// Subscribes to changes in a property of a specific type in the PropertyCollection.
109 ///
110 /// This function attempts to subscribe to changes in a property of the specified type `T` in the
111 /// `PropertyCollection`. If the property exists and its type matches `T`, it registers the provided
112 /// callback function to be called when the property's value changes. If the property does not
113 /// exist in the collection yet, an *early subscription* is performed by adding the callback to
114 /// ab empty property of type `T`. This allows the callback to be called when the property is
115 /// assigned a value later.
116 ///
117 ///
118 /// # Arguments
119 /// * `callback` - The callback function to be called when the property's value changes.
120 ///
121 /// # Example
122 ///
123 /// ```
124 /// use dynp::PropertyCollection;
125 ///
126 /// // define a custom property using the Newtype pattern
127 /// #[derive(Copy, Clone, Debug)]
128 /// struct CustomProperty(i32);
129 ///
130 /// fn main() {
131 /// // create a new property collection
132 /// let mut collection = PropertyCollection::new();
133 /// collection.subscribe::<CustomProperty>(|value: &CustomProperty| {
134 /// println!("Property changed: {:?}", value);
135 /// });
136 ///
137 /// // assign a new property
138 /// collection.assign(CustomProperty(42));
139 /// }
140 /// ```
141 pub fn subscribe<T: Any>(
142 &mut self,
143 callback: impl FnMut(&T) + 'static
144 ) {
145 if let Some(property) = self.get_property_mut::<T>() {
146 // if the property exists, add the callback to its subscriptions
147 property.subscribe(Box::new(callback));
148 } else {
149 // if the property does not exist, add an empty property and add the callback to its
150 // subscriptions
151 let mut property: Property<T> = Property::empty();
152 property.subscribe(Box::new(callback));
153 self.properties.insert(
154 TypeId::of::<T>(),
155 Box::new(property)
156 );
157 }
158 }
159}
160
161#[cfg(test)]
162mod tests {
163 use super::*;
164
165 /// A newtype wrapper for `i32` that implements the `ToOwned` trait.
166 #[derive(Copy, Clone, PartialEq, Eq, Debug)]
167 struct MyInt(i32);
168
169 #[test]
170 fn test_new_property_collection() {
171 let collection = PropertyCollection::new();
172 assert!(collection.properties.is_empty());
173 }
174
175 #[test]
176 fn test_assign_get_property_native() {
177 let mut collection = PropertyCollection::new();
178 collection.assign(42);
179
180 assert_eq!(collection.get::<i32>(), Some(&42));
181 assert_eq!(collection.get::<u32>(), None);
182 assert_eq!(collection.get::<String>(), None);
183
184
185 collection.assign(11);
186 assert_eq!(collection.get::<i32>(), Some(&11));
187 assert_eq!(collection.get::<u32>(), None);
188 assert_eq!(collection.get::<String>(), None);
189
190 // make sure the reference does not interfere with the original property
191 collection.assign(&99);
192 assert_eq!(collection.get::<i32>(), Some(&11));
193 assert_eq!(collection.get::<&i32>(), Some(&&99));
194 assert_eq!(collection.get::<u32>(), None);
195 assert_eq!(collection.get::<String>(), None);
196 }
197
198 #[test]
199 fn test_assign_get_property_newtype() {
200 let mut collection = PropertyCollection::new();
201
202 collection.assign(MyInt(42));
203 assert_eq!(collection.get::<MyInt>(), Some(&MyInt(42)));
204 assert_eq!(collection.get::<i32>(), None);
205 assert_eq!(collection.get::<u32>(), None);
206 }
207
208 #[test]
209 fn test_contains_property_native() {
210 let mut collection = PropertyCollection::new();
211 assert!(!collection.contains::<i32>());
212
213 collection.assign(42);
214 collection.assign(&42);
215 assert!(collection.contains::<i32>());
216 assert!(!collection.contains::<String>());
217 }
218
219 #[test]
220 fn test_contains_property_newtype() {
221 let mut collection = PropertyCollection::new();
222 assert!(!collection.contains::<MyInt>());
223
224 collection.assign(MyInt(42));
225 assert!(collection.contains::<MyInt>());
226 assert!(!collection.contains::<i32>());
227 assert!(!collection.contains::<String>());
228 }
229
230 #[test]
231 fn test_early_subscribe() {
232 let mut collection = PropertyCollection::new();
233
234 let count = std::sync::Arc::new(std::sync::Mutex::new(0));
235 let count_clone = std::sync::Arc::clone(&count);
236
237 // create an early subscription
238 collection.subscribe::<i32>(move |value: &i32| {
239 assert_eq!(*value, 11);
240 let mut count = count_clone.lock().unwrap();
241 *count += 1;
242 });
243
244 // perform an assignment
245 collection.assign::<i32>(11);
246 assert_eq!(*count.lock().unwrap(), 1);
247
248 // perform a new assignment
249 collection.assign::<i32>(11);
250 assert_eq!(*count.lock().unwrap(), 2);
251 }
252
253 #[test]
254 fn test_subscribe() {
255 let mut collection = PropertyCollection::new();
256
257 let count = std::sync::Arc::new(std::sync::Mutex::new(0));
258 let count_clone = std::sync::Arc::clone(&count);
259
260 // perform an assignment
261 collection.assign::<i32>(11);
262 assert_eq!(*count.lock().unwrap(), 0);
263
264 // create an early subscription
265 collection.subscribe::<i32>(move |value: &i32| {
266 assert_eq!(*value, 11);
267 let mut count = count_clone.lock().unwrap();
268 *count += 1;
269 });
270
271 assert_eq!(*count.lock().unwrap(), 0);
272
273 // perform an assignment
274 collection.assign::<i32>(11);
275 assert_eq!(*count.lock().unwrap(), 1);
276
277 // perform an assignment
278 collection.assign::<i32>(11);
279 assert_eq!(*count.lock().unwrap(), 2);
280 }
281
282}