`Context::repaint_causes`: `file:line` of what caused a repaint (#3949)
* Basic version of https://github.com/emilk/egui/issues/3931 This adds `Context::repaint_causes` which returns a `Vec<RepaintCause>`, containing the `file:line` of the call to `ctx.request_repaint()`. If your application is stuck forever repainting, this could be a useful tool to diagnose it.
This commit is contained in:
parent
c5352cf6c1
commit
114f820170
|
|
@ -1,6 +1,6 @@
|
||||||
#![warn(missing_docs)] // Let's keep `Context` well-documented.
|
#![warn(missing_docs)] // Let's keep `Context` well-documented.
|
||||||
|
|
||||||
use std::{borrow::Cow, cell::RefCell, sync::Arc, time::Duration};
|
use std::{borrow::Cow, cell::RefCell, panic::Location, sync::Arc, time::Duration};
|
||||||
|
|
||||||
use ahash::HashMap;
|
use ahash::HashMap;
|
||||||
use epaint::{mutex::*, stats::*, text::Fonts, util::OrderedFloat, TessellationOptions, *};
|
use epaint::{mutex::*, stats::*, text::Fonts, util::OrderedFloat, TessellationOptions, *};
|
||||||
|
|
@ -112,6 +112,12 @@ impl ContextImpl {
|
||||||
fn begin_frame_repaint_logic(&mut self, viewport_id: ViewportId) {
|
fn begin_frame_repaint_logic(&mut self, viewport_id: ViewportId) {
|
||||||
let viewport = self.viewports.entry(viewport_id).or_default();
|
let viewport = self.viewports.entry(viewport_id).or_default();
|
||||||
|
|
||||||
|
std::mem::swap(
|
||||||
|
&mut viewport.repaint.prev_causes,
|
||||||
|
&mut viewport.repaint.causes,
|
||||||
|
);
|
||||||
|
viewport.repaint.causes.clear();
|
||||||
|
|
||||||
viewport.repaint.prev_frame_paint_delay = viewport.repaint.repaint_delay;
|
viewport.repaint.prev_frame_paint_delay = viewport.repaint.repaint_delay;
|
||||||
|
|
||||||
if viewport.repaint.outstanding == 0 {
|
if viewport.repaint.outstanding == 0 {
|
||||||
|
|
@ -130,17 +136,24 @@ impl ContextImpl {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn request_repaint(&mut self, viewport_id: ViewportId) {
|
fn request_repaint(&mut self, viewport_id: ViewportId, cause: RepaintCause) {
|
||||||
self.request_repaint_after(Duration::ZERO, viewport_id);
|
self.request_repaint_after(Duration::ZERO, viewport_id, cause);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn request_repaint_after(&mut self, delay: Duration, viewport_id: ViewportId) {
|
fn request_repaint_after(
|
||||||
|
&mut self,
|
||||||
|
delay: Duration,
|
||||||
|
viewport_id: ViewportId,
|
||||||
|
cause: RepaintCause,
|
||||||
|
) {
|
||||||
let viewport = self.viewports.entry(viewport_id).or_default();
|
let viewport = self.viewports.entry(viewport_id).or_default();
|
||||||
|
|
||||||
// Each request results in two repaints, just to give some things time to settle.
|
// 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.
|
// This solves some corner-cases of missing repaints on frame-delayed responses.
|
||||||
viewport.repaint.outstanding = 1;
|
viewport.repaint.outstanding = 1;
|
||||||
|
|
||||||
|
viewport.repaint.causes.push(cause);
|
||||||
|
|
||||||
// We save some CPU time by only calling the callback if we need to.
|
// 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,
|
// 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.
|
// it means we have already called the callback, and don't need to do it again.
|
||||||
|
|
@ -262,6 +275,35 @@ struct ViewportState {
|
||||||
commands: Vec<ViewportCommand>,
|
commands: Vec<ViewportCommand>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// What called [`Context::request_repaint`]?
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct RepaintCause {
|
||||||
|
/// What file had the call that requested the repaint?
|
||||||
|
pub file: String,
|
||||||
|
|
||||||
|
/// What line number of the the call that requested the repaint?
|
||||||
|
pub line: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RepaintCause {
|
||||||
|
/// Capture the file and line number of the call site.
|
||||||
|
#[allow(clippy::new_without_default)]
|
||||||
|
#[track_caller]
|
||||||
|
pub fn new() -> Self {
|
||||||
|
let caller = Location::caller();
|
||||||
|
Self {
|
||||||
|
file: caller.file().to_owned(),
|
||||||
|
line: caller.line(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for RepaintCause {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{}:{}", self.file, self.line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Per-viewport state related to repaint scheduling.
|
/// Per-viewport state related to repaint scheduling.
|
||||||
struct ViewportRepaintInfo {
|
struct ViewportRepaintInfo {
|
||||||
/// Monotonically increasing counter.
|
/// Monotonically increasing counter.
|
||||||
|
|
@ -278,6 +320,13 @@ struct ViewportRepaintInfo {
|
||||||
/// While positive, keep requesting repaints. Decrement at the start of each frame.
|
/// While positive, keep requesting repaints. Decrement at the start of each frame.
|
||||||
outstanding: u8,
|
outstanding: u8,
|
||||||
|
|
||||||
|
/// What caused repaints during this frame?
|
||||||
|
causes: Vec<RepaintCause>,
|
||||||
|
|
||||||
|
/// What triggered a repaint the previous frame?
|
||||||
|
/// (i.e: why are we updating now?)
|
||||||
|
prev_causes: Vec<RepaintCause>,
|
||||||
|
|
||||||
/// What was the output of `repaint_delay` on the previous frame?
|
/// What was the output of `repaint_delay` on the previous frame?
|
||||||
///
|
///
|
||||||
/// If this was zero, we are repaining as quickly as possible
|
/// If this was zero, we are repaining as quickly as possible
|
||||||
|
|
@ -296,6 +345,9 @@ impl Default for ViewportRepaintInfo {
|
||||||
// Let's run a couple of frames at the start, because why not.
|
// Let's run a couple of frames at the start, because why not.
|
||||||
outstanding: 1,
|
outstanding: 1,
|
||||||
|
|
||||||
|
causes: Default::default(),
|
||||||
|
prev_causes: Default::default(),
|
||||||
|
|
||||||
prev_frame_paint_delay: Duration::MAX,
|
prev_frame_paint_delay: Duration::MAX,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1306,6 +1358,7 @@ impl Context {
|
||||||
/// (this will work on `eframe`).
|
/// (this will work on `eframe`).
|
||||||
///
|
///
|
||||||
/// This will repaint the current viewport.
|
/// This will repaint the current viewport.
|
||||||
|
#[track_caller]
|
||||||
pub fn request_repaint(&self) {
|
pub fn request_repaint(&self) {
|
||||||
self.request_repaint_of(self.viewport_id());
|
self.request_repaint_of(self.viewport_id());
|
||||||
}
|
}
|
||||||
|
|
@ -1322,8 +1375,10 @@ impl Context {
|
||||||
/// (this will work on `eframe`).
|
/// (this will work on `eframe`).
|
||||||
///
|
///
|
||||||
/// This will repaint the specified viewport.
|
/// This will repaint the specified viewport.
|
||||||
|
#[track_caller]
|
||||||
pub fn request_repaint_of(&self, id: ViewportId) {
|
pub fn request_repaint_of(&self, id: ViewportId) {
|
||||||
self.write(|ctx| ctx.request_repaint(id));
|
let cause = RepaintCause::new();
|
||||||
|
self.write(|ctx| ctx.request_repaint(id, cause));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Request repaint after at most the specified duration elapses.
|
/// Request repaint after at most the specified duration elapses.
|
||||||
|
|
@ -1354,6 +1409,7 @@ impl Context {
|
||||||
/// during app idle time where we are not receiving any new input events.
|
/// during app idle time where we are not receiving any new input events.
|
||||||
///
|
///
|
||||||
/// This repaints the current viewport
|
/// This repaints the current viewport
|
||||||
|
#[track_caller]
|
||||||
pub fn request_repaint_after(&self, duration: Duration) {
|
pub fn request_repaint_after(&self, duration: Duration) {
|
||||||
self.request_repaint_after_for(duration, self.viewport_id());
|
self.request_repaint_after_for(duration, self.viewport_id());
|
||||||
}
|
}
|
||||||
|
|
@ -1386,8 +1442,10 @@ impl Context {
|
||||||
/// during app idle time where we are not receiving any new input events.
|
/// during app idle time where we are not receiving any new input events.
|
||||||
///
|
///
|
||||||
/// This repaints the specified viewport
|
/// This repaints the specified viewport
|
||||||
|
#[track_caller]
|
||||||
pub fn request_repaint_after_for(&self, duration: Duration, id: ViewportId) {
|
pub fn request_repaint_after_for(&self, duration: Duration, id: ViewportId) {
|
||||||
self.write(|ctx| ctx.request_repaint_after(duration, id));
|
let cause = RepaintCause::new();
|
||||||
|
self.write(|ctx| ctx.request_repaint_after(duration, id, cause));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Was a repaint requested last frame for the current viewport?
|
/// Was a repaint requested last frame for the current viewport?
|
||||||
|
|
@ -1414,6 +1472,18 @@ impl Context {
|
||||||
self.read(|ctx| ctx.has_requested_repaint(viewport_id))
|
self.read(|ctx| ctx.has_requested_repaint(viewport_id))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Why are we repainting?
|
||||||
|
///
|
||||||
|
/// This can be helpful in debugging why egui is constantly repainting.
|
||||||
|
pub fn repaint_causes(&self) -> Vec<RepaintCause> {
|
||||||
|
self.read(|ctx| {
|
||||||
|
ctx.viewports
|
||||||
|
.get(&ctx.viewport_id())
|
||||||
|
.map(|v| v.repaint.causes.clone())
|
||||||
|
})
|
||||||
|
.unwrap_or_default()
|
||||||
|
}
|
||||||
|
|
||||||
/// For integrations: this callback will be called when an egui user calls [`Self::request_repaint`] or [`Self::request_repaint_after`].
|
/// For integrations: this callback will be called when an egui user calls [`Self::request_repaint`] or [`Self::request_repaint_after`].
|
||||||
///
|
///
|
||||||
/// This lets you wake up a sleeping UI thread.
|
/// This lets you wake up a sleeping UI thread.
|
||||||
|
|
@ -1579,11 +1649,12 @@ impl Context {
|
||||||
/// [`Options::zoom_factor`].
|
/// [`Options::zoom_factor`].
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn set_zoom_factor(&self, zoom_factor: f32) {
|
pub fn set_zoom_factor(&self, zoom_factor: f32) {
|
||||||
|
let cause = RepaintCause::new();
|
||||||
self.write(|ctx| {
|
self.write(|ctx| {
|
||||||
if ctx.memory.options.zoom_factor != zoom_factor {
|
if ctx.memory.options.zoom_factor != zoom_factor {
|
||||||
ctx.new_zoom_factor = Some(zoom_factor);
|
ctx.new_zoom_factor = Some(zoom_factor);
|
||||||
for id in ctx.all_viewport_ids() {
|
for viewport_id in ctx.all_viewport_ids() {
|
||||||
ctx.request_repaint(id);
|
ctx.request_repaint(viewport_id, cause.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -1830,7 +1901,7 @@ impl ContextImpl {
|
||||||
}
|
}
|
||||||
|
|
||||||
if repaint_needed || viewport.input.wants_repaint() {
|
if repaint_needed || viewport.input.wants_repaint() {
|
||||||
self.request_repaint(ended_viewport_id);
|
self.request_repaint(ended_viewport_id, RepaintCause::new());
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------
|
// -------------------
|
||||||
|
|
@ -2296,12 +2367,14 @@ impl Context {
|
||||||
/// The function will call [`Self::request_repaint()`] when appropriate.
|
/// The function will call [`Self::request_repaint()`] when appropriate.
|
||||||
///
|
///
|
||||||
/// The animation time is taken from [`Style::animation_time`].
|
/// The animation time is taken from [`Style::animation_time`].
|
||||||
|
#[track_caller] // To track repaint cause
|
||||||
pub fn animate_bool(&self, id: Id, value: bool) -> f32 {
|
pub fn animate_bool(&self, id: Id, value: bool) -> f32 {
|
||||||
let animation_time = self.style().animation_time;
|
let animation_time = self.style().animation_time;
|
||||||
self.animate_bool_with_time(id, value, animation_time)
|
self.animate_bool_with_time(id, value, animation_time)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Like [`Self::animate_bool`] but allows you to control the animation time.
|
/// Like [`Self::animate_bool`] but allows you to control the animation time.
|
||||||
|
#[track_caller] // To track repaint cause
|
||||||
pub fn animate_bool_with_time(&self, id: Id, target_value: bool, animation_time: f32) -> f32 {
|
pub fn animate_bool_with_time(&self, id: Id, target_value: bool, animation_time: f32) -> f32 {
|
||||||
let animated_value = self.write(|ctx| {
|
let animated_value = self.write(|ctx| {
|
||||||
ctx.animation_manager.animate_bool(
|
ctx.animation_manager.animate_bool(
|
||||||
|
|
@ -2322,6 +2395,7 @@ impl Context {
|
||||||
///
|
///
|
||||||
/// At the first call the value is written to memory.
|
/// At the first call the value is written to memory.
|
||||||
/// When it is called with a new value, it linearly interpolates to it in the given time.
|
/// When it is called with a new value, it linearly interpolates to it in the given time.
|
||||||
|
#[track_caller] // To track repaint cause
|
||||||
pub fn animate_value_with_time(&self, id: Id, target_value: f32, animation_time: f32) -> f32 {
|
pub fn animate_value_with_time(&self, id: Id, target_value: f32, animation_time: f32) -> f32 {
|
||||||
let animated_value = self.write(|ctx| {
|
let animated_value = self.write(|ctx| {
|
||||||
ctx.animation_manager.animate_value(
|
ctx.animation_manager.animate_value(
|
||||||
|
|
@ -2402,6 +2476,18 @@ impl Context {
|
||||||
.on_hover_text("This is approximately the number of text strings on screen");
|
.on_hover_text("This is approximately the number of text strings on screen");
|
||||||
ui.add_space(16.0);
|
ui.add_space(16.0);
|
||||||
|
|
||||||
|
CollapsingHeader::new("🔃 Repaint Causes")
|
||||||
|
.default_open(false)
|
||||||
|
.show(ui, |ui| {
|
||||||
|
ui.set_min_height(120.0);
|
||||||
|
ui.label("What caused egui to reapint:");
|
||||||
|
ui.add_space(8.0);
|
||||||
|
let causes = ui.ctx().repaint_causes();
|
||||||
|
for cause in causes {
|
||||||
|
ui.label(cause.to_string());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
CollapsingHeader::new("📥 Input")
|
CollapsingHeader::new("📥 Input")
|
||||||
.default_open(false)
|
.default_open(false)
|
||||||
.show(ui, |ui| {
|
.show(ui, |ui| {
|
||||||
|
|
|
||||||
|
|
@ -410,7 +410,7 @@ pub mod text {
|
||||||
|
|
||||||
pub use {
|
pub use {
|
||||||
containers::*,
|
containers::*,
|
||||||
context::{Context, RequestRepaintInfo, WidgetRect, WidgetRects},
|
context::{Context, RepaintCause, RequestRepaintInfo, WidgetRect, WidgetRects},
|
||||||
data::{
|
data::{
|
||||||
input::*,
|
input::*,
|
||||||
output::{
|
output::{
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue