bevy/examples/stress_tests/many_gizmos.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

108 lines
2.8 KiB
Rust

use std::f32::consts::TAU;
use bevy::{
diagnostic::{DiagnosticsStore, FrameTimeDiagnosticsPlugin},
prelude::*,
window::PresentMode,
};
const SYSTEM_COUNT: u32 = 10;
fn main() {
let mut app = App::new();
app.add_plugins(DefaultPlugins.set(WindowPlugin {
primary_window: Some(Window {
title: "Many Debug Lines".to_string(),
present_mode: PresentMode::AutoNoVsync,
..default()
}),
..default()
}))
.add_plugin(FrameTimeDiagnosticsPlugin)
.insert_resource(Config {
line_count: 50_000,
fancy: false,
})
.insert_resource(GizmoConfig {
on_top: false,
..default()
})
.add_systems(Startup, setup)
.add_systems(Update, (input, ui_system));
for _ in 0..SYSTEM_COUNT {
app.add_systems(Update, system);
}
app.run();
}
#[derive(Resource, Debug)]
struct Config {
line_count: u32,
fancy: bool,
}
fn input(mut config: ResMut<Config>, input: Res<Input<KeyCode>>) {
if input.just_pressed(KeyCode::Up) {
config.line_count += 10_000;
}
if input.just_pressed(KeyCode::Down) {
config.line_count = config.line_count.saturating_sub(10_000);
}
if input.just_pressed(KeyCode::Space) {
config.fancy = !config.fancy;
}
}
fn system(config: Res<Config>, time: Res<Time>, mut draw: Gizmos) {
if !config.fancy {
for _ in 0..(config.line_count / SYSTEM_COUNT) {
draw.line(Vec3::NEG_Y, Vec3::Y, Color::BLACK);
}
} else {
for i in 0..(config.line_count / SYSTEM_COUNT) {
let angle = i as f32 / (config.line_count / SYSTEM_COUNT) as f32 * TAU;
let vector = Vec2::from(angle.sin_cos()).extend(time.elapsed_seconds().sin());
let start_color = Color::rgb(vector.x, vector.z, 0.5);
let end_color = Color::rgb(-vector.z, -vector.y, 0.5);
draw.line_gradient(vector, -vector, start_color, end_color);
}
}
}
fn setup(mut commands: Commands) {
warn!(include_str!("warning_string.txt"));
commands.spawn(Camera3dBundle {
transform: Transform::from_xyz(3., 1., 5.).looking_at(Vec3::ZERO, Vec3::Y),
..default()
});
commands.spawn(TextBundle::from_section(
"",
TextStyle {
font_size: 30.,
..default()
},
));
}
fn ui_system(mut query: Query<&mut Text>, config: Res<Config>, diag: Res<DiagnosticsStore>) {
let mut text = query.single_mut();
let Some(fps) = diag.get(FrameTimeDiagnosticsPlugin::FPS).and_then(|fps| fps.smoothed()) else {
return;
};
text.sections[0].value = format!(
"Line count: {}\n\
FPS: {:.0}\n\n\
Controls:\n\
Up/Down: Raise or lower the line count.\n\
Spacebar: Toggle fancy mode.",
config.line_count, fps,
);
}