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    epi,
14    native::{event_loop_context, winit_integration::EventResult},
15    Result,
16};
17
18// ----------------------------------------------------------------------------
19fn create_event_loop(native_options: &mut epi::NativeOptions) -> Result<EventLoop<UserEvent>> {
20    #[cfg(target_os = "android")]
21    use winit::platform::android::EventLoopBuilderExtAndroid as _;
22
23    profiling::function_scope!();
24    let mut builder = winit::event_loop::EventLoop::with_user_event();
25
26    #[cfg(target_os = "android")]
27    let mut builder =
28        builder.with_android_app(native_options.android_app.take().ok_or_else(|| {
29            crate::Error::AppCreation(Box::from(
30                "`NativeOptions` is missing required `android_app`",
31            ))
32        })?);
33
34    if let Some(hook) = std::mem::take(&mut native_options.event_loop_builder) {
35        hook(&mut builder);
36    }
37
38    profiling::scope!("EventLoopBuilder::build");
39    Ok(builder.build()?)
40}
41
42/// Access a thread-local event loop.
43///
44/// We reuse the event-loop so we can support closing and opening an eframe window
45/// multiple times. This is just a limitation of winit.
46#[cfg(not(target_os = "ios"))]
47fn with_event_loop<R>(
48    mut native_options: epi::NativeOptions,
49    f: impl FnOnce(&mut EventLoop<UserEvent>, epi::NativeOptions) -> R,
50) -> Result<R> {
51    thread_local!(static EVENT_LOOP: std::cell::RefCell<Option<EventLoop<UserEvent>>> = const { std::cell::RefCell::new(None) });
52
53    EVENT_LOOP.with(|event_loop| {
54        // Since we want to reference NativeOptions when creating the EventLoop we can't
55        // do that as part of the lazy thread local storage initialization and so we instead
56        // create the event loop lazily here
57        let mut event_loop_lock = event_loop.borrow_mut();
58        let event_loop = if let Some(event_loop) = &mut *event_loop_lock {
59            event_loop
60        } else {
61            event_loop_lock.insert(create_event_loop(&mut native_options)?)
62        };
63        Ok(f(event_loop, native_options))
64    })
65}
66
67/// Wraps a [`WinitApp`] to implement [`ApplicationHandler`]. This handles redrawing, exit states, and
68/// some events, but otherwise forwards events to the [`WinitApp`].
69struct WinitAppWrapper<T: WinitApp> {
70    windows_next_repaint_times: HashMap<WindowId, Instant>,
71    winit_app: T,
72    return_result: Result<(), crate::Error>,
73    run_and_return: bool,
74}
75
76impl<T: WinitApp> WinitAppWrapper<T> {
77    fn new(winit_app: T, run_and_return: bool) -> Self {
78        Self {
79            windows_next_repaint_times: HashMap::default(),
80            winit_app,
81            return_result: Ok(()),
82            run_and_return,
83        }
84    }
85
86    fn handle_event_result(
87        &mut self,
88        event_loop: &ActiveEventLoop,
89        event_result: Result<EventResult>,
90    ) {
91        let mut exit = false;
92
93        log::trace!("event_result: {event_result:?}");
94
95        let combined_result = event_result.and_then(|event_result| {
96            match event_result {
97                EventResult::Wait => {
98                    event_loop.set_control_flow(ControlFlow::Wait);
99                    Ok(event_result)
100                }
101                EventResult::RepaintNow(window_id) => {
102                    log::trace!("RepaintNow of {window_id:?}",);
103
104                    if cfg!(target_os = "windows") {
105                        // Fix flickering on Windows, see https://github.com/emilk/egui/pull/2280
106                        self.winit_app.run_ui_and_paint(event_loop, window_id)
107                    } else {
108                        // Fix for https://github.com/emilk/egui/issues/2425
109                        self.windows_next_repaint_times
110                            .insert(window_id, Instant::now());
111                        Ok(event_result)
112                    }
113                }
114                EventResult::RepaintNext(window_id) => {
115                    log::trace!("RepaintNext of {window_id:?}",);
116                    self.windows_next_repaint_times
117                        .insert(window_id, Instant::now());
118                    Ok(event_result)
119                }
120                EventResult::RepaintAt(window_id, repaint_time) => {
121                    self.windows_next_repaint_times.insert(
122                        window_id,
123                        self.windows_next_repaint_times
124                            .get(&window_id)
125                            .map_or(repaint_time, |last| (*last).min(repaint_time)),
126                    );
127                    Ok(event_result)
128                }
129                EventResult::Exit => {
130                    exit = true;
131                    Ok(event_result)
132                }
133            }
134        });
135
136        if let Err(err) = combined_result {
137            log::error!("Exiting because of error: {err}");
138            exit = true;
139            self.return_result = Err(err);
140        };
141
142        if exit {
143            if self.run_and_return {
144                log::debug!("Asking to exit event loop…");
145                event_loop.exit();
146            } else {
147                log::debug!("Quitting - saving app state…");
148                self.winit_app.save_and_destroy();
149
150                log::debug!("Exiting with return code 0");
151
152                #[allow(clippy::exit)]
153                std::process::exit(0);
154            }
155        }
156
157        self.check_redraw_requests(event_loop);
158    }
159
160    fn check_redraw_requests(&mut self, event_loop: &ActiveEventLoop) {
161        let now = Instant::now();
162
163        self.windows_next_repaint_times
164            .retain(|window_id, repaint_time| {
165                if now < *repaint_time {
166                    return true; // not yet ready
167                };
168
169                event_loop.set_control_flow(ControlFlow::Poll);
170
171                if let Some(window) = self.winit_app.window(*window_id) {
172                    log::trace!("request_redraw for {window_id:?}");
173                    window.request_redraw();
174                } else {
175                    log::trace!("No window found for {window_id:?}");
176                }
177                false
178            });
179
180        let next_repaint_time = self.windows_next_repaint_times.values().min().copied();
181        if let Some(next_repaint_time) = next_repaint_time {
182            event_loop.set_control_flow(ControlFlow::WaitUntil(next_repaint_time));
183        };
184    }
185}
186
187impl<T: WinitApp> ApplicationHandler<UserEvent> for WinitAppWrapper<T> {
188    fn suspended(&mut self, event_loop: &ActiveEventLoop) {
189        profiling::scope!("Event::Suspended");
190
191        event_loop_context::with_event_loop_context(event_loop, move || {
192            let event_result = self.winit_app.suspended(event_loop);
193            self.handle_event_result(event_loop, event_result);
194        });
195    }
196
197    fn resumed(&mut self, event_loop: &ActiveEventLoop) {
198        profiling::scope!("Event::Resumed");
199
200        // Nb: Make sure this guard is dropped after this function returns.
201        event_loop_context::with_event_loop_context(event_loop, move || {
202            let event_result = self.winit_app.resumed(event_loop);
203            self.handle_event_result(event_loop, event_result);
204        });
205    }
206
207    fn exiting(&mut self, event_loop: &ActiveEventLoop) {
208        // On Mac, Cmd-Q we get here and then `run_app_on_demand` doesn't return (despite its name),
209        // so we need to save state now:
210        log::debug!("Received Event::LoopExiting - saving app state…");
211        event_loop_context::with_event_loop_context(event_loop, move || {
212            self.winit_app.save_and_destroy();
213        });
214    }
215
216    fn device_event(
217        &mut self,
218        event_loop: &ActiveEventLoop,
219        device_id: winit::event::DeviceId,
220        event: winit::event::DeviceEvent,
221    ) {
222        profiling::function_scope!(egui_winit::short_device_event_description(&event));
223
224        // Nb: Make sure this guard is dropped after this function returns.
225        event_loop_context::with_event_loop_context(event_loop, move || {
226            let event_result = self.winit_app.device_event(event_loop, device_id, event);
227            self.handle_event_result(event_loop, event_result);
228        });
229    }
230
231    fn user_event(&mut self, event_loop: &ActiveEventLoop, event: UserEvent) {
232        profiling::function_scope!(match &event {
233            UserEvent::RequestRepaint { .. } => "UserEvent::RequestRepaint",
234            #[cfg(feature = "accesskit")]
235            UserEvent::AccessKitActionRequest(_) => "UserEvent::AccessKitActionRequest",
236        });
237
238        event_loop_context::with_event_loop_context(event_loop, move || {
239            let event_result = match event {
240                UserEvent::RequestRepaint {
241                    when,
242                    cumulative_pass_nr,
243                    viewport_id,
244                } => {
245                    let current_pass_nr = self
246                        .winit_app
247                        .egui_ctx()
248                        .map_or(0, |ctx| ctx.cumulative_pass_nr_for(viewport_id));
249                    if current_pass_nr == cumulative_pass_nr
250                        || current_pass_nr == cumulative_pass_nr + 1
251                    {
252                        log::trace!("UserEvent::RequestRepaint scheduling repaint at {when:?}");
253                        if let Some(window_id) =
254                            self.winit_app.window_id_from_viewport_id(viewport_id)
255                        {
256                            Ok(EventResult::RepaintAt(window_id, when))
257                        } else {
258                            Ok(EventResult::Wait)
259                        }
260                    } else {
261                        log::trace!("Got outdated UserEvent::RequestRepaint");
262                        Ok(EventResult::Wait) // old request - we've already repainted
263                    }
264                }
265                #[cfg(feature = "accesskit")]
266                UserEvent::AccessKitActionRequest(request) => {
267                    self.winit_app.on_accesskit_event(request)
268                }
269            };
270            self.handle_event_result(event_loop, event_result);
271        });
272    }
273
274    fn new_events(&mut self, event_loop: &ActiveEventLoop, cause: winit::event::StartCause) {
275        if let winit::event::StartCause::ResumeTimeReached { .. } = cause {
276            log::trace!("Woke up to check next_repaint_time");
277        }
278
279        self.check_redraw_requests(event_loop);
280    }
281
282    fn window_event(
283        &mut self,
284        event_loop: &ActiveEventLoop,
285        window_id: WindowId,
286        event: winit::event::WindowEvent,
287    ) {
288        profiling::function_scope!(egui_winit::short_window_event_description(&event));
289
290        // Nb: Make sure this guard is dropped after this function returns.
291        event_loop_context::with_event_loop_context(event_loop, move || {
292            let event_result = match event {
293                winit::event::WindowEvent::RedrawRequested => {
294                    self.winit_app.run_ui_and_paint(event_loop, window_id)
295                }
296                _ => self.winit_app.window_event(event_loop, window_id, event),
297            };
298
299            self.handle_event_result(event_loop, event_result);
300        });
301    }
302}
303
304#[cfg(not(target_os = "ios"))]
305fn run_and_return(event_loop: &mut EventLoop<UserEvent>, winit_app: impl WinitApp) -> Result {
306    use winit::platform::run_on_demand::EventLoopExtRunOnDemand;
307
308    log::trace!("Entering the winit event loop (run_app_on_demand)…");
309
310    let mut app = WinitAppWrapper::new(winit_app, true);
311    event_loop.run_app_on_demand(&mut app)?;
312    log::debug!("eframe window closed");
313    app.return_result
314}
315
316fn run_and_exit(event_loop: EventLoop<UserEvent>, winit_app: impl WinitApp) -> Result {
317    log::trace!("Entering the winit event loop (run_app)…");
318
319    // When to repaint what window
320    let mut app = WinitAppWrapper::new(winit_app, false);
321    event_loop.run_app(&mut app)?;
322
323    log::debug!("winit event loop unexpectedly returned");
324    Ok(())
325}
326
327// ----------------------------------------------------------------------------
328
329#[cfg(feature = "glow")]
330pub fn run_glow(
331    app_name: &str,
332    mut native_options: epi::NativeOptions,
333    app_creator: epi::AppCreator<'_>,
334) -> Result {
335    #![allow(clippy::needless_return_with_question_mark)] // False positive
336
337    use super::glow_integration::GlowWinitApp;
338
339    #[cfg(not(target_os = "ios"))]
340    if native_options.run_and_return {
341        return with_event_loop(native_options, |event_loop, native_options| {
342            let glow_eframe = GlowWinitApp::new(event_loop, app_name, native_options, app_creator);
343            run_and_return(event_loop, glow_eframe)
344        })?;
345    }
346
347    let event_loop = create_event_loop(&mut native_options)?;
348    let glow_eframe = GlowWinitApp::new(&event_loop, app_name, native_options, app_creator);
349    run_and_exit(event_loop, glow_eframe)
350}
351
352// ----------------------------------------------------------------------------
353
354#[cfg(feature = "wgpu")]
355pub fn run_wgpu(
356    app_name: &str,
357    mut native_options: epi::NativeOptions,
358    app_creator: epi::AppCreator<'_>,
359) -> Result {
360    #![allow(clippy::needless_return_with_question_mark)] // False positive
361
362    use super::wgpu_integration::WgpuWinitApp;
363
364    #[cfg(not(target_os = "ios"))]
365    if native_options.run_and_return {
366        return with_event_loop(native_options, |event_loop, native_options| {
367            let wgpu_eframe = WgpuWinitApp::new(event_loop, app_name, native_options, app_creator);
368            run_and_return(event_loop, wgpu_eframe)
369        })?;
370    }
371
372    let event_loop = create_event_loop(&mut native_options)?;
373    let wgpu_eframe = WgpuWinitApp::new(&event_loop, app_name, native_options, app_creator);
374    run_and_exit(event_loop, wgpu_eframe)
375}