Skip to main content

euv_engine/engine/
impl.rs

1use super::*;
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`. Initialization errors are not
42    ///   logged inside the engine; callers should call `init_webgpu`
43    ///   directly if they need access to the typed `WebGpuInitError`.
44    pub async fn run(config: EngineConfig, handler: TickHandlerRc) -> EngineHandle {
45        let mut handle: EngineHandle = Engine::new_handle(config);
46        match handle.get_config().get_render().get_backend() {
47            RenderBackendType::Canvas2D => {
48                handle.init_canvas();
49            }
50            RenderBackendType::WebGpu => {
51                let _ = handle.init_webgpu().await;
52            }
53        }
54        handle.start(handler);
55        handle
56    }
57
58    /// Returns the default engine configuration.
59    ///
60    /// Uses `RenderConfig::default()` (Canvas 2D backend) and the default
61    /// scheduler configuration (60 Hz fixed timestep).
62    ///
63    /// # Returns
64    ///
65    /// - `EngineConfig` - The default engine configuration.
66    pub fn default_config() -> EngineConfig {
67        EngineConfig::default()
68    }
69
70    /// Creates a Canvas 2D renderer directly from a render configuration.
71    ///
72    /// Use this when you want a `CanvasRenderer` without going through
73    /// the full `EngineHandle` lifecycle (for example, in a custom render
74    /// loop that does not use the fixed-timestep scheduler).
75    ///
76    /// # Arguments
77    ///
78    /// - `&RenderConfig` - The rendering configuration.
79    ///
80    /// # Returns
81    ///
82    /// - `Option<CanvasRenderer>` - The renderer, or `None` if the canvas element was not found.
83    pub fn canvas_renderer(config: &RenderConfig) -> Option<CanvasRenderer> {
84        CanvasRenderer::from_selector(
85            config.get_canvas_selector(),
86            config.get_width(),
87            config.get_height(),
88        )
89    }
90
91    /// Creates a WebGPU renderer directly from a render configuration.
92    ///
93    /// Async because GPU adapter and device acquisition returns JavaScript
94    /// Promises that must be awaited. Mirrors `Engine::canvas_renderer` for
95    /// callers that want the renderer without the full engine handle.
96    ///
97    /// Failures are surfaced as `WebGpuInitError` so the caller can decide
98    /// how to react (typically by logging via `Console::error` or by
99    /// falling back to the Canvas 2D backend).
100    ///
101    /// # Arguments
102    ///
103    /// - `&RenderConfig` - The rendering configuration.
104    ///
105    /// # Returns
106    ///
107    /// - `Result<WebGpuRenderer, WebGpuInitError>` - The initialized renderer,
108    ///   or a typed error describing the specific failure.
109    pub async fn webgpu_renderer(config: &RenderConfig) -> Result<WebGpuRenderer, WebGpuInitError> {
110        WebGpuRenderer::init(config).await
111    }
112}
113
114/// Implements lifecycle management for `EngineHandle`.
115///
116/// All getter methods (`get_config`, `get_canvas_renderer`, `get_webgpu_renderer`)
117/// are generated by the `Data` derive on the struct definition; this impl
118/// only contributes lifecycle and accessor behavior that is not expressible
119/// as a plain getter.
120impl EngineHandle {
121    /// Initializes the Canvas 2D rendering backend.
122    ///
123    /// On success, populates `canvas_renderer` and clears `webgpu_renderer`.
124    /// On failure, both renderer fields remain `None`.
125    ///
126    /// # Returns
127    ///
128    /// - `bool` - `true` if the renderer was created successfully.
129    pub fn init_canvas(&mut self) -> bool {
130        let render_config: &RenderConfig = &self.get_config().get_render();
131        let renderer: Option<CanvasRenderer> = CanvasRenderer::from_selector(
132            render_config.get_canvas_selector(),
133            render_config.get_width(),
134            render_config.get_height(),
135        );
136        match renderer {
137            Some(r) => {
138                self.set_canvas_renderer(Some(r));
139                self.set_webgpu_renderer(None);
140                true
141            }
142            None => {
143                self.set_canvas_renderer(None);
144                self.set_webgpu_renderer(None);
145                false
146            }
147        }
148    }
149
150    /// Initializes the WebGPU rendering backend.
151    ///
152    /// On success, populates `webgpu_renderer` and clears `canvas_renderer`.
153    /// On failure, both renderer fields remain `None` and the typed error is
154    /// returned so the caller can decide how to surface it (typically via
155    /// `Console::error`).
156    ///
157    /// # Returns
158    ///
159    /// - `Result<WebGpuRenderer, WebGpuInitError>` - The initialized renderer,
160    ///   or a typed error describing the specific failure.
161    pub async fn init_webgpu(&mut self) -> Result<WebGpuRenderer, WebGpuInitError> {
162        let render_config: &RenderConfig = &self.get_config().get_render();
163        let renderer: WebGpuRenderer = WebGpuRenderer::init(render_config).await?;
164        self.set_webgpu_renderer(Some(renderer.clone()));
165        self.set_canvas_renderer(None);
166        Ok(renderer)
167    }
168
169    /// Starts the game loop with the given tick handler.
170    ///
171    /// The scheduler configuration from `EngineConfig` controls the fixed
172    /// timestep and maximum frame time. The scheduler handle is stored
173    /// internally and can be stopped via `stop`.
174    ///
175    /// # Arguments
176    ///
177    /// - `TickHandlerRc` - The tick handler receiving update and render callbacks.
178    pub fn start(&mut self, handler: TickHandlerRc) {
179        let scheduler_config: SchedulerConfig = self.get_config().get_scheduler();
180        self.set_scheduler_handle(Some(SchedulerHandle::start(scheduler_config, handler)));
181    }
182
183    /// Stops the game loop and cancels any pending animation frame request.
184    pub fn stop(&self) {
185        if let Some(handle) = self.try_get_scheduler_handle().as_ref() {
186            handle.stop();
187        }
188    }
189
190    /// Returns whether the game loop is currently running.
191    ///
192    /// # Returns
193    ///
194    /// - `bool` - `true` if the scheduler is running.
195    pub fn is_running(&self) -> bool {
196        self.try_get_scheduler_handle()
197            .as_ref()
198            .is_some_and(|handle: &SchedulerHandle| handle.is_running())
199    }
200}