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}