diff --git a/benches/benches/bevy_ecs/observers/mod.rs b/benches/benches/bevy_ecs/observers/mod.rs index 15ac476abc..0b8c3f2486 100644 --- a/benches/benches/bevy_ecs/observers/mod.rs +++ b/benches/benches/bevy_ecs/observers/mod.rs @@ -1,6 +1,8 @@ use criterion::criterion_group; mod propagation; +mod simple; use propagation::*; +use simple::*; -criterion_group!(observer_benches, event_propagation); +criterion_group!(observer_benches, event_propagation, observe_simple); diff --git a/benches/benches/bevy_ecs/observers/propagation.rs b/benches/benches/bevy_ecs/observers/propagation.rs index ed22b1b519..06d2d45d21 100644 --- a/benches/benches/bevy_ecs/observers/propagation.rs +++ b/benches/benches/bevy_ecs/observers/propagation.rs @@ -1,99 +1,66 @@ -use bevy_app::{App, First, Startup}; use bevy_ecs::{ component::Component, entity::Entity, event::{Event, EventWriter}, observer::Trigger, - query::{Or, With, Without}, - system::{Commands, EntityCommands, Query}, + world::World, }; use bevy_hierarchy::{BuildChildren, Children, Parent}; -use bevy_internal::MinimalPlugins; use criterion::{black_box, criterion_group, criterion_main, Criterion}; +use rand::{prelude::SliceRandom, SeedableRng}; use rand::{seq::IteratorRandom, Rng}; +use rand_chacha::ChaCha8Rng; const DENSITY: usize = 20; // percent of nodes with listeners const ENTITY_DEPTH: usize = 64; const ENTITY_WIDTH: usize = 200; const N_EVENTS: usize = 500; +fn deterministic_rand() -> ChaCha8Rng { + ChaCha8Rng::seed_from_u64(42) +} pub fn event_propagation(criterion: &mut Criterion) { let mut group = criterion.benchmark_group("event_propagation"); group.warm_up_time(std::time::Duration::from_millis(500)); group.measurement_time(std::time::Duration::from_secs(4)); - group.bench_function("baseline", |bencher| { - let mut app = App::new(); - app.add_plugins(MinimalPlugins) - .add_systems(Startup, spawn_listener_hierarchy); - app.update(); - - bencher.iter(|| { - black_box(app.update()); - }); - }); - group.bench_function("single_event_type", |bencher| { - let mut app = App::new(); - app.add_plugins(MinimalPlugins) - .add_systems( - Startup, - ( - spawn_listener_hierarchy, - add_listeners_to_hierarchy::, - ), - ) - .add_systems(First, send_events::<1, N_EVENTS>); - app.update(); + let mut world = World::new(); + let (roots, leaves, nodes) = spawn_listener_hierarchy(&mut world); + add_listeners_to_hierarchy::(&roots, &leaves, &nodes, &mut world); bencher.iter(|| { - black_box(app.update()); + send_events::<1, N_EVENTS>(&mut world, &leaves); }); }); group.bench_function("single_event_type_no_listeners", |bencher| { - let mut app = App::new(); - app.add_plugins(MinimalPlugins) - .add_systems( - Startup, - ( - spawn_listener_hierarchy, - add_listeners_to_hierarchy::, - ), - ) - .add_systems(First, send_events::<9, N_EVENTS>); - app.update(); + let mut world = World::new(); + let (roots, leaves, nodes) = spawn_listener_hierarchy(&mut world); + add_listeners_to_hierarchy::(&roots, &leaves, &nodes, &mut world); bencher.iter(|| { - black_box(app.update()); + // no listeners to observe TestEvent<9> + send_events::<9, N_EVENTS>(&mut world, &leaves); }); }); group.bench_function("four_event_types", |bencher| { - let mut app = App::new(); + let mut world = World::new(); + let (roots, leaves, nodes) = spawn_listener_hierarchy(&mut world); const FRAC_N_EVENTS_4: usize = N_EVENTS / 4; const FRAC_DENSITY_4: usize = DENSITY / 4; - - app.add_plugins(MinimalPlugins) - .add_systems( - Startup, - ( - spawn_listener_hierarchy, - add_listeners_to_hierarchy::, - add_listeners_to_hierarchy::, - add_listeners_to_hierarchy::, - add_listeners_to_hierarchy::, - ), - ) - .add_systems(First, send_events::<1, FRAC_N_EVENTS_4>) - .add_systems(First, send_events::<2, FRAC_N_EVENTS_4>) - .add_systems(First, send_events::<3, FRAC_N_EVENTS_4>) - .add_systems(First, send_events::<4, FRAC_N_EVENTS_4>); - app.update(); + add_listeners_to_hierarchy::(&roots, &leaves, &nodes, &mut world); + add_listeners_to_hierarchy::(&roots, &leaves, &nodes, &mut world); + add_listeners_to_hierarchy::(&roots, &leaves, &nodes, &mut world); + add_listeners_to_hierarchy::(&roots, &leaves, &nodes, &mut world); bencher.iter(|| { - black_box(app.update()); + send_events::<1, FRAC_N_EVENTS_4>(&mut world, &leaves); + send_events::<2, FRAC_N_EVENTS_4>(&mut world, &leaves); + send_events::<3, FRAC_N_EVENTS_4>(&mut world, &leaves); + send_events::<4, FRAC_N_EVENTS_4>(&mut world, &leaves); }); }); @@ -108,44 +75,54 @@ impl Event for TestEvent { const AUTO_PROPAGATE: bool = true; } -fn send_events( - mut commands: Commands, - entities: Query>, -) { - let target = entities.iter().choose(&mut rand::thread_rng()).unwrap(); +fn send_events(world: &mut World, leaves: &Vec) { + let target = leaves.iter().choose(&mut rand::thread_rng()).unwrap(); + (0..N_EVENTS).for_each(|_| { - commands.trigger_targets(TestEvent:: {}, target); + world.trigger_targets(TestEvent:: {}, *target); }); } -fn spawn_listener_hierarchy(mut commands: Commands) { +fn spawn_listener_hierarchy(world: &mut World) -> (Vec, Vec, Vec) { + let mut roots = vec![]; + let mut leaves = vec![]; + let mut nodes = vec![]; for _ in 0..ENTITY_WIDTH { - let mut parent = commands.spawn_empty().id(); + let mut parent = world.spawn_empty().id(); + roots.push(parent); for _ in 0..ENTITY_DEPTH { - let child = commands.spawn_empty().id(); - commands.entity(parent).add_child(child); + let child = world.spawn_empty().id(); + nodes.push(child); + + world.entity_mut(parent).add_child(child); parent = child; } + nodes.pop(); + leaves.push(parent); + } + (roots, leaves, nodes) +} + +fn add_listeners_to_hierarchy( + roots: &Vec, + leaves: &Vec, + nodes: &Vec, + world: &mut World, +) { + for e in roots.iter() { + world.entity_mut(*e).observe(empty_listener::); + } + for e in leaves.iter() { + world.entity_mut(*e).observe(empty_listener::); + } + let mut rng = deterministic_rand(); + for e in nodes.iter() { + if rng.gen_bool(DENSITY as f64 / 100.0) { + world.entity_mut(*e).observe(empty_listener::); + } } } -fn empty_listener(_trigger: Trigger>) {} - -fn add_listeners_to_hierarchy( - mut commands: Commands, - roots_and_leaves: Query, Without)>>, - nodes: Query, With)>, -) { - for entity in &roots_and_leaves { - commands.entity(entity).observe(empty_listener::); - } - for entity in &nodes { - maybe_insert_listener::(&mut commands.entity(entity)); - } -} - -fn maybe_insert_listener(commands: &mut EntityCommands) { - if rand::thread_rng().gen_bool(DENSITY as f64 / 100.0) { - commands.observe(empty_listener::); - } +fn empty_listener(trigger: Trigger>) { + black_box(trigger); } diff --git a/benches/benches/bevy_ecs/observers/simple.rs b/benches/benches/bevy_ecs/observers/simple.rs new file mode 100644 index 0000000000..6da8669456 --- /dev/null +++ b/benches/benches/bevy_ecs/observers/simple.rs @@ -0,0 +1,49 @@ +use bevy_ecs::{entity::Entity, event::Event, observer::Trigger, world::World}; + +use criterion::{black_box, criterion_group, criterion_main, Criterion}; +use rand::{prelude::SliceRandom, SeedableRng}; +use rand_chacha::ChaCha8Rng; +fn deterministic_rand() -> ChaCha8Rng { + ChaCha8Rng::seed_from_u64(42) +} + +#[derive(Clone, Event)] +struct EventBase; + +pub fn observe_simple(criterion: &mut Criterion) { + let mut group = criterion.benchmark_group("observe"); + group.warm_up_time(std::time::Duration::from_millis(500)); + group.measurement_time(std::time::Duration::from_secs(4)); + + group.bench_function("trigger_simple", |bencher| { + let mut world = World::new(); + world.observe(empty_listener_base); + bencher.iter(|| { + for _ in 0..10000 { + world.trigger(EventBase) + } + }); + }); + + group.bench_function("trigger_targets_simple/10000_entity", |bencher| { + let mut world = World::new(); + let mut entities = vec![]; + for _ in 0..10000 { + entities.push(world.spawn_empty().observe(empty_listener_base).id()); + } + entities.shuffle(&mut deterministic_rand()); + bencher.iter(|| { + send_base_event(&mut world, &entities); + }); + }); + + group.finish(); +} + +fn empty_listener_base(trigger: Trigger) { + black_box(trigger); +} + +fn send_base_event(world: &mut World, entities: &Vec) { + world.trigger_targets(EventBase, entities); +} diff --git a/crates/bevy_ecs/src/observer/trigger_event.rs b/crates/bevy_ecs/src/observer/trigger_event.rs index d50e4ab78a..6323eb42c9 100644 --- a/crates/bevy_ecs/src/observer/trigger_event.rs +++ b/crates/bevy_ecs/src/observer/trigger_event.rs @@ -175,3 +175,13 @@ impl TriggerTargets for [ComponentId; N] { &[] } } + +impl TriggerTargets for &Vec { + fn components(&self) -> &[ComponentId] { + &[] + } + + fn entities(&self) -> &[Entity] { + self.as_slice() + } +}