Skip to main content

ranim/cmd/preview/
hot_reload.rs

1//! Bridge patch notifications to egui, including when playback is paused.
2
3use std::sync::{
4    Arc, Once,
5    atomic::{AtomicBool, Ordering},
6};
7
8use eframe::egui;
9
10struct Notification {
11    pending: AtomicBool,
12    ctx: egui::Context,
13}
14
15/// The registered handler only holds a weak reference: closing the preview
16/// releases its egui context even though Subsecond retains handlers globally.
17pub(super) struct HotReload(Arc<Notification>);
18
19impl HotReload {
20    pub(super) fn new(ctx: egui::Context) -> Self {
21        let notification = Arc::new(Notification {
22            pending: AtomicBool::new(false),
23            ctx,
24        });
25        let weak = Arc::downgrade(&notification);
26        dioxus_devtools::subsecond::register_handler(Arc::new(move || {
27            if let Some(notification) = weak.upgrade() {
28                notification.pending.store(true, Ordering::Release);
29                notification.ctx.request_repaint();
30            }
31        }));
32
33        // Register before connecting, so the first patch cannot be missed.
34        static CONNECT: Once = Once::new();
35        CONNECT.call_once(dioxus_devtools::connect_subsecond);
36        Self(notification)
37    }
38
39    pub(super) fn take_pending(&self) -> bool {
40        self.0.pending.swap(false, Ordering::AcqRel)
41    }
42}
43
44#[cfg(test)]
45mod tests {
46    use super::*;
47
48    #[test]
49    fn notifications_coalesce_and_are_consumed_once() {
50        // No dx connection is needed to test the UI-side mailbox.
51        let reload = HotReload(Arc::new(Notification {
52            pending: AtomicBool::new(false),
53            ctx: egui::Context::default(),
54        }));
55        assert!(!reload.take_pending());
56        reload.0.pending.store(true, Ordering::Release);
57        reload.0.pending.store(true, Ordering::Release);
58        assert!(reload.take_pending());
59        assert!(!reload.take_pending());
60    }
61}