Skip to main content

optic_loop/
runtime.rs

1use crate::Game;
2
3/// The lifecycle trait for game logic.
4///
5/// Implement this trait to hook your application into the engine's event
6/// loop. Pass an instance to [`Game::new`] or [`Game::run`] and the engine
7/// drives the three lifecycle methods automatically.
8///
9/// | Method | Called | Purpose |
10/// |---|---|---|
11/// | [`start`](Runtime::start) | Once, before the first frame | One-time setup |
12/// | [`update`](Runtime::update) | Every frame | Input, logic, rendering |
13/// | [`end`](Runtime::end) | Once, on shutdown | Cleanup |
14///
15/// Each method receives `&mut Game`, giving you access to the renderer,
16/// camera, window, events, and time.
17///
18/// # Example
19///
20/// ```ignore
21/// use optic_loop::{Game, Runtime};
22///
23/// struct FpsCounter {
24///     frames: u64,
25/// }
26///
27/// impl Runtime for FpsCounter {
28///     fn start(&mut self, _game: &mut Game) {
29///         println!("Starting...");
30///     }
31///
32///     fn update(&mut self, game: &mut Game) {
33///         self.frames += 1;
34///         if self.frames % 60 == 0 {
35///             let fps = game.time.fps();
36///             println!("FPS: {:.1}", fps);
37///         }
38///         game.renderer.clear();
39///         // ... draw scene ...
40///     }
41///
42///     fn end(&mut self, _game: &mut Game) {
43///         println!("Rendered {} frames total", self.frames);
44///     }
45/// }
46///
47/// Game::run(FpsCounter { frames: 0 });
48/// ```
49///
50/// # Why three methods?
51///
52/// Separating `start` from `update` avoids re-initialising assets every
53/// frame. Separating `end` from `drop` gives you a predictable point to
54/// save state before the engine tears down subsystems.
55pub trait Runtime {
56    /// Called once before the first frame.
57    ///
58    /// Use this for one-time initialisation:
59    ///
60    /// - Load meshes, textures, and shaders via [`GPU::ship_*`]
61    /// - Set up initial game state
62    /// - Connect to a server (see [`Game::enable_networking`])
63    ///
64    /// The window is not yet visible when `start` runs. The engine calls
65    /// `start`, makes the window visible, then immediately enters the
66    /// update loop.
67    fn start(&mut self, game: &mut Game);
68
69    /// Called every frame.
70    ///
71    /// This is the main game loop body. Do all per-frame work here:
72    ///
73    /// 1. **Poll input** — check `game.events` for keyboard, mouse, gamepad
74    /// 2. **Update logic** — move entities, run physics, check collisions
75    /// 3. **Render** — clear the screen, bind shaders, draw meshes
76    ///
77    /// Use [`Game::exit`] to stop the loop from within `update`.
78    fn update(&mut self, game: &mut Game);
79
80    /// Called once on shutdown.
81    ///
82    /// Triggered by [`Game::exit`], window close, or `SIGINT`. Use this to:
83    ///
84    /// - Save persistent state (scores, settings)
85    /// - Disconnect from servers
86    /// - Release non-GPU resources
87    ///
88    /// The GPU and window are still alive during `end`; they are destroyed
89    /// after it returns.
90    fn end(&mut self, game: &mut Game);
91}