eframe/native/
run.rs

1use std::time::Instant;
2
3use winit::{
4    application::ApplicationHandler,
5    event_loop::{ActiveEventLoop, ControlFlow, EventLoop},
6    window::WindowId,
7};
8
9use ahash::HashMap;
10
11use super::winit_integration::{UserEvent, WinitApp};
12use crate::{
13    Result, epi,
14    native::{event_loop_context, winit_integration::EventResult},
15};
16
17// ----------------------------------------------------------------------------
18fn create_event_loop(native_options: &mut epi::NativeOptions) -> Result<EventLoop<UserEvent>> {
19    #[cfg(target_os = "android")]
20    use winit::platform::android::EventLoopBuilderExtAndroid as _;
21
22    profiling::function_scope!();
23    let mut builder = winit::event_loop::EventLoop::with_user_event();
24
25    #[cfg(target_os = "android")]
26    let mut builder =
27        builder.with_android_app(native_options.android_app.take().ok_or_else(|| {
28            crate::Error::AppCreation(Box::from(
29                "`NativeOptions` is missing required `android_app`",
30            ))
31        })?);
32
33    if let Some(hook) = std::mem::take(&mut native_options.event_loop_builder) {
34        hook(&mut builder);
35    }
36
37    profiling::scope!("EventLoopBuilder::build");
38    Ok(builder.build()?)
39}
40
41/// Access a thread-local event loop.
42///
43/// We reuse the event-loop so we can support closing and opening an eframe window
44/// multiple times. This is just a limitation of winit.
45#[cfg(not(target_os = "ios"))]
46fn with_event_loop<R>(
47    mut native_options: epi::NativeOptions,
48    f: impl FnOnce(&mut EventLoop<UserEvent>, epi::NativeOptions) -> R,
49) -> Result<R> {
50    thread_local!(static EVENT_LOOP: std::cell::RefCell<Option<EventLoop<UserEvent>>> = const { std::cell::RefCell::new(None) });
51
52    EVENT_LOOP.with(|event_loop| {
53        // Since we want to reference NativeOptions when creating the EventLoop we can't
54        // do that as part of the lazy thread local storage initialization and so we instead
55        // create the event loop lazily here
56        let mut event_loop_lock = event_loop.borrow_mut();
57        let event_loop = if let Some(event_loop) = &mut *event_loop_lock {
58            event_loop
59        } else {
60            event_loop_lock.insert(create_event_loop(&mut native_options)?)
61        };
62        Ok(f(event_loop, native_options))
63    })
64}
65
66/// Wraps a [`WinitApp`] to implement [`ApplicationHandler`]. This handles redrawing, exit states, and
67/// some events, but otherwise forwards events to the [`WinitApp`].
68struct WinitAppWrapper<T: WinitApp> {
69    windows_next_repaint_times: HashMap<WindowId, Instant>,
70    winit_app: T,
71    return_result: Result<(), crate::Error>,
72    run_and_return: bool,
73}
74
75impl<T: WinitApp> WinitAppWrapper<T> {
76    fn new(winit_app: T, run_and_return: bool) -> Self {
77        Self {
78            windows_next_repaint_times: HashMap::default(),
79            winit_app,
80            return_result: Ok(()),
81            run_and_return,
82        }
83    }
84
85    fn handle_event_result(
86        &mut self,
87        event_loop: &ActiveEventLoop,
88        event_result: Result<EventResult>,
89    ) {
90        let mut exit = false;
91        let mut save = false;
92
93        log::trace!("event_result: {event_result:?}");
94
95        let mut event_result = event_result;
96
97        if cfg!(target_os = "windows")
98            && let Ok(EventResult::RepaintNow(window_id)) = event_result
99        {
100            log::trace!("RepaintNow of {window_id:?}");
101            self.windows_next_repaint_times
102                .insert(window_id, Instant::now());
103
104            // Fix flickering on Windows, see https://github.com/emilk/egui/pull/2280
105            event_result = self.winit_app.run_ui_and_paint(event_loop, window_id);
106        }
107
108        let combined_result = event_result.map(|event_result| match event_result {
109            EventResult::Wait => {
110                event_loop.set_control_flow(ControlFlow::Wait);
111                event_result
112            }
113            EventResult::RepaintNow(window_id) => {
114                log::trace!("RepaintNow of {window_id:?}",);
115                self.windows_next_repaint_times
116                    .insert(window_id, Instant::now());
117                event_result
118            }
119            EventResult::RepaintNext(window_id) => {
120                log::trace!("RepaintNext of {window_id:?}",);
121                self.windows_next_repaint_times
122                    .insert(window_id, Instant::now());
123                event_result
124            }
125            EventResult::RepaintAt(window_id, repaint_time) => {
126                self.windows_next_repaint_times.insert(
127                    window_id,
128                    self.windows_next_repaint_times
129                        .get(&window_id)
130                        .map_or(repaint_time, |last| (*last).min(repaint_time)),
131                );
132                event_result
133            }
134            EventResult::Save => {
135                save = true;
136                event_result
137            }
138            EventResult::Exit => {
139                exit = true;
140                event_result
141            }
142            EventResult::CloseRequested => {
143                // The windows need to be dropped whilst the event loop is running to allow for proper cleanup.
144                self.winit_app.save_and_destroy();
145                event_result
146            }
147        });
148
149        if let Err(err) = combined_result {
150            log::error!("Exiting because of error: {err}");
151            exit = true;
152            self.return_result = Err(err);
153        }
154
155        if save {
156            log::debug!("Received an EventResult::Save - saving app state");
157            self.winit_app.save();
158        }
159
160        if exit {
161            if self.run_and_return {
162                log::debug!("Asking to exit event loop…");
163                event_loop.exit();
164            } else {
165                log::debug!("Quitting - saving app state…");
166                self.winit_app.save_and_destroy();
167
168                log::debug!("Exiting with return code 0");
169
170                std::process::exit(0);
171            }
172        }
173
174        self.check_redraw_requests(event_loop);
175    }
176
177    fn check_redraw_requests(&mut self, event_loop: &ActiveEventLoop) {
178        let now = Instant::now();
179
180        self.windows_next_repaint_times
181            .retain(|window_id, repaint_time| {
182                if now < *repaint_time {
183                    return true; // not yet ready
184                }
185
186                event_loop.set_control_flow(ControlFlow::Poll);
187
188                if let Some(window) = self.winit_app.window(*window_id) {
189                    log::trace!("request_redraw for {window_id:?}");
190                    window.request_redraw();
191                } else {
192                    log::trace!("No window found for {window_id:?}");
193                }
194                false
195            });
196
197        let next_repaint_time = self.windows_next_repaint_times.values().min().copied();
198        if let Some(next_repaint_time) = next_repaint_time {
199            event_loop.set_control_flow(ControlFlow::WaitUntil(next_repaint_time));
200        }
201    }
202}
203
204impl<T: WinitApp> ApplicationHandler<UserEvent> for WinitAppWrapper<T> {
205    fn suspended(&mut self, event_loop: &ActiveEventLoop) {
206        profiling::scope!("Event::Suspended");
207
208        event_loop_context::with_event_loop_context(event_loop, move || {
209            let event_result = self.winit_app.suspended(event_loop);
210            self.handle_event_result(event_loop, event_result);
211        });
212    }
213
214    fn resumed(&mut self, event_loop: &ActiveEventLoop) {
215        profiling::scope!("Event::Resumed");
216
217        // Nb: Make sure this guard is dropped after this function returns.
218        event_loop_context::with_event_loop_context(event_loop, move || {
219            let event_result = self.winit_app.resumed(event_loop);
220            self.handle_event_result(event_loop, event_result);
221        });
222    }
223
224    fn exiting(&mut self, event_loop: &ActiveEventLoop) {
225        // On Mac, Cmd-Q we get here and then `run_app_on_demand` doesn't return (despite its name),
226        // so we need to save state now:
227        log::debug!("Received Event::LoopExiting - saving app state…");
228        event_loop_context::with_event_loop_context(event_loop, move || {
229            self.winit_app.save_and_destroy();
230        });
231    }
232
233    fn device_event(
234        &mut self,
235        event_loop: &ActiveEventLoop,
236        device_id: winit::event::DeviceId,
237        event: winit::event::DeviceEvent,
238    ) {
239        profiling::function_scope!(egui_winit::short_device_event_description(&event));
240
241        // Nb: Make sure this guard is dropped after this function returns.
242        event_loop_context::with_event_loop_context(event_loop, move || {
243            let event_result = self.winit_app.device_event(event_loop, device_id, event);
244            self.handle_event_result(event_loop, event_result);
245        });
246    }
247
248    fn user_event(&mut self, event_loop: &ActiveEventLoop, event: UserEvent) {
249        profiling::function_scope!(match &event {
250            UserEvent::RequestRepaint { .. } => "UserEvent::RequestRepaint",
251            #[cfg(feature = "accesskit")]
252            UserEvent::AccessKitActionRequest(_) => "UserEvent::AccessKitActionRequest",
253        });
254
255        event_loop_context::with_event_loop_context(event_loop, move || {
256            let event_result = match event {
257                UserEvent::RequestRepaint {
258                    when,
259                    cumulative_pass_nr,
260                    viewport_id,
261                } => {
262                    let current_pass_nr = self
263                        .winit_app
264                        .egui_ctx()
265                        .map_or(0, |ctx| ctx.cumulative_pass_nr_for(viewport_id));
266                    if current_pass_nr == cumulative_pass_nr
267                        || current_pass_nr == cumulative_pass_nr + 1
268                    {
269                        log::trace!("UserEvent::RequestRepaint scheduling repaint at {when:?}");
270                        if let Some(window_id) =
271                            self.winit_app.window_id_from_viewport_id(viewport_id)
272                        {
273                            Ok(EventResult::RepaintAt(window_id, when))
274                        } else {
275                            Ok(EventResult::Wait)
276                        }
277                    } else {
278                        log::trace!("Got outdated UserEvent::RequestRepaint");
279                        Ok(EventResult::Wait) // old request - we've already repainted
280                    }
281                }
282                #[cfg(feature = "accesskit")]
283                UserEvent::AccessKitActionRequest(request) => {
284                    self.winit_app.on_accesskit_event(request)
285                }
286            };
287            self.handle_event_result(event_loop, event_result);
288        });
289    }
290
291    fn new_events(&mut self, event_loop: &ActiveEventLoop, cause: winit::event::StartCause) {
292        if let winit::event::StartCause::ResumeTimeReached { .. } = cause {
293            log::trace!("Woke up to check next_repaint_time");
294        }
295
296        self.check_redraw_requests(event_loop);
297    }
298
299    fn window_event(
300        &mut self,
301        event_loop: &ActiveEventLoop,
302        window_id: WindowId,
303        event: winit::event::WindowEvent,
304    ) {
305        profiling::function_scope!(egui_winit::short_window_event_description(&event));
306
307        // Nb: Make sure this guard is dropped after this function returns.
308        event_loop_context::with_event_loop_context(event_loop, move || {
309            let event_result = match event {
310                winit::event::WindowEvent::RedrawRequested => {
311                    self.winit_app.run_ui_and_paint(event_loop, window_id)
312                }
313                _ => self.winit_app.window_event(event_loop, window_id, event),
314            };
315
316            self.handle_event_result(event_loop, event_result);
317        });
318    }
319}
320
321#[cfg(not(target_os = "ios"))]
322fn run_and_return(event_loop: &mut EventLoop<UserEvent>, winit_app: impl WinitApp) -> Result {
323    use winit::platform::run_on_demand::EventLoopExtRunOnDemand as _;
324
325    log::trace!("Entering the winit event loop (run_app_on_demand)…");
326
327    let mut app = WinitAppWrapper::new(winit_app, true);
328    event_loop.run_app_on_demand(&mut app)?;
329    log::debug!("eframe window closed");
330    app.return_result
331}
332
333fn run_and_exit(event_loop: EventLoop<UserEvent>, winit_app: impl WinitApp) -> Result {
334    log::trace!("Entering the winit event loop (run_app)…");
335
336    // When to repaint what window
337    let mut app = WinitAppWrapper::new(winit_app, false);
338    event_loop.run_app(&mut app)?;
339
340    log::debug!("winit event loop unexpectedly returned");
341    Ok(())
342}
343
344// ----------------------------------------------------------------------------
345
346#[cfg(feature = "glow")]
347pub fn run_glow(
348    app_name: &str,
349    mut native_options: epi::NativeOptions,
350    app_creator: epi::AppCreator<'_>,
351) -> Result {
352    #![allow(clippy::needless_return_with_question_mark)] // False positive
353
354    use super::glow_integration::GlowWinitApp;
355
356    #[cfg(not(target_os = "ios"))]
357    if native_options.run_and_return {
358        return with_event_loop(native_options, |event_loop, native_options| {
359            let glow_eframe = GlowWinitApp::new(event_loop, app_name, native_options, app_creator);
360            run_and_return(event_loop, glow_eframe)
361        })?;
362    }
363
364    let event_loop = create_event_loop(&mut native_options)?;
365    let glow_eframe = GlowWinitApp::new(&event_loop, app_name, native_options, app_creator);
366    run_and_exit(event_loop, glow_eframe)
367}
368
369#[cfg(feature = "glow")]
370pub fn create_glow<'a>(
371    app_name: &str,
372    native_options: epi::NativeOptions,
373    app_creator: epi::AppCreator<'a>,
374    event_loop: &EventLoop<UserEvent>,
375) -> impl ApplicationHandler<UserEvent> + 'a {
376    use super::glow_integration::GlowWinitApp;
377
378    let glow_eframe = GlowWinitApp::new(event_loop, app_name, native_options, app_creator);
379    WinitAppWrapper::new(glow_eframe, true)
380}
381
382// ----------------------------------------------------------------------------
383
384#[cfg(feature = "wgpu")]
385pub fn run_wgpu(
386    app_name: &str,
387    mut native_options: epi::NativeOptions,
388    app_creator: epi::AppCreator<'_>,
389) -> Result {
390    #![allow(clippy::needless_return_with_question_mark)] // False positive
391
392    use super::wgpu_integration::WgpuWinitApp;
393
394    #[cfg(not(target_os = "ios"))]
395    if native_options.run_and_return {
396        return with_event_loop(native_options, |event_loop, native_options| {
397            let wgpu_eframe = WgpuWinitApp::new(event_loop, app_name, native_options, app_creator);
398            run_and_return(event_loop, wgpu_eframe)
399        })?;
400    }
401
402    let event_loop = create_event_loop(&mut native_options)?;
403    let wgpu_eframe = WgpuWinitApp::new(&event_loop, app_name, native_options, app_creator);
404    run_and_exit(event_loop, wgpu_eframe)
405}
406
407#[cfg(feature = "wgpu")]
408pub fn create_wgpu<'a>(
409    app_name: &str,
410    native_options: epi::NativeOptions,
411    app_creator: epi::AppCreator<'a>,
412    event_loop: &EventLoop<UserEvent>,
413) -> impl ApplicationHandler<UserEvent> + 'a {
414    use super::wgpu_integration::WgpuWinitApp;
415
416    let wgpu_eframe = WgpuWinitApp::new(event_loop, app_name, native_options, app_creator);
417    WinitAppWrapper::new(wgpu_eframe, true)
418}
419
420// ----------------------------------------------------------------------------
421
422/// A proxy to the eframe application that implements [`ApplicationHandler`].
423///
424/// This can be run directly on your own [`EventLoop`] by itself or with other
425/// windows you manage outside of eframe.
426pub struct EframeWinitApplication<'a> {
427    wrapper: Box<dyn ApplicationHandler<UserEvent> + 'a>,
428    control_flow: ControlFlow,
429}
430
431impl ApplicationHandler<UserEvent> for EframeWinitApplication<'_> {
432    fn resumed(&mut self, event_loop: &ActiveEventLoop) {
433        self.wrapper.resumed(event_loop);
434    }
435
436    fn window_event(
437        &mut self,
438        event_loop: &ActiveEventLoop,
439        window_id: winit::window::WindowId,
440        event: winit::event::WindowEvent,
441    ) {
442        self.wrapper.window_event(event_loop, window_id, event);
443    }
444
445    fn new_events(&mut self, event_loop: &ActiveEventLoop, cause: winit::event::StartCause) {
446        self.wrapper.new_events(event_loop, cause);
447    }
448
449    fn user_event(&mut self, event_loop: &ActiveEventLoop, event: UserEvent) {
450        self.wrapper.user_event(event_loop, event);
451    }
452
453    fn device_event(
454        &mut self,
455        event_loop: &ActiveEventLoop,
456        device_id: winit::event::DeviceId,
457        event: winit::event::DeviceEvent,
458    ) {
459        self.wrapper.device_event(event_loop, device_id, event);
460    }
461
462    fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) {
463        self.wrapper.about_to_wait(event_loop);
464        self.control_flow = event_loop.control_flow();
465    }
466
467    fn suspended(&mut self, event_loop: &ActiveEventLoop) {
468        self.wrapper.suspended(event_loop);
469    }
470
471    fn exiting(&mut self, event_loop: &ActiveEventLoop) {
472        self.wrapper.exiting(event_loop);
473    }
474
475    fn memory_warning(&mut self, event_loop: &ActiveEventLoop) {
476        self.wrapper.memory_warning(event_loop);
477    }
478}
479
480impl<'a> EframeWinitApplication<'a> {
481    pub(crate) fn new<T: ApplicationHandler<UserEvent> + 'a>(app: T) -> Self {
482        Self {
483            wrapper: Box::new(app),
484            control_flow: ControlFlow::default(),
485        }
486    }
487
488    /// Pump the `EventLoop` to check for and dispatch pending events to this application.
489    ///
490    /// Returns either the exit code for the application or the final state of the [`ControlFlow`]
491    /// after all events have been dispatched in this iteration.
492    ///
493    /// This is useful when your [`EventLoop`] is not the main event loop for your application.
494    /// See the `external_eventloop_async` example.
495    #[cfg(not(target_os = "ios"))]
496    pub fn pump_eframe_app(
497        &mut self,
498        event_loop: &mut EventLoop<UserEvent>,
499        timeout: Option<std::time::Duration>,
500    ) -> EframePumpStatus {
501        use winit::platform::pump_events::{EventLoopExtPumpEvents as _, PumpStatus};
502
503        match event_loop.pump_app_events(timeout, self) {
504            PumpStatus::Continue => EframePumpStatus::Continue(self.control_flow),
505            PumpStatus::Exit(code) => EframePumpStatus::Exit(code),
506        }
507    }
508}
509
510/// Either an exit code or a [`ControlFlow`] from the [`ActiveEventLoop`].
511///
512/// The result of [`EframeWinitApplication::pump_eframe_app`].
513#[cfg(not(target_os = "ios"))]
514pub enum EframePumpStatus {
515    /// The final state of the [`ControlFlow`] after all events have been dispatched
516    ///
517    /// Callers should perform the action that is appropriate for the [`ControlFlow`] value.
518    Continue(ControlFlow),
519
520    /// The exit code for the application
521    Exit(i32),
522}