Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions benches/benches/bevy_ecs/world/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,9 @@ pub fn spawn_commands(criterion: &mut Criterion) {
for i in 0..entity_count {
let mut entity = commands.spawn_empty();
entity
.insert_if(A, || black_box(i % 2 == 0))
.insert_if(B, || black_box(i % 3 == 0))
.insert_if(C, || black_box(i % 4 == 0));
.insert_if(A, black_box(i % 2 == 0))
.insert_if(B, black_box(i % 3 == 0))
.insert_if(C, black_box(i % 4 == 0));

if black_box(i % 5 == 0) {
entity.despawn();
Expand Down
67 changes: 67 additions & 0 deletions crates/bevy_ecs/src/system/commands/condition.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
//! Contains the definition of the [`CommandCondition`] trait,
//! as well as blanket implementations for boolean values and closures.
//!
//! These conditions act as predicates for [`Commands`](crate::system::Commands)
//! and [`EntityCommands`](crate::system::EntityCommands), allowing for
//! conditional world mutations in a fluent, chainable manner.

/// A predicate used to determine if a world mutation should be applied.
///
/// Types implementing this trait can be evaluated to a boolean state. This is
/// primarily used in conditional command methods like `insert_if` or `remove_if`.
///
/// # Ownership and Lifecycle
///
/// Since [`evaluate`](Self::evaluate) takes `self` by value, the condition is
/// consumed upon evaluation. This allows closures to capture and move data
/// from their environment, making it a flexible tool for one-off system logic.
///
/// # Examples
///
/// Using a simple boolean variable:
/// ```
/// # use bevy_ecs::prelude::*;
/// # #[derive(Component)]
/// # struct Poison;
/// # fn system(mut commands: Commands, entity: Entity) {
/// let is_immune = true;
/// commands.entity(entity).insert_if(Poison, !is_immune);
/// # }
/// ```
///
/// Using a closure for lazy evaluation:
/// ```
/// # use bevy_ecs::prelude::*;
/// # #[derive(Component)]
/// # struct Health(u32);
/// # fn system(mut commands: Commands, entity: Entity) {
/// commands.entity(entity).remove_if::<Health>(|| {
/// // Complex logic evaluated at call time
/// 1 + 1 == 2
/// });
/// # }
/// ```
pub trait CommandCondition: Sized {
/// Evaluates the condition, returning `true` if the associated action should proceed.
///
/// This consumes the condition.
fn evaluate(self) -> bool;
}

impl CommandCondition for bool {
#[inline]
fn evaluate(self) -> bool {
self
}
}

impl<F> CommandCondition for F
where
F: FnOnce() -> bool,
{
/// Executes the closure and returns its result.
#[inline]
fn evaluate(self) -> bool {
self()
}
}
70 changes: 38 additions & 32 deletions crates/bevy_ecs/src/system/commands/mod.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
pub mod command;
pub mod condition;
pub mod entity_command;

#[cfg(feature = "std")]
mod parallel_scope;

use bevy_ptr::move_as_ptr;
pub use command::Command;
pub use condition::CommandCondition;
pub use entity_command::EntityCommand;

#[cfg(feature = "std")]
Expand Down Expand Up @@ -1445,17 +1447,18 @@ impl<'a> EntityCommands<'a> {
/// fn add_health_system(mut commands: Commands, player: Res<PlayerEntity>) {
/// commands
/// .entity(player.entity)
/// .insert_if(Health(10), || !player.is_spectator())
/// .insert_if(Health(10), !player.is_spectator())
/// .remove::<StillLoadingStats>();
/// }
/// # bevy_ecs::system::assert_is_system(add_health_system);
/// ```
#[track_caller]
pub fn insert_if<F>(&mut self, bundle: impl Bundle, condition: F) -> &mut Self
where
F: FnOnce() -> bool,
{
if condition() {
pub fn insert_if(
&mut self,
bundle: impl Bundle,
condition: impl CommandCondition,
) -> &mut Self {
if condition.evaluate() {
self.insert(bundle)
} else {
self
Expand All @@ -1480,11 +1483,12 @@ impl<'a> EntityCommands<'a> {
/// This is the same as [`EntityCommands::insert_if`], but in case of duplicate
/// components will leave the old values instead of replacing them with new ones.
#[track_caller]
pub fn insert_if_new_and<F>(&mut self, bundle: impl Bundle, condition: F) -> &mut Self
where
F: FnOnce() -> bool,
{
if condition() {
pub fn insert_if_new_and(
&mut self,
bundle: impl Bundle,
condition: impl CommandCondition,
) -> &mut Self {
if condition.evaluate() {
self.insert_if_new(bundle)
} else {
self
Expand Down Expand Up @@ -1604,11 +1608,12 @@ impl<'a> EntityCommands<'a> {
/// If the entity does not exist when this command is executed,
/// the resulting error will be ignored.
#[track_caller]
pub fn try_insert_if<F>(&mut self, bundle: impl Bundle, condition: F) -> &mut Self
where
F: FnOnce() -> bool,
{
if condition() {
pub fn try_insert_if(
&mut self,
bundle: impl Bundle,
condition: impl CommandCondition,
) -> &mut Self {
if condition.evaluate() {
self.try_insert(bundle)
} else {
self
Expand All @@ -1626,11 +1631,12 @@ impl<'a> EntityCommands<'a> {
/// If the entity does not exist when this command is executed,
/// the resulting error will be ignored.
#[track_caller]
pub fn try_insert_if_new_and<F>(&mut self, bundle: impl Bundle, condition: F) -> &mut Self
where
F: FnOnce() -> bool,
{
if condition() {
pub fn try_insert_if_new_and(
&mut self,
bundle: impl Bundle,
condition: impl CommandCondition,
) -> &mut Self {
if condition.evaluate() {
self.try_insert_if_new(bundle)
} else {
self
Expand Down Expand Up @@ -1722,13 +1728,13 @@ impl<'a> EntityCommands<'a> {
/// fn remove_combat_stats_system(mut commands: Commands, player: Res<PlayerEntity>) {
/// commands
/// .entity(player.entity)
/// .remove_if::<(Defense, CombatBundle)>(|| !player.is_spectator());
/// .remove_if::<(Defense, CombatBundle)>(!player.is_spectator());
/// }
/// # bevy_ecs::system::assert_is_system(remove_combat_stats_system);
/// ```
#[track_caller]
pub fn remove_if<B: Bundle>(&mut self, condition: impl FnOnce() -> bool) -> &mut Self {
if condition() {
pub fn remove_if<B: Bundle>(&mut self, condition: impl CommandCondition) -> &mut Self {
if condition.evaluate() {
self.remove::<B>()
} else {
self
Expand All @@ -1744,8 +1750,8 @@ impl<'a> EntityCommands<'a> {
/// If the entity does not exist when this command is executed,
/// the resulting error will be ignored.
#[track_caller]
pub fn try_remove_if<B: Bundle>(&mut self, condition: impl FnOnce() -> bool) -> &mut Self {
if condition() {
pub fn try_remove_if<B: Bundle>(&mut self, condition: impl CommandCondition) -> &mut Self {
if condition.evaluate() {
self.try_remove::<B>()
} else {
self
Expand Down Expand Up @@ -2643,13 +2649,13 @@ mod tests {
// insert components
let entity = Commands::new(&mut command_queue1, &world)
.spawn(())
.insert_if(W(1u8), || true)
.insert_if(W(2u8), || false)
.insert_if(W(1u8), true)
.insert_if(W(2u8), false)
.insert_if_new(W(1u16))
.insert_if_new(W(2u16))
.insert_if_new_and(W(1u32), || false)
.insert_if_new_and(W(2u32), || true)
.insert_if_new_and(W(3u32), || true)
.insert_if_new_and(W(1u32), false)
.insert_if_new_and(W(2u32), true)
.insert_if_new_and(W(3u32), true)
.id();
command_queue1.apply(&mut world);

Expand All @@ -2664,7 +2670,7 @@ mod tests {
// in another command queue
Commands::new(&mut command_queue1, &world)
.entity(entity)
.try_insert_if_new_and(W(1u64), || true);
.try_insert_if_new_and(W(1u64), true);

let mut command_queue2 = CommandQueue::default();
Commands::new(&mut command_queue2, &world)
Expand Down
2 changes: 1 addition & 1 deletion examples/3d/motion_blur.rs
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ fn spawn_cars(
make_wheel(-1.0, -1.0)
],
))
.insert_if(CameraTracked, || i == 0);
.insert_if(CameraTracked, i == 0);
}
}

Expand Down
6 changes: 3 additions & 3 deletions examples/3d/solari.rs
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,7 @@ fn setup_many_lights(
..default()
})),
))
.insert_if(Mesh3d(plane_mesh), || args.pathtracer != Some(true));
.insert_if(Mesh3d(plane_mesh), args.pathtracer != Some(true));

for _ in 0..8000 {
commands
Expand All @@ -279,7 +279,7 @@ fn setup_many_lights(
rng.random_range(-180.0..=180.0),
)),
))
.insert_if(Mesh3d(cube_mesh.clone()), || args.pathtracer != Some(true));
.insert_if(Mesh3d(cube_mesh.clone()), args.pathtracer != Some(true));
}

for x in -10..=10 {
Expand All @@ -304,7 +304,7 @@ fn setup_many_lights(
(y * 20) as f32,
)),
))
.insert_if(Mesh3d(sphere_mesh.clone()), || {
.insert_if(Mesh3d(sphere_mesh.clone()), {
args.pathtracer != Some(true)
});
}
Expand Down
12 changes: 6 additions & 6 deletions examples/3d/ssao.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,43 +114,43 @@ fn update(
quality_level: ScreenSpaceAmbientOcclusionQualityLevel::Low,
..current_ssao
},
|| keycode.just_pressed(KeyCode::Digit2),
keycode.just_pressed(KeyCode::Digit2),
)
.insert_if(
ScreenSpaceAmbientOcclusion {
quality_level: ScreenSpaceAmbientOcclusionQualityLevel::Medium,
..current_ssao
},
|| keycode.just_pressed(KeyCode::Digit3),
keycode.just_pressed(KeyCode::Digit3),
)
.insert_if(
ScreenSpaceAmbientOcclusion {
quality_level: ScreenSpaceAmbientOcclusionQualityLevel::High,
..current_ssao
},
|| keycode.just_pressed(KeyCode::Digit4),
keycode.just_pressed(KeyCode::Digit4),
)
.insert_if(
ScreenSpaceAmbientOcclusion {
quality_level: ScreenSpaceAmbientOcclusionQualityLevel::Ultra,
..current_ssao
},
|| keycode.just_pressed(KeyCode::Digit5),
keycode.just_pressed(KeyCode::Digit5),
)
.insert_if(
ScreenSpaceAmbientOcclusion {
constant_object_thickness: (current_ssao.constant_object_thickness * 2.0).min(4.0),
..current_ssao
},
|| keycode.just_pressed(KeyCode::ArrowUp),
keycode.just_pressed(KeyCode::ArrowUp),
)
.insert_if(
ScreenSpaceAmbientOcclusion {
constant_object_thickness: (current_ssao.constant_object_thickness * 0.5)
.max(0.0625),
..current_ssao
},
|| keycode.just_pressed(KeyCode::ArrowDown),
keycode.just_pressed(KeyCode::ArrowDown),
);
if keycode.just_pressed(KeyCode::Digit1) {
commands.remove::<ScreenSpaceAmbientOcclusion>();
Expand Down
16 changes: 8 additions & 8 deletions examples/large_scenes/bistro/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,7 @@ pub fn setup(mut commands: Commands, asset_server: Res<AssetServer>, args: Res<A
}
.build(),
))
.insert_if(OcclusionCulling, || !args.no_shadow_occlusion_culling);
.insert_if(OcclusionCulling, !args.no_shadow_occlusion_culling);

// Camera
let mut cam = commands.spawn((
Expand Down Expand Up @@ -285,13 +285,13 @@ pub fn setup(mut commands: Commands, asset_server: Res<AssetServer>, args: Res<A
FreeCamera::default(),
Spin,
));
cam.insert_if(DepthPrepass, || args.deferred)
.insert_if(DeferredPrepass, || args.deferred)
.insert_if(OcclusionCulling, || !args.no_view_occlusion_culling)
.insert_if(NoFrustumCulling, || args.no_frustum_culling)
.insert_if(NoAutomaticBatching, || args.no_automatic_batching)
.insert_if(NoIndirectDrawing, || args.no_indirect_drawing)
.insert_if(NoCpuCulling, || args.no_cpu_culling);
cam.insert_if(DepthPrepass, args.deferred)
.insert_if(DeferredPrepass, args.deferred)
.insert_if(OcclusionCulling, !args.no_view_occlusion_culling)
.insert_if(NoFrustumCulling, args.no_frustum_culling)
.insert_if(NoAutomaticBatching, args.no_automatic_batching)
.insert_if(NoIndirectDrawing, args.no_indirect_drawing)
.insert_if(NoCpuCulling, args.no_cpu_culling);
if !args.minimal {
cam.insert((
Bloom {
Expand Down
16 changes: 8 additions & 8 deletions examples/large_scenes/caldera_hotel/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ pub fn setup(
overlap_proportion: 0.2,
}),
))
.insert_if(OcclusionCulling, || !args.no_shadow_occlusion_culling);
.insert_if(OcclusionCulling, !args.no_shadow_occlusion_culling);

// Camera
let mut cam = commands.spawn((
Expand All @@ -221,13 +221,13 @@ pub fn setup(
Spin,
));

cam.insert_if(DepthPrepass, || args.deferred)
.insert_if(DeferredPrepass, || args.deferred)
.insert_if(OcclusionCulling, || !args.no_view_occlusion_culling)
.insert_if(NoFrustumCulling, || args.no_frustum_culling)
.insert_if(NoAutomaticBatching, || args.no_automatic_batching)
.insert_if(NoIndirectDrawing, || args.no_indirect_drawing)
.insert_if(NoCpuCulling, || args.no_cpu_culling);
cam.insert_if(DepthPrepass, args.deferred)
.insert_if(DeferredPrepass, args.deferred)
.insert_if(OcclusionCulling, !args.no_view_occlusion_culling)
.insert_if(NoFrustumCulling, args.no_frustum_culling)
.insert_if(NoAutomaticBatching, args.no_automatic_batching)
.insert_if(NoIndirectDrawing, args.no_indirect_drawing)
.insert_if(NoCpuCulling, args.no_cpu_culling);

if !args.minimal {
cam.insert((
Expand Down
4 changes: 2 additions & 2 deletions examples/stress_tests/many_cubes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -201,8 +201,8 @@ fn setup(
.looking_at(Vec3::ZERO, Vec3::Y)
.mul_transform(*transform),
))
.insert_if(NoFrustumCulling, || args.no_frustum_culling)
.insert_if(NoAutomaticBatching, || args.no_automatic_batching);
.insert_if(NoFrustumCulling, args.no_frustum_culling)
.insert_if(NoAutomaticBatching, args.no_automatic_batching);
}

// camera
Expand Down
4 changes: 2 additions & 2 deletions examples/testbed/ui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1764,8 +1764,8 @@ mod outer_color {
},
BorderColor::all(bevy::color::palettes::css::RED),
))
.insert_if(BackgroundColor(Color::WHITE), || !invert)
.insert_if(OuterColor(Color::WHITE), || invert);
.insert_if(BackgroundColor(Color::WHITE), !invert)
.insert_if(OuterColor(Color::WHITE), invert);
}
});
}
Expand Down