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}