mirror of
https://github.com/bevyengine/bevy
synced 2024-11-21 20:23:28 +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_utils = { path = "../bevy_utils", version = "0.12.0" }
|
||||
|
||||
const-fnv1a-hash = "1.1.0"
|
||||
|
||||
# MacOS
|
||||
[target.'cfg(all(target_os="macos"))'.dependencies]
|
||||
# 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 bevy_ecs::system::{Deferred, Res, Resource, SystemBuffer, SystemParam};
|
||||
use bevy_log::warn;
|
||||
use bevy_utils::{Duration, Instant, StableHashMap, Uuid};
|
||||
use std::hash::{Hash, Hasher};
|
||||
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`].
|
||||
#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, PartialOrd, Ord)]
|
||||
pub struct DiagnosticId(pub Uuid);
|
||||
use crate::DEFAULT_MAX_HISTORY_LENGTH;
|
||||
|
||||
impl DiagnosticId {
|
||||
pub const fn from_u128(value: u128) -> Self {
|
||||
DiagnosticId(Uuid::from_u128(value))
|
||||
/// Unique diagnostic path, separated by `/`.
|
||||
///
|
||||
/// 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 {
|
||||
fn default() -> Self {
|
||||
DiagnosticId(Uuid::new_v4())
|
||||
impl From<DiagnosticPath> for String {
|
||||
fn from(path: DiagnosticPath) -> Self {
|
||||
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
|
||||
#[derive(Debug)]
|
||||
pub struct Diagnostic {
|
||||
pub id: DiagnosticId,
|
||||
pub name: Cow<'static, str>,
|
||||
path: DiagnosticPath,
|
||||
pub suffix: Cow<'static, str>,
|
||||
history: VecDeque<DiagnosticMeasurement>,
|
||||
sum: f64,
|
||||
|
@ -71,27 +154,13 @@ impl Diagnostic {
|
|||
self.history.push_back(measurement);
|
||||
}
|
||||
|
||||
/// Create a new diagnostic with the given ID, name and maximum history.
|
||||
pub fn new(
|
||||
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
|
||||
);
|
||||
}
|
||||
/// Create a new diagnostic with the given path.
|
||||
pub fn new(path: DiagnosticPath) -> Diagnostic {
|
||||
Diagnostic {
|
||||
id,
|
||||
name,
|
||||
path,
|
||||
suffix: Cow::Borrowed(""),
|
||||
history: VecDeque::with_capacity(max_history_length),
|
||||
max_history_length,
|
||||
history: VecDeque::with_capacity(DEFAULT_MAX_HISTORY_LENGTH),
|
||||
max_history_length: DEFAULT_MAX_HISTORY_LENGTH,
|
||||
sum: 0.0,
|
||||
ema: 0.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.
|
||||
#[must_use]
|
||||
pub fn with_suffix(mut self, suffix: impl Into<Cow<'static, str>>) -> Self {
|
||||
|
@ -122,6 +200,10 @@ impl Diagnostic {
|
|||
self
|
||||
}
|
||||
|
||||
pub fn path(&self) -> &DiagnosticPath {
|
||||
&self.path
|
||||
}
|
||||
|
||||
/// Get the latest measurement from this diagnostic.
|
||||
#[inline]
|
||||
pub fn measurement(&self) -> Option<&DiagnosticMeasurement> {
|
||||
|
@ -198,9 +280,7 @@ impl Diagnostic {
|
|||
/// A collection of [`Diagnostic`]s.
|
||||
#[derive(Debug, Default, Resource)]
|
||||
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<DiagnosticId, Diagnostic>,
|
||||
diagnostics: HashMap<DiagnosticPath, Diagnostic, PassHash>,
|
||||
}
|
||||
|
||||
impl DiagnosticsStore {
|
||||
|
@ -208,21 +288,21 @@ impl DiagnosticsStore {
|
|||
///
|
||||
/// If possible, prefer calling [`App::register_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> {
|
||||
self.diagnostics.get(&id)
|
||||
pub fn get(&self, path: &DiagnosticPath) -> Option<&Diagnostic> {
|
||||
self.diagnostics.get(path)
|
||||
}
|
||||
|
||||
pub fn get_mut(&mut self, id: DiagnosticId) -> Option<&mut Diagnostic> {
|
||||
self.diagnostics.get_mut(&id)
|
||||
pub fn get_mut(&mut self, path: &DiagnosticPath) -> Option<&mut Diagnostic> {
|
||||
self.diagnostics.get_mut(path)
|
||||
}
|
||||
|
||||
/// 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
|
||||
.get(&id)
|
||||
.get(path)
|
||||
.filter(|diagnostic| diagnostic.is_enabled)
|
||||
.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
|
||||
/// it will be evaluated only if the [`Diagnostic`] is enabled. This can be useful if the value is
|
||||
/// 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
|
||||
F: FnOnce() -> f64,
|
||||
{
|
||||
if self
|
||||
.store
|
||||
.get(id)
|
||||
.get(path)
|
||||
.filter(|diagnostic| diagnostic.is_enabled)
|
||||
.is_some()
|
||||
{
|
||||
|
@ -263,13 +343,13 @@ impl<'w, 's> Diagnostics<'w, 's> {
|
|||
time: Instant::now(),
|
||||
value: value(),
|
||||
};
|
||||
self.queue.0.insert(id, measurement);
|
||||
self.queue.0.insert(path.clone(), measurement);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct DiagnosticsBuffer(StableHashMap<DiagnosticId, DiagnosticMeasurement>);
|
||||
struct DiagnosticsBuffer(HashMap<DiagnosticPath, DiagnosticMeasurement, PassHash>);
|
||||
|
||||
impl SystemBuffer for DiagnosticsBuffer {
|
||||
fn apply(
|
||||
|
@ -278,8 +358,8 @@ impl SystemBuffer for DiagnosticsBuffer {
|
|||
world: &mut bevy_ecs::world::World,
|
||||
) {
|
||||
let mut diagnostics = world.resource_mut::<DiagnosticsStore>();
|
||||
for (id, measurement) in self.0.drain() {
|
||||
if let Some(diagnostic) = diagnostics.get_mut(id) {
|
||||
for (path, measurement) in self.0.drain() {
|
||||
if let Some(diagnostic) = diagnostics.get_mut(&path) {
|
||||
diagnostic.add_measurement(measurement);
|
||||
}
|
||||
}
|
||||
|
@ -298,12 +378,12 @@ impl RegisterDiagnostic for 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()
|
||||
/// .register_diagnostic(Diagnostic::new(UNIQUE_DIAG_ID, "example", 10))
|
||||
/// .register_diagnostic(Diagnostic::new(UNIQUE_DIAG_PATH))
|
||||
/// .add_plugins(DiagnosticsPlugin)
|
||||
/// .run();
|
||||
/// ```
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use bevy_app::prelude::*;
|
||||
use bevy_ecs::entity::Entities;
|
||||
|
||||
use crate::{Diagnostic, DiagnosticId, Diagnostics, RegisterDiagnostic};
|
||||
use crate::{Diagnostic, DiagnosticPath, Diagnostics, RegisterDiagnostic};
|
||||
|
||||
/// Adds "entity count" diagnostic to an App.
|
||||
///
|
||||
|
@ -13,16 +13,15 @@ pub struct EntityCountDiagnosticsPlugin;
|
|||
|
||||
impl Plugin for EntityCountDiagnosticsPlugin {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
impl EntityCountDiagnosticsPlugin {
|
||||
pub const ENTITY_COUNT: DiagnosticId =
|
||||
DiagnosticId::from_u128(187513512115068938494459732780662867798);
|
||||
pub const ENTITY_COUNT: DiagnosticPath = DiagnosticPath::const_new("entity_count");
|
||||
|
||||
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_core::FrameCount;
|
||||
use bevy_ecs::prelude::*;
|
||||
|
@ -13,39 +13,33 @@ use bevy_time::{Real, Time};
|
|||
pub struct FrameTimeDiagnosticsPlugin;
|
||||
|
||||
impl Plugin for FrameTimeDiagnosticsPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
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);
|
||||
fn build(&self, app: &mut bevy_app::App) {
|
||||
app.register_diagnostic(Diagnostic::new(Self::FRAME_TIME).with_suffix("ms"))
|
||||
.register_diagnostic(Diagnostic::new(Self::FPS))
|
||||
.register_diagnostic(Diagnostic::new(Self::FRAME_COUNT).with_smoothing_factor(0.0))
|
||||
.add_systems(Update, Self::diagnostic_system);
|
||||
}
|
||||
}
|
||||
|
||||
impl FrameTimeDiagnosticsPlugin {
|
||||
pub const FPS: DiagnosticId = DiagnosticId::from_u128(288146834822086093791974408528866909483);
|
||||
pub const FRAME_COUNT: DiagnosticId =
|
||||
DiagnosticId::from_u128(54021991829115352065418785002088010277);
|
||||
pub const FRAME_TIME: DiagnosticId =
|
||||
DiagnosticId::from_u128(73441630925388532774622109383099159699);
|
||||
pub const FPS: DiagnosticPath = DiagnosticPath::const_new("fps");
|
||||
pub const FRAME_COUNT: DiagnosticPath = DiagnosticPath::const_new("frame_count");
|
||||
pub const FRAME_TIME: DiagnosticPath = DiagnosticPath::const_new("frame_time");
|
||||
|
||||
pub fn diagnostic_system(
|
||||
mut diagnostics: Diagnostics,
|
||||
time: Res<Time<Real>>,
|
||||
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();
|
||||
if delta_seconds == 0.0 {
|
||||
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
|
||||
/// Plugin names should not be longer than this value
|
||||
pub const MAX_DIAGNOSTIC_NAME_WIDTH: usize = 32;
|
||||
/// Default max history length for new diagnostics.
|
||||
pub const DEFAULT_MAX_HISTORY_LENGTH: usize = 120;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use super::{Diagnostic, DiagnosticId, DiagnosticsStore};
|
||||
use super::{Diagnostic, DiagnosticPath, DiagnosticsStore};
|
||||
use bevy_app::prelude::*;
|
||||
use bevy_ecs::prelude::*;
|
||||
use bevy_log::{debug, info};
|
||||
|
@ -15,14 +15,14 @@ use bevy_utils::Duration;
|
|||
pub struct LogDiagnosticsPlugin {
|
||||
pub debug: bool,
|
||||
pub wait_duration: Duration,
|
||||
pub filter: Option<Vec<DiagnosticId>>,
|
||||
pub filter: Option<Vec<DiagnosticPath>>,
|
||||
}
|
||||
|
||||
/// State used by the [`LogDiagnosticsPlugin`]
|
||||
#[derive(Resource)]
|
||||
struct LogDiagnosticsState {
|
||||
timer: Timer,
|
||||
filter: Option<Vec<DiagnosticId>>,
|
||||
filter: Option<Vec<DiagnosticPath>>,
|
||||
}
|
||||
|
||||
impl Default for LogDiagnosticsPlugin {
|
||||
|
@ -51,63 +51,84 @@ impl Plugin for LogDiagnosticsPlugin {
|
|||
}
|
||||
|
||||
impl LogDiagnosticsPlugin {
|
||||
pub fn filtered(filter: Vec<DiagnosticId>) -> Self {
|
||||
pub fn filtered(filter: Vec<DiagnosticPath>) -> Self {
|
||||
LogDiagnosticsPlugin {
|
||||
filter: Some(filter),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
fn log_diagnostic(diagnostic: &Diagnostic) {
|
||||
if let Some(value) = diagnostic.smoothed() {
|
||||
if diagnostic.get_max_history_length() > 1 {
|
||||
if let Some(average) = diagnostic.average() {
|
||||
info!(
|
||||
target: "bevy diagnostic",
|
||||
// Suffix is only used for 's' or 'ms' currently,
|
||||
// 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
|
||||
"{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;
|
||||
fn for_each_diagnostic(
|
||||
state: &LogDiagnosticsState,
|
||||
diagnostics: &DiagnosticsStore,
|
||||
mut callback: impl FnMut(&Diagnostic),
|
||||
) {
|
||||
if let Some(filter) = &state.filter {
|
||||
for path in filter {
|
||||
if let Some(diagnostic) = diagnostics.get(path) {
|
||||
if diagnostic.is_enabled {
|
||||
callback(diagnostic);
|
||||
}
|
||||
}
|
||||
}
|
||||
} 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!(
|
||||
target: "bevy diagnostic",
|
||||
"{name:<name_width$}: {value:>.6}{suffix:}",
|
||||
name = diagnostic.name,
|
||||
// Suffix is only used for 's' or 'ms' currently,
|
||||
// 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,
|
||||
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(
|
||||
mut state: ResMut<LogDiagnosticsState>,
|
||||
time: Res<Time<Real>>,
|
||||
diagnostics: Res<DiagnosticsStore>,
|
||||
) {
|
||||
if state.timer.tick(time.delta()).finished() {
|
||||
if let Some(ref filter) = state.filter {
|
||||
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);
|
||||
}
|
||||
}
|
||||
Self::log_diagnostics(&state, &diagnostics);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -117,22 +138,9 @@ impl LogDiagnosticsPlugin {
|
|||
diagnostics: Res<DiagnosticsStore>,
|
||||
) {
|
||||
if state.timer.tick(time.delta()).finished() {
|
||||
if let Some(ref filter) = state.filter {
|
||||
for diagnostic in filter.iter().flat_map(|id| {
|
||||
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);
|
||||
}
|
||||
}
|
||||
Self::for_each_diagnostic(&state, &diagnostics, |diagnostic| {
|
||||
debug!("{:#?}\n", diagnostic);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use crate::DiagnosticId;
|
||||
use crate::DiagnosticPath;
|
||||
use bevy_app::prelude::*;
|
||||
|
||||
/// Adds a System Information Diagnostic, specifically `cpu_usage` (in %) and `mem_usage` (in %)
|
||||
|
@ -24,10 +24,8 @@ impl Plugin for SystemInformationDiagnosticsPlugin {
|
|||
}
|
||||
|
||||
impl SystemInformationDiagnosticsPlugin {
|
||||
pub const CPU_USAGE: DiagnosticId =
|
||||
DiagnosticId::from_u128(78494871623549551581510633532637320956);
|
||||
pub const MEM_USAGE: DiagnosticId =
|
||||
DiagnosticId::from_u128(42846254859293759601295317811892519825);
|
||||
pub const CPU_USAGE: DiagnosticPath = DiagnosticPath::const_new("system/cpu_usage");
|
||||
pub const MEM_USAGE: DiagnosticPath = DiagnosticPath::const_new("system/mem_usage");
|
||||
}
|
||||
|
||||
// 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 super::SystemInformationDiagnosticsPlugin;
|
||||
|
||||
const BYTES_TO_GIB: f64 = 1.0 / 1024.0 / 1024.0 / 1024.0;
|
||||
|
||||
pub(crate) fn setup_system(mut diagnostics: ResMut<DiagnosticsStore>) {
|
||||
diagnostics.add(
|
||||
Diagnostic::new(
|
||||
super::SystemInformationDiagnosticsPlugin::CPU_USAGE,
|
||||
"cpu_usage",
|
||||
20,
|
||||
)
|
||||
.with_suffix("%"),
|
||||
);
|
||||
diagnostics.add(
|
||||
Diagnostic::new(
|
||||
super::SystemInformationDiagnosticsPlugin::MEM_USAGE,
|
||||
"mem_usage",
|
||||
20,
|
||||
)
|
||||
.with_suffix("%"),
|
||||
);
|
||||
diagnostics
|
||||
.add(Diagnostic::new(SystemInformationDiagnosticsPlugin::CPU_USAGE).with_suffix("%"));
|
||||
diagnostics
|
||||
.add(Diagnostic::new(SystemInformationDiagnosticsPlugin::MEM_USAGE).with_suffix("%"));
|
||||
}
|
||||
|
||||
pub(crate) fn diagnostic_system(
|
||||
|
@ -91,10 +79,10 @@ pub mod internal {
|
|||
let used_mem = sys.used_memory() as f64 / BYTES_TO_GIB;
|
||||
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
|
||||
});
|
||||
diagnostics.add_measurement(super::SystemInformationDiagnosticsPlugin::MEM_USAGE, || {
|
||||
diagnostics.add_measurement(&SystemInformationDiagnosticsPlugin::MEM_USAGE, || {
|
||||
current_used_mem
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
//! This example illustrates how to create a custom diagnostic.
|
||||
|
||||
use bevy::{
|
||||
diagnostic::{Diagnostic, DiagnosticId, Diagnostics, LogDiagnosticsPlugin, RegisterDiagnostic},
|
||||
diagnostic::{
|
||||
Diagnostic, DiagnosticPath, Diagnostics, LogDiagnosticsPlugin, RegisterDiagnostic,
|
||||
},
|
||||
prelude::*,
|
||||
};
|
||||
|
||||
|
@ -14,20 +16,16 @@ fn main() {
|
|||
LogDiagnosticsPlugin::default(),
|
||||
))
|
||||
// Diagnostics must be initialized before measurements can be added.
|
||||
.register_diagnostic(
|
||||
Diagnostic::new(SYSTEM_ITERATION_COUNT, "system_iteration_count", 10)
|
||||
.with_suffix(" iterations"),
|
||||
)
|
||||
.register_diagnostic(Diagnostic::new(SYSTEM_ITERATION_COUNT).with_suffix(" iterations"))
|
||||
.add_systems(Update, my_system)
|
||||
.run();
|
||||
}
|
||||
|
||||
// All diagnostics should have a unique DiagnosticId.
|
||||
// For each new diagnostic, generate a new random number.
|
||||
pub const SYSTEM_ITERATION_COUNT: DiagnosticId =
|
||||
DiagnosticId::from_u128(337040787172757619024841343456040760896);
|
||||
// All diagnostics should have a unique DiagnosticPath.
|
||||
pub const SYSTEM_ITERATION_COUNT: DiagnosticPath =
|
||||
DiagnosticPath::const_new("system_iteration_count");
|
||||
|
||||
fn my_system(mut diagnostics: Diagnostics) {
|
||||
// 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();
|
||||
}
|
||||
|
||||
if let Some(fps) = diagnostics.get(FrameTimeDiagnosticsPlugin::FPS) {
|
||||
if let Some(fps) = diagnostics.get(&FrameTimeDiagnosticsPlugin::FPS) {
|
||||
if let Some(raw) = fps.value() {
|
||||
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 Some(fps) = diag
|
||||
.get(FrameTimeDiagnosticsPlugin::FPS)
|
||||
.get(&FrameTimeDiagnosticsPlugin::FPS)
|
||||
.and_then(|fps| fps.smoothed())
|
||||
else {
|
||||
return;
|
||||
|
|
|
@ -134,7 +134,7 @@ fn text_update_system(
|
|||
mut query: Query<&mut Text, With<FpsText>>,
|
||||
) {
|
||||
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() {
|
||||
// Update the value of the second section
|
||||
text.sections[1].value = format!("{value:.2}");
|
||||
|
|
|
@ -197,14 +197,15 @@ fn change_text_system(
|
|||
) {
|
||||
for mut text in &mut query {
|
||||
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() {
|
||||
fps = fps_smoothed;
|
||||
}
|
||||
}
|
||||
|
||||
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() {
|
||||
frame_time = frame_time_smoothed;
|
||||
|
|
Loading…
Reference in a new issue