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
55impl Default for GameConfig {
56    fn default() -> Self {
57        Self {
58            title: "GoudEngine Game".to_string(),
59            width: 800,
60            height: 600,
61            vsync: true,
62            fullscreen: false,
63            resizable: true,
64            target_fps: 60,
65            debug_rendering: false,
66        }
67    }
68}
69
70impl GameConfig {
71    /// Creates a new configuration with the given title and dimensions.
72    ///
73    /// # Example
74    ///
75    /// ```rust
76    /// use goud_engine::sdk::GameConfig;
77    ///
78    /// let config = GameConfig::new("My Game", 800, 600);
79    /// ```
80    pub fn new(title: impl Into<String>, width: u32, height: u32) -> Self {
81        Self {
82            title: title.into(),
83            width,
84            height,
85            ..Default::default()
86        }
87    }
88
89    /// Sets the window title.
90    pub fn with_title(mut self, title: impl Into<String>) -> Self {
91        self.title = title.into();
92        self
93    }
94
95    /// Sets the window dimensions.
96    pub fn with_size(mut self, width: u32, height: u32) -> Self {
97        self.width = width;
98        self.height = height;
99        self
100    }
101
102    /// Enables or disables vsync.
103    pub fn with_vsync(mut self, enabled: bool) -> Self {
104        self.vsync = enabled;
105        self
106    }
107
108    /// Enables or disables fullscreen mode.
109    pub fn with_fullscreen(mut self, enabled: bool) -> Self {
110        self.fullscreen = enabled;
111        self
112    }
113
114    /// Sets the target FPS (0 for unlimited).
115    pub fn with_target_fps(mut self, fps: u32) -> Self {
116        self.target_fps = fps;
117        self
118    }
119}
120
121// =============================================================================
122// Game Context (passed to update callback)
123// =============================================================================
124
125/// Runtime context passed to the game update callback.
126///
127/// This struct provides access to frame timing, input state, and other
128/// runtime information needed during the game loop.
129///
130/// # Example
131///
132/// ```rust,ignore
133/// game.run(|ctx| {
134///     let dt = ctx.delta_time();
135///     let fps = ctx.fps();
136///
137///     // Move something based on time
138///     position.x += velocity * dt;
139/// });
140/// ```
141#[derive(Debug)]
142pub struct GameContext {
143    /// Time elapsed since last frame in seconds.
144    delta_time: f32,
145
146    /// Total time elapsed since game start in seconds.
147    total_time: f32,
148
149    /// Current frames per second.
150    fps: f32,
151
152    /// Current frame number.
153    frame_count: u64,
154
155    /// Window dimensions.
156    window_size: (u32, u32),
157
158    /// Whether the game should continue running.
159    running: bool,
160}
161
162impl GameContext {
163    /// Creates a new game context with default values.
164    pub(crate) fn new(window_size: (u32, u32)) -> Self {
165        Self {
166            delta_time: 0.0,
167            total_time: 0.0,
168            fps: 0.0,
169            frame_count: 0,
170            window_size,
171            running: true,
172        }
173    }
174
175    /// Returns the time elapsed since the last frame in seconds.
176    ///
177    /// Use this for frame-rate independent movement and animations.
178    ///
179    /// # Example
180    ///
181    /// ```rust,ignore
182    /// // Move at 100 pixels per second regardless of frame rate
183    /// position.x += 100.0 * ctx.delta_time();
184    /// ```
185    #[inline]
186    pub fn delta_time(&self) -> f32 {
187        self.delta_time
188    }
189
190    /// Returns the total time elapsed since game start in seconds.
191    #[inline]
192    pub fn total_time(&self) -> f32 {
193        self.total_time
194    }
195
196    /// Returns the current frames per second.
197    #[inline]
198    pub fn fps(&self) -> f32 {
199        self.fps
200    }
201
202    /// Returns the current frame number (0-indexed).
203    #[inline]
204    pub fn frame_count(&self) -> u64 {
205        self.frame_count
206    }
207
208    /// Returns the window dimensions as (width, height).
209    #[inline]
210    pub fn window_size(&self) -> (u32, u32) {
211        self.window_size
212    }
213
214    /// Returns the window width in pixels.
215    #[inline]
216    pub fn window_width(&self) -> u32 {
217        self.window_size.0
218    }
219
220    /// Returns the window height in pixels.
221    #[inline]
222    pub fn window_height(&self) -> u32 {
223        self.window_size.1
224    }
225
226    /// Returns true if the game is still running.
227    #[inline]
228    pub fn is_running(&self) -> bool {
229        self.running
230    }
231
232    /// Signals the game to exit after the current frame.
233    #[inline]
234    pub fn quit(&mut self) {
235        self.running = false;
236    }
237
238    /// Updates the context for a new frame.
239    pub(crate) fn update(&mut self, delta_time: f32) {
240        self.delta_time = delta_time;
241        self.total_time += delta_time;
242        self.frame_count += 1;
243
244        // Simple FPS calculation (could be smoothed)
245        if delta_time > 0.0 {
246            self.fps = 1.0 / delta_time;
247        }
248    }
249}
250
251// =============================================================================
252// Tests
253// =============================================================================
254
255#[cfg(test)]
256mod tests {
257    use super::*;
258
259    // =========================================================================
260    // GameConfig Tests
261    // =========================================================================
262
263    #[test]
264    fn test_game_config_default() {
265        let config = GameConfig::default();
266        assert_eq!(config.title, "GoudEngine Game");
267        assert_eq!(config.width, 800);
268        assert_eq!(config.height, 600);
269        assert!(config.vsync);
270        assert!(!config.fullscreen);
271    }
272
273    #[test]
274    fn test_game_config_new() {
275        let config = GameConfig::new("Test Game", 1920, 1080);
276        assert_eq!(config.title, "Test Game");
277        assert_eq!(config.width, 1920);
278        assert_eq!(config.height, 1080);
279    }
280
281    #[test]
282    fn test_game_config_builder() {
283        let config = GameConfig::default()
284            .with_title("Builder Game")
285            .with_size(640, 480)
286            .with_vsync(false)
287            .with_fullscreen(true)
288            .with_target_fps(144);
289
290        assert_eq!(config.title, "Builder Game");
291        assert_eq!(config.width, 640);
292        assert_eq!(config.height, 480);
293        assert!(!config.vsync);
294        assert!(config.fullscreen);
295        assert_eq!(config.target_fps, 144);
296    }
297
298    // =========================================================================
299    // GameContext Tests
300    // =========================================================================
301
302    #[test]
303    fn test_game_context_new() {
304        let ctx = GameContext::new((800, 600));
305        assert_eq!(ctx.delta_time(), 0.0);
306        assert_eq!(ctx.total_time(), 0.0);
307        assert_eq!(ctx.frame_count(), 0);
308        assert_eq!(ctx.window_size(), (800, 600));
309        assert!(ctx.is_running());
310    }
311
312    #[test]
313    fn test_game_context_update() {
314        let mut ctx = GameContext::new((800, 600));
315        ctx.update(0.016); // ~60 FPS
316
317        assert!((ctx.delta_time() - 0.016).abs() < 0.001);
318        assert!((ctx.total_time() - 0.016).abs() < 0.001);
319        assert_eq!(ctx.frame_count(), 1);
320        assert!((ctx.fps() - 62.5).abs() < 1.0);
321    }
322
323    #[test]
324    fn test_game_context_quit() {
325        let mut ctx = GameContext::new((800, 600));
326        assert!(ctx.is_running());
327
328        ctx.quit();
329        assert!(!ctx.is_running());
330    }
331}