bevy/examples/ui/text_debug.rs
Michael Johnson 3507b21dce
Allow systems using Diagnostics to run in parallel (#8677)
# Objective

I was trying to add some `Diagnostics` to have a better break down of
performance but I noticed that the current implementation uses a
`ResMut` which forces the functions to all run sequentially whereas
before they could run in parallel. This created too great a performance
penalty to be usable.

## Solution

This PR reworks how the diagnostics work with a couple of breaking
changes. The idea is to change how `Diagnostics` works by changing it to
a `SystemParam`. This allows us to hold a `Deferred` buffer of
measurements that can be applied later, avoiding the need for multiple
mutable references to the hashmap. This means we can run systems that
write diagnostic measurements in parallel.

Firstly, we rename the old `Diagnostics` to `DiagnosticsStore`. This
clears up the original name for the new interface while allowing us to
preserve more closely the original API.

Then we create a new `Diagnostics` struct which implements `SystemParam`
and contains a deferred `SystemBuffer`. This can be used very similar to
the old `Diagnostics` for writing new measurements.

```rust
fn system(diagnostics: ResMut<Diagnostics>) { diagnostics.new_measurement(ID, || 10.0)}
// changes to
fn system(mut diagnostics: Diagnostics) { diagnostics.new_measurement(ID, || 10.0)}
``` 
For reading the diagnostics, the user needs to change from `Diagnostics`
to `DiagnosticsStore` but otherwise the function calls are the same.

Finally, we add a new method to the `App` for registering diagnostics.
This replaces the old method of creating a startup system and adding it
manually.

Testing it, this PR does indeed allow Diagnostic systems to be run in
parallel.

## Changelog

- Change `Diagnostics` to implement `SystemParam` which allows
diagnostic systems to run in parallel.

## Migration Guide

- Register `Diagnostic`'s using the new
`app.register_diagnostic(Diagnostic::new(DIAGNOSTIC_ID,
"diagnostic_name", 10));`
- In systems for writing new measurements, change `mut diagnostics:
ResMut<Diagnostics>` to `mut diagnostics: Diagnostics` to allow the
systems to run in parallel.
- In systems for reading measurements, change `diagnostics:
Res<Diagnostics>` to `diagnostics: Res<DiagnosticsStore>`.
2023-06-05 20:51:22 +00:00

165 lines
5 KiB
Rust

//! Shows various text layout options.
use bevy::{
diagnostic::{DiagnosticsStore, FrameTimeDiagnosticsPlugin},
prelude::*,
window::{PresentMode, WindowPlugin},
};
fn main() {
App::new()
.add_plugins(DefaultPlugins.set(WindowPlugin {
primary_window: Some(Window {
present_mode: PresentMode::AutoNoVsync,
..default()
}),
..default()
}))
.add_plugin(FrameTimeDiagnosticsPlugin)
.add_systems(Startup, infotext_system)
.add_systems(Update, change_text_system)
.run();
}
#[derive(Component)]
struct TextChanges;
fn infotext_system(mut commands: Commands, asset_server: Res<AssetServer>) {
let font = asset_server.load("fonts/FiraSans-Bold.ttf");
commands.spawn(Camera2dBundle::default());
commands.spawn(
TextBundle::from_section(
"This is\ntext with\nline breaks\nin the top left",
TextStyle {
font: font.clone(),
font_size: 50.0,
color: Color::WHITE,
},
)
.with_style(Style {
position_type: PositionType::Absolute,
top: Val::Px(5.0),
left: Val::Px(15.0),
..default()
}),
);
commands.spawn(TextBundle::from_section(
"This text is very long, has a limited width, is centered, is positioned in the top right and is also colored pink.",
TextStyle {
font: font.clone(),
font_size: 50.0,
color: Color::rgb(0.8, 0.2, 0.7),
},
)
.with_text_alignment(TextAlignment::Center)
.with_style(Style {
position_type: PositionType::Absolute,
top: Val::Px(5.0),
right: Val::Px(15.0),
max_width: Val::Px(400.),
..default()
})
);
commands.spawn((
TextBundle::from_sections([
TextSection::new(
"This text changes in the bottom right",
TextStyle {
font: font.clone(),
font_size: 30.0,
color: Color::WHITE,
},
),
TextSection::new(
"\nThis text changes in the bottom right - ",
TextStyle {
font: font.clone(),
font_size: 30.0,
color: Color::RED,
},
),
TextSection::from_style(TextStyle {
font: font.clone(),
font_size: 30.0,
color: Color::ORANGE_RED,
}),
TextSection::new(
" fps, ",
TextStyle {
font: font.clone(),
font_size: 30.0,
color: Color::YELLOW,
},
),
TextSection::from_style(TextStyle {
font: font.clone(),
font_size: 30.0,
color: Color::GREEN,
}),
TextSection::new(
" ms/frame",
TextStyle {
font: font.clone(),
font_size: 30.0,
color: Color::BLUE,
},
),
])
.with_style(Style {
position_type: PositionType::Absolute,
bottom: Val::Px(5.0),
right: Val::Px(15.0),
..default()
}),
TextChanges,
));
commands.spawn(
TextBundle::from_section(
"This\ntext has\nline breaks and also a set width in the bottom left",
TextStyle {
font,
font_size: 50.0,
color: Color::WHITE,
},
)
.with_style(Style {
align_self: AlignSelf::FlexEnd,
position_type: PositionType::Absolute,
bottom: Val::Px(5.0),
left: Val::Px(15.0),
width: Val::Px(200.0),
..default()
}),
);
}
fn change_text_system(
time: Res<Time>,
diagnostics: Res<DiagnosticsStore>,
mut query: Query<&mut Text, With<TextChanges>>,
) {
for mut text in &mut query {
let mut fps = 0.0;
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_smoothed) = frame_time_diagnostic.smoothed() {
frame_time = frame_time_smoothed;
}
}
text.sections[0].value = format!(
"This text changes in the bottom right - {fps:.1} fps, {frame_time:.3} ms/frame",
);
text.sections[2].value = format!("{fps:.1}");
text.sections[4].value = format!("{frame_time:.3}");
}
}