avian3d/schedule/mod.rs
1//! Sets up the default scheduling, system set configuration, and time resources for physics.
2//!
3//! See [`PhysicsSchedulePlugin`].
4
5mod time;
6use dynamics::solver::schedule::SubstepCount;
7pub use time::*;
8
9use core::time::Duration;
10
11// For doc links
12#[allow(unused_imports)]
13use crate::prelude::*;
14
15use bevy::{
16 ecs::{
17 change_detection::Tick,
18 intern::Interned,
19 schedule::{LogLevel, ScheduleBuildSettings, ScheduleLabel, SingleThreadedExecutor},
20 system::SystemChangeTick,
21 },
22 prelude::*,
23 transform::TransformSystems,
24};
25
26/// Sets up the default scheduling, system set configuration, and time resources for physics.
27///
28/// # Schedules and Sets
29///
30/// This plugin initializes and configures the following schedules and system sets:
31///
32/// - [`PhysicsSystems`]: High-level system sets for the main phases of the physics engine.
33/// You can use these to schedule your own systems before or after physics is run without
34/// having to worry about implementation details.
35/// - [`PhysicsSchedule`]: Responsible for advancing the simulation in [`PhysicsSystems::StepSimulation`].
36/// - [`PhysicsStepSystems`]: System sets for the steps of the actual physics simulation loop.
37pub struct PhysicsSchedulePlugin {
38 schedule: Interned<dyn ScheduleLabel>,
39}
40
41impl PhysicsSchedulePlugin {
42 /// Creates a [`PhysicsSchedulePlugin`] using the given schedule for running the [`PhysicsSchedule`].
43 ///
44 /// The default schedule is `FixedPostUpdate`.
45 pub fn new(schedule: impl ScheduleLabel) -> Self {
46 Self {
47 schedule: schedule.intern(),
48 }
49 }
50}
51
52impl Default for PhysicsSchedulePlugin {
53 fn default() -> Self {
54 Self::new(FixedPostUpdate)
55 }
56}
57
58impl Plugin for PhysicsSchedulePlugin {
59 fn build(&self, app: &mut App) {
60 // Register types with generics.
61 app.register_type::<Time<Physics>>();
62
63 app.init_resource::<Time<Physics>>()
64 .insert_resource(Time::new_with(Substeps))
65 .init_resource::<SubstepCount>()
66 .init_resource::<LastPhysicsTick>();
67
68 // TODO: Where should this be initialized?
69 app.init_resource::<PhysicsLengthUnit>();
70
71 // Configure higher level system sets for the given schedule
72 let schedule = self.schedule;
73
74 app.configure_sets(
75 schedule,
76 (
77 PhysicsSystems::First,
78 PhysicsSystems::Prepare,
79 PhysicsSystems::StepSimulation,
80 PhysicsSystems::Writeback,
81 PhysicsSystems::Last,
82 )
83 .chain()
84 .before(TransformSystems::Propagate),
85 );
86
87 // Set up the physics schedule, the schedule that advances the physics simulation
88 app.edit_schedule(PhysicsSchedule, |schedule| {
89 schedule
90 .set_executor(SingleThreadedExecutor::new())
91 .set_build_settings(ScheduleBuildSettings {
92 ambiguity_detection: LogLevel::Error,
93 ..default()
94 });
95
96 schedule.configure_sets(
97 (
98 PhysicsStepSystems::First,
99 PhysicsStepSystems::BroadPhase,
100 PhysicsStepSystems::NarrowPhase,
101 PhysicsStepSystems::Solver,
102 PhysicsStepSystems::Sleeping,
103 PhysicsStepSystems::Finalize,
104 PhysicsStepSystems::Last,
105 )
106 .chain(),
107 );
108 });
109
110 app.add_systems(
111 schedule,
112 run_physics_schedule.in_set(PhysicsSystems::StepSimulation),
113 );
114
115 app.add_systems(
116 PhysicsSchedule,
117 update_last_physics_tick.after(PhysicsStepSystems::Last),
118 );
119
120 #[cfg(debug_assertions)]
121 app.add_systems(
122 schedule,
123 assert_components_finite.in_set(PhysicsSystems::First),
124 );
125 }
126}
127
128/// True if a system is running for the first time.
129struct IsFirstRun(bool);
130
131impl Default for IsFirstRun {
132 fn default() -> Self {
133 Self(true)
134 }
135}
136
137/// Responsible for advancing the physics simulation. This is run in [`PhysicsSystems::StepSimulation`].
138///
139/// See [`PhysicsStepSystems`] for the system sets that are run in this schedule.
140#[derive(Debug, Hash, PartialEq, Eq, Clone, ScheduleLabel)]
141pub struct PhysicsSchedule;
142
143/// High-level system sets for the main phases of the physics engine.
144/// You can use these to schedule your own systems before or after physics is run without
145/// having to worry about implementation details.
146///
147/// 1. `First`: Runs right before any of Avian's physics systems. Empty by default.
148/// 2. `Prepare`: Responsible for preparing data for the physics simulation, such as updating
149/// physics transforms or mass properties.
150/// 3. `StepSimulation`: Responsible for advancing the simulation by running the steps in [`PhysicsStepSystems`].
151/// 4. `Writeback`: Responsible for writing back the results of the physics simulation to other data,
152/// such as updating [`Transform`] based on the new [`Position`] and [`Rotation`].
153/// 5. `Last`: Runs right after all of Avian's physics systems. Empty by default.
154///
155/// # See Also
156///
157/// - [`PhysicsSchedule`]: Responsible for advancing the simulation in [`PhysicsSystems::StepSimulation`].
158/// - [`PhysicsStepSystems`]: System sets for the steps of the actual physics simulation loop, like
159/// the broad phase and the substepping loop.
160/// - [`SubstepSchedule`]: Responsible for running the substepping loop in [`PhysicsStepSystems::Solver`].
161#[derive(SystemSet, Clone, Copy, Debug, PartialEq, Eq, Hash)]
162pub enum PhysicsSystems {
163 /// Runs right before any of Avian's physics systems. Empty by default.
164 First,
165 /// Responsible for preparing data for the physics simulation, such as updating
166 /// physics transforms or mass properties.
167 Prepare,
168 /// Responsible for advancing the simulation by running the steps in [`PhysicsStepSystems`].
169 /// Systems in this set are run in the [`PhysicsSchedule`].
170 StepSimulation,
171 /// Responsible for writing back the results of the physics simulation to other data,
172 /// such as updating [`Transform`] based on the new [`Position`] and [`Rotation`].
173 Writeback,
174 /// Runs right after all of Avian's physics systems. Empty by default.
175 Last,
176}
177
178/// A deprecated alias for [`PhysicsSystems`].
179#[deprecated(since = "0.4.0", note = "Renamed to `PhysicsSystems`")]
180pub type PhysicsSet = PhysicsSystems;
181
182/// System sets for the main steps in the physics simulation loop. These are typically run in the [`PhysicsSchedule`].
183///
184/// 1. First
185/// 2. Broad phase
186/// 3. Narrow phase
187/// 4. Solver
188/// 5. Sleeping
189/// 6. Finalize
190/// 7. Last
191#[derive(SystemSet, Clone, Copy, Debug, PartialEq, Eq, Hash)]
192pub enum PhysicsStepSystems {
193 /// Runs at the start of the [`PhysicsSchedule`].
194 First,
195 /// Responsible for finding pairs of entities with overlapping [`ColliderAabb`]
196 /// and creating contact pairs for them in the [`ContactGraph`].
197 ///
198 /// See the [`broad_phase`](crate::collision::broad_phase) module.
199 BroadPhase,
200 /// Responsible for updating contacts in the [`ContactGraph`] and processing contact state changes.
201 ///
202 /// See [`NarrowPhasePlugin`].
203 NarrowPhase,
204 /// Responsible for running the solver and its substepping loop.
205 ///
206 /// See [`SolverPlugin`] and [`SubstepSchedule`].
207 Solver,
208 /// Responsible for controlling when bodies should be deactivated and marked as [`Sleeping`].
209 Sleeping,
210 /// Responsible for logic that runs after the core physics step and prepares for the next one.
211 Finalize,
212 /// Runs at the end of the [`PhysicsSchedule`].
213 Last,
214}
215
216/// A deprecated alias for [`PhysicsStepSystems`].
217#[deprecated(since = "0.4.0", note = "Renamed to `PhysicsStepSystems`")]
218pub type PhysicsStepSet = PhysicsStepSystems;
219
220/// A [`Tick`] corresponding to the end of the previous run of the [`PhysicsSchedule`].
221#[derive(Resource, Reflect, Default)]
222#[reflect(Resource, Default)]
223pub struct LastPhysicsTick(pub Tick);
224
225pub(crate) fn is_changed_after_tick<C: Component>(
226 component_ref: Ref<C>,
227 tick: Tick,
228 this_run: Tick,
229) -> bool {
230 let last_changed = component_ref.last_changed();
231 component_ref.is_changed() && last_changed.is_newer_than(tick, this_run)
232}
233
234/// Runs the [`PhysicsSchedule`].
235fn run_physics_schedule(world: &mut World, mut is_first_run: Local<IsFirstRun>) {
236 let _ = world.try_schedule_scope(PhysicsSchedule, |world, schedule| {
237 let is_paused = world.resource::<Time<Physics>>().is_paused();
238 let old_clock = world.resource::<Time>().as_generic();
239 let physics_clock = world.resource_mut::<Time<Physics>>();
240
241 // Get the scaled timestep delta time based on the timestep mode.
242 let timestep = old_clock
243 .delta()
244 .mul_f64(physics_clock.relative_speed_f64());
245
246 // Advance the physics clock by the timestep if not paused.
247 if !is_paused {
248 world.resource_mut::<Time<Physics>>().advance_by(timestep);
249
250 // Advance the substep clock already so that systems running
251 // before the substepping loop have the right delta.
252 let SubstepCount(substeps) = *world.resource::<SubstepCount>();
253 let sub_delta = timestep.div_f64(substeps as f64);
254 world.resource_mut::<Time<Substeps>>().advance_by(sub_delta);
255 }
256
257 // Set the generic `Time` resource to `Time<Physics>`.
258 *world.resource_mut::<Time>() = world.resource::<Time<Physics>>().as_generic();
259
260 // Advance the simulation.
261 if !world.resource::<Time>().delta().is_zero() {
262 trace!("running PhysicsSchedule");
263 schedule.run(world);
264 }
265
266 // If physics is paused, reset delta time to stop the simulation
267 // unless users manually advance `Time<Physics>`.
268 if is_paused {
269 world
270 .resource_mut::<Time<Physics>>()
271 .advance_by(Duration::ZERO);
272 }
273
274 // Set the generic `Time` resource back to the clock that was active before physics.
275 *world.resource_mut::<Time>() = old_clock;
276 });
277
278 is_first_run.0 = false;
279}
280
281fn update_last_physics_tick(
282 mut last_physics_tick: ResMut<LastPhysicsTick>,
283 system_change_tick: SystemChangeTick,
284) {
285 last_physics_tick.0 = system_change_tick.this_run();
286}
287
288/// Debug system that checks for NaNs and infinities in Avian components and
289/// reports the last location they were written to.
290#[cfg(debug_assertions)]
291fn assert_components_finite(
292 pos_query: Query<(Entity, Ref<Position>)>,
293 lin_vel_query: Query<(Entity, Ref<LinearVelocity>)>,
294 ang_vel_query: Query<(Entity, Ref<AngularVelocity>)>,
295) {
296 macro_rules! assert_finite {
297 ($ent:expr, $val:ident, $ty:ty) => {
298 debug_assert!(
299 $val.is_finite(),
300 "NaN or infinity found in Avian component: type={} entity={} location='{}' (enable feature \"bevy/track_location\" if location is empty)",
301 stringify!($ty),
302 $ent,
303 $val.changed_by()
304 );
305 };
306 }
307 for (entity, position) in pos_query {
308 assert_finite!(entity, position, Position);
309 }
310 for (entity, velocity) in lin_vel_query {
311 assert_finite!(entity, velocity, LinearVelocity);
312 }
313 for (entity, velocity) in ang_vel_query {
314 assert_finite!(entity, velocity, AngularVelocity);
315 }
316}