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}