mirror of
https://github.com/bevyengine/bevy
synced 2024-11-22 04:33:37 +00:00
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:
parent
04aedf12fa
commit
320ac65a9e
12 changed files with 242 additions and 173 deletions
|
@ -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
|
||||||
|
|
|
@ -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();
|
||||||
/// ```
|
/// ```
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
|
||||||
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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}");
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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}");
|
||||||
|
|
|
@ -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;
|
||||||
|
|
Loading…
Reference in a new issue