Skip to main content

ranim_render/
world.rs

1use std::{
2    collections::{HashMap, HashSet},
3    hash::{BuildHasherDefault, Hasher},
4};
5
6use bevy_ecs::prelude::*;
7use ranim_core::core_item::{
8    CoreItem, camera_frame::CameraFrame, mesh_item::MeshItem, vitem::VItem,
9};
10
11pub type CoreItemId = (usize, usize);
12
13type CoreItemIdBuildHasher = BuildHasherDefault<CoreItemIdHasher>;
14type CoreItemIdMap<V> = HashMap<CoreItemId, V, CoreItemIdBuildHasher>;
15type CoreItemIdSet = HashSet<CoreItemId, CoreItemIdBuildHasher>;
16
17/// Fast deterministic hashing for trusted, internal `(animation_id, part)` keys.
18struct CoreItemIdHasher(u64);
19
20impl Default for CoreItemIdHasher {
21    fn default() -> Self {
22        Self(0xcbf2_9ce4_8422_2325)
23    }
24}
25
26impl Hasher for CoreItemIdHasher {
27    fn finish(&self) -> u64 {
28        self.0
29    }
30
31    fn write(&mut self, bytes: &[u8]) {
32        for byte in bytes {
33            self.0 ^= u64::from(*byte);
34            self.0 = self.0.wrapping_mul(0x0000_0100_0000_01b3);
35        }
36    }
37
38    fn write_usize(&mut self, value: usize) {
39        self.0 ^= value as u64;
40        self.0 = self.0.wrapping_mul(0x0000_0100_0000_01b3);
41    }
42}
43
44/// A reusable, frame-local transport buffer between evaluation and rendering.
45#[derive(Default)]
46pub struct RenderFrame {
47    items: Vec<(CoreItemId, CoreItem)>,
48}
49
50impl RenderFrame {
51    pub fn new() -> Self {
52        Self::default()
53    }
54
55    pub fn update(&mut self, items: impl Iterator<Item = (CoreItemId, CoreItem)>) {
56        self.items.clear();
57        self.items.extend(items);
58    }
59
60    pub(crate) fn iter(&self) -> impl Iterator<Item = &(CoreItemId, CoreItem)> {
61        self.items.iter()
62    }
63}
64
65#[derive(Component, Clone, Copy, Debug, PartialEq, Eq, Hash)]
66pub(crate) struct CoreItemIdentity(pub CoreItemId);
67
68#[derive(Component, Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
69pub(crate) struct SceneOrder(pub usize);
70
71#[derive(Resource, Default)]
72pub(crate) struct CoreItemEntities(CoreItemIdMap<Entity>);
73
74pub(crate) fn reconcile(world: &mut World, frame: &RenderFrame) {
75    let mut seen = CoreItemIdSet::with_capacity_and_hasher(
76        frame.items.len(),
77        CoreItemIdBuildHasher::default(),
78    );
79
80    for (order, (id, item)) in frame.iter().enumerate() {
81        assert!(seen.insert(*id), "duplicate CoreItem id {id:?}");
82
83        let (entity, is_new) = if let Some(entity) = world.resource::<CoreItemEntities>().0.get(id)
84        {
85            (*entity, false)
86        } else {
87            let entity = world.spawn((CoreItemIdentity(*id), SceneOrder(order))).id();
88            world
89                .resource_mut::<CoreItemEntities>()
90                .0
91                .insert(*id, entity);
92            (entity, true)
93        };
94
95        replace_component(world, entity, &SceneOrder(order));
96        match item {
97            CoreItem::CameraFrame(item) => {
98                let changes_kind = world.get::<CameraFrame>(entity).is_none();
99                replace_component(world, entity, item);
100                if !is_new && changes_kind {
101                    world.entity_mut(entity).remove::<(VItem, MeshItem)>();
102                }
103            }
104            CoreItem::VItem(item) => {
105                let changes_kind = world.get::<VItem>(entity).is_none();
106                replace_component(world, entity, item);
107                if !is_new && changes_kind {
108                    world.entity_mut(entity).remove::<(CameraFrame, MeshItem)>();
109                }
110            }
111            CoreItem::MeshItem(item) => {
112                let changes_kind = world.get::<MeshItem>(entity).is_none();
113                replace_component(world, entity, item);
114                if !is_new && changes_kind {
115                    world.entity_mut(entity).remove::<(CameraFrame, VItem)>();
116                }
117            }
118        }
119    }
120
121    let stale = world
122        .resource::<CoreItemEntities>()
123        .0
124        .keys()
125        .filter(|id| !seen.contains(id))
126        .copied()
127        .collect::<Vec<_>>();
128    for id in stale {
129        let entity = world
130            .resource_mut::<CoreItemEntities>()
131            .0
132            .remove(&id)
133            .unwrap();
134        world.despawn(entity);
135    }
136}
137
138fn replace_component<T>(world: &mut World, entity: Entity, value: &T)
139where
140    T: Component + PartialEq + Clone,
141{
142    if world.get::<T>(entity) != Some(value) {
143        world.entity_mut(entity).insert(value.clone());
144    }
145}
146
147#[cfg(test)]
148mod tests {
149    use bevy_ecs::query::Changed;
150
151    use super::*;
152
153    #[test]
154    fn reconciliation_preserves_identity_and_change_ticks() {
155        let mut world = World::new();
156        world.init_resource::<CoreItemEntities>();
157        let mut frame = RenderFrame::new();
158        frame.update([((1, 2), CoreItem::VItem(VItem::default()))].into_iter());
159        reconcile(&mut world, &frame);
160        let entity = world.resource::<CoreItemEntities>().0[&(1, 2)];
161        world.clear_trackers();
162
163        reconcile(&mut world, &frame);
164        let mut changed = world.query_filtered::<Entity, Changed<VItem>>();
165
166        assert_eq!(world.resource::<CoreItemEntities>().0[&(1, 2)], entity);
167        assert_eq!(changed.iter(&world).count(), 0);
168    }
169
170    #[test]
171    fn reconciliation_replaces_the_core_item_kind() {
172        let mut world = World::new();
173        world.init_resource::<CoreItemEntities>();
174        let mut frame = RenderFrame::new();
175        frame.update([((1, 2), CoreItem::VItem(VItem::default()))].into_iter());
176        reconcile(&mut world, &frame);
177        let entity = world.resource::<CoreItemEntities>().0[&(1, 2)];
178
179        frame.update([((1, 2), CoreItem::MeshItem(MeshItem::default()))].into_iter());
180        reconcile(&mut world, &frame);
181
182        assert_eq!(world.resource::<CoreItemEntities>().0[&(1, 2)], entity);
183        assert!(world.get::<MeshItem>(entity).is_some());
184        assert!(world.get::<VItem>(entity).is_none());
185        assert!(world.get::<CameraFrame>(entity).is_none());
186    }
187}