Allow users to customize history length in FrameTimeDiagnosticsPlugin (#17259)

# Objective

I have an application where I'd like to measure average frame rate over
the entire life of the application, and it would be handy if I could
just configure this on the existing `FrameTimeDiagnosticsPlugin`.

Probably fixes #10948?

## Solution

Add `max_history_length` to `FrameTimeDiagnosticsPlugin`, and because
`smoothing_factor` seems to be based on history length, add that too.

## Discussion

I'm not totally sure that `DEFAULT_MAX_HISTORY_LENGTH` is a great
default for `FrameTimeDiagnosticsPlugin` (or any diagnostic?). That's
1/3 of a second at typical game frame rates. Moreover, the default print
interval for `LogDiagnosticsPlugin` is 1 second. So when the two are
combined, you are printing the average over the last third of the
duration between now and the previous print, which seems a bit wonky.
(related: #11429)

I'm pretty sure this default value discussed and the current value
wasn't totally arbitrary though.

Maybe it would be nice for `Diagnostic` to have a
`with_max_history_length_and_also_calculate_a_good_default_smoothing_factor`
method? And then make an explicit smoothing factor in
`FrameTimeDiagnosticsPlugin` optional?

Or add a `new(max_history_length: usize)` method to
`FrameTimeDiagnosticsPlugin` that sets a reasonable default
`smoothing_factor`? edit: This one seems like a no-brainer, doing it.

## Alternatives

It's really easy to roll your own `FrameTimeDiagnosticsPlugin`, but that
might not be super interoperable with, for example, third party FPS
overlays. Still, might be the right call.

## Testing

`cargo run --example many_sprites` (modified to use a custom
`max_history_length`)

## Migration Guide

`FrameTimeDiagnosticsPlugin` now contains two fields. Use
`FrameTimeDiagnosticsPlugin::default()` to match Bevy's previous
behavior or, for example, `FrameTimeDiagnosticsPlugin::new(60)` to
configure it.
This commit is contained in:
Rob Parrett 2025-01-12 10:18:14 -08:00 committed by GitHub
parent bb0a82b9a7
commit f0047899d7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
20 changed files with 63 additions and 26 deletions

View File

@ -42,7 +42,7 @@ impl Plugin for FpsOverlayPlugin {
fn build(&self, app: &mut bevy_app::App) {
// TODO: Use plugin dependencies, see https://github.com/bevyengine/bevy/issues/69
if !app.is_plugin_added::<FrameTimeDiagnosticsPlugin>() {
app.add_plugins(FrameTimeDiagnosticsPlugin);
app.add_plugins(FrameTimeDiagnosticsPlugin::default());
}
app.insert_resource(self.config.clone())
.add_systems(Startup, setup)

View File

@ -1,4 +1,7 @@
use crate::{Diagnostic, DiagnosticPath, Diagnostics, FrameCount, RegisterDiagnostic};
use crate::{
Diagnostic, DiagnosticPath, Diagnostics, FrameCount, RegisterDiagnostic,
DEFAULT_MAX_HISTORY_LENGTH,
};
use bevy_app::prelude::*;
use bevy_ecs::prelude::*;
use bevy_time::{Real, Time};
@ -8,15 +11,49 @@ use bevy_time::{Real, Time};
/// # See also
///
/// [`LogDiagnosticsPlugin`](crate::LogDiagnosticsPlugin) to output diagnostics to the console.
#[derive(Default)]
pub struct FrameTimeDiagnosticsPlugin;
pub struct FrameTimeDiagnosticsPlugin {
/// The total number of values to keep for averaging.
pub max_history_length: usize,
/// The smoothing factor for the exponential moving average. Usually `(history_length + 1.0) / 2.0)`.
pub smoothing_factor: f64,
}
impl Default for FrameTimeDiagnosticsPlugin {
fn default() -> Self {
Self::new(DEFAULT_MAX_HISTORY_LENGTH)
}
}
impl FrameTimeDiagnosticsPlugin {
/// Creates a new `FrameTimeDiagnosticsPlugin` with the specified `max_history_length` and a
/// reasonable `smoothing_factor`.
pub fn new(max_history_length: usize) -> Self {
Self {
max_history_length,
smoothing_factor: (max_history_length as f64 + 1.0) / 2.0,
}
}
}
impl Plugin for FrameTimeDiagnosticsPlugin {
fn build(&self, app: &mut 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);
app.register_diagnostic(
Diagnostic::new(Self::FRAME_TIME)
.with_suffix("ms")
.with_max_history_length(self.max_history_length)
.with_smoothing_factor(self.smoothing_factor),
)
.register_diagnostic(
Diagnostic::new(Self::FPS)
.with_max_history_length(self.max_history_length)
.with_smoothing_factor(self.smoothing_factor),
)
// An average frame count would be nonsensical, so we set the max history length
// to zero and disable smoothing.
.register_diagnostic(
Diagnostic::new(Self::FRAME_COUNT)
.with_smoothing_factor(0.0)
.with_max_history_length(0),
)
.add_systems(Update, Self::diagnostic_system);
}
}

View File

@ -12,7 +12,7 @@ fn main() {
App::new()
.add_plugins((
DefaultPlugins,
FrameTimeDiagnosticsPlugin,
FrameTimeDiagnosticsPlugin::default(),
LogDiagnosticsPlugin::default(),
))
.add_systems(

View File

@ -10,7 +10,7 @@ fn main() {
.add_plugins((
DefaultPlugins,
// Adds frame time diagnostics
FrameTimeDiagnosticsPlugin,
FrameTimeDiagnosticsPlugin::default(),
// Adds a system that prints diagnostics to the console
LogDiagnosticsPlugin::default(),
// Any plugin can register diagnostics. Uncomment this to add an entity count diagnostics:

View File

@ -142,7 +142,7 @@ fn main() {
}),
..default()
}),
FrameTimeDiagnosticsPlugin,
FrameTimeDiagnosticsPlugin::default(),
LogDiagnosticsPlugin::default(),
))
.insert_resource(WinitSettings {

View File

@ -21,7 +21,7 @@ fn main() {
// Since this is also used as a benchmark, we want it to display performance data.
.add_plugins((
LogDiagnosticsPlugin::default(),
FrameTimeDiagnosticsPlugin,
FrameTimeDiagnosticsPlugin::default(),
DefaultPlugins.set(WindowPlugin {
primary_window: Some(Window {
present_mode: PresentMode::AutoNoVsync,

View File

@ -73,7 +73,7 @@ fn main() {
}),
..default()
}),
FrameTimeDiagnosticsPlugin,
FrameTimeDiagnosticsPlugin::default(),
LogDiagnosticsPlugin::default(),
))
.insert_resource(WinitSettings {

View File

@ -146,7 +146,7 @@ fn stress_test(num_entities: u32, num_components: u32, num_systems: u32) {
app.add_plugins(MinimalPlugins)
.add_plugins(DiagnosticsPlugin)
.add_plugins(LogPlugin::default())
.add_plugins(FrameTimeDiagnosticsPlugin)
.add_plugins(FrameTimeDiagnosticsPlugin::default())
.add_plugins(LogDiagnosticsPlugin::filtered(vec![DiagnosticPath::new(
"fps",
)]));

View File

@ -111,7 +111,7 @@ fn main() {
}),
..default()
}),
FrameTimeDiagnosticsPlugin,
FrameTimeDiagnosticsPlugin::default(),
LogDiagnosticsPlugin::default(),
))
.insert_resource(WinitSettings {

View File

@ -51,7 +51,7 @@ fn main() {
}),
..default()
}),
FrameTimeDiagnosticsPlugin,
FrameTimeDiagnosticsPlugin::default(),
LogDiagnosticsPlugin::default(),
))
.insert_resource(WinitSettings {

View File

@ -23,7 +23,7 @@ fn main() {
}),
..default()
}),
FrameTimeDiagnosticsPlugin,
FrameTimeDiagnosticsPlugin::default(),
LogDiagnosticsPlugin::default(),
))
.insert_resource(WinitSettings {

View File

@ -48,7 +48,7 @@ fn main() {
}),
..default()
}),
FrameTimeDiagnosticsPlugin,
FrameTimeDiagnosticsPlugin::default(),
LogDiagnosticsPlugin::default(),
))
.insert_resource(WinitSettings {

View File

@ -28,7 +28,7 @@ fn main() {
}),
..default()
}),
FrameTimeDiagnosticsPlugin,
FrameTimeDiagnosticsPlugin::default(),
LogDiagnosticsPlugin::default(),
LogVisibleLights,
))

View File

@ -32,7 +32,7 @@ fn main() {
// Since this is also used as a benchmark, we want it to display performance data.
.add_plugins((
LogDiagnosticsPlugin::default(),
FrameTimeDiagnosticsPlugin,
FrameTimeDiagnosticsPlugin::default(),
DefaultPlugins.set(WindowPlugin {
primary_window: Some(Window {
present_mode: PresentMode::AutoNoVsync,

View File

@ -66,7 +66,7 @@ fn main() {
let mut app = App::new();
app.add_plugins((
FrameTimeDiagnosticsPlugin,
FrameTimeDiagnosticsPlugin::default(),
LogDiagnosticsPlugin::default(),
DefaultPlugins.set(WindowPlugin {
primary_window: Some(Window {

View File

@ -23,7 +23,7 @@ fn main() {
}),
..default()
}),
FrameTimeDiagnosticsPlugin,
FrameTimeDiagnosticsPlugin::default(),
LogDiagnosticsPlugin::default(),
))
.insert_resource(WinitSettings {

View File

@ -193,7 +193,7 @@ fn main() {
exit_condition: ExitCondition::DontExit,
..default()
}),
FrameTimeDiagnosticsPlugin,
FrameTimeDiagnosticsPlugin::default(),
LogDiagnosticsPlugin::default(),
))
.add_systems(Startup, setup)

View File

@ -11,7 +11,7 @@ use bevy::{
fn main() {
App::new()
.add_plugins((DefaultPlugins, FrameTimeDiagnosticsPlugin))
.add_plugins((DefaultPlugins, FrameTimeDiagnosticsPlugin::default()))
.add_systems(Startup, setup)
.add_systems(Update, (text_update_system, text_color_system))
.run();

View File

@ -20,7 +20,7 @@ fn main() {
}),
..default()
}),
FrameTimeDiagnosticsPlugin,
FrameTimeDiagnosticsPlugin::default(),
))
.add_systems(Startup, infotext_system)
.add_systems(Update, change_text_system)

View File

@ -37,7 +37,7 @@ fn main() {
..default()
}),
LogDiagnosticsPlugin::default(),
FrameTimeDiagnosticsPlugin,
FrameTimeDiagnosticsPlugin::default(),
))
.add_systems(Startup, init_cursor_icons)
.add_systems(