bevy/crates/bevy_diagnostic/src/log_diagnostics_plugin.rs
Carter Anderson dcc03724a5 Base Sets (#7466)
# Objective

NOTE: This depends on #7267 and should not be merged until #7267 is merged. If you are reviewing this before that is merged, I highly recommend viewing the Base Sets commit instead of trying to find my changes amongst those from #7267.

"Default sets" as described by the [Stageless RFC](https://github.com/bevyengine/rfcs/pull/45) have some [unfortunate consequences](https://github.com/bevyengine/bevy/discussions/7365).

## Solution

This adds "base sets" as a variant of `SystemSet`:

A set is a "base set" if `SystemSet::is_base` returns `true`. Typically this will be opted-in to using the `SystemSet` derive:

```rust
#[derive(SystemSet, Clone, Hash, Debug, PartialEq, Eq)]
#[system_set(base)]
enum MyBaseSet {
  A,
  B,
}
``` 

**Base sets are exclusive**: a system can belong to at most one "base set". Adding a system to more than one will result in an error. When possible we fail immediately during system-config-time with a nice file + line number. For the more nested graph-ey cases, this will fail at the final schedule build. 

**Base sets cannot belong to other sets**: this is where the word "base" comes from

Systems and Sets can only be added to base sets using `in_base_set`. Calling `in_set` with a base set will fail. As will calling `in_base_set` with a normal set.

```rust
app.add_system(foo.in_base_set(MyBaseSet::A))
       // X must be a normal set ... base sets cannot be added to base sets
       .configure_set(X.in_base_set(MyBaseSet::A))
```

Base sets can still be configured like normal sets:

```rust
app.add_system(MyBaseSet::B.after(MyBaseSet::Ap))
``` 

The primary use case for base sets is enabling a "default base set":

```rust
schedule.set_default_base_set(CoreSet::Update)
  // this will belong to CoreSet::Update by default
  .add_system(foo)
  // this will override the default base set with PostUpdate
  .add_system(bar.in_base_set(CoreSet::PostUpdate))
```

This allows us to build apis that work by default in the standard Bevy style. This is a rough analog to the "default stage" model, but it use the new "stageless sets" model instead, with all of the ordering flexibility (including exclusive systems) that it provides.

---

## Changelog

- Added "base sets" and ported CoreSet to use them.

## Migration Guide

TODO
2023-02-06 03:10:08 +00:00

133 lines
4.3 KiB
Rust

use super::{Diagnostic, DiagnosticId, Diagnostics};
use bevy_app::prelude::*;
use bevy_ecs::prelude::*;
use bevy_log::{debug, info};
use bevy_time::{Time, Timer, TimerMode};
use bevy_utils::Duration;
/// An App Plugin that logs diagnostics to the console
pub struct LogDiagnosticsPlugin {
pub debug: bool,
pub wait_duration: Duration,
pub filter: Option<Vec<DiagnosticId>>,
}
/// State used by the [`LogDiagnosticsPlugin`]
#[derive(Resource)]
struct LogDiagnosticsState {
timer: Timer,
filter: Option<Vec<DiagnosticId>>,
}
impl Default for LogDiagnosticsPlugin {
fn default() -> Self {
LogDiagnosticsPlugin {
debug: false,
wait_duration: Duration::from_secs(1),
filter: None,
}
}
}
impl Plugin for LogDiagnosticsPlugin {
fn build(&self, app: &mut App) {
app.insert_resource(LogDiagnosticsState {
timer: Timer::new(self.wait_duration, TimerMode::Repeating),
filter: self.filter.clone(),
});
if self.debug {
app.add_system(Self::log_diagnostics_debug_system.in_base_set(CoreSet::PostUpdate));
} else {
app.add_system(Self::log_diagnostics_system.in_base_set(CoreSet::PostUpdate));
}
}
}
impl LogDiagnosticsPlugin {
pub fn filtered(filter: Vec<DiagnosticId>) -> 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;
}
}
info!(
target: "bevy diagnostic",
"{name:<name_width$}: {value:>.6}{suffix:}",
name = diagnostic.name,
suffix = diagnostic.suffix,
name_width = crate::MAX_DIAGNOSTIC_NAME_WIDTH,
);
}
}
fn log_diagnostics_system(
mut state: ResMut<LogDiagnosticsState>,
time: Res<Time>,
diagnostics: Res<Diagnostics>,
) {
if state.timer.tick(time.raw_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);
}
}
}
}
fn log_diagnostics_debug_system(
mut state: ResMut<LogDiagnosticsState>,
time: Res<Time>,
diagnostics: Res<Diagnostics>,
) {
if state.timer.tick(time.raw_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);
}
}
}
}
}