bevy_xpbd icon indicating copy to clipboard operation
bevy_xpbd copied to clipboard

Freeze when cloning Collider

Open nobe4 opened this issue 3 weeks ago • 1 comments
trafficstars

Hi, love the lib, thanks for all your work 🙏

ℹ️ I found a way around the bug described below.

After experimenting for a while, I found a roundabout way to do what I wanted, without freezing the game.

The idea is to delay the creation of the Collider and to set it in a system that only does that. For some reason it seems to work and I could spawn/duplicate thousands of bullets with 0 freeze.

I will use that in my game for now, because at least it's working. But I'm still curious about the problem I describe.

use avian2d::prelude::*;
use bevy::prelude::*;

#[derive(Component, Clone)]
struct Bullet;

#[derive(Component)]
struct ToDup;

#[derive(Component, Clone)]
struct NeedCollider;

fn main() {
    App::new()
        .add_plugins((DefaultPlugins, PhysicsPlugins::default()))
        .insert_resource(Gravity(Vec2::ZERO))
        .add_systems(Startup, |mut cmds: Commands| {
            cmds.spawn(Camera2d);
        })
        .add_systems(Update, |q: Query<Entity, With<Bullet>>| {
            println!("{}", q.iter().len())
        })
        .add_systems(Update, spawn)
        .add_systems(Update, dup)
        .add_systems(Update, col)
        .run();
}

fn spawn(mut cmds: Commands, mut mt: ResMut<Assets<ColorMaterial>>, mut ms: ResMut<Assets<Mesh>>) {
    cmds.spawn((
        Bullet,
        NeedCollider,
        ToDup,
        Transform::default(),
        MeshMaterial2d(mt.add(ColorMaterial::from_color(Color::WHITE))),
        Mesh2d(ms.add(Circle::new(2.0))),
    ));
}

fn dup(
    mut cmds: Commands,
    bullets: Query<Entity, With<ToDup>>,
    mut mt: ResMut<Assets<ColorMaterial>>,
) {
    bullets.iter().for_each(|entity| {
        let mut bullet = cmds.entity(entity);
        bullet.remove::<ToDup>();

        bullet.clone_and_spawn().insert((
            MeshMaterial2d(mt.add(ColorMaterial::from_color(Color::BLACK))),
            LinearVelocity(Vec2 { x: 0.1, y: 0.0 }),
        ));
    });
}

fn col(mut cmds: Commands, bullets: Query<Entity, With<NeedCollider>>) {
    bullets.iter().for_each(|entity| {
        cmds.entity(entity)
            .insert((RigidBody::Dynamic, Collider::circle(2.0)));
    });
}

I'm working on a game that uses some component caching, which prepares an entity, and when needed, clone it for actual usage. This all works well, except for when I try to clone a Collider. I've reduced the code to a minimum of:

[dependencies]
avian2d = "0.4"
bevy = "0.17.2"
use avian2d::prelude::*;
use bevy::prelude::*;

#[derive(Component, Clone)]
struct NeedDup;

fn main() {
    App::new()
        .add_plugins((DefaultPlugins, PhysicsPlugins::default()))
        .add_systems(Startup, |mut cmds: Commands| {
            cmds.spawn(Camera2d);
        })
        .add_systems(Update, spawn)
        .add_systems(Update, dup)
        .run();
}

fn spawn(mut cmds: Commands, mut mt: ResMut<Assets<ColorMaterial>>, mut ms: ResMut<Assets<Mesh>>) {
    cmds.spawn((
        NeedDup,
        RigidBody::Dynamic,
        Collider::circle(2.0),
        MeshMaterial2d(mt.add(ColorMaterial::from_color(Color::WHITE))),
        Mesh2d(ms.add(Circle::new(2.0))),
    ));
}

fn dup(mut cmds: Commands, entities: Query<Entity, With<NeedDup>>) {
    entities.iter().for_each(|entity| {
        cmds.entity(entity).remove::<NeedDup>().clone_and_spawn();
    });
}
Without the collider, I can hold the left mouse button for a long time. I get some warnings, but otherwise the bullet are displayed/moved correctly Image
As soon as I enable the collider, it freezes right after a couple of bullets Image

As far as I could tell, the Collider::circle is solely responsible for making this freeze.

What am I doing wrong? How can I make the code better? Thanks!


As far as I can tell, spawning many entities at the same position isn't responsible for this.
// similar
fn click(
    mut cmds: Commands,
    m: Res<ButtonInput<MouseButton>>,
    mut mt: ResMut<Assets<ColorMaterial>>,
    mut ms: ResMut<Assets<Mesh>>,
) {
    if !m.pressed(MouseButton::Left) && !m.just_pressed(MouseButton::Right) {
        return;
    }

    cmds.spawn_batch([
        (
            Bullet,
            ToDup,
            RigidBody::Dynamic,
            Collider::circle(2.0),
            Transform::default(),
            MeshMaterial2d(mt.add(ColorMaterial::from_color(Color::WHITE))),
            Mesh2d(ms.add(Circle::new(2.0))),
        ),
        (
            Bullet,
            ToDup,
            RigidBody::Dynamic,
            Collider::circle(2.0),
            Transform::default(),
            MeshMaterial2d(mt.add(ColorMaterial::from_color(Color::WHITE))),
            Mesh2d(ms.add(Circle::new(2.0))),
        ),
        // and more
    ]);

// similar
}

Removing the Collider fixes the issue, but trying to add it back, either directly or indirectly is causing the same issue.
  • Direct

    fn dup(
        mut cmds: Commands,
        bullets: Query<Entity, With<ToDup>>,
        mut mt: ResMut<Assets<ColorMaterial>>,
    ) {
        bullets.iter().for_each(|entity| {
            let mut bullet = cmds.entity(entity);
            bullet.remove::<ToDup>();
    
            bullet
                .clone_and_spawn()
                .remove::<Collider>()
                .insert((
                    Collider::circle(2.0),
                    MeshMaterial2d(mt.add(ColorMaterial::from_color(Color::BLACK))),
                    LinearVelocity(Vec2 { x: 10.0, y: 0.0 }),
                ));
        });
    }
    
  • Indirect

    fn dup(
        mut cmds: Commands,
        bullets: Query<Entity, With<ToDup>>,
        mut mt: ResMut<Assets<ColorMaterial>>,
    ) {
        bullets.iter().for_each(|entity| {
            let mut bullet = cmds.entity(entity);
            bullet.remove::<ToDup>();
    
            bullet
                .clone_and_spawn()
                .remove::<Collider>()
                .insert((
                    NeedCollider,
                    MeshMaterial2d(mt.add(ColorMaterial::from_color(Color::BLACK))),
                    LinearVelocity(Vec2 { x: 10.0, y: 0.0 }),
                ));
        });
    }
    
    fn col(mut cmds: Commands, bullets: Query<Entity, With<NeedCollider>>) {
        bullets.iter().for_each(|entity| {
            cmds.entity(entity).insert(Collider::circle(2.0));
        });
    }
    

nobe4 avatar Oct 30 '25 19:10 nobe4