Skip to content

Commit 5ee488a

Browse files
committed
Separate update_bounds logic & handle ABA issue
**This Commit** Separates the logic for calculating and updating Aabbs into two systems. The `update_bounds` system now handles updates from reassignment and mutation. Additionally it deregisters relationships when an entity's mesh handle is removed. **Why?** This ensures the following bug doesn't happen: 1. Entity has Mesh A and gets an Aabb assigned by `calculate_bounds` 2. Entity is assigned new Mesh B and gets a new Aabb 3. Mesh A is mutated and because we still have a link from the Entity to Mesh A, the Entity's Aabb is updated to Mesh A's new Aabb when it should still be Mesh B's Aabb This is accomplished by sharing a pair of maps that go from Entity -> Mesh and Mesh -> Entities. **Note** The iteration and `position` finding and `swap_remove`ing might be less efficient than having some `HashSet`s and doing some intersection logic. **Note** In the spirit of "when there's one, there's many" `deregister` should probably take `Vec<Entity>` and it can maybe group entities by mesh or generally do something smarter. I'll note this in the PR and we'll see what happens.
1 parent 9730917 commit 5ee488a

File tree

1 file changed

+116
-53
lines changed
  • crates/bevy_render/src/view/visibility

1 file changed

+116
-53
lines changed

crates/bevy_render/src/view/visibility/mod.rs

Lines changed: 116 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -71,9 +71,44 @@ impl VisibleEntities {
7171
}
7272
}
7373

74+
#[derive(Debug, Default, Clone)]
75+
pub struct EntityMeshRelationships {
76+
entities_with_mesh: HashMap<Handle<Mesh>, SmallVec<[Entity; 1]>>,
77+
mesh_for_entity: HashMap<Entity, Handle<Mesh>>,
78+
}
79+
80+
impl EntityMeshRelationships {
81+
/// Register the passed `entity` has having the passed `mesh_handle`.
82+
///
83+
/// Note that this list _could_ have duplicates if an entity is assigned a new mesh and
84+
/// then re-assigned the old mesh. This case should be rare so, for now, we'll risk
85+
/// duplicating `Aabb` cloning+assigning.
86+
fn register(&mut self, entity: Entity, mesh_handle: &Handle<Mesh>) {
87+
self.entities_with_mesh
88+
.entry(mesh_handle.clone_weak())
89+
.or_default()
90+
.push(entity);
91+
self.mesh_for_entity
92+
.insert(entity, mesh_handle.clone_weak());
93+
}
94+
95+
/// Deregisters the relationship between an `Entity` and `Mesh`. Used so [`update_bounds`] can
96+
/// track which relationships are still active so `Aabb`s are updated correctly.
97+
fn deregister(&mut self, entity: Entity) {
98+
if let Some(mesh) = self.mesh_for_entity.remove(&entity) {
99+
if let Some(entities) = self.entities_with_mesh.get_mut(&mesh) {
100+
if let Some(idx) = entities.iter().position(|&e| e == entity) {
101+
entities.swap_remove(idx);
102+
}
103+
}
104+
}
105+
}
106+
}
107+
74108
#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemLabel)]
75109
pub enum VisibilitySystems {
76110
CalculateBounds,
111+
UpdateBounds,
77112
UpdateOrthographicFrusta,
78113
UpdatePerspectiveFrusta,
79114
UpdateProjectionFrusta,
@@ -86,80 +121,108 @@ impl Plugin for VisibilityPlugin {
86121
fn build(&self, app: &mut bevy_app::App) {
87122
use VisibilitySystems::*;
88123

89-
app.add_system_to_stage(
90-
CoreStage::PostUpdate,
91-
calculate_bounds.label(CalculateBounds),
92-
)
93-
.add_system_to_stage(
94-
CoreStage::PostUpdate,
95-
update_frusta::<OrthographicProjection>
96-
.label(UpdateOrthographicFrusta)
97-
.after(TransformSystem::TransformPropagate),
98-
)
99-
.add_system_to_stage(
100-
CoreStage::PostUpdate,
101-
update_frusta::<PerspectiveProjection>
102-
.label(UpdatePerspectiveFrusta)
103-
.after(TransformSystem::TransformPropagate),
104-
)
105-
.add_system_to_stage(
106-
CoreStage::PostUpdate,
107-
update_frusta::<Projection>
108-
.label(UpdateProjectionFrusta)
109-
.after(TransformSystem::TransformPropagate),
110-
)
111-
.add_system_to_stage(
112-
CoreStage::PostUpdate,
113-
check_visibility
114-
.label(CheckVisibility)
115-
.after(CalculateBounds)
116-
.after(UpdateOrthographicFrusta)
117-
.after(UpdatePerspectiveFrusta)
118-
.after(UpdateProjectionFrusta)
119-
.after(TransformSystem::TransformPropagate),
120-
);
124+
app.init_resource::<EntityMeshRelationships>()
125+
.add_system_to_stage(
126+
CoreStage::PostUpdate,
127+
calculate_bounds.label(CalculateBounds),
128+
)
129+
.add_system_to_stage(CoreStage::PostUpdate, update_bounds.label(UpdateBounds))
130+
.add_system_to_stage(
131+
CoreStage::PostUpdate,
132+
update_frusta::<OrthographicProjection>
133+
.label(UpdateOrthographicFrusta)
134+
.after(TransformSystem::TransformPropagate),
135+
)
136+
.add_system_to_stage(
137+
CoreStage::PostUpdate,
138+
update_frusta::<PerspectiveProjection>
139+
.label(UpdatePerspectiveFrusta)
140+
.after(TransformSystem::TransformPropagate),
141+
)
142+
.add_system_to_stage(
143+
CoreStage::PostUpdate,
144+
update_frusta::<Projection>
145+
.label(UpdateProjectionFrusta)
146+
.after(TransformSystem::TransformPropagate),
147+
)
148+
.add_system_to_stage(
149+
CoreStage::PostUpdate,
150+
check_visibility
151+
.label(CheckVisibility)
152+
.after(CalculateBounds)
153+
.after(UpdateOrthographicFrusta)
154+
.after(UpdatePerspectiveFrusta)
155+
.after(UpdateProjectionFrusta)
156+
.after(TransformSystem::TransformPropagate),
157+
);
121158
}
122159
}
123160

124-
/// Calculates and updates [`Aabb`]s for [`Entities`](Entity) with [`Mesh`]es.
125-
/// To opt out of bound calculation for an `Entity`, give it the [`NoFrustumCulling`] component.
161+
/// Calculates [`Aabb`]s for [`Entities`](Entity) with [`Mesh`]es. To opt out of bound calculation
162+
/// for an `Entity`, give it the [`NoFrustumCulling`] component.
126163
pub fn calculate_bounds(
127164
mut commands: Commands,
128165
meshes: Res<Assets<Mesh>>,
129-
without_aabb_or_with_changed_mesh: Query<
130-
(Entity, &Handle<Mesh>),
131-
(
132-
Or<(Without<Aabb>, Changed<Handle<Mesh>>)>,
133-
Without<NoFrustumCulling>,
134-
),
135-
>,
136-
mut entity_mesh_map: Local<HashMap<Handle<Mesh>, SmallVec<[Entity; 1]>>>,
137-
mut mesh_events: EventReader<AssetEvent<Mesh>>,
166+
without_aabb: Query<(Entity, &Handle<Mesh>), (Without<Aabb>, Without<NoFrustumCulling>)>,
167+
mut entity_mesh_rel: ResMut<EntityMeshRelationships>,
138168
) {
139-
for (entity, mesh_handle) in without_aabb_or_with_changed_mesh.iter() {
140-
// Record entities that have mesh handles. Note that this list _could_ have duplicates if
141-
// an entity is assigned a new mesh and then re-assigned the old mesh. This case should be
142-
// rare so, for now, we'll risk duplicating `Aabb` cloning+assigning.
143-
entity_mesh_map
144-
.entry(mesh_handle.clone_weak())
145-
.or_default()
146-
.push(entity);
169+
for (entity, mesh_handle) in without_aabb.iter() {
170+
// Record entities whose Aabb's we're managing.
171+
entity_mesh_rel.register(entity, mesh_handle);
147172

148173
if let Some(mesh) = meshes.get(mesh_handle) {
149174
if let Some(aabb) = mesh.compute_aabb() {
150175
commands.entity(entity).insert(aabb);
151176
}
152177
}
153178
}
179+
}
180+
181+
/// Updates [`Aabb`]s for [`Entities`](Entity) with [`Mesh`]es. This includes `Entities` that have
182+
/// been assigned new `Mesh`es as well as `Entities` whose `Mesh` has been directly mutated.
183+
///
184+
/// To opt out of bound calculation for an `Entity`, give it the [`NoFrustumCulling`] component.
185+
pub fn update_bounds(
186+
mut commands: Commands,
187+
meshes: Res<Assets<Mesh>>,
188+
mut mesh_reassigned: Query<
189+
(Entity, &Handle<Mesh>, &mut Aabb),
190+
(Changed<Handle<Mesh>>, Without<NoFrustumCulling>),
191+
>,
192+
mut entity_mesh_rel: ResMut<EntityMeshRelationships>,
193+
mut mesh_events: EventReader<AssetEvent<Mesh>>,
194+
entities_lost_mesh: RemovedComponents<Handle<Mesh>>,
195+
) {
196+
// If an entity's mesh handle was removed, unlink them.
197+
for entity in entities_lost_mesh.iter() {
198+
entity_mesh_rel.deregister(entity);
199+
}
200+
201+
// If an entity with an Aabb was assigned a new mesh, move it to the new list and update its
202+
// Aabb.
203+
for (entity, mesh_handle, mut aabb) in mesh_reassigned.iter_mut() {
204+
// Remove it from the previous list
205+
entity_mesh_rel.deregister(entity);
206+
207+
// Add it to the new list.
208+
entity_mesh_rel.register(entity, mesh_handle);
209+
210+
// Update its Aabb
211+
if let Some(mesh) = meshes.get(mesh_handle) {
212+
if let Some(new_aabb) = mesh.compute_aabb() {
213+
*aabb = new_aabb;
214+
}
215+
}
216+
}
154217

155-
// Calculate bounds for entities whose meshes have been mutated.
218+
// If an entity's mesh was mutated, update its Aabb.
156219
let to_update = |event: &AssetEvent<Mesh>| {
157220
let handle = match event {
158221
AssetEvent::Modified { handle } => handle,
159222
_ => return None,
160223
};
161224
let mesh = meshes.get(handle)?;
162-
let entities_with_handle = entity_mesh_map.get(handle)?;
225+
let entities_with_handle = entity_mesh_rel.entities_with_mesh.get(handle)?;
163226
let aabb = mesh.compute_aabb()?;
164227
Some((aabb, entities_with_handle))
165228
};

0 commit comments

Comments
 (0)