Skip to content

Commit

Permalink
Make Context::request_repaint safe to call from any thread.
Browse files Browse the repository at this point in the history
Closes #1379
  • Loading branch information
emilk committed Mar 20, 2022
1 parent 861e129 commit 4d98f81
Show file tree
Hide file tree
Showing 5 changed files with 64 additions and 49 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ NOTE: [`epaint`](epaint/CHANGELOG.md), [`eframe`](eframe/CHANGELOG.md), [`egui_w
### Added ⭐
* Added `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)).
* `Context::request_repaint` will wake up UI thread, if integrations has called `Context::with_repaint_callback` ([#1366](https://github.com/emilk/egui/pull/1366)).
* Added `Ui::push_id` ([#1374](https://github.com/emilk/egui/pull/1374)).

### Changed 🔧
Expand Down
3 changes: 1 addition & 2 deletions egui-winit/src/epi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -229,12 +229,11 @@ pub struct EpiIntegration {
impl EpiIntegration {
pub fn new(
integration_name: &'static str,
egui_ctx: egui::Context,
max_texture_side: usize,
window: &winit::window::Window,
persistence: crate::epi::Persistence,
) -> Self {
let egui_ctx = egui::Context::default();

*egui_ctx.memory() = persistence.load_memory().unwrap_or_default();

let prefer_dark_mode = prefer_dark_mode();
Expand Down
83 changes: 50 additions & 33 deletions egui/src/context.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
// #![warn(missing_docs)]

use std::sync::atomic::AtomicU32;
use std::sync::atomic::Ordering::SeqCst;

use crate::{
animation_manager::AnimationManager, data::output::PlatformOutput, frame_state::FrameState,
input_state::*, layers::GraphicLayers, memory::Options, output::FullOutput, TextureHandle, *,
Expand Down Expand Up @@ -27,6 +30,25 @@ impl Default for WrappedTextureManager {

// ----------------------------------------------------------------------------

struct RepaintInfo {
/// While positive, keep requesting repaints. Decrement at the end of each frame.
repaint_requests: AtomicU32,
request_repaint_callbacks: Option<Box<dyn Fn() + Send + Sync>>,
}

impl Default for RepaintInfo {
fn default() -> Self {
Self {
// Start with painting an extra frame to compensate for some widgets
// that take two frames before they "settle":
repaint_requests: 1.into(),
request_repaint_callbacks: None,
}
}
}

// ----------------------------------------------------------------------------

#[derive(Default)]
struct ContextImpl {
/// `None` until the start of the first frame.
Expand All @@ -45,10 +67,6 @@ struct ContextImpl {
output: PlatformOutput,

paint_stats: PaintStats,

/// While positive, keep requesting repaints. Decrement at the end of each frame.
repaint_requests: u32,
request_repaint_callbacks: Option<Box<dyn Fn() + Send + Sync>>,
}

impl ContextImpl {
Expand Down Expand Up @@ -142,33 +160,41 @@ impl ContextImpl {
/// paint(full_output.textures_delta, clipped_primitives);
/// }
/// ```
#[derive(Clone)]
pub struct Context(Arc<RwLock<ContextImpl>>);
#[derive(Clone, Default)]
pub struct Context {
ctx: Arc<RwLock<ContextImpl>>,
repaint_info: Arc<RepaintInfo>,
}

impl std::cmp::PartialEq for Context {
fn eq(&self, other: &Context) -> bool {
Arc::ptr_eq(&self.0, &other.0)
Arc::ptr_eq(&self.ctx, &other.ctx)
}
}

impl Default for Context {
fn default() -> Self {
Self(Arc::new(RwLock::new(ContextImpl {
// Start with painting an extra frame to compensate for some widgets
// that take two frames before they "settle":
repaint_requests: 1,
..ContextImpl::default()
})))
impl Context {
pub fn new() -> Self {
Self::default()
}

/// Construct a [`Context`] with a callback that is called when the user calls
/// [`Context::request_repaint`].
pub fn with_repaint_callback(callback: impl Fn() + Send + Sync + 'static) -> Self {
Self {
ctx: Default::default(),
repaint_info: Arc::new(RepaintInfo {
request_repaint_callbacks: Some(Box::new(callback)),
..Default::default()
}),
}
}
}

impl Context {
fn read(&self) -> RwLockReadGuard<'_, ContextImpl> {
self.0.read()
self.ctx.read()
}

fn write(&self) -> RwLockWriteGuard<'_, ContextImpl> {
self.0.write()
self.ctx.write()
}

/// Run the ui code for one frame.
Expand Down Expand Up @@ -539,25 +565,16 @@ impl Context {
/// 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`]
/// provided the egui integration has set that up via [`Self::with_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):
let mut ctx = self.write();
ctx.repaint_requests = 2;
if let Some(callback) = &ctx.request_repaint_callbacks {
self.repaint_info.repaint_requests.store(2, SeqCst);
if let Some(callback) = &self.repaint_info.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.
///
/// The default `egui` fonts only support latin and cyrillic alphabets,
Expand Down Expand Up @@ -765,8 +782,8 @@ impl Context {

let platform_output: PlatformOutput = std::mem::take(&mut self.output());

let needs_repaint = if self.read().repaint_requests > 0 {
self.write().repaint_requests -= 1;
let needs_repaint = if self.repaint_info.repaint_requests.load(SeqCst) > 0 {
self.repaint_info.repaint_requests.fetch_sub(1, SeqCst);
true
} else {
false
Expand Down
15 changes: 8 additions & 7 deletions egui_glow/src/epi_backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,20 +51,21 @@ pub fn run(app_name: &str, native_options: &epi::NativeOptions, app_creator: epi
let mut painter = crate::Painter::new(gl.clone(), None, "")
.unwrap_or_else(|error| panic!("some OpenGL error occurred {}\n", error));

let event_loop_proxy = parking_lot::Mutex::new(event_loop.create_proxy());
let egui_ctx = egui::Context::with_repaint_callback({
move || {
event_loop_proxy.lock().send_event(RequestRepaintEvent).ok();
}
});

let mut integration = egui_winit::epi::EpiIntegration::new(
"egui_glow",
egui_ctx,
painter.max_texture_side(),
gl_window.window(),
persistence,
);

{
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 app = app_creator(&epi::CreationContext {
egui_ctx: integration.egui_ctx.clone(),
integration_info: integration.frame.info(),
Expand Down
10 changes: 4 additions & 6 deletions egui_web/src/backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,14 +159,12 @@ impl AppRunner {

let needs_repaint: std::sync::Arc<NeedRepaint> = Default::default();

let egui_ctx = egui::Context::default();

{
let egui_ctx = egui::Context::with_repaint_callback({
let needs_repaint = needs_repaint.clone();
egui_ctx.set_request_repaint_callback(move || {
move || {
needs_repaint.0.store(true, SeqCst);
});
}
}
});

load_memory(&egui_ctx);
if prefer_dark_mode == Some(true) {
Expand Down

0 comments on commit 4d98f81

Please sign in to comment.