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`. 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}