Replace DiagnosticId by DiagnosticPath (#9266)

# Objective

Implements #9216 

## Solution

- Replace `DiagnosticId` by `DiagnosticPath`. It's pre-hashed using
`const-fnv1a-hash` crate, so it's possible to create path in const
contexts.

---

## Changelog

- Replaced `DiagnosticId` by `DiagnosticPath`
- Set default history length to 120 measurements (2 seconds on 60 fps).

I've noticed hardcoded constant 20 everywhere and decided to change it
to `DEFAULT_MAX_HISTORY_LENGTH` , which is set to new diagnostics by
default. To override it, use `with_max_history_length`.


## Migration Guide

```diff
- const UNIQUE_DIAG_ID: DiagnosticId = DiagnosticId::from_u128(42);
+ const UNIQUE_DIAG_PATH: DiagnosticPath = DiagnosticPath::const_new("foo/bar");

- Diagnostic::new(UNIQUE_DIAG_ID, "example", 10)
+ Diagnostic::new(UNIQUE_DIAG_PATH).with_max_history_length(10)

- diagnostics.add_measurement(UNIQUE_DIAG_ID, || 42);
+ diagnostics.add_measurement(&UNIQUE_DIAG_ID, || 42);
```
This commit is contained in:
LeshaInc 2024-01-20 18:42:51 +03:00 committed by GitHub
parent 04aedf12fa
commit 320ac65a9e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 242 additions and 173 deletions

View file

@ -21,6 +21,8 @@ bevy_log = { path = "../bevy_log", version = "0.12.0" }
bevy_time = { path = "../bevy_time", version = "0.12.0" } bevy_time = { path = "../bevy_time", version = "0.12.0" }
bevy_utils = { path = "../bevy_utils", version = "0.12.0" } bevy_utils = { path = "../bevy_utils", version = "0.12.0" }
const-fnv1a-hash = "1.1.0"
# MacOS # MacOS
[target.'cfg(all(target_os="macos"))'.dependencies] [target.'cfg(all(target_os="macos"))'.dependencies]
# Some features of sysinfo are not supported by apple. This will disable those features on apple devices # Some features of sysinfo are not supported by apple. This will disable those features on apple devices

View file

@ -1,24 +1,108 @@
use bevy_app::App; use std::hash::{Hash, Hasher};
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}; use std::{borrow::Cow, collections::VecDeque};
use crate::MAX_DIAGNOSTIC_NAME_WIDTH; use bevy_app::App;
use bevy_ecs::system::{Deferred, Res, Resource, SystemBuffer, SystemParam};
use bevy_utils::{hashbrown::HashMap, Duration, Instant, PassHash};
use const_fnv1a_hash::fnv1a_hash_str_64;
/// Unique identifier for a [`Diagnostic`]. use crate::DEFAULT_MAX_HISTORY_LENGTH;
#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, PartialOrd, Ord)]
pub struct DiagnosticId(pub Uuid);
impl DiagnosticId { /// Unique diagnostic path, separated by `/`.
pub const fn from_u128(value: u128) -> Self { ///
DiagnosticId(Uuid::from_u128(value)) /// Requirements:
/// - Can't be empty
/// - Can't have leading or trailing `/`
/// - Can't have empty components.
#[derive(Debug, Clone)]
pub struct DiagnosticPath {
path: Cow<'static, str>,
hash: u64,
}
impl DiagnosticPath {
/// Create a new `DiagnosticPath`. Usable in const contexts.
///
/// **Note**: path is not validated, so make sure it follows all the requirements.
pub const fn const_new(path: &'static str) -> DiagnosticPath {
DiagnosticPath {
path: Cow::Borrowed(path),
hash: fnv1a_hash_str_64(path),
}
}
/// Create a new `DiagnosticPath` from the specified string.
pub fn new(path: impl Into<Cow<'static, str>>) -> DiagnosticPath {
let path = path.into();
debug_assert!(!path.is_empty(), "diagnostic path can't be empty");
debug_assert!(
!path.starts_with('/'),
"diagnostic path can't be start with `/`"
);
debug_assert!(
!path.ends_with('/'),
"diagnostic path can't be end with `/`"
);
debug_assert!(
!path.contains("//"),
"diagnostic path can't contain empty components"
);
DiagnosticPath {
hash: fnv1a_hash_str_64(&path),
path,
}
}
/// Create a new `DiagnosticPath` from an iterator over components.
pub fn from_components<'a>(components: impl IntoIterator<Item = &'a str>) -> DiagnosticPath {
let mut buf = String::new();
for (i, component) in components.into_iter().enumerate() {
if i > 0 {
buf.push('/');
}
buf.push_str(component);
}
DiagnosticPath::new(buf)
}
/// Returns full path, joined by `/`
pub fn as_str(&self) -> &str {
&self.path
}
/// Returns an iterator over path components.
pub fn components(&self) -> impl Iterator<Item = &str> + '_ {
self.path.split('/')
} }
} }
impl Default for DiagnosticId { impl From<DiagnosticPath> for String {
fn default() -> Self { fn from(path: DiagnosticPath) -> Self {
DiagnosticId(Uuid::new_v4()) path.path.into()
}
}
impl Eq for DiagnosticPath {}
impl PartialEq for DiagnosticPath {
fn eq(&self, other: &Self) -> bool {
self.hash == other.hash && self.path == other.path
}
}
impl Hash for DiagnosticPath {
fn hash<H: Hasher>(&self, state: &mut H) {
state.write_u64(self.hash);
}
}
impl std::fmt::Display for DiagnosticPath {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.path.fmt(f)
} }
} }
@ -33,8 +117,7 @@ pub struct DiagnosticMeasurement {
/// Diagnostic examples: frames per second, CPU usage, network latency /// Diagnostic examples: frames per second, CPU usage, network latency
#[derive(Debug)] #[derive(Debug)]
pub struct Diagnostic { pub struct Diagnostic {
pub id: DiagnosticId, path: DiagnosticPath,
pub name: Cow<'static, str>,
pub suffix: Cow<'static, str>, pub suffix: Cow<'static, str>,
history: VecDeque<DiagnosticMeasurement>, history: VecDeque<DiagnosticMeasurement>,
sum: f64, sum: f64,
@ -71,27 +154,13 @@ impl Diagnostic {
self.history.push_back(measurement); self.history.push_back(measurement);
} }
/// Create a new diagnostic with the given ID, name and maximum history. /// Create a new diagnostic with the given path.
pub fn new( pub fn new(path: DiagnosticPath) -> Diagnostic {
id: DiagnosticId,
name: impl Into<Cow<'static, str>>,
max_history_length: usize,
) -> Diagnostic {
let name = name.into();
if name.chars().count() > MAX_DIAGNOSTIC_NAME_WIDTH {
// This could be a false positive due to a unicode width being shorter
warn!(
"Diagnostic {:?} has name longer than {} characters, and so might overflow in the LogDiagnosticsPlugin\
Consider using a shorter name.",
name, MAX_DIAGNOSTIC_NAME_WIDTH
);
}
Diagnostic { Diagnostic {
id, path,
name,
suffix: Cow::Borrowed(""), suffix: Cow::Borrowed(""),
history: VecDeque::with_capacity(max_history_length), history: VecDeque::with_capacity(DEFAULT_MAX_HISTORY_LENGTH),
max_history_length, max_history_length: DEFAULT_MAX_HISTORY_LENGTH,
sum: 0.0, sum: 0.0,
ema: 0.0, ema: 0.0,
ema_smoothing_factor: 2.0 / 21.0, ema_smoothing_factor: 2.0 / 21.0,
@ -99,6 +168,15 @@ impl Diagnostic {
} }
} }
/// Set the maximum history length.
#[must_use]
pub fn with_max_history_length(mut self, max_history_length: usize) -> Self {
self.max_history_length = max_history_length;
self.history.reserve(self.max_history_length);
self.history.shrink_to(self.max_history_length);
self
}
/// Add a suffix to use when logging the value, can be used to show a unit. /// Add a suffix to use when logging the value, can be used to show a unit.
#[must_use] #[must_use]
pub fn with_suffix(mut self, suffix: impl Into<Cow<'static, str>>) -> Self { pub fn with_suffix(mut self, suffix: impl Into<Cow<'static, str>>) -> Self {
@ -122,6 +200,10 @@ impl Diagnostic {
self self
} }
pub fn path(&self) -> &DiagnosticPath {
&self.path
}
/// Get the latest measurement from this diagnostic. /// Get the latest measurement from this diagnostic.
#[inline] #[inline]
pub fn measurement(&self) -> Option<&DiagnosticMeasurement> { pub fn measurement(&self) -> Option<&DiagnosticMeasurement> {
@ -198,9 +280,7 @@ impl Diagnostic {
/// A collection of [`Diagnostic`]s. /// A collection of [`Diagnostic`]s.
#[derive(Debug, Default, Resource)] #[derive(Debug, Default, Resource)]
pub struct DiagnosticsStore { pub struct DiagnosticsStore {
// This uses a [`StableHashMap`] to ensure that the iteration order is deterministic between diagnostics: HashMap<DiagnosticPath, Diagnostic, PassHash>,
// runs when all diagnostics are inserted in the same order.
diagnostics: StableHashMap<DiagnosticId, Diagnostic>,
} }
impl DiagnosticsStore { impl DiagnosticsStore {
@ -208,21 +288,21 @@ impl DiagnosticsStore {
/// ///
/// If possible, prefer calling [`App::register_diagnostic`]. /// If possible, prefer calling [`App::register_diagnostic`].
pub fn add(&mut self, diagnostic: Diagnostic) { pub fn add(&mut self, diagnostic: Diagnostic) {
self.diagnostics.insert(diagnostic.id, diagnostic); self.diagnostics.insert(diagnostic.path.clone(), diagnostic);
} }
pub fn get(&self, id: DiagnosticId) -> Option<&Diagnostic> { pub fn get(&self, path: &DiagnosticPath) -> Option<&Diagnostic> {
self.diagnostics.get(&id) self.diagnostics.get(path)
} }
pub fn get_mut(&mut self, id: DiagnosticId) -> Option<&mut Diagnostic> { pub fn get_mut(&mut self, path: &DiagnosticPath) -> Option<&mut Diagnostic> {
self.diagnostics.get_mut(&id) self.diagnostics.get_mut(path)
} }
/// Get the latest [`DiagnosticMeasurement`] from an enabled [`Diagnostic`]. /// Get the latest [`DiagnosticMeasurement`] from an enabled [`Diagnostic`].
pub fn get_measurement(&self, id: DiagnosticId) -> Option<&DiagnosticMeasurement> { pub fn get_measurement(&self, path: &DiagnosticPath) -> Option<&DiagnosticMeasurement> {
self.diagnostics self.diagnostics
.get(&id) .get(path)
.filter(|diagnostic| diagnostic.is_enabled) .filter(|diagnostic| diagnostic.is_enabled)
.and_then(|diagnostic| diagnostic.measurement()) .and_then(|diagnostic| diagnostic.measurement())
} }
@ -249,13 +329,13 @@ impl<'w, 's> Diagnostics<'w, 's> {
/// Add a measurement to an enabled [`Diagnostic`]. The measurement is passed as a function so that /// 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 /// it will be evaluated only if the [`Diagnostic`] is enabled. This can be useful if the value is
/// costly to calculate. /// costly to calculate.
pub fn add_measurement<F>(&mut self, id: DiagnosticId, value: F) pub fn add_measurement<F>(&mut self, path: &DiagnosticPath, value: F)
where where
F: FnOnce() -> f64, F: FnOnce() -> f64,
{ {
if self if self
.store .store
.get(id) .get(path)
.filter(|diagnostic| diagnostic.is_enabled) .filter(|diagnostic| diagnostic.is_enabled)
.is_some() .is_some()
{ {
@ -263,13 +343,13 @@ impl<'w, 's> Diagnostics<'w, 's> {
time: Instant::now(), time: Instant::now(),
value: value(), value: value(),
}; };
self.queue.0.insert(id, measurement); self.queue.0.insert(path.clone(), measurement);
} }
} }
} }
#[derive(Default)] #[derive(Default)]
struct DiagnosticsBuffer(StableHashMap<DiagnosticId, DiagnosticMeasurement>); struct DiagnosticsBuffer(HashMap<DiagnosticPath, DiagnosticMeasurement, PassHash>);
impl SystemBuffer for DiagnosticsBuffer { impl SystemBuffer for DiagnosticsBuffer {
fn apply( fn apply(
@ -278,8 +358,8 @@ impl SystemBuffer for DiagnosticsBuffer {
world: &mut bevy_ecs::world::World, world: &mut bevy_ecs::world::World,
) { ) {
let mut diagnostics = world.resource_mut::<DiagnosticsStore>(); let mut diagnostics = world.resource_mut::<DiagnosticsStore>();
for (id, measurement) in self.0.drain() { for (path, measurement) in self.0.drain() {
if let Some(diagnostic) = diagnostics.get_mut(id) { if let Some(diagnostic) = diagnostics.get_mut(&path) {
diagnostic.add_measurement(measurement); diagnostic.add_measurement(measurement);
} }
} }
@ -298,12 +378,12 @@ impl RegisterDiagnostic for App {
/// ///
/// ``` /// ```
/// use bevy_app::App; /// use bevy_app::App;
/// use bevy_diagnostic::{Diagnostic, DiagnosticsPlugin, DiagnosticId, RegisterDiagnostic}; /// use bevy_diagnostic::{Diagnostic, DiagnosticsPlugin, DiagnosticPath, RegisterDiagnostic};
/// ///
/// const UNIQUE_DIAG_ID: DiagnosticId = DiagnosticId::from_u128(42); /// const UNIQUE_DIAG_PATH: DiagnosticPath = DiagnosticPath::const_new("foo/bar");
/// ///
/// App::new() /// App::new()
/// .register_diagnostic(Diagnostic::new(UNIQUE_DIAG_ID, "example", 10)) /// .register_diagnostic(Diagnostic::new(UNIQUE_DIAG_PATH))
/// .add_plugins(DiagnosticsPlugin) /// .add_plugins(DiagnosticsPlugin)
/// .run(); /// .run();
/// ``` /// ```

View file

@ -1,7 +1,7 @@
use bevy_app::prelude::*; use bevy_app::prelude::*;
use bevy_ecs::entity::Entities; use bevy_ecs::entity::Entities;
use crate::{Diagnostic, DiagnosticId, Diagnostics, RegisterDiagnostic}; use crate::{Diagnostic, DiagnosticPath, Diagnostics, RegisterDiagnostic};
/// Adds "entity count" diagnostic to an App. /// Adds "entity count" diagnostic to an App.
/// ///
@ -13,16 +13,15 @@ pub struct EntityCountDiagnosticsPlugin;
impl Plugin for EntityCountDiagnosticsPlugin { impl Plugin for EntityCountDiagnosticsPlugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
app.register_diagnostic(Diagnostic::new(Self::ENTITY_COUNT, "entity_count", 20)) app.register_diagnostic(Diagnostic::new(Self::ENTITY_COUNT))
.add_systems(Update, Self::diagnostic_system); .add_systems(Update, Self::diagnostic_system);
} }
} }
impl EntityCountDiagnosticsPlugin { impl EntityCountDiagnosticsPlugin {
pub const ENTITY_COUNT: DiagnosticId = pub const ENTITY_COUNT: DiagnosticPath = DiagnosticPath::const_new("entity_count");
DiagnosticId::from_u128(187513512115068938494459732780662867798);
pub fn diagnostic_system(mut diagnostics: Diagnostics, entities: &Entities) { pub fn diagnostic_system(mut diagnostics: Diagnostics, entities: &Entities) {
diagnostics.add_measurement(Self::ENTITY_COUNT, || entities.len() as f64); diagnostics.add_measurement(&Self::ENTITY_COUNT, || entities.len() as f64);
} }
} }

View file

@ -1,4 +1,4 @@
use crate::{Diagnostic, DiagnosticId, Diagnostics, RegisterDiagnostic}; use crate::{Diagnostic, DiagnosticPath, Diagnostics, RegisterDiagnostic};
use bevy_app::prelude::*; use bevy_app::prelude::*;
use bevy_core::FrameCount; use bevy_core::FrameCount;
use bevy_ecs::prelude::*; use bevy_ecs::prelude::*;
@ -13,39 +13,33 @@ use bevy_time::{Real, Time};
pub struct FrameTimeDiagnosticsPlugin; pub struct FrameTimeDiagnosticsPlugin;
impl Plugin for FrameTimeDiagnosticsPlugin { impl Plugin for FrameTimeDiagnosticsPlugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut bevy_app::App) {
app.register_diagnostic( app.register_diagnostic(Diagnostic::new(Self::FRAME_TIME).with_suffix("ms"))
Diagnostic::new(Self::FRAME_TIME, "frame_time", 20).with_suffix("ms"), .register_diagnostic(Diagnostic::new(Self::FPS))
) .register_diagnostic(Diagnostic::new(Self::FRAME_COUNT).with_smoothing_factor(0.0))
.register_diagnostic(Diagnostic::new(Self::FPS, "fps", 20)) .add_systems(Update, Self::diagnostic_system);
.register_diagnostic(
Diagnostic::new(Self::FRAME_COUNT, "frame_count", 1).with_smoothing_factor(0.0),
)
.add_systems(Update, Self::diagnostic_system);
} }
} }
impl FrameTimeDiagnosticsPlugin { impl FrameTimeDiagnosticsPlugin {
pub const FPS: DiagnosticId = DiagnosticId::from_u128(288146834822086093791974408528866909483); pub const FPS: DiagnosticPath = DiagnosticPath::const_new("fps");
pub const FRAME_COUNT: DiagnosticId = pub const FRAME_COUNT: DiagnosticPath = DiagnosticPath::const_new("frame_count");
DiagnosticId::from_u128(54021991829115352065418785002088010277); pub const FRAME_TIME: DiagnosticPath = DiagnosticPath::const_new("frame_time");
pub const FRAME_TIME: DiagnosticId =
DiagnosticId::from_u128(73441630925388532774622109383099159699);
pub fn diagnostic_system( pub fn diagnostic_system(
mut diagnostics: Diagnostics, mut diagnostics: Diagnostics,
time: Res<Time<Real>>, time: Res<Time<Real>>,
frame_count: Res<FrameCount>, frame_count: Res<FrameCount>,
) { ) {
diagnostics.add_measurement(Self::FRAME_COUNT, || frame_count.0 as f64); diagnostics.add_measurement(&Self::FRAME_COUNT, || frame_count.0 as f64);
let delta_seconds = time.delta_seconds_f64(); let delta_seconds = time.delta_seconds_f64();
if delta_seconds == 0.0 { if delta_seconds == 0.0 {
return; return;
} }
diagnostics.add_measurement(Self::FRAME_TIME, || delta_seconds * 1000.0); diagnostics.add_measurement(&Self::FRAME_TIME, || delta_seconds * 1000.0);
diagnostics.add_measurement(Self::FPS, || 1.0 / delta_seconds); diagnostics.add_measurement(&Self::FPS, || 1.0 / delta_seconds);
} }
} }

View file

@ -28,6 +28,5 @@ impl Plugin for DiagnosticsPlugin {
} }
} }
/// The width which diagnostic names will be printed as /// Default max history length for new diagnostics.
/// Plugin names should not be longer than this value pub const DEFAULT_MAX_HISTORY_LENGTH: usize = 120;
pub const MAX_DIAGNOSTIC_NAME_WIDTH: usize = 32;

View file

@ -1,4 +1,4 @@
use super::{Diagnostic, DiagnosticId, DiagnosticsStore}; use super::{Diagnostic, DiagnosticPath, DiagnosticsStore};
use bevy_app::prelude::*; use bevy_app::prelude::*;
use bevy_ecs::prelude::*; use bevy_ecs::prelude::*;
use bevy_log::{debug, info}; use bevy_log::{debug, info};
@ -15,14 +15,14 @@ use bevy_utils::Duration;
pub struct LogDiagnosticsPlugin { pub struct LogDiagnosticsPlugin {
pub debug: bool, pub debug: bool,
pub wait_duration: Duration, pub wait_duration: Duration,
pub filter: Option<Vec<DiagnosticId>>, pub filter: Option<Vec<DiagnosticPath>>,
} }
/// State used by the [`LogDiagnosticsPlugin`] /// State used by the [`LogDiagnosticsPlugin`]
#[derive(Resource)] #[derive(Resource)]
struct LogDiagnosticsState { struct LogDiagnosticsState {
timer: Timer, timer: Timer,
filter: Option<Vec<DiagnosticId>>, filter: Option<Vec<DiagnosticPath>>,
} }
impl Default for LogDiagnosticsPlugin { impl Default for LogDiagnosticsPlugin {
@ -51,63 +51,84 @@ impl Plugin for LogDiagnosticsPlugin {
} }
impl LogDiagnosticsPlugin { impl LogDiagnosticsPlugin {
pub fn filtered(filter: Vec<DiagnosticId>) -> Self { pub fn filtered(filter: Vec<DiagnosticPath>) -> Self {
LogDiagnosticsPlugin { LogDiagnosticsPlugin {
filter: Some(filter), filter: Some(filter),
..Default::default() ..Default::default()
} }
} }
fn log_diagnostic(diagnostic: &Diagnostic) { fn for_each_diagnostic(
if let Some(value) = diagnostic.smoothed() { state: &LogDiagnosticsState,
if diagnostic.get_max_history_length() > 1 { diagnostics: &DiagnosticsStore,
if let Some(average) = diagnostic.average() { mut callback: impl FnMut(&Diagnostic),
info!( ) {
target: "bevy diagnostic", if let Some(filter) = &state.filter {
// Suffix is only used for 's' or 'ms' currently, for path in filter {
// so we reserve two columns for it; however, if let Some(diagnostic) = diagnostics.get(path) {
// Do not reserve columns for the suffix in the average if diagnostic.is_enabled {
// The ) hugging the value is more aesthetically pleasing callback(diagnostic);
"{name:<name_width$}: {value:>11.6}{suffix:2} (avg {average:>.6}{suffix:})", }
name = diagnostic.name,
suffix = diagnostic.suffix,
name_width = crate::MAX_DIAGNOSTIC_NAME_WIDTH,
);
return;
} }
} }
} else {
for diagnostic in diagnostics.iter() {
if diagnostic.is_enabled {
callback(diagnostic);
}
}
}
}
fn log_diagnostic(path_width: usize, diagnostic: &Diagnostic) {
let Some(value) = diagnostic.smoothed() else {
return;
};
if diagnostic.get_max_history_length() > 1 {
let Some(average) = diagnostic.average() else {
return;
};
info!( info!(
target: "bevy diagnostic", target: "bevy diagnostic",
"{name:<name_width$}: {value:>.6}{suffix:}", // Suffix is only used for 's' or 'ms' currently,
name = diagnostic.name, // so we reserve two columns for it; however,
// Do not reserve columns for the suffix in the average
// The ) hugging the value is more aesthetically pleasing
"{path:<path_width$}: {value:>11.6}{suffix:2} (avg {average:>.6}{suffix:})",
path = diagnostic.path(),
suffix = diagnostic.suffix,
);
} else {
info!(
target: "bevy diagnostic",
"{path:<path_width$}: {value:>.6}{suffix:}",
path = diagnostic.path(),
suffix = diagnostic.suffix, suffix = diagnostic.suffix,
name_width = crate::MAX_DIAGNOSTIC_NAME_WIDTH,
); );
} }
} }
fn log_diagnostics(state: &LogDiagnosticsState, diagnostics: &DiagnosticsStore) {
let mut path_width = 0;
Self::for_each_diagnostic(state, diagnostics, |diagnostic| {
let width = diagnostic.path().as_str().len();
path_width = path_width.max(width);
});
Self::for_each_diagnostic(state, diagnostics, |diagnostic| {
Self::log_diagnostic(path_width, diagnostic);
});
}
fn log_diagnostics_system( fn log_diagnostics_system(
mut state: ResMut<LogDiagnosticsState>, mut state: ResMut<LogDiagnosticsState>,
time: Res<Time<Real>>, time: Res<Time<Real>>,
diagnostics: Res<DiagnosticsStore>, diagnostics: Res<DiagnosticsStore>,
) { ) {
if state.timer.tick(time.delta()).finished() { if state.timer.tick(time.delta()).finished() {
if let Some(ref filter) = state.filter { Self::log_diagnostics(&state, &diagnostics);
for diagnostic in filter.iter().flat_map(|id| {
diagnostics
.get(*id)
.filter(|diagnostic| diagnostic.is_enabled)
}) {
Self::log_diagnostic(diagnostic);
}
} else {
for diagnostic in diagnostics
.iter()
.filter(|diagnostic| diagnostic.is_enabled)
{
Self::log_diagnostic(diagnostic);
}
}
} }
} }
@ -117,22 +138,9 @@ impl LogDiagnosticsPlugin {
diagnostics: Res<DiagnosticsStore>, diagnostics: Res<DiagnosticsStore>,
) { ) {
if state.timer.tick(time.delta()).finished() { if state.timer.tick(time.delta()).finished() {
if let Some(ref filter) = state.filter { Self::for_each_diagnostic(&state, &diagnostics, |diagnostic| {
for diagnostic in filter.iter().flat_map(|id| { debug!("{:#?}\n", diagnostic);
diagnostics });
.get(*id)
.filter(|diagnostic| diagnostic.is_enabled)
}) {
debug!("{:#?}\n", diagnostic);
}
} else {
for diagnostic in diagnostics
.iter()
.filter(|diagnostic| diagnostic.is_enabled)
{
debug!("{:#?}\n", diagnostic);
}
}
} }
} }
} }

View file

@ -1,4 +1,4 @@
use crate::DiagnosticId; use crate::DiagnosticPath;
use bevy_app::prelude::*; use bevy_app::prelude::*;
/// Adds a System Information Diagnostic, specifically `cpu_usage` (in %) and `mem_usage` (in %) /// Adds a System Information Diagnostic, specifically `cpu_usage` (in %) and `mem_usage` (in %)
@ -24,10 +24,8 @@ impl Plugin for SystemInformationDiagnosticsPlugin {
} }
impl SystemInformationDiagnosticsPlugin { impl SystemInformationDiagnosticsPlugin {
pub const CPU_USAGE: DiagnosticId = pub const CPU_USAGE: DiagnosticPath = DiagnosticPath::const_new("system/cpu_usage");
DiagnosticId::from_u128(78494871623549551581510633532637320956); pub const MEM_USAGE: DiagnosticPath = DiagnosticPath::const_new("system/mem_usage");
pub const MEM_USAGE: DiagnosticId =
DiagnosticId::from_u128(42846254859293759601295317811892519825);
} }
// NOTE: sysinfo fails to compile when using bevy dynamic or on iOS and does nothing on wasm // NOTE: sysinfo fails to compile when using bevy dynamic or on iOS and does nothing on wasm
@ -47,25 +45,15 @@ pub mod internal {
use crate::{Diagnostic, Diagnostics, DiagnosticsStore}; use crate::{Diagnostic, Diagnostics, DiagnosticsStore};
use super::SystemInformationDiagnosticsPlugin;
const BYTES_TO_GIB: f64 = 1.0 / 1024.0 / 1024.0 / 1024.0; const BYTES_TO_GIB: f64 = 1.0 / 1024.0 / 1024.0 / 1024.0;
pub(crate) fn setup_system(mut diagnostics: ResMut<DiagnosticsStore>) { pub(crate) fn setup_system(mut diagnostics: ResMut<DiagnosticsStore>) {
diagnostics.add( diagnostics
Diagnostic::new( .add(Diagnostic::new(SystemInformationDiagnosticsPlugin::CPU_USAGE).with_suffix("%"));
super::SystemInformationDiagnosticsPlugin::CPU_USAGE, diagnostics
"cpu_usage", .add(Diagnostic::new(SystemInformationDiagnosticsPlugin::MEM_USAGE).with_suffix("%"));
20,
)
.with_suffix("%"),
);
diagnostics.add(
Diagnostic::new(
super::SystemInformationDiagnosticsPlugin::MEM_USAGE,
"mem_usage",
20,
)
.with_suffix("%"),
);
} }
pub(crate) fn diagnostic_system( pub(crate) fn diagnostic_system(
@ -91,10 +79,10 @@ pub mod internal {
let used_mem = sys.used_memory() as f64 / BYTES_TO_GIB; let used_mem = sys.used_memory() as f64 / BYTES_TO_GIB;
let current_used_mem = used_mem / total_mem * 100.0; let current_used_mem = used_mem / total_mem * 100.0;
diagnostics.add_measurement(super::SystemInformationDiagnosticsPlugin::CPU_USAGE, || { diagnostics.add_measurement(&SystemInformationDiagnosticsPlugin::CPU_USAGE, || {
current_cpu_usage as f64 current_cpu_usage as f64
}); });
diagnostics.add_measurement(super::SystemInformationDiagnosticsPlugin::MEM_USAGE, || { diagnostics.add_measurement(&SystemInformationDiagnosticsPlugin::MEM_USAGE, || {
current_used_mem current_used_mem
}); });
} }

View file

@ -1,7 +1,9 @@
//! This example illustrates how to create a custom diagnostic. //! This example illustrates how to create a custom diagnostic.
use bevy::{ use bevy::{
diagnostic::{Diagnostic, DiagnosticId, Diagnostics, LogDiagnosticsPlugin, RegisterDiagnostic}, diagnostic::{
Diagnostic, DiagnosticPath, Diagnostics, LogDiagnosticsPlugin, RegisterDiagnostic,
},
prelude::*, prelude::*,
}; };
@ -14,20 +16,16 @@ fn main() {
LogDiagnosticsPlugin::default(), LogDiagnosticsPlugin::default(),
)) ))
// Diagnostics must be initialized before measurements can be added. // Diagnostics must be initialized before measurements can be added.
.register_diagnostic( .register_diagnostic(Diagnostic::new(SYSTEM_ITERATION_COUNT).with_suffix(" iterations"))
Diagnostic::new(SYSTEM_ITERATION_COUNT, "system_iteration_count", 10)
.with_suffix(" iterations"),
)
.add_systems(Update, my_system) .add_systems(Update, my_system)
.run(); .run();
} }
// All diagnostics should have a unique DiagnosticId. // All diagnostics should have a unique DiagnosticPath.
// For each new diagnostic, generate a new random number. pub const SYSTEM_ITERATION_COUNT: DiagnosticPath =
pub const SYSTEM_ITERATION_COUNT: DiagnosticId = DiagnosticPath::const_new("system_iteration_count");
DiagnosticId::from_u128(337040787172757619024841343456040760896);
fn my_system(mut diagnostics: Diagnostics) { fn my_system(mut diagnostics: Diagnostics) {
// Add a measurement of 10.0 for our diagnostic each time this system runs. // Add a measurement of 10.0 for our diagnostic each time this system runs.
diagnostics.add_measurement(SYSTEM_ITERATION_COUNT, || 10.0); diagnostics.add_measurement(&SYSTEM_ITERATION_COUNT, || 10.0);
} }

View file

@ -517,7 +517,7 @@ fn counter_system(
text.sections[1].value = counter.count.to_string(); text.sections[1].value = counter.count.to_string();
} }
if let Some(fps) = diagnostics.get(FrameTimeDiagnosticsPlugin::FPS) { if let Some(fps) = diagnostics.get(&FrameTimeDiagnosticsPlugin::FPS) {
if let Some(raw) = fps.value() { if let Some(raw) = fps.value() {
text.sections[3].value = format!("{raw:.2}"); text.sections[3].value = format!("{raw:.2}");
} }

View file

@ -93,7 +93,7 @@ fn ui_system(mut query: Query<&mut Text>, config: Res<Config>, diag: Res<Diagnos
let mut text = query.single_mut(); let mut text = query.single_mut();
let Some(fps) = diag let Some(fps) = diag
.get(FrameTimeDiagnosticsPlugin::FPS) .get(&FrameTimeDiagnosticsPlugin::FPS)
.and_then(|fps| fps.smoothed()) .and_then(|fps| fps.smoothed())
else { else {
return; return;

View file

@ -134,7 +134,7 @@ fn text_update_system(
mut query: Query<&mut Text, With<FpsText>>, mut query: Query<&mut Text, With<FpsText>>,
) { ) {
for mut text in &mut query { for mut text in &mut query {
if let Some(fps) = diagnostics.get(FrameTimeDiagnosticsPlugin::FPS) { if let Some(fps) = diagnostics.get(&FrameTimeDiagnosticsPlugin::FPS) {
if let Some(value) = fps.smoothed() { if let Some(value) = fps.smoothed() {
// Update the value of the second section // Update the value of the second section
text.sections[1].value = format!("{value:.2}"); text.sections[1].value = format!("{value:.2}");

View file

@ -197,14 +197,15 @@ fn change_text_system(
) { ) {
for mut text in &mut query { for mut text in &mut query {
let mut fps = 0.0; let mut fps = 0.0;
if let Some(fps_diagnostic) = diagnostics.get(FrameTimeDiagnosticsPlugin::FPS) { if let Some(fps_diagnostic) = diagnostics.get(&FrameTimeDiagnosticsPlugin::FPS) {
if let Some(fps_smoothed) = fps_diagnostic.smoothed() { if let Some(fps_smoothed) = fps_diagnostic.smoothed() {
fps = fps_smoothed; fps = fps_smoothed;
} }
} }
let mut frame_time = time.delta_seconds_f64(); let mut frame_time = time.delta_seconds_f64();
if let Some(frame_time_diagnostic) = diagnostics.get(FrameTimeDiagnosticsPlugin::FRAME_TIME) if let Some(frame_time_diagnostic) =
diagnostics.get(&FrameTimeDiagnosticsPlugin::FRAME_TIME)
{ {
if let Some(frame_time_smoothed) = frame_time_diagnostic.smoothed() { if let Some(frame_time_smoothed) = frame_time_diagnostic.smoothed() {
frame_time = frame_time_smoothed; frame_time = frame_time_smoothed;