Skip to content

Commit

Permalink
Output the repaint delay for integrations that don't install a callback
Browse files Browse the repository at this point in the history
  • Loading branch information
emilk committed Nov 15, 2023
1 parent 09b639e commit e222247
Show file tree
Hide file tree
Showing 3 changed files with 73 additions and 36 deletions.
6 changes: 4 additions & 2 deletions crates/eframe/src/native/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1169,6 +1169,7 @@ mod glow_integration {
builder,
viewport_ui_cb,
commands,
repaint_delay: _, // ignored - we listend to the repaint callback instead
},
) in viewport_output
{
Expand Down Expand Up @@ -1386,7 +1387,7 @@ mod glow_integration {
.egui_ctx
.set_request_repaint_callback(move |info| {
log::trace!("request_repaint_callback: {info:?}");
let when = Instant::now() + info.after;
let when = Instant::now() + info.delay;
let frame_nr = info.current_frame_nr;
event_loop_proxy
.lock()
Expand Down Expand Up @@ -1989,7 +1990,7 @@ mod wgpu_integration {
.egui_ctx
.set_request_repaint_callback(move |info| {
log::trace!("request_repaint_callback: {info:?}");
let when = Instant::now() + info.after;
let when = Instant::now() + info.delay;
let frame_nr = info.current_frame_nr;

event_loop_proxy
Expand Down Expand Up @@ -2676,6 +2677,7 @@ mod wgpu_integration {
builder,
viewport_ui_cb,
commands,
repaint_delay: _, // ignored - we listend to the repaint callback instead
},
) in viewport_output
{
Expand Down
93 changes: 59 additions & 34 deletions crates/egui/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ pub struct RequestRepaintInfo {
pub viewport_id: ViewportId,

/// Repaint after this duration. If zero, repaint as soon as possible.
pub after: Duration,
pub delay: Duration,

/// The current frame number.
///
Expand Down Expand Up @@ -71,30 +71,26 @@ impl ContextImpl {
self.request_repaint_after(Duration::ZERO, viewport_id);
}

fn request_repaint_after(&mut self, after: Duration, viewport_id: ViewportId) {
fn request_repaint_after(&mut self, delay: Duration, viewport_id: ViewportId) {
let mut viewport = self.viewports.entry(viewport_id).or_default();

// Each request results in two repaints, just to give some things time to settle.
// This solves some corner-cases of missing repaints on frame-delayed responses.
viewport.repaint.outstanding = 1;

let current_frame_nr = viewport.repaint.frame_nr;

self.call_repaint_callback(RequestRepaintInfo {
viewport_id,
after,
current_frame_nr,
});
}

fn call_repaint_callback(&mut self, info: RequestRepaintInfo) {
if let Some(callback) = &self.request_repaint_callback {
(callback)(info);
} else {
eprintln!(
"request_repaint_callback is not implemented by egui integration!
If is your integration you need to call `Context::set_request_repaint_callback`"
);
// We save some CPU time by only calling the callback if we need to.
// If the new delay is greater or equal to the previous lowest,
// it means we have already called the callback, and don't need to do it again.
if delay < viewport.repaint.repaint_delay {
viewport.repaint.repaint_delay = delay;

(callback)(RequestRepaintInfo {
viewport_id,
delay,
current_frame_nr: viewport.repaint.frame_nr,
});
}
}
}

Expand All @@ -106,10 +102,10 @@ impl ContextImpl {
}

#[must_use]
fn requested_repaint(&self, viewport_id: &ViewportId) -> bool {
self.viewports
.get(viewport_id)
.map_or(false, |v| 0 < v.repaint.outstanding)
fn has_requested_repaint(&self, viewport_id: &ViewportId) -> bool {
self.viewports.get(viewport_id).map_or(false, |v| {
0 < v.repaint.outstanding || v.repaint.repaint_delay < Duration::MAX
})
}
}

Expand Down Expand Up @@ -152,18 +148,41 @@ struct ViewportState {
}

/// Per-viewport state related to repaint scheduling.
#[derive(Default)]
struct ViewportRepaintInfo {
/// Monotonically increasing counter.
frame_nr: u64,

/// The duration which the backend will poll for new events
/// before forcing another egui update, even if there's no new events.
///
/// Also used to suppress multiple calls to the repaint callback during the same frame.
///
/// This is also returned in [`crate::ViewportOutput`].
repaint_delay: Duration,

/// While positive, keep requesting repaints. Decrement at the start of each frame.
outstanding: u8,

/// Did we?
requested_last_frame: bool,
}

impl Default for ViewportRepaintInfo {
fn default() -> Self {
Self {
frame_nr: 0,

// We haven't scheduled a repaint yet.
repaint_delay: Duration::MAX,

// Let's run a couple of frames at the start, because why not.
outstanding: 1,

requested_last_frame: false,
}
}
}

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

#[derive(Default)]
Expand Down Expand Up @@ -206,14 +225,19 @@ impl ContextImpl {
self.viewport_stack.push(ids);
let viewport = self.viewports.entry(viewport_id).or_default();

if 0 < viewport.repaint.outstanding {
if viewport.repaint.outstanding == 0 {
// We are repainting now, so we can wait a while for the next repaint.
viewport.repaint.repaint_delay = Duration::MAX;
} else {
viewport.repaint.repaint_delay = Duration::ZERO;
viewport.repaint.outstanding -= 1;
let current_frame_nr = viewport.repaint.frame_nr;
self.call_repaint_callback(RequestRepaintInfo {
viewport_id,
after: Duration::ZERO,
current_frame_nr,
});
if let Some(callback) = &self.request_repaint_callback {
(callback)(RequestRepaintInfo {
viewport_id,
delay: Duration::ZERO,
current_frame_nr: viewport.repaint.frame_nr,
});
}
}

if let Some(new_pixels_per_point) = self.memory.override_pixels_per_point {
Expand Down Expand Up @@ -1222,14 +1246,14 @@ impl Context {

/// Has a repaint been requested for the current viewport?
#[must_use]
pub fn requested_repaint(&self) -> bool {
self.requested_repaint_for(&self.viewport_id())
pub fn has_requested_repaint(&self) -> bool {
self.has_requested_repaint_for(&self.viewport_id())
}

/// Has a repaint been requested for the given viewport?
#[must_use]
pub fn requested_repaint_for(&self, viewport_id: &ViewportId) -> bool {
self.read(|ctx| ctx.requested_repaint(viewport_id))
pub fn has_requested_repaint_for(&self, viewport_id: &ViewportId) -> bool {
self.read(|ctx| ctx.has_requested_repaint(viewport_id))
}

/// For integrations: this callback will be called when an egui user calls [`Self::request_repaint`] or [`Self::request_repaint_after`].
Expand Down Expand Up @@ -1590,6 +1614,7 @@ impl ContextImpl {
builder: viewport.builder.clone(),
viewport_ui_cb: viewport.viewport_ui_cb.clone(),
commands,
repaint_delay: viewport.repaint.repaint_delay,
},
)
})
Expand Down
10 changes: 10 additions & 0 deletions crates/egui/src/viewport.rs
Original file line number Diff line number Diff line change
Expand Up @@ -677,6 +677,14 @@ pub struct ViewportOutput {

/// Commands to change the viewport, e.g. window title and size.
pub commands: Vec<ViewportCommand>,

/// Schedulare a repaint of this viewport after this delay.
///
/// It is preferably to instead install a [`Context::set_request_repaint_callback`],
/// but if you haven't, you can use this instead.
///
/// If the duration is zero, schedule a repaint immediately.
pub repaint_delay: std::time::Duration,
}

impl ViewportOutput {
Expand All @@ -691,12 +699,14 @@ impl ViewportOutput {
builder,
viewport_ui_cb,
mut commands,
repaint_delay,
} = newer;

self.ids = ids;
self.builder.patch(&builder);
self.viewport_ui_cb = viewport_ui_cb;
self.commands.append(&mut commands);
self.repaint_delay = self.repaint_delay.min(repaint_delay);
}
}

Expand Down

0 comments on commit e222247

Please sign in to comment.