Skip to content

Commit

Permalink
experiment on stepping to test system ordering and consistent ecs state
Browse files Browse the repository at this point in the history
  • Loading branch information
Vrixyz committed Jul 9, 2024
1 parent 9142888 commit ba0cd76
Show file tree
Hide file tree
Showing 5 changed files with 174 additions and 21 deletions.
1 change: 1 addition & 0 deletions bevy_rapier2d/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ serde = { version = "1", features = ["derive"], optional = true }
bevy = { version = "0.14", default-features = false, features = [
"x11",
"bevy_state",
"bevy_debug_stepping",
] }
oorandom = "11"
approx = "0.5.1"
Expand Down
1 change: 1 addition & 0 deletions bevy_rapier3d/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ bevy = { version = "0.14", default-features = false, features = [
"x11",
"tonemapping_luts",
"bevy_state",
"bevy_debug_stepping",
] }
approx = "0.5.1"
glam = { version = "0.27", features = ["approx"] }
Expand Down
14 changes: 9 additions & 5 deletions src/pipeline/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ impl<'a> EventHandler for EventQueue<'a> {

#[cfg(test)]
mod test {
use bevy::time::TimePlugin;
use bevy::time::{TimePlugin, TimeUpdateStrategy};
use systems::tests::HeadlessRenderPlugin;

use crate::{plugin::*, prelude::*};
Expand Down Expand Up @@ -175,10 +175,14 @@ mod test {
// }
// app.finish();
// app.cleanup();
let mut time = app.world_mut().get_resource_mut::<Time<Virtual>>().unwrap();
time.set_relative_speed(1000f32);
for _ in 0..300 {
// FIXME: advance by set durations to avoid being at the mercy of the CPU.

// Simulates 60 updates per seconds
app.insert_resource(TimeUpdateStrategy::ManualDuration(
std::time::Duration::from_secs_f32(1f32 / 60f32),
));
// 2 seconds should be plenty of time for the cube to fall on the
// lowest collider.
for _ in 0..120 {
app.update();
}
let saved_collisions = app
Expand Down
152 changes: 152 additions & 0 deletions src/plugin/plugin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -335,3 +335,155 @@ pub fn setup_rapier_simulation_to_render_time(
commands.entity(e).insert(SimulationToRenderTime::default());
}
}

#[cfg(test)]
mod test {

use bevy::{
ecs::schedule::Stepping,
prelude::Component,
time::{TimePlugin, TimeUpdateStrategy},
};
use rapier::{data::Index, dynamics::RigidBodyHandle};
use systems::tests::HeadlessRenderPlugin;

use crate::{plugin::*, prelude::*};

#[cfg(feature = "dim3")]
fn cuboid(hx: Real, hy: Real, hz: Real) -> Collider {
Collider::cuboid(hx, hy, hz)
}
#[cfg(feature = "dim2")]
fn cuboid(hx: Real, hy: Real, _hz: Real) -> Collider {
Collider::cuboid(hx, hy)
}

#[derive(Component)]
pub struct TestMarker;

#[test]
pub fn hierarchy_link_propagation() {
return main();

use bevy::prelude::*;

fn run_test(app: &mut App) {
app.insert_resource(TimeUpdateStrategy::ManualDuration(
std::time::Duration::from_secs_f32(1f32 / 60f32),
));

app.add_systems(Update, setup_physics);

// while app.plugins_state() == bevy::app::PluginsState::Adding {
// #[cfg(not(target_arch = "wasm32"))]
// bevy::tasks::tick_global_task_pools_on_main_thread();
// }
// app.finish();
// app.cleanup();

let mut stepping = Stepping::new();

app.update();

stepping
.add_schedule(PostUpdate)
.add_schedule(Update)
.enable()
.set_breakpoint(PostUpdate, systems::on_add_entity_with_parent)
.set_breakpoint(PostUpdate, systems::init_rigid_bodies)
.set_breakpoint(PostUpdate, systems::on_change_world)
.set_breakpoint(PostUpdate, systems::sync_removals)
.set_breakpoint(Update, setup_physics);

app.insert_resource(stepping);

let mut stepping = app.world_mut().resource_mut::<Stepping>();
// Advancing once to get the context.
stepping.continue_frame();
app.update();
// arbitrary hardcoded amount to run the simulation for a few frames.
// This test uses stepping so the actual amount of frames is this `number / breakpoints`
for i in 0..20 {
let world = app.world_mut();
let stepping = world.resource_mut::<Stepping>();
if let Some(cursor) = &stepping.cursor() {
let system = world
.resource::<Schedules>()
.get(cursor.0)
.unwrap()
.systems()
.unwrap()
.find(|s| s.0 == cursor.1)
.unwrap();
println!(
"next system: {}",
system
.1
.name()
.to_string()
.split_terminator("::")
.last()
.unwrap()
);
} else {
println!("no cursor, new frame!");
}
let mut stepping = world.resource_mut::<Stepping>();
stepping.continue_frame();
app.update();
let context = app
.world_mut()
.query::<&RapierContext>()
.get_single(&app.world())
.unwrap();

println!("{:?}", &context.entity2body);
}
let context = app
.world_mut()
.query::<&RapierContext>()
.get_single(&app.world())
.unwrap();

assert_eq!(
context.entity2body.iter().next().unwrap().1,
// assert the generation is 0, that means we didn't modify it twice (due to change world detection)
&RigidBodyHandle(Index::from_raw_parts(0, 0))
);
}

fn main() {
let mut app = App::new();
app.add_plugins((
HeadlessRenderPlugin,
TransformPlugin,
TimePlugin,
RapierPhysicsPlugin::<NoUserData>::default(),
));
run_test(&mut app);
}

pub fn setup_physics(mut commands: Commands, mut counter: Local<i32>) {
// run on the 3rd iteration: I believe current logic is:
// - 1st is to setup bevy internals
// - 2nd is to wait for having stepping enabled, as I couldnt get it to work for the first update.
// - 3rd, we're looking to test adding a rapier entity while playing, opposed to a Startup function,
// which most examples are focused on.
let run_at = 3;
if *counter == run_at {
return;
}
*counter += 1;
if *counter < run_at {
return;
}

commands.spawn((
TransformBundle::from(Transform::from_xyz(0.0, 13.0, 0.0)),
RigidBody::Dynamic,
cuboid(0.5, 0.5, 0.5),
TestMarker,
));
}
}
}
27 changes: 11 additions & 16 deletions src/plugin/systems/multiple_rapier_contexts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,17 @@ use crate::geometry::RapierColliderHandle;
use crate::plugin::{RapierContext, RapierContextEntityLink};
use bevy::prelude::*;

/// If an entity is turned into the child of something with a physics world, the child should become a part of that physics world
/// If an entity is turned into the child of something with a physics context link,
/// the child should become a part of that physics context
///
/// If this fails to happen, weirdness will ensue.
pub fn on_add_entity_with_parent(
q_add_entity_without_parent: Query<
(Entity, &Parent),
(With<RapierContextEntityLink>, Changed<Parent>),
(
With<RapierContextEntityLink>,
Or<(Changed<RapierContextEntityLink>, Changed<Parent>)>,
),
>,
q_parent: Query<&Parent>,
q_physics_world: Query<&RapierContextEntityLink>,
Expand All @@ -27,7 +31,6 @@ pub fn on_add_entity_with_parent(
remove_old_physics(ent, &mut commands);
break;
}

parent = q_parent.get(parent_entity).ok().map(|x| x.get());
}
}
Expand Down Expand Up @@ -58,31 +61,23 @@ pub fn on_change_world(
) {
for (entity, new_physics_world) in &q_changed_worlds {
let context = q_context.get(new_physics_world.0);
if new_physics_world.is_added() {
continue;
}
// Ensure the world actually changed before removing them from the world
if !context
.map(|x| {
// They are already apart of this world if any of these are true
x.entity2impulse_joint.contains_key(&entity)
|| x.entity2multibody_joint.contains_key(&entity)
|| x.entity2collider.contains_key(&entity)
x.entity2collider.contains_key(&entity)
|| x.entity2body.contains_key(&entity)
|| x.entity2impulse_joint.contains_key(&entity)
|| x.entity2multibody_joint.contains_key(&entity)
})
.unwrap_or(false)
{
remove_old_physics(entity, &mut commands);

remove_old_physics(dbg!(entity), &mut commands);
bubble_down_world_change(
&mut commands,
entity,
&q_children,
*new_physics_world,
&q_physics_world,
);
}
}
*new_physics_world,
}

fn bubble_down_world_change(
Expand Down

0 comments on commit ba0cd76

Please sign in to comment.