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
|
- run: ./scripts/wasm_bindgen_check.sh --skip-setup
|
||||||
|
|
||||||
- name: Cranky wasm32
|
- name: Cranky wasm32
|
||||||
uses: actions-rs/cargo@v1
|
run: ./scripts/clippy_wasm.sh
|
||||||
with:
|
|
||||||
command: cranky
|
|
||||||
args: --target wasm32-unknown-unknown --all-features -p egui_demo_app --lib -- -D warnings
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1272,7 +1272,9 @@ dependencies = [
|
||||||
"log",
|
"log",
|
||||||
"poll-promise",
|
"poll-promise",
|
||||||
"serde",
|
"serde",
|
||||||
|
"wasm-bindgen",
|
||||||
"wasm-bindgen-futures",
|
"wasm-bindgen-futures",
|
||||||
|
"web-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[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`].
|
//! 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.
|
//! 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::{
|
use winit::event_loop::{
|
||||||
ControlFlow, EventLoop, EventLoopBuilder, EventLoopProxy, EventLoopWindowTarget,
|
ControlFlow, EventLoop, EventLoopBuilder, EventLoopProxy, EventLoopWindowTarget,
|
||||||
|
|
@ -19,7 +19,12 @@ use super::epi_integration::{self, EpiIntegration};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum UserEvent {
|
pub enum UserEvent {
|
||||||
RequestRepaint,
|
RequestRepaint {
|
||||||
|
when: Instant,
|
||||||
|
/// What the frame number was when the repaint was _requested_.
|
||||||
|
frame_nr: u64,
|
||||||
|
},
|
||||||
|
|
||||||
#[cfg(feature = "accesskit")]
|
#[cfg(feature = "accesskit")]
|
||||||
AccessKitActionRequest(accesskit_winit::ActionRequestEvent),
|
AccessKitActionRequest(accesskit_winit::ActionRequestEvent),
|
||||||
}
|
}
|
||||||
|
|
@ -58,6 +63,9 @@ enum EventResult {
|
||||||
}
|
}
|
||||||
|
|
||||||
trait WinitApp {
|
trait WinitApp {
|
||||||
|
/// The current frame number, as reported by egui.
|
||||||
|
fn frame_nr(&self) -> u64;
|
||||||
|
|
||||||
fn is_focused(&self) -> bool;
|
fn is_focused(&self) -> bool;
|
||||||
|
|
||||||
fn integration(&self) -> Option<&EpiIntegration>;
|
fn integration(&self) -> Option<&EpiIntegration>;
|
||||||
|
|
@ -66,7 +74,7 @@ trait WinitApp {
|
||||||
|
|
||||||
fn save_and_destroy(&mut self);
|
fn save_and_destroy(&mut self);
|
||||||
|
|
||||||
fn paint(&mut self) -> EventResult;
|
fn run_ui_and_paint(&mut self) -> EventResult;
|
||||||
|
|
||||||
fn on_event(
|
fn on_event(
|
||||||
&mut self,
|
&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/987
|
||||||
// See: https://github.com/rust-windowing/winit/issues/1619
|
// See: https://github.com/rust-windowing/winit/issues/1619
|
||||||
winit::event::Event::RedrawEventsCleared if cfg!(windows) => {
|
winit::event::Event::RedrawEventsCleared if cfg!(windows) => {
|
||||||
next_repaint_time = Instant::now() + Duration::from_secs(1_000_000_000);
|
next_repaint_time = extremely_far_future();
|
||||||
winit_app.paint()
|
winit_app.run_ui_and_paint()
|
||||||
}
|
}
|
||||||
winit::event::Event::RedrawRequested(_) if !cfg!(windows) => {
|
winit::event::Event::RedrawRequested(_) if !cfg!(windows) => {
|
||||||
next_repaint_time = Instant::now() + Duration::from_secs(1_000_000_000);
|
next_repaint_time = extremely_far_future();
|
||||||
winit_app.paint()
|
winit_app.run_ui_and_paint()
|
||||||
}
|
}
|
||||||
|
|
||||||
winit::event::Event::UserEvent(UserEvent::RequestRepaint)
|
winit::event::Event::UserEvent(UserEvent::RequestRepaint { when, frame_nr }) => {
|
||||||
| winit::event::Event::NewEvents(winit::event::StartCause::ResumeTimeReached {
|
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, .. }
|
winit::event::Event::WindowEvent { window_id, .. }
|
||||||
if winit_app.window().is_none()
|
if winit_app.window().is_none()
|
||||||
|
|
@ -174,8 +194,8 @@ fn run_and_return(
|
||||||
log::trace!("Repaint caused by winit::Event: {:?}", event);
|
log::trace!("Repaint caused by winit::Event: {:?}", event);
|
||||||
if cfg!(windows) {
|
if cfg!(windows) {
|
||||||
// Fix flickering on Windows, see https://github.com/emilk/egui/pull/2280
|
// Fix flickering on Windows, see https://github.com/emilk/egui/pull/2280
|
||||||
next_repaint_time = Instant::now() + Duration::from_secs(1_000_000_000);
|
next_repaint_time = extremely_far_future();
|
||||||
winit_app.paint();
|
winit_app.run_ui_and_paint();
|
||||||
} else {
|
} else {
|
||||||
// Fix for https://github.com/emilk/egui/issues/2425
|
// Fix for https://github.com/emilk/egui/issues/2425
|
||||||
next_repaint_time = Instant::now();
|
next_repaint_time = Instant::now();
|
||||||
|
|
@ -196,18 +216,20 @@ fn run_and_return(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
*control_flow = match next_repaint_time.checked_duration_since(Instant::now()) {
|
*control_flow = if next_repaint_time <= Instant::now() {
|
||||||
None => {
|
if let Some(window) = winit_app.window() {
|
||||||
if let Some(window) = winit_app.window() {
|
log::trace!("request_redraw");
|
||||||
window.request_redraw();
|
window.request_redraw();
|
||||||
}
|
|
||||||
next_repaint_time = Instant::now() + Duration::from_secs(1_000_000_000);
|
|
||||||
ControlFlow::Poll
|
|
||||||
}
|
}
|
||||||
Some(time_until_next_repaint) => {
|
next_repaint_time = extremely_far_future();
|
||||||
ControlFlow::WaitUntil(Instant::now() + time_until_next_repaint)
|
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");
|
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/987
|
||||||
// See: https://github.com/rust-windowing/winit/issues/1619
|
// See: https://github.com/rust-windowing/winit/issues/1619
|
||||||
winit::event::Event::RedrawEventsCleared if cfg!(windows) => {
|
winit::event::Event::RedrawEventsCleared if cfg!(windows) => {
|
||||||
next_repaint_time = Instant::now() + Duration::from_secs(1_000_000_000);
|
next_repaint_time = extremely_far_future();
|
||||||
winit_app.paint()
|
winit_app.run_ui_and_paint()
|
||||||
}
|
}
|
||||||
winit::event::Event::RedrawRequested(_) if !cfg!(windows) => {
|
winit::event::Event::RedrawRequested(_) if !cfg!(windows) => {
|
||||||
next_repaint_time = Instant::now() + Duration::from_secs(1_000_000_000);
|
next_repaint_time = extremely_far_future();
|
||||||
winit_app.paint()
|
winit_app.run_ui_and_paint()
|
||||||
}
|
}
|
||||||
|
|
||||||
winit::event::Event::UserEvent(UserEvent::RequestRepaint)
|
winit::event::Event::UserEvent(UserEvent::RequestRepaint { when, frame_nr }) => {
|
||||||
| winit::event::Event::NewEvents(winit::event::StartCause::ResumeTimeReached {
|
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) {
|
event => match winit_app.on_event(event_loop, &event) {
|
||||||
Ok(event_result) => event_result,
|
Ok(event_result) => event_result,
|
||||||
|
|
@ -265,8 +294,8 @@ fn run_and_exit(event_loop: EventLoop<UserEvent>, mut winit_app: impl WinitApp +
|
||||||
EventResult::RepaintNow => {
|
EventResult::RepaintNow => {
|
||||||
if cfg!(windows) {
|
if cfg!(windows) {
|
||||||
// Fix flickering on Windows, see https://github.com/emilk/egui/pull/2280
|
// Fix flickering on Windows, see https://github.com/emilk/egui/pull/2280
|
||||||
next_repaint_time = Instant::now() + Duration::from_secs(1_000_000_000);
|
next_repaint_time = extremely_far_future();
|
||||||
winit_app.paint();
|
winit_app.run_ui_and_paint();
|
||||||
} else {
|
} else {
|
||||||
// Fix for https://github.com/emilk/egui/issues/2425
|
// Fix for https://github.com/emilk/egui/issues/2425
|
||||||
next_repaint_time = Instant::now();
|
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()) {
|
*control_flow = if next_repaint_time <= Instant::now() {
|
||||||
None => {
|
if let Some(window) = winit_app.window() {
|
||||||
if let Some(window) = winit_app.window() {
|
window.request_redraw();
|
||||||
window.request_redraw();
|
|
||||||
}
|
|
||||||
ControlFlow::Poll
|
|
||||||
}
|
}
|
||||||
Some(time_until_next_repaint) => {
|
next_repaint_time = extremely_far_future();
|
||||||
ControlFlow::WaitUntil(Instant::now() + time_until_next_repaint)
|
ControlFlow::Poll
|
||||||
}
|
} else {
|
||||||
}
|
ControlFlow::WaitUntil(next_repaint_time)
|
||||||
|
};
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -601,8 +628,6 @@ mod glow_integration {
|
||||||
// suspends and resumes.
|
// suspends and resumes.
|
||||||
app_creator: Option<epi::AppCreator>,
|
app_creator: Option<epi::AppCreator>,
|
||||||
is_focused: bool,
|
is_focused: bool,
|
||||||
|
|
||||||
frame_nr: u64,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GlowWinitApp {
|
impl GlowWinitApp {
|
||||||
|
|
@ -619,7 +644,6 @@ mod glow_integration {
|
||||||
running: None,
|
running: None,
|
||||||
app_creator: Some(app_creator),
|
app_creator: Some(app_creator),
|
||||||
is_focused: true,
|
is_focused: true,
|
||||||
frame_nr: 0,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -698,12 +722,17 @@ mod glow_integration {
|
||||||
|
|
||||||
{
|
{
|
||||||
let event_loop_proxy = self.repaint_proxy.clone();
|
let event_loop_proxy = self.repaint_proxy.clone();
|
||||||
integration.egui_ctx.set_request_repaint_callback(move || {
|
integration
|
||||||
event_loop_proxy
|
.egui_ctx
|
||||||
.lock()
|
.set_request_repaint_callback(move |info| {
|
||||||
.send_event(UserEvent::RequestRepaint)
|
log::trace!("request_repaint_callback: {info:?}");
|
||||||
.ok();
|
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)
|
let app_creator = std::mem::take(&mut self.app_creator)
|
||||||
|
|
@ -734,6 +763,12 @@ mod glow_integration {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WinitApp for GlowWinitApp {
|
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 {
|
fn is_focused(&self) -> bool {
|
||||||
self.is_focused
|
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 {
|
if let Some(running) = &mut self.running {
|
||||||
#[cfg(feature = "puffin")]
|
#[cfg(feature = "puffin")]
|
||||||
puffin::GlobalProfiler::lock().new_frame();
|
puffin::GlobalProfiler::lock().new_frame();
|
||||||
|
|
@ -820,7 +855,7 @@ mod glow_integration {
|
||||||
|
|
||||||
#[cfg(feature = "__screenshot")]
|
#[cfg(feature = "__screenshot")]
|
||||||
// give it time to settle:
|
// 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") {
|
if let Ok(path) = std::env::var("EFRAME_SCREENSHOT_TO") {
|
||||||
assert!(
|
assert!(
|
||||||
path.ends_with(".png"),
|
path.ends_with(".png"),
|
||||||
|
|
@ -871,8 +906,6 @@ mod glow_integration {
|
||||||
std::thread::sleep(std::time::Duration::from_millis(10));
|
std::thread::sleep(std::time::Duration::from_millis(10));
|
||||||
}
|
}
|
||||||
|
|
||||||
self.frame_nr += 1;
|
|
||||||
|
|
||||||
control_flow
|
control_flow
|
||||||
} else {
|
} else {
|
||||||
EventResult::Wait
|
EventResult::Wait
|
||||||
|
|
@ -1150,13 +1183,18 @@ mod wgpu_integration {
|
||||||
|
|
||||||
{
|
{
|
||||||
let event_loop_proxy = self.repaint_proxy.clone();
|
let event_loop_proxy = self.repaint_proxy.clone();
|
||||||
integration.egui_ctx.set_request_repaint_callback(move || {
|
integration
|
||||||
event_loop_proxy
|
.egui_ctx
|
||||||
.lock()
|
.set_request_repaint_callback(move |info| {
|
||||||
.unwrap()
|
log::trace!("request_repaint_callback: {info:?}");
|
||||||
.send_event(UserEvent::RequestRepaint)
|
let when = Instant::now() + info.after;
|
||||||
.ok();
|
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)
|
let app_creator = std::mem::take(&mut self.app_creator)
|
||||||
|
|
@ -1186,6 +1224,12 @@ mod wgpu_integration {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WinitApp for WgpuWinitApp {
|
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 {
|
fn is_focused(&self) -> bool {
|
||||||
self.is_focused
|
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) {
|
if let (Some(running), Some(window)) = (&mut self.running, &self.window) {
|
||||||
#[cfg(feature = "puffin")]
|
#[cfg(feature = "puffin")]
|
||||||
puffin::GlobalProfiler::lock().new_frame();
|
puffin::GlobalProfiler::lock().new_frame();
|
||||||
|
|
@ -1433,12 +1477,11 @@ mod wgpu_integration {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
#[cfg(feature = "wgpu")]
|
#[cfg(feature = "wgpu")]
|
||||||
pub use wgpu_integration::run_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> {
|
fn system_theme(window: &winit::window::Window, options: &NativeOptions) -> Option<crate::Theme> {
|
||||||
if options.follow_system_theme {
|
if options.follow_system_theme {
|
||||||
window
|
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 extremely_far_future() -> std::time::Instant {
|
||||||
fn system_theme(_window: &winit::window::Window, _options: &NativeOptions) -> Option<crate::Theme> {
|
std::time::Instant::now() + std::time::Duration::from_secs(10_000_000_000)
|
||||||
None
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -259,8 +259,8 @@ impl AppRunner {
|
||||||
let needs_repaint: std::sync::Arc<NeedRepaint> = Default::default();
|
let needs_repaint: std::sync::Arc<NeedRepaint> = Default::default();
|
||||||
{
|
{
|
||||||
let needs_repaint = needs_repaint.clone();
|
let needs_repaint = needs_repaint.clone();
|
||||||
egui_ctx.set_request_repaint_callback(move || {
|
egui_ctx.set_request_repaint_callback(move |info| {
|
||||||
needs_repaint.repaint_asap();
|
needs_repaint.repaint_after(info.after.as_secs_f64());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,12 +4,15 @@ use egui::Key;
|
||||||
|
|
||||||
use super::*;
|
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(
|
pub fn paint_and_schedule(
|
||||||
runner_ref: &AppRunnerRef,
|
runner_ref: &AppRunnerRef,
|
||||||
panicked: Arc<AtomicBool>,
|
panicked: Arc<AtomicBool>,
|
||||||
) -> Result<(), JsValue> {
|
) -> Result<(), JsValue> {
|
||||||
|
struct IsDestroyed(pub bool);
|
||||||
|
|
||||||
fn paint_if_needed(runner_ref: &AppRunnerRef) -> Result<IsDestroyed, JsValue> {
|
fn paint_if_needed(runner_ref: &AppRunnerRef) -> Result<IsDestroyed, JsValue> {
|
||||||
let mut runner_lock = runner_ref.lock();
|
let mut runner_lock = runner_ref.lock();
|
||||||
let is_destroyed = runner_lock.is_destroyed.fetch();
|
let is_destroyed = runner_lock.is_destroyed.fetch();
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,21 @@ use crate::{
|
||||||
};
|
};
|
||||||
use epaint::{mutex::*, stats::*, text::Fonts, TessellationOptions, *};
|
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>>);
|
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)]
|
#[derive(Default)]
|
||||||
struct ContextImpl {
|
struct ContextImpl {
|
||||||
/// `None` until the start of the first frame.
|
/// `None` until the start of the first frame.
|
||||||
|
|
@ -50,18 +153,7 @@ struct ContextImpl {
|
||||||
|
|
||||||
paint_stats: PaintStats,
|
paint_stats: PaintStats,
|
||||||
|
|
||||||
/// the duration backend will poll for new events, before forcing another egui update
|
repaint: Repaint,
|
||||||
/// 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,
|
|
||||||
|
|
||||||
/// Written to during the frame.
|
/// Written to during the frame.
|
||||||
layer_rects_this_frame: ahash::HashMap<LayerId, Vec<(Id, Rect)>>,
|
layer_rects_this_frame: ahash::HashMap<LayerId, Vec<(Id, Rect)>>,
|
||||||
|
|
@ -77,7 +169,7 @@ struct ContextImpl {
|
||||||
|
|
||||||
impl ContextImpl {
|
impl ContextImpl {
|
||||||
fn begin_frame_mut(&mut self, mut new_raw_input: RawInput) {
|
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() {
|
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);
|
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.memory.begin_frame(&self.input, &new_raw_input);
|
||||||
|
|
||||||
self.input = std::mem::take(&mut self.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);
|
self.frame_state.begin_frame(&self.input);
|
||||||
|
|
||||||
|
|
@ -239,12 +331,7 @@ impl std::cmp::PartialEq for Context {
|
||||||
|
|
||||||
impl Default for Context {
|
impl Default for Context {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self(Arc::new(RwLock::new(ContextImpl {
|
Self(Arc::new(RwLock::new(ContextImpl::default())))
|
||||||
// Start with painting an extra frame to compensate for some widgets
|
|
||||||
// that take two frames before they "settle":
|
|
||||||
repaint_requests: 1,
|
|
||||||
..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.
|
/// 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.
|
/// 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`).
|
/// (this will work on `eframe`).
|
||||||
pub fn request_repaint(&self) {
|
pub fn request_repaint(&self) {
|
||||||
// request two frames of repaint, just to cover some corner cases (frame delays):
|
// request two frames of repaint, just to cover some corner cases (frame delays):
|
||||||
self.write(|ctx| {
|
self.write(|ctx| ctx.repaint.request_repaint());
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Request repaint after the specified duration elapses in the case of no new input
|
/// Request repaint after at most the specified duration elapses.
|
||||||
/// events being received.
|
///
|
||||||
|
/// 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.
|
/// 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
|
/// 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.
|
/// during app idle time where we are not receiving any new input events.
|
||||||
pub fn request_repaint_after(&self, duration: std::time::Duration) {
|
pub fn request_repaint_after(&self, duration: std::time::Duration) {
|
||||||
// Maybe we can check if duration is ZERO, and call self.request_repaint()?
|
// 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`].
|
/// 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.
|
/// This lets you wake up a sleeping UI thread.
|
||||||
///
|
///
|
||||||
/// Note that only one callback can be set. Any new call overrides the previous callback.
|
/// 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);
|
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.
|
/// 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
|
let repaint_after = self.write(|ctx| ctx.repaint.end_frame());
|
||||||
// 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 shapes = self.drain_paint_lists();
|
let shapes = self.drain_paint_lists();
|
||||||
|
|
||||||
FullOutput {
|
FullOutput {
|
||||||
|
|
|
||||||
|
|
@ -354,7 +354,7 @@ pub mod text {
|
||||||
|
|
||||||
pub use {
|
pub use {
|
||||||
containers::*,
|
containers::*,
|
||||||
context::Context,
|
context::{Context, RequestRepaintInfo},
|
||||||
data::{
|
data::{
|
||||||
input::*,
|
input::*,
|
||||||
output::{self, CursorIcon, FullOutput, PlatformOutput, UserAttentionType, WidgetInfo},
|
output::{self, CursorIcon, FullOutput, PlatformOutput, UserAttentionType, WidgetInfo},
|
||||||
|
|
|
||||||
|
|
@ -63,4 +63,6 @@ env_logger = "0.10"
|
||||||
# web:
|
# web:
|
||||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||||
console_error_panic_hook = "0.1.6"
|
console_error_panic_hook = "0.1.6"
|
||||||
|
wasm-bindgen = "=0.2.84"
|
||||||
wasm-bindgen-futures = "0.4"
|
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
|
/// How often we repaint the demo app by default
|
||||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
enum RunMode {
|
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", derive(serde::Deserialize, serde::Serialize))]
|
||||||
#[cfg_attr(feature = "serde", serde(default))]
|
#[cfg_attr(feature = "serde", serde(default))]
|
||||||
pub struct BackendPanel {
|
pub struct BackendPanel {
|
||||||
|
|
@ -52,9 +51,6 @@ pub struct BackendPanel {
|
||||||
// go back to [`RunMode::Reactive`] mode each time we start
|
// go back to [`RunMode::Reactive`] mode each time we start
|
||||||
run_mode: RunMode,
|
run_mode: RunMode,
|
||||||
|
|
||||||
#[cfg_attr(feature = "serde", serde(skip))]
|
|
||||||
repaint_after_seconds: f32,
|
|
||||||
|
|
||||||
/// current slider value for current gui scale
|
/// current slider value for current gui scale
|
||||||
#[cfg_attr(feature = "serde", serde(skip))]
|
#[cfg_attr(feature = "serde", serde(skip))]
|
||||||
pixels_per_point: Option<f32>,
|
pixels_per_point: Option<f32>,
|
||||||
|
|
@ -65,19 +61,6 @@ pub struct BackendPanel {
|
||||||
egui_windows: EguiWindows,
|
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 {
|
impl BackendPanel {
|
||||||
pub fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
|
pub fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
|
||||||
self.frame_history
|
self.frame_history
|
||||||
|
|
@ -90,9 +73,6 @@ impl BackendPanel {
|
||||||
}
|
}
|
||||||
RunMode::Reactive => {
|
RunMode::Reactive => {
|
||||||
// let the computer rest for a bit
|
// 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 {
|
} else {
|
||||||
ui.label("Only running UI code when there are animations or input.");
|
ui.label("Only running UI code when there are animations or input.");
|
||||||
|
|
||||||
ui.horizontal(|ui| {
|
// Add a test for `request_repaint_after`, but only in debug
|
||||||
ui.spacing_mut().item_spacing.x = 0.0;
|
// builds to keep the noise down in the official demo.
|
||||||
ui.label("(but at least every ");
|
if cfg!(debug_assertions) {
|
||||||
egui::DragValue::new(&mut self.repaint_after_seconds)
|
ui.collapsing("More…", |ui| {
|
||||||
.clamp_range(0.1..=10.0)
|
ui.horizontal(|ui| {
|
||||||
.speed(0.1)
|
ui.label("Frame number:");
|
||||||
.suffix(" s")
|
ui.monospace(ui.ctx().frame_nr().to_string());
|
||||||
.ui(ui)
|
});
|
||||||
.on_hover_text("Repaint this often, even if there is no input.");
|
if ui
|
||||||
ui.label(")");
|
.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) {
|
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!(
|
ui.label(format!(
|
||||||
"Mean CPU usage: {:.2} ms / frame",
|
"Mean CPU usage: {:.2} ms / frame",
|
||||||
1e3 * self.mean_frame_time()
|
1e3 * self.mean_frame_time()
|
||||||
|
|
|
||||||
|
|
@ -3,29 +3,41 @@ set -eu
|
||||||
script_path=$( cd "$(dirname "${BASH_SOURCE[0]}")" ; pwd -P )
|
script_path=$( cd "$(dirname "${BASH_SOURCE[0]}")" ; pwd -P )
|
||||||
cd "$script_path/.."
|
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"
|
CRATE_NAME="egui_demo_app"
|
||||||
|
|
||||||
# NOTE: persistence use up about 400kB (10%) of the WASM!
|
# NOTE: persistence use up about 400kB (10%) of the WASM!
|
||||||
FEATURES="glow,http,persistence,web_screen_reader"
|
FEATURES="glow,http,persistence,web_screen_reader"
|
||||||
|
|
||||||
OPEN=false
|
OPEN=false
|
||||||
OPTIMIZE=false
|
OPTIMIZE=false
|
||||||
|
BUILD=debug
|
||||||
|
BUILD_FLAGS=""
|
||||||
|
|
||||||
while test $# -gt 0; do
|
while test $# -gt 0; do
|
||||||
case "$1" in
|
case "$1" in
|
||||||
-h|--help)
|
-h|--help)
|
||||||
echo "build_demo_web.sh [--optimize] [--open]"
|
echo "build_demo_web.sh [--release] [--open]"
|
||||||
echo ""
|
echo ""
|
||||||
echo " --optimize: Enable optimization step"
|
echo " --release: Build with --release, and enable extra optimization step"
|
||||||
echo " Runs wasm-opt."
|
echo " Runs wasm-opt."
|
||||||
echo " NOTE: --optimize also removes debug symbols which are otherwise useful for in-browser profiling."
|
echo " NOTE: --release also removes debug symbols which are otherwise useful for in-browser profiling."
|
||||||
echo ""
|
echo ""
|
||||||
echo " --open: Open the result in a browser"
|
echo " --open: Open the result in a browser"
|
||||||
exit 0
|
exit 0
|
||||||
;;
|
;;
|
||||||
|
|
||||||
-O|--optimize)
|
--release)
|
||||||
shift
|
shift
|
||||||
OPTIMIZE=true
|
OPTIMIZE=true
|
||||||
|
BUILD="release"
|
||||||
|
BUILD_FLAGS="--release"
|
||||||
;;
|
;;
|
||||||
|
|
||||||
--open)
|
--open)
|
||||||
|
|
@ -39,22 +51,14 @@ while test $# -gt 0; do
|
||||||
esac
|
esac
|
||||||
done
|
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:
|
# Clear output from old stuff:
|
||||||
rm -f "docs/${CRATE_NAME}_bg.wasm"
|
rm -f "docs/${CRATE_NAME}_bg.wasm"
|
||||||
|
|
||||||
echo "Building rust…"
|
echo "Building rust…"
|
||||||
BUILD=release
|
|
||||||
|
|
||||||
(cd crates/$CRATE_NAME &&
|
(cd crates/$CRATE_NAME &&
|
||||||
cargo build \
|
cargo build \
|
||||||
--release \
|
${BUILD_FLAGS} \
|
||||||
--lib \
|
--lib \
|
||||||
--target wasm32-unknown-unknown \
|
--target wasm32-unknown-unknown \
|
||||||
--no-default-features \
|
--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