Skip to main content

ferroui_base/
ferro_property_registry.rs

1use crate::direct_property::DirectPropertyDyn;
2use crate::{DirectPropertyBase, FerroObject, FerroProperty, PropertyValue, TypeInfo};
3use std::cell::RefCell;
4use std::collections::HashMap;
5use std::rc::Rc;
6
7type TypeKey = *const TypeInfo;
8type PropertyList = Rc<Vec<&'static FerroProperty>>;
9
10#[derive(Default)]
11struct Inner {
12    properties: HashMap<u32, &'static FerroProperty>,
13    registered: HashMap<TypeKey, HashMap<u32, &'static FerroProperty>>,
14    attached: HashMap<TypeKey, HashMap<u32, &'static FerroProperty>>,
15    direct: HashMap<TypeKey, HashMap<u32, &'static dyn DirectPropertyDyn>>,
16    registered_cache: HashMap<TypeKey, PropertyList>,
17    attached_cache: HashMap<TypeKey, PropertyList>,
18    direct_cache: HashMap<TypeKey, PropertyList>,
19    inherited_cache: HashMap<TypeKey, PropertyList>,
20}
21
22/// Tracks registered property definitions.
23#[derive(Default)]
24pub struct FerroPropertyRegistry {
25    inner: RefCell<Inner>,
26}
27
28fn key(type_: &'static TypeInfo) -> TypeKey {
29    type_ as TypeKey
30}
31
32fn collect(
33    map: &HashMap<TypeKey, HashMap<u32, &'static FerroProperty>>,
34    type_: &'static TypeInfo,
35    walk_bases: bool,
36    out: &mut Vec<&'static FerroProperty>,
37) {
38    let mut current = Some(type_);
39    while let Some(t) = current {
40        if let Some(props) = map.get(&key(t)) {
41            let start = out.len();
42            out.extend(props.values().copied());
43            // Keep a stable, registration-ordered listing per type.
44            out[start..].sort_by_key(|p| p.id());
45        }
46        if !walk_bases {
47            break;
48        }
49        current = t.base_type();
50    }
51}
52
53impl FerroPropertyRegistry {
54    /// The property registry of the current thread.
55    pub fn instance() -> &'static FerroPropertyRegistry {
56        crate::ferro_thread_local! {
57            static INSTANCE: &'static FerroPropertyRegistry = Box::leak(Box::default());
58        }
59        INSTANCE.with(|instance| *instance)
60    }
61
62    /// Gets every property known to the registry.
63    pub(crate) fn properties(&self) -> Vec<&'static FerroProperty> {
64        self.inner.borrow().properties.values().copied().collect()
65    }
66
67    /// Unregisters the properties of the given types: removes the metadata
68    /// every registered, attached and direct property holds for each type,
69    /// and the property lists cached for it.
70    ///
71    /// Returns whether the types were unregistered. (The managed original
72    /// returns false when unregistering throws; nothing fails here.)
73    pub fn unregister_by_module(&self, types: impl IntoIterator<Item = &'static TypeInfo>) -> bool {
74        for type_ in types {
75            // The properties are collected first: removing metadata does not
76            // call back into the registry, but the registry is not borrowed
77            // while code of a property runs.
78            let properties: Vec<&'static FerroProperty> = {
79                let inner = self.inner.borrow();
80                let registered = inner.registered.values().flat_map(|properties| properties.values().copied());
81                let attached = inner.attached.values().flat_map(|properties| properties.values().copied());
82                let direct = inner.direct.values().flat_map(|properties| {
83                    properties.values().map(|property| {
84                        let property: &'static dyn DirectPropertyDyn = *property;
85                        property.as_property()
86                    })
87                });
88                let properties: Vec<&'static FerroProperty> = registered.chain(attached).chain(direct).collect();
89                properties
90            };
91            for property in properties {
92                property.unregister(type_);
93            }
94
95            let mut inner = self.inner.borrow_mut();
96            inner.registered_cache.remove(&key(type_));
97            inner.attached_cache.remove(&key(type_));
98            inner.direct_cache.remove(&key(type_));
99            inner.inherited_cache.remove(&key(type_));
100        }
101
102        true
103    }
104
105    /// Gets all non-attached properties registered on a type (and its bases).
106    pub fn get_registered(&self, type_: &'static TypeInfo) -> PropertyList {
107        if let Some(result) = self.inner.borrow().registered_cache.get(&key(type_)) {
108            return result.clone();
109        }
110        // The static initialisation of the type and its bases registers the
111        // properties they declare: a type is listed in full whether or not
112        // it has an instance, as when the managed runtime runs the class
113        // constructors of the type and its base types.
114        type_.ensure_class_init();
115        let mut inner = self.inner.borrow_mut();
116        let mut list = Vec::new();
117        collect(&inner.registered, type_, true, &mut list);
118        let result = Rc::new(list);
119        inner.registered_cache.insert(key(type_), result.clone());
120        result
121    }
122
123    /// Gets all attached properties registered on a type (and its bases).
124    pub fn get_registered_attached(&self, type_: &'static TypeInfo) -> PropertyList {
125        if let Some(result) = self.inner.borrow().attached_cache.get(&key(type_)) {
126            return result.clone();
127        }
128        type_.ensure_class_init();
129        let mut inner = self.inner.borrow_mut();
130        let mut list = Vec::new();
131        collect(&inner.attached, type_, true, &mut list);
132        let result = Rc::new(list);
133        inner.attached_cache.insert(key(type_), result.clone());
134        result
135    }
136
137    /// Gets all direct properties registered on a type (and its bases).
138    pub fn get_registered_direct(&self, type_: &'static TypeInfo) -> PropertyList {
139        if let Some(result) = self.inner.borrow().direct_cache.get(&key(type_)) {
140            return result.clone();
141        }
142        type_.ensure_class_init();
143        let mut inner = self.inner.borrow_mut();
144        let mut list: Vec<&'static FerroProperty> = Vec::new();
145        let mut current = Some(type_);
146        while let Some(t) = current {
147            if let Some(props) = inner.direct.get(&key(t)) {
148                for p in props.values() {
149                    let p: &'static dyn DirectPropertyDyn = *p;
150                    list.push(p.as_property());
151                }
152            }
153            current = t.base_type();
154        }
155        let result = Rc::new(list);
156        inner.direct_cache.insert(key(type_), result.clone());
157        result
158    }
159
160    /// Gets all inherited properties registered on a type: registered and
161    /// attached properties whose values are inherited.
162    pub fn get_registered_inherited(&self, type_: &'static TypeInfo) -> PropertyList {
163        if let Some(result) = self.inner.borrow().inherited_cache.get(&key(type_)) {
164            return result.clone();
165        }
166        let registered = self.get_registered(type_);
167        let attached = self.get_registered_attached(type_);
168        let mut list: Vec<&'static FerroProperty> = Vec::new();
169        for p in registered.iter().chain(attached.iter()) {
170            if p.inherits() && !list.iter().any(|x| x.id() == p.id()) {
171                list.push(p);
172            }
173        }
174        let result = Rc::new(list);
175        self.inner.borrow_mut().inherited_cache.insert(key(type_), result.clone());
176        result
177    }
178
179    /// Gets the properties a type itself declares (registered with the type
180    /// as owner: styled, direct, attached and added-owner properties), in
181    /// declaration order, without those of its base types.
182    pub fn get_declared(&self, type_: &'static TypeInfo) -> Vec<&'static FerroProperty> {
183        type_.ensure_class_init();
184        let mut list = Vec::new();
185        collect(&self.inner.borrow().registered, type_, false, &mut list);
186        list
187    }
188
189    /// Gets all properties registered on an object's type.
190    pub fn get_registered_for(&self, o: &FerroObject) -> PropertyList {
191        self.get_registered(o.get_type())
192    }
193
194    /// Finds the direct property registered on the type of `o` that shares
195    /// the identity of `property`.
196    ///
197    /// Panics if the property is not registered on the object's type.
198    pub fn get_registered_direct_for<T: PropertyValue>(
199        &self,
200        o: &FerroObject,
201        property: &'static DirectPropertyBase<T>,
202    ) -> &'static DirectPropertyBase<T> {
203        self.find_registered_direct(o, property).unwrap_or_else(|| {
204            panic!("Property '{}.{}' not registered on '{}'", property.owner(), property.name(), o.get_type())
205        })
206    }
207
208    /// Finds the direct property registered on the type of `o` that shares
209    /// the identity of `property`.
210    pub fn find_registered_direct<T: PropertyValue>(
211        &self,
212        o: &FerroObject,
213        property: &'static DirectPropertyBase<T>,
214    ) -> Option<&'static DirectPropertyBase<T>> {
215        let object_type = o.get_type();
216        if property.owner().is_assignable_from(object_type) {
217            // Fast path: the property is owned by the object's class chain. A
218            // more derived owner that re-registered it still takes priority.
219            if std::ptr::eq(property.owner(), object_type) {
220                return Some(property);
221            }
222        }
223        let inner = self.inner.borrow();
224        let mut current = Some(object_type);
225        while let Some(t) = current {
226            if let Some(found) = inner.direct.get(&key(t)).and_then(|m| m.get(&property.id())) {
227                let found: &'static dyn DirectPropertyDyn = *found;
228                return found.as_any().downcast_ref::<DirectPropertyBase<T>>();
229            }
230            current = t.base_type();
231        }
232        None
233    }
234
235    /// Finds a registered property on a type by name.
236    ///
237    /// Panics if `name` contains a `.`: attached properties are not looked up
238    /// through this method.
239    pub fn find_registered(&self, type_: &'static TypeInfo, name: &str) -> Option<&'static FerroProperty> {
240        assert!(!name.contains('.'), "Attached properties not supported.");
241        self.get_registered(type_).iter().copied().find(|p| p.name() == name)
242    }
243
244    /// Finds a registered property on an object's type by name.
245    pub fn find_registered_for(&self, o: &FerroObject, name: &str) -> Option<&'static FerroProperty> {
246        self.find_registered(o.get_type(), name)
247    }
248
249    /// Finds a registered property by its ID.
250    pub fn find_registered_by_id(&self, id: u32) -> Option<&'static FerroProperty> {
251        self.inner.borrow().properties.get(&id).copied()
252    }
253
254    /// Checks whether a property is registered on a type (or its bases).
255    pub fn is_registered(&self, type_: &'static TypeInfo, property: &FerroProperty) -> bool {
256        let has = |list: PropertyList| list.iter().any(|p| p.id() == property.id());
257        has(self.get_registered(type_)) || has(self.get_registered_attached(type_))
258    }
259
260    /// Checks whether a property is registered on an object's type.
261    pub fn is_registered_for(&self, o: &FerroObject, property: &FerroProperty) -> bool {
262        self.is_registered(o.get_type(), property)
263    }
264
265    /// Registers a property on a type.
266    ///
267    /// You won't usually want to call this method directly; use the
268    /// `FerroProperty::register*` functions instead.
269    pub fn register<P: Registrable>(&self, type_: &'static TypeInfo, property: &'static P) {
270        self.register_core(type_, property.as_registered_property(), property.as_direct());
271    }
272
273    /// [`register`](Self::register) for a property of any kind (compiled
274    /// once, not per property type).
275    fn register_core(
276        &self,
277        type_: &'static TypeInfo,
278        base: &'static FerroProperty,
279        direct: Option<&'static dyn DirectPropertyDyn>,
280    ) {
281        let mut inner = self.inner.borrow_mut();
282        inner.registered.entry(key(type_)).or_default().entry(base.id()).or_insert(base);
283        if let Some(direct) = direct {
284            inner.direct.entry(key(type_)).or_default().entry(base.id()).or_insert(direct);
285            inner.direct_cache.clear();
286        }
287        inner.properties.entry(base.id()).or_insert(base);
288        inner.registered_cache.clear();
289        inner.inherited_cache.clear();
290    }
291
292    /// Registers an attached property on a host type.
293    pub fn register_attached<P: Registrable>(&self, type_: &'static TypeInfo, property: &'static P) {
294        self.register_attached_core(type_, property.as_registered_property());
295    }
296
297    /// [`register_attached`](Self::register_attached) for a property of
298    /// any value type (compiled once, not per property type).
299    fn register_attached_core(&self, type_: &'static TypeInfo, base: &'static FerroProperty) {
300        assert!(base.is_attached(), "Cannot register a non-attached property as attached.");
301        let mut inner = self.inner.borrow_mut();
302        inner.attached.entry(key(type_)).or_default().entry(base.id()).or_insert(base);
303        inner.properties.entry(base.id()).or_insert(base);
304        inner.attached_cache.clear();
305        inner.inherited_cache.clear();
306    }
307}
308
309/// Property kinds that can be registered.
310pub trait Registrable: 'static {
311    #[doc(hidden)]
312    fn as_registered_property(&'static self) -> &'static FerroProperty;
313    #[doc(hidden)]
314    #[allow(private_interfaces)]
315    fn as_direct(&'static self) -> Option<&'static dyn DirectPropertyDyn> {
316        None
317    }
318}
319
320impl<T: PropertyValue> Registrable for crate::StyledProperty<T> {
321    fn as_registered_property(&'static self) -> &'static FerroProperty {
322        self
323    }
324}
325
326impl<T: PropertyValue> Registrable for crate::AttachedProperty<T> {
327    fn as_registered_property(&'static self) -> &'static FerroProperty {
328        self
329    }
330}
331
332impl<TOwner: crate::ObjectType, T: PropertyValue> Registrable for crate::DirectProperty<TOwner, T> {
333    fn as_registered_property(&'static self) -> &'static FerroProperty {
334        self
335    }
336
337    #[allow(private_interfaces)]
338    fn as_direct(&'static self) -> Option<&'static dyn DirectPropertyDyn> {
339        let base: &'static DirectPropertyBase<T> = self;
340        Some(base)
341    }
342}