avian3d/diagnostics/
mod.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
//! Diagnostics support for tracking physics timers and counters. Useful for profiling and debugging.
//!
//! # Overview
//!
//! Each physics plugin such as [`NarrowPhasePlugin`] and [`SolverPlugin`] is responsible
//! for implementing its own diagnostics resource using the [`PhysicsDiagnostics`] trait
//! and registering it using [`AppDiagnosticsExt::register_physics_diagnostics`].
//!
//! If the `bevy_diagnostic` feature is enabled and the [`PhysicsDiagnosticsPlugin`] is added to the app,
//! these diagnostics will also be automatically written to the [`DiagnosticsStore`] resource.
//!
//! If the `diagnostic_ui` feature is enabled and the [`PhysicsDiagnosticsUiPlugin`] is added to the app,
//! a debug UI will also be available for displaying these diagnostics in real-time.
//!
//! [`NarrowPhasePlugin`]: crate::collision::narrow_phase::NarrowPhasePlugin
//! [`SolverPlugin`]: crate::dynamics::solver::SolverPlugin
//! [`DiagnosticsStore`]: bevy::diagnostic::DiagnosticsStore
//! [`PhysicsDiagnosticsUiPlugin`]: crate::diagnostics::ui::PhysicsDiagnosticsUiPlugin
//!
//! # Example
//!
//! ```no_run
#![cfg_attr(feature = "2d", doc = "use avian2d::prelude::*;")]
#![cfg_attr(feature = "3d", doc = "use avian3d::prelude::*;")]
//! use bevy::prelude::*;
//!
//! fn main() {
//!     App::new()
//!         .add_plugins((
//!             DefaultPlugins,
//!             PhysicsPlugins::default(),
//!             // Add the `PhysicsDiagnosticsPlugin` to write physics diagnostics
//!             // to the `DiagnosticsStore` resource in `bevy_diagnostic`.
//!             // Requires the `bevy_diagnostic` feature.
//! #           #[cfg(feature = "bevy_diagnostic")]
//!             PhysicsDiagnosticsPlugin,
//!             // Add the `PhysicsDiagnosticsUiPlugin` to display physics diagnostics
//!             // in a debug UI. Requires the `diagnostic_ui` feature.
//! #           #[cfg(feature = "diagnostic_ui")]
//!             PhysicsDiagnosticsUiPlugin,
//!         ))
//!         // ...your other plugins, systems and resources
//!         .run();
//! }
//! ```
//!
//! # Supported Diagnostics
//!
//! The following diagnostics are available but not added by default:
//!
//! - [`PhysicsTotalDiagnostics`]: Total physics timers and counters.
//! - [`PhysicsEntityDiagnostics`]: Physics entity counters.
//!
//! Additionally, physics plugins may implement their own diagnostics that *are* enabled by default.
//! These include:
//!
//! - [`CollisionDiagnostics`]: Diagnostics for collision detection.
//! - [`SolverDiagnostics`]: Diagnostics for the physics solver.
//! - [`SpatialQueryDiagnostics`]: Diagnostics for spatial queries.
//! - [`PhysicsPickingDiagnostics`]: Diagnostics for physics picking.
//!
//! [`CollisionDiagnostics`]: crate::collision::CollisionDiagnostics
//! [`SolverDiagnostics`]: crate::dynamics::solver::SolverDiagnostics
//! [`SpatialQueryDiagnostics`]: crate::spatial_query::SpatialQueryDiagnostics
//! [`PhysicsPickingDiagnostics`]: crate::picking::PhysicsPickingDiagnostics

mod entity_counters;
mod path_macro;
mod total;

#[cfg(feature = "diagnostic_ui")]
pub mod ui;
pub use entity_counters::PhysicsEntityDiagnostics;
pub(crate) use path_macro::impl_diagnostic_paths;
#[cfg(feature = "bevy_diagnostic")]
pub use total::{PhysicsTotalDiagnostics, PhysicsTotalDiagnosticsPlugin};

use crate::{schedule::PhysicsSchedule, PhysicsStepSet};
use bevy::{
    app::{App, Plugin},
    diagnostic::{Diagnostic, DiagnosticPath, Diagnostics, RegisterDiagnostic},
    prelude::{IntoScheduleConfigs, Res, ResMut, Resource, SystemSet},
};
use core::time::Duration;

/// A plugin that enables writing [physics diagnostics](crate::diagnostics)
/// to [`bevy::diagnostic::DiagnosticsStore`]. It is not enabled by default
/// and must be added manually.
///
/// To add a debug UI for physics diagnostics, enable the `diagnostic_ui` feature, and add the
/// [`PhysicsDiagnosticsUiPlugin`] to your app.
///
/// See the [module-level documentation](crate::diagnostics) for more information.
///
/// [`PhysicsDiagnosticsUiPlugin`]: crate::diagnostics::ui::PhysicsDiagnosticsUiPlugin
#[cfg(feature = "bevy_diagnostic")]
pub struct PhysicsDiagnosticsPlugin;

#[cfg(feature = "bevy_diagnostic")]
impl Plugin for PhysicsDiagnosticsPlugin {
    fn build(&self, app: &mut App) {
        // Configure system sets for physics diagnostics.
        app.configure_sets(
            PhysicsSchedule,
            (
                PhysicsDiagnosticsSystems::Reset.before(PhysicsStepSet::First),
                PhysicsDiagnosticsSystems::WriteDiagnostics.after(PhysicsStepSet::Last),
            ),
        );
    }
}

/// A system set for [physics diagnostics](crate::diagnostics).
#[derive(SystemSet, Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum PhysicsDiagnosticsSystems {
    /// Resets diagnostics to their default values.
    Reset,
    /// Writes physics diagnostics to other resources, commonly `DiagnosticsStore`.
    WriteDiagnostics,
}

/// A trait for resources storing timers and counters for [physics diagnostics](crate::diagnostics).
pub trait PhysicsDiagnostics: Default + Resource {
    /// Maps diagnostic paths to their respective duration fields.
    fn timer_paths(&self) -> Vec<(&'static DiagnosticPath, Duration)> {
        Vec::new()
    }

    /// Maps diagnostic paths to their respective counter fields.
    fn counter_paths(&self) -> Vec<(&'static DiagnosticPath, u32)> {
        Vec::new()
    }

    /// A system that resets the diagnostics to their default values.
    fn reset(mut physics_diagnostics: ResMut<Self>) {
        *physics_diagnostics = Self::default();
    }

    /// A system that writes diagnostics to the given [`Diagnostics`] instance.
    #[cfg(feature = "bevy_diagnostic")]
    fn write_diagnostics(physics_diagnostics: Res<Self>, mut diagnostics: Diagnostics) {
        for (path, duration) in physics_diagnostics.timer_paths() {
            diagnostics.add_measurement(path, || duration.as_secs_f64() * 1000.0);
        }

        for (path, count) in physics_diagnostics.counter_paths() {
            diagnostics.add_measurement(path, || count as f64);
        }
    }
}

/// An extension trait for registering [physics diagnostics](crate::diagnostics) in an [`App`].
pub trait AppDiagnosticsExt {
    /// Registers timer and counter diagnostics for a resource implementing [`PhysicsDiagnostics`].
    ///
    /// This method should be called in [`Plugin::finish`] to ensure that [`Diagnostic`]s
    /// are only tracked if the [`PhysicsDiagnosticsPlugin`] was added to the app.
    fn register_physics_diagnostics<T: PhysicsDiagnostics>(&mut self);
}

impl AppDiagnosticsExt for App {
    fn register_physics_diagnostics<T: PhysicsDiagnostics>(&mut self) {
        // Avoid duplicate registrations.
        if self.world().is_resource_added::<T>() {
            return;
        }

        // Initialize the diagnostics resource.
        self.init_resource::<T>();

        // Make sure the system set exists, even if `PhysicsDiagnosticsPlugin` is not added.
        self.configure_sets(
            PhysicsSchedule,
            PhysicsDiagnosticsSystems::Reset.before(PhysicsStepSet::First),
        );

        // Add a system to reset the resource, even if `PhysicsDiagnosticsPlugin` is not added.
        self.add_systems(
            PhysicsSchedule,
            T::reset
                .in_set(PhysicsDiagnosticsSystems::Reset)
                .ambiguous_with_all(),
        );

        #[cfg(feature = "bevy_diagnostic")]
        {
            // If physics diagnostics are not enabled, return early.
            if !self.is_plugin_added::<PhysicsDiagnosticsPlugin>() {
                return;
            }

            // Register diagnostics for the paths returned by the diagnostics resource.
            let diagnostics = T::default();
            let timer_paths = diagnostics.timer_paths();
            let counter_paths = diagnostics.counter_paths();

            for path in timer_paths.iter().map(|(path, _)| *path) {
                // All timers are in milliseconds.
                self.register_diagnostic(Diagnostic::new(path.clone()).with_suffix("ms"));
            }

            for path in counter_paths.iter().map(|(path, _)| *path) {
                // All counters are in whole numbers.
                self.register_diagnostic(Diagnostic::new(path.clone()).with_smoothing_factor(0.0));
            }

            // Add systems to reset the diagnostics and write them to the `DiagnosticsStore` resource.
            self.add_systems(
                PhysicsSchedule,
                T::write_diagnostics
                    .in_set(PhysicsDiagnosticsSystems::WriteDiagnostics)
                    .ambiguous_with_all(),
            );
        }
    }
}