1use std::sync::Arc;
2use std::time::Duration;
3
4use web_time::Instant;
5use winit::application::ApplicationHandler;
6use winit::event::{DeviceEvent, DeviceId, MouseButton, MouseScrollDelta, WindowEvent};
7use winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoop};
8use winit::window::{CursorGrabMode, Window, WindowAttributes, WindowId};
9
10use loam_render::device::{FeatureRequest, RenderDevice};
11use loam_runtime::host::HostError;
12use loam_runtime::{PointerButton, Session, Stores};
13
14use super::app::SessionApp;
15use super::frame::{failed, Frame};
16use super::input::{winit_alt, winit_key};
17use super::pacing::Pace;
18use super::surface::{Attempt, SurfaceHost};
19use crate::args::Args;
20use crate::WasmConfig;
21
22pub fn launch<A: Stores>(
23 factory: impl FnOnce(Args) -> Result<(Session<A>, SessionApp<A>), HostError>,
24) -> Result<(), HostError> {
25 launch_with(WasmConfig::default(), factory)
26}
27
28pub fn launch_with<A: Stores>(
29 _wasm: WasmConfig,
30 factory: impl FnOnce(Args) -> Result<(Session<A>, SessionApp<A>), HostError>,
31) -> Result<(), HostError> {
32 install_tracing();
33 crate::par_native::install();
34 run(Args::current(), factory)
35}
36
37pub fn launch_or_headless<A: Stores>(
38 factory: impl FnOnce(Args) -> Result<(Session<A>, SessionApp<A>), HostError>,
39 headless: impl FnOnce(Args) -> Result<(), HostError>,
40) -> Result<(), HostError> {
41 install_tracing();
42 crate::par_native::install();
43 let args = Args::current();
44 if args.has_bare_flag("headless") || args.get("headless").is_some() {
45 return headless(args);
46 }
47 run(args, factory)
48}
49
50fn install_tracing() {
51 let filter = tracing_subscriber::EnvFilter::try_from_default_env()
52 .unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("warn"));
53 let _ = tracing_subscriber::fmt()
54 .with_env_filter(filter)
55 .with_writer(std::io::stderr)
56 .try_init();
57}
58
59const FALLBACK_REFRESH_MILLIHERTZ: u32 = 60_000;
60
61fn refresh_period(millihertz: Option<u32>) -> Duration {
62 let millihertz = millihertz
63 .filter(|millihertz| *millihertz > 0)
64 .unwrap_or(FALLBACK_REFRESH_MILLIHERTZ);
65 Duration::from_nanos(1_000_000_000_000 / u64::from(millihertz))
66}
67
68fn run<A: Stores>(
69 args: Args,
70 factory: impl FnOnce(Args) -> Result<(Session<A>, SessionApp<A>), HostError>,
71) -> Result<(), HostError> {
72 let (session, app) = factory(args)?;
73 let event_loop = EventLoop::new().map_err(failed)?;
74 event_loop.set_control_flow(ControlFlow::Poll);
75 let mut host = Host::new(session, app)?;
76 event_loop.run_app(&mut host).map_err(failed)?;
77 match host.failure {
78 Some(error) => Err(error),
79 None => Ok(()),
80 }
81}
82
83struct Host<A: Stores> {
84 frame: Frame<A>,
85 window: Option<Arc<Window>>,
86 surface: Option<SurfaceHost>,
87 device: Option<RenderDevice>,
88 redraw_deadline: Option<Instant>,
89 refresh: Duration,
90 failure: Option<HostError>,
91}
92
93impl<A: Stores> Host<A> {
94 fn new(session: Session<A>, app: SessionApp<A>) -> Result<Self, HostError> {
95 Ok(Self {
96 frame: Frame::new(session, app)?,
97 window: None,
98 surface: None,
99 device: None,
100 redraw_deadline: None,
101 refresh: refresh_period(None),
102 failure: None,
103 })
104 }
105
106 fn stop(&mut self, elwt: &ActiveEventLoop, error: HostError) {
107 self.failure = Some(error);
108 elwt.exit();
109 }
110
111 fn redraw(&mut self, elwt: &ActiveEventLoop) {
112 if let Err(error) = self.present(elwt) {
113 self.stop(elwt, error);
114 }
115 }
116
117 fn present(&mut self, elwt: &ActiveEventLoop) -> Result<(), HostError> {
118 let Host {
119 frame,
120 window,
121 surface,
122 device,
123 redraw_deadline,
124 refresh,
125 ..
126 } = self;
127 let Some(device) = device.as_mut() else {
128 return Ok(());
129 };
130 let Some(surface) = surface.as_mut() else {
131 return Ok(());
132 };
133 let size = surface.size();
134 if size.0 == 0 || size.1 == 0 {
135 return Ok(());
136 }
137
138 if let Some(loss) = device.take_device_loss() {
139 tracing::warn!("device lost ({:?}): {}", loss.reason, loss.message);
140 pollster::block_on(device.recover()).map_err(|error| failed(format!("{error:#}")))?;
141 surface.reconfigure(&device.context.device);
142 return frame.recover(&device.context);
143 }
144
145 if let Some(enabled) = frame.app_mut().vsync.take() {
146 surface.set_vsync(&device.context.device, enabled);
147 frame.invalidate();
148 }
149
150 let now = Instant::now();
151 if let Pace::Wait(deadline) = frame.app_mut().pacer.decide(now) {
152 *redraw_deadline = Some(deadline);
153 elwt.set_control_flow(ControlFlow::WaitUntil(deadline));
154 return Ok(());
155 }
156 *redraw_deadline = frame.app_mut().pacer.deadline();
157 match *redraw_deadline {
158 Some(deadline) => elwt.set_control_flow(ControlFlow::WaitUntil(deadline)),
159 None => elwt.set_control_flow(ControlFlow::Poll),
160 };
161
162 let window = window.as_deref();
163 let attempt = surface.present(device, frame, now, |frame, stepped| {
164 if stepped {
165 Self::apply_cursor_request(frame, window);
166 }
167 })?;
168 if matches!(attempt, Attempt::Unchanged) && redraw_deadline.is_none() {
170 let deadline = now + *refresh;
171 *redraw_deadline = Some(deadline);
172 elwt.set_control_flow(ControlFlow::WaitUntil(deadline));
173 }
174 Ok(())
175 }
176
177 fn apply_cursor_request(frame: &mut Frame<A>, window: Option<&Window>) {
178 let Some(locked) = frame.take_cursor_request() else {
179 return;
180 };
181 let Some(window) = window else {
182 return;
183 };
184 let mode = if locked {
185 CursorGrabMode::Locked
186 } else {
187 CursorGrabMode::None
188 };
189 let applied = window.set_cursor_grab(mode).or_else(|locked_error| {
190 if !locked {
191 return Err(locked_error);
192 }
193 window
194 .set_cursor_grab(CursorGrabMode::Confined)
195 .map_err(|confined_error| {
196 tracing::warn!(
197 "locked cursor failed: {locked_error}; confined cursor failed: {confined_error}"
198 );
199 confined_error
200 })
201 });
202 match applied {
203 Ok(()) => {
204 window.set_cursor_visible(!locked);
205 frame.cursor_applied(locked);
206 }
207 Err(error) => {
208 tracing::warn!("cursor lock request failed: {error:?}");
209 if locked {
210 frame.cursor_applied(false);
211 }
212 }
213 }
214 }
215
216 fn wake_frame(&mut self, elwt: &ActiveEventLoop) {
217 self.redraw_deadline = None;
218 self.frame.app_mut().pacer.reset();
219 elwt.set_control_flow(ControlFlow::Wait);
220 if let Some(window) = self.window.as_ref() {
221 window.request_redraw();
222 }
223 }
224}
225
226impl<A: Stores> ApplicationHandler for Host<A> {
227 fn resumed(&mut self, elwt: &ActiveEventLoop) {
228 if self.window.is_some() {
229 return;
230 }
231 let attributes = WindowAttributes::default().with_title(self.frame.config().title);
232 let window = match elwt.create_window(attributes) {
233 Ok(window) => Arc::new(window),
234 Err(error) => return self.stop(elwt, failed(error)),
235 };
236 let instance = wgpu::Instance::default();
237 let surface = match instance.create_surface(window.clone()) {
238 Ok(surface) => surface,
239 Err(error) => return self.stop(elwt, failed(error)),
240 };
241 let size = window.inner_size();
242 let attached = pollster::block_on(SurfaceHost::new(
243 instance,
244 surface,
245 (size.width, size.height),
246 FeatureRequest::default(),
247 ));
248 let (surface, device) = match attached {
249 Ok(attached) => attached,
250 Err(error) => return self.stop(elwt, failed(format!("{error:#}"))),
251 };
252 let attached = self.frame.attach(
253 &device.context,
254 device.target_format(),
255 Some(window.clone()),
256 (size.width, size.height),
257 window.scale_factor() as f32,
258 );
259 if let Err(error) = attached {
260 return self.stop(elwt, error);
261 }
262 self.surface = Some(surface);
263 self.device = Some(device);
264 self.refresh = refresh_period(
265 window
266 .current_monitor()
267 .and_then(|monitor| monitor.refresh_rate_millihertz()),
268 );
269 self.window = Some(window);
270 self.frame.reset_clock(Instant::now());
271 }
272
273 fn window_event(&mut self, elwt: &ActiveEventLoop, _id: WindowId, event: WindowEvent) {
274 let wakes_frame = !matches!(
275 &event,
276 WindowEvent::RedrawRequested | WindowEvent::CloseRequested
277 );
278 let hidden_pointer = self.frame.cursor_locked()
279 && matches!(
280 &event,
281 WindowEvent::CursorMoved { .. }
282 | WindowEvent::MouseInput { .. }
283 | WindowEvent::MouseWheel { .. }
284 );
285 #[cfg(feature = "egui")]
286 let consumed = hidden_pointer
287 || self
288 .frame
289 .layer()
290 .is_some_and(|layer| layer.on_window_event(&event));
291 #[cfg(not(feature = "egui"))]
292 let consumed = hidden_pointer;
293 let scale = self
294 .window
295 .as_ref()
296 .map_or(1.0, |window| window.scale_factor() as f32);
297 match event {
298 WindowEvent::CloseRequested => elwt.exit(),
299 WindowEvent::Resized(size) => {
300 if let (Some(surface), Some(device)) = (self.surface.as_mut(), self.device.as_mut())
301 {
302 let size = (size.width, size.height);
303 surface.resize(&device.context.device, size);
304 device.resize(size);
305 }
306 self.frame.resize(size.width, size.height, scale);
307 }
308 WindowEvent::ScaleFactorChanged { .. } => {
309 let (width, height) = self.frame.input_mut().size();
310 self.frame.resize(width, height, scale);
311 }
312 WindowEvent::KeyboardInput { event, .. } => {
313 if let Some(index) = winit_alt(&event) {
314 self.frame.alt(index, event.state.is_pressed());
315 }
316 if let Some(key) = winit_key(&event) {
317 self.frame.action(key, event.state.is_pressed(), consumed);
318 }
319 }
320 WindowEvent::CursorMoved { position, .. } if !consumed => {
321 let input = self.frame.input_mut();
322 let ndc = input.ndc(position.x, position.y);
323 input.moved(ndc);
324 }
325 WindowEvent::MouseInput { state, button, .. } => {
326 let button = match button {
327 MouseButton::Left => Some(PointerButton::Primary),
328 MouseButton::Right => Some(PointerButton::Secondary),
329 MouseButton::Middle => Some(PointerButton::Middle),
330 _ => None,
331 };
332 if let Some(button) = button {
333 let input = self.frame.input_mut();
334 let cursor = input.cursor();
335 input.host_button(cursor, button, state.is_pressed(), consumed);
336 }
337 }
338 WindowEvent::Focused(focused) => self.frame.focus(focused),
339 WindowEvent::MouseWheel { delta, .. } if !consumed => {
340 let delta = match delta {
341 MouseScrollDelta::LineDelta(x, y) => [x, y],
342 MouseScrollDelta::PixelDelta(position) => [
343 position.x as f32 / crate::wasm::input_queue::SCROLL_PIXELS_PER_LINE,
344 position.y as f32 / crate::wasm::input_queue::SCROLL_PIXELS_PER_LINE,
345 ],
346 };
347 self.frame.input_mut().wheel(delta);
348 }
349 WindowEvent::RedrawRequested => self.redraw(elwt),
350 _ => {}
351 }
352 Self::apply_cursor_request(&mut self.frame, self.window.as_deref());
353 if wakes_frame {
354 self.wake_frame(elwt);
355 }
356 }
357
358 fn device_event(&mut self, elwt: &ActiveEventLoop, _id: DeviceId, event: DeviceEvent) {
359 if let DeviceEvent::MouseMotion { delta } = event {
360 self.frame
361 .input_mut()
362 .raw_motion(delta.0 as f32, delta.1 as f32);
363 self.wake_frame(elwt);
364 }
365 }
366
367 fn about_to_wait(&mut self, elwt: &ActiveEventLoop) {
368 if let Some(deadline) = self.redraw_deadline {
369 if Instant::now() < deadline {
370 elwt.set_control_flow(ControlFlow::WaitUntil(deadline));
371 return;
372 }
373 self.redraw_deadline = None;
374 }
375 if let Some(window) = self.window.as_ref() {
376 window.request_redraw();
377 }
378 }
379}