Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

eframe web: detect and report panics during startup #2992

Merged
Prev Previous commit
Next Next commit
Hide AppRunner
  • Loading branch information
emilk committed May 16, 2023
commit e289c70d4252e9bd152aff2a199325edfa0f69b1
23 changes: 8 additions & 15 deletions crates/eframe/src/web/app_runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ pub struct AppRunner {
pub(crate) input: super::WebInput,
app: Box<dyn epi::App>,
pub(crate) needs_repaint: std::sync::Arc<NeedRepaint>,
pub(crate) is_destroyed: std::sync::Arc<super::IsDestroyed>,
last_save_time: f64,
screen_reader: super::screen_reader::ScreenReader,
pub(crate) text_cursor_pos: Option<egui::Pos2>,
Expand Down Expand Up @@ -105,7 +104,6 @@ impl AppRunner {
input: Default::default(),
app,
needs_repaint,
is_destroyed: Default::default(),
last_save_time: now_sec(),
screen_reader: Default::default(),
text_cursor_pos: None,
Expand Down Expand Up @@ -154,32 +152,26 @@ impl AppRunner {
self.painter.canvas_id()
}

pub fn warm_up(&mut self) -> Result<(), JsValue> {
pub fn warm_up(&mut self) {
if self.app.warm_up_enabled() {
let saved_memory: egui::Memory = self.egui_ctx.memory(|m| m.clone());
self.egui_ctx
.memory_mut(|m| m.set_everything_is_visible(true));
self.logic()?;
self.logic();
self.egui_ctx.memory_mut(|m| *m = saved_memory); // We don't want to remember that windows were huge.
self.egui_ctx.clear_animations();
}
Ok(())
}

pub fn destroy(&mut self) {
if self.is_destroyed.fetch() {
log::warn!("App was destroyed already");
} else {
log::debug!("Destroying");
self.painter.destroy();
self.is_destroyed.set_true();
}
pub fn destroy(mut self) {
log::debug!("Destroying AppRunner");
self.painter.destroy();
}

/// Returns how long to wait until the next repaint.
///
/// Call [`Self::paint`] later to paint
pub fn logic(&mut self) -> Result<(std::time::Duration, Vec<egui::ClippedPrimitive>), JsValue> {
pub fn logic(&mut self) -> (std::time::Duration, Vec<egui::ClippedPrimitive>) {
let frame_start = now_sec();

super::resize_canvas_to_screen_size(self.canvas_id(), self.app.max_size_points());
Expand All @@ -206,7 +198,8 @@ impl AppRunner {
}

self.frame.info.cpu_usage = Some((now_sec() - frame_start) as f32);
Ok((repaint_after, clipped_primitives))

(repaint_after, clipped_primitives)
}

/// Paint the results of the last call to [`Self::logic`].
Expand Down
61 changes: 47 additions & 14 deletions crates/eframe/src/web/app_runner_ref.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,36 +2,71 @@ use std::{cell::RefCell, rc::Rc};

use wasm_bindgen::prelude::*;

use crate::App;
use crate::{epi, App};

use super::{AppRunner, PanicHandler};
use super::{events, AppRunner, PanicHandler};

/// This is how we access the [`AppRunner`].
///
/// This is cheap to clone.
#[derive(Clone)]
pub struct AppRunnerRef {
/// If we ever panic during running, this mutex is poisoned.
/// So before we use it, we need to check `panic_handler`.
runner: Rc<RefCell<AppRunner>>,

/// Have we ever panicked?
panic_handler: PanicHandler,

/// If we ever panic during running, this RefCell is poisoned.
/// So before we use it, we need to check [`Self::panic_handler`].
runner: Rc<RefCell<Option<AppRunner>>>,

/// In case of a panic, unsubscribe these.
/// They have to be in a separate `Rc` so that we don't need to pass them to
/// the panic handler, since they aren't `Send`.
events_to_unsubscribe: Rc<RefCell<Vec<EventToUnsubscribe>>>,
}

impl AppRunnerRef {
pub fn new(panic_handler: PanicHandler, runner: AppRunner) -> Self {
pub fn new(panic_handler: PanicHandler) -> Self {
#[cfg(not(web_sys_unstable_apis))]
log::warn!(
"eframe compiled without RUSTFLAGS='--cfg=web_sys_unstable_apis'. Copying text won't work."
);

Self {
panic_handler,
runner: Rc::new(RefCell::new(runner)),
runner: Rc::new(RefCell::new(None)),
events_to_unsubscribe: Rc::new(RefCell::new(Default::default())),
}
}

pub async fn initialize(
&self,
canvas_id: &str,
web_options: crate::WebOptions,
app_creator: epi::AppCreator,
) -> Result<(), JsValue> {
self.destroy();

let follow_system_theme = web_options.follow_system_theme;

let mut runner = AppRunner::new(canvas_id, web_options, app_creator).await?;
runner.warm_up();
self.runner.replace(Some(runner));

// Install events:
{
events::install_canvas_events(self)?;
events::install_document_events(self)?;
events::install_window_events(self)?;
super::text_agent::install_text_agent(self)?;
if follow_system_theme {
events::install_color_scheme_change_event(self)?;
}
events::paint_and_schedule(self)?;
}

Ok(())
}

fn unsubscribe_from_all_events(&self) {
let events_to_unsubscribe: Vec<_> =
std::mem::take(&mut *self.events_to_unsubscribe.borrow_mut());
Expand All @@ -48,7 +83,8 @@ impl AppRunnerRef {

pub fn destroy(&self) {
self.unsubscribe_from_all_events();
if let Some(mut runner) = self.try_lock() {

if let Some(runner) = self.runner.replace(None) {
runner.destroy();
}
}
Expand All @@ -63,11 +99,8 @@ impl AppRunnerRef {
None
} else {
let lock = self.runner.try_borrow_mut().ok()?;
if lock.is_destroyed.fetch() {
None
} else {
Some(lock)
}
std::cell::RefMut::filter_map(lock, |lock| -> Option<&mut AppRunner> { lock.as_mut() })
.ok()
}
}

Expand Down
2 changes: 1 addition & 1 deletion crates/eframe/src/web/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ pub fn paint_and_schedule(runner_ref: &AppRunnerRef) -> Result<(), JsValue> {
fn paint_if_needed(runner: &mut AppRunner) -> Result<(), JsValue> {
if runner.needs_repaint.when_to_repaint() <= now_sec() {
runner.needs_repaint.clear();
let (repaint_after, clipped_primitives) = runner.logic()?;
let (repaint_after, clipped_primitives) = runner.logic();
runner.paint(&clipped_primitives)?;
runner
.needs_repaint
Expand Down
28 changes: 5 additions & 23 deletions crates/eframe/src/web/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ pub mod storage;
mod text_agent;
mod web_logger;

pub use app_runner::AppRunner;
pub(crate) use app_runner::AppRunner;
pub use app_runner_ref::AppRunnerRef;
pub use panic_handler::{PanicHandler, PanicSummary};
pub use web_logger::WebLogger;
Expand Down Expand Up @@ -87,28 +87,10 @@ pub async fn start_web(
web_options: crate::WebOptions,
app_creator: epi::AppCreator,
) -> Result<AppRunnerRef, JsValue> {
#[cfg(not(web_sys_unstable_apis))]
log::warn!(
"eframe compiled without RUSTFLAGS='--cfg=web_sys_unstable_apis'. Copying text won't work."
);
let follow_system_theme = web_options.follow_system_theme;

let mut runner = AppRunner::new(canvas_id, web_options, app_creator).await?;
runner.warm_up()?;
let runner_ref = AppRunnerRef::new(panic_handler, runner);

// Install events:
{
events::install_canvas_events(&runner_ref)?;
events::install_document_events(&runner_ref)?;
events::install_window_events(&runner_ref)?;
text_agent::install_text_agent(&runner_ref)?;
if follow_system_theme {
events::install_color_scheme_change_event(&runner_ref)?;
}
events::paint_and_schedule(&runner_ref)?;
}

let runner_ref = AppRunnerRef::new(panic_handler);
runner_ref
.initialize(canvas_id, web_options, app_creator)
.await?;
Ok(runner_ref)
}

Expand Down
2 changes: 2 additions & 0 deletions crates/eframe/src/web/panic_handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ use wasm_bindgen::prelude::*;
/// Detects panics, logs them using `console.error`, and stores the panics message and callstack.
///
/// This lets you query `PanicHandler` for the panic message (if any) so you can show it in the HTML.
///
/// Chep to clone (ref-counted).
#[derive(Clone)]
pub struct PanicHandler(Arc<Mutex<PanicHandlerInner>>);

Expand Down