Skip to content

Commit

Permalink
Refactor and speed up transform propagation and hierarchies further (#…
Browse files Browse the repository at this point in the history
…380)

# Objective

#377 refactored collider hierarchy logic and extracted it into a `ColliderHierarchyPlugin<C: ScalableCollider>`, along with significantly speeding up `ColliderTransform` propagation using a `ColliderAncestor` marker component to ignore trees that don't have colliders.

However, a similar marker component approach can *also* be used to speed up the many copies of transform propagation that the engine has. By limiting the propagation runs to physics entities, we could drastically reduce the overhead for scenes with lots of non-physics entities, further addressing #356.

## Solution

- Extract the ancestor marker logic from `ColliderHierarchyPlugin` into an `AncestorMarkerPlugin<C: Component>` that adds an `AncestorMarker<C>` component for all ancestors of entities with the given component `C`. The logic was also refactored to be more robust and fix some edge cases and fully support hierarchy changes, along with adding more tests.
- Use this plugin in `SyncPlugin` to mark rigid body ancestors.
- Define custom transform propagation systems that *only* traverse trees that have physics entities (rigid bodies or colliders). See the comments in `sync.rs` above the propagation systems to see how this works.
- To support custom collision backends without having to add generics to all the propagation systems, and to avoid needing to unnecessarily duplicate systems, the `ColliderBackendPlugin` now adds a general, automatically managed `ColliderMarker` component for all colliders, which allows filtering collider entities without knowing the collider type. This lets us get rid of the generics on `ColliderHierarchyPlugin`!
- I also changed some scheduling and system sets in the `PrepareSet` to account for some changes and to be more logical.

## Results

Test scene: 12 substeps, 1 root entity, 100,000 child entities. All entities have just a `SpatialBundle`, and one child entity also has a `Collider`.

- Before [#377](#377): ~22 FPS
- After [#377](#377): ~200 FPS
- After this PR: ~490 FPS

Of course, this scene is not very representative of an actual game scene, but it does show just how much of an impact the transform propagation was having. A *lot* of games (probably most of them) do have many more non-physics entities than physics entities, and the overhead added by the marker components and new checks should be very minimal in comparison.

---

## Migration Guide

Some `PrepareSet` system sets have changed order.

Before:

1. `PreInit`
2. `PropagateTransforms`
3. `InitRigidBodies`
4. `InitMassProperties`
5. `InitColliders`
6. `InitTransforms`
7. `Finalize`

After:

1. `PreInit`
2. `InitRigidBodies`
3. `InitColliders`
4. `PropagateTransforms`
5. `InitMassProperties`
6. `InitTransforms`
7. `Finalize`

Additionally, the `ColliderHierarchyPlugin` added in #377 no longer needs generics. The plugin is new anyway however, so this isn't really a breaking change.
  • Loading branch information
Jondolf committed Jun 21, 2024
1 parent 1570143 commit 7975464
Show file tree
Hide file tree
Showing 8 changed files with 946 additions and 279 deletions.
66 changes: 60 additions & 6 deletions src/plugins/collision/collider/backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,22 @@ use bevy::{
ecs::{intern::Interned, system::SystemId},
prelude::*,
};
use sync::SyncSet;

/// A plugin for handling generic collider backend logic.
///
/// - Initializes colliders, including [`AsyncCollider`] and [`AsyncSceneCollider`].
/// - Updates [`ColliderAabb`]s.
/// - Updates collider scale based on `Transform` scale.
/// - Updates collider mass properties, also updating rigid bodies accordingly.
///
/// This plugin should typically be used together with the [`ColliderHierarchyPlugin`].
///
/// ## Custom collision backends
///
/// By default, [`PhysicsPlugins`] adds this plugin for the [`Collider`] component.
/// You can also create custom collider backends by implementing the [`AnyCollider`] trait for a type.
/// You can also create custom collider backends by implementing the [`AnyCollider`]
/// and [`ScalableCollider`] traits for a type.
///
/// To use a custom collider backend, simply add the [`ColliderBackendPlugin`] with your collider type:
///
Expand All @@ -54,12 +57,9 @@ use bevy::{
/// }
/// ```
///
/// Assuming you have implemented [`AnyCollider`] correctly,
/// Assuming you have implemented the required traits correctly,
/// it should now work with the rest of the engine just like normal [`Collider`]s!
///
/// Remember to also add the [`ColliderHierarchyPlugin`] for your custom collider
/// type if you want transforms to work for them.
///
/// **Note**: [Spatial queries](spatial_query) are not supported for custom colliders yet.

pub struct ColliderBackendPlugin<C: ScalableCollider> {
Expand Down Expand Up @@ -98,10 +98,16 @@ impl<C: ScalableCollider> Plugin for ColliderBackendPlugin<C> {

// Register a component hook that updates mass properties of rigid bodies
// when the colliders attached to them are removed.
// Also removes `ColliderMarker` components.
app.world_mut()
.register_component_hooks::<C>()
.on_remove(|mut world, entity, _| {
let entity_ref = world.entity(entity);
// Remove the `ColliderMarker` associated with the collider.
// TODO: If the same entity had multiple *different* types of colliders, this would
// get removed even if just one collider was removed. This is a very niche edge case though.
world.commands().entity(entity).remove::<ColliderMarker>();

let entity_ref = world.entity_mut(entity);

// Get the needed collider components.
// TODO: Is there an efficient way to do this with QueryState?
Expand Down Expand Up @@ -138,6 +144,14 @@ impl<C: ScalableCollider> Plugin for ColliderBackendPlugin<C> {
),
);

// Update colliders based on the scale from `ColliderTransform`.
app.add_systems(
self.schedule,
update_collider_scale::<C>
.after(SyncSet::Update)
.before(SyncSet::Last),
);

let physics_schedule = app
.get_schedule_mut(PhysicsSchedule)
.expect("add PhysicsSchedule first");
Expand All @@ -161,6 +175,12 @@ impl<C: ScalableCollider> Plugin for ColliderBackendPlugin<C> {
}
}

/// A marker component for colliders. Inserted and removed automatically.
///
/// This is useful for filtering collider entities regardless of the [collider backend](ColliderBackendPlugin).
#[derive(Reflect, Component, Clone, Copy, Debug)]
pub struct ColliderMarker;

/// Initializes missing components for [colliders](Collider).
#[allow(clippy::type_complexity)]
pub(crate) fn init_colliders<C: AnyCollider>(
Expand All @@ -183,6 +203,7 @@ pub(crate) fn init_colliders<C: AnyCollider>(
density,
*mass_properties.unwrap_or(&collider.mass_properties(density.0)),
CollidingEntities::default(),
ColliderMarker,
));
}
}
Expand Down Expand Up @@ -394,6 +415,39 @@ fn update_aabb<C: AnyCollider>(
}
}

/// Updates the scale of colliders based on [`Transform`] scale.
#[allow(clippy::type_complexity)]
pub fn update_collider_scale<C: ScalableCollider>(
mut colliders: ParamSet<(
// Root bodies
Query<(&Transform, &mut C), Without<Parent>>,
// Child colliders
Query<(&ColliderTransform, &mut C), With<Parent>>,
)>,
) {
// Update collider scale for root bodies
for (transform, mut collider) in &mut colliders.p0() {
#[cfg(feature = "2d")]
let scale = transform.scale.truncate().adjust_precision();
#[cfg(feature = "3d")]
let scale = transform.scale.adjust_precision();
if scale != collider.scale() {
// TODO: Support configurable subdivision count for shapes that
// can't be represented without approximations after scaling.
collider.set_scale(scale, 10);
}
}

// Update collider scale for child colliders
for (collider_transform, mut collider) in &mut colliders.p1() {
if collider_transform.scale != collider.scale() {
// TODO: Support configurable subdivision count for shapes that
// can't be represented without approximations after scaling.
collider.set_scale(collider_transform.scale, 10);
}
}
}

/// A resource that stores the system ID for the system that reacts to collider removals.
#[derive(Resource)]
struct ColliderRemovalSystem(SystemId<(ColliderParent, ColliderMassProperties, ColliderTransform)>);
Expand Down
Loading

0 comments on commit 7975464

Please sign in to comment.