1
0
mirror of https://github.com/emilk/egui.git synced 2026-06-27 15:13:12 -04:00
Files
egui/crates/eframe/src/native/run.rs
Oscar Gustafsson cd45d18615 Do no use the ahash reimport (#4504)
<!--
Please read the "Making a PR" section of
[`CONTRIBUTING.md`](https://github.com/emilk/egui/blob/master/CONTRIBUTING.md)
before opening a Pull Request!

* Keep your PR:s small and focused.
* The PR title is what ends up in the changelog, so make it descriptive!
* If applicable, add a screenshot or gif.
* If it is a non-trivial addition, consider adding a demo for it to
`egui_demo_lib`, or a new example.
* Do NOT open PR:s from your `master` branch, as that makes it hard for
maintainers to add commits to your PR.
* Remember to run `cargo fmt` and `cargo clippy`.
* Open the PR as a draft until you have self-reviewed it and run
`./scripts/check.sh`.
* When you have addressed a PR comment, mark it as resolved.

Please be patient! I will review your PR, but my time is limited!
-->

Related to #3482 

Not sure what the "best practice" is, to me it seems like one should
import from "the original location" if possible, but now it should at
least be possible to not re-export ahash without any breakage in the
egui code base (but possibly in projects using egui, so one should
probably deprecate it if one would like to go that path). It also seems
like epaint re-exports ahash.
2024-05-27 16:24:50 +02:00

435 lines
16 KiB
Rust

use std::{cell::RefCell, time::Instant};
use winit::event_loop::{EventLoop, EventLoopBuilder};
use ahash::HashMap;
use crate::{
epi,
native::winit_integration::{short_event_description, EventResult},
Result,
};
use super::winit_integration::{UserEvent, WinitApp};
// ----------------------------------------------------------------------------
fn create_event_loop_builder(
native_options: &mut epi::NativeOptions,
) -> EventLoopBuilder<UserEvent> {
crate::profile_function!();
let mut event_loop_builder = winit::event_loop::EventLoopBuilder::with_user_event();
if let Some(hook) = std::mem::take(&mut native_options.event_loop_builder) {
hook(&mut event_loop_builder);
}
event_loop_builder
}
fn create_event_loop(native_options: &mut epi::NativeOptions) -> Result<EventLoop<UserEvent>> {
crate::profile_function!();
let mut builder = create_event_loop_builder(native_options);
crate::profile_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.
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: RefCell<Option<EventLoop<UserEvent>>> = 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))
})
}
#[cfg(not(target_os = "ios"))]
fn run_and_return(
event_loop: &mut EventLoop<UserEvent>,
mut winit_app: impl WinitApp,
) -> Result<()> {
use winit::{event_loop::ControlFlow, platform::run_on_demand::EventLoopExtRunOnDemand};
log::trace!("Entering the winit event loop (run_on_demand)…");
// When to repaint what window
let mut windows_next_repaint_times = HashMap::default();
let mut returned_result = Ok(());
event_loop.run_on_demand(|event, event_loop_window_target| {
crate::profile_scope!("winit_event", short_event_description(&event));
log::trace!("winit event: {event:?}");
if matches!(event, winit::event::Event::AboutToWait) {
return; // early-out: don't trigger another wait
}
let event_result = match &event {
winit::event::Event::LoopExiting => {
// On Mac, Cmd-Q we get here and then `run_on_demand` doesn't return (despite its name),
// so we need to save state now:
log::debug!("Received Event::LoopExiting - saving app state…");
winit_app.save_and_destroy();
return;
}
winit::event::Event::WindowEvent {
event: winit::event::WindowEvent::RedrawRequested,
window_id,
} => {
windows_next_repaint_times.remove(window_id);
winit_app.run_ui_and_paint(event_loop_window_target, *window_id)
}
winit::event::Event::UserEvent(UserEvent::RequestRepaint {
when,
frame_nr,
viewport_id,
}) => {
let current_frame_nr = winit_app.frame_nr(*viewport_id);
if current_frame_nr == *frame_nr || current_frame_nr == *frame_nr + 1 {
log::trace!("UserEvent::RequestRepaint scheduling repaint at {when:?}");
if let Some(window_id) = winit_app.window_id_from_viewport_id(*viewport_id) {
EventResult::RepaintAt(window_id, *when)
} else {
EventResult::Wait
}
} else {
log::trace!("Got outdated UserEvent::RequestRepaint");
EventResult::Wait // old request - we've already repainted
}
}
winit::event::Event::NewEvents(winit::event::StartCause::ResumeTimeReached {
..
}) => {
log::trace!("Woke up to check next_repaint_time");
EventResult::Wait
}
event => match winit_app.on_event(event_loop_window_target, event) {
Ok(event_result) => {
log::trace!("event_result: {event_result:?}");
event_result
}
Err(err) => {
log::error!("Exiting because of error: {err} during event {event:?}");
returned_result = Err(err);
EventResult::Exit
}
},
};
match event_result {
EventResult::Wait => {
event_loop_window_target.set_control_flow(ControlFlow::Wait);
}
EventResult::RepaintNow(window_id) => {
log::trace!(
"RepaintNow of {window_id:?} caused by {}",
short_event_description(&event)
);
if cfg!(target_os = "windows") {
// Fix flickering on Windows, see https://github.com/emilk/egui/pull/2280
windows_next_repaint_times.remove(&window_id);
winit_app.run_ui_and_paint(event_loop_window_target, window_id);
} else {
// Fix for https://github.com/emilk/egui/issues/2425
windows_next_repaint_times.insert(window_id, Instant::now());
}
}
EventResult::RepaintNext(window_id) => {
log::trace!(
"RepaintNext of {window_id:?} caused by {}",
short_event_description(&event)
);
windows_next_repaint_times.insert(window_id, Instant::now());
}
EventResult::RepaintAt(window_id, repaint_time) => {
windows_next_repaint_times.insert(
window_id,
windows_next_repaint_times
.get(&window_id)
.map_or(repaint_time, |last| (*last).min(repaint_time)),
);
}
EventResult::Exit => {
log::debug!("Asking to exit event loop…");
winit_app.save_and_destroy();
event_loop_window_target.exit();
return;
}
}
let mut next_repaint_time = windows_next_repaint_times.values().min().copied();
windows_next_repaint_times.retain(|window_id, repaint_time| {
if Instant::now() < *repaint_time {
return true; // not yet ready
};
next_repaint_time = None;
event_loop_window_target.set_control_flow(ControlFlow::Poll);
if let Some(window) = winit_app.window(*window_id) {
log::trace!("request_redraw for {window_id:?}");
let is_minimized = window.is_minimized().unwrap_or(false);
if is_minimized {
false
} else {
window.request_redraw();
true
}
} else {
log::trace!("No window found for {window_id:?}");
false
}
});
if let Some(next_repaint_time) = next_repaint_time {
event_loop_window_target.set_control_flow(ControlFlow::WaitUntil(next_repaint_time));
};
})?;
log::debug!("eframe window closed");
drop(winit_app);
// On Windows this clears out events so that we can later create another window.
// See https://github.com/emilk/egui/pull/1889 for details.
//
// Note that this approach may cause issues on macOS (emilk/egui#2768); therefore,
// we only apply this approach on Windows to minimize the affect.
#[cfg(target_os = "windows")]
{
event_loop
.run_on_demand(|_, event_loop_window_target| {
event_loop_window_target.exit();
})
.ok();
}
returned_result
}
fn run_and_exit(
event_loop: EventLoop<UserEvent>,
mut winit_app: impl WinitApp + 'static,
) -> Result<()> {
use winit::event_loop::ControlFlow;
log::trace!("Entering the winit event loop (run)…");
// When to repaint what window
let mut windows_next_repaint_times = HashMap::default();
event_loop.run(move |event, event_loop_window_target| {
crate::profile_scope!("winit_event", short_event_description(&event));
log::trace!("winit event: {event:?}");
if matches!(event, winit::event::Event::AboutToWait) {
return; // early-out: don't trigger another wait
}
let event_result = match &event {
winit::event::Event::LoopExiting => {
log::debug!("Received Event::LoopExiting");
EventResult::Exit
}
winit::event::Event::WindowEvent {
event: winit::event::WindowEvent::RedrawRequested,
window_id,
} => {
windows_next_repaint_times.remove(window_id);
winit_app.run_ui_and_paint(event_loop_window_target, *window_id)
}
winit::event::Event::UserEvent(UserEvent::RequestRepaint {
when,
frame_nr,
viewport_id,
}) => {
let current_frame_nr = winit_app.frame_nr(*viewport_id);
if current_frame_nr == *frame_nr || current_frame_nr == *frame_nr + 1 {
if let Some(window_id) = winit_app.window_id_from_viewport_id(*viewport_id) {
EventResult::RepaintAt(window_id, *when)
} else {
EventResult::Wait
}
} else {
log::trace!("Got outdated UserEvent::RequestRepaint");
EventResult::Wait // old request - we've already repainted
}
}
winit::event::Event::NewEvents(winit::event::StartCause::ResumeTimeReached {
..
}) => {
log::trace!("Woke up to check next_repaint_time");
EventResult::Wait
}
event => match winit_app.on_event(event_loop_window_target, event) {
Ok(event_result) => {
log::trace!("event_result: {event_result:?}");
event_result
}
Err(err) => {
panic!("eframe encountered a fatal error: {err} during event {event:?}");
}
},
};
match event_result {
EventResult::Wait => {
event_loop_window_target.set_control_flow(ControlFlow::Wait);
}
EventResult::RepaintNow(window_id) => {
log::trace!("RepaintNow caused by {}", short_event_description(&event));
if cfg!(target_os = "windows") {
// Fix flickering on Windows, see https://github.com/emilk/egui/pull/2280
windows_next_repaint_times.remove(&window_id);
winit_app.run_ui_and_paint(event_loop_window_target, window_id);
} else {
// Fix for https://github.com/emilk/egui/issues/2425
windows_next_repaint_times.insert(window_id, Instant::now());
}
}
EventResult::RepaintNext(window_id) => {
log::trace!("RepaintNext caused by {}", short_event_description(&event));
windows_next_repaint_times.insert(window_id, Instant::now());
}
EventResult::RepaintAt(window_id, repaint_time) => {
windows_next_repaint_times.insert(
window_id,
windows_next_repaint_times
.get(&window_id)
.map_or(repaint_time, |last| (*last).min(repaint_time)),
);
}
EventResult::Exit => {
log::debug!("Quitting - saving app state…");
winit_app.save_and_destroy();
log::debug!("Exiting with return code 0");
#[allow(clippy::exit)]
std::process::exit(0);
}
}
let mut next_repaint_time = windows_next_repaint_times.values().min().copied();
windows_next_repaint_times.retain(|window_id, repaint_time| {
if Instant::now() < *repaint_time {
return true; // not yet ready
}
next_repaint_time = None;
event_loop_window_target.set_control_flow(ControlFlow::Poll);
if let Some(window) = winit_app.window(*window_id) {
log::trace!("request_redraw for {window_id:?}");
let is_minimized = window.is_minimized().unwrap_or(false);
if is_minimized {
false
} else {
window.request_redraw();
true
}
} else {
log::trace!("No window found for {window_id:?}");
false
}
});
if let Some(next_repaint_time) = next_repaint_time {
// WaitUntil seems to not work on iOS
#[cfg(target_os = "ios")]
winit_app
.get_window_winit_id(ViewportId::ROOT)
.map(|window_id| {
winit_app
.window(window_id)
.map(|window| window.request_redraw())
});
event_loop_window_target.set_control_flow(ControlFlow::WaitUntil(next_repaint_time));
};
})?;
log::debug!("winit event loop unexpectedly returned");
Ok(())
}
// ----------------------------------------------------------------------------
#[cfg(feature = "glow")]
pub fn run_glow(
app_name: &str,
mut native_options: epi::NativeOptions,
app_creator: epi::AppCreator,
) -> Result<()> {
#![allow(clippy::needless_return_with_question_mark)] // False positive
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, 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, app_creator);
run_and_exit(event_loop, glow_eframe)
}
// ----------------------------------------------------------------------------
#[cfg(feature = "wgpu")]
pub fn run_wgpu(
app_name: &str,
mut native_options: epi::NativeOptions,
app_creator: epi::AppCreator,
) -> Result<()> {
#![allow(clippy::needless_return_with_question_mark)] // False positive
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, 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, app_creator);
run_and_exit(event_loop, wgpu_eframe)
}