1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
//! A simple library to provide an on-screen FPS display for Bevy projects.

use bevy::{
    prelude::*,
    utils::{Duration, Instant},
};

/// The plugin
#[derive(Debug, Default, Clone)]
pub struct ScreenDiagsPlugin {
    /// The configurable settings for the plugin.
    pub settings: ScreenDiagsSettings,
}

impl Plugin for ScreenDiagsPlugin {
    fn build(&self, app: &mut AppBuilder) {
        app.add_resource(self.settings.clone())
            .add_startup_system(setup.system())
            .add_system(update.system());
    }
}

/// The settings
#[derive(Debug, Clone)]
pub struct ScreenDiagsSettings {
    /// The interval between screen updates. A balance between being responsive
    /// and easy to read. Defaults to 1 second.
    pub interval: Duration,
    /// Whether the FPS display is enabled.  Any change in status
    /// will be responded to at the end of the `interval`. Defaults to true.
    pub enabled: bool,
}

impl Default for ScreenDiagsSettings {
    fn default() -> Self {
        ScreenDiagsSettings {
            interval: Duration::from_secs(1),
            enabled: true,
        }
    }
}

/// The marker for the text to be updated, and the container for the state
#[derive(Debug, Default, Clone)]
struct ScreenDiagsText {
    state: Option<ScreenDiagsState>,
}

/// The state to be updated
#[derive(Debug, Clone, Copy)]
struct ScreenDiagsState {
    last_time: Instant,
    frame_count: u32,
}

/// Update the FPS state and, if sufficient time has passed, change the display.
fn update(
    settings: Res<ScreenDiagsSettings>,
    time: Res<Time>,
    mut query: Query<(&mut Text, &mut ScreenDiagsText)>,
) {
    let now: Instant = time.last_update().unwrap_or_else(|| time.startup());
    for (mut text, mut marker) in query.iter_mut() {
        if let Some(state) = marker.state.as_mut() {
            state.frame_count += 1;

            let so_far = now - state.last_time;
            if so_far > settings.interval {
                if settings.enabled {
                    let fps = state.frame_count as f64 / so_far.as_secs_f64();
                    text.value = format!("FPS: {:4.0}", fps);
                } else {
                    text.value = "".to_owned();
                }

                marker.state = None;
            }
        }
        if marker.state.is_none() {
            marker.state = Some(ScreenDiagsState {
                last_time: now,
                frame_count: 0,
            });
        }
    }
}

/// Set up the UI camera, the text element and, attached to it, the plugin state.
fn setup(commands: &mut Commands, mut assets: ResMut<Assets<Font>>) {
    // The font file to use is included in this crate so you don't need to access the file at runtime.
    // Here we load it as an asset.
    let font_bytes = include_bytes!("../assets/fonts/FiraSans-Bold.ttf").to_vec();
    let font_struct = Font::try_from_bytes(font_bytes).expect("Font should be present and valid");
    let font = assets.add(font_struct);

    commands
        // The UI camera is required to show the text. It can coexist with other cameras.
        .spawn(CameraUiBundle::default())
        // The text is not currently configurable, but could be.
        .spawn(TextBundle {
            style: Style {
                align_self: AlignSelf::FlexEnd,
                ..Default::default()
            },
            text: Text {
                value: "FPS: ...".to_owned(),
                font,
                style: TextStyle {
                    font_size: 32.0,
                    color: Color::WHITE,
                    ..Default::default()
                },
            },
            ..Default::default()
        })
        // The state is not set up initially. This is to avoid the start-up time being counted as the first frame.
        .with(ScreenDiagsText::default());
}