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
17struct 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#[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}