Skip to main content

goud_engine/sdk/
game_config.rs

1//! Game configuration and runtime context types.
2//!
3//! Contains [`GameConfig`] for initialization settings and [`GameContext`]
4//! for per-frame runtime state passed to update callbacks.
5
6// =============================================================================
7// Game Configuration
8// =============================================================================
9
10/// Configuration for creating a GoudGame instance.
11///
12/// This struct holds all the settings needed to initialize the game engine,
13/// including window properties, rendering options, and engine settings.
14///
15/// # Example
16///
17/// ```rust
18/// use goud_engine::sdk::GameConfig;
19///
20/// let config = GameConfig {
21///     title: "My Awesome Game".to_string(),
22///     width: 1280,
23///     height: 720,
24///     vsync: true,
25///     ..Default::default()
26/// };
27/// ```
28#[derive(Debug, Clone)]
29pub struct GameConfig {
30    /// Window title displayed in the title bar.
31    pub title: String,
32
33    /// Window width in pixels.
34    pub width: u32,
35
36    /// Window height in pixels.
37    pub height: u32,
38
39    /// Enable vertical sync to prevent screen tearing.
40    pub vsync: bool,
41
42    /// Enable fullscreen mode.
43    pub fullscreen: bool,
44
45    /// Enable window resizing.
46    pub resizable: bool,
47
48    /// Target frames per second (0 = unlimited).
49    pub target_fps: u32,
50
51    /// Enable debug rendering (collision boxes, etc.).
52    pub debug_rendering: bool,
53
54    /// Show the FPS stats overlay.
55    pub show_fps_overlay: bool,
56
57    /// How often (in seconds) the FPS overlay recomputes statistics.
58    pub fps_update_interval: f32,
59}
60
61impl Default for GameConfig {
62    fn default() -> Self {
63        Self {
64            title: "GoudEngine Game".to_string(),
65            width: 800,
66            height: 600,
67            vsync: true,
68            fullscreen: false,
69            resizable: true,
70            target_fps: 60,
71            debug_rendering: false,
72            show_fps_overlay: false,
73            fps_update_interval: 0.5,
74        }
75    }
76}
77
78impl GameConfig {
79    /// Creates a new configuration with the given title and dimensions.
80    ///
81    /// # Example
82    ///
83    /// ```rust
84    /// use goud_engine::sdk::GameConfig;
85    ///
86    /// let config = GameConfig::new("My Game", 800, 600);
87    /// ```
88    pub fn new(title: impl Into<String>, width: u32, height: u32) -> Self {
89        Self {
90            title: title.into(),
91            width,
92            height,
93            ..Default::default()
94        }
95    }
96
97    /// Sets the window title.
98    pub fn with_title(mut self, title: impl Into<String>) -> Self {
99        self.title = title.into();
100        self
101    }
102
103    /// Sets the window dimensions.
104    pub fn with_size(mut self, width: u32, height: u32) -> Self {
105        self.width = width;
106        self.height = height;
107        self
108    }
109
110    /// Enables or disables vsync.
111    pub fn with_vsync(mut self, enabled: bool) -> Self {
112        self.vsync = enabled;
113        self
114    }
115
116    /// Enables or disables fullscreen mode.
117    pub fn with_fullscreen(mut self, enabled: bool) -> Self {
118        self.fullscreen = enabled;
119        self
120    }
121
122    /// Sets the target FPS (0 for unlimited).
123    pub fn with_target_fps(mut self, fps: u32) -> Self {
124        self.target_fps = fps;
125        self
126    }
127
128    /// Enables or disables the FPS stats overlay.
129    pub fn with_fps_overlay(mut self, enabled: bool) -> Self {
130        self.show_fps_overlay = enabled;
131        self
132    }
133
134    /// Sets how often (in seconds) the FPS overlay recomputes statistics.
135    pub fn with_fps_update_interval(mut self, interval: f32) -> Self {
136        self.fps_update_interval = interval;
137        self
138    }
139}
140
141// =============================================================================
142// Game Context (passed to update callback)
143// =============================================================================
144
145/// Runtime context passed to the game update callback.
146///
147/// This struct provides access to frame timing, input state, and other
148/// runtime information needed during the game loop.
149///
150/// # Example
151///
152/// ```rust,ignore
153/// game.run(|ctx| {
154///     let dt = ctx.delta_time();
155///     let fps = ctx.fps();
156///
157///     // Move something based on time
158///     position.x += velocity * dt;
159/// });
160/// ```
161#[derive(Debug)]
162pub struct GameContext {
163    /// Time elapsed since last frame in seconds.
164    delta_time: f32,
165
166    /// Total time elapsed since game start in seconds.
167    total_time: f32,
168
169    /// Current frames per second.
170    fps: f32,
171
172    /// Current frame number.
173    frame_count: u64,
174
175    /// Window dimensions.
176    window_size: (u32, u32),
177
178    /// Whether the game should continue running.
179    running: bool,
180}
181
182impl GameContext {
183    /// Creates a new game context with default values.
184    pub(crate) fn new(window_size: (u32, u32)) -> Self {
185        Self {
186            delta_time: 0.0,
187            total_time: 0.0,
188            fps: 0.0,
189            frame_count: 0,
190            window_size,
191            running: true,
192        }
193    }
194
195    /// Returns the time elapsed since the last frame in seconds.
196    ///
197    /// Use this for frame-rate independent movement and animations.
198    ///
199    /// # Example
200    ///
201    /// ```rust,ignore
202    /// // Move at 100 pixels per second regardless of frame rate
203    /// position.x += 100.0 * ctx.delta_time();
204    /// ```
205    #[inline]
206    pub fn delta_time(&self) -> f32 {
207        self.delta_time
208    }
209
210    /// Returns the total time elapsed since game start in seconds.
211    #[inline]
212    pub fn total_time(&self) -> f32 {
213        self.total_time
214    }
215
216    /// Returns the current frames per second.
217    #[inline]
218    pub fn fps(&self) -> f32 {
219        self.fps
220    }
221
222    /// Returns the current frame number (0-indexed).
223    #[inline]
224    pub fn frame_count(&self) -> u64 {
225        self.frame_count
226    }
227
228    /// Returns the window dimensions as (width, height).
229    #[inline]
230    pub fn window_size(&self) -> (u32, u32) {
231        self.window_size
232    }
233
234    /// Returns the window width in pixels.
235    #[inline]
236    pub fn window_width(&self) -> u32 {
237        self.window_size.0
238    }
239
240    /// Returns the window height in pixels.
241    #[inline]
242    pub fn window_height(&self) -> u32 {
243        self.window_size.1
244    }
245
246    /// Returns true if the game is still running.
247    #[inline]
248    pub fn is_running(&self) -> bool {
249        self.running
250    }
251
252    /// Signals the game to exit after the current frame.
253    #[inline]
254    pub fn quit(&mut self) {
255        self.running = false;
256    }
257
258    /// Updates the context for a new frame.
259    pub(crate) fn update(&mut self, delta_time: f32) {
260        self.delta_time = delta_time;
261        self.total_time += delta_time;
262        self.frame_count += 1;
263
264        // Simple FPS calculation (could be smoothed)
265        if delta_time > 0.0 {
266            self.fps = 1.0 / delta_time;
267        }
268    }
269}
270
271// =============================================================================
272// Tests
273// =============================================================================
274
275#[cfg(test)]
276mod tests {
277    use super::*;
278
279    // =========================================================================
280    // GameConfig Tests
281    // =========================================================================
282
283    #[test]
284    fn test_game_config_default() {
285        let config = GameConfig::default();
286        assert_eq!(config.title, "GoudEngine Game");
287        assert_eq!(config.width, 800);
288        assert_eq!(config.height, 600);
289        assert!(config.vsync);
290        assert!(!config.fullscreen);
291    }
292
293    #[test]
294    fn test_game_config_new() {
295        let config = GameConfig::new("Test Game", 1920, 1080);
296        assert_eq!(config.title, "Test Game");
297        assert_eq!(config.width, 1920);
298        assert_eq!(config.height, 1080);
299    }
300
301    #[test]
302    fn test_game_config_builder() {
303        let config = GameConfig::default()
304            .with_title("Builder Game")
305            .with_size(640, 480)
306            .with_vsync(false)
307            .with_fullscreen(true)
308            .with_target_fps(144);
309
310        assert_eq!(config.title, "Builder Game");
311        assert_eq!(config.width, 640);
312        assert_eq!(config.height, 480);
313        assert!(!config.vsync);
314        assert!(config.fullscreen);
315        assert_eq!(config.target_fps, 144);
316    }
317
318    // =========================================================================
319    // GameContext Tests
320    // =========================================================================
321
322    #[test]
323    fn test_game_context_new() {
324        let ctx = GameContext::new((800, 600));
325        assert_eq!(ctx.delta_time(), 0.0);
326        assert_eq!(ctx.total_time(), 0.0);
327        assert_eq!(ctx.frame_count(), 0);
328        assert_eq!(ctx.window_size(), (800, 600));
329        assert!(ctx.is_running());
330    }
331
332    #[test]
333    fn test_game_context_update() {
334        let mut ctx = GameContext::new((800, 600));
335        ctx.update(0.016); // ~60 FPS
336
337        assert!((ctx.delta_time() - 0.016).abs() < 0.001);
338        assert!((ctx.total_time() - 0.016).abs() < 0.001);
339        assert_eq!(ctx.frame_count(), 1);
340        assert!((ctx.fps() - 62.5).abs() < 1.0);
341    }
342
343    #[test]
344    fn test_game_context_quit() {
345        let mut ctx = GameContext::new((800, 600));
346        assert!(ctx.is_running());
347
348        ctx.quit();
349        assert!(!ctx.is_running());
350    }
351}