use crate::config::{STRING_FORMAT, UPDATE_INTERVAL};
use bevy::{
diagnostic::{DiagnosticsStore, FrameTimeDiagnosticsPlugin},
prelude::*,
};
use config::{STRING_INITIAL, STRING_MISSING};
use std::fmt::Write;
mod config {
use bevy::prelude::*;
use std::time::Duration;
pub const FONT_SIZE: f32 = 32.;
pub const FONT_COLOR: Color = Color::WHITE;
pub const UPDATE_INTERVAL: Duration = Duration::from_secs(1);
pub const STRING_FORMAT: &str = "FPS: ";
pub const STRING_INITIAL: &str = "FPS: ...";
pub const STRING_MISSING: &str = "FPS: ???";
}
pub struct FpsCounterPlugin;
impl Plugin for FpsCounterPlugin {
fn build(&self, app: &mut App) {
app.add_plugins(FrameTimeDiagnosticsPlugin)
.add_systems(Startup, spawn_text)
.add_systems(Update, update)
.init_resource::<FpsCounter>();
}
}
#[derive(Resource)]
pub struct FpsCounter {
pub timer: Timer,
pub update_now: bool,
}
impl Default for FpsCounter {
fn default() -> Self {
Self {
timer: Timer::new(UPDATE_INTERVAL, TimerMode::Repeating),
update_now: true,
}
}
}
impl FpsCounter {
pub fn enable(&mut self) {
self.timer.unpause();
self.update_now = true;
}
pub fn disable(&mut self) {
self.timer.pause();
self.update_now = true;
}
pub fn is_enabled(&self) -> bool {
!self.timer.paused()
}
}
#[derive(Component)]
pub struct FpsCounterText;
fn update(
time: Res<Time>,
diagnostics: Res<DiagnosticsStore>,
state_resources: Option<ResMut<FpsCounter>>,
mut text_query: Query<&mut Text, With<FpsCounterText>>,
) {
let Some(mut state) = state_resources else {
return;
};
if !(state.update_now || state.timer.tick(time.delta()).just_finished()) {
return;
}
if state.timer.paused() {
for mut text in text_query.iter_mut() {
let value: &mut String = &mut text.sections[0].value;
value.clear();
}
} else {
let fps_dialog: Option<f64> = extract_fps(&diagnostics);
for mut text in text_query.iter_mut() {
let value: &mut String = &mut text.sections[0].value;
value.clear();
if let Some(fps) = fps_dialog {
write!(value, "{}{:.0}", STRING_FORMAT, fps).expect("Failed to write");
} else {
value.clear();
write!(value, "{}", STRING_MISSING).expect("Failed to write");
}
}
}
}
fn extract_fps(diagnostics: &Res<DiagnosticsStore>) -> Option<f64> {
diagnostics
.get(&bevy::diagnostic::FrameTimeDiagnosticsPlugin::FPS)
.and_then(|fps| fps.average())
}
fn spawn_text(mut commands: Commands) {
commands
.spawn(TextBundle {
text: Text {
sections: vec![TextSection {
value: STRING_INITIAL.to_string(),
style: TextStyle {
font_size: config::FONT_SIZE,
color: config::FONT_COLOR,
..default()
},
}],
..default()
},
..default()
})
.insert(FpsCounterText);
}