diff --git a/crates/bevy_asset/src/diagnostic/asset_count_diagnostics_plugin.rs b/crates/bevy_asset/src/diagnostic/asset_count_diagnostics_plugin.rs index a5ce2546db..dafd215673 100644 --- a/crates/bevy_asset/src/diagnostic/asset_count_diagnostics_plugin.rs +++ b/crates/bevy_asset/src/diagnostic/asset_count_diagnostics_plugin.rs @@ -1,6 +1,8 @@ use crate::{Asset, Assets}; use bevy_app::prelude::*; -use bevy_diagnostic::{Diagnostic, DiagnosticId, Diagnostics, MAX_DIAGNOSTIC_NAME_WIDTH}; +use bevy_diagnostic::{ + Diagnostic, DiagnosticId, Diagnostics, DiagnosticsStore, MAX_DIAGNOSTIC_NAME_WIDTH, +}; use bevy_ecs::prelude::*; /// Adds an asset count diagnostic to an [`App`] for assets of type `T`. @@ -32,7 +34,7 @@ impl AssetCountDiagnosticsPlugin { } /// Registers the asset count diagnostic for the current application. - pub fn setup_system(mut diagnostics: ResMut) { + pub fn setup_system(mut diagnostics: ResMut) { let asset_type_name = std::any::type_name::(); let max_length = MAX_DIAGNOSTIC_NAME_WIDTH - "asset_count ".len(); diagnostics.add(Diagnostic::new( @@ -52,7 +54,7 @@ impl AssetCountDiagnosticsPlugin { } /// Updates the asset count of `T` assets. - pub fn diagnostic_system(mut diagnostics: ResMut, assets: Res>) { + pub fn diagnostic_system(mut diagnostics: Diagnostics, assets: Res>) { diagnostics.add_measurement(Self::diagnostic_id(), || assets.len() as f64); } } diff --git a/crates/bevy_diagnostic/src/diagnostic.rs b/crates/bevy_diagnostic/src/diagnostic.rs index aa2c868258..b7f7192dc1 100644 --- a/crates/bevy_diagnostic/src/diagnostic.rs +++ b/crates/bevy_diagnostic/src/diagnostic.rs @@ -1,4 +1,5 @@ -use bevy_ecs::system::Resource; +use bevy_app::App; +use bevy_ecs::system::{Deferred, Res, Resource, SystemBuffer, SystemParam}; use bevy_log::warn; use bevy_utils::{Duration, Instant, StableHashMap, Uuid}; use std::{borrow::Cow, collections::VecDeque}; @@ -44,16 +45,14 @@ pub struct Diagnostic { } impl Diagnostic { - /// Add a new value as a [`DiagnosticMeasurement`]. Its timestamp will be [`Instant::now`]. - pub fn add_measurement(&mut self, value: f64) { - let time = Instant::now(); - + /// Add a new value as a [`DiagnosticMeasurement`]. + pub fn add_measurement(&mut self, measurement: DiagnosticMeasurement) { if let Some(previous) = self.measurement() { - let delta = (time - previous.time).as_secs_f64(); + let delta = (measurement.time - previous.time).as_secs_f64(); let alpha = (delta / self.ema_smoothing_factor).clamp(0.0, 1.0); - self.ema += alpha * (value - self.ema); + self.ema += alpha * (measurement.value - self.ema); } else { - self.ema = value; + self.ema = measurement.value; } if self.max_history_length > 1 { @@ -63,14 +62,13 @@ impl Diagnostic { } } - self.sum += value; + self.sum += measurement.value; } else { self.history.clear(); - self.sum = value; + self.sum = measurement.value; } - self.history - .push_back(DiagnosticMeasurement { time, value }); + self.history.push_back(measurement); } /// Create a new diagnostic with the given ID, name and maximum history. @@ -199,14 +197,16 @@ impl Diagnostic { /// A collection of [Diagnostic]s #[derive(Debug, Default, Resource)] -pub struct Diagnostics { +pub struct DiagnosticsStore { // This uses a [`StableHashMap`] to ensure that the iteration order is deterministic between // runs when all diagnostics are inserted in the same order. diagnostics: StableHashMap, } -impl Diagnostics { +impl DiagnosticsStore { /// Add a new [`Diagnostic`]. + /// + /// If possible, prefer calling [`App::register_diagnostic`]. pub fn add(&mut self, diagnostic: Diagnostic) { self.diagnostics.insert(diagnostic.id, diagnostic); } @@ -227,6 +227,20 @@ impl Diagnostics { .and_then(|diagnostic| diagnostic.measurement()) } + /// Return an iterator over all [`Diagnostic`]. + pub fn iter(&self) -> impl Iterator { + self.diagnostics.values() + } +} + +/// Record new [`DiagnosticMeasurement`]'s. +#[derive(SystemParam)] +pub struct Diagnostics<'w, 's> { + store: Res<'w, DiagnosticsStore>, + queue: Deferred<'s, DiagnosticsBuffer>, +} + +impl<'w, 's> Diagnostics<'w, 's> { /// Add a measurement to an enabled [`Diagnostic`]. The measurement is passed as a function so that /// it will be evaluated only if the [`Diagnostic`] is enabled. This can be useful if the value is /// costly to calculate. @@ -234,17 +248,63 @@ impl Diagnostics { where F: FnOnce() -> f64, { - if let Some(diagnostic) = self - .diagnostics - .get_mut(&id) + if self + .store + .get(id) .filter(|diagnostic| diagnostic.is_enabled) + .is_some() { - diagnostic.add_measurement(value()); + let measurement = DiagnosticMeasurement { + time: Instant::now(), + value: value(), + }; + self.queue.0.insert(id, measurement); } } +} - /// Return an iterator over all [`Diagnostic`]. - pub fn iter(&self) -> impl Iterator { - self.diagnostics.values() +#[derive(Default)] +struct DiagnosticsBuffer(StableHashMap); + +impl SystemBuffer for DiagnosticsBuffer { + fn apply( + &mut self, + _system_meta: &bevy_ecs::system::SystemMeta, + world: &mut bevy_ecs::world::World, + ) { + let mut diagnostics = world.resource_mut::(); + for (id, measurement) in self.0.drain() { + if let Some(diagnostic) = diagnostics.get_mut(id) { + diagnostic.add_measurement(measurement); + } + } + } +} + +/// Extend [`App`] with new `register_diagnostic` function. +pub trait RegisterDiagnostic { + fn register_diagnostic(&mut self, diagnostic: Diagnostic) -> &mut Self; +} + +impl RegisterDiagnostic for App { + /// Register a new [`Diagnostic`] with an [`App`]. + /// + /// ```rust + /// use bevy_app::App; + /// use bevy_diagnostic::{Diagnostic, DiagnosticsPlugin, DiagnosticId, RegisterDiagnostic}; + /// + /// const UNIQUE_DIAG_ID: DiagnosticId = DiagnosticId::from_u128(42); + /// + /// App::new() + /// .add_plugin(DiagnosticsPlugin) + /// // Must only be called after the `DiagnosticsPlugin` has been added. + /// .register_diagnostic(Diagnostic::new(UNIQUE_DIAG_ID, "example", 10)) + /// .run(); + /// ``` + fn register_diagnostic(&mut self, diagnostic: Diagnostic) -> &mut Self { + let mut diagnostics = self.world.resource_mut::(); + diagnostics.add(diagnostic); + + self } } diff --git a/crates/bevy_diagnostic/src/entity_count_diagnostics_plugin.rs b/crates/bevy_diagnostic/src/entity_count_diagnostics_plugin.rs index 5c4415c09b..8999163aa9 100644 --- a/crates/bevy_diagnostic/src/entity_count_diagnostics_plugin.rs +++ b/crates/bevy_diagnostic/src/entity_count_diagnostics_plugin.rs @@ -1,7 +1,7 @@ use bevy_app::prelude::*; -use bevy_ecs::{entity::Entities, prelude::*}; +use bevy_ecs::entity::Entities; -use crate::{Diagnostic, DiagnosticId, Diagnostics}; +use crate::{Diagnostic, DiagnosticId, Diagnostics, RegisterDiagnostic}; /// Adds "entity count" diagnostic to an App #[derive(Default)] @@ -9,7 +9,7 @@ pub struct EntityCountDiagnosticsPlugin; impl Plugin for EntityCountDiagnosticsPlugin { fn build(&self, app: &mut App) { - app.add_systems(Startup, Self::setup_system) + app.register_diagnostic(Diagnostic::new(Self::ENTITY_COUNT, "entity_count", 20)) .add_systems(Update, Self::diagnostic_system); } } @@ -18,11 +18,7 @@ impl EntityCountDiagnosticsPlugin { pub const ENTITY_COUNT: DiagnosticId = DiagnosticId::from_u128(187513512115068938494459732780662867798); - pub fn setup_system(mut diagnostics: ResMut) { - diagnostics.add(Diagnostic::new(Self::ENTITY_COUNT, "entity_count", 20)); - } - - pub fn diagnostic_system(mut diagnostics: ResMut, entities: &Entities) { + pub fn diagnostic_system(mut diagnostics: Diagnostics, entities: &Entities) { diagnostics.add_measurement(Self::ENTITY_COUNT, || entities.len() as f64); } } diff --git a/crates/bevy_diagnostic/src/frame_time_diagnostics_plugin.rs b/crates/bevy_diagnostic/src/frame_time_diagnostics_plugin.rs index 4dbc3c4cfa..a17a6a19d4 100644 --- a/crates/bevy_diagnostic/src/frame_time_diagnostics_plugin.rs +++ b/crates/bevy_diagnostic/src/frame_time_diagnostics_plugin.rs @@ -1,4 +1,4 @@ -use crate::{Diagnostic, DiagnosticId, Diagnostics}; +use crate::{Diagnostic, DiagnosticId, Diagnostics, RegisterDiagnostic}; use bevy_app::prelude::*; use bevy_core::FrameCount; use bevy_ecs::prelude::*; @@ -10,8 +10,14 @@ pub struct FrameTimeDiagnosticsPlugin; impl Plugin for FrameTimeDiagnosticsPlugin { fn build(&self, app: &mut bevy_app::App) { - app.add_systems(Startup, Self::setup_system) - .add_systems(Update, Self::diagnostic_system); + app.register_diagnostic( + Diagnostic::new(Self::FRAME_TIME, "frame_time", 20).with_suffix("ms"), + ) + .register_diagnostic(Diagnostic::new(Self::FPS, "fps", 20)) + .register_diagnostic( + Diagnostic::new(Self::FRAME_COUNT, "frame_count", 1).with_smoothing_factor(0.0), + ) + .add_systems(Update, Self::diagnostic_system); } } @@ -22,15 +28,8 @@ impl FrameTimeDiagnosticsPlugin { pub const FRAME_TIME: DiagnosticId = DiagnosticId::from_u128(73441630925388532774622109383099159699); - pub fn setup_system(mut diagnostics: ResMut) { - diagnostics.add(Diagnostic::new(Self::FRAME_TIME, "frame_time", 20).with_suffix("ms")); - diagnostics.add(Diagnostic::new(Self::FPS, "fps", 20)); - diagnostics - .add(Diagnostic::new(Self::FRAME_COUNT, "frame_count", 1).with_smoothing_factor(0.0)); - } - pub fn diagnostic_system( - mut diagnostics: ResMut, + mut diagnostics: Diagnostics, time: Res