Fix: `request_repaint_after` works even when called from background thread (#2939)
* Refactor repaint logic * request_repaint_after also fires the request_repaint callback * Bug fixes * Add test to egui_demo_app * build_demo_web: build debug unless --release is specified * Fix the web backend too * Run special clippy for wasm, forbidding some types/methods * Remove wasm_bindgen_check.sh * Fix typos * Revert "Remove wasm_bindgen_check.sh" This reverts commit 92dde253446a6930f34f2fcf67f76bc11669ec3b. * Only run cranky/clippy once
This commit is contained in:
parent
d46cf067ea
commit
834e2e9f50
|
|
@ -157,10 +157,7 @@ jobs:
|
|||
- run: ./scripts/wasm_bindgen_check.sh --skip-setup
|
||||
|
||||
- name: Cranky wasm32
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: cranky
|
||||
args: --target wasm32-unknown-unknown --all-features -p egui_demo_app --lib -- -D warnings
|
||||
run: ./scripts/clippy_wasm.sh
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
|
|
|||
|
|
@ -1272,7 +1272,9 @@ dependencies = [
|
|||
"log",
|
||||
"poll-promise",
|
||||
"serde",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
|
|||
11
clippy.toml
11
clippy.toml
|
|
@ -1 +1,10 @@
|
|||
doc-valid-idents = ["AccessKit", ".."]
|
||||
# There is also a scripts/clippy_wasm/clippy.toml which forbids some mthods that are not available in wasm.
|
||||
|
||||
msrv = "1.65"
|
||||
|
||||
# Allow-list of words for markdown in dosctrings https://rust-lang.github.io/rust-clippy/master/index.html#doc_markdown
|
||||
doc-valid-idents = [
|
||||
# You must also update the same list in the root `clippy.toml`!
|
||||
"AccessKit",
|
||||
"..",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
//! 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::time::{Duration, Instant};
|
||||
use std::time::Instant;
|
||||
|
||||
use winit::event_loop::{
|
||||
ControlFlow, EventLoop, EventLoopBuilder, EventLoopProxy, EventLoopWindowTarget,
|
||||
|
|
@ -19,7 +19,12 @@ use super::epi_integration::{self, EpiIntegration};
|
|||
|
||||
#[derive(Debug)]
|
||||
pub enum UserEvent {
|
||||
RequestRepaint,
|
||||
RequestRepaint {
|
||||
when: Instant,
|
||||
/// What the frame number was when the repaint was _requested_.
|
||||
frame_nr: u64,
|
||||
},
|
||||
|
||||
#[cfg(feature = "accesskit")]
|
||||
AccessKitActionRequest(accesskit_winit::ActionRequestEvent),
|
||||
}
|
||||
|
|
@ -58,6 +63,9 @@ enum EventResult {
|
|||
}
|
||||
|
||||
trait WinitApp {
|
||||
/// The current frame number, as reported by egui.
|
||||
fn frame_nr(&self) -> u64;
|
||||
|
||||
fn is_focused(&self) -> bool;
|
||||
|
||||
fn integration(&self) -> Option<&EpiIntegration>;
|
||||
|
|
@ -66,7 +74,7 @@ trait WinitApp {
|
|||
|
||||
fn save_and_destroy(&mut self);
|
||||
|
||||
fn paint(&mut self) -> EventResult;
|
||||
fn run_ui_and_paint(&mut self) -> EventResult;
|
||||
|
||||
fn on_event(
|
||||
&mut self,
|
||||
|
|
@ -136,18 +144,30 @@ fn run_and_return(
|
|||
// 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) => {
|
||||
next_repaint_time = Instant::now() + Duration::from_secs(1_000_000_000);
|
||||
winit_app.paint()
|
||||
next_repaint_time = extremely_far_future();
|
||||
winit_app.run_ui_and_paint()
|
||||
}
|
||||
winit::event::Event::RedrawRequested(_) if !cfg!(windows) => {
|
||||
next_repaint_time = Instant::now() + Duration::from_secs(1_000_000_000);
|
||||
winit_app.paint()
|
||||
next_repaint_time = extremely_far_future();
|
||||
winit_app.run_ui_and_paint()
|
||||
}
|
||||
|
||||
winit::event::Event::UserEvent(UserEvent::RequestRepaint)
|
||||
| winit::event::Event::NewEvents(winit::event::StartCause::ResumeTimeReached {
|
||||
winit::event::Event::UserEvent(UserEvent::RequestRepaint { when, frame_nr }) => {
|
||||
if winit_app.frame_nr() == *frame_nr {
|
||||
log::trace!("UserEvent::RequestRepaint scheduling repaint at {when:?}");
|
||||
EventResult::RepaintAt(*when)
|
||||
} else {
|
||||
log::trace!("Got outdated UserEvent::RequestRepaint");
|
||||
EventResult::Wait // old request - we've already repainted
|
||||
}
|
||||
}
|
||||
|
||||
winit::event::Event::NewEvents(winit::event::StartCause::ResumeTimeReached {
|
||||
..
|
||||
}) => EventResult::RepaintNext,
|
||||
}) => {
|
||||
log::trace!("Woke up to check next_repaint_time");
|
||||
EventResult::Wait
|
||||
}
|
||||
|
||||
winit::event::Event::WindowEvent { window_id, .. }
|
||||
if winit_app.window().is_none()
|
||||
|
|
@ -174,8 +194,8 @@ fn run_and_return(
|
|||
log::trace!("Repaint caused by winit::Event: {:?}", event);
|
||||
if cfg!(windows) {
|
||||
// Fix flickering on Windows, see https://github.com/emilk/egui/pull/2280
|
||||
next_repaint_time = Instant::now() + Duration::from_secs(1_000_000_000);
|
||||
winit_app.paint();
|
||||
next_repaint_time = extremely_far_future();
|
||||
winit_app.run_ui_and_paint();
|
||||
} else {
|
||||
// Fix for https://github.com/emilk/egui/issues/2425
|
||||
next_repaint_time = Instant::now();
|
||||
|
|
@ -196,18 +216,20 @@ fn run_and_return(
|
|||
}
|
||||
}
|
||||
|
||||
*control_flow = match next_repaint_time.checked_duration_since(Instant::now()) {
|
||||
None => {
|
||||
if let Some(window) = winit_app.window() {
|
||||
window.request_redraw();
|
||||
}
|
||||
next_repaint_time = Instant::now() + Duration::from_secs(1_000_000_000);
|
||||
ControlFlow::Poll
|
||||
*control_flow = if next_repaint_time <= Instant::now() {
|
||||
if let Some(window) = winit_app.window() {
|
||||
log::trace!("request_redraw");
|
||||
window.request_redraw();
|
||||
}
|
||||
Some(time_until_next_repaint) => {
|
||||
ControlFlow::WaitUntil(Instant::now() + time_until_next_repaint)
|
||||
next_repaint_time = extremely_far_future();
|
||||
ControlFlow::Poll
|
||||
} else {
|
||||
let time_until_next = next_repaint_time.saturating_duration_since(Instant::now());
|
||||
if time_until_next < std::time::Duration::from_secs(10_000) {
|
||||
log::trace!("WaitUntil {time_until_next:?}");
|
||||
}
|
||||
}
|
||||
ControlFlow::WaitUntil(next_repaint_time)
|
||||
};
|
||||
});
|
||||
|
||||
log::debug!("eframe window closed");
|
||||
|
|
@ -239,18 +261,25 @@ fn run_and_exit(event_loop: EventLoop<UserEvent>, mut winit_app: impl WinitApp +
|
|||
// 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) => {
|
||||
next_repaint_time = Instant::now() + Duration::from_secs(1_000_000_000);
|
||||
winit_app.paint()
|
||||
next_repaint_time = extremely_far_future();
|
||||
winit_app.run_ui_and_paint()
|
||||
}
|
||||
winit::event::Event::RedrawRequested(_) if !cfg!(windows) => {
|
||||
next_repaint_time = Instant::now() + Duration::from_secs(1_000_000_000);
|
||||
winit_app.paint()
|
||||
next_repaint_time = extremely_far_future();
|
||||
winit_app.run_ui_and_paint()
|
||||
}
|
||||
|
||||
winit::event::Event::UserEvent(UserEvent::RequestRepaint)
|
||||
| winit::event::Event::NewEvents(winit::event::StartCause::ResumeTimeReached {
|
||||
winit::event::Event::UserEvent(UserEvent::RequestRepaint { when, frame_nr }) => {
|
||||
if winit_app.frame_nr() == frame_nr {
|
||||
EventResult::RepaintAt(when)
|
||||
} else {
|
||||
EventResult::Wait // old request - we've already repainted
|
||||
}
|
||||
}
|
||||
|
||||
winit::event::Event::NewEvents(winit::event::StartCause::ResumeTimeReached {
|
||||
..
|
||||
}) => EventResult::RepaintNext,
|
||||
}) => EventResult::Wait, // We just woke up to check next_repaint_time
|
||||
|
||||
event => match winit_app.on_event(event_loop, &event) {
|
||||
Ok(event_result) => event_result,
|
||||
|
|
@ -265,8 +294,8 @@ fn run_and_exit(event_loop: EventLoop<UserEvent>, mut winit_app: impl WinitApp +
|
|||
EventResult::RepaintNow => {
|
||||
if cfg!(windows) {
|
||||
// Fix flickering on Windows, see https://github.com/emilk/egui/pull/2280
|
||||
next_repaint_time = Instant::now() + Duration::from_secs(1_000_000_000);
|
||||
winit_app.paint();
|
||||
next_repaint_time = extremely_far_future();
|
||||
winit_app.run_ui_and_paint();
|
||||
} else {
|
||||
// Fix for https://github.com/emilk/egui/issues/2425
|
||||
next_repaint_time = Instant::now();
|
||||
|
|
@ -286,17 +315,15 @@ fn run_and_exit(event_loop: EventLoop<UserEvent>, mut winit_app: impl WinitApp +
|
|||
}
|
||||
}
|
||||
|
||||
*control_flow = match next_repaint_time.checked_duration_since(Instant::now()) {
|
||||
None => {
|
||||
if let Some(window) = winit_app.window() {
|
||||
window.request_redraw();
|
||||
}
|
||||
ControlFlow::Poll
|
||||
*control_flow = if next_repaint_time <= Instant::now() {
|
||||
if let Some(window) = winit_app.window() {
|
||||
window.request_redraw();
|
||||
}
|
||||
Some(time_until_next_repaint) => {
|
||||
ControlFlow::WaitUntil(Instant::now() + time_until_next_repaint)
|
||||
}
|
||||
}
|
||||
next_repaint_time = extremely_far_future();
|
||||
ControlFlow::Poll
|
||||
} else {
|
||||
ControlFlow::WaitUntil(next_repaint_time)
|
||||
};
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -601,8 +628,6 @@ mod glow_integration {
|
|||
// suspends and resumes.
|
||||
app_creator: Option<epi::AppCreator>,
|
||||
is_focused: bool,
|
||||
|
||||
frame_nr: u64,
|
||||
}
|
||||
|
||||
impl GlowWinitApp {
|
||||
|
|
@ -619,7 +644,6 @@ mod glow_integration {
|
|||
running: None,
|
||||
app_creator: Some(app_creator),
|
||||
is_focused: true,
|
||||
frame_nr: 0,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -698,12 +722,17 @@ mod glow_integration {
|
|||
|
||||
{
|
||||
let event_loop_proxy = self.repaint_proxy.clone();
|
||||
integration.egui_ctx.set_request_repaint_callback(move || {
|
||||
event_loop_proxy
|
||||
.lock()
|
||||
.send_event(UserEvent::RequestRepaint)
|
||||
.ok();
|
||||
});
|
||||
integration
|
||||
.egui_ctx
|
||||
.set_request_repaint_callback(move |info| {
|
||||
log::trace!("request_repaint_callback: {info:?}");
|
||||
let when = Instant::now() + info.after;
|
||||
let frame_nr = info.current_frame_nr;
|
||||
event_loop_proxy
|
||||
.lock()
|
||||
.send_event(UserEvent::RequestRepaint { when, frame_nr })
|
||||
.ok();
|
||||
});
|
||||
}
|
||||
|
||||
let app_creator = std::mem::take(&mut self.app_creator)
|
||||
|
|
@ -734,6 +763,12 @@ mod glow_integration {
|
|||
}
|
||||
|
||||
impl WinitApp for GlowWinitApp {
|
||||
fn frame_nr(&self) -> u64 {
|
||||
self.running
|
||||
.as_ref()
|
||||
.map_or(0, |r| r.integration.egui_ctx.frame_nr())
|
||||
}
|
||||
|
||||
fn is_focused(&self) -> bool {
|
||||
self.is_focused
|
||||
}
|
||||
|
|
@ -756,7 +791,7 @@ mod glow_integration {
|
|||
}
|
||||
}
|
||||
|
||||
fn paint(&mut self) -> EventResult {
|
||||
fn run_ui_and_paint(&mut self) -> EventResult {
|
||||
if let Some(running) = &mut self.running {
|
||||
#[cfg(feature = "puffin")]
|
||||
puffin::GlobalProfiler::lock().new_frame();
|
||||
|
|
@ -820,7 +855,7 @@ mod glow_integration {
|
|||
|
||||
#[cfg(feature = "__screenshot")]
|
||||
// give it time to settle:
|
||||
if self.frame_nr == 2 {
|
||||
if integration.egui_ctx.frame_nr() == 2 {
|
||||
if let Ok(path) = std::env::var("EFRAME_SCREENSHOT_TO") {
|
||||
assert!(
|
||||
path.ends_with(".png"),
|
||||
|
|
@ -871,8 +906,6 @@ mod glow_integration {
|
|||
std::thread::sleep(std::time::Duration::from_millis(10));
|
||||
}
|
||||
|
||||
self.frame_nr += 1;
|
||||
|
||||
control_flow
|
||||
} else {
|
||||
EventResult::Wait
|
||||
|
|
@ -1150,13 +1183,18 @@ mod wgpu_integration {
|
|||
|
||||
{
|
||||
let event_loop_proxy = self.repaint_proxy.clone();
|
||||
integration.egui_ctx.set_request_repaint_callback(move || {
|
||||
event_loop_proxy
|
||||
.lock()
|
||||
.unwrap()
|
||||
.send_event(UserEvent::RequestRepaint)
|
||||
.ok();
|
||||
});
|
||||
integration
|
||||
.egui_ctx
|
||||
.set_request_repaint_callback(move |info| {
|
||||
log::trace!("request_repaint_callback: {info:?}");
|
||||
let when = Instant::now() + info.after;
|
||||
let frame_nr = info.current_frame_nr;
|
||||
event_loop_proxy
|
||||
.lock()
|
||||
.unwrap()
|
||||
.send_event(UserEvent::RequestRepaint { when, frame_nr })
|
||||
.ok();
|
||||
});
|
||||
}
|
||||
|
||||
let app_creator = std::mem::take(&mut self.app_creator)
|
||||
|
|
@ -1186,6 +1224,12 @@ mod wgpu_integration {
|
|||
}
|
||||
|
||||
impl WinitApp for WgpuWinitApp {
|
||||
fn frame_nr(&self) -> u64 {
|
||||
self.running
|
||||
.as_ref()
|
||||
.map_or(0, |r| r.integration.egui_ctx.frame_nr())
|
||||
}
|
||||
|
||||
fn is_focused(&self) -> bool {
|
||||
self.is_focused
|
||||
}
|
||||
|
|
@ -1214,7 +1258,7 @@ mod wgpu_integration {
|
|||
}
|
||||
}
|
||||
|
||||
fn paint(&mut self) -> EventResult {
|
||||
fn run_ui_and_paint(&mut self) -> EventResult {
|
||||
if let (Some(running), Some(window)) = (&mut self.running, &self.window) {
|
||||
#[cfg(feature = "puffin")]
|
||||
puffin::GlobalProfiler::lock().new_frame();
|
||||
|
|
@ -1433,12 +1477,11 @@ mod wgpu_integration {
|
|||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
#[cfg(feature = "wgpu")]
|
||||
pub use wgpu_integration::run_wgpu;
|
||||
|
||||
#[cfg(any(target_os = "windows", target_os = "macos"))]
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
fn system_theme(window: &winit::window::Window, options: &NativeOptions) -> Option<crate::Theme> {
|
||||
if options.follow_system_theme {
|
||||
window
|
||||
|
|
@ -1449,9 +1492,8 @@ fn system_theme(window: &winit::window::Window, options: &NativeOptions) -> Opti
|
|||
}
|
||||
}
|
||||
|
||||
// Winit only reads the system theme on macOS and Windows.
|
||||
// See: https://github.com/rust-windowing/winit/issues/1549
|
||||
#[cfg(not(any(target_os = "windows", target_os = "macos")))]
|
||||
fn system_theme(_window: &winit::window::Window, _options: &NativeOptions) -> Option<crate::Theme> {
|
||||
None
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
fn extremely_far_future() -> std::time::Instant {
|
||||
std::time::Instant::now() + std::time::Duration::from_secs(10_000_000_000)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -259,8 +259,8 @@ impl AppRunner {
|
|||
let needs_repaint: std::sync::Arc<NeedRepaint> = Default::default();
|
||||
{
|
||||
let needs_repaint = needs_repaint.clone();
|
||||
egui_ctx.set_request_repaint_callback(move || {
|
||||
needs_repaint.repaint_asap();
|
||||
egui_ctx.set_request_repaint_callback(move |info| {
|
||||
needs_repaint.repaint_after(info.after.as_secs_f64());
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,12 +4,15 @@ use egui::Key;
|
|||
|
||||
use super::*;
|
||||
|
||||
struct IsDestroyed(pub bool);
|
||||
|
||||
/// Calls `request_animation_frame` to schedule repaint.
|
||||
///
|
||||
/// It will only paint if needed, but will always call `request_animation_frame` immediately.
|
||||
pub fn paint_and_schedule(
|
||||
runner_ref: &AppRunnerRef,
|
||||
panicked: Arc<AtomicBool>,
|
||||
) -> Result<(), JsValue> {
|
||||
struct IsDestroyed(pub bool);
|
||||
|
||||
fn paint_if_needed(runner_ref: &AppRunnerRef) -> Result<IsDestroyed, JsValue> {
|
||||
let mut runner_lock = runner_ref.lock();
|
||||
let is_destroyed = runner_lock.is_destroyed.fetch();
|
||||
|
|
|
|||
|
|
@ -8,6 +8,21 @@ use crate::{
|
|||
};
|
||||
use epaint::{mutex::*, stats::*, text::Fonts, TessellationOptions, *};
|
||||
|
||||
/// Information given to the backend about when it is time to repaint the ui.
|
||||
///
|
||||
/// This is given in the callback set by [`Context::set_request_repaint_callback`].
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct RequestRepaintInfo {
|
||||
/// Repaint after this duration. If zero, repaint as soon as possible.
|
||||
pub after: std::time::Duration,
|
||||
|
||||
/// The current frame number.
|
||||
///
|
||||
/// This can be compared to [`Context::frame_nr`] to see if we've already
|
||||
/// triggered the painting of the next frame.
|
||||
pub current_frame_nr: u64,
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
struct WrappedTextureManager(Arc<RwLock<epaint::TextureManager>>);
|
||||
|
|
@ -29,6 +44,94 @@ impl Default for WrappedTextureManager {
|
|||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/// Logic related to repainting the ui.
|
||||
struct Repaint {
|
||||
/// The current frame number.
|
||||
///
|
||||
/// Incremented at the end of each frame.
|
||||
frame_nr: u64,
|
||||
|
||||
/// The duration 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.
|
||||
repaint_after: std::time::Duration,
|
||||
|
||||
/// While positive, keep requesting repaints. Decrement at the end of each frame.
|
||||
repaint_requests: u32,
|
||||
request_repaint_callback: Option<Box<dyn Fn(RequestRepaintInfo) + Send + Sync>>,
|
||||
|
||||
requested_repaint_last_frame: bool,
|
||||
}
|
||||
|
||||
impl Default for Repaint {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
frame_nr: 0,
|
||||
repaint_after: std::time::Duration::from_millis(100),
|
||||
// Start with painting an extra frame to compensate for some widgets
|
||||
// that take two frames before they "settle":
|
||||
repaint_requests: 1,
|
||||
request_repaint_callback: None,
|
||||
requested_repaint_last_frame: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Repaint {
|
||||
fn request_repaint(&mut self) {
|
||||
self.request_repaint_after(std::time::Duration::ZERO);
|
||||
}
|
||||
|
||||
fn request_repaint_after(&mut self, after: std::time::Duration) {
|
||||
if after == std::time::Duration::ZERO {
|
||||
// Do a few extra frames to let things settle.
|
||||
// This is a bit of a hack, and we don't support it for `repaint_after` callbacks yet.
|
||||
self.repaint_requests = 2;
|
||||
}
|
||||
|
||||
// We only re-call the callback if we get a lower duration,
|
||||
// otherwise it's already been covered by the previous callback.
|
||||
if after < self.repaint_after {
|
||||
self.repaint_after = after;
|
||||
|
||||
if let Some(callback) = &self.request_repaint_callback {
|
||||
let info = RequestRepaintInfo {
|
||||
after,
|
||||
current_frame_nr: self.frame_nr,
|
||||
};
|
||||
(callback)(info);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn start_frame(&mut self) {
|
||||
// We are repainting; no need to reschedule a repaint unless the user asks for it again.
|
||||
self.repaint_after = std::time::Duration::MAX;
|
||||
}
|
||||
|
||||
// returns how long to wait until repaint
|
||||
fn end_frame(&mut self) -> std::time::Duration {
|
||||
// if repaint_requests is greater than zero. just set the duration to zero for immediate
|
||||
// repaint. if there's no repaint requests, then we can use the actual repaint_after instead.
|
||||
let repaint_after = if self.repaint_requests > 0 {
|
||||
self.repaint_requests -= 1;
|
||||
std::time::Duration::ZERO
|
||||
} else {
|
||||
self.repaint_after
|
||||
};
|
||||
self.repaint_after = std::time::Duration::MAX;
|
||||
|
||||
self.requested_repaint_last_frame = repaint_after.is_zero();
|
||||
self.frame_nr += 1;
|
||||
|
||||
repaint_after
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
#[derive(Default)]
|
||||
struct ContextImpl {
|
||||
/// `None` until the start of the first frame.
|
||||
|
|
@ -50,18 +153,7 @@ struct ContextImpl {
|
|||
|
||||
paint_stats: PaintStats,
|
||||
|
||||
/// the duration backend will poll for new events, before forcing another egui update
|
||||
/// even if there's no new events.
|
||||
repaint_after: std::time::Duration,
|
||||
|
||||
/// While positive, keep requesting repaints. Decrement at the end of each frame.
|
||||
repaint_requests: u32,
|
||||
request_repaint_callback: Option<Box<dyn Fn() + Send + Sync>>,
|
||||
|
||||
/// used to suppress multiple calls to [`Self::request_repaint_callback`] during the same frame.
|
||||
has_requested_repaint_this_frame: bool,
|
||||
|
||||
requested_repaint_last_frame: bool,
|
||||
repaint: Repaint,
|
||||
|
||||
/// Written to during the frame.
|
||||
layer_rects_this_frame: ahash::HashMap<LayerId, Vec<(Id, Rect)>>,
|
||||
|
|
@ -77,7 +169,7 @@ struct ContextImpl {
|
|||
|
||||
impl ContextImpl {
|
||||
fn begin_frame_mut(&mut self, mut new_raw_input: RawInput) {
|
||||
self.has_requested_repaint_this_frame = false; // allow new calls during the frame
|
||||
self.repaint.start_frame();
|
||||
|
||||
if let Some(new_pixels_per_point) = self.memory.new_pixels_per_point.take() {
|
||||
new_raw_input.pixels_per_point = Some(new_pixels_per_point);
|
||||
|
|
@ -95,7 +187,7 @@ impl ContextImpl {
|
|||
self.memory.begin_frame(&self.input, &new_raw_input);
|
||||
|
||||
self.input = std::mem::take(&mut self.input)
|
||||
.begin_frame(new_raw_input, self.requested_repaint_last_frame);
|
||||
.begin_frame(new_raw_input, self.repaint.requested_repaint_last_frame);
|
||||
|
||||
self.frame_state.begin_frame(&self.input);
|
||||
|
||||
|
|
@ -239,12 +331,7 @@ impl std::cmp::PartialEq for Context {
|
|||
|
||||
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()
|
||||
})))
|
||||
Self(Arc::new(RwLock::new(ContextImpl::default())))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -850,6 +937,15 @@ impl Context {
|
|||
}
|
||||
}
|
||||
|
||||
/// The current frame number.
|
||||
///
|
||||
/// Starts at zero, and is incremented at the end of [`Self::run`] or by [`Self::end_frame`].
|
||||
///
|
||||
/// Between calls to [`Self::run`], this is the frame number of the coming frame.
|
||||
pub fn frame_nr(&self) -> u64 {
|
||||
self.read(|ctx| ctx.repaint.frame_nr)
|
||||
}
|
||||
|
||||
/// 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.
|
||||
|
|
@ -860,19 +956,13 @@ impl Context {
|
|||
/// (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(|ctx| {
|
||||
ctx.repaint_requests = 2;
|
||||
if let Some(callback) = &ctx.request_repaint_callback {
|
||||
if !ctx.has_requested_repaint_this_frame {
|
||||
(callback)();
|
||||
ctx.has_requested_repaint_this_frame = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
self.write(|ctx| ctx.repaint.request_repaint());
|
||||
}
|
||||
|
||||
/// Request repaint after the specified duration elapses in the case of no new input
|
||||
/// events being received.
|
||||
/// Request repaint after at most the specified duration elapses.
|
||||
///
|
||||
/// The backend can chose to repaint sooner, for instance if some other code called
|
||||
/// this method with a lower duration, or if new events arrived.
|
||||
///
|
||||
/// The function can be multiple times, but only the *smallest* duration will be considered.
|
||||
/// So, if the function is called two times with `1 second` and `2 seconds`, egui will repaint
|
||||
|
|
@ -900,7 +990,7 @@ impl Context {
|
|||
/// during app idle time where we are not receiving any new input events.
|
||||
pub fn request_repaint_after(&self, duration: std::time::Duration) {
|
||||
// Maybe we can check if duration is ZERO, and call self.request_repaint()?
|
||||
self.write(|ctx| ctx.repaint_after = ctx.repaint_after.min(duration));
|
||||
self.write(|ctx| ctx.repaint.request_repaint_after(duration));
|
||||
}
|
||||
|
||||
/// For integrations: this callback will be called when an egui user calls [`Self::request_repaint`].
|
||||
|
|
@ -908,9 +998,12 @@ impl Context {
|
|||
/// This lets you wake up a sleeping UI thread.
|
||||
///
|
||||
/// Note that only one callback can be set. Any new call overrides the previous callback.
|
||||
pub fn set_request_repaint_callback(&self, callback: impl Fn() + Send + Sync + 'static) {
|
||||
pub fn set_request_repaint_callback(
|
||||
&self,
|
||||
callback: impl Fn(RequestRepaintInfo) + Send + Sync + 'static,
|
||||
) {
|
||||
let callback = Box::new(callback);
|
||||
self.write(|ctx| ctx.request_repaint_callback = Some(callback));
|
||||
self.write(|ctx| ctx.repaint.request_repaint_callback = Some(callback));
|
||||
}
|
||||
|
||||
/// Tell `egui` which fonts to use.
|
||||
|
|
@ -1164,28 +1257,7 @@ impl Context {
|
|||
}
|
||||
}
|
||||
|
||||
// if repaint_requests is greater than zero. just set the duration to zero for immediate
|
||||
// repaint. if there's no repaint requests, then we can use the actual repaint_after instead.
|
||||
let repaint_after = self.write(|ctx| {
|
||||
if ctx.repaint_requests > 0 {
|
||||
ctx.repaint_requests -= 1;
|
||||
std::time::Duration::ZERO
|
||||
} else {
|
||||
ctx.repaint_after
|
||||
}
|
||||
});
|
||||
|
||||
self.write(|ctx| {
|
||||
ctx.requested_repaint_last_frame = repaint_after.is_zero();
|
||||
|
||||
ctx.has_requested_repaint_this_frame = false; // allow new calls between frames
|
||||
|
||||
// make sure we reset the repaint_after duration.
|
||||
// otherwise, if repaint_after is low, then any widget setting repaint_after next frame,
|
||||
// will fail to overwrite the previous lower value. and thus, repaints will never
|
||||
// go back to higher values.
|
||||
ctx.repaint_after = std::time::Duration::MAX;
|
||||
});
|
||||
let repaint_after = self.write(|ctx| ctx.repaint.end_frame());
|
||||
let shapes = self.drain_paint_lists();
|
||||
|
||||
FullOutput {
|
||||
|
|
|
|||
|
|
@ -354,7 +354,7 @@ pub mod text {
|
|||
|
||||
pub use {
|
||||
containers::*,
|
||||
context::Context,
|
||||
context::{Context, RequestRepaintInfo},
|
||||
data::{
|
||||
input::*,
|
||||
output::{self, CursorIcon, FullOutput, PlatformOutput, UserAttentionType, WidgetInfo},
|
||||
|
|
|
|||
|
|
@ -63,4 +63,6 @@ env_logger = "0.10"
|
|||
# web:
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
console_error_panic_hook = "0.1.6"
|
||||
wasm-bindgen = "=0.2.84"
|
||||
wasm-bindgen-futures = "0.4"
|
||||
web-sys = "0.3"
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
use egui::Widget;
|
||||
|
||||
/// How often we repaint the demo app by default
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
enum RunMode {
|
||||
|
|
@ -43,6 +41,7 @@ impl Default for RunMode {
|
|||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
#[derive(Default)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
#[cfg_attr(feature = "serde", serde(default))]
|
||||
pub struct BackendPanel {
|
||||
|
|
@ -52,9 +51,6 @@ pub struct BackendPanel {
|
|||
// go back to [`RunMode::Reactive`] mode each time we start
|
||||
run_mode: RunMode,
|
||||
|
||||
#[cfg_attr(feature = "serde", serde(skip))]
|
||||
repaint_after_seconds: f32,
|
||||
|
||||
/// current slider value for current gui scale
|
||||
#[cfg_attr(feature = "serde", serde(skip))]
|
||||
pixels_per_point: Option<f32>,
|
||||
|
|
@ -65,19 +61,6 @@ pub struct BackendPanel {
|
|||
egui_windows: EguiWindows,
|
||||
}
|
||||
|
||||
impl Default for BackendPanel {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
open: false,
|
||||
run_mode: Default::default(),
|
||||
repaint_after_seconds: 1.0,
|
||||
pixels_per_point: None,
|
||||
frame_history: Default::default(),
|
||||
egui_windows: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BackendPanel {
|
||||
pub fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
|
||||
self.frame_history
|
||||
|
|
@ -90,9 +73,6 @@ impl BackendPanel {
|
|||
}
|
||||
RunMode::Reactive => {
|
||||
// let the computer rest for a bit
|
||||
ctx.request_repaint_after(std::time::Duration::from_secs_f32(
|
||||
self.repaint_after_seconds,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -271,17 +251,27 @@ impl BackendPanel {
|
|||
} else {
|
||||
ui.label("Only running UI code when there are animations or input.");
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
ui.spacing_mut().item_spacing.x = 0.0;
|
||||
ui.label("(but at least every ");
|
||||
egui::DragValue::new(&mut self.repaint_after_seconds)
|
||||
.clamp_range(0.1..=10.0)
|
||||
.speed(0.1)
|
||||
.suffix(" s")
|
||||
.ui(ui)
|
||||
.on_hover_text("Repaint this often, even if there is no input.");
|
||||
ui.label(")");
|
||||
});
|
||||
// Add a test for `request_repaint_after`, but only in debug
|
||||
// builds to keep the noise down in the official demo.
|
||||
if cfg!(debug_assertions) {
|
||||
ui.collapsing("More…", |ui| {
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("Frame number:");
|
||||
ui.monospace(ui.ctx().frame_nr().to_string());
|
||||
});
|
||||
if ui
|
||||
.button("Wait 2s, then request repaint after another 3s")
|
||||
.clicked()
|
||||
{
|
||||
log::info!("Waiting 2s before requesting repaint...");
|
||||
let ctx = ui.ctx().clone();
|
||||
call_after_delay(std::time::Duration::from_secs(2), move || {
|
||||
log::info!("Request a repaint in 3s...");
|
||||
ctx.request_repaint_after(std::time::Duration::from_secs(3));
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -394,3 +384,28 @@ impl EguiWindows {
|
|||
});
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn call_after_delay(delay: std::time::Duration, f: impl FnOnce() + Send + 'static) {
|
||||
std::thread::spawn(move || {
|
||||
std::thread::sleep(delay);
|
||||
f();
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
fn call_after_delay(delay: std::time::Duration, f: impl FnOnce() + Send + 'static) {
|
||||
use wasm_bindgen::prelude::*;
|
||||
let window = web_sys::window().unwrap();
|
||||
let closure = Closure::once(f);
|
||||
let delay_ms = delay.as_millis() as _;
|
||||
window
|
||||
.set_timeout_with_callback_and_timeout_and_arguments_0(
|
||||
closure.as_ref().unchecked_ref(),
|
||||
delay_ms,
|
||||
)
|
||||
.unwrap();
|
||||
closure.forget(); // We must forget it, or else the callback is canceled on drop
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,12 +33,6 @@ impl FrameHistory {
|
|||
}
|
||||
|
||||
pub fn ui(&mut self, ui: &mut egui::Ui) {
|
||||
ui.label(format!(
|
||||
"Total frames painted: {}",
|
||||
self.frame_times.total_count()
|
||||
))
|
||||
.on_hover_text("Includes this frame.");
|
||||
|
||||
ui.label(format!(
|
||||
"Mean CPU usage: {:.2} ms / frame",
|
||||
1e3 * self.mean_frame_time()
|
||||
|
|
|
|||
|
|
@ -3,29 +3,41 @@ set -eu
|
|||
script_path=$( cd "$(dirname "${BASH_SOURCE[0]}")" ; pwd -P )
|
||||
cd "$script_path/.."
|
||||
|
||||
./scripts/setup_web.sh
|
||||
|
||||
# This is required to enable the web_sys clipboard API which eframe web uses
|
||||
# https://rustwasm.github.io/wasm-bindgen/api/web_sys/struct.Clipboard.html
|
||||
# https://rustwasm.github.io/docs/wasm-bindgen/web-sys/unstable-apis.html
|
||||
export RUSTFLAGS=--cfg=web_sys_unstable_apis
|
||||
|
||||
CRATE_NAME="egui_demo_app"
|
||||
|
||||
# NOTE: persistence use up about 400kB (10%) of the WASM!
|
||||
FEATURES="glow,http,persistence,web_screen_reader"
|
||||
|
||||
OPEN=false
|
||||
OPTIMIZE=false
|
||||
BUILD=debug
|
||||
BUILD_FLAGS=""
|
||||
|
||||
while test $# -gt 0; do
|
||||
case "$1" in
|
||||
-h|--help)
|
||||
echo "build_demo_web.sh [--optimize] [--open]"
|
||||
echo "build_demo_web.sh [--release] [--open]"
|
||||
echo ""
|
||||
echo " --optimize: Enable optimization step"
|
||||
echo " Runs wasm-opt."
|
||||
echo " NOTE: --optimize also removes debug symbols which are otherwise useful for in-browser profiling."
|
||||
echo " --release: Build with --release, and enable extra optimization step"
|
||||
echo " Runs wasm-opt."
|
||||
echo " NOTE: --release also removes debug symbols which are otherwise useful for in-browser profiling."
|
||||
echo ""
|
||||
echo " --open: Open the result in a browser"
|
||||
exit 0
|
||||
;;
|
||||
|
||||
-O|--optimize)
|
||||
--release)
|
||||
shift
|
||||
OPTIMIZE=true
|
||||
BUILD="release"
|
||||
BUILD_FLAGS="--release"
|
||||
;;
|
||||
|
||||
--open)
|
||||
|
|
@ -39,22 +51,14 @@ while test $# -gt 0; do
|
|||
esac
|
||||
done
|
||||
|
||||
./scripts/setup_web.sh
|
||||
|
||||
# This is required to enable the web_sys clipboard API which eframe web uses
|
||||
# https://rustwasm.github.io/wasm-bindgen/api/web_sys/struct.Clipboard.html
|
||||
# https://rustwasm.github.io/docs/wasm-bindgen/web-sys/unstable-apis.html
|
||||
export RUSTFLAGS=--cfg=web_sys_unstable_apis
|
||||
|
||||
# Clear output from old stuff:
|
||||
rm -f "docs/${CRATE_NAME}_bg.wasm"
|
||||
|
||||
echo "Building rust…"
|
||||
BUILD=release
|
||||
|
||||
(cd crates/$CRATE_NAME &&
|
||||
cargo build \
|
||||
--release \
|
||||
${BUILD_FLAGS} \
|
||||
--lib \
|
||||
--target wasm32-unknown-unknown \
|
||||
--no-default-features \
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
#!/usr/bin/env bash
|
||||
# This scripts run clippy on the wasm32-unknown-unknown target,
|
||||
# using a special clippy.toml config file which forbids a few more things.
|
||||
|
||||
set -eu
|
||||
script_path=$( cd "$(dirname "${BASH_SOURCE[0]}")" ; pwd -P )
|
||||
cd "$script_path/.."
|
||||
set -x
|
||||
|
||||
# Use scripts/clippy_wasm/clippy.toml
|
||||
export CLIPPY_CONF_DIR="scripts/clippy_wasm"
|
||||
|
||||
cargo cranky --all-features --target wasm32-unknown-unknown --target-dir target_wasm -p egui_demo_app --lib -- --deny warnings
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
# This is used by `scripts/clippy_wasm.sh` so we can forbid some methods that are not available in wasm.
|
||||
#
|
||||
# We cannot forbid all these methods in the main `clippy.toml` because of
|
||||
# https://github.com/rust-lang/rust-clippy/issues/10406
|
||||
|
||||
msrv = "1.65"
|
||||
|
||||
# https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_methods
|
||||
disallowed-methods = [
|
||||
"std::time::Instant::now", # use `instant` crate instead for wasm/web compatibility
|
||||
"std::time::Duration::elapsed", # use `instant` crate instead for wasm/web compatibility
|
||||
"std::time::SystemTime::now", # use `instant` or `time` crates instead for wasm/web compatibility
|
||||
|
||||
# Cannot spawn threads on wasm:
|
||||
"std::thread::spawn",
|
||||
]
|
||||
|
||||
# https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_types
|
||||
disallowed-types = [
|
||||
# Cannot spawn threads on wasm:
|
||||
"std::thread::Builder",
|
||||
]
|
||||
|
||||
# Allow-list of words for markdown in dosctrings https://rust-lang.github.io/rust-clippy/master/index.html#doc_markdown
|
||||
doc-valid-idents = [
|
||||
# You must also update the same list in the root `clippy.toml`!
|
||||
"AccessKit",
|
||||
"..",
|
||||
]
|
||||
Loading…
Reference in New Issue