Skip to main content

ranim_core/
frame.rs

1//! Typed CPU frame data transported from evaluation to extraction.
2
3use std::{
4    any::{Any, TypeId},
5    collections::HashMap,
6};
7
8use bevy_ecs::prelude::Resource;
9
10/// Item identity: a top-level animation source and its evaluated output part.
11pub type FrameItemId = (usize, usize);
12
13/// One evaluated item and its order among all items in the frame.
14pub struct FrameItem<T> {
15    /// Source-local identity of the item.
16    pub id: FrameItemId,
17    /// Insertion order across every item type in the frame.
18    pub order: usize,
19    /// Evaluated CPU data consumed by extraction systems.
20    pub value: T,
21}
22
23/// Type-independent identity of one evaluated frame item.
24#[derive(Debug, Clone, Copy)]
25pub struct FrameItemMetadata<'a> {
26    /// Identity within the scene.
27    pub id: FrameItemId,
28    /// Insertion order across all item types.
29    pub order: usize,
30    /// Concrete type of the evaluated CPU value.
31    pub type_id: TypeId,
32    /// Concrete type name for diagnostics and inspection.
33    pub type_name: &'a str,
34}
35
36trait ErasedItems: Any + Send + Sync {
37    fn clear(&mut self);
38    fn len(&self) -> usize;
39    fn metadata_at(&self, index: usize) -> FrameItemMetadata<'_>;
40}
41
42impl<T: Send + Sync + 'static> ErasedItems for Vec<FrameItem<T>> {
43    fn clear(&mut self) {
44        Vec::clear(self);
45    }
46
47    fn len(&self) -> usize {
48        Vec::len(self)
49    }
50
51    fn metadata_at(&self, index: usize) -> FrameItemMetadata<'_> {
52        let item = &self[index];
53        FrameItemMetadata {
54            id: item.id,
55            order: item.order,
56            type_id: TypeId::of::<T>(),
57            type_name: std::any::type_name::<T>(),
58        }
59    }
60}
61
62/// An evaluated frame stored in reusable batches by item type.
63///
64/// Extraction systems read their input with [`Self::items`]. Items retain a
65/// global insertion order even when different types share a frame. Evaluator
66/// outputs retain their complete types and nested structure.
67#[derive(Resource, Default)]
68pub struct EvaluatedFrame {
69    batches: HashMap<TypeId, Box<dyn ErasedItems>>,
70    len: usize,
71}
72
73impl EvaluatedFrame {
74    /// Create an empty frame.
75    pub fn new() -> Self {
76        Self::default()
77    }
78
79    /// Return the total number of items across all batches.
80    pub fn len(&self) -> usize {
81        self.len
82    }
83
84    /// Return whether the frame contains no items.
85    pub fn is_empty(&self) -> bool {
86        self.len == 0
87    }
88
89    /// Remove all items while retaining each batch and its allocated capacity.
90    pub fn clear(&mut self) {
91        for batch in self.batches.values_mut() {
92            batch.clear();
93        }
94        self.len = 0;
95    }
96
97    /// Append an item, assigning the next global insertion order.
98    pub fn push<T: Send + Sync + 'static>(&mut self, id: FrameItemId, value: T) {
99        let order = self.len;
100        self.batch_mut().push(FrameItem { id, order, value });
101        self.len += 1;
102    }
103
104    /// Replace the whole frame with items of one type, reusing batch storage.
105    pub fn update<T: Send + Sync + 'static>(
106        &mut self,
107        items: impl IntoIterator<Item = (FrameItemId, T)>,
108    ) {
109        self.clear();
110        let batch = self.batch_mut::<T>();
111        batch.extend(
112            items
113                .into_iter()
114                .enumerate()
115                .map(|(order, (id, value))| FrameItem { id, order, value }),
116        );
117        self.len = batch.len();
118    }
119
120    /// Read all items of one type, or an empty slice if the type is absent.
121    pub fn items<T: 'static>(&self) -> &[FrameItem<T>] {
122        let Some(batch) = self.batches.get(&TypeId::of::<T>()) else {
123            return &[];
124        };
125        let batch: &dyn Any = batch.as_ref();
126        batch
127            .downcast_ref::<Vec<FrameItem<T>>>()
128            .expect("frame batch type must match its TypeId")
129            .as_slice()
130    }
131
132    /// Visit every item's metadata without reading its payload.
133    ///
134    /// Batch iteration is unordered; use [`FrameItemMetadata::order`] when
135    /// scene insertion order is needed.
136    pub fn item_metadata(&self) -> impl Iterator<Item = FrameItemMetadata<'_>> + '_ {
137        self.batches
138            .values()
139            .flat_map(|batch| (0..batch.len()).map(move |index| batch.metadata_at(index)))
140    }
141
142    fn batch_mut<T: Send + Sync + 'static>(&mut self) -> &mut Vec<FrameItem<T>> {
143        let batch = self
144            .batches
145            .entry(TypeId::of::<T>())
146            .or_insert_with(|| Box::new(Vec::<FrameItem<T>>::new()));
147        let batch: &mut dyn Any = batch.as_mut();
148        batch
149            .downcast_mut::<Vec<FrameItem<T>>>()
150            .expect("frame batch type must match its TypeId")
151    }
152}
153
154#[cfg(test)]
155mod tests {
156    use super::*;
157
158    #[test]
159    fn typed_batches_preserve_global_insertion_order() {
160        let mut frame = EvaluatedFrame::new();
161        frame.push((0, 0), 10_u32);
162        frame.push((1, 0), String::from("middle"));
163        frame.push((2, 0), 20_u32);
164
165        assert_eq!(frame.len(), 3);
166        assert!(!frame.is_empty());
167        let numbers = frame.items::<u32>();
168        assert_eq!(numbers.len(), 2);
169        assert_eq!(
170            (numbers[0].id, numbers[0].order, numbers[0].value),
171            ((0, 0), 0, 10)
172        );
173        assert_eq!(
174            (numbers[1].id, numbers[1].order, numbers[1].value),
175            ((2, 0), 2, 20)
176        );
177        let strings = frame.items::<String>();
178        assert_eq!(strings.len(), 1);
179        assert_eq!(strings[0].id, (1, 0));
180        assert_eq!(strings[0].order, 1);
181        assert_eq!(strings[0].value, "middle");
182    }
183
184    #[test]
185    fn metadata_reports_all_payload_types_without_retained_empty_batches() {
186        let mut frame = EvaluatedFrame::new();
187        frame.push((0, 0), 10_u32);
188        frame.push((0, 1), String::from("text"));
189        let mut metadata = frame.item_metadata().collect::<Vec<_>>();
190        metadata.sort_by_key(|item| item.order);
191        assert_eq!(metadata.len(), 2);
192        assert_eq!(metadata[0].id, (0, 0));
193        assert_eq!(metadata[0].type_id, TypeId::of::<u32>());
194        assert_eq!(metadata[1].id, (0, 1));
195        assert_eq!(metadata[1].type_name, std::any::type_name::<String>());
196        frame.clear();
197        assert_eq!(frame.item_metadata().count(), 0);
198        frame.push((1, 0), 20_u32);
199        let metadata = frame.item_metadata().collect::<Vec<_>>();
200        assert_eq!(metadata.len(), 1);
201        assert_eq!(metadata[0].type_id, TypeId::of::<u32>());
202        assert_eq!(metadata[0].type_name, "u32");
203    }
204
205    #[test]
206    fn update_replaces_every_type_and_restarts_order() {
207        let mut frame = EvaluatedFrame::new();
208        frame.push((0, 0), 10_u32);
209        frame.push((1, 0), String::from("old"));
210
211        frame.update([
212            ((2, 0), String::from("new")),
213            ((3, 0), String::from("next")),
214        ]);
215
216        assert_eq!(frame.len(), 2);
217        assert!(frame.items::<u32>().is_empty());
218        let strings = frame.items::<String>();
219        assert_eq!(strings.len(), 2);
220        assert_eq!(strings[0].id, (2, 0));
221        assert_eq!(strings[0].order, 0);
222        assert_eq!(strings[0].value, "new");
223        assert_eq!(strings[1].id, (3, 0));
224        assert_eq!(strings[1].order, 1);
225        assert_eq!(strings[1].value, "next");
226
227        frame.update(std::iter::empty::<(FrameItemId, String)>());
228        assert!(frame.is_empty());
229        assert!(frame.items::<String>().is_empty());
230    }
231
232    #[test]
233    fn clear_retains_batches_and_their_capacity() {
234        fn capacity<T: 'static>(frame: &EvaluatedFrame) -> usize {
235            let batch: &dyn Any = frame.batches[&TypeId::of::<T>()].as_ref();
236            batch
237                .downcast_ref::<Vec<FrameItem<T>>>()
238                .unwrap()
239                .capacity()
240        }
241
242        let mut frame = EvaluatedFrame::new();
243        for index in 0..32 {
244            frame.push((index, 0), index);
245            frame.push((index, 1), index.to_string());
246        }
247        let number_capacity = capacity::<usize>(&frame);
248        let string_capacity = capacity::<String>(&frame);
249
250        frame.clear();
251
252        assert!(frame.is_empty());
253        assert_eq!(frame.len(), 0);
254        assert_eq!(frame.batches.len(), 2);
255        assert!(frame.items::<usize>().is_empty());
256        assert!(frame.items::<String>().is_empty());
257        assert_eq!(capacity::<usize>(&frame), number_capacity);
258        assert_eq!(capacity::<String>(&frame), string_capacity);
259
260        frame.push((0, 0), 99_usize);
261        assert_eq!(frame.items::<usize>()[0].order, 0);
262        assert_eq!(capacity::<usize>(&frame), number_capacity);
263    }
264
265    #[test]
266    fn missing_type_has_no_items() {
267        let mut frame = EvaluatedFrame::new();
268        assert!(frame.items::<String>().is_empty());
269        frame.push((0, 0), 10_u32);
270        assert!(frame.items::<String>().is_empty());
271    }
272
273    #[test]
274    fn frame_is_send_and_sync() {
275        fn assert_send_sync<T: Send + Sync>() {}
276        assert_send_sync::<EvaluatedFrame>();
277    }
278}