Skip to main content

euv_engine/engine/
impl.rs

1use crate::*;
2
3/// Implements the top-level static engine entry points on the `Engine` namespace.
4///
5/// Mirrors the role of `euv::App`: every public engine operation begins
6/// with an `Engine::xxx` call, and `Engine` itself holds no state.
7impl Engine {
8    /// Creates a new engine handle bound to the given configuration.
9    ///
10    /// The handle is uninitialized; call `init_canvas` / `init_webgpu` and
11    /// `start` on the returned handle to begin the game loop, or use
12    /// `Engine::run` for the one-line equivalent.
13    ///
14    /// # Arguments
15    ///
16    /// - `EngineConfig` - The engine configuration.
17    ///
18    /// # Returns
19    ///
20    /// - `EngineHandle` - The new uninitialized engine handle.
21    pub fn new_handle(config: EngineConfig) -> EngineHandle {
22        EngineHandle::new(config, None, None, None)
23    }
24
25    /// Runs the engine through its complete lifecycle in a single async call.
26    ///
27    /// Equivalent to calling `Engine::new_handle(config)` followed by
28    /// `init_canvas` / `init_webgpu` (matching the chosen backend) and
29    /// `start(handler)`. The returned handle can still be used to stop
30    /// the loop later via `handle.stop()`.
31    ///
32    /// # Arguments
33    ///
34    /// - `EngineConfig` - The engine configuration.
35    /// - `TickHandlerRc` - The tick handler receiving update and render callbacks.
36    ///
37    /// # Returns
38    ///
39    /// - `EngineHandle` - A handle to the running engine. If renderer
40    ///   initialization failed the handle's renderer field will be `None`
41    ///   and `is_running` will be `false`.
42    pub async fn run(config: EngineConfig, handler: TickHandlerRc) -> EngineHandle {
43        let mut handle: EngineHandle = Engine::new_handle(config);
44        match handle.get_config().get_render().get_backend() {
45            RenderBackendType::Canvas2D => {
46                handle.init_canvas();
47            }
48            RenderBackendType::WebGpu => {
49                handle.init_webgpu().await;
50            }
51        }
52        handle.start(handler);
53        handle
54    }
55
56    /// Returns the default engine configuration.
57    ///
58    /// Uses `RenderConfig::default()` (Canvas 2D backend) and the default
59    /// scheduler configuration (60 Hz fixed timestep).
60    ///
61    /// # Returns
62    ///
63    /// - `EngineConfig` - The default engine configuration.
64    pub fn default_config() -> EngineConfig {
65        EngineConfig::default()
66    }
67
68    /// Creates a Canvas 2D renderer directly from a render configuration.
69    ///
70    /// Use this when you want a `CanvasRenderer` without going through
71    /// the full `EngineHandle` lifecycle (for example, in a custom render
72    /// loop that does not use the fixed-timestep scheduler).
73    ///
74    /// # Arguments
75    ///
76    /// - `&RenderConfig` - The rendering configuration.
77    ///
78    /// # Returns
79    ///
80    /// - `Option<CanvasRenderer>` - The renderer, or `None` if the canvas element was not found.
81    pub fn canvas_renderer(config: &RenderConfig) -> Option<CanvasRenderer> {
82        CanvasRenderer::from_selector(
83            config.get_canvas_selector(),
84            config.get_width(),
85            config.get_height(),
86        )
87    }
88
89    /// Creates a WebGPU renderer directly from a render configuration.
90    ///
91    /// Async because GPU adapter and device acquisition returns JavaScript
92    /// Promises that must be awaited. Mirrors `Engine::canvas_renderer` for
93    /// callers that want the renderer without the full engine handle.
94    ///
95    /// # Arguments
96    ///
97    /// - `&RenderConfig` - The rendering configuration.
98    ///
99    /// # Returns
100    ///
101    /// - `Option<WebGpuRenderer>` - The renderer, or `None` on GPU initialization failure.
102    pub async fn webgpu_renderer(config: &RenderConfig) -> Option<WebGpuRenderer> {
103        WebGpuRenderer::init(config).await
104    }
105}
106
107/// Implements lifecycle management for `EngineHandle`.
108///
109/// All getter methods (`get_config`, `get_canvas_renderer`, `get_webgpu_renderer`)
110/// are generated by the `Data` derive on the struct definition; this impl
111/// only contributes lifecycle and accessor behavior that is not expressible
112/// as a plain getter.
113impl EngineHandle {
114    /// Initializes the Canvas 2D rendering backend.
115    ///
116    /// On success, populates `canvas_renderer` and clears `webgpu_renderer`.
117    /// On failure, both renderer fields remain `None`.
118    ///
119    /// # Returns
120    ///
121    /// - `bool` - `true` if the renderer was created successfully.
122    pub fn init_canvas(&mut self) -> bool {
123        let render_config: &RenderConfig = &self.config.get_render();
124        let renderer: Option<CanvasRenderer> = CanvasRenderer::from_selector(
125            render_config.get_canvas_selector(),
126            render_config.get_width(),
127            render_config.get_height(),
128        );
129        match renderer {
130            Some(r) => {
131                self.canvas_renderer = Some(r);
132                self.webgpu_renderer = None;
133                true
134            }
135            None => {
136                self.canvas_renderer = None;
137                self.webgpu_renderer = None;
138                false
139            }
140        }
141    }
142
143    /// Initializes the WebGPU rendering backend.
144    ///
145    /// On success, populates `webgpu_renderer` and clears `canvas_renderer`.
146    /// On failure, both renderer fields remain `None`.
147    ///
148    /// # Returns
149    ///
150    /// - `bool` - `true` if the renderer was created successfully.
151    pub async fn init_webgpu(&mut self) -> bool {
152        let render_config: &RenderConfig = &self.config.get_render();
153        let renderer: Option<WebGpuRenderer> = WebGpuRenderer::init(render_config).await;
154        match renderer {
155            Some(r) => {
156                self.webgpu_renderer = Some(r);
157                self.canvas_renderer = None;
158                true
159            }
160            None => {
161                self.canvas_renderer = None;
162                self.webgpu_renderer = None;
163                false
164            }
165        }
166    }
167
168    /// Starts the game loop with the given tick handler.
169    ///
170    /// The scheduler configuration from `EngineConfig` controls the fixed
171    /// timestep and maximum frame time. The scheduler handle is stored
172    /// internally and can be stopped via `stop`.
173    ///
174    /// # Arguments
175    ///
176    /// - `TickHandlerRc` - The tick handler receiving update and render callbacks.
177    pub fn start(&mut self, handler: TickHandlerRc) {
178        let scheduler_config: SchedulerConfig = self.config.get_scheduler();
179        self.scheduler_handle = Some(SchedulerHandle::start(scheduler_config, handler));
180    }
181
182    /// Stops the game loop and cancels any pending animation frame request.
183    pub fn stop(&self) {
184        if let Some(handle) = &self.scheduler_handle {
185            handle.stop();
186        }
187    }
188
189    /// Returns whether the game loop is currently running.
190    ///
191    /// # Returns
192    ///
193    /// - `bool` - `true` if the scheduler is running.
194    pub fn is_running(&self) -> bool {
195        self.scheduler_handle
196            .as_ref()
197            .is_some_and(|handle: &SchedulerHandle| handle.is_running())
198    }
199}