avian2d/diagnostics/
mod.rs

1//! Diagnostics support for tracking physics timers and counters. Useful for profiling and debugging.
2//!
3//! # Overview
4//!
5//! Each physics plugin such as [`NarrowPhasePlugin`] and [`SolverPlugin`] is responsible
6//! for implementing its own diagnostics resource using the [`PhysicsDiagnostics`] trait
7//! and registering it using [`AppDiagnosticsExt::register_physics_diagnostics`].
8//!
9//! If the `bevy_diagnostic` feature is enabled and the [`PhysicsDiagnosticsPlugin`] is added to the app,
10//! these diagnostics will also be automatically written to the [`DiagnosticsStore`] resource.
11//!
12//! If the `diagnostic_ui` feature is enabled and the [`PhysicsDiagnosticsUiPlugin`] is added to the app,
13//! a debug UI will also be available for displaying these diagnostics in real-time.
14//!
15//! [`NarrowPhasePlugin`]: crate::collision::narrow_phase::NarrowPhasePlugin
16//! [`SolverPlugin`]: crate::dynamics::solver::SolverPlugin
17//! [`DiagnosticsStore`]: bevy::diagnostic::DiagnosticsStore
18//! [`PhysicsDiagnosticsUiPlugin`]: crate::diagnostics::ui::PhysicsDiagnosticsUiPlugin
19//!
20//! # Example
21//!
22//! ```no_run
23#![cfg_attr(feature = "2d", doc = "use avian2d::prelude::*;")]
24#![cfg_attr(feature = "3d", doc = "use avian3d::prelude::*;")]
25//! use bevy::prelude::*;
26//!
27//! fn main() {
28//!     App::new()
29//!         .add_plugins((
30//!             DefaultPlugins,
31//!             PhysicsPlugins::default(),
32//!             // Add the `PhysicsDiagnosticsPlugin` to write physics diagnostics
33//!             // to the `DiagnosticsStore` resource in `bevy_diagnostic`.
34//!             // Requires the `bevy_diagnostic` feature.
35//! #           #[cfg(feature = "bevy_diagnostic")]
36//!             PhysicsDiagnosticsPlugin,
37//!             // Add the `PhysicsDiagnosticsUiPlugin` to display physics diagnostics
38//!             // in a debug UI. Requires the `diagnostic_ui` feature.
39//! #           #[cfg(feature = "diagnostic_ui")]
40//!             PhysicsDiagnosticsUiPlugin,
41//!         ))
42//!         // ...your other plugins, systems and resources
43//!         .run();
44//! }
45//! ```
46//!
47//! # Supported Diagnostics
48//!
49//! The following diagnostics are available but not added by default:
50//!
51//! - [`PhysicsTotalDiagnostics`]: Total physics timers and counters.
52//! - [`PhysicsEntityDiagnostics`]: Physics entity counters.
53//!
54//! Additionally, physics plugins may implement their own diagnostics that *are* enabled by default.
55//! These include:
56//!
57//! - [`CollisionDiagnostics`]: Diagnostics for collision detection.
58//! - [`SolverDiagnostics`]: Diagnostics for the physics solver.
59//! - [`SpatialQueryDiagnostics`]: Diagnostics for spatial queries.
60//! - [`PhysicsPickingDiagnostics`]: Diagnostics for physics picking.
61//!
62//! [`CollisionDiagnostics`]: crate::collision::CollisionDiagnostics
63//! [`SolverDiagnostics`]: crate::dynamics::solver::SolverDiagnostics
64//! [`SpatialQueryDiagnostics`]: crate::spatial_query::SpatialQueryDiagnostics
65//! [`PhysicsPickingDiagnostics`]: crate::picking::PhysicsPickingDiagnostics
66
67#[cfg(feature = "bevy_diagnostic")]
68mod entity_counters;
69mod path_macro;
70#[cfg(feature = "bevy_diagnostic")]
71mod total;
72
73#[cfg(feature = "diagnostic_ui")]
74pub mod ui;
75#[cfg(feature = "bevy_diagnostic")]
76pub use entity_counters::{PhysicsEntityDiagnostics, PhysicsEntityDiagnosticsPlugin};
77pub(crate) use path_macro::impl_diagnostic_paths;
78#[cfg(feature = "bevy_diagnostic")]
79pub use total::{PhysicsTotalDiagnostics, PhysicsTotalDiagnosticsPlugin};
80
81use crate::{PhysicsStepSystems, schedule::PhysicsSchedule};
82use bevy::{
83    diagnostic::DiagnosticPath,
84    prelude::{App, IntoScheduleConfigs, ResMut, Resource, SystemSet},
85};
86#[cfg(feature = "bevy_diagnostic")]
87use bevy::{
88    diagnostic::{Diagnostic, Diagnostics, RegisterDiagnostic},
89    prelude::{Plugin, Res},
90};
91use core::time::Duration;
92
93/// A plugin that enables writing [physics diagnostics](crate::diagnostics)
94/// to [`bevy::diagnostic::DiagnosticsStore`]. It is not enabled by default
95/// and must be added manually.
96///
97/// To add a debug UI for physics diagnostics, enable the `diagnostic_ui` feature, and add the
98/// [`PhysicsDiagnosticsUiPlugin`] to your app.
99///
100/// See the [module-level documentation](crate::diagnostics) for more information.
101///
102/// [`PhysicsDiagnosticsUiPlugin`]: crate::diagnostics::ui::PhysicsDiagnosticsUiPlugin
103#[cfg(feature = "bevy_diagnostic")]
104pub struct PhysicsDiagnosticsPlugin;
105
106#[cfg(feature = "bevy_diagnostic")]
107impl Plugin for PhysicsDiagnosticsPlugin {
108    fn build(&self, app: &mut App) {
109        // Configure system sets for physics diagnostics.
110        app.configure_sets(
111            PhysicsSchedule,
112            (
113                PhysicsDiagnosticsSystems::Reset.before(PhysicsStepSystems::First),
114                PhysicsDiagnosticsSystems::WriteDiagnostics.after(PhysicsStepSystems::Last),
115            ),
116        );
117    }
118}
119
120/// A system set for [physics diagnostics](crate::diagnostics).
121#[derive(SystemSet, Clone, Copy, Debug, PartialEq, Eq, Hash)]
122pub enum PhysicsDiagnosticsSystems {
123    /// Resets diagnostics to their default values.
124    Reset,
125    /// Writes physics diagnostics to other resources, commonly `DiagnosticsStore`.
126    WriteDiagnostics,
127}
128
129/// A trait for resources storing timers and counters for [physics diagnostics](crate::diagnostics).
130pub trait PhysicsDiagnostics: Default + Resource {
131    /// Maps diagnostic paths to their respective duration fields.
132    fn timer_paths(&self) -> Vec<(&'static DiagnosticPath, Duration)> {
133        Vec::new()
134    }
135
136    /// Maps diagnostic paths to their respective counter fields.
137    fn counter_paths(&self) -> Vec<(&'static DiagnosticPath, u32)> {
138        Vec::new()
139    }
140
141    /// A system that resets the diagnostics to their default values.
142    fn reset(mut physics_diagnostics: ResMut<Self>) {
143        *physics_diagnostics = Self::default();
144    }
145
146    /// A system that writes diagnostics to the given [`Diagnostics`] instance.
147    #[cfg(feature = "bevy_diagnostic")]
148    fn write_diagnostics(physics_diagnostics: Res<Self>, mut diagnostics: Diagnostics) {
149        for (path, duration) in physics_diagnostics.timer_paths() {
150            diagnostics.add_measurement(path, || duration.as_secs_f64() * 1000.0);
151        }
152
153        for (path, count) in physics_diagnostics.counter_paths() {
154            diagnostics.add_measurement(path, || count as f64);
155        }
156    }
157}
158
159/// An extension trait for registering [physics diagnostics](crate::diagnostics) in an [`App`].
160pub trait AppDiagnosticsExt {
161    /// Registers timer and counter diagnostics for a resource implementing [`PhysicsDiagnostics`].
162    ///
163    /// This method should be called in [`Plugin::finish`] to ensure that [`Diagnostic`]s
164    /// are only tracked if the [`PhysicsDiagnosticsPlugin`] was added to the app.
165    fn register_physics_diagnostics<T: PhysicsDiagnostics>(&mut self);
166}
167
168impl AppDiagnosticsExt for App {
169    fn register_physics_diagnostics<T: PhysicsDiagnostics>(&mut self) {
170        // Avoid duplicate registrations.
171        if self.world().is_resource_added::<T>() {
172            return;
173        }
174
175        // Initialize the diagnostics resource.
176        self.init_resource::<T>();
177
178        // Make sure the system set exists, even if `PhysicsDiagnosticsPlugin` is not added.
179        self.configure_sets(
180            PhysicsSchedule,
181            PhysicsDiagnosticsSystems::Reset.before(PhysicsStepSystems::First),
182        );
183
184        // Add a system to reset the resource, even if `PhysicsDiagnosticsPlugin` is not added.
185        self.add_systems(
186            PhysicsSchedule,
187            T::reset
188                .in_set(PhysicsDiagnosticsSystems::Reset)
189                .ambiguous_with_all(),
190        );
191
192        #[cfg(feature = "bevy_diagnostic")]
193        {
194            // If physics diagnostics are not enabled, return early.
195            if !self.is_plugin_added::<PhysicsDiagnosticsPlugin>() {
196                return;
197            }
198
199            // Register diagnostics for the paths returned by the diagnostics resource.
200            let diagnostics = T::default();
201            let timer_paths = diagnostics.timer_paths();
202            let counter_paths = diagnostics.counter_paths();
203
204            for path in timer_paths.iter().map(|(path, _)| *path) {
205                // All timers are in milliseconds.
206                self.register_diagnostic(Diagnostic::new(path.clone()).with_suffix("ms"));
207            }
208
209            for path in counter_paths.iter().map(|(path, _)| *path) {
210                // All counters are in whole numbers.
211                self.register_diagnostic(Diagnostic::new(path.clone()).with_smoothing_factor(0.0));
212            }
213
214            // Add systems to reset the diagnostics and write them to the `DiagnosticsStore` resource.
215            self.add_systems(
216                PhysicsSchedule,
217                T::write_diagnostics
218                    .in_set(PhysicsDiagnosticsSystems::WriteDiagnostics)
219                    .ambiguous_with_all(),
220            );
221        }
222    }
223}