bevy/crates/bevy_app/src/main_schedule.rs
Carter Anderson 513be52505
AnimationEvent -> Event and other improvements (#16440)
# Objective

Needing to derive `AnimationEvent` for `Event` is unnecessary, and the
trigger logic coupled to it feels like we're coupling "event producer"
logic with the event itself, which feels wrong. It also comes with a
bunch of complexity, which is again unnecessary. We can have the
flexibility of "custom animation event trigger logic" without this
coupling and complexity.

The current `animation_events` example is also needlessly complicated,
due to it needing to work around system ordering issues. The docs
describing it are also slightly wrong. We can make this all a non-issue
by solving the underlying ordering problem.

Related to this, we use the `bevy_animation::Animation` system set to
solve PostUpdate animation order-of-operations issues. If we move this
to bevy_app as part of our "core schedule", we can cut out needless
`bevy_animation` crate dependencies in these instances.

## Solution

- Remove `AnimationEvent`, the derive, and all other infrastructure
associated with it (such as the `bevy_animation/derive` crate)
- Replace all instances of `AnimationEvent` traits with `Event + Clone`
- Store and use functions for custom animation trigger logic (ex:
`clip.add_event_fn()`). For "normal" cases users dont need to think
about this and should use the simpler `clip.add_event()`
- Run the `Animation` system set _before_ updating text
- Move `bevy_animation::Animation` to `bevy_app::Animation`. Remove
unnecessary `bevy_animation` dependency from `bevy_ui`
- Adjust `animation_events` example to use the simpler `clip.add_event`
API, as the workarounds are no longer necessary

This is polishing work that will land in 0.15, and I think it is simple
enough and valuable enough to land in 0.15 with it, in the interest of
making the feature as compelling as possible.
2024-11-22 00:16:04 +00:00

469 lines
19 KiB
Rust

use crate::{App, Plugin};
use bevy_ecs::{
schedule::{
ExecutorKind, InternedScheduleLabel, IntoSystemSetConfigs, Schedule, ScheduleLabel,
SystemSet,
},
system::{Local, Resource},
world::{Mut, World},
};
/// The schedule that contains the app logic that is evaluated each tick of [`App::update()`].
///
/// By default, it will run the following schedules in the given order:
///
/// On the first run of the schedule (and only on the first run), it will run:
/// * [`PreStartup`]
/// * [`Startup`]
/// * [`PostStartup`]
///
/// Then it will run:
/// * [`First`]
/// * [`PreUpdate`]
/// * [`StateTransition`]
/// * [`RunFixedMainLoop`]
/// * This will run [`FixedMain`] zero to many times, based on how much time has elapsed.
/// * [`Update`]
/// * [`PostUpdate`]
/// * [`Last`]
///
/// # Rendering
///
/// Note rendering is not executed in the main schedule by default.
/// Instead, rendering is performed in a separate [`SubApp`]
/// which exchanges data with the main app in between the main schedule runs.
///
/// See [`RenderPlugin`] and [`PipelinedRenderingPlugin`] for more details.
///
/// [`StateTransition`]: https://docs.rs/bevy/latest/bevy/prelude/struct.StateTransition.html
/// [`RenderPlugin`]: https://docs.rs/bevy/latest/bevy/render/struct.RenderPlugin.html
/// [`PipelinedRenderingPlugin`]: https://docs.rs/bevy/latest/bevy/render/pipelined_rendering/struct.PipelinedRenderingPlugin.html
/// [`SubApp`]: crate::SubApp
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
pub struct Main;
/// The schedule that runs before [`Startup`].
///
/// See the [`Main`] schedule for some details about how schedules are run.
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
pub struct PreStartup;
/// The schedule that runs once when the app starts.
///
/// See the [`Main`] schedule for some details about how schedules are run.
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
pub struct Startup;
/// The schedule that runs once after [`Startup`].
///
/// See the [`Main`] schedule for some details about how schedules are run.
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
pub struct PostStartup;
/// Runs first in the schedule.
///
/// See the [`Main`] schedule for some details about how schedules are run.
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
pub struct First;
/// The schedule that contains logic that must run before [`Update`]. For example, a system that reads raw keyboard
/// input OS events into an `Events` resource. This enables systems in [`Update`] to consume the events from the `Events`
/// resource without actually knowing about (or taking a direct scheduler dependency on) the "os-level keyboard event system".
///
/// [`PreUpdate`] exists to do "engine/plugin preparation work" that ensures the APIs consumed in [`Update`] are "ready".
/// [`PreUpdate`] abstracts out "pre work implementation details".
///
/// See the [`Main`] schedule for some details about how schedules are run.
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
pub struct PreUpdate;
/// Runs the [`FixedMain`] schedule in a loop according until all relevant elapsed time has been "consumed".
///
/// If you need to order your variable timestep systems
/// before or after the fixed update logic, use the [`RunFixedMainLoopSystem`] system set.
///
/// Note that in contrast to most other Bevy schedules, systems added directly to
/// [`RunFixedMainLoop`] will *not* be parallelized between each other.
///
/// See the [`Main`] schedule for some details about how schedules are run.
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
pub struct RunFixedMainLoop;
/// Runs first in the [`FixedMain`] schedule.
///
/// See the [`FixedMain`] schedule for details on how fixed updates work.
/// See the [`Main`] schedule for some details about how schedules are run.
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
pub struct FixedFirst;
/// The schedule that contains logic that must run before [`FixedUpdate`].
///
/// See the [`FixedMain`] schedule for details on how fixed updates work.
/// See the [`Main`] schedule for some details about how schedules are run.
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
pub struct FixedPreUpdate;
/// The schedule that contains most gameplay logic, which runs at a fixed rate rather than every render frame.
/// For logic that should run once per render frame, use the [`Update`] schedule instead.
///
/// Examples of systems that should run at a fixed rate include (but are not limited to):
/// - Physics
/// - AI
/// - Networking
/// - Game rules
///
/// See the [`Update`] schedule for examples of systems that *should not* use this schedule.
/// See the [`FixedMain`] schedule for details on how fixed updates work.
/// See the [`Main`] schedule for some details about how schedules are run.
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
pub struct FixedUpdate;
/// The schedule that runs after the [`FixedUpdate`] schedule, for reacting
/// to changes made in the main update logic.
///
/// See the [`FixedMain`] schedule for details on how fixed updates work.
/// See the [`Main`] schedule for some details about how schedules are run.
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
pub struct FixedPostUpdate;
/// The schedule that runs last in [`FixedMain`]
///
/// See the [`FixedMain`] schedule for details on how fixed updates work.
/// See the [`Main`] schedule for some details about how schedules are run.
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
pub struct FixedLast;
/// The schedule that contains systems which only run after a fixed period of time has elapsed.
///
/// This is run by the [`RunFixedMainLoop`] schedule. If you need to order your variable timestep systems
/// before or after the fixed update logic, use the [`RunFixedMainLoopSystem`] system set.
///
/// Frequency of execution is configured by inserting `Time<Fixed>` resource, 64 Hz by default.
/// See [this example](https://github.com/bevyengine/bevy/blob/latest/examples/time/time.rs).
///
/// See the [`Main`] schedule for some details about how schedules are run.
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
pub struct FixedMain;
/// The schedule that contains any app logic that must run once per render frame.
/// For most gameplay logic, consider using [`FixedUpdate`] instead.
///
/// Examples of systems that should run once per render frame include (but are not limited to):
/// - UI
/// - Input handling
/// - Audio control
///
/// See the [`FixedUpdate`] schedule for examples of systems that *should not* use this schedule.
/// See the [`Main`] schedule for some details about how schedules are run.
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
pub struct Update;
/// The schedule that contains scene spawning.
///
/// See the [`Main`] schedule for some details about how schedules are run.
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
pub struct SpawnScene;
/// The schedule that contains logic that must run after [`Update`]. For example, synchronizing "local transforms" in a hierarchy
/// to "global" absolute transforms. This enables the [`PostUpdate`] transform-sync system to react to "local transform" changes in
/// [`Update`] without the [`Update`] systems needing to know about (or add scheduler dependencies for) the "global transform sync system".
///
/// [`PostUpdate`] exists to do "engine/plugin response work" to things that happened in [`Update`].
/// [`PostUpdate`] abstracts out "implementation details" from users defining systems in [`Update`].
///
/// See the [`Main`] schedule for some details about how schedules are run.
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
pub struct PostUpdate;
/// Runs last in the schedule.
///
/// See the [`Main`] schedule for some details about how schedules are run.
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
pub struct Last;
/// Animation system set. This exists in [`PostUpdate`].
#[derive(SystemSet, Debug, Hash, PartialEq, Eq, Clone)]
pub struct Animation;
/// Defines the schedules to be run for the [`Main`] schedule, including
/// their order.
#[derive(Resource, Debug)]
pub struct MainScheduleOrder {
/// The labels to run for the main phase of the [`Main`] schedule (in the order they will be run).
pub labels: Vec<InternedScheduleLabel>,
/// The labels to run for the startup phase of the [`Main`] schedule (in the order they will be run).
pub startup_labels: Vec<InternedScheduleLabel>,
}
impl Default for MainScheduleOrder {
fn default() -> Self {
Self {
labels: vec![
First.intern(),
PreUpdate.intern(),
RunFixedMainLoop.intern(),
Update.intern(),
SpawnScene.intern(),
PostUpdate.intern(),
Last.intern(),
],
startup_labels: vec![PreStartup.intern(), Startup.intern(), PostStartup.intern()],
}
}
}
impl MainScheduleOrder {
/// Adds the given `schedule` after the `after` schedule in the main list of schedules.
pub fn insert_after(&mut self, after: impl ScheduleLabel, schedule: impl ScheduleLabel) {
let index = self
.labels
.iter()
.position(|current| (**current).eq(&after))
.unwrap_or_else(|| panic!("Expected {after:?} to exist"));
self.labels.insert(index + 1, schedule.intern());
}
/// Adds the given `schedule` before the `before` schedule in the main list of schedules.
pub fn insert_before(&mut self, before: impl ScheduleLabel, schedule: impl ScheduleLabel) {
let index = self
.labels
.iter()
.position(|current| (**current).eq(&before))
.unwrap_or_else(|| panic!("Expected {before:?} to exist"));
self.labels.insert(index, schedule.intern());
}
/// Adds the given `schedule` after the `after` schedule in the list of startup schedules.
pub fn insert_startup_after(
&mut self,
after: impl ScheduleLabel,
schedule: impl ScheduleLabel,
) {
let index = self
.startup_labels
.iter()
.position(|current| (**current).eq(&after))
.unwrap_or_else(|| panic!("Expected {after:?} to exist"));
self.startup_labels.insert(index + 1, schedule.intern());
}
/// Adds the given `schedule` before the `before` schedule in the list of startup schedules.
pub fn insert_startup_before(
&mut self,
before: impl ScheduleLabel,
schedule: impl ScheduleLabel,
) {
let index = self
.startup_labels
.iter()
.position(|current| (**current).eq(&before))
.unwrap_or_else(|| panic!("Expected {before:?} to exist"));
self.startup_labels.insert(index, schedule.intern());
}
}
impl Main {
/// A system that runs the "main schedule"
pub fn run_main(world: &mut World, mut run_at_least_once: Local<bool>) {
if !*run_at_least_once {
world.resource_scope(|world, order: Mut<MainScheduleOrder>| {
for &label in &order.startup_labels {
let _ = world.try_run_schedule(label);
}
});
*run_at_least_once = true;
}
world.resource_scope(|world, order: Mut<MainScheduleOrder>| {
for &label in &order.labels {
let _ = world.try_run_schedule(label);
}
});
}
}
/// Initializes the [`Main`] schedule, sub schedules, and resources for a given [`App`].
pub struct MainSchedulePlugin;
impl Plugin for MainSchedulePlugin {
fn build(&self, app: &mut App) {
// simple "facilitator" schedules benefit from simpler single threaded scheduling
let mut main_schedule = Schedule::new(Main);
main_schedule.set_executor_kind(ExecutorKind::SingleThreaded);
let mut fixed_main_schedule = Schedule::new(FixedMain);
fixed_main_schedule.set_executor_kind(ExecutorKind::SingleThreaded);
let mut fixed_main_loop_schedule = Schedule::new(RunFixedMainLoop);
fixed_main_loop_schedule.set_executor_kind(ExecutorKind::SingleThreaded);
app.add_schedule(main_schedule)
.add_schedule(fixed_main_schedule)
.add_schedule(fixed_main_loop_schedule)
.init_resource::<MainScheduleOrder>()
.init_resource::<FixedMainScheduleOrder>()
.add_systems(Main, Main::run_main)
.add_systems(FixedMain, FixedMain::run_fixed_main)
.configure_sets(
RunFixedMainLoop,
(
RunFixedMainLoopSystem::BeforeFixedMainLoop,
RunFixedMainLoopSystem::FixedMainLoop,
RunFixedMainLoopSystem::AfterFixedMainLoop,
)
.chain(),
);
#[cfg(feature = "bevy_debug_stepping")]
{
use bevy_ecs::schedule::{IntoSystemConfigs, Stepping};
app.add_systems(Main, Stepping::begin_frame.before(Main::run_main));
}
}
}
/// Defines the schedules to be run for the [`FixedMain`] schedule, including
/// their order.
#[derive(Resource, Debug)]
pub struct FixedMainScheduleOrder {
/// The labels to run for the [`FixedMain`] schedule (in the order they will be run).
pub labels: Vec<InternedScheduleLabel>,
}
impl Default for FixedMainScheduleOrder {
fn default() -> Self {
Self {
labels: vec![
FixedFirst.intern(),
FixedPreUpdate.intern(),
FixedUpdate.intern(),
FixedPostUpdate.intern(),
FixedLast.intern(),
],
}
}
}
impl FixedMainScheduleOrder {
/// Adds the given `schedule` after the `after` schedule
pub fn insert_after(&mut self, after: impl ScheduleLabel, schedule: impl ScheduleLabel) {
let index = self
.labels
.iter()
.position(|current| (**current).eq(&after))
.unwrap_or_else(|| panic!("Expected {after:?} to exist"));
self.labels.insert(index + 1, schedule.intern());
}
/// Adds the given `schedule` before the `before` schedule
pub fn insert_before(&mut self, before: impl ScheduleLabel, schedule: impl ScheduleLabel) {
let index = self
.labels
.iter()
.position(|current| (**current).eq(&before))
.unwrap_or_else(|| panic!("Expected {before:?} to exist"));
self.labels.insert(index, schedule.intern());
}
}
impl FixedMain {
/// A system that runs the fixed timestep's "main schedule"
pub fn run_fixed_main(world: &mut World) {
world.resource_scope(|world, order: Mut<FixedMainScheduleOrder>| {
for &label in &order.labels {
let _ = world.try_run_schedule(label);
}
});
}
}
/// Set enum for the systems that want to run inside [`RunFixedMainLoop`],
/// but before or after the fixed update logic. Systems in this set
/// will run exactly once per frame, regardless of the number of fixed updates.
/// They will also run under a variable timestep.
///
/// This is useful for handling things that need to run every frame, but
/// also need to be read by the fixed update logic. See the individual variants
/// for examples of what kind of systems should be placed in each.
///
/// Note that in contrast to most other Bevy schedules, systems added directly to
/// [`RunFixedMainLoop`] will *not* be parallelized between each other.
#[derive(Debug, Hash, PartialEq, Eq, Copy, Clone, SystemSet)]
pub enum RunFixedMainLoopSystem {
/// Runs before the fixed update logic.
///
/// A good example of a system that fits here
/// is camera movement, which needs to be updated in a variable timestep,
/// as you want the camera to move with as much precision and updates as
/// the frame rate allows. A physics system that needs to read the camera
/// position and orientation, however, should run in the fixed update logic,
/// as it needs to be deterministic and run at a fixed rate for better stability.
/// Note that we are not placing the camera movement system in `Update`, as that
/// would mean that the physics system already ran at that point.
///
/// # Example
/// ```
/// # use bevy_app::prelude::*;
/// # use bevy_ecs::prelude::*;
/// App::new()
/// .add_systems(
/// RunFixedMainLoop,
/// update_camera_rotation.in_set(RunFixedMainLoopSystem::BeforeFixedMainLoop))
/// .add_systems(FixedUpdate, update_physics);
///
/// # fn update_camera_rotation() {}
/// # fn update_physics() {}
/// ```
BeforeFixedMainLoop,
/// Contains the fixed update logic.
/// Runs [`FixedMain`] zero or more times based on delta of
/// [`Time<Virtual>`] and [`Time::overstep`].
///
/// Don't place systems here, use [`FixedUpdate`] and friends instead.
/// Use this system instead to order your systems to run specifically inbetween the fixed update logic and all
/// other systems that run in [`RunFixedMainLoopSystem::BeforeFixedMainLoop`] or [`RunFixedMainLoopSystem::AfterFixedMainLoop`].
///
/// [`Time<Virtual>`]: https://docs.rs/bevy/latest/bevy/prelude/struct.Virtual.html
/// [`Time::overstep`]: https://docs.rs/bevy/latest/bevy/time/struct.Time.html#method.overstep
/// # Example
/// ```
/// # use bevy_app::prelude::*;
/// # use bevy_ecs::prelude::*;
/// App::new()
/// .add_systems(FixedUpdate, update_physics)
/// .add_systems(
/// RunFixedMainLoop,
/// (
/// // This system will be called before all interpolation systems
/// // that third-party plugins might add.
/// prepare_for_interpolation
/// .after(RunFixedMainLoopSystem::FixedMainLoop)
/// .before(RunFixedMainLoopSystem::AfterFixedMainLoop),
/// )
/// );
///
/// # fn prepare_for_interpolation() {}
/// # fn update_physics() {}
/// ```
FixedMainLoop,
/// Runs after the fixed update logic.
///
/// A good example of a system that fits here
/// is a system that interpolates the transform of an entity between the last and current fixed update.
/// See the [fixed timestep example] for more details.
///
/// [fixed timestep example]: https://github.com/bevyengine/bevy/blob/main/examples/movement/physics_in_fixed_timestep.rs
///
/// # Example
/// ```
/// # use bevy_app::prelude::*;
/// # use bevy_ecs::prelude::*;
/// App::new()
/// .add_systems(FixedUpdate, update_physics)
/// .add_systems(
/// RunFixedMainLoop,
/// interpolate_transforms.in_set(RunFixedMainLoopSystem::AfterFixedMainLoop));
///
/// # fn interpolate_transforms() {}
/// # fn update_physics() {}
/// ```
AfterFixedMainLoop,
}