From 657ddc53062f370090d6f40549737bfa526da0e5 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 3 Aug 2022 15:58:36 +0200 Subject: [PATCH 1/8] Break up run_glow into three functions This will allow us to try out another event loop, one that continues execution after the window closes. --- eframe/src/native/run.rs | 298 +++++++++++++++++++++++++-------------- 1 file changed, 193 insertions(+), 105 deletions(-) diff --git a/eframe/src/native/run.rs b/eframe/src/native/run.rs index 0387cb5aee9..4d7b96d9763 100644 --- a/eframe/src/native/run.rs +++ b/eframe/src/native/run.rs @@ -1,6 +1,13 @@ +//! Note that this file contains two similar paths - one for [`glow`], one for [`wgpu`]. +//! When making changes to one you often also want to apply it to the other. + +use std::sync::Arc; + +use egui_winit::winit; +use winit::event_loop::ControlFlow; + use super::epi_integration; use crate::epi; -use egui_winit::winit; struct RequestRepaintEvent; @@ -47,73 +54,107 @@ fn create_display( pub use epi::NativeOptions; +enum EventResult { + Repaint, + Exit, + Continue, +} + /// Run an egui app #[cfg(feature = "glow")] -pub fn run_glow( - app_name: &str, - native_options: &epi::NativeOptions, - app_creator: epi::AppCreator, -) -> ! { - let storage = epi_integration::create_storage(app_name); - let window_settings = epi_integration::load_window_settings(storage.as_deref()); - let event_loop = winit::event_loop::EventLoop::with_user_event(); - - let window_builder = - epi_integration::window_builder(native_options, &window_settings).with_title(app_name); - let (gl_window, gl) = create_display(native_options, window_builder, &event_loop); - let gl = std::sync::Arc::new(gl); - - let mut painter = egui_glow::Painter::new(gl.clone(), None, "") - .unwrap_or_else(|error| panic!("some OpenGL error occurred {}\n", error)); - - let system_theme = native_options.system_theme(); - let mut integration = epi_integration::EpiIntegration::new( - &event_loop, - painter.max_texture_side(), - gl_window.window(), - system_theme, - storage, - Some(gl.clone()), - #[cfg(feature = "wgpu")] - None, - ); - let theme = system_theme.unwrap_or(native_options.default_theme); - integration.egui_ctx.set_visuals(theme.egui_visuals()); - - { - let event_loop_proxy = egui::mutex::Mutex::new(event_loop.create_proxy()); - integration.egui_ctx.set_request_repaint_callback(move || { - event_loop_proxy.lock().send_event(RequestRepaintEvent).ok(); - }); +mod glow_integration { + use super::*; + + struct GlowEframe { + gl_window: glutin::WindowedContext, + gl: Arc, + painter: egui_glow::Painter, + integration: epi_integration::EpiIntegration, + app: Box, + is_focused: bool, } - let mut app = app_creator(&epi::CreationContext { - egui_ctx: integration.egui_ctx.clone(), - integration_info: integration.frame.info(), - storage: integration.frame.storage(), - gl: Some(gl.clone()), - #[cfg(feature = "wgpu")] - render_state: None, - }); + impl GlowEframe { + fn new( + event_loop: &winit::event_loop::EventLoop, + app_name: &str, + native_options: &epi::NativeOptions, + app_creator: epi::AppCreator, + ) -> Self { + let storage = epi_integration::create_storage(app_name); + let window_settings = epi_integration::load_window_settings(storage.as_deref()); + + let window_builder = epi_integration::window_builder(native_options, &window_settings) + .with_title(app_name); + let (gl_window, gl) = create_display(native_options, window_builder, event_loop); + let gl = Arc::new(gl); + + let painter = egui_glow::Painter::new(gl.clone(), None, "") + .unwrap_or_else(|error| panic!("some OpenGL error occurred {}\n", error)); + + let system_theme = native_options.system_theme(); + let mut integration = epi_integration::EpiIntegration::new( + event_loop, + painter.max_texture_side(), + gl_window.window(), + system_theme, + storage, + Some(gl.clone()), + #[cfg(feature = "wgpu")] + None, + ); + let theme = system_theme.unwrap_or(native_options.default_theme); + integration.egui_ctx.set_visuals(theme.egui_visuals()); - if app.warm_up_enabled() { - integration.warm_up(app.as_mut(), gl_window.window()); - } + { + let event_loop_proxy = egui::mutex::Mutex::new(event_loop.create_proxy()); + integration.egui_ctx.set_request_repaint_callback(move || { + event_loop_proxy.lock().send_event(RequestRepaintEvent).ok(); + }); + } - let mut is_focused = true; + let mut app = app_creator(&epi::CreationContext { + egui_ctx: integration.egui_ctx.clone(), + integration_info: integration.frame.info(), + storage: integration.frame.storage(), + gl: Some(gl.clone()), + #[cfg(feature = "wgpu")] + render_state: None, + }); + + if app.warm_up_enabled() { + integration.warm_up(app.as_mut(), gl_window.window()); + } - event_loop.run(move |event, _, control_flow| { - let window = gl_window.window(); + Self { + gl_window, + gl, + painter, + integration, + app, + is_focused: true, + } + } - let mut redraw = || { + fn paint(&mut self) -> ControlFlow { #[cfg(feature = "puffin")] puffin::GlobalProfiler::lock().new_frame(); crate::profile_scope!("frame"); + let Self { + gl_window, + gl, + app, + integration, + painter, + .. + } = self; + let window = gl_window.window(); + let screen_size_in_pixels: [u32; 2] = window.inner_size().into(); egui_glow::painter::clear( - &gl, + gl, screen_size_in_pixels, app.clear_color(&integration.egui_ctx.style().visuals), ); @@ -146,11 +187,11 @@ pub fn run_glow( gl_window.swap_buffers().unwrap(); } - *control_flow = if integration.should_quit() { - winit::event_loop::ControlFlow::Exit + let control_flow = if integration.should_quit() { + ControlFlow::Exit } else if repaint_after.is_zero() { window.request_redraw(); - winit::event_loop::ControlFlow::Poll + ControlFlow::Poll } else if let Some(repaint_after_instant) = std::time::Instant::now().checked_add(repaint_after) { @@ -159,14 +200,14 @@ pub fn run_glow( // technically, this might lead to some weird corner cases where the user *WANTS* // winit to use `WaitUntil(MAX_INSTANT)` explicitly. they can roll their own // egui backend impl i guess. - winit::event_loop::ControlFlow::WaitUntil(repaint_after_instant) + ControlFlow::WaitUntil(repaint_after_instant) } else { - winit::event_loop::ControlFlow::Wait + ControlFlow::Wait }; integration.maybe_autosave(app.as_mut(), window); - if !is_focused { + if !self.is_focused { // On Mac, a minimized Window uses up all CPU: https://github.com/emilk/egui/issues/325 // We can't know if we are minimized: https://github.com/rust-windowing/winit/issues/208 // But we know if we are focused (in foreground). When minimized, we are not focused. @@ -175,58 +216,105 @@ pub fn run_glow( crate::profile_scope!("bg_sleep"); std::thread::sleep(std::time::Duration::from_millis(10)); } - }; - match event { - // Platform-dependent event handlers to workaround a winit bug - // See: https://github.com/rust-windowing/winit/issues/987 - // See: https://github.com/rust-windowing/winit/issues/1619 - winit::event::Event::RedrawEventsCleared if cfg!(windows) => redraw(), - winit::event::Event::RedrawRequested(_) if !cfg!(windows) => redraw(), - winit::event::Event::WindowEvent { event, .. } => { - match &event { - winit::event::WindowEvent::Focused(new_focused) => { - is_focused = *new_focused; - } - winit::event::WindowEvent::Resized(physical_size) => { - // Resize with 0 width and height is used by winit to signal a minimize event on Windows. - // See: https://github.com/rust-windowing/winit/issues/208 - // This solves an issue where the app would panic when minimizing on Windows. - if physical_size.width > 0 && physical_size.height > 0 { - gl_window.resize(*physical_size); + control_flow + } + + fn on_event(&mut self, event: winit::event::Event<'_, RequestRepaintEvent>) -> EventResult { + let Self { + gl_window, + gl, + integration, + painter, + .. + } = self; + let window = gl_window.window(); + + match event { + // Platform-dependent event handlers to workaround a winit bug + // See: https://github.com/rust-windowing/winit/issues/987 + // See: https://github.com/rust-windowing/winit/issues/1619 + winit::event::Event::RedrawEventsCleared if cfg!(windows) => EventResult::Repaint, + winit::event::Event::RedrawRequested(_) if !cfg!(windows) => EventResult::Repaint, + + winit::event::Event::WindowEvent { event, .. } => { + match &event { + winit::event::WindowEvent::Focused(new_focused) => { + self.is_focused = *new_focused; } + winit::event::WindowEvent::Resized(physical_size) => { + // Resize with 0 width and height is used by winit to signal a minimize event on Windows. + // See: https://github.com/rust-windowing/winit/issues/208 + // This solves an issue where the app would panic when minimizing on Windows. + if physical_size.width > 0 && physical_size.height > 0 { + gl_window.resize(*physical_size); + } + } + winit::event::WindowEvent::ScaleFactorChanged { + new_inner_size, .. + } => { + gl_window.resize(**new_inner_size); + } + winit::event::WindowEvent::CloseRequested if integration.should_quit() => { + return EventResult::Exit + } + _ => {} } - winit::event::WindowEvent::ScaleFactorChanged { new_inner_size, .. } => { - gl_window.resize(**new_inner_size); - } - winit::event::WindowEvent::CloseRequested if integration.should_quit() => { - *control_flow = winit::event_loop::ControlFlow::Exit; + + integration.on_event(self.app.as_mut(), &event); + if integration.should_quit() { + EventResult::Exit + } else { + window.request_redraw(); // TODO(emilk): ask egui if the events warrants a repaint instead + EventResult::Continue } - _ => {} } - - integration.on_event(app.as_mut(), &event); - if integration.should_quit() { - *control_flow = winit::event_loop::ControlFlow::Exit; + winit::event::Event::LoopDestroyed => { + integration.save(&mut *self.app, window); + self.app.on_exit(Some(gl)); + painter.destroy(); + EventResult::Continue } - window.request_redraw(); // TODO(emilk): ask egui if the events warrants a repaint instead - } - winit::event::Event::LoopDestroyed => { - integration.save(&mut *app, window); - app.on_exit(Some(&gl)); - painter.destroy(); - } - winit::event::Event::UserEvent(RequestRepaintEvent) => window.request_redraw(), - winit::event::Event::NewEvents(winit::event::StartCause::ResumeTimeReached { - .. - }) => { - window.request_redraw(); + winit::event::Event::UserEvent(RequestRepaintEvent) + | winit::event::Event::NewEvents(winit::event::StartCause::ResumeTimeReached { + .. + }) => { + window.request_redraw(); + EventResult::Continue + } + _ => EventResult::Continue, } - _ => {} } - }); + } + + pub fn run_glow( + app_name: &str, + native_options: &epi::NativeOptions, + app_creator: epi::AppCreator, + ) -> ! { + let event_loop = winit::event_loop::EventLoop::with_user_event(); + let mut glow_eframe = GlowEframe::new(&event_loop, app_name, native_options, app_creator); + + event_loop.run(move |event, _, control_flow| { + let event_result = glow_eframe.on_event(event); + match event_result { + EventResult::Continue => {} + EventResult::Repaint => { + *control_flow = glow_eframe.paint(); + } + EventResult::Exit => { + *control_flow = ControlFlow::Exit; + } + } + }) + } } +#[cfg(feature = "glow")] +pub use glow_integration::run_glow; + +// ---------------------------------------------------------------------------- + // TODO(emilk): merge with with the clone above /// Run an egui app #[cfg(feature = "wgpu")] @@ -331,10 +419,10 @@ pub fn run_wgpu( ); *control_flow = if integration.should_quit() { - winit::event_loop::ControlFlow::Exit + ControlFlow::Exit } else if repaint_after.is_zero() { window.request_redraw(); - winit::event_loop::ControlFlow::Poll + ControlFlow::Poll } else if let Some(repaint_after_instant) = std::time::Instant::now().checked_add(repaint_after) { @@ -343,9 +431,9 @@ pub fn run_wgpu( // technically, this might lead to some weird corner cases where the user *WANTS* // winit to use `WaitUntil(MAX_INSTANT)` explicitly. they can roll their own // egui backend impl i guess. - winit::event_loop::ControlFlow::WaitUntil(repaint_after_instant) + ControlFlow::WaitUntil(repaint_after_instant) } else { - winit::event_loop::ControlFlow::Wait + ControlFlow::Wait }; integration.maybe_autosave(app.as_mut(), window); From c839cde38dc61ac33bd7eb9c7591cf6f6a7d0f10 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 3 Aug 2022 20:56:22 +0200 Subject: [PATCH 2/8] Add `NativeOptions::exit_on_window_close` option `true` by default. Only implemented for glow so far. --- eframe/src/epi.rs | 15 +++ eframe/src/lib.rs | 4 +- eframe/src/native/epi_integration.rs | 4 + eframe/src/native/run.rs | 162 +++++++++++++++++++++------ 4 files changed, 147 insertions(+), 38 deletions(-) diff --git a/eframe/src/epi.rs b/eframe/src/epi.rs index a5b32d7d6ab..483edf58c8a 100644 --- a/eframe/src/epi.rs +++ b/eframe/src/epi.rs @@ -170,6 +170,20 @@ pub enum HardwareAcceleration { #[cfg(not(target_arch = "wasm32"))] #[derive(Clone)] pub struct NativeOptions { + /// If `true`, the app will close once the egui window is closed. + /// If `false`, execution will continue. + /// + /// This is `true` by default, because setting it to `false` has several downsides, + /// at least on Mac: + /// + /// * Window resizing is now longer instantaneous + /// * CPU usage is higher when idle + /// * [`Frame::drag_window`] doesn't work as expected + /// + /// When `true`, [`winit::event_loop::EventLoop::run`] is used. + /// When `false`, [`winit::platform::run_return::EventLoopExtRunReturn::run_return`] is used. + pub exit_on_window_close: bool, + /// Sets whether or not the window will always be on top of other windows. pub always_on_top: bool, @@ -275,6 +289,7 @@ pub struct NativeOptions { impl Default for NativeOptions { fn default() -> Self { Self { + exit_on_window_close: true, always_on_top: false, maximized: false, decorated: true, diff --git a/eframe/src/lib.rs b/eframe/src/lib.rs index 1175c2af94f..29a46a00363 100644 --- a/eframe/src/lib.rs +++ b/eframe/src/lib.rs @@ -161,14 +161,14 @@ mod native; /// ``` #[cfg(not(target_arch = "wasm32"))] #[allow(clippy::needless_pass_by_value)] -pub fn run_native(app_name: &str, native_options: NativeOptions, app_creator: AppCreator) -> ! { +pub fn run_native(app_name: &str, native_options: NativeOptions, app_creator: AppCreator) { let renderer = native_options.renderer; match renderer { #[cfg(feature = "glow")] Renderer::Glow => { tracing::debug!("Using the glow renderer"); - native::run::run_glow(app_name, &native_options, app_creator) + native::run::run_glow(app_name, &native_options, app_creator); } #[cfg(feature = "wgpu")] diff --git a/eframe/src/native/epi_integration.rs b/eframe/src/native/epi_integration.rs index b2b7d7e266b..e25a41606d1 100644 --- a/eframe/src/native/epi_integration.rs +++ b/eframe/src/native/epi_integration.rs @@ -350,6 +350,10 @@ impl EpiIntegration { storage.flush(); } } + + pub fn files_are_hovering(&self) -> bool { + !self.egui_winit.egui_input().hovered_files.is_empty() + } } #[cfg(feature = "persistence")] diff --git a/eframe/src/native/run.rs b/eframe/src/native/run.rs index 4d7b96d9763..8c707d8165b 100644 --- a/eframe/src/native/run.rs +++ b/eframe/src/native/run.rs @@ -4,11 +4,12 @@ use std::sync::Arc; use egui_winit::winit; -use winit::event_loop::ControlFlow; +use winit::event_loop::{ControlFlow, EventLoop}; use super::epi_integration; use crate::epi; +#[derive(Debug)] struct RequestRepaintEvent; #[cfg(feature = "glow")] @@ -16,7 +17,7 @@ struct RequestRepaintEvent; fn create_display( native_options: &NativeOptions, window_builder: winit::window::WindowBuilder, - event_loop: &winit::event_loop::EventLoop, + event_loop: &EventLoop, ) -> ( glutin::WindowedContext, glow::Context, @@ -63,6 +64,8 @@ enum EventResult { /// Run an egui app #[cfg(feature = "glow")] mod glow_integration { + use std::time::{Duration, Instant}; + use super::*; struct GlowEframe { @@ -76,7 +79,7 @@ mod glow_integration { impl GlowEframe { fn new( - event_loop: &winit::event_loop::EventLoop, + event_loop: &EventLoop, app_name: &str, native_options: &epi::NativeOptions, app_creator: epi::AppCreator, @@ -136,6 +139,13 @@ mod glow_integration { } } + fn save_and_destroy(&mut self) { + self.integration + .save(&mut *self.app, self.gl_window.window()); + self.app.on_exit(Some(&self.gl)); + self.painter.destroy(); + } + fn paint(&mut self) -> ControlFlow { #[cfg(feature = "puffin")] puffin::GlobalProfiler::lock().new_frame(); @@ -221,15 +231,6 @@ mod glow_integration { } fn on_event(&mut self, event: winit::event::Event<'_, RequestRepaintEvent>) -> EventResult { - let Self { - gl_window, - gl, - integration, - painter, - .. - } = self; - let window = gl_window.window(); - match event { // Platform-dependent event handlers to workaround a winit bug // See: https://github.com/rust-windowing/winit/issues/987 @@ -247,39 +248,39 @@ mod glow_integration { // See: https://github.com/rust-windowing/winit/issues/208 // This solves an issue where the app would panic when minimizing on Windows. if physical_size.width > 0 && physical_size.height > 0 { - gl_window.resize(*physical_size); + self.gl_window.resize(*physical_size); } } winit::event::WindowEvent::ScaleFactorChanged { new_inner_size, .. } => { - gl_window.resize(**new_inner_size); + self.gl_window.resize(**new_inner_size); } - winit::event::WindowEvent::CloseRequested if integration.should_quit() => { + winit::event::WindowEvent::CloseRequested + if self.integration.should_quit() => + { return EventResult::Exit } _ => {} } - integration.on_event(self.app.as_mut(), &event); - if integration.should_quit() { + self.integration.on_event(self.app.as_mut(), &event); + + if self.integration.should_quit() { EventResult::Exit } else { - window.request_redraw(); // TODO(emilk): ask egui if the events warrants a repaint instead + self.gl_window.window().request_redraw(); // TODO(emilk): ask egui if the event warrants a repaint EventResult::Continue } } winit::event::Event::LoopDestroyed => { - integration.save(&mut *self.app, window); - self.app.on_exit(Some(gl)); - painter.destroy(); - EventResult::Continue + unreachable!("Should be handled outside this function!") } winit::event::Event::UserEvent(RequestRepaintEvent) | winit::event::Event::NewEvents(winit::event::StartCause::ResumeTimeReached { .. }) => { - window.request_redraw(); + self.gl_window.window().request_redraw(); EventResult::Continue } _ => EventResult::Continue, @@ -291,19 +292,108 @@ mod glow_integration { app_name: &str, native_options: &epi::NativeOptions, app_creator: epi::AppCreator, - ) -> ! { - let event_loop = winit::event_loop::EventLoop::with_user_event(); - let mut glow_eframe = GlowEframe::new(&event_loop, app_name, native_options, app_creator); + ) { + let event_loop = EventLoop::with_user_event(); + let glow_eframe = GlowEframe::new(&event_loop, app_name, native_options, app_creator); + + if native_options.exit_on_window_close { + run_then_exit(event_loop, glow_eframe); + } else { + run_and_continue(event_loop, glow_eframe); + } + } - event_loop.run(move |event, _, control_flow| { - let event_result = glow_eframe.on_event(event); - match event_result { - EventResult::Continue => {} - EventResult::Repaint => { - *control_flow = glow_eframe.paint(); + fn suggest_sleep_duration(glow_eframe: &GlowEframe) -> Duration { + if glow_eframe.is_focused || glow_eframe.integration.files_are_hovering() { + Duration::from_millis(10) + } else { + Duration::from_millis(50) + } + } + + fn run_and_continue( + mut event_loop: EventLoop, + mut glow_eframe: GlowEframe, + ) { + let mut running = true; + let mut needs_repaint_by = Instant::now(); + + while running { + use winit::platform::run_return::EventLoopExtRunReturn as _; + event_loop.run_return(|event, _, control_flow| { + *control_flow = match event { + winit::event::Event::LoopDestroyed => ControlFlow::Exit, + winit::event::Event::MainEventsCleared => ControlFlow::Wait, + event => { + let event_result = glow_eframe.on_event(event); + match event_result { + EventResult::Continue => ControlFlow::Wait, + EventResult::Repaint => { + needs_repaint_by = Instant::now(); + ControlFlow::Exit + } + EventResult::Exit => { + running = false; + ControlFlow::Exit + } + } + } + }; + + match needs_repaint_by.checked_duration_since(Instant::now()) { + None => { + *control_flow = ControlFlow::Exit; // Time to redraw + } + Some(duration_until_repaint) => { + if *control_flow == ControlFlow::Wait { + // On Mac, ControlFlow::WaitUntil doesn't sleep enough. It uses a lot of CPU. + // So we sleep manually. But, it still uses 1-3% CPU :( + let sleep_duration = + duration_until_repaint.min(suggest_sleep_duration(&glow_eframe)); + std::thread::sleep(sleep_duration); + + *control_flow = ControlFlow::WaitUntil(needs_repaint_by); + } + } + } + }); + + if running && needs_repaint_by <= Instant::now() { + let paint_result = glow_eframe.paint(); + match paint_result { + ControlFlow::Poll => { + needs_repaint_by = Instant::now(); + } + ControlFlow::Wait => { + // wait a long time unless something happens + needs_repaint_by = Instant::now() + Duration::from_secs(3600); + } + ControlFlow::WaitUntil(repaint_time) => { + needs_repaint_by = repaint_time; + } + ControlFlow::Exit => { + running = false; + } } - EventResult::Exit => { - *control_flow = ControlFlow::Exit; + } + } + glow_eframe.save_and_destroy(); + } + + fn run_then_exit(event_loop: EventLoop, mut glow_eframe: GlowEframe) -> ! { + event_loop.run(move |event, _, control_flow| { + if let winit::event::Event::LoopDestroyed = event { + glow_eframe.save_and_destroy(); + } else { + let event_result = glow_eframe.on_event(event); + match event_result { + EventResult::Continue => {} + EventResult::Repaint => { + *control_flow = glow_eframe.paint(); + } + EventResult::Exit => { + *control_flow = ControlFlow::Exit; + } } } }) @@ -325,7 +415,7 @@ pub fn run_wgpu( ) -> ! { let storage = epi_integration::create_storage(app_name); let window_settings = epi_integration::load_window_settings(storage.as_deref()); - let event_loop = winit::event_loop::EventLoop::with_user_event(); + let event_loop = EventLoop::with_user_event(); let window = epi_integration::window_builder(native_options, &window_settings) .with_title(app_name) @@ -491,7 +581,7 @@ pub fn run_wgpu( if integration.should_quit() { *control_flow = winit::event_loop::ControlFlow::Exit; } - window.request_redraw(); // TODO(emilk): ask egui if the events warrants a repaint instead + window.request_redraw(); // TODO(emilk): ask egui if the event warrants a repaint } winit::event::Event::LoopDestroyed => { integration.save(&mut *app, window); From 2512aaeb06a9eabc2c10923e5d59d77a4cc7d447 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 3 Aug 2022 21:22:25 +0200 Subject: [PATCH 3/8] Refactor wgpu path to also support `exit_on_window_close == false` --- eframe/src/lib.rs | 4 +- eframe/src/native/run.rs | 411 +++++++++++++++++++++++++++------------ 2 files changed, 287 insertions(+), 128 deletions(-) diff --git a/eframe/src/lib.rs b/eframe/src/lib.rs index 29a46a00363..8338f97c830 100644 --- a/eframe/src/lib.rs +++ b/eframe/src/lib.rs @@ -174,9 +174,11 @@ pub fn run_native(app_name: &str, native_options: NativeOptions, app_creator: Ap #[cfg(feature = "wgpu")] Renderer::Wgpu => { tracing::debug!("Using the wgpu renderer"); - native::run::run_wgpu(app_name, &native_options, app_creator) + native::run::run_wgpu(app_name, &native_options, app_creator); } } + + tracing::debug!("eframe window closed"); } // --------------------------------------------------------------------------- diff --git a/eframe/src/native/run.rs b/eframe/src/native/run.rs index 8c707d8165b..06d37c61e92 100644 --- a/eframe/src/native/run.rs +++ b/eframe/src/native/run.rs @@ -405,88 +405,124 @@ pub use glow_integration::run_glow; // ---------------------------------------------------------------------------- -// TODO(emilk): merge with with the clone above -/// Run an egui app #[cfg(feature = "wgpu")] -pub fn run_wgpu( - app_name: &str, - native_options: &epi::NativeOptions, - app_creator: epi::AppCreator, -) -> ! { - let storage = epi_integration::create_storage(app_name); - let window_settings = epi_integration::load_window_settings(storage.as_deref()); - let event_loop = EventLoop::with_user_event(); - - let window = epi_integration::window_builder(native_options, &window_settings) - .with_title(app_name) - .build(&event_loop) - .unwrap(); - - // SAFETY: `window` must outlive `painter`. - #[allow(unsafe_code)] - let mut painter = unsafe { - let mut painter = egui_wgpu::winit::Painter::new( - wgpu::Backends::PRIMARY | wgpu::Backends::GL, - wgpu::PowerPreference::HighPerformance, - wgpu::DeviceDescriptor { - label: None, - features: wgpu::Features::default(), - limits: wgpu::Limits::default(), - }, - wgpu::PresentMode::Fifo, - native_options.multisampling.max(1) as _, - ); - #[cfg(not(target_os = "android"))] - painter.set_window(Some(&window)); - painter - }; +mod wgpu_integration { + use std::time::{Duration, Instant}; - let render_state = painter.get_render_state().expect("Uninitialized"); - - let system_theme = native_options.system_theme(); - let mut integration = epi_integration::EpiIntegration::new( - &event_loop, - painter.max_texture_side().unwrap_or(2048), - &window, - system_theme, - storage, - #[cfg(feature = "glow")] - None, - Some(render_state.clone()), - ); - let theme = system_theme.unwrap_or(native_options.default_theme); - integration.egui_ctx.set_visuals(theme.egui_visuals()); - - { - let event_loop_proxy = egui::mutex::Mutex::new(event_loop.create_proxy()); - integration.egui_ctx.set_request_repaint_callback(move || { - event_loop_proxy.lock().send_event(RequestRepaintEvent).ok(); - }); - } + use super::*; - let mut app = app_creator(&epi::CreationContext { - egui_ctx: integration.egui_ctx.clone(), - integration_info: integration.frame.info(), - storage: integration.frame.storage(), - #[cfg(feature = "glow")] - gl: None, - render_state: Some(render_state), - }); - - if app.warm_up_enabled() { - integration.warm_up(app.as_mut(), &window); + struct WgpuEframe { + window: winit::window::Window, + painter: egui_wgpu::winit::Painter<'static>, + integration: epi_integration::EpiIntegration, + app: Box, + is_focused: bool, } - let mut is_focused = true; + impl WgpuEframe { + fn new( + event_loop: &EventLoop, + app_name: &str, + native_options: &epi::NativeOptions, + app_creator: epi::AppCreator, + ) -> Self { + let storage = epi_integration::create_storage(app_name); + let window_settings = epi_integration::load_window_settings(storage.as_deref()); + + let window = epi_integration::window_builder(native_options, &window_settings) + .with_title(app_name) + .build(event_loop) + .unwrap(); + + // SAFETY: `window` must outlive `painter`. + #[allow(unsafe_code)] + let painter = unsafe { + let mut painter = egui_wgpu::winit::Painter::new( + wgpu::Backends::PRIMARY | wgpu::Backends::GL, + wgpu::PowerPreference::HighPerformance, + wgpu::DeviceDescriptor { + label: None, + features: wgpu::Features::default(), + limits: wgpu::Limits::default(), + }, + wgpu::PresentMode::Fifo, + native_options.multisampling.max(1) as _, + ); + #[cfg(not(target_os = "android"))] + painter.set_window(Some(&window)); + painter + }; - event_loop.run(move |event, _, control_flow| { - let window = &window; + let render_state = painter.get_render_state().expect("Uninitialized"); + + let system_theme = native_options.system_theme(); + let mut integration = epi_integration::EpiIntegration::new( + event_loop, + painter.max_texture_side().unwrap_or(2048), + &window, + system_theme, + storage, + #[cfg(feature = "glow")] + None, + Some(render_state.clone()), + ); + let theme = system_theme.unwrap_or(native_options.default_theme); + integration.egui_ctx.set_visuals(theme.egui_visuals()); + + { + let event_loop_proxy = egui::mutex::Mutex::new(event_loop.create_proxy()); + integration.egui_ctx.set_request_repaint_callback(move || { + event_loop_proxy.lock().send_event(RequestRepaintEvent).ok(); + }); + } + + let mut app = app_creator(&epi::CreationContext { + egui_ctx: integration.egui_ctx.clone(), + integration_info: integration.frame.info(), + storage: integration.frame.storage(), + #[cfg(feature = "glow")] + gl: None, + render_state: Some(render_state), + }); + + if app.warm_up_enabled() { + integration.warm_up(app.as_mut(), &window); + } + + Self { + window, + painter, + integration, + app, + is_focused: true, + } + } + + fn save_and_destroy(&mut self) { + self.integration.save(&mut *self.app, &self.window); - let mut redraw = || { + #[cfg(feature = "glow")] + self.app.on_exit(None); + + #[cfg(not(feature = "glow"))] + self.app.on_exit(); + + self.painter.destroy(); + } + + fn paint(&mut self) -> ControlFlow { #[cfg(feature = "puffin")] puffin::GlobalProfiler::lock().new_frame(); crate::profile_scope!("frame"); + let Self { + window, + app, + integration, + painter, + .. + } = self; + let egui::FullOutput { platform_output, repaint_after, @@ -508,7 +544,7 @@ pub fn run_wgpu( &textures_delta, ); - *control_flow = if integration.should_quit() { + let control_flow = if integration.should_quit() { ControlFlow::Exit } else if repaint_after.is_zero() { window.request_redraw(); @@ -528,7 +564,7 @@ pub fn run_wgpu( integration.maybe_autosave(app.as_mut(), window); - if !is_focused { + if !self.is_focused { // On Mac, a minimized Window uses up all CPU: https://github.com/emilk/egui/issues/325 // We can't know if we are minimized: https://github.com/rust-windowing/winit/issues/208 // But we know if we are focused (in foreground). When minimized, we are not focused. @@ -537,70 +573,191 @@ pub fn run_wgpu( crate::profile_scope!("bg_sleep"); std::thread::sleep(std::time::Duration::from_millis(10)); } - }; - match event { - // Platform-dependent event handlers to workaround a winit bug - // See: https://github.com/rust-windowing/winit/issues/987 - // See: https://github.com/rust-windowing/winit/issues/1619 - winit::event::Event::RedrawEventsCleared if cfg!(windows) => redraw(), - winit::event::Event::RedrawRequested(_) if !cfg!(windows) => redraw(), + control_flow + } - #[cfg(target_os = "android")] - winit::event::Event::Resumed => unsafe { - painter.set_window(Some(&window)); - }, - #[cfg(target_os = "android")] - winit::event::Event::Paused => unsafe { - painter.set_window(None); - }, - - winit::event::Event::WindowEvent { event, .. } => { - match &event { - winit::event::WindowEvent::Focused(new_focused) => { - is_focused = *new_focused; + fn on_event(&mut self, event: winit::event::Event<'_, RequestRepaintEvent>) -> EventResult { + match event { + // Platform-dependent event handlers to workaround a winit bug + // See: https://github.com/rust-windowing/winit/issues/987 + // See: https://github.com/rust-windowing/winit/issues/1619 + winit::event::Event::RedrawEventsCleared if cfg!(windows) => EventResult::Repaint, + winit::event::Event::RedrawRequested(_) if !cfg!(windows) => EventResult::Repaint, + + #[cfg(target_os = "android")] + winit::event::Event::Resumed => unsafe { + painter.set_window(Some(&window)); + }, + #[cfg(target_os = "android")] + winit::event::Event::Paused => unsafe { + painter.set_window(None); + }, + + winit::event::Event::WindowEvent { event, .. } => { + match &event { + winit::event::WindowEvent::Focused(new_focused) => { + self.is_focused = *new_focused; + } + winit::event::WindowEvent::Resized(physical_size) => { + // Resize with 0 width and height is used by winit to signal a minimize event on Windows. + // See: https://github.com/rust-windowing/winit/issues/208 + // This solves an issue where the app would panic when minimizing on Windows. + if physical_size.width > 0 && physical_size.height > 0 { + self.painter + .on_window_resized(physical_size.width, physical_size.height); + } + } + winit::event::WindowEvent::ScaleFactorChanged { + new_inner_size, .. + } => { + self.painter + .on_window_resized(new_inner_size.width, new_inner_size.height); + } + winit::event::WindowEvent::CloseRequested + if self.integration.should_quit() => + { + return EventResult::Exit + } + _ => {} + }; + + self.integration.on_event(self.app.as_mut(), &event); + if self.integration.should_quit() { + EventResult::Exit + } else { + self.window.request_redraw(); // TODO(emilk): ask egui if the event warrants a repaint + EventResult::Continue } - winit::event::WindowEvent::Resized(physical_size) => { - // Resize with 0 width and height is used by winit to signal a minimize event on Windows. - // See: https://github.com/rust-windowing/winit/issues/208 - // This solves an issue where the app would panic when minimizing on Windows. - if physical_size.width > 0 && physical_size.height > 0 { - painter.on_window_resized(physical_size.width, physical_size.height); + } + winit::event::Event::LoopDestroyed => { + unreachable!("Should be handled outside this function!") + } + winit::event::Event::UserEvent(RequestRepaintEvent) + | winit::event::Event::NewEvents(winit::event::StartCause::ResumeTimeReached { + .. + }) => { + self.window.request_redraw(); + EventResult::Continue + } + _ => EventResult::Continue, + } + } + } + + pub fn run_wgpu( + app_name: &str, + native_options: &epi::NativeOptions, + app_creator: epi::AppCreator, + ) { + let event_loop = EventLoop::with_user_event(); + let wgpu_eframe = WgpuEframe::new(&event_loop, app_name, native_options, app_creator); + + if native_options.exit_on_window_close { + run_then_exit(event_loop, wgpu_eframe); + } else { + run_and_continue(event_loop, wgpu_eframe); + } + } + + fn suggest_sleep_duration(wgpu_eframe: &WgpuEframe) -> Duration { + if wgpu_eframe.is_focused || wgpu_eframe.integration.files_are_hovering() { + Duration::from_millis(10) + } else { + Duration::from_millis(50) + } + } + + fn run_and_continue( + mut event_loop: EventLoop, + mut wgpu_eframe: WgpuEframe, + ) { + let mut running = true; + let mut needs_repaint_by = Instant::now(); + + while running { + use winit::platform::run_return::EventLoopExtRunReturn as _; + event_loop.run_return(|event, _, control_flow| { + *control_flow = match event { + winit::event::Event::LoopDestroyed => ControlFlow::Exit, + winit::event::Event::MainEventsCleared => ControlFlow::Wait, + event => { + let event_result = wgpu_eframe.on_event(event); + match event_result { + EventResult::Continue => ControlFlow::Wait, + EventResult::Repaint => { + needs_repaint_by = Instant::now(); + ControlFlow::Exit + } + EventResult::Exit => { + running = false; + ControlFlow::Exit + } } } - winit::event::WindowEvent::ScaleFactorChanged { new_inner_size, .. } => { - painter.on_window_resized(new_inner_size.width, new_inner_size.height); + }; + + match needs_repaint_by.checked_duration_since(Instant::now()) { + None => { + *control_flow = ControlFlow::Exit; // Time to redraw } - winit::event::WindowEvent::CloseRequested if integration.should_quit() => { - *control_flow = winit::event_loop::ControlFlow::Exit; + Some(duration_until_repaint) => { + if *control_flow == ControlFlow::Wait { + // On Mac, ControlFlow::WaitUntil doesn't sleep enough. It uses a lot of CPU. + // So we sleep manually. But, it still uses 1-3% CPU :( + let sleep_duration = + duration_until_repaint.min(suggest_sleep_duration(&wgpu_eframe)); + std::thread::sleep(sleep_duration); + + *control_flow = ControlFlow::WaitUntil(needs_repaint_by); + } } - _ => {} - }; + } + }); - integration.on_event(app.as_mut(), &event); - if integration.should_quit() { - *control_flow = winit::event_loop::ControlFlow::Exit; + if running && needs_repaint_by <= Instant::now() { + let paint_result = wgpu_eframe.paint(); + match paint_result { + ControlFlow::Poll => { + needs_repaint_by = Instant::now(); + } + ControlFlow::Wait => { + // wait a long time unless something happens + needs_repaint_by = Instant::now() + Duration::from_secs(3600); + } + ControlFlow::WaitUntil(repaint_time) => { + needs_repaint_by = repaint_time; + } + ControlFlow::Exit => { + running = false; + } } - window.request_redraw(); // TODO(emilk): ask egui if the event warrants a repaint } - winit::event::Event::LoopDestroyed => { - integration.save(&mut *app, window); - - #[cfg(feature = "glow")] - app.on_exit(None); - - #[cfg(not(feature = "glow"))] - app.on_exit(); + } + wgpu_eframe.save_and_destroy(); + } - painter.destroy(); - } - winit::event::Event::UserEvent(RequestRepaintEvent) => window.request_redraw(), - winit::event::Event::NewEvents(winit::event::StartCause::ResumeTimeReached { - .. - }) => { - window.request_redraw(); + fn run_then_exit(event_loop: EventLoop, mut wgpu_eframe: WgpuEframe) -> ! { + event_loop.run(move |event, _, control_flow| { + if let winit::event::Event::LoopDestroyed = event { + wgpu_eframe.save_and_destroy(); + } else { + let event_result = wgpu_eframe.on_event(event); + match event_result { + EventResult::Continue => {} + EventResult::Repaint => { + *control_flow = wgpu_eframe.paint(); + } + EventResult::Exit => { + *control_flow = ControlFlow::Exit; + } + } } - _ => (), - } - }); + }) + } } + +// ---------------------------------------------------------------------------- + +#[cfg(feature = "wgpu")] +pub use wgpu_integration::run_wgpu; From 5549910785644492a6a47853f62e0efed3ff2f8d Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 3 Aug 2022 21:36:49 +0200 Subject: [PATCH 4/8] Reduce code duplication --- eframe/src/epi.rs | 2 +- eframe/src/native/run.rs | 337 ++++++++++++++++----------------------- 2 files changed, 135 insertions(+), 204 deletions(-) diff --git a/eframe/src/epi.rs b/eframe/src/epi.rs index 483edf58c8a..7fdb6cca01c 100644 --- a/eframe/src/epi.rs +++ b/eframe/src/epi.rs @@ -176,7 +176,7 @@ pub struct NativeOptions { /// This is `true` by default, because setting it to `false` has several downsides, /// at least on Mac: /// - /// * Window resizing is now longer instantaneous + /// * Window resizing is no longer instantaneous /// * CPU usage is higher when idle /// * [`Frame::drag_window`] doesn't work as expected /// diff --git a/eframe/src/native/run.rs b/eframe/src/native/run.rs index 06d37c61e92..4c51cbea39a 100644 --- a/eframe/src/native/run.rs +++ b/eframe/src/native/run.rs @@ -2,11 +2,12 @@ //! When making changes to one you often also want to apply it to the other. use std::sync::Arc; +use std::time::{Duration, Instant}; use egui_winit::winit; use winit::event_loop::{ControlFlow, EventLoop}; -use super::epi_integration; +use super::epi_integration::{self, EpiIntegration}; use crate::epi; #[derive(Debug)] @@ -61,14 +62,118 @@ enum EventResult { Continue, } +trait WinitApp { + fn is_focused(&self) -> bool; + fn integration(&self) -> &EpiIntegration; + fn save_and_destroy(&mut self); + fn paint(&mut self) -> ControlFlow; + fn on_event(&mut self, event: winit::event::Event<'_, RequestRepaintEvent>) -> EventResult; +} + +fn suggest_sleep_duration(winit_app: &dyn WinitApp) -> Duration { + if winit_app.is_focused() || winit_app.integration().files_are_hovering() { + Duration::from_millis(10) + } else { + Duration::from_millis(50) + } +} + +fn run_and_continue(mut event_loop: EventLoop, mut winit_app: impl WinitApp) { + let mut running = true; + let mut needs_repaint_by = Instant::now(); + + while running { + use winit::platform::run_return::EventLoopExtRunReturn as _; + event_loop.run_return(|event, _, control_flow| { + *control_flow = match event { + winit::event::Event::LoopDestroyed => ControlFlow::Exit, + winit::event::Event::MainEventsCleared => ControlFlow::Wait, + event => { + let event_result = winit_app.on_event(event); + match event_result { + EventResult::Continue => ControlFlow::Wait, + EventResult::Repaint => { + needs_repaint_by = Instant::now(); + ControlFlow::Exit + } + EventResult::Exit => { + running = false; + ControlFlow::Exit + } + } + } + }; + + match needs_repaint_by.checked_duration_since(Instant::now()) { + None => { + *control_flow = ControlFlow::Exit; // Time to redraw + } + Some(duration_until_repaint) => { + if *control_flow == ControlFlow::Wait { + // On Mac, ControlFlow::WaitUntil doesn't sleep enough. It uses a lot of CPU. + // So we sleep manually. But, it still uses 1-3% CPU :( + let sleep_duration = + duration_until_repaint.min(suggest_sleep_duration(&winit_app)); + std::thread::sleep(sleep_duration); + + *control_flow = ControlFlow::WaitUntil(needs_repaint_by); + } + } + } + }); + + if running && needs_repaint_by <= Instant::now() { + let paint_result = winit_app.paint(); + match paint_result { + ControlFlow::Poll => { + needs_repaint_by = Instant::now(); + } + ControlFlow::Wait => { + // wait a long time unless something happens + needs_repaint_by = Instant::now() + Duration::from_secs(3600); + } + ControlFlow::WaitUntil(repaint_time) => { + needs_repaint_by = repaint_time; + } + ControlFlow::Exit => { + running = false; + } + } + } + } + winit_app.save_and_destroy(); +} + +fn run_then_exit( + event_loop: EventLoop, + mut winit_app: impl WinitApp + 'static, +) -> ! { + event_loop.run(move |event, _, control_flow| { + if let winit::event::Event::LoopDestroyed = event { + winit_app.save_and_destroy(); + } else { + let event_result = winit_app.on_event(event); + match event_result { + EventResult::Continue => {} + EventResult::Repaint => { + *control_flow = winit_app.paint(); + } + EventResult::Exit => { + *control_flow = ControlFlow::Exit; + } + } + } + }) +} + +// ---------------------------------------------------------------------------- + /// Run an egui app #[cfg(feature = "glow")] mod glow_integration { - use std::time::{Duration, Instant}; - use super::*; - struct GlowEframe { + struct GlowWinitApp { gl_window: glutin::WindowedContext, gl: Arc, painter: egui_glow::Painter, @@ -77,7 +182,7 @@ mod glow_integration { is_focused: bool, } - impl GlowEframe { + impl GlowWinitApp { fn new( event_loop: &EventLoop, app_name: &str, @@ -138,6 +243,16 @@ mod glow_integration { is_focused: true, } } + } + + impl WinitApp for GlowWinitApp { + fn is_focused(&self) -> bool { + self.is_focused + } + + fn integration(&self) -> &EpiIntegration { + &self.integration + } fn save_and_destroy(&mut self) { self.integration @@ -294,7 +409,7 @@ mod glow_integration { app_creator: epi::AppCreator, ) { let event_loop = EventLoop::with_user_event(); - let glow_eframe = GlowEframe::new(&event_loop, app_name, native_options, app_creator); + let glow_eframe = GlowWinitApp::new(&event_loop, app_name, native_options, app_creator); if native_options.exit_on_window_close { run_then_exit(event_loop, glow_eframe); @@ -302,102 +417,6 @@ mod glow_integration { run_and_continue(event_loop, glow_eframe); } } - - fn suggest_sleep_duration(glow_eframe: &GlowEframe) -> Duration { - if glow_eframe.is_focused || glow_eframe.integration.files_are_hovering() { - Duration::from_millis(10) - } else { - Duration::from_millis(50) - } - } - - fn run_and_continue( - mut event_loop: EventLoop, - mut glow_eframe: GlowEframe, - ) { - let mut running = true; - let mut needs_repaint_by = Instant::now(); - - while running { - use winit::platform::run_return::EventLoopExtRunReturn as _; - event_loop.run_return(|event, _, control_flow| { - *control_flow = match event { - winit::event::Event::LoopDestroyed => ControlFlow::Exit, - winit::event::Event::MainEventsCleared => ControlFlow::Wait, - event => { - let event_result = glow_eframe.on_event(event); - match event_result { - EventResult::Continue => ControlFlow::Wait, - EventResult::Repaint => { - needs_repaint_by = Instant::now(); - ControlFlow::Exit - } - EventResult::Exit => { - running = false; - ControlFlow::Exit - } - } - } - }; - - match needs_repaint_by.checked_duration_since(Instant::now()) { - None => { - *control_flow = ControlFlow::Exit; // Time to redraw - } - Some(duration_until_repaint) => { - if *control_flow == ControlFlow::Wait { - // On Mac, ControlFlow::WaitUntil doesn't sleep enough. It uses a lot of CPU. - // So we sleep manually. But, it still uses 1-3% CPU :( - let sleep_duration = - duration_until_repaint.min(suggest_sleep_duration(&glow_eframe)); - std::thread::sleep(sleep_duration); - - *control_flow = ControlFlow::WaitUntil(needs_repaint_by); - } - } - } - }); - - if running && needs_repaint_by <= Instant::now() { - let paint_result = glow_eframe.paint(); - match paint_result { - ControlFlow::Poll => { - needs_repaint_by = Instant::now(); - } - ControlFlow::Wait => { - // wait a long time unless something happens - needs_repaint_by = Instant::now() + Duration::from_secs(3600); - } - ControlFlow::WaitUntil(repaint_time) => { - needs_repaint_by = repaint_time; - } - ControlFlow::Exit => { - running = false; - } - } - } - } - glow_eframe.save_and_destroy(); - } - - fn run_then_exit(event_loop: EventLoop, mut glow_eframe: GlowEframe) -> ! { - event_loop.run(move |event, _, control_flow| { - if let winit::event::Event::LoopDestroyed = event { - glow_eframe.save_and_destroy(); - } else { - let event_result = glow_eframe.on_event(event); - match event_result { - EventResult::Continue => {} - EventResult::Repaint => { - *control_flow = glow_eframe.paint(); - } - EventResult::Exit => { - *control_flow = ControlFlow::Exit; - } - } - } - }) - } } #[cfg(feature = "glow")] @@ -407,11 +426,9 @@ pub use glow_integration::run_glow; #[cfg(feature = "wgpu")] mod wgpu_integration { - use std::time::{Duration, Instant}; - use super::*; - struct WgpuEframe { + struct WgpuWinitApp { window: winit::window::Window, painter: egui_wgpu::winit::Painter<'static>, integration: epi_integration::EpiIntegration, @@ -419,7 +436,7 @@ mod wgpu_integration { is_focused: bool, } - impl WgpuEframe { + impl WgpuWinitApp { fn new( event_loop: &EventLoop, app_name: &str, @@ -497,6 +514,16 @@ mod wgpu_integration { is_focused: true, } } + } + + impl WinitApp for WgpuWinitApp { + fn is_focused(&self) -> bool { + self.is_focused + } + + fn integration(&self) -> &EpiIntegration { + &self.integration + } fn save_and_destroy(&mut self) { self.integration.save(&mut *self.app, &self.window); @@ -651,7 +678,7 @@ mod wgpu_integration { app_creator: epi::AppCreator, ) { let event_loop = EventLoop::with_user_event(); - let wgpu_eframe = WgpuEframe::new(&event_loop, app_name, native_options, app_creator); + let wgpu_eframe = WgpuWinitApp::new(&event_loop, app_name, native_options, app_creator); if native_options.exit_on_window_close { run_then_exit(event_loop, wgpu_eframe); @@ -659,102 +686,6 @@ mod wgpu_integration { run_and_continue(event_loop, wgpu_eframe); } } - - fn suggest_sleep_duration(wgpu_eframe: &WgpuEframe) -> Duration { - if wgpu_eframe.is_focused || wgpu_eframe.integration.files_are_hovering() { - Duration::from_millis(10) - } else { - Duration::from_millis(50) - } - } - - fn run_and_continue( - mut event_loop: EventLoop, - mut wgpu_eframe: WgpuEframe, - ) { - let mut running = true; - let mut needs_repaint_by = Instant::now(); - - while running { - use winit::platform::run_return::EventLoopExtRunReturn as _; - event_loop.run_return(|event, _, control_flow| { - *control_flow = match event { - winit::event::Event::LoopDestroyed => ControlFlow::Exit, - winit::event::Event::MainEventsCleared => ControlFlow::Wait, - event => { - let event_result = wgpu_eframe.on_event(event); - match event_result { - EventResult::Continue => ControlFlow::Wait, - EventResult::Repaint => { - needs_repaint_by = Instant::now(); - ControlFlow::Exit - } - EventResult::Exit => { - running = false; - ControlFlow::Exit - } - } - } - }; - - match needs_repaint_by.checked_duration_since(Instant::now()) { - None => { - *control_flow = ControlFlow::Exit; // Time to redraw - } - Some(duration_until_repaint) => { - if *control_flow == ControlFlow::Wait { - // On Mac, ControlFlow::WaitUntil doesn't sleep enough. It uses a lot of CPU. - // So we sleep manually. But, it still uses 1-3% CPU :( - let sleep_duration = - duration_until_repaint.min(suggest_sleep_duration(&wgpu_eframe)); - std::thread::sleep(sleep_duration); - - *control_flow = ControlFlow::WaitUntil(needs_repaint_by); - } - } - } - }); - - if running && needs_repaint_by <= Instant::now() { - let paint_result = wgpu_eframe.paint(); - match paint_result { - ControlFlow::Poll => { - needs_repaint_by = Instant::now(); - } - ControlFlow::Wait => { - // wait a long time unless something happens - needs_repaint_by = Instant::now() + Duration::from_secs(3600); - } - ControlFlow::WaitUntil(repaint_time) => { - needs_repaint_by = repaint_time; - } - ControlFlow::Exit => { - running = false; - } - } - } - } - wgpu_eframe.save_and_destroy(); - } - - fn run_then_exit(event_loop: EventLoop, mut wgpu_eframe: WgpuEframe) -> ! { - event_loop.run(move |event, _, control_flow| { - if let winit::event::Event::LoopDestroyed = event { - wgpu_eframe.save_and_destroy(); - } else { - let event_result = wgpu_eframe.on_event(event); - match event_result { - EventResult::Continue => {} - EventResult::Repaint => { - *control_flow = wgpu_eframe.paint(); - } - EventResult::Exit => { - *control_flow = ControlFlow::Exit; - } - } - } - }) - } } // ---------------------------------------------------------------------------- From c0fa0f85672bc86e7bfcc745fac323ea2d786c15 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 3 Aug 2022 23:00:20 +0200 Subject: [PATCH 5/8] Make run_return behave MUCH better --- eframe/src/epi.rs | 25 ++- eframe/src/native/epi_integration.rs | 4 - eframe/src/native/run.rs | 235 ++++++++++++--------------- 3 files changed, 115 insertions(+), 149 deletions(-) diff --git a/eframe/src/epi.rs b/eframe/src/epi.rs index 7fdb6cca01c..eba453ff5f1 100644 --- a/eframe/src/epi.rs +++ b/eframe/src/epi.rs @@ -170,20 +170,6 @@ pub enum HardwareAcceleration { #[cfg(not(target_arch = "wasm32"))] #[derive(Clone)] pub struct NativeOptions { - /// If `true`, the app will close once the egui window is closed. - /// If `false`, execution will continue. - /// - /// This is `true` by default, because setting it to `false` has several downsides, - /// at least on Mac: - /// - /// * Window resizing is no longer instantaneous - /// * CPU usage is higher when idle - /// * [`Frame::drag_window`] doesn't work as expected - /// - /// When `true`, [`winit::event_loop::EventLoop::run`] is used. - /// When `false`, [`winit::platform::run_return::EventLoopExtRunReturn::run_return`] is used. - pub exit_on_window_close: bool, - /// Sets whether or not the window will always be on top of other windows. pub always_on_top: bool, @@ -283,13 +269,21 @@ pub struct NativeOptions { /// /// Default: `Theme::Dark`. pub default_theme: Theme, + + /// If `true`, the app will close once the egui window is closed. + /// If `false`, execution will continue. + /// + /// This is `true` by default. + /// + /// When `true`, [`winit::event_loop::EventLoop::run`] is used. + /// When `false`, [`winit::platform::run_return::EventLoopExtRunReturn::run_return`] is used. + pub exit_on_window_close: bool, } #[cfg(not(target_arch = "wasm32"))] impl Default for NativeOptions { fn default() -> Self { Self { - exit_on_window_close: true, always_on_top: false, maximized: false, decorated: true, @@ -310,6 +304,7 @@ impl Default for NativeOptions { renderer: Renderer::default(), follow_system_theme: cfg!(target_os = "macos") || cfg!(target_os = "windows"), default_theme: Theme::Dark, + exit_on_window_close: false, } } } diff --git a/eframe/src/native/epi_integration.rs b/eframe/src/native/epi_integration.rs index e25a41606d1..b2b7d7e266b 100644 --- a/eframe/src/native/epi_integration.rs +++ b/eframe/src/native/epi_integration.rs @@ -350,10 +350,6 @@ impl EpiIntegration { storage.flush(); } } - - pub fn files_are_hovering(&self) -> bool { - !self.egui_winit.egui_input().hovered_files.is_empty() - } } #[cfg(feature = "persistence")] diff --git a/eframe/src/native/run.rs b/eframe/src/native/run.rs index 4c51cbea39a..b84893177b7 100644 --- a/eframe/src/native/run.rs +++ b/eframe/src/native/run.rs @@ -2,7 +2,7 @@ //! When making changes to one you often also want to apply it to the other. use std::sync::Arc; -use std::time::{Duration, Instant}; +use std::time::Instant; use egui_winit::winit; use winit::event_loop::{ControlFlow, EventLoop}; @@ -57,90 +57,73 @@ fn create_display( pub use epi::NativeOptions; enum EventResult { - Repaint, + Wait, + RepaintNow, + RepaintAt(Instant), Exit, - Continue, } trait WinitApp { fn is_focused(&self) -> bool; fn integration(&self) -> &EpiIntegration; + fn window(&self) -> &winit::window::Window; fn save_and_destroy(&mut self); - fn paint(&mut self) -> ControlFlow; + fn paint(&mut self) -> EventResult; fn on_event(&mut self, event: winit::event::Event<'_, RequestRepaintEvent>) -> EventResult; } -fn suggest_sleep_duration(winit_app: &dyn WinitApp) -> Duration { - if winit_app.is_focused() || winit_app.integration().files_are_hovering() { - Duration::from_millis(10) - } else { - Duration::from_millis(50) - } -} - fn run_and_continue(mut event_loop: EventLoop, mut winit_app: impl WinitApp) { - let mut running = true; let mut needs_repaint_by = Instant::now(); - while running { - use winit::platform::run_return::EventLoopExtRunReturn as _; - event_loop.run_return(|event, _, control_flow| { - *control_flow = match event { - winit::event::Event::LoopDestroyed => ControlFlow::Exit, - winit::event::Event::MainEventsCleared => ControlFlow::Wait, - event => { - let event_result = winit_app.on_event(event); - match event_result { - EventResult::Continue => ControlFlow::Wait, - EventResult::Repaint => { - needs_repaint_by = Instant::now(); - ControlFlow::Exit - } - EventResult::Exit => { - running = false; - ControlFlow::Exit - } - } - } - }; + use winit::platform::run_return::EventLoopExtRunReturn as _; + tracing::debug!("event_loop.run_return"); - match needs_repaint_by.checked_duration_since(Instant::now()) { - None => { - *control_flow = ControlFlow::Exit; // Time to redraw - } - Some(duration_until_repaint) => { - if *control_flow == ControlFlow::Wait { - // On Mac, ControlFlow::WaitUntil doesn't sleep enough. It uses a lot of CPU. - // So we sleep manually. But, it still uses 1-3% CPU :( - let sleep_duration = - duration_until_repaint.min(suggest_sleep_duration(&winit_app)); - std::thread::sleep(sleep_duration); - - *control_flow = ControlFlow::WaitUntil(needs_repaint_by); - } - } - } - }); + event_loop.run_return(|event, _, control_flow| { + let event_result = match event { + winit::event::Event::LoopDestroyed => EventResult::Exit, - if running && needs_repaint_by <= Instant::now() { - let paint_result = winit_app.paint(); - match paint_result { - ControlFlow::Poll => { - needs_repaint_by = Instant::now(); - } - ControlFlow::Wait => { - // wait a long time unless something happens - needs_repaint_by = Instant::now() + Duration::from_secs(3600); - } - ControlFlow::WaitUntil(repaint_time) => { - needs_repaint_by = repaint_time; - } - ControlFlow::Exit => { - running = false; - } + // Platform-dependent event handlers to workaround a winit bug + // See: https://github.com/rust-windowing/winit/issues/987 + // See: https://github.com/rust-windowing/winit/issues/1619 + winit::event::Event::RedrawEventsCleared if cfg!(windows) => winit_app.paint(), + winit::event::Event::RedrawRequested(_) if !cfg!(windows) => winit_app.paint(), + + winit::event::Event::UserEvent(RequestRepaintEvent) + | winit::event::Event::NewEvents(winit::event::StartCause::ResumeTimeReached { + .. + }) => EventResult::RepaintNow, + + event => winit_app.on_event(event), + }; + + match event_result { + EventResult::Wait => {} + EventResult::RepaintNow => { + needs_repaint_by = Instant::now(); + } + EventResult::RepaintAt(repaint_time) => { + needs_repaint_by = repaint_time; + } + EventResult::Exit => { + *control_flow = ControlFlow::Exit; + return; } } - } + + *control_flow = match needs_repaint_by.checked_duration_since(Instant::now()) { + None => { + // repaint asap! + winit_app.window().request_redraw(); + ControlFlow::Poll + } + Some(duration_until_repaint) => { + ControlFlow::WaitUntil(Instant::now() + duration_until_repaint) + } + }; + }); + + tracing::debug!("eframe window closed"); + winit_app.save_and_destroy(); } @@ -148,19 +131,37 @@ fn run_then_exit( event_loop: EventLoop, mut winit_app: impl WinitApp + 'static, ) -> ! { + tracing::debug!("event_loop.run"); event_loop.run(move |event, _, control_flow| { - if let winit::event::Event::LoopDestroyed = event { - winit_app.save_and_destroy(); - } else { - let event_result = winit_app.on_event(event); - match event_result { - EventResult::Continue => {} - EventResult::Repaint => { - *control_flow = winit_app.paint(); - } - EventResult::Exit => { - *control_flow = ControlFlow::Exit; - } + let event_result = match event { + winit::event::Event::LoopDestroyed => EventResult::Exit, + + // Platform-dependent event handlers to workaround a winit bug + // See: https://github.com/rust-windowing/winit/issues/987 + // See: https://github.com/rust-windowing/winit/issues/1619 + winit::event::Event::RedrawEventsCleared if cfg!(windows) => winit_app.paint(), + winit::event::Event::RedrawRequested(_) if !cfg!(windows) => winit_app.paint(), + + winit::event::Event::UserEvent(RequestRepaintEvent) + | winit::event::Event::NewEvents(winit::event::StartCause::ResumeTimeReached { + .. + }) => EventResult::RepaintNow, + + event => winit_app.on_event(event), + }; + + *control_flow = match event_result { + EventResult::Wait => ControlFlow::Wait, + EventResult::RepaintNow => { + winit_app.window().request_redraw(); + ControlFlow::Poll + } + EventResult::RepaintAt(time) => ControlFlow::WaitUntil(time), + EventResult::Exit => { + tracing::debug!("quitting…"); + winit_app.save_and_destroy(); + #[allow(clippy::exit)] + std::process::exit(0); } } }) @@ -254,6 +255,10 @@ mod glow_integration { &self.integration } + fn window(&self) -> &winit::window::Window { + self.gl_window.window() + } + fn save_and_destroy(&mut self) { self.integration .save(&mut *self.app, self.gl_window.window()); @@ -261,7 +266,7 @@ mod glow_integration { self.painter.destroy(); } - fn paint(&mut self) -> ControlFlow { + fn paint(&mut self) -> EventResult { #[cfg(feature = "puffin")] puffin::GlobalProfiler::lock().new_frame(); crate::profile_scope!("frame"); @@ -313,10 +318,9 @@ mod glow_integration { } let control_flow = if integration.should_quit() { - ControlFlow::Exit + EventResult::Exit } else if repaint_after.is_zero() { - window.request_redraw(); - ControlFlow::Poll + EventResult::RepaintNow } else if let Some(repaint_after_instant) = std::time::Instant::now().checked_add(repaint_after) { @@ -325,9 +329,9 @@ mod glow_integration { // technically, this might lead to some weird corner cases where the user *WANTS* // winit to use `WaitUntil(MAX_INSTANT)` explicitly. they can roll their own // egui backend impl i guess. - ControlFlow::WaitUntil(repaint_after_instant) + EventResult::RepaintAt(repaint_after_instant) } else { - ControlFlow::Wait + EventResult::Wait }; integration.maybe_autosave(app.as_mut(), window); @@ -347,12 +351,6 @@ mod glow_integration { fn on_event(&mut self, event: winit::event::Event<'_, RequestRepaintEvent>) -> EventResult { match event { - // Platform-dependent event handlers to workaround a winit bug - // See: https://github.com/rust-windowing/winit/issues/987 - // See: https://github.com/rust-windowing/winit/issues/1619 - winit::event::Event::RedrawEventsCleared if cfg!(windows) => EventResult::Repaint, - winit::event::Event::RedrawRequested(_) if !cfg!(windows) => EventResult::Repaint, - winit::event::Event::WindowEvent { event, .. } => { match &event { winit::event::WindowEvent::Focused(new_focused) => { @@ -384,21 +382,11 @@ mod glow_integration { if self.integration.should_quit() { EventResult::Exit } else { - self.gl_window.window().request_redraw(); // TODO(emilk): ask egui if the event warrants a repaint - EventResult::Continue + // TODO(emilk): ask egui if the event warrants a repaint + EventResult::RepaintNow } } - winit::event::Event::LoopDestroyed => { - unreachable!("Should be handled outside this function!") - } - winit::event::Event::UserEvent(RequestRepaintEvent) - | winit::event::Event::NewEvents(winit::event::StartCause::ResumeTimeReached { - .. - }) => { - self.gl_window.window().request_redraw(); - EventResult::Continue - } - _ => EventResult::Continue, + _ => EventResult::Wait, } } } @@ -525,6 +513,10 @@ mod wgpu_integration { &self.integration } + fn window(&self) -> &winit::window::Window { + &self.window + } + fn save_and_destroy(&mut self) { self.integration.save(&mut *self.app, &self.window); @@ -537,7 +529,7 @@ mod wgpu_integration { self.painter.destroy(); } - fn paint(&mut self) -> ControlFlow { + fn paint(&mut self) -> EventResult { #[cfg(feature = "puffin")] puffin::GlobalProfiler::lock().new_frame(); crate::profile_scope!("frame"); @@ -572,10 +564,9 @@ mod wgpu_integration { ); let control_flow = if integration.should_quit() { - ControlFlow::Exit + EventResult::Exit } else if repaint_after.is_zero() { - window.request_redraw(); - ControlFlow::Poll + EventResult::RepaintNow } else if let Some(repaint_after_instant) = std::time::Instant::now().checked_add(repaint_after) { @@ -584,9 +575,9 @@ mod wgpu_integration { // technically, this might lead to some weird corner cases where the user *WANTS* // winit to use `WaitUntil(MAX_INSTANT)` explicitly. they can roll their own // egui backend impl i guess. - ControlFlow::WaitUntil(repaint_after_instant) + EventResult::RepaintAt(repaint_after_instant) } else { - ControlFlow::Wait + EventResult::Wait }; integration.maybe_autosave(app.as_mut(), window); @@ -606,12 +597,6 @@ mod wgpu_integration { fn on_event(&mut self, event: winit::event::Event<'_, RequestRepaintEvent>) -> EventResult { match event { - // Platform-dependent event handlers to workaround a winit bug - // See: https://github.com/rust-windowing/winit/issues/987 - // See: https://github.com/rust-windowing/winit/issues/1619 - winit::event::Event::RedrawEventsCleared if cfg!(windows) => EventResult::Repaint, - winit::event::Event::RedrawRequested(_) if !cfg!(windows) => EventResult::Repaint, - #[cfg(target_os = "android")] winit::event::Event::Resumed => unsafe { painter.set_window(Some(&window)); @@ -653,21 +638,11 @@ mod wgpu_integration { if self.integration.should_quit() { EventResult::Exit } else { - self.window.request_redraw(); // TODO(emilk): ask egui if the event warrants a repaint - EventResult::Continue + // TODO(emilk): ask egui if the event warrants a repaint + EventResult::RepaintNow } } - winit::event::Event::LoopDestroyed => { - unreachable!("Should be handled outside this function!") - } - winit::event::Event::UserEvent(RequestRepaintEvent) - | winit::event::Event::NewEvents(winit::event::StartCause::ResumeTimeReached { - .. - }) => { - self.window.request_redraw(); - EventResult::Continue - } - _ => EventResult::Continue, + _ => EventResult::Wait, } } } From b5ba103fec93ebf038bf95e4eb6790242844c6e5 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 3 Aug 2022 23:23:40 +0200 Subject: [PATCH 6/8] Fix repaint_after --- eframe/src/lib.rs | 2 - eframe/src/native/run.rs | 84 ++++++++++++++++++++++++++-------------- 2 files changed, 55 insertions(+), 31 deletions(-) diff --git a/eframe/src/lib.rs b/eframe/src/lib.rs index 8338f97c830..7558163d329 100644 --- a/eframe/src/lib.rs +++ b/eframe/src/lib.rs @@ -177,8 +177,6 @@ pub fn run_native(app_name: &str, native_options: NativeOptions, app_creator: Ap native::run::run_wgpu(app_name, &native_options, app_creator); } } - - tracing::debug!("eframe window closed"); } // --------------------------------------------------------------------------- diff --git a/eframe/src/native/run.rs b/eframe/src/native/run.rs index b84893177b7..80b62ebf97c 100644 --- a/eframe/src/native/run.rs +++ b/eframe/src/native/run.rs @@ -1,8 +1,8 @@ //! Note that this file contains two similar paths - one for [`glow`], one for [`wgpu`]. //! When making changes to one you often also want to apply it to the other. -use std::sync::Arc; use std::time::Instant; +use std::{sync::Arc, time::Duration}; use egui_winit::winit; use winit::event_loop::{ControlFlow, EventLoop}; @@ -58,7 +58,7 @@ pub use epi::NativeOptions; enum EventResult { Wait, - RepaintNow, + RepaintAsap, RepaintAt(Instant), Exit, } @@ -73,11 +73,12 @@ trait WinitApp { } fn run_and_continue(mut event_loop: EventLoop, mut winit_app: impl WinitApp) { - let mut needs_repaint_by = Instant::now(); - use winit::platform::run_return::EventLoopExtRunReturn as _; + tracing::debug!("event_loop.run_return"); + let mut next_repaint_time = Instant::now(); + event_loop.run_return(|event, _, control_flow| { let event_result = match event { winit::event::Event::LoopDestroyed => EventResult::Exit, @@ -85,24 +86,30 @@ fn run_and_continue(mut event_loop: EventLoop, mut winit_ap // Platform-dependent event handlers to workaround a winit bug // See: https://github.com/rust-windowing/winit/issues/987 // See: https://github.com/rust-windowing/winit/issues/1619 - winit::event::Event::RedrawEventsCleared if cfg!(windows) => winit_app.paint(), - winit::event::Event::RedrawRequested(_) if !cfg!(windows) => winit_app.paint(), + winit::event::Event::RedrawEventsCleared if cfg!(windows) => { + next_repaint_time = Instant::now() + Duration::from_secs(1_000_000_000); + winit_app.paint() + } + winit::event::Event::RedrawRequested(_) if !cfg!(windows) => { + next_repaint_time = Instant::now() + Duration::from_secs(1_000_000_000); + winit_app.paint() + } winit::event::Event::UserEvent(RequestRepaintEvent) | winit::event::Event::NewEvents(winit::event::StartCause::ResumeTimeReached { .. - }) => EventResult::RepaintNow, + }) => EventResult::RepaintAsap, event => winit_app.on_event(event), }; match event_result { EventResult::Wait => {} - EventResult::RepaintNow => { - needs_repaint_by = Instant::now(); + EventResult::RepaintAsap => { + next_repaint_time = Instant::now(); } EventResult::RepaintAt(repaint_time) => { - needs_repaint_by = repaint_time; + next_repaint_time = next_repaint_time.min(repaint_time); } EventResult::Exit => { *control_flow = ControlFlow::Exit; @@ -110,16 +117,15 @@ fn run_and_continue(mut event_loop: EventLoop, mut winit_ap } } - *control_flow = match needs_repaint_by.checked_duration_since(Instant::now()) { + *control_flow = match next_repaint_time.checked_duration_since(Instant::now()) { None => { - // repaint asap! winit_app.window().request_redraw(); ControlFlow::Poll } - Some(duration_until_repaint) => { - ControlFlow::WaitUntil(Instant::now() + duration_until_repaint) + Some(time_until_next_repaint) => { + ControlFlow::WaitUntil(Instant::now() + time_until_next_repaint) } - }; + } }); tracing::debug!("eframe window closed"); @@ -132,6 +138,9 @@ fn run_then_exit( mut winit_app: impl WinitApp + 'static, ) -> ! { tracing::debug!("event_loop.run"); + + let mut next_repaint_time = Instant::now(); + event_loop.run(move |event, _, control_flow| { let event_result = match event { winit::event::Event::LoopDestroyed => EventResult::Exit, @@ -139,31 +148,48 @@ fn run_then_exit( // Platform-dependent event handlers to workaround a winit bug // See: https://github.com/rust-windowing/winit/issues/987 // See: https://github.com/rust-windowing/winit/issues/1619 - winit::event::Event::RedrawEventsCleared if cfg!(windows) => winit_app.paint(), - winit::event::Event::RedrawRequested(_) if !cfg!(windows) => winit_app.paint(), + winit::event::Event::RedrawEventsCleared if cfg!(windows) => { + next_repaint_time = Instant::now() + Duration::from_secs(1_000_000_000); + winit_app.paint() + } + winit::event::Event::RedrawRequested(_) if !cfg!(windows) => { + next_repaint_time = Instant::now() + Duration::from_secs(1_000_000_000); + winit_app.paint() + } winit::event::Event::UserEvent(RequestRepaintEvent) | winit::event::Event::NewEvents(winit::event::StartCause::ResumeTimeReached { .. - }) => EventResult::RepaintNow, + }) => EventResult::RepaintAsap, event => winit_app.on_event(event), }; - *control_flow = match event_result { - EventResult::Wait => ControlFlow::Wait, - EventResult::RepaintNow => { - winit_app.window().request_redraw(); - ControlFlow::Poll + match event_result { + EventResult::Wait => {} + EventResult::RepaintAsap => { + next_repaint_time = Instant::now(); + } + EventResult::RepaintAt(repaint_time) => { + next_repaint_time = next_repaint_time.min(repaint_time); } - EventResult::RepaintAt(time) => ControlFlow::WaitUntil(time), EventResult::Exit => { - tracing::debug!("quitting…"); + tracing::debug!("Quitting…"); winit_app.save_and_destroy(); #[allow(clippy::exit)] std::process::exit(0); } } + + *control_flow = match next_repaint_time.checked_duration_since(Instant::now()) { + None => { + winit_app.window().request_redraw(); + ControlFlow::Poll + } + Some(time_until_next_repaint) => { + ControlFlow::WaitUntil(Instant::now() + time_until_next_repaint) + } + } }) } @@ -320,7 +346,7 @@ mod glow_integration { let control_flow = if integration.should_quit() { EventResult::Exit } else if repaint_after.is_zero() { - EventResult::RepaintNow + EventResult::RepaintAsap } else if let Some(repaint_after_instant) = std::time::Instant::now().checked_add(repaint_after) { @@ -383,7 +409,7 @@ mod glow_integration { EventResult::Exit } else { // TODO(emilk): ask egui if the event warrants a repaint - EventResult::RepaintNow + EventResult::RepaintAsap } } _ => EventResult::Wait, @@ -566,7 +592,7 @@ mod wgpu_integration { let control_flow = if integration.should_quit() { EventResult::Exit } else if repaint_after.is_zero() { - EventResult::RepaintNow + EventResult::RepaintAsap } else if let Some(repaint_after_instant) = std::time::Instant::now().checked_add(repaint_after) { @@ -639,7 +665,7 @@ mod wgpu_integration { EventResult::Exit } else { // TODO(emilk): ask egui if the event warrants a repaint - EventResult::RepaintNow + EventResult::RepaintAsap } } _ => EventResult::Wait, From dbbfb305d62d645944bcc3dbe48e5915b144320e Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 3 Aug 2022 23:31:15 +0200 Subject: [PATCH 7/8] Turn the new feature on by default --- eframe/src/epi.rs | 19 ++++++++++++------- eframe/src/native/run.rs | 16 ++++++++-------- 2 files changed, 20 insertions(+), 15 deletions(-) diff --git a/eframe/src/epi.rs b/eframe/src/epi.rs index eba453ff5f1..4dcaa96050c 100644 --- a/eframe/src/epi.rs +++ b/eframe/src/epi.rs @@ -270,14 +270,19 @@ pub struct NativeOptions { /// Default: `Theme::Dark`. pub default_theme: Theme, - /// If `true`, the app will close once the egui window is closed. - /// If `false`, execution will continue. + /// This controls what happens when you close the main egui window. /// - /// This is `true` by default. + /// If `true`, execution will continue after the egui window is closed. + /// If `false`, the app will close once the egui window is closed. /// - /// When `true`, [`winit::event_loop::EventLoop::run`] is used. - /// When `false`, [`winit::platform::run_return::EventLoopExtRunReturn::run_return`] is used. - pub exit_on_window_close: bool, + /// This is `true` by default, and the `false` option is only there + /// so we can revert if we find any bugs. + /// + /// This feature was introduced in . + /// + /// When `true`, [`winit::platform::run_return::EventLoopExtRunReturn::run_return`] is used. + /// When `false`, [`winit::event_loop::EventLoop::run`] is used. + pub run_and_return: bool, } #[cfg(not(target_arch = "wasm32"))] @@ -304,7 +309,7 @@ impl Default for NativeOptions { renderer: Renderer::default(), follow_system_theme: cfg!(target_os = "macos") || cfg!(target_os = "windows"), default_theme: Theme::Dark, - exit_on_window_close: false, + run_and_return: true, } } } diff --git a/eframe/src/native/run.rs b/eframe/src/native/run.rs index 80b62ebf97c..0087aafa908 100644 --- a/eframe/src/native/run.rs +++ b/eframe/src/native/run.rs @@ -72,7 +72,7 @@ trait WinitApp { fn on_event(&mut self, event: winit::event::Event<'_, RequestRepaintEvent>) -> EventResult; } -fn run_and_continue(mut event_loop: EventLoop, mut winit_app: impl WinitApp) { +fn run_and_return(mut event_loop: EventLoop, mut winit_app: impl WinitApp) { use winit::platform::run_return::EventLoopExtRunReturn as _; tracing::debug!("event_loop.run_return"); @@ -133,7 +133,7 @@ fn run_and_continue(mut event_loop: EventLoop, mut winit_ap winit_app.save_and_destroy(); } -fn run_then_exit( +fn run_and_exit( event_loop: EventLoop, mut winit_app: impl WinitApp + 'static, ) -> ! { @@ -425,10 +425,10 @@ mod glow_integration { let event_loop = EventLoop::with_user_event(); let glow_eframe = GlowWinitApp::new(&event_loop, app_name, native_options, app_creator); - if native_options.exit_on_window_close { - run_then_exit(event_loop, glow_eframe); + if native_options.run_and_return { + run_and_return(event_loop, glow_eframe); } else { - run_and_continue(event_loop, glow_eframe); + run_and_exit(event_loop, glow_eframe); } } } @@ -681,10 +681,10 @@ mod wgpu_integration { let event_loop = EventLoop::with_user_event(); let wgpu_eframe = WgpuWinitApp::new(&event_loop, app_name, native_options, app_creator); - if native_options.exit_on_window_close { - run_then_exit(event_loop, wgpu_eframe); + if native_options.run_and_return { + run_and_return(event_loop, wgpu_eframe); } else { - run_and_continue(event_loop, wgpu_eframe); + run_and_exit(event_loop, wgpu_eframe); } } } From 5848bd5f35c53638b1ea8bb2eec8e8510744ac80 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 3 Aug 2022 23:35:08 +0200 Subject: [PATCH 8/8] Better docs --- eframe/src/epi.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/eframe/src/epi.rs b/eframe/src/epi.rs index 4dcaa96050c..ab4ee0ef04b 100644 --- a/eframe/src/epi.rs +++ b/eframe/src/epi.rs @@ -270,10 +270,10 @@ pub struct NativeOptions { /// Default: `Theme::Dark`. pub default_theme: Theme, - /// This controls what happens when you close the main egui window. + /// This controls what happens when you close the main eframe window. /// - /// If `true`, execution will continue after the egui window is closed. - /// If `false`, the app will close once the egui window is closed. + /// If `true`, execution will continue after the eframe window is closed. + /// If `false`, the app will close once the eframe window is closed. /// /// This is `true` by default, and the `false` option is only there /// so we can revert if we find any bugs.