config_it/config/
group.rs

1use bitfield::bitfield;
2use std::any::{Any, TypeId};
3use std::iter::zip;
4use std::sync::atomic::{AtomicU64, Ordering};
5use std::sync::{Arc, Weak};
6use strseq::SharedStringSequence;
7
8use crate::shared::GroupID;
9
10use super::entity::{Entity, EntityData, EntityValue, PropertyInfo};
11use super::noti;
12
13///
14/// Base trait that is automatically generated
15///
16pub trait Template: Clone + 'static {
17    /// Relevant type for stack allocation of props
18    type LocalPropContextArray: LocalPropContextArray;
19
20    /// Returns table mapping to <offset_from_base:property_metadata>
21    #[doc(hidden)]
22    fn props__() -> &'static [PropertyInfo];
23
24    /// Gets property at memory offset
25    #[doc(hidden)]
26    fn prop_at_offset__(offset: usize) -> Option<&'static PropertyInfo>;
27
28    /// Get path of this config template (module path, struct name)
29    fn template_name() -> (&'static str, &'static str);
30
31    /// Create default configuration object
32    fn default_config() -> Self;
33
34    #[doc(hidden)]
35    fn elem_at_mut__(&mut self, index: usize) -> &mut dyn Any;
36
37    #[doc(hidden)]
38    fn update_elem_at__(&mut self, index: usize, value: &dyn Any, meta: &PropertyInfo) {
39        let data = self.elem_at_mut__(index);
40        meta.vtable.clone_in_place(value, data);
41    }
42}
43
44/* --------------------------------------- Local Property --------------------------------------- */
45
46/// Allows local properties to be stored on stack.
47#[doc(hidden)]
48pub trait LocalPropContextArray: Clone + Default + std::fmt::Debug {
49    const N: usize;
50
51    fn as_slice(&self) -> &[PropLocalContext];
52    fn as_slice_mut(&mut self) -> &mut [PropLocalContext];
53}
54
55#[doc(hidden)]
56#[derive(Clone, Debug)]
57pub struct LocalPropContextArrayImpl<const N: usize>([PropLocalContext; N]);
58
59impl<const N: usize> LocalPropContextArray for LocalPropContextArrayImpl<N> {
60    const N: usize = N;
61
62    fn as_slice(&self) -> &[PropLocalContext] {
63        &self.0
64    }
65
66    fn as_slice_mut(&mut self) -> &mut [PropLocalContext] {
67        &mut self.0
68    }
69}
70
71impl<const N: usize> Default for LocalPropContextArrayImpl<N> {
72    fn default() -> Self {
73        Self([0; N].map(|_| PropLocalContext::default()))
74    }
75}
76
77/// Represents the context associated with a configuration group in the storage system.
78///
79/// A `GroupContext` provides necessary information about a particular group's instantiation
80/// and its connection to the underlying storage. Implementations of the storage system may
81/// use this structure to manage and access configurations tied to a specific group.
82#[derive(cs::Debug)]
83pub struct GroupContext {
84    /// A unique identifier for the configuration group instance.
85    pub group_id: GroupID,
86
87    /// The type ID of the base template from which this group was derived.
88    /// Used to validate the legitimacy of groups created afresh.
89    pub template_type_id: TypeId,
90
91    /// Type name and module path of this group (cached),
92    pub template_name: (&'static str, &'static str),
93
94    /// An ordered list of data entities, each corresponding to an individual property
95    /// within the configuration group.
96    pub(crate) sources: Arc<[EntityData]>,
97
98    /// A weak reference to a hook which, if set, can be triggered upon
99    /// group unregistration. Useful for clean-up operations or notifications.
100    pub(crate) w_unregister_hook: Weak<dyn Any + Send + Sync>,
101
102    /// Represents the current version of the group. This may be incremented with
103    /// updates, allowing for versioned access and change tracking.
104    pub(crate) version: AtomicU64,
105
106    /// The hierarchical path representing the location of this configuration set
107    /// within a broader configuration system.
108    pub path: SharedStringSequence,
109
110    /// A channel for receiving update notifications from the
111    /// backend, enabling the group to respond to external changes or synchronize its state.
112    pub(crate) update_receiver_channel: noti::Receiver,
113}
114
115mod monitor {
116    //! Exposed APIs to control over entities
117
118    use crate::{config::noti, shared::ItemID};
119
120    impl super::GroupContext {
121        /// Finds an item with the given `item_id` in the group's sources.
122        pub fn find_item(&self, item_id: ItemID) -> Option<&super::EntityData> {
123            debug_assert!(
124                self.sources.windows(2).all(|w| w[0].id < w[1].id),
125                "Logic Error: Sources are not sorted!"
126            );
127
128            self.sources
129                .binary_search_by(|x| x.id.cmp(&item_id))
130                .map(|index| &self.sources[index])
131                .ok()
132        }
133
134        /// Returns a channel for receiving update notifications for this group.
135        pub fn watch_update(&self) -> noti::Receiver {
136            self.update_receiver_channel.clone()
137        }
138
139        /// List of available entities
140        pub fn entities(&self) -> &[super::EntityData] {
141            &self.sources
142        }
143    }
144}
145
146///
147/// Primary interface that end user may interact with
148///
149/// Wrap `ReflectData` derivative like `Group<MyData>`
150///
151pub struct Group<T: Template> {
152    /// Cached local content
153    __body: T,
154
155    /// Cached update fence
156    version_cached: u64,
157
158    /// Property-wise contexts
159    local: T::LocalPropContextArray,
160
161    /// List of managed properties. This act as source container
162    origin: Arc<GroupContext>,
163
164    /// Unregister hook anchor.
165    ///
166    /// It will unregister this config set from owner storage automatically, when all
167    ///  instances of config set disposed.
168    _unregister_hook: Arc<dyn Any + Send + Sync>,
169}
170
171impl<T: Clone + Template> Clone for Group<T> {
172    fn clone(&self) -> Self {
173        Self {
174            __body: self.__body.clone(),
175            version_cached: self.version_cached,
176            local: self.local.clone(),
177            origin: self.origin.clone(),
178            _unregister_hook: self._unregister_hook.clone(),
179        }
180    }
181}
182
183impl<T: std::fmt::Debug + Template> std::fmt::Debug for Group<T> {
184    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
185        f.debug_struct("Group")
186            .field("__body", &self.__body)
187            .field("fence", &self.version_cached)
188            .finish()
189    }
190}
191
192#[derive(Debug, Clone)]
193pub struct PropLocalContext {
194    /// Locally cached update fence.
195    bits: VersionBits,
196}
197
198bitfield! {
199    #[derive(Clone, Copy, PartialEq, Eq)]
200    struct VersionBits(u64);
201    impl Debug;
202
203    version, set_version: 62, 0;
204    is_dirty, set_dirty: 63, 63;
205}
206
207impl Default for PropLocalContext {
208    fn default() -> Self {
209        Self {
210            bits: {
211                let mut bits = VersionBits(0);
212                bits.set_dirty(1);
213                bits
214            },
215        }
216    }
217}
218
219/// Type alias for broadcast receiver
220pub type WatchUpdate = noti::Receiver;
221
222impl<T: Template> Group<T> {
223    #[doc(hidden)]
224    pub(crate) fn create_with__(
225        core: Arc<GroupContext>,
226        unregister_anchor: Arc<dyn Any + Send + Sync>,
227    ) -> Self {
228        Self {
229            origin: core,
230            __body: T::default_config(),
231            version_cached: 0,
232            local: T::LocalPropContextArray::default(),
233            _unregister_hook: unregister_anchor,
234        }
235    }
236
237    /// Conveniently updates the group instance during method chaining. Especially useful when
238    /// initializing a group immediately after its creation, without the need to assign it to a
239    /// separate mutable variable.
240    ///
241    /// ```ignore
242    /// // Without `updated`:
243    /// let group = {
244    ///     let mut group = storage.create(["my","group"]).unwrap();
245    ///     group.update();
246    ///     group
247    /// };
248    ///
249    /// // With `updated`:
250    /// let group = storage.create(["my","group"]).map(|x| x.updated());
251    /// ```
252    pub fn updated(mut self) -> Self {
253        self.update();
254        self
255    }
256
257    /// Fetches and applies updates from the underlying object to the local cache.
258    ///
259    /// This function checks if there are updates available in the source and applies them to the
260    /// local cache. If any updates are found, or if this is the initial fetch (determined by the
261    /// `version_cached` being 0), the function will return `true`.
262    ///
263    /// # Returns
264    ///
265    /// - `true` if updates were found and applied or if this is the initial fetch.
266    /// - `false` otherwise.
267    pub fn update(&mut self) -> bool {
268        let local = self.local.as_slice_mut();
269
270        // Ensures that the initial update always returns true.
271        let mut has_update = self.version_cached == 0;
272
273        // Check if the update fence value has changed.
274        match self.origin.version.load(Ordering::Relaxed) {
275            new_ver if new_ver == self.version_cached => return false,
276            new_ver => self.version_cached = new_ver,
277        }
278
279        // Ensure the local and origin sources have the same length.
280        debug_assert_eq!(
281            local.len(),
282            self.origin.sources.len(),
283            "Logic Error: The set was not correctly initialized!"
284        );
285
286        for ((index, local), source) in zip(zip(0..local.len(), &mut *local), &*self.origin.sources)
287        {
288            // Check if the given config entity has any updates.
289            match source.version() {
290                // NOTE: The locally updated version uses 63 bits out of 64. In rare scenarios, this
291                // might cause it to deviate from the source version. However, this situation is
292                // unlikely to be of practical concern unless a version gap of at least 2^63 arises.
293                // Moreover, the tolerance resets to 2^63 with each update.
294                v if v == local.bits.version() => continue,
295                v => local.bits.set_version(v),
296            }
297
298            has_update = true;
299            local.bits.set_dirty(1);
300
301            let (meta, value) = source.property_value();
302            self.__body.update_elem_at__(index, value.as_any(), meta);
303        }
304
305        has_update
306    }
307
308    /// Inspects the given element for updates using its address, then resets its dirty flag. For
309    /// this check to have meaningful results, it's typically followed by a [`Group::update`]
310    /// invocation.
311    ///
312    /// # Arguments
313    ///
314    /// * `prop` - A raw pointer to the property to be checked.
315    ///
316    /// # Returns
317    ///
318    /// * `true` if the property was marked dirty and has been reset, `false` otherwise.
319    pub fn consume_update<U: 'static>(&mut self, prop: *const U) -> bool {
320        let Some(index) = self.get_index_by_ptr(prop) else { return false };
321        let bits = &mut self.local.as_slice_mut()[index].bits;
322
323        if bits.is_dirty() == 1 {
324            bits.set_dirty(0);
325            true
326        } else {
327            false
328        }
329    }
330
331    #[doc(hidden)]
332    pub fn get_index_by_ptr<U: 'static>(&self, e: *const U) -> Option<usize> {
333        debug_assert!({
334            let e = e as usize;
335            let base = &self.__body as *const _ as usize;
336            e >= base && e < base + std::mem::size_of::<T>()
337        });
338
339        self.get_prop_by_ptr(e).map(|prop| prop.index)
340    }
341
342    #[doc(hidden)]
343    pub fn get_prop_by_ptr<U: 'static>(&self, e: *const U) -> Option<&'static PropertyInfo> {
344        let ptr = e as *const u8 as isize;
345        let base = &self.__body as *const _ as *const u8 as isize;
346
347        match ptr - base {
348            v if v < 0 => None,
349            v if v >= std::mem::size_of::<T>() as isize => None,
350            v => {
351                if let Some(prop) = T::prop_at_offset__(v as usize) {
352                    debug_assert_eq!(prop.type_id, TypeId::of::<U>());
353                    debug_assert!(prop.index < self.local.as_slice().len());
354                    Some(prop)
355                } else {
356                    None
357                }
358            }
359        }
360    }
361
362    /// Commits changes made to an element, ensuring that these changes are propagated to all other
363    /// groups that share the same core context.
364    ///
365    /// # Arguments
366    ///
367    /// * `prop`: The element containing the changes to be committed.
368    /// * `notify`: If set to `true`, it triggers other groups that share the same context to be
369    ///   notified of this change.
370    pub fn commit_elem<U: Clone + Entity>(&self, prop: &U, notify: bool) {
371        // Replace source argument with created pointer
372        let elem = &(*self.origin.sources)[self.get_index_by_ptr(prop).unwrap()];
373
374        // Determine if the value type supports copy operations
375        let impl_copy = elem.meta.vtable.implements_copy();
376
377        // SAFETY: We rely on the `vtable.implements_copy()` check to ensure safe data handling.
378        // Proper management of this check is essential to guarantee the safety of this operation.
379        let new_value = unsafe { EntityValue::from_value(prop.clone(), impl_copy) };
380
381        // Apply the new value to the element
382        elem.__apply_value(new_value);
383        // Update and potentially notify other contexts of the change
384        elem.touch(notify);
385    }
386
387    /// Notify changes to core context, without actual content change. This will trigger the entire
388    /// notification mechanism as if the value has been changed.
389    pub fn touch_elem<U: 'static>(&self, prop: *const U) {
390        let elem = &(*self.origin.sources)[self.get_index_by_ptr(prop).unwrap()];
391        elem.touch(true)
392    }
393
394    /// Creates a new update receiver channel. The provided channel is notified whenever an
395    /// `update()` method call detects changes. However, note that the event can be manually
396    /// triggered even if there are no actual updates. Therefore, relying on this signal for
397    /// critical logic is not recommended.
398    ///
399    /// The channel will always be notified on its first `wait()` call.
400    pub fn watch_update(&self) -> WatchUpdate {
401        self.origin.watch_update()
402    }
403
404    /// Mark all elements dirty. Next call to [`Group::update()`] may not return true if there
405    /// wasn't any actual update, however, every call to [`Group::clear_flag()`] for
406    /// each elements will return true.
407    pub fn mark_all_elem_dirty(&mut self) {
408        // Raising dirty flag does not incur remote reload.
409        self.local.as_slice_mut().iter_mut().for_each(|e| e.bits.set_dirty(1));
410    }
411
412    /// Marks the entire group as dirty. The subsequent call to the `update()` method will return
413    /// `true`, irrespective of any actual underlying updates. This operation doesn't affect
414    /// individual property-wise dirty flags within the group.
415    pub fn mark_group_dirty(&mut self) {
416        self.version_cached = 0;
417    }
418
419    /// Mark given element dirty.
420    pub fn mark_dirty<U: 'static>(&mut self, elem: *const U) {
421        let index = self.get_index_by_ptr(elem).unwrap();
422        self.local.as_slice_mut()[index].bits.set_dirty(1);
423    }
424
425    /// Get generated metadata of given element
426    pub fn meta<U: 'static>(&self, elem: *const U) -> &'static PropertyInfo {
427        self.get_prop_by_ptr(elem).unwrap()
428    }
429
430    /// Retrieves the instance path of `self`. This value corresponds to the list of tokens
431    /// provided during the group's creation with the [`crate::Storage::create_group`] method.
432    pub fn path(&self) -> &SharedStringSequence {
433        &self.origin.path
434    }
435}
436
437impl<T: Template> std::ops::Deref for Group<T> {
438    type Target = T;
439
440    fn deref(&self) -> &Self::Target {
441        &self.__body
442    }
443}
444
445impl<T: Template> std::ops::DerefMut for Group<T> {
446    fn deref_mut(&mut self) -> &mut Self::Target {
447        &mut self.__body
448    }
449}
450
451#[test]
452fn _verify_send_impl() {
453    #[derive(Clone, Default)]
454    struct Example {}
455    impl Template for Example {
456        type LocalPropContextArray = LocalPropContextArrayImpl<0>;
457
458        fn prop_at_offset__(_offset: usize) -> Option<&'static PropertyInfo> {
459            unimplemented!()
460        }
461
462        fn props__() -> &'static [PropertyInfo] {
463            unimplemented!()
464        }
465
466        fn elem_at_mut__(&mut self, _: usize) -> &mut dyn Any {
467            unimplemented!()
468        }
469
470        fn template_name() -> (&'static str, &'static str) {
471            unimplemented!()
472        }
473
474        fn default_config() -> Self {
475            Self::default()
476        }
477    }
478
479    fn _assert_send<T: Send + Sync>() {}
480    _assert_send::<Group<Example>>();
481}
482
483impl<T: Template> Group<T> {
484    #[doc(hidden)]
485    pub fn __macro_as_mut(&mut self) -> &mut Self {
486        //! Use coercion to get mutable reference to self regardless of its expression.
487        self
488    }
489}