emilk / egui

egui: an easy-to-use immediate mode GUI in Rust that runs on both web and native
https://www.egui.rs/
Apache License 2.0
22.31k stars 1.61k forks source link

`request_repaint` panics when used from a Web Worker #4368

Open zdimension opened 6 months ago

zdimension commented 6 months ago

Describe the bug

Using request_repaint (or any of its variants) from a Web Worker panics. The panic info points here:

https://github.com/emilk/egui/blob/a8501c963dba0f14a8feff28ed91ff245956662c/crates/eframe/src/web/mod.rs#L60-L67

It seems like eframe ends up trying to get the time, but the now_sec function uses the JS Window object which isn't available from a Web Worker.

Right now, my workaround is to force my app to run at 60fps continuous during the loading time, instead of repainting when needed.

Screenshots

image

Desktop (please complete the following information):

emilk commented 6 months ago

Interesting!

The fix could be for NeedRepaint::repaint_after to try to get the current time and on failure set repaint_time to -INF to just force a repaint asap. ctx.request_repaint_after(…) would not work properly from a web-worker though. Not sure how to handle that case.

zdimension commented 6 months ago

It's possible to get the time though, chrono::Local::now() works from the web worker. But eframe is using the Performance api which isn't exposed by js-sys for workers apparently (though the object is useable from workers). Either we could write some kind of wrapper for that that calls the Performance api without using the window object, or when called from a web worker request_repaint could use the low resolution time.

white-axe commented 6 months ago

web-sys exposes the Performance API for workers as web_sys::WorkerGlobalScope::performance. You just need to be using web-sys 0.3.65 or later and have the Performance and WorkerGlobalScope features enabled.

zdimension commented 6 months ago

web-sys exposes the Performance API for workers as web_sys::WorkerGlobalScope::performance. You just need to be using web-sys 0.3.65 or later and have the Performance and WorkerGlobalScope features enabled.

Thanks, that was it! Performance was already listed by eframe but not WorkerGlobalScope, adding it fixed the problem. I think mentioning that in the docs somewhere (for other people who'd like to use egui along with Web Workers) could be good

emilk commented 6 months ago

Thanks for describing the fix! I'll re-open this as a reminder to add docs, or add WorkerGlobalScope to eframe

BGR360 commented 2 months ago

Hi @zdimension, I'm curious to know more about the original code you were writing when you encountered this issue. Were you running an egui application inside of a web worker? Or was your egui application running on the main browser thread and you were somehow sharing an egui::Context between the main thread and the web worker? If it's the latter, how did you do that?

zdimension commented 2 months ago

Hi @zdimension, I'm curious to know more about the original code you were writing when you encountered this issue. Were you running an egui application inside of a web worker? Or was your egui application running on the main browser thread and you were somehow sharing an egui::Context between the main thread and the web worker? If it's the latter, how did you do that?

Hi, here is the commit where I fixed my issue, you can see a bit how it works: https://github.com/zdimension/graphrust/commit/43913282e5367339857b4c2f4df3e8becb3a3c65

My project is an app that runs egui on the main thread, and offloads data processing to threads (or, on the Web, workers). The workers regularly report their progress to the UI (as percentages or status messages).

Once cloned, an egui Context can be moved to another thread.

My current design uses channels which is not perfect but it's simple and fast enough.