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
45pub 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
151pub 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}