1use std::cell::RefCell;
2use std::rc::Rc;
3use std::sync::{
4 Arc, Mutex,
5 atomic::{AtomicU8, AtomicU64, Ordering},
6};
7
8#[derive(Clone, Copy, Debug, PartialEq, Eq)]
9pub enum AppLifecycle {
10 Foreground,
11 Background,
12}
13
14type LifecycleCallback = Box<dyn Fn(AppLifecycle) + Send>;
15type DeeplinkCallback = Box<dyn Fn(Vec<u8>) + Send>;
16
17struct LifecycleSlot {
18 callback: Mutex<Option<LifecycleCallback>>,
19}
20
21struct DeeplinkSlot {
22 callback: Mutex<Option<DeeplinkCallback>>,
23}
24
25#[derive(Clone)]
26struct LifecycleEntry {
27 id: u64,
28 slot: Arc<LifecycleSlot>,
29}
30
31#[derive(Clone)]
32struct DeeplinkEntry {
33 id: u64,
34 slot: Arc<DeeplinkSlot>,
35}
36
37pub struct LifecycleDispatcher {
38 current: AtomicU8,
39 primary: Mutex<Option<Arc<LifecycleSlot>>>,
40 pending: Mutex<Vec<AppLifecycle>>,
41 listeners: Mutex<Vec<LifecycleEntry>>,
42 next_id: AtomicU64,
43 processing: Mutex<()>,
44}
45
46impl LifecycleDispatcher {
47 const fn new() -> Self {
48 Self {
49 current: AtomicU8::new(0),
50 primary: Mutex::new(None),
51 pending: Mutex::new(Vec::new()),
52 listeners: Mutex::new(Vec::new()),
53 next_id: AtomicU64::new(1),
54 processing: Mutex::new(()),
55 }
56 }
57
58 pub fn set_callback(&self, callback: Box<dyn Fn(AppLifecycle) + Send>) {
59 *lock(&self.primary) = Some(Arc::new(LifecycleSlot {
60 callback: Mutex::new(Some(callback)),
61 }));
62 }
63
64 pub fn add_listener(&self, callback: Box<dyn Fn(AppLifecycle) + Send>) -> u64 {
65 let id = self.next_id.fetch_add(1, Ordering::Relaxed);
66 lock(&self.listeners).push(LifecycleEntry {
67 id,
68 slot: Arc::new(LifecycleSlot {
69 callback: Mutex::new(Some(callback)),
70 }),
71 });
72 id
73 }
74
75 pub fn remove_listener(&self, id: u64) -> bool {
76 let mut listeners = lock(&self.listeners);
77 let before = listeners.len();
78 listeners.retain(|entry| entry.id != id);
79 listeners.len() != before
80 }
81
82 pub fn current(&self) -> Option<AppLifecycle> {
83 lifecycle_from_code(self.current.load(Ordering::Relaxed))
84 }
85
86 pub fn push(&self, state: AppLifecycle) {
87 self.current.store(lifecycle_code(state), Ordering::Relaxed);
88 lock(&self.pending).push(state);
89 }
90
91 pub fn process(&self) {
92 let _processing = match self.processing.try_lock() {
93 Ok(guard) => guard,
94 Err(std::sync::TryLockError::Poisoned(error)) => error.into_inner(),
95 Err(std::sync::TryLockError::WouldBlock) => return,
96 };
97 let batch = std::mem::take(&mut *lock(&self.pending));
98 if batch.is_empty() {
99 return;
100 }
101 let mut retained = Vec::new();
102 for state in batch {
103 let primary = lock(&self.primary).clone();
104 let mut handled = primary.as_ref().is_some_and(|slot| {
105 invoke_lifecycle(slot, state, || {
106 lock(&self.primary)
107 .as_ref()
108 .is_some_and(|current| Arc::ptr_eq(current, slot))
109 })
110 });
111 let listeners = lock(&self.listeners).clone();
112 for entry in &listeners {
113 if lifecycle_listener_is_current(&self.listeners, entry.id, &entry.slot)
114 && invoke_lifecycle(&entry.slot, state, || {
115 lifecycle_listener_is_current(&self.listeners, entry.id, &entry.slot)
116 })
117 {
118 handled = true;
119 }
120 }
121 if !handled {
122 retained.push(state);
123 }
124 }
125 if !retained.is_empty() {
126 lock(&self.pending).splice(0..0, retained);
127 }
128 }
129}
130
131impl Default for LifecycleDispatcher {
132 fn default() -> Self {
133 Self::new()
134 }
135}
136
137#[derive(Clone)]
138pub struct RuntimeDispatchers {
139 pub lifecycle: Arc<LifecycleDispatcher>,
140 pub deeplink: Arc<DeeplinkDispatcher>,
141}
142
143pub struct ActiveDispatcherScope {
144 previous: Option<RuntimeDispatchers>,
145}
146
147static EXTERNAL_LIFECYCLE: Mutex<Vec<AppLifecycle>> = Mutex::new(Vec::new());
148static EXTERNAL_DEEPLINKS: Mutex<Vec<Vec<u8>>> = Mutex::new(Vec::new());
149
150thread_local! {
151 static ACTIVE_DISPATCHERS: RefCell<Option<RuntimeDispatchers>> = const { RefCell::new(None) };
152}
153
154pub fn enter_dispatchers(dispatchers: RuntimeDispatchers) -> ActiveDispatcherScope {
155 let lifecycle = std::mem::take(&mut *lock(&EXTERNAL_LIFECYCLE));
156 for state in lifecycle {
157 dispatchers.lifecycle.push(state);
158 }
159 let deeplinks = std::mem::take(&mut *lock(&EXTERNAL_DEEPLINKS));
160 for data in deeplinks {
161 dispatchers.deeplink.push(data);
162 }
163 let previous = ACTIVE_DISPATCHERS.with(|slot| slot.borrow_mut().replace(dispatchers));
164 ActiveDispatcherScope { previous }
165}
166
167impl Drop for ActiveDispatcherScope {
168 fn drop(&mut self) {
169 ACTIVE_DISPATCHERS.with(|slot| {
170 *slot.borrow_mut() = self.previous.take();
171 });
172 }
173}
174
175fn active_dispatchers() -> Option<RuntimeDispatchers> {
176 ACTIVE_DISPATCHERS.with(|slot| slot.borrow().clone())
177}
178
179pub struct DeeplinkDispatcher {
180 primary: Mutex<Option<Arc<DeeplinkSlot>>>,
181 pending: Mutex<Vec<Vec<u8>>>,
182 listeners: Mutex<Vec<DeeplinkEntry>>,
183 next_id: AtomicU64,
184 processing: Mutex<()>,
185}
186
187impl DeeplinkDispatcher {
188 const fn new() -> Self {
189 Self {
190 primary: Mutex::new(None),
191 pending: Mutex::new(Vec::new()),
192 listeners: Mutex::new(Vec::new()),
193 next_id: AtomicU64::new(1),
194 processing: Mutex::new(()),
195 }
196 }
197
198 pub fn set_callback(&self, callback: Box<dyn Fn(Vec<u8>) + Send>) {
199 *lock(&self.primary) = Some(Arc::new(DeeplinkSlot {
200 callback: Mutex::new(Some(callback)),
201 }));
202 }
203
204 pub fn add_listener(&self, callback: Box<dyn Fn(Vec<u8>) + Send>) -> u64 {
205 let id = self.next_id.fetch_add(1, Ordering::Relaxed);
206 lock(&self.listeners).push(DeeplinkEntry {
207 id,
208 slot: Arc::new(DeeplinkSlot {
209 callback: Mutex::new(Some(callback)),
210 }),
211 });
212 id
213 }
214
215 pub fn remove_listener(&self, id: u64) -> bool {
216 let mut listeners = lock(&self.listeners);
217 let before = listeners.len();
218 listeners.retain(|entry| entry.id != id);
219 listeners.len() != before
220 }
221
222 pub fn push(&self, data: Vec<u8>) {
223 lock(&self.pending).push(data);
224 }
225
226 pub fn process(&self) {
227 let _processing = match self.processing.try_lock() {
228 Ok(guard) => guard,
229 Err(std::sync::TryLockError::Poisoned(error)) => error.into_inner(),
230 Err(std::sync::TryLockError::WouldBlock) => return,
231 };
232 let batch = std::mem::take(&mut *lock(&self.pending));
233 if batch.is_empty() {
234 return;
235 }
236 let mut retained = Vec::new();
237 for data in batch {
238 let primary = lock(&self.primary).clone();
239 let mut handled = primary.as_ref().is_some_and(|slot| {
240 invoke_deeplink(slot, &data, || {
241 lock(&self.primary)
242 .as_ref()
243 .is_some_and(|current| Arc::ptr_eq(current, slot))
244 })
245 });
246 let listeners = lock(&self.listeners).clone();
247 for entry in &listeners {
248 if deeplink_listener_is_current(&self.listeners, entry.id, &entry.slot)
249 && invoke_deeplink(&entry.slot, &data, || {
250 deeplink_listener_is_current(&self.listeners, entry.id, &entry.slot)
251 })
252 {
253 handled = true;
254 }
255 }
256 if !handled {
257 retained.push(data);
258 }
259 }
260 if !retained.is_empty() {
261 lock(&self.pending).splice(0..0, retained);
262 }
263 }
264}
265
266impl Default for DeeplinkDispatcher {
267 fn default() -> Self {
268 Self::new()
269 }
270}
271
272static LIFECYCLE_DISPATCHER: LifecycleDispatcher = LifecycleDispatcher::new();
273static DEEPLINK_DISPATCHER: DeeplinkDispatcher = DeeplinkDispatcher::new();
274
275thread_local! {
276 static PRE_REDRAW: RefCell<Option<Rc<RefCell<Option<Box<dyn FnMut(&repose_core::RenderContext)>>>>>> =
277 const { RefCell::new(None) };
278}
279
280pub fn set_pre_redraw(cb: Option<Box<dyn FnMut(&repose_core::RenderContext)>>) {
281 PRE_REDRAW.with(|current| {
282 *current.borrow_mut() = cb.map(|callback| Rc::new(RefCell::new(Some(callback))));
283 });
284}
285
286pub fn run_pre_redraw(ctx: &repose_core::RenderContext) {
287 PRE_REDRAW.with(|current| {
288 let Some(slot) = current.borrow().clone() else {
289 return;
290 };
291 let callback = slot.borrow_mut().take();
292 let Some(mut callback) = callback else {
293 return;
294 };
295 callback(ctx);
296 let still_current = current
297 .borrow()
298 .as_ref()
299 .is_some_and(|active| Rc::ptr_eq(active, &slot));
300 if still_current && slot.borrow().is_none() {
301 *slot.borrow_mut() = Some(callback);
302 }
303 });
304}
305
306pub fn set_on_lifecycle(callback: Box<dyn Fn(AppLifecycle) + Send>) {
307 if let Some(dispatchers) = active_dispatchers() {
308 dispatchers.lifecycle.set_callback(callback);
309 } else {
310 LIFECYCLE_DISPATCHER.set_callback(callback);
311 }
312}
313
314pub fn add_lifecycle_listener(callback: Box<dyn Fn(AppLifecycle) + Send>) -> u64 {
315 if let Some(dispatchers) = active_dispatchers() {
316 dispatchers.lifecycle.add_listener(callback)
317 } else {
318 LIFECYCLE_DISPATCHER.add_listener(callback)
319 }
320}
321
322pub fn remove_lifecycle_listener(id: u64) -> bool {
323 active_dispatchers()
324 .map(|dispatchers| dispatchers.lifecycle.remove_listener(id))
325 .unwrap_or_else(|| LIFECYCLE_DISPATCHER.remove_listener(id))
326}
327
328pub fn current_lifecycle() -> Option<AppLifecycle> {
329 active_dispatchers()
330 .map(|dispatchers| dispatchers.lifecycle.current())
331 .unwrap_or_else(|| LIFECYCLE_DISPATCHER.current())
332}
333
334pub fn push_lifecycle(state: AppLifecycle) {
335 if let Some(dispatchers) = active_dispatchers() {
336 dispatchers.lifecycle.push(state);
337 } else {
338 LIFECYCLE_DISPATCHER.push(state);
339 lock(&EXTERNAL_LIFECYCLE).push(state);
340 }
341}
342
343pub fn process_lifecycle() {
344 if let Some(dispatchers) = active_dispatchers() {
345 dispatchers.lifecycle.process();
346 } else {
347 LIFECYCLE_DISPATCHER.process();
348 }
349}
350
351pub fn set_on_deeplink(callback: Box<dyn Fn(Vec<u8>) + Send>) {
352 if let Some(dispatchers) = active_dispatchers() {
353 dispatchers.deeplink.set_callback(callback);
354 } else {
355 DEEPLINK_DISPATCHER.set_callback(callback);
356 }
357}
358
359pub fn add_deeplink_listener(callback: Box<dyn Fn(Vec<u8>) + Send>) -> u64 {
360 if let Some(dispatchers) = active_dispatchers() {
361 dispatchers.deeplink.add_listener(callback)
362 } else {
363 DEEPLINK_DISPATCHER.add_listener(callback)
364 }
365}
366
367pub fn remove_deeplink_listener(id: u64) -> bool {
368 active_dispatchers()
369 .map(|dispatchers| dispatchers.deeplink.remove_listener(id))
370 .unwrap_or_else(|| DEEPLINK_DISPATCHER.remove_listener(id))
371}
372
373pub fn push_deeplink(data: Vec<u8>) {
374 if let Some(dispatchers) = active_dispatchers() {
375 dispatchers.deeplink.push(data);
376 } else {
377 DEEPLINK_DISPATCHER.push(data.clone());
378 lock(&EXTERNAL_DEEPLINKS).push(data);
379 }
380}
381
382pub fn process_deeplinks() {
383 if let Some(dispatchers) = active_dispatchers() {
384 dispatchers.deeplink.process();
385 } else {
386 DEEPLINK_DISPATCHER.process();
387 }
388}
389
390fn lifecycle_code(state: AppLifecycle) -> u8 {
391 match state {
392 AppLifecycle::Foreground => 1,
393 AppLifecycle::Background => 2,
394 }
395}
396
397fn lifecycle_from_code(code: u8) -> Option<AppLifecycle> {
398 match code {
399 1 => Some(AppLifecycle::Foreground),
400 2 => Some(AppLifecycle::Background),
401 _ => None,
402 }
403}
404
405fn lifecycle_listener_is_current(
406 listeners: &Mutex<Vec<LifecycleEntry>>,
407 id: u64,
408 slot: &Arc<LifecycleSlot>,
409) -> bool {
410 lock(listeners)
411 .iter()
412 .any(|entry| entry.id == id && Arc::ptr_eq(&entry.slot, slot))
413}
414
415fn deeplink_listener_is_current(
416 listeners: &Mutex<Vec<DeeplinkEntry>>,
417 id: u64,
418 slot: &Arc<DeeplinkSlot>,
419) -> bool {
420 lock(listeners)
421 .iter()
422 .any(|entry| entry.id == id && Arc::ptr_eq(&entry.slot, slot))
423}
424
425fn invoke_lifecycle(slot: &LifecycleSlot, state: AppLifecycle, restore: impl Fn() -> bool) -> bool {
426 let callback = slot
427 .callback
428 .lock()
429 .unwrap_or_else(|error| error.into_inner())
430 .take();
431 let Some(callback) = callback else {
432 return false;
433 };
434 let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| callback(state)));
435 if let Err(error) = result {
436 log::error!("lifecycle callback panicked: {}", panic_message(&error));
437 }
438 if restore()
439 && slot
440 .callback
441 .lock()
442 .unwrap_or_else(|error| error.into_inner())
443 .is_none()
444 {
445 *slot
446 .callback
447 .lock()
448 .unwrap_or_else(|error| error.into_inner()) = Some(callback);
449 }
450 true
451}
452
453fn invoke_deeplink(slot: &DeeplinkSlot, data: &[u8], restore: impl Fn() -> bool) -> bool {
454 let callback = slot
455 .callback
456 .lock()
457 .unwrap_or_else(|error| error.into_inner())
458 .take();
459 let Some(callback) = callback else {
460 return false;
461 };
462 let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| callback(data.to_vec())));
463 if let Err(error) = result {
464 log::error!("deeplink callback panicked: {}", panic_message(&error));
465 }
466 if restore()
467 && slot
468 .callback
469 .lock()
470 .unwrap_or_else(|error| error.into_inner())
471 .is_none()
472 {
473 *slot
474 .callback
475 .lock()
476 .unwrap_or_else(|error| error.into_inner()) = Some(callback);
477 }
478 true
479}
480
481fn lock<T>(mutex: &Mutex<T>) -> std::sync::MutexGuard<'_, T> {
482 mutex.lock().unwrap_or_else(|error| error.into_inner())
483}
484
485fn panic_message(error: &Box<dyn std::any::Any + Send>) -> &str {
486 error
487 .downcast_ref::<String>()
488 .map(String::as_str)
489 .or_else(|| error.downcast_ref::<&str>().copied())
490 .unwrap_or("unknown")
491}
492
493#[cfg(test)]
494mod tests {
495 use super::*;
496 use std::sync::atomic::{AtomicUsize, Ordering};
497
498 #[test]
499 fn active_dispatchers_receive_legacy_global_calls() {
500 let lifecycle = Arc::new(LifecycleDispatcher::default());
501 let deeplink = Arc::new(DeeplinkDispatcher::default());
502 let seen = Arc::new(AtomicUsize::new(0));
503 let callback_seen = seen.clone();
504 {
505 let _scope = enter_dispatchers(RuntimeDispatchers {
506 lifecycle: lifecycle.clone(),
507 deeplink: deeplink.clone(),
508 });
509 set_on_lifecycle(Box::new(move |_state| {
510 callback_seen.fetch_add(1, Ordering::Relaxed);
511 }));
512 push_lifecycle(AppLifecycle::Foreground);
513 process_lifecycle();
514 }
515 assert_eq!(seen.load(Ordering::Relaxed), 1);
516 }
517}