Skip to main content

goud_engine/sdk/game/
instance.rs

1//! [`GoudGame`] struct definition, construction, and core API.
2
3use crate::core::error::GoudResult;
4use crate::ecs::{Component, Entity, World};
5
6#[cfg(feature = "native")]
7use crate::ecs::InputManager;
8#[cfg(feature = "native")]
9use crate::libs::graphics::backend::opengl::OpenGLBackend;
10#[cfg(feature = "native")]
11use crate::libs::graphics::renderer3d::Renderer3D;
12#[cfg(feature = "native")]
13use crate::libs::platform::PlatformBackend;
14#[cfg(feature = "native")]
15use crate::rendering::sprite_batch::SpriteBatch;
16
17use crate::sdk::entity_builder::EntityBuilder;
18use crate::sdk::game_config::{GameConfig, GameContext};
19
20/// The main game instance managing the ECS world and game loop.
21///
22/// # Example
23///
24/// ```rust
25/// use goud_engine::sdk::{GoudGame, GameConfig};
26/// use goud_engine::sdk::components::Transform2D;
27/// use goud_engine::core::math::Vec2;
28///
29/// let mut game = GoudGame::new(GameConfig::default()).unwrap();
30/// let player = game.spawn()
31///     .with(Transform2D::from_position(Vec2::new(400.0, 300.0)))
32///     .build();
33/// ```
34pub struct GoudGame {
35    /// The ECS world containing all game state.
36    pub(crate) world: World,
37
38    /// Game configuration.
39    pub(crate) config: GameConfig,
40
41    /// Runtime context for the game loop.
42    pub(crate) context: GameContext,
43
44    /// Whether the game has been initialized.
45    pub(crate) initialized: bool,
46
47    // =========================================================================
48    // Native-only fields (require windowing + OpenGL)
49    // =========================================================================
50    /// Platform backend for window management (GLFW).
51    #[cfg(feature = "native")]
52    pub(crate) platform: Option<Box<dyn PlatformBackend>>,
53
54    /// OpenGL rendering backend.
55    #[cfg(feature = "native")]
56    pub(crate) render_backend: Option<OpenGLBackend>,
57
58    /// Input manager for keyboard/mouse/gamepad state.
59    #[cfg(feature = "native")]
60    pub(crate) input_manager: InputManager,
61
62    /// 2D sprite batch renderer.
63    #[cfg(feature = "native")]
64    pub(crate) sprite_batch: Option<SpriteBatch<OpenGLBackend>>,
65
66    /// Asset server for loading and managing assets.
67    #[cfg(feature = "native")]
68    pub(crate) asset_server: Option<crate::assets::AssetServer>,
69
70    /// 3D renderer for primitives, lighting, and camera.
71    #[cfg(feature = "native")]
72    pub(crate) renderer_3d: Option<Renderer3D>,
73
74    /// GPU resources for immediate-mode sprite/quad rendering.
75    #[cfg(feature = "native")]
76    pub(crate) immediate_state: Option<crate::sdk::rendering::ImmediateRenderState>,
77}
78
79impl GoudGame {
80    /// Creates a new game instance with the given configuration.
81    ///
82    /// This creates a headless game instance suitable for testing and
83    /// non-graphical use. For a windowed game with rendering, use
84    /// [`with_platform`](Self::with_platform) instead.
85    pub fn new(config: GameConfig) -> GoudResult<Self> {
86        let window_size = (config.width, config.height);
87        Ok(Self {
88            world: World::new(),
89            config,
90            context: GameContext::new(window_size),
91            initialized: false,
92            #[cfg(feature = "native")]
93            platform: None,
94            #[cfg(feature = "native")]
95            render_backend: None,
96            #[cfg(feature = "native")]
97            input_manager: InputManager::default(),
98            #[cfg(feature = "native")]
99            sprite_batch: None,
100            #[cfg(feature = "native")]
101            asset_server: None,
102            #[cfg(feature = "native")]
103            renderer_3d: None,
104            #[cfg(feature = "native")]
105            immediate_state: None,
106        })
107    }
108
109    /// Creates a game with default configuration.
110    pub fn default_game() -> GoudResult<Self> {
111        Self::new(GameConfig::default())
112    }
113
114    /// Creates a windowed game instance with a GLFW platform backend.
115    ///
116    /// This initializes a GLFW window with an OpenGL 3.3 Core context,
117    /// sets up the sprite batch renderer, and prepares the asset server.
118    ///
119    /// # Errors
120    ///
121    /// Returns an error if GLFW initialization or window creation fails.
122    #[cfg(feature = "native")]
123    pub fn with_platform(config: GameConfig) -> GoudResult<Self> {
124        use crate::libs::platform::glfw_platform::GlfwPlatform;
125        use crate::libs::platform::WindowConfig;
126
127        let window_config = WindowConfig {
128            width: config.width,
129            height: config.height,
130            title: config.title.clone(),
131            vsync: config.vsync,
132            resizable: config.resizable,
133        };
134
135        let platform = GlfwPlatform::new(&window_config)?;
136        let window_size = (config.width, config.height);
137
138        Ok(Self {
139            world: World::new(),
140            config,
141            context: GameContext::new(window_size),
142            initialized: false,
143            platform: Some(Box::new(platform)),
144            render_backend: None,
145            input_manager: InputManager::default(),
146            sprite_batch: None,
147            asset_server: None,
148            renderer_3d: None,
149            immediate_state: None,
150        })
151    }
152
153    /// Returns a reference to the ECS world.
154    #[inline]
155    pub fn world(&self) -> &World {
156        &self.world
157    }
158
159    /// Returns a mutable reference to the ECS world.
160    #[inline]
161    pub fn world_mut(&mut self) -> &mut World {
162        &mut self.world
163    }
164
165    /// Creates an entity builder for fluent entity creation.
166    #[inline]
167    pub fn spawn(&mut self) -> EntityBuilder<'_> {
168        EntityBuilder::new(&mut self.world)
169    }
170
171    /// Spawns an empty entity with no components.
172    #[inline]
173    pub fn spawn_empty(&mut self) -> Entity {
174        self.world.spawn_empty()
175    }
176
177    /// Spawns multiple empty entities at once.
178    #[inline]
179    pub fn spawn_batch(&mut self, count: usize) -> Vec<Entity> {
180        self.world.spawn_batch(count)
181    }
182
183    /// Despawns an entity and removes all its components.
184    #[inline]
185    pub fn despawn(&mut self, entity: Entity) -> bool {
186        self.world.despawn(entity)
187    }
188
189    /// Gets a reference to a component on an entity.
190    #[inline]
191    pub fn get<T: Component>(&self, entity: Entity) -> Option<&T> {
192        self.world.get::<T>(entity)
193    }
194
195    /// Gets a mutable reference to a component on an entity.
196    #[inline]
197    pub fn get_mut<T: Component>(&mut self, entity: Entity) -> Option<&mut T> {
198        self.world.get_mut::<T>(entity)
199    }
200
201    /// Adds or replaces a component on an entity.
202    #[inline]
203    pub fn insert<T: Component>(&mut self, entity: Entity, component: T) {
204        self.world.insert(entity, component);
205    }
206
207    /// Removes a component from an entity.
208    #[inline]
209    pub fn remove<T: Component>(&mut self, entity: Entity) -> Option<T> {
210        self.world.remove::<T>(entity)
211    }
212
213    /// Checks if an entity has a specific component.
214    #[inline]
215    pub fn has<T: Component>(&self, entity: Entity) -> bool {
216        self.world.has::<T>(entity)
217    }
218
219    /// Returns the number of entities in the world.
220    #[inline]
221    pub fn entity_count(&self) -> usize {
222        self.world.entity_count()
223    }
224
225    /// Checks if an entity is alive.
226    #[inline]
227    pub fn is_alive(&self, entity: Entity) -> bool {
228        self.world.is_alive(entity)
229    }
230
231    /// Returns the game configuration.
232    #[inline]
233    pub fn config(&self) -> &GameConfig {
234        &self.config
235    }
236
237    /// Returns the window title.
238    #[inline]
239    pub fn title(&self) -> &str {
240        &self.config.title
241    }
242
243    /// Returns the window dimensions.
244    #[inline]
245    pub fn window_size(&self) -> (u32, u32) {
246        (self.config.width, self.config.height)
247    }
248
249    /// Runs the game loop with the given update callback.
250    pub fn run<F>(&mut self, mut update: F)
251    where
252        F: FnMut(&mut GameContext, &mut World),
253    {
254        self.initialized = true;
255
256        // Simple game loop (actual implementation would use GLFW/window events)
257        let frame_time = if self.config.target_fps > 0 {
258            1.0 / self.config.target_fps as f32
259        } else {
260            1.0 / 60.0 // Default to 60 FPS for simulation
261        };
262
263        // For now, just run a few frames to demonstrate the API
264        // Real implementation would integrate with windowing system
265        while self.context.is_running() {
266            self.context.update(frame_time);
267            update(&mut self.context, &mut self.world);
268
269            // Safety: Limit iterations in tests/examples without actual window
270            if self.context.frame_count() > 10000 {
271                break;
272            }
273        }
274    }
275
276    /// Runs a single frame update.
277    pub fn update_frame<F>(&mut self, delta_time: f32, mut update: F)
278    where
279        F: FnMut(&mut GameContext, &mut World),
280    {
281        self.context.update(delta_time);
282        update(&mut self.context, &mut self.world);
283    }
284
285    /// Returns the current frame count.
286    #[inline]
287    pub fn frame_count(&self) -> u64 {
288        self.context.frame_count()
289    }
290
291    /// Returns the total time elapsed since game start.
292    #[inline]
293    pub fn total_time(&self) -> f32 {
294        self.context.total_time()
295    }
296
297    /// Returns the current FPS.
298    #[inline]
299    pub fn fps(&self) -> f32 {
300        self.context.fps()
301    }
302
303    /// Returns true if the game has been initialized.
304    #[inline]
305    pub fn is_initialized(&self) -> bool {
306        self.initialized
307    }
308}
309
310impl Default for GoudGame {
311    fn default() -> Self {
312        Self::new(GameConfig::default()).expect("Failed to create default GoudGame")
313    }
314}
315
316impl std::fmt::Debug for GoudGame {
317    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
318        f.debug_struct("GoudGame")
319            .field("config", &self.config)
320            .field("entity_count", &self.world.entity_count())
321            .field("initialized", &self.initialized)
322            .finish()
323    }
324}