diff --git a/Cargo.toml b/Cargo.toml index 699ab830ff4f5..d052bb0e0ceb0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -190,6 +190,14 @@ path = "examples/diagnostics/print_diagnostics.rs" name = "event" path = "examples/ecs/event.rs" +[[example]] +name = "event_stage" +path = "examples/ecs/event_stage.rs" + +[[example]] +name = "any_event_stage" +path = "examples/ecs/any_event_stage.rs" + [[example]] name = "fixed_timestep" path = "examples/ecs/fixed_timestep.rs" diff --git a/crates/bevy_app/src/any_event_stage.rs b/crates/bevy_app/src/any_event_stage.rs new file mode 100644 index 0000000000000..cd4d30efcd8ae --- /dev/null +++ b/crates/bevy_app/src/any_event_stage.rs @@ -0,0 +1,118 @@ +use super::event::{EventReader, Events}; +use bevy_ecs::{Stage, World, Resources, System, IntoSystem, Local, Res, ShouldRun, SystemStage, IntoChainSystem}; +use std::marker::PhantomData; + +pub struct AnyEventStage { + inner: SystemStage, + _marker: PhantomData +} + +impl Default for AnyEventStage<(A, B, C)> + where + A: Clone + Send + Sync + 'static, + B: Clone + Send + Sync + 'static, + C: Clone + Send + Sync + 'static, +{ + fn default() -> Self { + Self::parallel() + } +} + +impl AnyEventStage<(A, B, C)> + where + A: Clone + Send + Sync + 'static, + B: Clone + Send + Sync + 'static, + C: Clone + Send + Sync + 'static, +{ + pub fn new(system_stage: SystemStage) -> Self { + let inner = system_stage + .with_run_criteria(any_event_stage_run_criteria::); + + Self { + inner, + _marker: PhantomData + } + } + + pub fn serial() -> Self { + Self::new(SystemStage::serial()) + } + + pub fn parallel() -> Self { + Self::new(SystemStage::parallel()) + } + + pub fn with_system(mut self, system: IntoS) -> Self + where + S: System, Option, Option), Output = ()>, + IntoS: IntoSystem, + { + self.inner.add_system_boxed(Box::new(any_event_system.chain(system))); + self + } + + pub fn add_system(&mut self, system: IntoS) -> &mut Self + where + S: System, Option, Option), Output = ()>, + IntoS: IntoSystem, + { + self.inner.add_system_boxed(Box::new(any_event_system.chain(system))); + self + } +} + +impl Stage for AnyEventStage<(A, B, C)> + where + A: Clone + Send + Sync + 'static, + B: Clone + Send + Sync + 'static, + C: Clone + Send + Sync + 'static, +{ + fn run(&mut self, world: &mut World, resources: &mut Resources) { + self.inner.run(world, resources) + } +} + +/// Execute systems if there exists an event to consume. +fn any_event_stage_run_criteria( + mut reader_a: Local>, + events_a: Res>, + mut reader_b: Local>, + events_b: Res>, + mut reader_c: Local>, + events_c: Res>, +) -> ShouldRun + where + A: Clone + Send + Sync + 'static, + B: Clone + Send + Sync + 'static, + C: Clone + Send + Sync + 'static, +{ + let a = reader_a.earliest(&events_a); + let b = reader_b.earliest(&events_b); + let c = reader_c.earliest(&events_c); + + if a.is_some() || b.is_some() || c.is_some() { + ShouldRun::YesAndLoop + } else { + ShouldRun::No + } +} + +fn any_event_system( + mut reader_a: Local>, + events_a: Res>, + mut reader_b: Local>, + events_b: Res>, + mut reader_c: Local>, + events_c: Res>, +) -> (Option, Option, Option) + where + A: Clone + Send + Sync + 'static, + B: Clone + Send + Sync + 'static, + C: Clone + Send + Sync + 'static, +{ + let a: Option = reader_a.earliest(&events_a).map(|e| e.clone()); + let b: Option = reader_b.earliest(&events_b).map(|e| e.clone()); + let c: Option = reader_c.earliest(&events_c).map(|e| e.clone()); + + (a, b, c) +} \ No newline at end of file diff --git a/crates/bevy_app/src/event_stage.rs b/crates/bevy_app/src/event_stage.rs new file mode 100644 index 0000000000000..44caaa012fef5 --- /dev/null +++ b/crates/bevy_app/src/event_stage.rs @@ -0,0 +1,90 @@ +use super::event::{EventReader, Events}; +use bevy_ecs::{Stage, World, Resources, System, IntoSystem, Local, Res, ShouldRun, SystemStage, IntoChainSystem}; +use std::marker::PhantomData; + + +pub struct EventStage { + inner: SystemStage, + _marker: PhantomData +} + +impl Default for EventStage + where + T: Clone + Send + Sync + 'static +{ + fn default() -> Self { + Self::parallel() + } +} + +impl EventStage + where + T: Clone + Send + Sync + 'static +{ + pub fn new(system_stage: SystemStage) -> Self { + let inner = system_stage + .with_run_criteria(event_stage_run_criteria::); + + Self { + inner, + _marker: PhantomData + } + } + + pub fn serial() -> Self { + Self::new(SystemStage::serial()) + } + + pub fn parallel() -> Self { + Self::new(SystemStage::parallel()) + } + + pub fn with_system(mut self, system: IntoS) -> Self + where + S: System, + IntoS: IntoSystem, + { + self.inner.add_system_boxed(Box::new(next_event_system.chain(system))); + self + } + + pub fn add_system(&mut self, system: IntoS) -> &mut Self + where + S: System, + IntoS: IntoSystem, + { + self.inner.add_system_boxed(Box::new(next_event_system.chain(system))); + self + } +} + +impl Stage for EventStage + where + T: Clone + Send + Sync + 'static +{ + fn run(&mut self, world: &mut World, resources: &mut Resources) { + self.inner.run(world, resources) + } +} + +/// Execute systems if there exists an event to consume. +fn event_stage_run_criteria( + mut reader: Local>, + events: Res> +) -> ShouldRun { + if reader.earliest(&events).is_some() { + ShouldRun::YesAndLoop + } else { + ShouldRun::No + } +} + +/// Fetch the next event and return it. This system is chained into all systems added to EventStage. +/// +/// Unwrap is okay here because this system will only be run if there exists an event to consume +fn next_event_system( + mut reader: Local>, + events: Res> +) -> T { + reader.earliest(&events).unwrap().clone() +} diff --git a/crates/bevy_app/src/lib.rs b/crates/bevy_app/src/lib.rs index 3d82bab5b09ba..8f2b45b27cece 100644 --- a/crates/bevy_app/src/lib.rs +++ b/crates/bevy_app/src/lib.rs @@ -6,14 +6,18 @@ pub mod startup_stage; mod app; mod app_builder; mod event; +mod event_stage; mod plugin; mod plugin_group; mod schedule_runner; +mod any_event_stage; pub use app::*; pub use app_builder::*; pub use bevy_derive::DynamicPlugin; pub use event::*; +pub use event_stage::*; +pub use any_event_stage::*; pub use plugin::*; pub use plugin_group::*; pub use schedule_runner::*; @@ -23,6 +27,7 @@ pub mod prelude { app::App, app_builder::AppBuilder, event::{EventReader, Events}, + event_stage::EventStage, stage, DynamicPlugin, Plugin, PluginGroup, }; } diff --git a/examples/ecs/any_event_stage.rs b/examples/ecs/any_event_stage.rs new file mode 100644 index 0000000000000..e97b192964cab --- /dev/null +++ b/examples/ecs/any_event_stage.rs @@ -0,0 +1,71 @@ +use bevy::prelude::*; +use bevy::app::AnyEventStage; + +/// This example creates a three new events & demonstrates a system that runs when any of those +/// events have fired. +fn main() { + App::build() + .add_plugins(DefaultPlugins) + .add_event::() + .add_event::() + .add_event::() + .init_resource::() + .add_system(event_trigger_system) + .add_stage_after( + stage::UPDATE, + "event_handlers", + AnyEventStage::<(EventA, EventB, EventC)>::default().with_system(event_listener_system) + ) + .run(); +} + +#[derive(Clone, Debug)] +struct EventA; + +#[derive(Clone, Debug)] +struct EventB; + +#[derive(Clone, Debug)] +struct EventC; + +struct EventTriggerState { + event_timer_a: Timer, + event_timer_b: Timer, + event_timer_c: Timer, +} + +impl Default for EventTriggerState { + fn default() -> Self { + EventTriggerState { + event_timer_a: Timer::from_seconds(1.0, true), + event_timer_b: Timer::from_seconds(0.5, true), + event_timer_c: Timer::from_seconds(0.8, true), + } + } +} + +// sends EventA every second, EventB every 0.5 seconds, and EventC every 0.8 seconds +fn event_trigger_system( + time: Res