1use std::fmt;
2use std::os::raw::c_void;
3use std::panic::{AssertUnwindSafe, catch_unwind};
4use std::ptr;
5use std::sync::{Condvar, Mutex};
6
7use maplibre_native_ffi_core as maplibre_core;
8use maplibre_native_ffi_sys as sys;
9
10use crate::custom_geometry::CanonicalTileId;
11
12type TileCallback = dyn Fn(CanonicalTileId) + Send + Sync + 'static;
13
14#[non_exhaustive]
21pub struct CustomMvtVectorSourceOptions {
22 fetch_tile: Box<TileCallback>,
23 cancel_tile: Option<Box<TileCallback>>,
24 pub min_zoom: Option<f64>,
26 pub max_zoom: Option<f64>,
28}
29
30impl fmt::Debug for CustomMvtVectorSourceOptions {
31 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
32 f.debug_struct("CustomMvtVectorSourceOptions")
33 .field("has_cancel_tile", &self.cancel_tile.is_some())
34 .field("min_zoom", &self.min_zoom)
35 .field("max_zoom", &self.max_zoom)
36 .finish_non_exhaustive()
37 }
38}
39
40impl CustomMvtVectorSourceOptions {
41 pub fn new<F>(fetch_tile: F) -> Self
42 where
43 F: Fn(CanonicalTileId) + Send + Sync + 'static,
44 {
45 Self {
46 fetch_tile: Box::new(fetch_tile),
47 cancel_tile: None,
48 min_zoom: None,
49 max_zoom: None,
50 }
51 }
52
53 pub fn with_cancel_tile<F>(mut self, cancel_tile: F) -> Self
54 where
55 F: Fn(CanonicalTileId) + Send + Sync + 'static,
56 {
57 self.cancel_tile = Some(Box::new(cancel_tile));
58 self
59 }
60}
61
62#[derive(Debug, Default)]
63struct CallbackLifecycle {
64 active: usize,
65 closing: bool,
66 closed: bool,
67}
68
69pub(crate) struct CustomMvtVectorSourceState {
72 fetch_tile: Box<TileCallback>,
73 cancel_tile: Option<Box<TileCallback>>,
74 min_zoom: Option<f64>,
75 max_zoom: Option<f64>,
76 lifecycle: Mutex<CallbackLifecycle>,
77 idle: Condvar,
78}
79
80impl fmt::Debug for CustomMvtVectorSourceState {
81 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
82 f.debug_struct("CustomMvtVectorSourceState")
83 .finish_non_exhaustive()
84 }
85}
86
87impl CustomMvtVectorSourceState {
88 pub(crate) fn new(options: CustomMvtVectorSourceOptions) -> Box<Self> {
89 Box::new(Self {
90 fetch_tile: options.fetch_tile,
91 cancel_tile: options.cancel_tile,
92 min_zoom: options.min_zoom,
93 max_zoom: options.max_zoom,
94 lifecycle: Mutex::new(CallbackLifecycle::default()),
95 idle: Condvar::new(),
96 })
97 }
98
99 pub(crate) fn descriptor(&self) -> sys::mln_custom_mvt_vector_source_options {
100 maplibre_core::style::custom_mvt_vector_source_options_to_native(
101 maplibre_core::style::CustomMvtVectorSourceDescriptorFields {
102 fetch_tile: Some(fetch_tile_trampoline),
103 cancel_tile: self
104 .cancel_tile
105 .as_ref()
106 .map(|_| cancel_tile_trampoline as _),
107 release_user_data: Some(release_trampoline),
108 user_data: ptr::from_ref(self).cast_mut().cast::<c_void>(),
109 min_zoom: self.min_zoom,
110 max_zoom: self.max_zoom,
111 },
112 )
113 }
114
115 pub(crate) fn close(&self) {
116 let mut lifecycle = self
117 .lifecycle
118 .lock()
119 .unwrap_or_else(|poisoned| poisoned.into_inner());
120 if lifecycle.closed {
121 return;
122 }
123 lifecycle.closing = true;
124 while lifecycle.active != 0 {
125 lifecycle = self
126 .idle
127 .wait(lifecycle)
128 .unwrap_or_else(|poisoned| poisoned.into_inner());
129 }
130 lifecycle.closed = true;
131 }
132
133 fn invoke_fetch(&self, tile_id: CanonicalTileId) {
134 let Some(_guard) = self.enter_callback() else {
135 return;
136 };
137 let _ = catch_unwind(AssertUnwindSafe(|| (self.fetch_tile)(tile_id)));
138 }
139
140 fn invoke_cancel(&self, tile_id: CanonicalTileId) {
141 let Some(_guard) = self.enter_callback() else {
142 return;
143 };
144 if let Some(cancel_tile) = &self.cancel_tile {
145 let _ = catch_unwind(AssertUnwindSafe(|| cancel_tile(tile_id)));
146 }
147 }
148
149 fn enter_callback(&self) -> Option<CallbackGuard<'_>> {
150 let mut lifecycle = self
151 .lifecycle
152 .lock()
153 .unwrap_or_else(|poisoned| poisoned.into_inner());
154 if lifecycle.closing || lifecycle.closed {
155 return None;
156 }
157 lifecycle.active += 1;
158 Some(CallbackGuard { state: self })
159 }
160
161 fn exit_callback(&self) {
162 let mut lifecycle = self
163 .lifecycle
164 .lock()
165 .unwrap_or_else(|poisoned| poisoned.into_inner());
166 lifecycle.active -= 1;
167 if lifecycle.active == 0 {
168 self.idle.notify_all();
169 }
170 }
171}
172
173impl Drop for CustomMvtVectorSourceState {
174 fn drop(&mut self) {
175 self.close();
176 }
177}
178
179struct CallbackGuard<'a> {
180 state: &'a CustomMvtVectorSourceState,
181}
182
183impl Drop for CallbackGuard<'_> {
184 fn drop(&mut self) {
185 self.state.exit_callback();
186 }
187}
188
189unsafe extern "C" fn fetch_tile_trampoline(
190 user_data: *mut c_void,
191 tile_id: sys::mln_canonical_tile_id,
192) {
193 let Some(state) = ptr::NonNull::new(user_data.cast::<CustomMvtVectorSourceState>()) else {
194 return;
195 };
196 unsafe { state.as_ref() }.invoke_fetch(CanonicalTileId::from_native(tile_id));
199}
200
201unsafe extern "C" fn release_trampoline(user_data: *mut c_void) {
202 let Some(state) = ptr::NonNull::new(user_data.cast::<CustomMvtVectorSourceState>()) else {
203 return;
204 };
205 let state = unsafe { Box::from_raw(state.as_ptr()) };
210 let _ = catch_unwind(AssertUnwindSafe(move || drop(state)));
211}
212
213unsafe extern "C" fn cancel_tile_trampoline(
214 user_data: *mut c_void,
215 tile_id: sys::mln_canonical_tile_id,
216) {
217 let Some(state) = ptr::NonNull::new(user_data.cast::<CustomMvtVectorSourceState>()) else {
218 return;
219 };
220 unsafe { state.as_ref() }.invoke_cancel(CanonicalTileId::from_native(tile_id));
223}
224
225#[cfg(test)]
226mod tests {
227 use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
228 use std::sync::{Arc, Condvar, Mutex};
229 use std::time::Duration;
230
231 use super::*;
232
233 fn tile(z: u32, x: u32, y: u32) -> sys::mln_canonical_tile_id {
234 CanonicalTileId::new(z, x, y).to_native()
235 }
236
237 #[test]
238 fn custom_mvt_vector_callbacks_invoke_fetch_and_cancel_with_copied_tile_id() {
240 let fetched = Arc::new(Mutex::new(Vec::new()));
241 let cancelled = Arc::new(Mutex::new(Vec::new()));
242 let fetched_callback = Arc::clone(&fetched);
243 let cancelled_callback = Arc::clone(&cancelled);
244 let state = CustomMvtVectorSourceState::new(
245 CustomMvtVectorSourceOptions::new(move |tile_id| {
246 fetched_callback.lock().unwrap().push(tile_id);
247 })
248 .with_cancel_tile(move |tile_id| {
249 cancelled_callback.lock().unwrap().push(tile_id);
250 }),
251 );
252 let descriptor = state.descriptor();
253
254 unsafe {
255 descriptor.fetch_tile.unwrap()(descriptor.user_data, tile(1, 2, 3));
256 descriptor.cancel_tile.unwrap()(descriptor.user_data, tile(4, 5, 6));
257 }
258
259 assert_eq!(
260 fetched.lock().unwrap().as_slice(),
261 &[CanonicalTileId::new(1, 2, 3)]
262 );
263 assert_eq!(
264 cancelled.lock().unwrap().as_slice(),
265 &[CanonicalTileId::new(4, 5, 6)]
266 );
267 }
268
269 #[test]
270 fn custom_mvt_vector_callbacks_contain_panics() {
272 let cancel_called = Arc::new(AtomicBool::new(false));
273 let cancel_called_callback = Arc::clone(&cancel_called);
274 let state = CustomMvtVectorSourceState::new(
275 CustomMvtVectorSourceOptions::new(|_| panic!("fetch panic")).with_cancel_tile(
276 move |_| {
277 cancel_called_callback.store(true, Ordering::SeqCst);
278 panic!("cancel panic");
279 },
280 ),
281 );
282 let descriptor = state.descriptor();
283
284 unsafe {
285 descriptor.fetch_tile.unwrap()(descriptor.user_data, tile(0, 0, 0));
286 descriptor.cancel_tile.unwrap()(descriptor.user_data, tile(0, 0, 0));
287 }
288
289 assert!(cancel_called.load(Ordering::SeqCst));
290 }
291
292 #[test]
293 fn custom_mvt_vector_state_release_waits_for_active_upcalls() {
295 let entered = Arc::new((Mutex::new(false), Condvar::new()));
296 let release = Arc::new((Mutex::new(false), Condvar::new()));
297 let closed = Arc::new(AtomicBool::new(false));
298 let close_attempts = Arc::new(AtomicUsize::new(0));
299 let entered_callback = Arc::clone(&entered);
300 let release_callback = Arc::clone(&release);
301 let state = CustomMvtVectorSourceState::new(CustomMvtVectorSourceOptions::new(move |_| {
302 let (entered_lock, entered_cvar) = &*entered_callback;
303 *entered_lock.lock().unwrap() = true;
304 entered_cvar.notify_all();
305
306 let (release_lock, release_cvar) = &*release_callback;
307 let released = release_lock.lock().unwrap();
308 let (_released, timeout) = release_cvar
309 .wait_timeout_while(released, Duration::from_secs(5), |released| !*released)
310 .unwrap();
311 assert!(!timeout.timed_out());
312 }));
313 let descriptor = state.descriptor();
314 let callback = descriptor.fetch_tile.unwrap();
315 let user_data = descriptor.user_data as usize;
316
317 std::thread::scope(|scope| {
318 scope.spawn(move || unsafe {
319 callback(user_data as *mut c_void, tile(1, 1, 1));
320 });
321 let (entered_lock, entered_cvar) = &*entered;
322 let entered_guard = entered_lock.lock().unwrap();
323 let (_entered_guard, timeout) = entered_cvar
324 .wait_timeout_while(entered_guard, Duration::from_secs(5), |entered| !*entered)
325 .unwrap();
326 assert!(!timeout.timed_out());
327
328 let closed_for_thread = Arc::clone(&closed);
329 let close_attempts_for_thread = Arc::clone(&close_attempts);
330 let state_ref = &*state;
331 scope.spawn(move || {
332 close_attempts_for_thread.fetch_add(1, Ordering::SeqCst);
333 state_ref.close();
334 closed_for_thread.store(true, Ordering::SeqCst);
335 });
336
337 std::thread::sleep(Duration::from_millis(50));
338 assert_eq!(close_attempts.load(Ordering::SeqCst), 1);
339 assert!(!closed.load(Ordering::SeqCst));
340 let (release_lock, release_cvar) = &*release;
341 *release_lock.lock().unwrap() = true;
342 release_cvar.notify_all();
343 });
344
345 assert!(closed.load(Ordering::SeqCst));
346 unsafe {
347 descriptor.fetch_tile.unwrap()(descriptor.user_data, tile(9, 9, 9));
348 }
349 }
350}