Skip to main content

repose_app/
lifecycle.rs

1use std::sync::{
2    Mutex,
3    atomic::{AtomicU8, Ordering},
4};
5
6#[derive(Clone, Copy, Debug, PartialEq, Eq)]
7pub enum AppLifecycle {
8    Foreground,
9    Background,
10}
11
12static CURRENT_LIFECYCLE: AtomicU8 = AtomicU8::new(0);
13static LIFECYCLE_CB: Mutex<Option<Box<dyn Fn(AppLifecycle) + Send>>> = Mutex::new(None);
14static PENDING_LIFECYCLE: Mutex<Vec<AppLifecycle>> = Mutex::new(Vec::new());
15static LIFECYCLE_LISTENERS: Mutex<Vec<(u64, Box<dyn Fn(AppLifecycle) + Send>)>> =
16    Mutex::new(Vec::new());
17static NEXT_LISTENER_ID: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(1);
18
19static DEEPLINK_CB: Mutex<Option<Box<dyn Fn(Vec<u8>) + Send>>> = Mutex::new(None);
20static PENDING_DEEPLINKS: Mutex<Vec<Vec<u8>>> = Mutex::new(Vec::new());
21static DEEPLINK_LISTENERS: Mutex<Vec<(u64, Box<dyn Fn(Vec<u8>) + Send>)>> =
22    Mutex::new(Vec::new());
23
24thread_local! {
25    static PRE_REDRAW: std::cell::RefCell<Option<Box<dyn FnMut(&repose_core::RenderContext)>>> =
26        const { std::cell::RefCell::new(None) };
27}
28
29pub fn set_pre_redraw(cb: Option<Box<dyn FnMut(&repose_core::RenderContext)>>) {
30    PRE_REDRAW.with(|c| *c.borrow_mut() = cb);
31}
32
33pub fn run_pre_redraw(ctx: &repose_core::RenderContext) {
34    PRE_REDRAW.with(|c| {
35        if let Some(cb) = c.borrow_mut().as_mut() {
36            cb(ctx);
37        }
38    });
39}
40
41pub fn set_on_lifecycle(callback: Box<dyn Fn(AppLifecycle) + Send>) {
42    *LIFECYCLE_CB.lock().unwrap_or_else(|e| e.into_inner()) = Some(callback);
43}
44
45/// Register an additional lifecycle listener alongside the single
46/// `set_on_lifecycle` callback. Returns an id for `remove_lifecycle_listener`.
47pub fn add_lifecycle_listener(callback: Box<dyn Fn(AppLifecycle) + Send>) -> u64 {
48    let id = NEXT_LISTENER_ID.fetch_add(1, Ordering::Relaxed);
49    LIFECYCLE_LISTENERS
50        .lock()
51        .unwrap_or_else(|e| e.into_inner())
52        .push((id, callback));
53    id
54}
55
56pub fn remove_lifecycle_listener(id: u64) -> bool {
57    let mut listeners = LIFECYCLE_LISTENERS
58        .lock()
59        .unwrap_or_else(|e| e.into_inner());
60    let before = listeners.len();
61    listeners.retain(|(lid, _)| *lid != id);
62    listeners.len() != before
63}
64
65fn has_lifecycle_listener() -> bool {
66    if LIFECYCLE_CB
67        .lock()
68        .unwrap_or_else(|e| e.into_inner())
69        .is_some()
70    {
71        return true;
72    }
73    !LIFECYCLE_LISTENERS
74        .lock()
75        .unwrap_or_else(|e| e.into_inner())
76        .is_empty()
77}
78
79pub fn current_lifecycle() -> Option<AppLifecycle> {
80    match CURRENT_LIFECYCLE.load(Ordering::Relaxed) {
81        1 => Some(AppLifecycle::Foreground),
82        2 => Some(AppLifecycle::Background),
83        _ => None,
84    }
85}
86
87pub fn push_lifecycle(state: AppLifecycle) {
88    let code = match state {
89        AppLifecycle::Foreground => 1,
90        AppLifecycle::Background => 2,
91    };
92    CURRENT_LIFECYCLE.store(code, Ordering::Relaxed);
93    PENDING_LIFECYCLE
94        .lock()
95        .unwrap_or_else(|e| e.into_inner())
96        .push(state);
97}
98
99pub fn process_lifecycle() {
100    if !has_lifecycle_listener() {
101        return;
102    }
103    let batch = std::mem::take(&mut *PENDING_LIFECYCLE.lock().unwrap_or_else(|e| e.into_inner()));
104    if batch.is_empty() {
105        return;
106    }
107    for state in batch {
108        if let Some(cb) = LIFECYCLE_CB
109            .lock()
110            .unwrap_or_else(|e| e.into_inner())
111            .as_ref()
112        {
113            let res = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| cb(state)));
114            if let Err(e) = res {
115                log::error!(
116                    "lifecycle callback panicked: {}",
117                    e.downcast_ref::<String>()
118                        .map(|s| s.as_str())
119                        .or_else(|| e.downcast_ref::<&str>().copied())
120                        .unwrap_or("unknown")
121                );
122            }
123        }
124        let listeners = std::mem::take(
125            &mut *LIFECYCLE_LISTENERS
126                .lock()
127                .unwrap_or_else(|e| e.into_inner()),
128        );
129        for (_, cb) in &listeners {
130            let res = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| cb(state)));
131            if let Err(e) = res {
132                log::error!(
133                    "lifecycle listener panicked: {}",
134                    e.downcast_ref::<String>()
135                        .map(|s| s.as_str())
136                        .or_else(|| e.downcast_ref::<&str>().copied())
137                        .unwrap_or("unknown")
138                );
139            }
140        }
141        *LIFECYCLE_LISTENERS
142            .lock()
143            .unwrap_or_else(|e| e.into_inner()) = listeners;
144    }
145}
146
147pub fn set_on_deeplink(callback: Box<dyn Fn(Vec<u8>) + Send>) {
148    *DEEPLINK_CB.lock().unwrap_or_else(|e| e.into_inner()) = Some(callback);
149}
150
151/// Register an additional deeplink listener. Returns an id for removal.
152pub fn add_deeplink_listener(callback: Box<dyn Fn(Vec<u8>) + Send>) -> u64 {
153    let id = NEXT_LISTENER_ID.fetch_add(1, Ordering::Relaxed);
154    DEEPLINK_LISTENERS
155        .lock()
156        .unwrap_or_else(|e| e.into_inner())
157        .push((id, callback));
158    id
159}
160
161pub fn remove_deeplink_listener(id: u64) -> bool {
162    let mut listeners = DEEPLINK_LISTENERS
163        .lock()
164        .unwrap_or_else(|e| e.into_inner());
165    let before = listeners.len();
166    listeners.retain(|(lid, _)| *lid != id);
167    listeners.len() != before
168}
169
170fn has_deeplink_listener() -> bool {
171    if DEEPLINK_CB
172        .lock()
173        .unwrap_or_else(|e| e.into_inner())
174        .is_some()
175    {
176        return true;
177    }
178    !DEEPLINK_LISTENERS
179        .lock()
180        .unwrap_or_else(|e| e.into_inner())
181        .is_empty()
182}
183
184pub fn push_deeplink(data: Vec<u8>) {
185    PENDING_DEEPLINKS
186        .lock()
187        .unwrap_or_else(|e| e.into_inner())
188        .push(data);
189}
190
191pub fn process_deeplinks() {
192    if !has_deeplink_listener() {
193        return;
194    }
195    let mut queue = PENDING_DEEPLINKS.lock().unwrap_or_else(|e| e.into_inner());
196    if queue.is_empty() {
197        return;
198    }
199    let batch = std::mem::take(&mut *queue);
200    drop(queue);
201    for data in batch {
202        if let Some(cb) = DEEPLINK_CB
203            .lock()
204            .unwrap_or_else(|e| e.into_inner())
205            .as_ref()
206        {
207            let res = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| cb(data.clone())));
208            if let Err(e) = res {
209                log::error!(
210                    "deeplink callback panicked: {}",
211                    e.downcast_ref::<String>()
212                        .map(|s| s.as_str())
213                        .or_else(|| e.downcast_ref::<&str>().copied())
214                        .unwrap_or("unknown")
215                );
216            }
217        }
218        let listeners = std::mem::take(
219            &mut *DEEPLINK_LISTENERS
220                .lock()
221                .unwrap_or_else(|e| e.into_inner()),
222        );
223        for (_, cb) in &listeners {
224            let res = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| cb(data.clone())));
225            if let Err(e) = res {
226                log::error!(
227                    "deeplink listener panicked: {}",
228                    e.downcast_ref::<String>()
229                        .map(|s| s.as_str())
230                        .or_else(|| e.downcast_ref::<&str>().copied())
231                        .unwrap_or("unknown")
232                );
233            }
234        }
235        *DEEPLINK_LISTENERS
236            .lock()
237            .unwrap_or_else(|e| e.into_inner()) = listeners;
238    }
239}