Skip to content

Commit 714e3d1

Browse files
committed
fixes, docs
1 parent c5e0bb3 commit 714e3d1

File tree

2 files changed

+69
-32
lines changed

2 files changed

+69
-32
lines changed

eframe/src/lib.rs

+20-2
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,20 @@ use web::AppRunner;
9090
#[cfg(target_arch = "wasm32")]
9191
pub use web_sys;
9292

93+
#[cfg(target_arch = "wasm32")]
94+
use arc_swap::ArcSwapOption;
95+
96+
#[cfg(target_arch = "wasm32")]
97+
#[allow(unsafe_code)]
98+
unsafe impl Send for AppRunner {}
99+
100+
#[cfg(target_arch = "wasm32")]
101+
#[allow(unsafe_code)]
102+
unsafe impl Sync for AppRunner {}
103+
104+
#[cfg(target_arch = "wasm32")]
105+
static GLOBAL_HANDLE: ArcSwapOption<Mutex<AppRunner>> = ArcSwapOption::const_empty();
106+
93107
/// Install event listeners to register different input events
94108
/// and start running the given app.
95109
///
@@ -98,8 +112,9 @@ pub use web_sys;
98112
/// use wasm_bindgen::prelude::*;
99113
///
100114
/// /// This is the entry-point for all the web-assembly.
101-
/// /// This is called once from the HTML.
115+
/// /// This is called from the HTML.
102116
/// /// It loads the app, installs some callbacks, then returns.
117+
/// /// It creates singleton app-handle that could be stopped calling `stop_web`
103118
/// /// You can add more callbacks like this if you want to call in to your code.
104119
/// #[cfg(target_arch = "wasm32")]
105120
/// #[wasm_bindgen]
@@ -128,10 +143,13 @@ pub fn start_web(canvas_id: &str, web_options: WebOptions, app_creator: AppCreat
128143
Ok(())
129144
}
130145

146+
/// Stop all event listeners, created by start_web.
147+
/// /// `start_web` could be called again after calling this.
148+
/// /// It aims to destroy all Arc's to `AppRunner` to not cause memory leak in start/stop cycless
131149
#[cfg(target_arch = "wasm32")]
132150
pub fn stop_web() -> Result<(), wasm_bindgen::JsValue> {
133151
if let Some(app_runner) = GLOBAL_HANDLE.load_full() {
134-
uu.lock().destroy();
152+
app_runner.lock().destroy()?;
135153

136154
GLOBAL_HANDLE.store(None);
137155

eframe/src/web/backend.rs

+49-30
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,7 @@ use super::{glow_wrapping::WrappedGlowPainter, *};
22

33
use crate::epi;
44

5-
use egui::mutex::{Mutex, MutexGuard};
65
use egui::TexturesDelta;
7-
86
pub use egui::{pos2, Color32};
97

108
// ----------------------------------------------------------------------------
@@ -36,38 +34,24 @@ impl WebInput {
3634

3735
use std::sync::atomic::Ordering::SeqCst;
3836

39-
/// Stores when to do the next repaint.
40-
pub struct NeedRepaint(Mutex<f64>);
37+
pub struct NeedRepaint(std::sync::atomic::AtomicBool);
4138

4239
impl Default for NeedRepaint {
4340
fn default() -> Self {
44-
Self(Mutex::new(f64::NEG_INFINITY)) // start with a repaint
41+
Self(true.into())
4542
}
4643
}
4744

4845
impl NeedRepaint {
49-
/// Returns the time (in [`now_sec`] scale) when
50-
/// we should next repaint.
51-
pub fn when_to_repaint(&self) -> f64 {
52-
*self.0.lock()
53-
}
54-
55-
/// Unschedule repainting.
56-
pub fn clear(&self) {
57-
*self.0.lock() = f64::INFINITY;
46+
pub fn fetch_and_clear(&self) -> bool {
47+
self.0.swap(false, SeqCst)
5848
}
5949

60-
pub fn repaint_after(&self, num_seconds: f64) {
61-
let mut repaint_time = self.0.lock();
62-
*repaint_time = repaint_time.min(now_sec() + num_seconds);
63-
}
64-
65-
pub fn repaint_asap(&self) {
66-
*self.0.lock() = f64::NEG_INFINITY;
50+
pub fn set_true(&self) {
51+
self.0.store(true, SeqCst);
6752
}
6853
}
6954

70-
7155
pub struct IsDestroyed(std::sync::atomic::AtomicBool);
7256

7357
impl Default for IsDestroyed {
@@ -238,7 +222,7 @@ impl AppRunner {
238222
{
239223
let needs_repaint = needs_repaint.clone();
240224
egui_ctx.set_request_repaint_callback(move || {
241-
needs_repaint.repaint_asap();
225+
needs_repaint.0.store(true, SeqCst);
242226
});
243227
}
244228

@@ -297,15 +281,16 @@ impl AppRunner {
297281
Ok(())
298282
}
299283

300-
pub fn destroy(&mut self) {
284+
pub fn destroy(&mut self) -> Result<(), JsValue> {
301285
tracing::debug!("destroying...");
302286

303-
self.events_to_unsubscribe
304-
.drain(..)
305-
.for_each(|x| x.unsubscribe());
287+
for x in self.events_to_unsubscribe.drain(..) {
288+
x.unsubscribe()?;
289+
}
306290

307291
self.painter.destroy();
308292
self.is_destroyed.set_true();
293+
Ok(())
309294
}
310295

311296
/// Returns how long to wait until the next repaint.
@@ -408,11 +393,48 @@ impl AppRunner {
408393

409394
pub type AppRunnerRef = Arc<Mutex<AppRunner>>;
410395

396+
pub struct TargetEvent {
397+
target: EventTarget,
398+
event_name: String,
399+
closure: Closure<dyn FnMut(web_sys::Event)>,
400+
}
401+
402+
pub struct RepetitativeHandle {
403+
pub handle: i32,
404+
pub closure: Closure<dyn FnMut()>,
405+
}
406+
407+
pub enum EventToUnsubscribe {
408+
TargetEvent(TargetEvent),
409+
RepititativeHandle(RepetitativeHandle),
410+
}
411+
412+
impl EventToUnsubscribe {
413+
pub fn unsubscribe(self) -> Result<(), JsValue> {
414+
use wasm_bindgen::JsCast;
415+
416+
match self {
417+
EventToUnsubscribe::TargetEvent(handle) => {
418+
handle.target.remove_event_listener_with_callback(
419+
handle.event_name.as_str(),
420+
handle.closure.as_ref().unchecked_ref(),
421+
)?;
422+
Ok(())
423+
}
424+
EventToUnsubscribe::RepititativeHandle(handle) => {
425+
let window = web_sys::window().unwrap();
426+
window.clear_interval_with_handle(handle.handle);
427+
Ok(())
428+
}
429+
}
430+
}
431+
}
411432
pub struct AppRunnerContainer {
412433
pub runner: AppRunnerRef,
413434
/// Set to `true` if there is a panic.
414435
/// Used to ignore callbacks after a panic.
415436
pub panicked: Arc<AtomicBool>,
437+
pub events: Vec<EventToUnsubscribe>,
416438
}
417439

418440
impl AppRunnerContainer {
@@ -457,9 +479,6 @@ impl AppRunnerContainer {
457479

458480
self.events.push(EventToUnsubscribe::TargetEvent(handle));
459481

460-
// Bypass closure drop so that event handler can call the closure
461-
// closure.forget();
462-
463482
Ok(())
464483
}
465484
}

0 commit comments

Comments
 (0)