thomas 0.2.4

An ECS game engine.
Documentation
use crate::{
    Alignment, EngineStats, GameCommand, GameCommandsArg, Identity, IntCoords2d, Query,
    QueryResultList, Rgb, System, SystemsGenerator, Text, Timer, UiAnchor, EVENT_BEFORE_UPDATE,
    EVENT_INIT, EVENT_UPDATE,
};

pub const FPS_TRACKER_ID: &str = "thomas_fps_tracking_tag";

pub struct EngineAnalysisOptions {
    /// Whether to include UI on the screen that will display current analysis stats.
    pub include_tracking_ui: bool,
}

/// Generator responsible for setting up and performing engine performance analysis. To use the data generated by the
/// analysis, you can query for the `EngineStats` component.
pub struct EngineAnalysisSystemsGenerator {
    options: EngineAnalysisOptions,
}
impl EngineAnalysisSystemsGenerator {
    pub fn new(options: EngineAnalysisOptions) -> Self {
        Self { options }
    }
}
impl SystemsGenerator for EngineAnalysisSystemsGenerator {
    fn generate(&self) -> Vec<(&'static str, System)> {
        let mut systems = vec![
            (
                EVENT_INIT,
                System::new(vec![], |_, commands| {
                    commands
                        .borrow_mut()
                        .issue(GameCommand::AddEntity(vec![Box::new(EngineStats {
                            fps: 0,
                            frame_timer: Timer::new(),
                            frame_counter: 0,
                        })]));
                }),
            ),
            (
                EVENT_BEFORE_UPDATE,
                System::new(vec![Query::new().has::<EngineStats>()], gather_stats),
            ),
        ];

        if self.options.include_tracking_ui {
            systems.append(&mut vec![
                (
                    EVENT_INIT,
                    System::new(vec![], |_, commands| {
                        commands.borrow_mut().issue(GameCommand::AddEntity(vec![
                            Box::new(Text {
                                anchor: UiAnchor::TopLeft,
                                justification: Alignment::Left,
                                offset: IntCoords2d::zero(),
                                value: String::from(""),
                                foreground_color: Some(Rgb::magenta()),
                                background_color: None,
                            }),
                            Box::new(Identity {
                                id: String::from(FPS_TRACKER_ID),
                                name: String::from("FPS Tracking Tag"),
                            }),
                        ]));
                    }),
                ),
                (
                    EVENT_UPDATE,
                    System::new(
                        vec![
                            Query::new()
                                .has::<Text>()
                                .has_where::<Identity>(|id| id.id == FPS_TRACKER_ID),
                            Query::new().has::<EngineStats>(),
                        ],
                        update_tracking_ui,
                    ),
                ),
            ])
        }

        systems
    }
}

fn gather_stats(results: Vec<QueryResultList>, _: GameCommandsArg) {
    if let [engine_stats_results, ..] = &results[..] {
        let mut engine_stats = engine_stats_results.get_only_mut::<EngineStats>();

        engine_stats.frame_counter += 1;

        if !engine_stats.frame_timer.is_running() {
            engine_stats.frame_timer.start();
        }

        if engine_stats.frame_timer.elapsed_millis() >= 1000 {
            engine_stats.fps = engine_stats.frame_counter;
            engine_stats.frame_counter = 0;

            engine_stats.frame_timer.restart();
        }
    }
}

fn update_tracking_ui(results: Vec<QueryResultList>, _: GameCommandsArg) {
    if let [fps_tag_results, engine_stats_results, ..] = &results[..] {
        let mut fps_tag = fps_tag_results.get_only_mut::<Text>();
        let stats = engine_stats_results.get_only::<EngineStats>();

        fps_tag.value = format!("FPS: {}", stats.fps);
    }
}