ranim_core/
store.rs

1use std::cell::RefCell;
2
3use crate::{
4    animation::{AnimationCell, CoreItemAnimation},
5    core_item::{AnyExtractCoreItem, CoreItem, mesh_item::MeshItem, vitem::VItem},
6    prelude::CameraFrame,
7};
8
9/// A store of animations
10///
11/// It has interior mutability, because when pushing an animation into it, we
12/// need to return a reference to the animation, which is bound to the store's lifetime.
13///
14/// To allow the mutation, we use a `RefCell<Vec<Box<dyn AnyAnimation>>>` in its inner.
15///
16/// # Safety Contract
17///
18/// The following invariants must be maintained:
19///
20/// - **No mutation after push**: Once an animation is pushed into the store, it should never
21///   be mutated or removed. The only allowed mutation is pushing new animations into the store.
22///
23/// - **No Vec reallocation issues**: The returned references from `push_eval_dynamic` point directly
24///   to the heap-allocated `AnimationCell<T>` data inside the `Box<dyn AnyAnimation>`. Even if the
25///   `Vec` reallocates (which moves the `Box`es), the heap data itself doesn't move, so the pointers
26///   remain valid. This is safe because `Box` owns heap-allocated data, and the data doesn't move
27///   when the `Box` is moved within the `Vec`.
28#[derive(Default)]
29pub struct AnimationStore {
30    anims: RefCell<Vec<Box<dyn CoreItemAnimation>>>,
31}
32
33impl AnimationStore {
34    /// Create a new store.
35    pub fn new() -> Self {
36        Self::default()
37    }
38
39    /// Push an `AnimationCell<T>` into the store and return a reference to it.
40    ///
41    /// The returned reference is bound to `&self`'s lifetime, which means it will be invalidated
42    /// when the store is dropped. Since we use `RefCell` for interior mutability, we can modify
43    /// the internal `Vec` while holding a shared reference `&self`.
44    ///
45    /// # Safety
46    ///
47    /// This function uses unsafe code to return a reference that outlives the `RefCell` borrow.
48    /// The safety relies on the following guarantees:
49    ///
50    /// 1. **Pointer validity**: The raw pointer `ptr` points to the heap-allocated `AnimationCell<T>`
51    ///    that is now owned by the `Vec<Box<dyn AnyAnimation>>` inside `self.anims`.
52    ///
53    /// 2. **Memory layout**: When we coerce `Box<AnimationCell<T>>` to `Box<dyn AnyAnimation>`,
54    ///    only the vtable pointer changes. The data pointer (pointing to the actual `AnimationCell<T>`
55    ///    on the heap) remains the same, so `ptr` is still valid.
56    ///
57    /// 3. **Vec reallocation safety**: Even if the `Vec` reallocates (which moves the `Box`es),
58    ///    the heap-allocated `AnimationCell<T>` data inside each `Box` does not move. The pointer
59    ///    `ptr` points directly to this heap data, not to the `Box` itself, so it remains valid
60    ///    regardless of `Vec` reallocations. This is a key property of `Box`: moving the `Box`
61    ///    doesn't move the data it points to on the heap.
62    ///
63    /// 4. **Lifetime binding**: The returned reference `&AnimationCell<T>` has a lifetime that is
64    ///    inferred from `&self`, ensuring it cannot outlive the store. This is enforced by Rust's
65    ///    borrow checker.
66    ///
67    /// 5. **No mutation after push**: Once pushed, the animation is never mutated or removed,
68    ///    so the pointer remains valid for the lifetime of the store.
69    pub fn push_animation<T: AnyExtractCoreItem>(
70        &self,
71        anim: AnimationCell<T>,
72    ) -> &AnimationCell<T> {
73        let boxed = Box::new(anim);
74
75        // Get a raw pointer to the heap-allocated AnimationCell<T> before converting
76        let ptr = Box::into_raw(boxed);
77        // Reconstruct as Box<AnimationCell<T>>, then coerce to Box<dyn AnyAnimation>
78        // This ensures the vtable is properly set up
79        let boxed_concrete: Box<AnimationCell<T>> = unsafe { Box::from_raw(ptr) };
80        let boxed_trait: Box<dyn CoreItemAnimation> = boxed_concrete;
81        self.anims.borrow_mut().push(boxed_trait);
82        // SAFETY: See function documentation for detailed safety guarantees.
83        // In summary: ptr points to memory owned by the Vec, the Vec won't reallocate
84        // until capacity is exceeded (and we're pushing one element), and the returned
85        // reference's lifetime is bound to &self, ensuring it cannot outlive the store.
86        unsafe { &*ptr }
87    }
88}
89
90/// A store of [`CoreItem`]s.
91#[derive(Default, Clone)]
92pub struct CoreItemStore {
93    /// Id of [`CameraFrame`]s
94    pub camera_frame_ids: Vec<(usize, usize)>,
95    /// [`CameraFrame`]s
96    pub camera_frames: Vec<CameraFrame>,
97
98    /// Id of [`VItem`]s
99    pub vitem_ids: Vec<(usize, usize)>,
100    /// [`VItem`]s
101    pub vitems: Vec<VItem>,
102
103    /// Id of [`MeshItem`]s
104    pub mesh_item_ids: Vec<(usize, usize)>,
105    /// [`MeshItem`]s
106    pub mesh_items: Vec<MeshItem>,
107}
108
109impl CoreItemStore {
110    /// Create an empty store
111    pub fn new() -> Self {
112        Self::default()
113    }
114
115    /// Update the inner store with the given iterator
116    pub fn update(&mut self, items: impl Iterator<Item = ((usize, usize), CoreItem)>) {
117        self.camera_frame_ids.clear();
118        self.camera_frames.clear();
119
120        self.vitem_ids.clear();
121        self.vitems.clear();
122
123        self.mesh_item_ids.clear();
124        self.mesh_items.clear();
125        for (id, item) in items {
126            match item {
127                CoreItem::CameraFrame(x) => {
128                    self.camera_frame_ids.push(id);
129                    self.camera_frames.push(x);
130                }
131                CoreItem::VItem(x) => {
132                    self.vitem_ids.push(id);
133                    self.vitems.push(x);
134                }
135                CoreItem::MeshItem(x) => {
136                    self.mesh_item_ids.push(id);
137                    self.mesh_items.push(x);
138                }
139            }
140        }
141    }
142}
143
144#[cfg(test)]
145mod tests {
146    use super::*;
147
148    #[test]
149    fn test_animation_store() {
150        use crate::animation::Eval;
151        use std::marker::PhantomData;
152        #[derive(Default)]
153        struct A<T: Default> {
154            _phantom: PhantomData<T>,
155        }
156        impl<T: Default> Eval<T> for A<T> {
157            fn eval_alpha(&self, _alpha: f64) -> T {
158                T::default()
159            }
160        }
161
162        let store = AnimationStore::new();
163        let anim = store.push_animation(A::<VItem>::default().into_animation_cell());
164        // drop(store); // This should cause a compile error because anim's lifetime is tied to store
165        assert_eq!(anim.eval_alpha(0.0), VItem::default());
166        assert_eq!(
167            anim.eval_alpha_core_item(0.0),
168            vec![CoreItem::VItem(VItem::default())]
169        );
170        drop(store);
171    }
172}