From c768d1d48e4c8b7620391f4b732ff836bc17adad Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Tue, 15 Mar 2022 17:21:52 +0100 Subject: [PATCH] Context::request_repaint will wake up the UI thread (#1366) This adds a callback (set by `Context::set_request_repaint_callback`) which integration can use to wake up the UI thread. eframe (egui_web and egui_glow) will use this, replacing `epi::Frame::request_repaint`. Existing code calling `epi::Frame::request_repaint` should be changed to instead call `egui::Context::request_repaint`. This is the first callback added to the egui API, which otherwise is completely driven by data. The purpose of this is to remove the confusion between the two `request_repaint` methods (by removing one). Furthermore, it makes `epi::Frame` a lot simpler, allowing future simplifications to it (perhaps no longer having it be `Send+Sync+Clone`). --- CHANGELOG.md | 1 + Cargo.lock | 2 ++ eframe/CHANGELOG.md | 1 + eframe/examples/download_image.rs | 6 +++--- egui-winit/src/epi.rs | 2 -- egui/src/context.rs | 20 +++++++++++++++++++- egui_demo_lib/Cargo.toml | 1 + egui_demo_lib/src/apps/http_app.rs | 3 +-- egui_glow/Cargo.toml | 1 + egui_glow/src/epi_backend.rs | 20 +++++++------------- egui_web/src/backend.rs | 19 ++++++++++--------- epi/src/lib.rs | 18 ------------------ 12 files changed, 46 insertions(+), 48 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 610143f1697..ccc18f40ced 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ NOTE: [`epaint`](epaint/CHANGELOG.md), [`eframe`](eframe/CHANGELOG.md), [`egui_w ### Added ⭐ * Add `Shape::Callback` for backend-specific painting ([#1351](https://github.com/emilk/egui/pull/1351)). * Added `Frame::canvas` ([#1362](https://github.com/emilk/egui/pull/1362)). +* `Context::request_repaint` will wake up UI thread, if integrations has called `Context::set_request_repaint_callback` ([#1366](https://github.com/emilk/egui/pull/1366)). ### Changed 🔧 * `ClippedMesh` has been replaced with `ClippedPrimitive` ([#1351](https://github.com/emilk/egui/pull/1351)). diff --git a/Cargo.lock b/Cargo.lock index 55dee5c1b43..30384431c02 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1053,6 +1053,7 @@ dependencies = [ "poll-promise", "serde", "syntect", + "tracing", "unicode_names2", ] @@ -1091,6 +1092,7 @@ dependencies = [ "glow", "glutin", "memoffset", + "parking_lot 0.12.0", "tracing", "wasm-bindgen", "web-sys", diff --git a/eframe/CHANGELOG.md b/eframe/CHANGELOG.md index 90cb6126688..b7fb979da89 100644 --- a/eframe/CHANGELOG.md +++ b/eframe/CHANGELOG.md @@ -7,6 +7,7 @@ NOTE: [`egui_web`](../egui_web/CHANGELOG.md), [`egui-winit`](../egui-winit/CHANG ## Unreleased * Remove the `egui_glium` feature. `eframe` will now always use `egui_glow` as the native backend ([#1357](https://github.com/emilk/egui/pull/1357)). * Change default for `NativeOptions::drag_and_drop_support` to `true` ([#1329](https://github.com/emilk/egui/pull/1329)). +* Removed `Frame::request_repaint` - just call `egui::Context::request_repaint` for the same effect ([#1366](https://github.com/emilk/egui/pull/1366)). ## 0.17.0 - 2022-02-22 diff --git a/eframe/examples/download_image.rs b/eframe/examples/download_image.rs index dede08c42cd..ba754b75aa7 100644 --- a/eframe/examples/download_image.rs +++ b/eframe/examples/download_image.rs @@ -20,18 +20,18 @@ impl epi::App for MyApp { "Download and show an image with eframe/egui" } - fn update(&mut self, ctx: &egui::Context, frame: &epi::Frame) { + fn update(&mut self, ctx: &egui::Context, _frame: &epi::Frame) { let promise = self.promise.get_or_insert_with(|| { // Begin download. // We download the image using `ehttp`, a library that works both in WASM and on native. // We use the `poll-promise` library to communicate with the UI thread. - let frame = frame.clone(); + let ctx = ctx.clone(); let (sender, promise) = Promise::new(); let request = ehttp::Request::get("https://picsum.photos/seed/1.759706314/1024"); ehttp::fetch(request, move |response| { let image = response.and_then(parse_response); sender.send(image); // send the results back to the UI thread. - frame.request_repaint(); // wake up UI thread + ctx.request_repaint(); // wake up UI thread }); promise }); diff --git a/egui-winit/src/epi.rs b/egui-winit/src/epi.rs index d257ca21edd..e09af05ef89 100644 --- a/egui-winit/src/epi.rs +++ b/egui-winit/src/epi.rs @@ -233,7 +233,6 @@ impl EpiIntegration { max_texture_side: usize, window: &winit::window::Window, gl: &std::rc::Rc, - repaint_signal: std::sync::Arc, persistence: crate::epi::Persistence, app: Box, ) -> Self { @@ -252,7 +251,6 @@ impl EpiIntegration { native_pixels_per_point: Some(crate::native_pixels_per_point(window)), }, output: Default::default(), - repaint_signal, }); if prefer_dark_mode == Some(true) { diff --git a/egui/src/context.rs b/egui/src/context.rs index fef366f68aa..75057f2c7f0 100644 --- a/egui/src/context.rs +++ b/egui/src/context.rs @@ -48,6 +48,7 @@ struct ContextImpl { /// While positive, keep requesting repaints. Decrement at the end of each frame. repaint_requests: u32, + request_repaint_callbacks: Option>, } impl ContextImpl { @@ -533,11 +534,28 @@ impl Context { impl Context { /// Call this if there is need to repaint the UI, i.e. if you are showing an animation. + /// /// If this is called at least once in a frame, then there will be another frame right after this. /// Call as many times as you wish, only one repaint will be issued. + /// + /// If called from outside the UI thread, the UI thread will wake up and run, + /// provided the egui integration has set that up via [`Self::set_request_repaint_callback`] + /// (this will work on `eframe`). pub fn request_repaint(&self) { // request two frames of repaint, just to cover some corner cases (frame delays): - self.write().repaint_requests = 2; + let mut ctx = self.write(); + ctx.repaint_requests = 2; + if let Some(callback) = &ctx.request_repaint_callbacks { + (callback)(); + } + } + + /// For integrations: this callback will be called when an egui user calls [`Self::request_repaint`]. + /// + /// This lets you wake up a sleeping UI thread. + pub fn set_request_repaint_callback(&self, callback: impl Fn() + Send + Sync + 'static) { + let callback = Box::new(callback); + self.write().request_repaint_callbacks = Some(callback); } /// Tell `egui` which fonts to use. diff --git a/egui_demo_lib/Cargo.toml b/egui_demo_lib/Cargo.toml index 11ff6764500..a41f3059ee9 100644 --- a/egui_demo_lib/Cargo.toml +++ b/egui_demo_lib/Cargo.toml @@ -39,6 +39,7 @@ epi = { version = "0.17.0", path = "../epi" } chrono = { version = "0.4", optional = true, features = ["js-sys", "wasmbind"] } enum-map = { version = "2", features = ["serde"] } +tracing = "0.1" unicode_names2 = { version = "0.5.0", default-features = false } # feature "http": diff --git a/egui_demo_lib/src/apps/http_app.rs b/egui_demo_lib/src/apps/http_app.rs index c8215b4254b..63ae9f01309 100644 --- a/egui_demo_lib/src/apps/http_app.rs +++ b/egui_demo_lib/src/apps/http_app.rs @@ -78,11 +78,10 @@ impl epi::App for HttpApp { if trigger_fetch { let ctx = ctx.clone(); - let frame = frame.clone(); let (sender, promise) = Promise::new(); let request = ehttp::Request::get(&self.url); ehttp::fetch(request, move |response| { - frame.request_repaint(); // wake up UI thread + ctx.request_repaint(); // wake up UI thread let resource = response.map(|response| Resource::from_response(&ctx, response)); sender.send(resource); }); diff --git a/egui_glow/Cargo.toml b/egui_glow/Cargo.toml index 924210a1eb9..90dfa48620b 100644 --- a/egui_glow/Cargo.toml +++ b/egui_glow/Cargo.toml @@ -64,6 +64,7 @@ epi = { version = "0.17.0", path = "../epi", optional = true } bytemuck = "1.7" glow = "0.11" memoffset = "0.6" +parking_lot = "0.12" tracing = "0.1" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] diff --git a/egui_glow/src/epi_backend.rs b/egui_glow/src/epi_backend.rs index 57c3f46ef92..bef12fad20d 100644 --- a/egui_glow/src/epi_backend.rs +++ b/egui_glow/src/epi_backend.rs @@ -3,14 +3,6 @@ use egui_winit::winit; struct RequestRepaintEvent; -struct GlowRepaintSignal(std::sync::Mutex>); - -impl epi::backend::RepaintSignal for GlowRepaintSignal { - fn request_repaint(&self) { - self.0.lock().unwrap().send_event(RequestRepaintEvent).ok(); - } -} - #[allow(unsafe_code)] fn create_display( window_builder: winit::window::WindowBuilder, @@ -56,10 +48,6 @@ pub fn run(app: Box, native_options: &epi::NativeOptions) -> ! { let (gl_window, gl) = create_display(window_builder, &event_loop); let gl = std::rc::Rc::new(gl); - let repaint_signal = std::sync::Arc::new(GlowRepaintSignal(std::sync::Mutex::new( - event_loop.create_proxy(), - ))); - let mut painter = crate::Painter::new(gl.clone(), None, "") .unwrap_or_else(|error| panic!("some OpenGL error occurred {}\n", error)); let mut integration = egui_winit::epi::EpiIntegration::new( @@ -67,11 +55,17 @@ pub fn run(app: Box, native_options: &epi::NativeOptions) -> ! { painter.max_texture_side(), gl_window.window(), &gl, - repaint_signal, persistence, app, ); + { + let event_loop_proxy = parking_lot::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; event_loop.run(move |event, _, control_flow| { diff --git a/egui_web/src/backend.rs b/egui_web/src/backend.rs index 50313303b8b..b85d1a08d80 100644 --- a/egui_web/src/backend.rs +++ b/egui_web/src/backend.rs @@ -50,12 +50,6 @@ impl NeedRepaint { } } -impl epi::backend::RepaintSignal for NeedRepaint { - fn request_repaint(&self) { - self.0.store(true, SeqCst); - } -} - // ---------------------------------------------------------------------------- fn web_location() -> epi::Location { @@ -150,8 +144,6 @@ impl AppRunner { let prefer_dark_mode = crate::prefer_dark_mode(); - let needs_repaint: std::sync::Arc = Default::default(); - let frame = epi::Frame::new(epi::backend::FrameData { info: epi::IntegrationInfo { name: "egui_web", @@ -163,10 +155,19 @@ impl AppRunner { native_pixels_per_point: Some(native_pixels_per_point()), }, output: Default::default(), - repaint_signal: needs_repaint.clone(), }); + let needs_repaint: std::sync::Arc = Default::default(); + let egui_ctx = egui::Context::default(); + + { + let needs_repaint = needs_repaint.clone(); + egui_ctx.set_request_repaint_callback(move || { + needs_repaint.0.store(true, SeqCst); + }); + } + load_memory(&egui_ctx); if prefer_dark_mode == Some(true) { egui_ctx.set_visuals(egui::Visuals::dark()); diff --git a/epi/src/lib.rs b/epi/src/lib.rs index 033a4a7880c..c618c0d8e87 100644 --- a/epi/src/lib.rs +++ b/epi/src/lib.rs @@ -359,13 +359,6 @@ impl Frame { self.lock().output.drag_window = true; } - /// This signals the [`egui`] integration that a repaint is required. - /// - /// Call this e.g. when a background process finishes in an async context and/or background thread. - pub fn request_repaint(&self) { - self.lock().repaint_signal.request_repaint(); - } - /// for integrations only: call once per frame pub fn take_app_output(&self) -> crate::backend::AppOutput { std::mem::take(&mut self.lock().output) @@ -524,14 +517,6 @@ pub const APP_KEY: &str = "app"; pub mod backend { use super::*; - /// How to signal the [`egui`] integration that a repaint is required. - pub trait RepaintSignal: Send + Sync { - /// This signals the [`egui`] integration that a repaint is required. - /// - /// Call this e.g. when a background process finishes in an async context and/or background thread. - fn request_repaint(&self); - } - /// The data required by [`Frame`] each frame. pub struct FrameData { /// Information about the integration. @@ -539,9 +524,6 @@ pub mod backend { /// Where the app can issue commands back to the integration. pub output: AppOutput, - - /// If you need to request a repaint from another thread, clone this and send it to that other thread. - pub repaint_signal: std::sync::Arc, } /// Action that can be taken by the user app.