Skip to main content

mittens_engine/engine/ecs/component/
renderer_stats.rs

1use crate::engine::ecs::ComponentId;
2use crate::engine::ecs::component::Component;
3use crate::engine::graphics::CameraTarget;
4
5#[derive(Debug, Clone)]
6pub struct RendererStatsComponent {
7    pub enabled: bool,
8
9    /// Which renderer timing source this stats widget displays.
10    ///
11    /// This is explicit (no scene traversal): set it to `CameraTarget::Window` or
12    /// `CameraTarget::Xr`.
13    pub target: CameraTarget,
14
15    /// Minimum time between text updates.
16    ///
17    /// Updating text is relatively expensive because it rebuilds glyph subtrees.
18    pub update_interval_sec: f32,
19
20    /// Exponential moving-average smoothing factor in $[0, 1]$.
21    ///
22    /// - 0.0: no smoothing
23    /// - 0.9: heavy smoothing (default)
24    pub smoothing: f32,
25
26    /// Text color (RGBA).
27    pub color: [f32; 4],
28
29    /// Whether spawned glyphs should be emissive (unlit).
30    pub emissive: bool,
31
32    // --- runtime-only state (not serialized) ---
33    component_id: Option<ComponentId>,
34    time_since_update_sec: f32,
35    smoothed_fps: Option<f32>,
36
37    // Auto-managed subtree ids.
38    text: Option<ComponentId>,
39    text_color: Option<ComponentId>,
40    text_emissive: Option<ComponentId>,
41}
42
43impl RendererStatsComponent {
44    pub fn new() -> Self {
45        Self {
46            enabled: true,
47            target: CameraTarget::Window,
48            update_interval_sec: 0.25,
49            smoothing: 0.9,
50            color: [1.0, 1.0, 1.0, 1.0],
51            emissive: true,
52
53            component_id: None,
54            time_since_update_sec: 0.0,
55            smoothed_fps: None,
56
57            text: None,
58            text_color: None,
59            text_emissive: None,
60        }
61    }
62
63    pub fn with_camera_target(mut self, target: CameraTarget) -> Self {
64        self.target = target;
65        self
66    }
67
68    pub fn id(&self) -> Option<ComponentId> {
69        self.component_id
70    }
71
72    pub fn accumulate_time(&mut self, dt_sec: f32) {
73        if !self.enabled {
74            return;
75        }
76        self.time_since_update_sec = (self.time_since_update_sec + dt_sec).max(0.0);
77    }
78
79    pub fn should_update(&self) -> bool {
80        if !self.enabled {
81            return false;
82        }
83        self.time_since_update_sec >= self.update_interval_sec.max(0.0)
84    }
85
86    pub fn reset_update_timer(&mut self) {
87        self.time_since_update_sec = 0.0;
88    }
89
90    pub fn smooth_fps(&mut self, fps: f32) -> f32 {
91        let fps = if fps.is_finite() { fps.max(0.0) } else { 0.0 };
92        let s = self.smoothing.clamp(0.0, 1.0);
93        let out = match self.smoothed_fps {
94            None => fps,
95            Some(prev) => prev * s + fps * (1.0 - s),
96        };
97        self.smoothed_fps = Some(out);
98        out
99    }
100
101    pub(crate) fn runtime_subtree_ids_mut(
102        &mut self,
103    ) -> (
104        &mut Option<ComponentId>,
105        &mut Option<ComponentId>,
106        &mut Option<ComponentId>,
107    ) {
108        (
109            &mut self.text,
110            &mut self.text_color,
111            &mut self.text_emissive,
112        )
113    }
114}
115
116impl Default for RendererStatsComponent {
117    fn default() -> Self {
118        Self::new()
119    }
120}
121
122impl Component for RendererStatsComponent {
123    fn name(&self) -> &'static str {
124        "renderer_stats"
125    }
126
127    fn set_id(&mut self, component: ComponentId) {
128        self.component_id = Some(component);
129    }
130
131    fn as_any(&self) -> &dyn std::any::Any {
132        self
133    }
134
135    fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
136        self
137    }
138
139    fn to_mms_ast(
140        &self,
141        _world: &crate::engine::ecs::World,
142    ) -> crate::scripting::ast::ComponentExpression {
143        use crate::engine::ecs::component::ce_helpers::*;
144        let target_str = match self.target {
145            CameraTarget::Window => "Window",
146            CameraTarget::Xr => "Xr",
147        };
148        ce("RendererStats")
149            .with_call("enabled", vec![b(self.enabled)])
150            .with_call("camera_target", vec![s(target_str)])
151            .with_call(
152                "update_interval_sec",
153                vec![num(self.update_interval_sec as f64)],
154            )
155            .with_call("smoothing", vec![num(self.smoothing as f64)])
156            .with_call(
157                "color",
158                vec![array(nums(self.color.iter().map(|&v| v as f64)))],
159            )
160            .with_call("emissive", vec![b(self.emissive)])
161    }
162}