dimforge / rapier

2D and 3D physics engines focused on performance.
https://rapier.rs
Apache License 2.0
4.04k stars 250 forks source link

Feature request: Panic if QueryPipeline fails to be updated #445

Closed RedBreadcat closed 7 months ago

RedBreadcat commented 1 year ago

The following code softlocks within physics.step(); in the loop, after "Step: 9" is printed. The freeze does not occur if self.query_pipeline.update(&self.bodies, &self.colliders); is uncommented.

use rapier2d::{
    na::{Isometry2, Vector2},
    prelude::*,
};

struct Physics {
    pipeline: PhysicsPipeline,
    query_pipeline: QueryPipeline,
    integration_parameters: IntegrationParameters,
    island_manager: IslandManager,
    broad_phase: BroadPhase,
    narrow_phase: NarrowPhase,
    bodies: RigidBodySet,
    colliders: ColliderSet,
    impulse_joints: ImpulseJointSet,
    multibody_joints: MultibodyJointSet,
    ccd_solver: CCDSolver,
}

impl Physics {
    fn step(&mut self) {
        self.pipeline.step(
            &Vector2::default(),
            &self.integration_parameters,
            &mut self.island_manager,
            &mut self.broad_phase,
            &mut self.narrow_phase,
            &mut self.bodies,
            &mut self.colliders,
            &mut self.impulse_joints,
            &mut self.multibody_joints,
            &mut self.ccd_solver,
            Some(&mut self.query_pipeline),
            &(),
            &(),
        );

        //self.query_pipeline.update(&self.bodies, &self.colliders);
    }

    fn build_rb_kinematic_with_collider(
        &mut self,
        pos: Vector2<f32>,
    ) -> (RigidBodyHandle, ColliderHandle) {
        println!("build");
        let rb = Self::build_rigidbody_kinematic_template(pos);
        let rbh = self.bodies.insert(rb);
        let col = Self::build_primary_collider_kinematic_template();
        let colh = self
            .colliders
            .insert_with_parent(col, rbh, &mut self.bodies);

        (rbh, colh)
    }

    fn build_rigidbody_kinematic_template(pos: Vector2<f32>) -> RigidBody {
        RigidBodyBuilder::new(RigidBodyType::KinematicVelocityBased)
            .position(Isometry2::new(pos, 0.0))
            .gravity_scale(0.0)
            .can_sleep(true)
            .build()
    }

    fn build_primary_collider_kinematic_template() -> Collider {
        let shape = SharedShape::ball(1.0);
        let cb = ColliderBuilder::new(shape);

        cb.build()
    }
}

impl Default for Physics {
    fn default() -> Self {
        let pipeline = PhysicsPipeline::new();
        let query_pipeline = QueryPipeline::new();
        let integration_parameters = IntegrationParameters {
            dt: 0.01,
            ..Default::default()
        };
        let island_manager = IslandManager::new();
        let broad_phase = BroadPhase::new();
        let narrow_phase = NarrowPhase::new();
        let bodies = RigidBodySet::new();
        let colliders = ColliderSet::new();
        let impulse_joints = ImpulseJointSet::new();
        let multibody_joints = MultibodyJointSet::new();
        let ccd_solver = CCDSolver::new();

        Self {
            pipeline,
            query_pipeline,
            integration_parameters,
            island_manager,
            broad_phase,
            narrow_phase,
            bodies,
            colliders,
            impulse_joints,
            multibody_joints,
            ccd_solver,
        }
    }
}

fn main() {
    let mut physics = Physics::default();

    let a = Vector::new(1.0, 0.0);
    let b = Vector::new(0.0, 0.0);

    physics.build_rb_kinematic_with_collider(a);

    let mut step = 0;
    loop {
        // 2 are needed
        physics.build_rb_kinematic_with_collider(b);
        physics.build_rb_kinematic_with_collider(b);

        step += 1;
        println!("Step: {}", step);
        physics.step();
    }
}

It was a bit difficult to figure out what was happening, as the freeze only occurred when rigidbodies were in certain positions. (If a == b, I could not trigger a freeze). If possible, it would be nice if the code panicked, or a more elegant solution that made it impossible for one to forget to update the QueryPipeline.

The freeze itself occurred inside this function call: https://github.com/dimforge/rapier/blob/1a4183cc94acc3210e4ae467abbea7d68e51c5ff/src/pipeline/query_pipeline.rs#L349

In more complex code that periodically deleted rigidbodies and colliders, and had dynamic physics objects too, I occasionally panicked inside the closure on the next line, due to the handle's two internal integer components being huge numbers that looked like uninitialized memory.

Thank you for your work!

sebcrozet commented 1 year ago

The qbvh::refit should not freeze (and, more generally, the query pipeline’s update should never fail). This is a bug in the QBVH update code.

sebcrozet commented 7 months ago

I wasn’t able to reproduce the bug with the provided code, but, other similar issues were reported and a fix just got merged in https://github.com/dimforge/parry/pull/185. So I’m closing this issue, but, feel free to open a new issue if this happens again.