mirror of
https://github.com/emilk/egui.git
synced 2026-08-29 04:40:03 -04:00
Closes #8326 `check_redraw_requests` switched the event loop to `ControlFlow::Poll` every time it called `request_redraw`, and only ever restored a sleeping control flow when a *timed* repaint was still pending. Once the last scheduled repaint had been consumed the `Poll` was never undone, so the loop kept spinning. This is most visible on Wayland, where `RedrawRequested` is only delivered after the compositor sends a frame callback: between the request and the callback eframe burns 100% of a CPU core, so simply moving the mouse over a reactive app pegs a core. `request_redraw` already wakes the event loop on its own, so the `Poll` is not needed. Drop it, and always set an explicit sleeping control flow at the end of `check_redraw_requests`: `WaitUntil` for the earliest scheduled repaint, `Wait` when nothing is scheduled. **Measured effect of this patch** Two byte-identical eframe apps (a 400-row scrolling page, free-running at 60fps), toggling only whether `eframe` resolves to stock 0.36.0 or this patch. Same machine and session, native Wayland (niri, wgpu/Vulkan). Whole-process CPU is `utime+stime` from `/proc/self/stat`, so it counts every thread — what a system monitor sees. | configuration | whole process | |---|---| | eframe 0.34.3, Wayland | ~12% of a core | | stock 0.36.0, Wayland | **99–100% of a core** | | stock 0.36.0, XWayland (same binary) | ~15% of a core | | **0.36.0 + this patch, Wayland** | **15–16% of a core** | The patch restores the 0.34 baseline and matches the XWayland figure for the same binary — ~6.5× less CPU — with frame delivery unchanged at 60fps. Two details worth noting: the same 0.36 binary is already fine on XWayland, so this isn't application repaint behaviour; and the per-frame *closure* cost rises slightly (1.55 to 2.15 ms) because those frames now run on a CPU that isn't being held at max clocks by the spin loop. * [X] I have followed the instructions in the PR template
589 lines
22 KiB
Rust
589 lines
22 KiB
Rust
use core::time::Duration;
|
|
use std::time::Instant;
|
|
|
|
use winit::{
|
|
application::ApplicationHandler,
|
|
event_loop::{ActiveEventLoop, ControlFlow, EventLoop},
|
|
window::WindowId,
|
|
};
|
|
|
|
use ahash::HashMap;
|
|
|
|
use super::winit_integration::{UserEvent, WinitApp};
|
|
use crate::{
|
|
Result, epi,
|
|
native::{
|
|
event_loop_context,
|
|
winit_integration::{EventResult, is_invisible_or_minimized},
|
|
},
|
|
};
|
|
|
|
/// Minimum interval between repaints for invisible windows.
|
|
///
|
|
/// On Windows, invisible windows don't receive `RedrawRequested` events,
|
|
/// so we throttle their repaints to avoid busy-looping while still
|
|
/// processing viewport commands like `Visible(true)`.
|
|
/// See <https://github.com/emilk/egui/issues/7776>.
|
|
const INVISIBLE_WINDOW_REPAINT_INTERVAL: Duration = Duration::from_millis(100);
|
|
|
|
// ----------------------------------------------------------------------------
|
|
fn create_event_loop(native_options: &mut epi::NativeOptions) -> Result<EventLoop<UserEvent>> {
|
|
#[cfg(target_os = "android")]
|
|
use winit::platform::android::EventLoopBuilderExtAndroid as _;
|
|
|
|
profiling::function_scope!();
|
|
let mut builder = winit::event_loop::EventLoop::with_user_event();
|
|
|
|
#[cfg(target_os = "android")]
|
|
let mut builder =
|
|
builder.with_android_app(native_options.android_app.take().ok_or_else(|| {
|
|
crate::Error::AppCreation(Box::from(
|
|
"`NativeOptions` is missing required `android_app`",
|
|
))
|
|
})?);
|
|
|
|
if let Some(hook) = core::mem::take(&mut native_options.event_loop_builder) {
|
|
hook(&mut builder);
|
|
}
|
|
|
|
profiling::scope!("EventLoopBuilder::build");
|
|
Ok(builder.build()?)
|
|
}
|
|
|
|
/// Access a thread-local event loop.
|
|
///
|
|
/// We reuse the event-loop so we can support closing and opening an eframe window
|
|
/// multiple times. This is just a limitation of winit.
|
|
#[cfg(not(target_os = "ios"))]
|
|
fn with_event_loop<R>(
|
|
mut native_options: epi::NativeOptions,
|
|
f: impl FnOnce(&mut EventLoop<UserEvent>, epi::NativeOptions) -> R,
|
|
) -> Result<R> {
|
|
thread_local!(static EVENT_LOOP: core::cell::RefCell<Option<EventLoop<UserEvent>>> = const { core::cell::RefCell::new(None) });
|
|
|
|
EVENT_LOOP.with(|event_loop| {
|
|
// Since we want to reference NativeOptions when creating the EventLoop we can't
|
|
// do that as part of the lazy thread local storage initialization and so we instead
|
|
// create the event loop lazily here
|
|
let mut event_loop_lock = event_loop.borrow_mut();
|
|
let event_loop = if let Some(event_loop) = &mut *event_loop_lock {
|
|
event_loop
|
|
} else {
|
|
event_loop_lock.insert(create_event_loop(&mut native_options)?)
|
|
};
|
|
Ok(f(event_loop, native_options))
|
|
})
|
|
}
|
|
|
|
/// Wraps a [`WinitApp`] to implement [`ApplicationHandler`]. This handles redrawing, exit states, and
|
|
/// some events, but otherwise forwards events to the [`WinitApp`].
|
|
struct WinitAppWrapper<T: WinitApp> {
|
|
windows_next_repaint_times: HashMap<WindowId, Instant>,
|
|
winit_app: T,
|
|
return_result: Result<(), crate::Error>,
|
|
run_and_return: bool,
|
|
}
|
|
|
|
impl<T: WinitApp> WinitAppWrapper<T> {
|
|
fn new(winit_app: T, run_and_return: bool) -> Self {
|
|
Self {
|
|
windows_next_repaint_times: HashMap::default(),
|
|
winit_app,
|
|
return_result: Ok(()),
|
|
run_and_return,
|
|
}
|
|
}
|
|
|
|
fn handle_event_result(
|
|
&mut self,
|
|
event_loop: &ActiveEventLoop,
|
|
event_result: Result<EventResult>,
|
|
) {
|
|
let mut exit = false;
|
|
let mut save = false;
|
|
|
|
log::trace!("event_result: {event_result:?}");
|
|
|
|
let mut event_result = event_result;
|
|
|
|
if cfg!(target_os = "windows")
|
|
&& let Ok(EventResult::RepaintNow(window_id)) = event_result
|
|
{
|
|
log::trace!("RepaintNow of {window_id:?}");
|
|
self.windows_next_repaint_times
|
|
.insert(window_id, Instant::now());
|
|
|
|
// Fix flickering on Windows, see https://github.com/emilk/egui/pull/2280
|
|
event_result = self.winit_app.run_ui_and_paint(event_loop, window_id);
|
|
}
|
|
|
|
let combined_result = event_result.map(|event_result| match event_result {
|
|
EventResult::Wait => {
|
|
event_loop.set_control_flow(ControlFlow::Wait);
|
|
event_result
|
|
}
|
|
EventResult::RepaintNow(window_id) => {
|
|
log::trace!("RepaintNow of {window_id:?}");
|
|
self.windows_next_repaint_times
|
|
.insert(window_id, Instant::now());
|
|
event_result
|
|
}
|
|
EventResult::RepaintNext(window_id) => {
|
|
log::trace!("RepaintNext of {window_id:?}");
|
|
self.windows_next_repaint_times
|
|
.insert(window_id, Instant::now());
|
|
event_result
|
|
}
|
|
EventResult::RepaintAt(window_id, repaint_time) => {
|
|
self.windows_next_repaint_times.insert(
|
|
window_id,
|
|
self.windows_next_repaint_times
|
|
.get(&window_id)
|
|
.map_or(repaint_time, |last| (*last).min(repaint_time)),
|
|
);
|
|
event_result
|
|
}
|
|
EventResult::Save => {
|
|
save = true;
|
|
event_result
|
|
}
|
|
EventResult::Exit => {
|
|
exit = true;
|
|
event_result
|
|
}
|
|
EventResult::CloseRequested => {
|
|
// The windows need to be dropped whilst the event loop is running to allow for proper cleanup.
|
|
self.winit_app.save_and_destroy();
|
|
event_result
|
|
}
|
|
});
|
|
|
|
if let Err(err) = combined_result {
|
|
log::error!("Exiting because of error: {err}");
|
|
exit = true;
|
|
self.return_result = Err(err);
|
|
}
|
|
|
|
if save {
|
|
log::debug!("Received an EventResult::Save - saving app state");
|
|
self.winit_app.save();
|
|
}
|
|
|
|
if exit {
|
|
if self.run_and_return {
|
|
log::debug!("Asking to exit event loop…");
|
|
event_loop.exit();
|
|
} else {
|
|
log::debug!("Quitting - saving app state…");
|
|
self.winit_app.save_and_destroy();
|
|
|
|
log::debug!("Exiting with return code 0");
|
|
|
|
std::process::exit(0);
|
|
}
|
|
}
|
|
|
|
self.check_redraw_requests(event_loop);
|
|
}
|
|
|
|
fn check_redraw_requests(&mut self, event_loop: &ActiveEventLoop) {
|
|
let now = Instant::now();
|
|
|
|
let mut invisible_window_ids = Vec::new();
|
|
|
|
self.windows_next_repaint_times
|
|
.retain(|window_id, repaint_time| {
|
|
if now < *repaint_time {
|
|
return true; // not yet ready
|
|
}
|
|
|
|
if let Some(window) = self.winit_app.window(*window_id) {
|
|
// On Windows, invisible windows don't receive RedrawRequested
|
|
// events, so pending viewport commands (e.g. Visible(true)) would
|
|
// never be processed. We collect these windows to paint them
|
|
// directly below.
|
|
// See: https://github.com/emilk/egui/issues/5229
|
|
if is_invisible_or_minimized(&window) {
|
|
invisible_window_ids.push(*window_id);
|
|
} else {
|
|
log::trace!("request_redraw for {window_id:?}");
|
|
// Don't switch to `ControlFlow::Poll` here. `request_redraw`
|
|
// is enough to wake the event loop, and on Wayland the
|
|
// `RedrawRequested` event is only delivered once the
|
|
// compositor sends a frame callback. Polling in the meantime
|
|
// busy-loops a whole CPU core.
|
|
// See https://github.com/emilk/egui/issues/8326.
|
|
window.request_redraw();
|
|
}
|
|
} else {
|
|
log::trace!("No window found for {window_id:?}");
|
|
}
|
|
false
|
|
});
|
|
|
|
// Paint invisible windows directly, since they won't receive
|
|
// RedrawRequested events on Windows. This ensures that viewport
|
|
// commands like Visible(true) are still processed.
|
|
for window_id in &invisible_window_ids {
|
|
let event_result = self.winit_app.run_ui_and_paint(event_loop, *window_id);
|
|
self.handle_event_result(event_loop, event_result);
|
|
}
|
|
|
|
// Throttle any already-scheduled repaints for invisible windows
|
|
// to avoid busy-looping. If no repaint was requested by the app,
|
|
// the window will simply sleep.
|
|
// See: https://github.com/emilk/egui/issues/7776
|
|
if !invisible_window_ids.is_empty() {
|
|
let next_paint = Instant::now() + INVISIBLE_WINDOW_REPAINT_INTERVAL;
|
|
for window_id in &invisible_window_ids {
|
|
self.windows_next_repaint_times
|
|
.entry(*window_id)
|
|
.and_modify(|t| *t = (*t).min(next_paint));
|
|
}
|
|
}
|
|
|
|
// Always set an explicit, sleeping control flow. Previously we only set
|
|
// `WaitUntil` when a repaint was already scheduled, which meant that a
|
|
// `ControlFlow::Poll` set earlier was never undone once the last timed
|
|
// repaint had been consumed, leaving the loop spinning.
|
|
// See https://github.com/emilk/egui/issues/8326.
|
|
let next_repaint_time = self.windows_next_repaint_times.values().min().copied();
|
|
event_loop.set_control_flow(match next_repaint_time {
|
|
Some(next_repaint_time) => ControlFlow::WaitUntil(next_repaint_time),
|
|
None => ControlFlow::Wait,
|
|
});
|
|
}
|
|
}
|
|
|
|
impl<T: WinitApp> ApplicationHandler<UserEvent> for WinitAppWrapper<T> {
|
|
fn suspended(&mut self, event_loop: &ActiveEventLoop) {
|
|
profiling::scope!("Event::Suspended");
|
|
|
|
event_loop_context::with_event_loop_context(event_loop, move || {
|
|
let event_result = self.winit_app.suspended(event_loop);
|
|
self.handle_event_result(event_loop, event_result);
|
|
});
|
|
}
|
|
|
|
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
|
|
profiling::scope!("Event::Resumed");
|
|
|
|
// Nb: Make sure this guard is dropped after this function returns.
|
|
event_loop_context::with_event_loop_context(event_loop, move || {
|
|
let event_result = self.winit_app.resumed(event_loop);
|
|
self.handle_event_result(event_loop, event_result);
|
|
});
|
|
}
|
|
|
|
fn exiting(&mut self, event_loop: &ActiveEventLoop) {
|
|
// On Mac, Cmd-Q we get here and then `run_app_on_demand` doesn't return (despite its name),
|
|
// so we need to save state now:
|
|
log::debug!("Received Event::LoopExiting - saving app state…");
|
|
event_loop_context::with_event_loop_context(event_loop, move || {
|
|
self.winit_app.save_and_destroy();
|
|
});
|
|
}
|
|
|
|
fn device_event(
|
|
&mut self,
|
|
event_loop: &ActiveEventLoop,
|
|
device_id: winit::event::DeviceId,
|
|
event: winit::event::DeviceEvent,
|
|
) {
|
|
profiling::function_scope!(egui_winit::short_device_event_description(&event));
|
|
|
|
// Nb: Make sure this guard is dropped after this function returns.
|
|
event_loop_context::with_event_loop_context(event_loop, move || {
|
|
let event_result = self.winit_app.device_event(event_loop, device_id, event);
|
|
self.handle_event_result(event_loop, event_result);
|
|
});
|
|
}
|
|
|
|
fn user_event(&mut self, event_loop: &ActiveEventLoop, event: UserEvent) {
|
|
profiling::function_scope!(match &event {
|
|
UserEvent::RequestRepaint { .. } => "UserEvent::RequestRepaint",
|
|
#[cfg(feature = "accesskit")]
|
|
UserEvent::AccessKitActionRequest(_) => "UserEvent::AccessKitActionRequest",
|
|
});
|
|
|
|
event_loop_context::with_event_loop_context(event_loop, move || {
|
|
let event_result = match event {
|
|
UserEvent::RequestRepaint {
|
|
when,
|
|
cumulative_pass_nr,
|
|
viewport_id,
|
|
} => {
|
|
let current_pass_nr = self
|
|
.winit_app
|
|
.egui_ctx()
|
|
.map_or(0, |ctx| ctx.cumulative_pass_nr_for(viewport_id));
|
|
if current_pass_nr == cumulative_pass_nr
|
|
|| current_pass_nr == cumulative_pass_nr + 1
|
|
{
|
|
log::trace!("UserEvent::RequestRepaint scheduling repaint at {when:?}");
|
|
if let Some(window_id) =
|
|
self.winit_app.window_id_from_viewport_id(viewport_id)
|
|
{
|
|
// Throttle repaints for invisible windows to prevent
|
|
// high CPU usage on Windows.
|
|
// See: https://github.com/emilk/egui/issues/7776
|
|
let when = if let Some(window) = self.winit_app.window(window_id)
|
|
&& is_invisible_or_minimized(&window)
|
|
{
|
|
when.max(Instant::now() + INVISIBLE_WINDOW_REPAINT_INTERVAL)
|
|
} else {
|
|
when
|
|
};
|
|
Ok(EventResult::RepaintAt(window_id, when))
|
|
} else {
|
|
Ok(EventResult::Wait)
|
|
}
|
|
} else {
|
|
log::trace!("Got outdated UserEvent::RequestRepaint");
|
|
Ok(EventResult::Wait) // old request - we've already repainted
|
|
}
|
|
}
|
|
#[cfg(feature = "accesskit")]
|
|
UserEvent::AccessKitActionRequest(request) => {
|
|
self.winit_app.on_accesskit_event(request)
|
|
}
|
|
};
|
|
self.handle_event_result(event_loop, event_result);
|
|
});
|
|
}
|
|
|
|
fn new_events(&mut self, event_loop: &ActiveEventLoop, cause: winit::event::StartCause) {
|
|
if let winit::event::StartCause::ResumeTimeReached { .. } = cause {
|
|
log::trace!("Woke up to check next_repaint_time");
|
|
}
|
|
|
|
self.check_redraw_requests(event_loop);
|
|
}
|
|
|
|
fn window_event(
|
|
&mut self,
|
|
event_loop: &ActiveEventLoop,
|
|
window_id: WindowId,
|
|
event: winit::event::WindowEvent,
|
|
) {
|
|
profiling::function_scope!(egui_winit::short_window_event_description(&event));
|
|
|
|
// Nb: Make sure this guard is dropped after this function returns.
|
|
event_loop_context::with_event_loop_context(event_loop, move || {
|
|
let event_result = match event {
|
|
winit::event::WindowEvent::RedrawRequested => {
|
|
self.winit_app.run_ui_and_paint(event_loop, window_id)
|
|
}
|
|
_ => self.winit_app.window_event(event_loop, window_id, event),
|
|
};
|
|
|
|
self.handle_event_result(event_loop, event_result);
|
|
});
|
|
}
|
|
}
|
|
|
|
#[cfg(not(target_os = "ios"))]
|
|
fn run_and_return(event_loop: &mut EventLoop<UserEvent>, winit_app: impl WinitApp) -> Result {
|
|
use winit::platform::run_on_demand::EventLoopExtRunOnDemand as _;
|
|
|
|
log::trace!("Entering the winit event loop (run_app_on_demand)…");
|
|
|
|
let mut app = WinitAppWrapper::new(winit_app, true);
|
|
event_loop.run_app_on_demand(&mut app)?;
|
|
log::debug!("eframe window closed");
|
|
app.return_result
|
|
}
|
|
|
|
fn run_and_exit(event_loop: EventLoop<UserEvent>, winit_app: impl WinitApp) -> Result {
|
|
log::trace!("Entering the winit event loop (run_app)…");
|
|
|
|
// When to repaint what window
|
|
let mut app = WinitAppWrapper::new(winit_app, false);
|
|
event_loop.run_app(&mut app)?;
|
|
|
|
log::debug!("winit event loop unexpectedly returned");
|
|
Ok(())
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
#[cfg(feature = "glow")]
|
|
pub fn run_glow(
|
|
app_name: &str,
|
|
mut native_options: epi::NativeOptions,
|
|
egui_ctx: Option<egui::Context>,
|
|
app_creator: epi::AppCreator<'_>,
|
|
) -> Result {
|
|
use super::glow_integration::GlowWinitApp;
|
|
|
|
#[cfg(not(target_os = "ios"))]
|
|
if native_options.run_and_return {
|
|
return with_event_loop(native_options, |event_loop, native_options| {
|
|
let glow_eframe =
|
|
GlowWinitApp::new(event_loop, app_name, native_options, egui_ctx, app_creator);
|
|
run_and_return(event_loop, glow_eframe)
|
|
})?;
|
|
}
|
|
|
|
let event_loop = create_event_loop(&mut native_options)?;
|
|
let glow_eframe =
|
|
GlowWinitApp::new(&event_loop, app_name, native_options, egui_ctx, app_creator);
|
|
run_and_exit(event_loop, glow_eframe)
|
|
}
|
|
|
|
#[cfg(feature = "glow")]
|
|
pub fn create_glow<'a>(
|
|
app_name: &str,
|
|
native_options: epi::NativeOptions,
|
|
app_creator: epi::AppCreator<'a>,
|
|
event_loop: &EventLoop<UserEvent>,
|
|
) -> impl ApplicationHandler<UserEvent> + 'a {
|
|
use super::glow_integration::GlowWinitApp;
|
|
|
|
let glow_eframe = GlowWinitApp::new(event_loop, app_name, native_options, None, app_creator);
|
|
WinitAppWrapper::new(glow_eframe, true)
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
#[cfg(feature = "wgpu_no_default_features")]
|
|
pub fn run_wgpu(
|
|
app_name: &str,
|
|
mut native_options: epi::NativeOptions,
|
|
egui_ctx: Option<egui::Context>,
|
|
app_creator: epi::AppCreator<'_>,
|
|
) -> Result {
|
|
use super::wgpu_integration::WgpuWinitApp;
|
|
|
|
#[cfg(not(target_os = "ios"))]
|
|
if native_options.run_and_return {
|
|
return with_event_loop(native_options, |event_loop, native_options| {
|
|
let wgpu_eframe =
|
|
WgpuWinitApp::new(event_loop, app_name, native_options, egui_ctx, app_creator);
|
|
run_and_return(event_loop, wgpu_eframe)
|
|
})?;
|
|
}
|
|
|
|
let event_loop = create_event_loop(&mut native_options)?;
|
|
let wgpu_eframe =
|
|
WgpuWinitApp::new(&event_loop, app_name, native_options, egui_ctx, app_creator);
|
|
run_and_exit(event_loop, wgpu_eframe)
|
|
}
|
|
|
|
#[cfg(feature = "wgpu_no_default_features")]
|
|
pub fn create_wgpu<'a>(
|
|
app_name: &str,
|
|
native_options: epi::NativeOptions,
|
|
app_creator: epi::AppCreator<'a>,
|
|
event_loop: &EventLoop<UserEvent>,
|
|
) -> impl ApplicationHandler<UserEvent> + 'a {
|
|
use super::wgpu_integration::WgpuWinitApp;
|
|
|
|
let wgpu_eframe = WgpuWinitApp::new(event_loop, app_name, native_options, None, app_creator);
|
|
WinitAppWrapper::new(wgpu_eframe, true)
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
/// A proxy to the eframe application that implements [`ApplicationHandler`].
|
|
///
|
|
/// This can be run directly on your own [`EventLoop`] by itself or with other
|
|
/// windows you manage outside of eframe.
|
|
pub struct EframeWinitApplication<'a> {
|
|
wrapper: Box<dyn ApplicationHandler<UserEvent> + 'a>,
|
|
control_flow: ControlFlow,
|
|
}
|
|
|
|
impl ApplicationHandler<UserEvent> for EframeWinitApplication<'_> {
|
|
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
|
|
self.wrapper.resumed(event_loop);
|
|
}
|
|
|
|
fn window_event(
|
|
&mut self,
|
|
event_loop: &ActiveEventLoop,
|
|
window_id: winit::window::WindowId,
|
|
event: winit::event::WindowEvent,
|
|
) {
|
|
self.wrapper.window_event(event_loop, window_id, event);
|
|
}
|
|
|
|
fn new_events(&mut self, event_loop: &ActiveEventLoop, cause: winit::event::StartCause) {
|
|
self.wrapper.new_events(event_loop, cause);
|
|
}
|
|
|
|
fn user_event(&mut self, event_loop: &ActiveEventLoop, event: UserEvent) {
|
|
self.wrapper.user_event(event_loop, event);
|
|
}
|
|
|
|
fn device_event(
|
|
&mut self,
|
|
event_loop: &ActiveEventLoop,
|
|
device_id: winit::event::DeviceId,
|
|
event: winit::event::DeviceEvent,
|
|
) {
|
|
self.wrapper.device_event(event_loop, device_id, event);
|
|
}
|
|
|
|
fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) {
|
|
self.wrapper.about_to_wait(event_loop);
|
|
self.control_flow = event_loop.control_flow();
|
|
}
|
|
|
|
fn suspended(&mut self, event_loop: &ActiveEventLoop) {
|
|
self.wrapper.suspended(event_loop);
|
|
}
|
|
|
|
fn exiting(&mut self, event_loop: &ActiveEventLoop) {
|
|
self.wrapper.exiting(event_loop);
|
|
}
|
|
|
|
fn memory_warning(&mut self, event_loop: &ActiveEventLoop) {
|
|
self.wrapper.memory_warning(event_loop);
|
|
}
|
|
}
|
|
|
|
impl<'a> EframeWinitApplication<'a> {
|
|
pub(crate) fn new<T: ApplicationHandler<UserEvent> + 'a>(app: T) -> Self {
|
|
Self {
|
|
wrapper: Box::new(app),
|
|
control_flow: ControlFlow::default(),
|
|
}
|
|
}
|
|
|
|
/// Pump the `EventLoop` to check for and dispatch pending events to this application.
|
|
///
|
|
/// Returns either the exit code for the application or the final state of the [`ControlFlow`]
|
|
/// after all events have been dispatched in this iteration.
|
|
///
|
|
/// This is useful when your [`EventLoop`] is not the main event loop for your application.
|
|
/// See the `external_eventloop_async` example.
|
|
#[cfg(not(target_os = "ios"))]
|
|
pub fn pump_eframe_app(
|
|
&mut self,
|
|
event_loop: &mut EventLoop<UserEvent>,
|
|
timeout: Option<core::time::Duration>,
|
|
) -> EframePumpStatus {
|
|
use winit::platform::pump_events::{EventLoopExtPumpEvents as _, PumpStatus};
|
|
|
|
match event_loop.pump_app_events(timeout, self) {
|
|
PumpStatus::Continue => EframePumpStatus::Continue(self.control_flow),
|
|
PumpStatus::Exit(code) => EframePumpStatus::Exit(code),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Either an exit code or a [`ControlFlow`] from the [`ActiveEventLoop`].
|
|
///
|
|
/// The result of [`EframeWinitApplication::pump_eframe_app`].
|
|
#[cfg(not(target_os = "ios"))]
|
|
pub enum EframePumpStatus {
|
|
/// The final state of the [`ControlFlow`] after all events have been dispatched
|
|
///
|
|
/// Callers should perform the action that is appropriate for the [`ControlFlow`] value.
|
|
Continue(ControlFlow),
|
|
|
|
/// The exit code for the application
|
|
Exit(i32),
|
|
}
|