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}