1use crate::core::error::GoudResult;
7use crate::ecs::{Component, Entity, World};
8
9#[cfg(feature = "native")]
10use crate::ecs::InputManager;
11#[cfg(feature = "native")]
12use crate::libs::graphics::backend::opengl::OpenGLBackend;
13#[cfg(feature = "native")]
14use crate::libs::graphics::renderer3d::Renderer3D;
15#[cfg(feature = "native")]
16use crate::libs::graphics::sprite_batch::SpriteBatch;
17#[cfg(feature = "native")]
18use crate::libs::platform::PlatformBackend;
19
20use super::entity_builder::EntityBuilder;
21use super::game_config::{GameConfig, GameContext};
22
23pub struct GoudGame {
38 pub(crate) world: World,
40
41 pub(crate) config: GameConfig,
43
44 pub(crate) context: GameContext,
46
47 pub(crate) initialized: bool,
49
50 #[cfg(feature = "native")]
55 pub(crate) platform: Option<Box<dyn PlatformBackend>>,
56
57 #[cfg(feature = "native")]
59 pub(crate) render_backend: Option<OpenGLBackend>,
60
61 #[cfg(feature = "native")]
63 pub(crate) input_manager: InputManager,
64
65 #[cfg(feature = "native")]
67 pub(crate) sprite_batch: Option<SpriteBatch<OpenGLBackend>>,
68
69 #[cfg(feature = "native")]
71 pub(crate) asset_server: Option<crate::assets::AssetServer>,
72
73 #[cfg(feature = "native")]
75 pub(crate) renderer_3d: Option<Renderer3D>,
76
77 #[cfg(feature = "native")]
79 pub(crate) immediate_state: Option<super::rendering::ImmediateRenderState>,
80}
81
82impl GoudGame {
83 pub fn new(config: GameConfig) -> GoudResult<Self> {
89 let window_size = (config.width, config.height);
90 Ok(Self {
91 world: World::new(),
92 config,
93 context: GameContext::new(window_size),
94 initialized: false,
95 #[cfg(feature = "native")]
96 platform: None,
97 #[cfg(feature = "native")]
98 render_backend: None,
99 #[cfg(feature = "native")]
100 input_manager: InputManager::default(),
101 #[cfg(feature = "native")]
102 sprite_batch: None,
103 #[cfg(feature = "native")]
104 asset_server: None,
105 #[cfg(feature = "native")]
106 renderer_3d: None,
107 #[cfg(feature = "native")]
108 immediate_state: None,
109 })
110 }
111
112 pub fn default_game() -> GoudResult<Self> {
114 Self::new(GameConfig::default())
115 }
116
117 #[cfg(feature = "native")]
126 pub fn with_platform(config: GameConfig) -> GoudResult<Self> {
127 use crate::libs::platform::glfw_platform::GlfwPlatform;
128 use crate::libs::platform::WindowConfig;
129
130 let window_config = WindowConfig {
131 width: config.width,
132 height: config.height,
133 title: config.title.clone(),
134 vsync: config.vsync,
135 resizable: config.resizable,
136 };
137
138 let platform = GlfwPlatform::new(&window_config)?;
139 let window_size = (config.width, config.height);
140
141 Ok(Self {
142 world: World::new(),
143 config,
144 context: GameContext::new(window_size),
145 initialized: false,
146 platform: Some(Box::new(platform)),
147 render_backend: None,
148 input_manager: InputManager::default(),
149 sprite_batch: None,
150 asset_server: None,
151 renderer_3d: None,
152 immediate_state: None,
153 })
154 }
155
156 #[inline]
158 pub fn world(&self) -> &World {
159 &self.world
160 }
161
162 #[inline]
164 pub fn world_mut(&mut self) -> &mut World {
165 &mut self.world
166 }
167
168 #[inline]
170 pub fn spawn(&mut self) -> EntityBuilder<'_> {
171 EntityBuilder::new(&mut self.world)
172 }
173
174 #[inline]
176 pub fn spawn_empty(&mut self) -> Entity {
177 self.world.spawn_empty()
178 }
179
180 #[inline]
182 pub fn spawn_batch(&mut self, count: usize) -> Vec<Entity> {
183 self.world.spawn_batch(count)
184 }
185
186 #[inline]
188 pub fn despawn(&mut self, entity: Entity) -> bool {
189 self.world.despawn(entity)
190 }
191
192 #[inline]
194 pub fn get<T: Component>(&self, entity: Entity) -> Option<&T> {
195 self.world.get::<T>(entity)
196 }
197
198 #[inline]
200 pub fn get_mut<T: Component>(&mut self, entity: Entity) -> Option<&mut T> {
201 self.world.get_mut::<T>(entity)
202 }
203
204 #[inline]
206 pub fn insert<T: Component>(&mut self, entity: Entity, component: T) {
207 self.world.insert(entity, component);
208 }
209
210 #[inline]
212 pub fn remove<T: Component>(&mut self, entity: Entity) -> Option<T> {
213 self.world.remove::<T>(entity)
214 }
215
216 #[inline]
218 pub fn has<T: Component>(&self, entity: Entity) -> bool {
219 self.world.has::<T>(entity)
220 }
221
222 #[inline]
224 pub fn entity_count(&self) -> usize {
225 self.world.entity_count()
226 }
227
228 #[inline]
230 pub fn is_alive(&self, entity: Entity) -> bool {
231 self.world.is_alive(entity)
232 }
233
234 #[inline]
236 pub fn config(&self) -> &GameConfig {
237 &self.config
238 }
239
240 #[inline]
242 pub fn title(&self) -> &str {
243 &self.config.title
244 }
245
246 #[inline]
248 pub fn window_size(&self) -> (u32, u32) {
249 (self.config.width, self.config.height)
250 }
251
252 pub fn run<F>(&mut self, mut update: F)
254 where
255 F: FnMut(&mut GameContext, &mut World),
256 {
257 self.initialized = true;
258
259 let frame_time = if self.config.target_fps > 0 {
261 1.0 / self.config.target_fps as f32
262 } else {
263 1.0 / 60.0 };
265
266 while self.context.is_running() {
269 self.context.update(frame_time);
270 update(&mut self.context, &mut self.world);
271
272 if self.context.frame_count() > 10000 {
274 break;
275 }
276 }
277 }
278
279 pub fn update_frame<F>(&mut self, delta_time: f32, mut update: F)
281 where
282 F: FnMut(&mut GameContext, &mut World),
283 {
284 self.context.update(delta_time);
285 update(&mut self.context, &mut self.world);
286 }
287
288 #[inline]
290 pub fn frame_count(&self) -> u64 {
291 self.context.frame_count()
292 }
293
294 #[inline]
296 pub fn total_time(&self) -> f32 {
297 self.context.total_time()
298 }
299
300 #[inline]
302 pub fn fps(&self) -> f32 {
303 self.context.fps()
304 }
305
306 #[inline]
308 pub fn is_initialized(&self) -> bool {
309 self.initialized
310 }
311}
312
313impl Default for GoudGame {
314 fn default() -> Self {
315 Self::new(GameConfig::default()).expect("Failed to create default GoudGame")
316 }
317}
318
319impl std::fmt::Debug for GoudGame {
320 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
321 f.debug_struct("GoudGame")
322 .field("config", &self.config)
323 .field("entity_count", &self.world.entity_count())
324 .field("initialized", &self.initialized)
325 .finish()
326 }
327}
328
329#[cfg(test)]
334mod tests {
335 use super::*;
336 use crate::core::math::Vec2;
337 use crate::sdk::components::{GlobalTransform2D, Transform2D};
338
339 #[test]
340 fn test_goud_game_new() {
341 let game = GoudGame::new(GameConfig::default()).unwrap();
342 assert_eq!(game.entity_count(), 0);
343 assert!(!game.is_initialized());
344 }
345
346 #[test]
347 fn test_goud_game_default() {
348 let game = GoudGame::default();
349 assert_eq!(game.title(), "GoudEngine Game");
350 assert_eq!(game.window_size(), (800, 600));
351 }
352
353 #[test]
354 fn test_goud_game_spawn() {
355 let mut game = GoudGame::default();
356
357 let entity = game
358 .spawn()
359 .with(Transform2D::from_position(Vec2::new(100.0, 100.0)))
360 .build();
361
362 assert!(game.is_alive(entity));
363 assert!(game.has::<Transform2D>(entity));
364 assert_eq!(game.entity_count(), 1);
365 }
366
367 #[test]
368 fn test_goud_game_spawn_empty() {
369 let mut game = GoudGame::default();
370
371 let entity = game.spawn_empty();
372 assert!(game.is_alive(entity));
373 assert_eq!(game.entity_count(), 1);
374 }
375
376 #[test]
377 fn test_goud_game_spawn_batch() {
378 let mut game = GoudGame::default();
379
380 let entities = game.spawn_batch(100);
381 assert_eq!(entities.len(), 100);
382 assert_eq!(game.entity_count(), 100);
383
384 for entity in entities {
385 assert!(game.is_alive(entity));
386 }
387 }
388
389 #[test]
390 fn test_goud_game_despawn() {
391 let mut game = GoudGame::default();
392
393 let entity = game.spawn_empty();
394 assert!(game.is_alive(entity));
395
396 let despawned = game.despawn(entity);
397 assert!(despawned);
398 assert!(!game.is_alive(entity));
399 }
400
401 #[test]
402 fn test_goud_game_component_operations() {
403 let mut game = GoudGame::default();
404 let entity = game.spawn_empty();
405
406 game.insert(entity, Transform2D::from_position(Vec2::new(5.0, 10.0)));
408 assert!(game.has::<Transform2D>(entity));
409
410 let transform = game.get::<Transform2D>(entity).unwrap();
412 assert_eq!(transform.position, Vec2::new(5.0, 10.0));
413
414 {
416 let transform = game.get_mut::<Transform2D>(entity).unwrap();
417 transform.position.x = 100.0;
418 }
419 let transform = game.get::<Transform2D>(entity).unwrap();
420 assert_eq!(transform.position.x, 100.0);
421
422 let removed = game.remove::<Transform2D>(entity);
424 assert!(removed.is_some());
425 assert!(!game.has::<Transform2D>(entity));
426 }
427
428 #[test]
429 fn test_goud_game_world_access() {
430 let mut game = GoudGame::default();
431
432 assert_eq!(game.world().entity_count(), 0);
434
435 let entity = game.world_mut().spawn_empty();
437 assert!(game.world().is_alive(entity));
438 }
439
440 #[test]
441 fn test_goud_game_update_frame() {
442 let mut game = GoudGame::default();
443 let mut update_count = 0;
444
445 game.update_frame(0.016, |_ctx, _world| {
446 update_count += 1;
447 });
448
449 assert_eq!(update_count, 1);
450 assert_eq!(game.frame_count(), 1);
451 }
452
453 #[test]
454 fn test_goud_game_run_with_quit() {
455 let mut game = GoudGame::default();
456 let mut frame_count = 0;
457
458 game.run(|ctx, _world| {
459 frame_count += 1;
460 if frame_count >= 5 {
461 ctx.quit();
462 }
463 });
464
465 assert_eq!(frame_count, 5);
466 assert!(game.is_initialized());
467 }
468
469 #[test]
470 fn test_goud_game_debug() {
471 let game = GoudGame::default();
472 let debug = format!("{:?}", game);
473
474 assert!(debug.contains("GoudGame"));
475 assert!(debug.contains("config"));
476 assert!(debug.contains("entity_count"));
477 }
478
479 #[test]
484 fn test_full_game_workflow() {
485 let mut game = GoudGame::new(GameConfig::new("Test", 800, 600)).unwrap();
487
488 let player = game
490 .spawn()
491 .with(Transform2D::from_position(Vec2::new(400.0, 300.0)))
492 .with(GlobalTransform2D::IDENTITY)
493 .build();
494
495 let mut enemies = Vec::new();
497 for i in 0..10 {
498 let enemy = game
499 .spawn()
500 .with(Transform2D::from_position(Vec2::new(
501 i as f32 * 50.0,
502 100.0,
503 )))
504 .build();
505 enemies.push(enemy);
506 }
507
508 assert_eq!(game.entity_count(), 11); let mut player_moved = false;
512 game.run(|ctx, world| {
513 if let Some(transform) = world.get_mut::<Transform2D>(player) {
515 transform.position.x += 10.0 * ctx.delta_time();
516 player_moved = true;
517 }
518
519 if ctx.frame_count() >= 3 {
521 ctx.quit();
522 }
523 });
524
525 assert!(player_moved);
526 assert!(game.is_alive(player));
527 }
528}