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(),
);
}
}
}