Skip to main content

goud_engine/sdk/game/
instance.rs

1//! [`GoudGame`] struct definition, construction, and core API.
2
3use crate::context_registry::scene::{SceneId, SceneManager};
4use crate::core::error::{GoudError, GoudResult};
5use crate::ecs::{Component, Entity, World};
6
7#[cfg(feature = "native")]
8use crate::ecs::InputManager;
9#[cfg(feature = "native")]
10use crate::libs::graphics::backend::opengl::OpenGLBackend;
11#[cfg(feature = "native")]
12use crate::libs::graphics::renderer3d::Renderer3D;
13#[cfg(feature = "native")]
14use crate::libs::platform::PlatformBackend;
15#[cfg(feature = "native")]
16use crate::rendering::sprite_batch::SpriteBatch;
17
18use crate::core::providers::ProviderRegistry;
19use crate::sdk::debug_overlay::{DebugOverlay, FpsStats};
20use crate::sdk::engine_config::EngineConfig;
21use crate::sdk::entity_builder::EntityBuilder;
22use crate::sdk::game_config::{GameConfig, GameContext};
23
24/// The main game instance managing the ECS world and game loop.
25///
26/// # Example
27///
28/// ```rust
29/// use goud_engine::sdk::{GoudGame, GameConfig};
30/// use goud_engine::sdk::components::Transform2D;
31/// use goud_engine::core::math::Vec2;
32///
33/// let mut game = GoudGame::new(GameConfig::default()).unwrap();
34/// let player = game.spawn()
35///     .with(Transform2D::from_position(Vec2::new(400.0, 300.0)))
36///     .build();
37/// ```
38pub struct GoudGame {
39    /// Manages multiple isolated ECS worlds (scenes).
40    pub(crate) scene_manager: SceneManager,
41
42    /// Game configuration.
43    pub(crate) config: GameConfig,
44
45    /// Runtime context for the game loop.
46    pub(crate) context: GameContext,
47
48    /// Whether the game has been initialized.
49    pub(crate) initialized: bool,
50
51    /// Debug overlay for FPS stats tracking.
52    pub(crate) debug_overlay: DebugOverlay,
53
54    /// Provider registry for subsystem backends (render, physics, audio, input).
55    pub(crate) providers: ProviderRegistry,
56
57    // =========================================================================
58    // Native-only fields (require windowing + OpenGL)
59    // =========================================================================
60    /// Platform backend for window management (GLFW).
61    #[cfg(feature = "native")]
62    pub(crate) platform: Option<Box<dyn PlatformBackend>>,
63
64    /// OpenGL rendering backend.
65    #[cfg(feature = "native")]
66    pub(crate) render_backend: Option<OpenGLBackend>,
67
68    /// Input manager for keyboard/mouse/gamepad state.
69    #[cfg(feature = "native")]
70    pub(crate) input_manager: InputManager,
71
72    /// 2D sprite batch renderer.
73    #[cfg(feature = "native")]
74    pub(crate) sprite_batch: Option<SpriteBatch<OpenGLBackend>>,
75
76    /// Asset server for loading and managing assets.
77    #[cfg(feature = "native")]
78    pub(crate) asset_server: Option<crate::assets::AssetServer>,
79
80    /// 3D renderer for primitives, lighting, and camera.
81    #[cfg(feature = "native")]
82    pub(crate) renderer_3d: Option<Renderer3D>,
83
84    /// GPU resources for immediate-mode sprite/quad rendering.
85    #[cfg(feature = "native")]
86    pub(crate) immediate_state: Option<crate::sdk::rendering::ImmediateRenderState>,
87}
88
89impl GoudGame {
90    /// Creates a new game instance with the given configuration.
91    ///
92    /// This creates a headless game instance suitable for testing and
93    /// non-graphical use. For a windowed game with rendering, use
94    /// [`with_platform`](Self::with_platform) instead.
95    pub fn new(config: GameConfig) -> GoudResult<Self> {
96        let window_size = (config.width, config.height);
97        let mut debug_overlay = DebugOverlay::new(config.fps_update_interval);
98        debug_overlay.set_enabled(config.show_fps_overlay);
99        Ok(Self {
100            scene_manager: SceneManager::new(),
101            config,
102            context: GameContext::new(window_size),
103            initialized: false,
104            debug_overlay,
105            providers: ProviderRegistry::default(),
106            #[cfg(feature = "native")]
107            platform: None,
108            #[cfg(feature = "native")]
109            render_backend: None,
110            #[cfg(feature = "native")]
111            input_manager: InputManager::default(),
112            #[cfg(feature = "native")]
113            sprite_batch: None,
114            #[cfg(feature = "native")]
115            asset_server: None,
116            #[cfg(feature = "native")]
117            renderer_3d: None,
118            #[cfg(feature = "native")]
119            immediate_state: None,
120        })
121    }
122
123    /// Creates a game with default configuration.
124    pub fn default_game() -> GoudResult<Self> {
125        Self::new(GameConfig::default())
126    }
127
128    /// Creates a windowed game instance with a GLFW platform backend.
129    ///
130    /// This initializes a GLFW window with an OpenGL 3.3 Core context,
131    /// sets up the sprite batch renderer, and prepares the asset server.
132    ///
133    /// # Errors
134    ///
135    /// Returns an error if GLFW initialization or window creation fails.
136    #[cfg(feature = "native")]
137    pub fn with_platform(config: GameConfig) -> GoudResult<Self> {
138        use crate::libs::platform::glfw_platform::GlfwPlatform;
139        use crate::libs::platform::WindowConfig;
140
141        let window_config = WindowConfig {
142            width: config.width,
143            height: config.height,
144            title: config.title.clone(),
145            vsync: config.vsync,
146            resizable: config.resizable,
147        };
148
149        let platform = GlfwPlatform::new(&window_config)?;
150        let window_size = (config.width, config.height);
151        let mut debug_overlay = DebugOverlay::new(config.fps_update_interval);
152        debug_overlay.set_enabled(config.show_fps_overlay);
153
154        Ok(Self {
155            scene_manager: SceneManager::new(),
156            config,
157            context: GameContext::new(window_size),
158            initialized: false,
159            debug_overlay,
160            providers: ProviderRegistry::default(),
161            platform: Some(Box::new(platform)),
162            render_backend: None,
163            input_manager: InputManager::default(),
164            sprite_batch: None,
165            asset_server: None,
166            renderer_3d: None,
167            immediate_state: None,
168        })
169    }
170
171    // =========================================================================
172    // Default-scene World Access (backward-compatible)
173    // =========================================================================
174
175    /// Returns a reference to the default scene's ECS world.
176    #[inline]
177    pub fn world(&self) -> &World {
178        self.scene_manager
179            .get_scene(self.scene_manager.default_scene())
180            .expect("default scene must exist")
181    }
182
183    /// Returns a mutable reference to the default scene's ECS world.
184    #[inline]
185    pub fn world_mut(&mut self) -> &mut World {
186        let default = self.scene_manager.default_scene();
187        self.scene_manager
188            .get_scene_mut(default)
189            .expect("default scene must exist")
190    }
191
192    /// Creates an entity builder for fluent entity creation (default scene).
193    #[inline]
194    pub fn spawn(&mut self) -> EntityBuilder<'_> {
195        let default = self.scene_manager.default_scene();
196        let world = self
197            .scene_manager
198            .get_scene_mut(default)
199            .expect("default scene must exist");
200        EntityBuilder::new(world)
201    }
202
203    /// Spawns an empty entity with no components (default scene).
204    #[inline]
205    pub fn spawn_empty(&mut self) -> Entity {
206        self.world_mut().spawn_empty()
207    }
208
209    /// Spawns multiple empty entities at once (default scene).
210    #[inline]
211    pub fn spawn_batch(&mut self, count: usize) -> Vec<Entity> {
212        self.world_mut().spawn_batch(count)
213    }
214
215    /// Despawns an entity and removes all its components (default scene).
216    #[inline]
217    pub fn despawn(&mut self, entity: Entity) -> bool {
218        self.world_mut().despawn(entity)
219    }
220
221    /// Gets a reference to a component on an entity (default scene).
222    #[inline]
223    pub fn get<T: Component>(&self, entity: Entity) -> Option<&T> {
224        self.world().get::<T>(entity)
225    }
226
227    /// Gets a mutable reference to a component on an entity (default scene).
228    #[inline]
229    pub fn get_mut<T: Component>(&mut self, entity: Entity) -> Option<&mut T> {
230        self.world_mut().get_mut::<T>(entity)
231    }
232
233    /// Adds or replaces a component on an entity (default scene).
234    #[inline]
235    pub fn insert<T: Component>(&mut self, entity: Entity, component: T) {
236        self.world_mut().insert(entity, component);
237    }
238
239    /// Removes a component from an entity (default scene).
240    #[inline]
241    pub fn remove<T: Component>(&mut self, entity: Entity) -> Option<T> {
242        self.world_mut().remove::<T>(entity)
243    }
244
245    /// Checks if an entity has a specific component (default scene).
246    #[inline]
247    pub fn has<T: Component>(&self, entity: Entity) -> bool {
248        self.world().has::<T>(entity)
249    }
250
251    /// Returns the number of entities in the default scene.
252    #[inline]
253    pub fn entity_count(&self) -> usize {
254        self.world().entity_count()
255    }
256
257    /// Checks if an entity is alive (default scene).
258    #[inline]
259    pub fn is_alive(&self, entity: Entity) -> bool {
260        self.world().is_alive(entity)
261    }
262
263    // =========================================================================
264    // Scene Management
265    // =========================================================================
266
267    /// Creates a new scene with the given name.
268    pub fn create_scene(&mut self, name: &str) -> Result<SceneId, GoudError> {
269        self.scene_manager.create_scene(name)
270    }
271
272    /// Destroys a scene. Cannot destroy the default scene.
273    pub fn destroy_scene(&mut self, id: SceneId) -> Result<(), GoudError> {
274        self.scene_manager.destroy_scene(id)
275    }
276
277    /// Returns a reference to a scene's world.
278    pub fn scene(&self, id: SceneId) -> Option<&World> {
279        self.scene_manager.get_scene(id)
280    }
281
282    /// Returns a mutable reference to a scene's world.
283    pub fn scene_mut(&mut self, id: SceneId) -> Option<&mut World> {
284        self.scene_manager.get_scene_mut(id)
285    }
286
287    /// Looks up a scene by name.
288    pub fn scene_by_name(&self, name: &str) -> Option<SceneId> {
289        self.scene_manager.get_scene_by_name(name)
290    }
291
292    /// Sets whether a scene is active.
293    pub fn set_scene_active(&mut self, id: SceneId, active: bool) -> Result<(), GoudError> {
294        self.scene_manager.set_active(id, active)
295    }
296
297    /// Returns a reference to the scene manager.
298    #[inline]
299    pub fn scene_manager(&self) -> &SceneManager {
300        &self.scene_manager
301    }
302
303    /// Returns a mutable reference to the scene manager.
304    #[inline]
305    pub fn scene_manager_mut(&mut self) -> &mut SceneManager {
306        &mut self.scene_manager
307    }
308
309    /// Returns the game configuration.
310    #[inline]
311    pub fn config(&self) -> &GameConfig {
312        &self.config
313    }
314
315    /// Returns the window title.
316    #[inline]
317    pub fn title(&self) -> &str {
318        &self.config.title
319    }
320
321    /// Returns the window dimensions.
322    #[inline]
323    pub fn window_size(&self) -> (u32, u32) {
324        (self.config.width, self.config.height)
325    }
326
327    /// Runs the game loop with the given update callback.
328    ///
329    /// The callback receives the default scene's world for backward
330    /// compatibility. Use [`scene_manager_mut`](Self::scene_manager_mut)
331    /// inside the callback for multi-scene access.
332    pub fn run<F>(&mut self, mut update: F)
333    where
334        F: FnMut(&mut GameContext, &mut World),
335    {
336        self.initialized = true;
337
338        // Simple game loop (actual implementation would use GLFW/window events)
339        let frame_time = if self.config.target_fps > 0 {
340            1.0 / self.config.target_fps as f32
341        } else {
342            1.0 / 60.0 // Default to 60 FPS for simulation
343        };
344
345        // For now, just run a few frames to demonstrate the API
346        // Real implementation would integrate with windowing system
347        while self.context.is_running() {
348            self.context.update(frame_time);
349            self.debug_overlay.update(frame_time);
350
351            // Update all active scenes each frame.
352            let active: Vec<SceneId> = self.scene_manager.active_scenes().to_vec();
353            for scene_id in active {
354                if let Some(world) = self.scene_manager.get_scene_mut(scene_id) {
355                    update(&mut self.context, world);
356                }
357            }
358
359            // Safety: Limit iterations in tests/examples without actual window
360            if self.context.frame_count() > 10000 {
361                break;
362            }
363        }
364    }
365
366    /// Runs a single frame update for all active scenes.
367    pub fn update_frame<F>(&mut self, delta_time: f32, mut update: F)
368    where
369        F: FnMut(&mut GameContext, &mut World),
370    {
371        self.context.update(delta_time);
372        self.debug_overlay.update(delta_time);
373
374        let active: Vec<SceneId> = self.scene_manager.active_scenes().to_vec();
375        for scene_id in active {
376            if let Some(world) = self.scene_manager.get_scene_mut(scene_id) {
377                update(&mut self.context, world);
378            }
379        }
380    }
381
382    /// Returns the current FPS statistics from the debug overlay.
383    #[inline]
384    pub fn fps_stats(&self) -> FpsStats {
385        self.debug_overlay.stats()
386    }
387
388    /// Enables or disables the FPS stats overlay.
389    #[inline]
390    pub fn set_fps_overlay_enabled(&mut self, enabled: bool) {
391        self.debug_overlay.set_enabled(enabled);
392    }
393
394    /// Returns the current frame count.
395    #[inline]
396    pub fn frame_count(&self) -> u64 {
397        self.context.frame_count()
398    }
399
400    /// Returns the total time elapsed since game start.
401    #[inline]
402    pub fn total_time(&self) -> f32 {
403        self.context.total_time()
404    }
405
406    /// Returns the current FPS.
407    #[inline]
408    pub fn fps(&self) -> f32 {
409        self.context.fps()
410    }
411
412    /// Returns true if the game has been initialized.
413    #[inline]
414    pub fn is_initialized(&self) -> bool {
415        self.initialized
416    }
417
418    /// Creates a headless game from an [`EngineConfig`] builder.
419    pub fn from_engine_config(config: EngineConfig) -> GoudResult<Self> {
420        let (game_config, providers) = config.build();
421        let mut game = Self::new(game_config)?;
422        game.providers = providers;
423        Ok(game)
424    }
425
426    /// Creates a windowed game from an [`EngineConfig`] builder.
427    #[cfg(feature = "native")]
428    pub fn from_engine_config_with_platform(config: EngineConfig) -> GoudResult<Self> {
429        let (game_config, providers) = config.build();
430        let mut game = Self::with_platform(game_config)?;
431        game.providers = providers;
432        Ok(game)
433    }
434
435    /// Returns a reference to the provider registry.
436    #[inline]
437    pub fn providers(&self) -> &ProviderRegistry {
438        &self.providers
439    }
440}
441
442impl Default for GoudGame {
443    fn default() -> Self {
444        Self::new(GameConfig::default()).expect("Failed to create default GoudGame")
445    }
446}
447
448impl std::fmt::Debug for GoudGame {
449    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
450        f.debug_struct("GoudGame")
451            .field("config", &self.config)
452            .field("entity_count", &self.entity_count())
453            .field("initialized", &self.initialized)
454            .finish()
455    }
456}