Skip to main content

loam_app/session/
native.rs

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        // ControlFlow::Poll would spin the loop on skipped frames.
169        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}