1use std::cell::{Cell, RefCell};
2use std::collections::HashMap;
3use std::fmt;
4use std::marker::PhantomData;
5use std::rc::{Rc, Weak};
6
7use maplibre_core::AmbientCacheOperation;
8use maplibre_native_core as maplibre_core;
9use maplibre_native_sys as sys;
10
11use crate::events::{
12 MapId, OfflineRegionDownloadState, OfflineRegionStatus, RuntimeEvent, RuntimeEventSource,
13 empty_runtime_event,
14};
15use crate::handle::{ThreadAffineNativeHandle, closed_handle_error, out_handle};
16use crate::map::MapState;
17use crate::resource::{ResourceProviderState, ResourceTransformState};
18use crate::{
19 Error, ErrorKind, HandleOperationError, MapHandle, MapOptions, ResourceProviderDecision, Result,
20};
21#[cfg(test)]
22use crate::{Geometry, LatLngBounds};
23
24pub use maplibre_core::runtime::{OfflineRegionDefinition, OfflineRegionInfo, RuntimeOptions};
25pub(crate) use maplibre_core::runtime::{
26 OfflineRegionDefinitionNativeExt, RuntimeOptionsNativeExt,
27};
28
29#[derive(Debug)]
30pub(crate) struct RuntimeState {
31 handle: ThreadAffineNativeHandle<sys::mln_runtime>,
32 next_map_id: Cell<u64>,
33 has_created_map: Cell<bool>,
34 map_ids: RefCell<HashMap<usize, MapId>>,
35 map_states: RefCell<HashMap<usize, Weak<MapState>>>,
36 resource_transform: RefCell<Option<Box<ResourceTransformState>>>,
37 resource_provider: RefCell<Option<Box<ResourceProviderState>>>,
38}
39
40impl RuntimeState {
41 fn new(ptr: std::ptr::NonNull<sys::mln_runtime>) -> Self {
42 let handle = unsafe {
45 ThreadAffineNativeHandle::from_raw(ptr, sys::mln_runtime_destroy, "mln_runtime")
46 };
47 Self {
48 handle,
49 next_map_id: Cell::new(1),
50 has_created_map: Cell::new(false),
51 map_ids: RefCell::new(HashMap::new()),
52 map_states: RefCell::new(HashMap::new()),
53 resource_transform: RefCell::new(None),
54 resource_provider: RefCell::new(None),
55 }
56 }
57
58 pub(crate) fn as_ptr(&self) -> Result<*mut sys::mln_runtime> {
59 let ptr = self.handle.as_ptr();
60 if ptr.is_null() {
61 Err(closed_handle_error("RuntimeHandle"))
62 } else {
63 Ok(ptr)
64 }
65 }
66
67 fn is_closed(&self) -> bool {
68 self.handle.is_closed()
69 }
70
71 fn close(&self) -> Result<()> {
72 self.handle.close()?;
73 self.resource_transform.borrow_mut().take();
74 self.resource_provider.borrow_mut().take();
75 Ok(())
76 }
77
78 fn set_resource_provider<F>(&self, callback: F) -> Result<()>
79 where
80 F: Fn(crate::ResourceRequest, crate::ResourceRequestHandle) -> ResourceProviderDecision
81 + Send
82 + Sync
83 + 'static,
84 {
85 self.check_resource_callbacks_allowed()?;
86 let runtime = self.as_ptr()?;
87 let replacement = ResourceProviderState::new(callback);
88 let descriptor = replacement.descriptor();
89
90 maplibre_core::check(unsafe {
95 sys::mln_runtime_set_resource_provider(runtime, &descriptor)
96 })?;
97 self.resource_provider.borrow_mut().replace(replacement);
98 Ok(())
99 }
100
101 fn set_resource_transform<F>(&self, callback: F) -> Result<()>
102 where
103 F: Fn(crate::ResourceTransformRequest) -> Option<String> + Send + Sync + 'static,
104 {
105 let runtime = self.as_ptr()?;
106 let replacement = ResourceTransformState::new(callback);
107 let descriptor = replacement.descriptor();
108
109 maplibre_core::check(unsafe {
114 sys::mln_runtime_set_resource_transform(runtime, &descriptor)
115 })?;
116 self.resource_transform.borrow_mut().replace(replacement);
117 Ok(())
118 }
119
120 fn clear_resource_transform(&self) -> Result<()> {
121 let runtime = self.as_ptr()?;
122
123 maplibre_core::check(unsafe { sys::mln_runtime_clear_resource_transform(runtime) })?;
126 self.resource_transform.borrow_mut().take();
127 Ok(())
128 }
129
130 fn check_resource_callbacks_allowed(&self) -> Result<()> {
131 if self.has_created_map.get() {
132 return Err(Error::new(
133 ErrorKind::InvalidState,
134 None,
135 "resource callbacks must be configured before creating maps from the runtime",
136 ));
137 }
138 Ok(())
139 }
140
141 pub(crate) fn register_map(&self, ptr: *mut sys::mln_map) -> MapId {
142 self.has_created_map.set(true);
143 let id = MapId::new(self.next_map_id.get());
144 self.next_map_id.set(id.get().saturating_add(1));
145 self.map_ids.borrow_mut().insert(ptr as usize, id);
146 id
147 }
148
149 pub(crate) fn register_map_state(&self, ptr: *mut sys::mln_map, state: Weak<MapState>) {
150 if !ptr.is_null() {
151 self.map_states.borrow_mut().insert(ptr as usize, state);
152 }
153 }
154
155 pub(crate) fn unregister_map(&self, ptr: *mut sys::mln_map) {
156 if !ptr.is_null() {
157 self.map_ids.borrow_mut().remove(&(ptr as usize));
158 self.map_states.borrow_mut().remove(&(ptr as usize));
159 }
160 }
161
162 fn apply_event_side_effects(&self, raw: &sys::mln_runtime_event) {
163 if raw.source_type != sys::MLN_RUNTIME_EVENT_SOURCE_MAP {
164 return;
165 }
166 let state = self
167 .map_states
168 .borrow()
169 .get(&(raw.source as usize))
170 .and_then(Weak::upgrade);
171 let Some(state) = state else {
172 return;
173 };
174 if raw.type_ == sys::MLN_RUNTIME_EVENT_MAP_STYLE_LOADED {
175 state.release_detached_custom_geometry_sources();
176 }
177 }
178
179 #[cfg(test)]
180 pub(crate) fn apply_event_side_effects_for_testing(&self, raw: &sys::mln_runtime_event) {
181 self.apply_event_side_effects(raw);
182 }
183
184 fn source_for_event(&self, raw: &sys::mln_runtime_event) -> RuntimeEventSource {
185 match raw.source_type {
186 sys::MLN_RUNTIME_EVENT_SOURCE_RUNTIME => RuntimeEventSource::Runtime,
187 sys::MLN_RUNTIME_EVENT_SOURCE_MAP => self
188 .map_ids
189 .borrow()
190 .get(&(raw.source as usize))
191 .copied()
192 .map(RuntimeEventSource::Map)
193 .unwrap_or(RuntimeEventSource::UnknownMap),
194 source_type => RuntimeEventSource::Unknown(source_type),
195 }
196 }
197}
198
199pub struct RuntimeHandle {
201 pub(crate) inner: Rc<RuntimeState>,
202}
203
204impl fmt::Debug for RuntimeHandle {
205 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
206 f.debug_struct("RuntimeHandle")
207 .field("closed", &self.inner.is_closed())
208 .finish()
209 }
210}
211
212pub struct OfflineOperationHandle<T> {
214 runtime: Rc<RuntimeState>,
215 operation_id: sys::mln_offline_operation_id,
216 operation_kind: maplibre_core::OfflineOperationKind,
217 result_kind: maplibre_core::OfflineOperationResultKind,
218 live: Cell<bool>,
219 _result: PhantomData<fn() -> T>,
220 _thread_affine: PhantomData<Rc<()>>,
221}
222
223impl<T> fmt::Debug for OfflineOperationHandle<T> {
224 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
225 f.debug_struct("OfflineOperationHandle")
226 .field("operation_id", &self.operation_id)
227 .field("operation_kind", &self.operation_kind)
228 .field("result_kind", &self.result_kind)
229 .field("live", &self.live.get())
230 .finish()
231 }
232}
233
234impl<T> OfflineOperationHandle<T> {
235 fn new(
236 runtime: Rc<RuntimeState>,
237 operation_id: sys::mln_offline_operation_id,
238 operation_kind: maplibre_core::OfflineOperationKind,
239 result_kind: maplibre_core::OfflineOperationResultKind,
240 ) -> Result<Self> {
241 if operation_id == 0 {
242 return Err(Error::invalid_argument(
243 "offline operation id must not be zero",
244 ));
245 }
246 Ok(Self {
247 runtime,
248 operation_id,
249 operation_kind,
250 result_kind,
251 live: Cell::new(true),
252 _result: PhantomData,
253 _thread_affine: PhantomData,
254 })
255 }
256
257 pub fn id(&self) -> u64 {
259 self.operation_id
260 }
261
262 pub fn operation_kind(&self) -> maplibre_core::OfflineOperationKind {
264 self.operation_kind
265 }
266
267 pub fn result_kind(&self) -> maplibre_core::OfflineOperationResultKind {
269 self.result_kind
270 }
271
272 pub fn is_live(&self) -> bool {
274 self.live.get()
275 }
276
277 fn runtime_ptr(&self) -> Result<*mut sys::mln_runtime> {
278 if !self.live.get() {
279 return Err(closed_handle_error("OfflineOperationHandle"));
280 }
281 self.runtime.as_ptr()
282 }
283
284 fn mark_consumed(&self) {
285 self.live.set(false);
286 }
287
288 #[allow(clippy::result_large_err)]
290 pub fn discard(self) -> std::result::Result<(), HandleOperationError<Self>> {
291 if !self.live.get() {
292 return Ok(());
293 }
294 let runtime = match self.runtime_ptr() {
295 Ok(runtime) => runtime,
296 Err(error) => return Err(HandleOperationError::new(error, self)),
297 };
298 let status =
299 unsafe { sys::mln_runtime_offline_operation_discard(runtime, self.operation_id) };
300 if let Err(error) = maplibre_core::check(status) {
301 return Err(HandleOperationError::new(error, self));
302 }
303 self.live.set(false);
304 Ok(())
305 }
306}
307
308impl<T> Drop for OfflineOperationHandle<T> {
309 fn drop(&mut self) {
310 if !self.live.get() {
311 return;
312 }
313 if let Ok(runtime) = self.runtime.as_ptr() {
314 let status =
316 unsafe { sys::mln_runtime_offline_operation_discard(runtime, self.operation_id) };
317 if status == sys::MLN_STATUS_OK {
318 self.live.set(false);
319 }
320 }
321 }
322}
323
324impl OfflineOperationHandle<OfflineRegionInfo> {
325 pub fn take(self) -> Result<OfflineRegionInfo> {
327 let runtime = self.runtime_ptr()?;
328 let mut out = maplibre_core::ptr::OutPtr::<sys::mln_offline_region_snapshot>::new();
329 let status = match self.operation_kind {
330 maplibre_core::OfflineOperationKind::RegionCreate => unsafe {
331 sys::mln_runtime_offline_region_create_take_result(
332 runtime,
333 self.operation_id,
334 out.as_mut_ptr(),
335 )
336 },
337 maplibre_core::OfflineOperationKind::RegionUpdateMetadata => unsafe {
338 sys::mln_runtime_offline_region_update_metadata_take_result(
339 runtime,
340 self.operation_id,
341 out.as_mut_ptr(),
342 )
343 },
344 _ => sys::MLN_STATUS_INVALID_STATE,
345 };
346 maplibre_core::check(status)?;
347 self.mark_consumed();
348 unsafe {
351 maplibre_core::runtime::copy_offline_region_snapshot(
352 out.into_non_null("mln_offline_region_snapshot")?,
353 )
354 }
355 }
356}
357
358impl OfflineOperationHandle<Option<OfflineRegionInfo>> {
359 pub fn take(self) -> Result<Option<OfflineRegionInfo>> {
361 let runtime = self.runtime_ptr()?;
362 let mut out = maplibre_core::ptr::OutPtr::<sys::mln_offline_region_snapshot>::new();
363 let mut found = false;
364 let status = unsafe {
365 sys::mln_runtime_offline_region_get_take_result(
366 runtime,
367 self.operation_id,
368 out.as_mut_ptr(),
369 &mut found,
370 )
371 };
372 maplibre_core::check(status)?;
373 self.mark_consumed();
374 if !found {
375 return Ok(None);
376 }
377 Ok(Some(unsafe {
380 maplibre_core::runtime::copy_offline_region_snapshot(
381 out.into_non_null("mln_offline_region_snapshot")?,
382 )
383 }?))
384 }
385}
386
387impl OfflineOperationHandle<Vec<OfflineRegionInfo>> {
388 pub fn take(self) -> Result<Vec<OfflineRegionInfo>> {
390 let runtime = self.runtime_ptr()?;
391 let mut out = maplibre_core::ptr::OutPtr::<sys::mln_offline_region_list>::new();
392 let status = match self.operation_kind {
393 maplibre_core::OfflineOperationKind::RegionsList => unsafe {
394 sys::mln_runtime_offline_regions_list_take_result(
395 runtime,
396 self.operation_id,
397 out.as_mut_ptr(),
398 )
399 },
400 maplibre_core::OfflineOperationKind::RegionsMergeDatabase => unsafe {
401 sys::mln_runtime_offline_regions_merge_database_take_result(
402 runtime,
403 self.operation_id,
404 out.as_mut_ptr(),
405 )
406 },
407 _ => sys::MLN_STATUS_INVALID_STATE,
408 };
409 maplibre_core::check(status)?;
410 self.mark_consumed();
411 unsafe {
414 maplibre_core::runtime::copy_offline_region_list(
415 out.into_non_null("mln_offline_region_list")?,
416 )
417 }
418 }
419}
420
421impl OfflineOperationHandle<OfflineRegionStatus> {
422 pub fn take(self) -> Result<OfflineRegionStatus> {
424 let runtime = self.runtime_ptr()?;
425 let mut raw = maplibre_core::events::empty_offline_region_status_native();
426 let status = unsafe {
427 sys::mln_runtime_offline_region_get_status_take_result(
428 runtime,
429 self.operation_id,
430 &mut raw,
431 )
432 };
433 maplibre_core::check(status)?;
434 self.mark_consumed();
435 Ok(maplibre_core::events::offline_region_status_from_native(
436 raw,
437 ))
438 }
439}
440
441impl RuntimeHandle {
442 pub fn new() -> Result<Self> {
444 maplibre_core::validate_abi_version()?;
445 Self::create_with_native_options_after_abi_validation(std::ptr::null())
446 }
447
448 pub fn with_options(options: &RuntimeOptions) -> Result<Self> {
450 let native_options = options.to_native()?;
451 let raw_options = native_options.to_raw();
452 Self::create_with_native_options_after_abi_validation(&raw_options)
453 }
454
455 fn create_with_native_options_after_abi_validation(
456 options: *const sys::mln_runtime_options,
457 ) -> Result<Self> {
458 let mut out = maplibre_core::ptr::OutPtr::<sys::mln_runtime>::new();
459 maplibre_core::check(unsafe { sys::mln_runtime_create(options, out.as_mut_ptr()) })?;
464 let ptr = out_handle(out, "mln_runtime")?;
465
466 Ok(Self {
467 inner: Rc::new(RuntimeState::new(ptr)),
468 })
469 }
470
471 pub fn create_map(&self) -> Result<MapHandle> {
473 MapHandle::new(self)
474 }
475
476 pub fn create_map_with_options(&self, options: &MapOptions) -> Result<MapHandle> {
478 MapHandle::with_options(self, options)
479 }
480
481 pub fn set_resource_provider<F>(&self, callback: F) -> Result<()>
493 where
494 F: Fn(crate::ResourceRequest, crate::ResourceRequestHandle) -> ResourceProviderDecision
495 + Send
496 + Sync
497 + 'static,
498 {
499 self.inner.set_resource_provider(callback)
500 }
501
502 pub fn set_resource_transform<F>(&self, callback: F) -> Result<()>
512 where
513 F: Fn(crate::ResourceTransformRequest) -> Option<String> + Send + Sync + 'static,
514 {
515 self.inner.set_resource_transform(callback)
516 }
517
518 pub fn clear_resource_transform(&self) -> Result<()> {
524 self.inner.clear_resource_transform()
525 }
526
527 fn start_operation<T>(
528 &self,
529 operation_id: sys::mln_offline_operation_id,
530 operation_kind: maplibre_core::OfflineOperationKind,
531 result_kind: maplibre_core::OfflineOperationResultKind,
532 ) -> Result<OfflineOperationHandle<T>> {
533 OfflineOperationHandle::new(
534 Rc::clone(&self.inner),
535 operation_id,
536 operation_kind,
537 result_kind,
538 )
539 }
540
541 pub fn start_ambient_cache_operation(
543 &self,
544 operation: AmbientCacheOperation,
545 ) -> Result<OfflineOperationHandle<()>> {
546 let runtime = self.inner.as_ptr()?;
547 let mut operation_id: sys::mln_offline_operation_id = 0;
548 maplibre_core::check(unsafe {
549 sys::mln_runtime_run_ambient_cache_operation_start(
550 runtime,
551 operation.to_native(),
552 &mut operation_id,
553 )
554 })?;
555 self.start_operation(
556 operation_id,
557 maplibre_core::OfflineOperationKind::AmbientCache,
558 maplibre_core::OfflineOperationResultKind::None,
559 )
560 }
561
562 pub fn start_create_offline_region(
564 &self,
565 definition: &OfflineRegionDefinition,
566 metadata: &[u8],
567 ) -> Result<OfflineOperationHandle<OfflineRegionInfo>> {
568 let runtime = self.inner.as_ptr()?;
569 let definition = definition.to_native()?;
570 let raw_definition = definition.to_raw();
571 let mut operation_id: sys::mln_offline_operation_id = 0;
572 maplibre_core::check(unsafe {
575 sys::mln_runtime_offline_region_create_start(
576 runtime,
577 &raw_definition,
578 maplibre_core::runtime::metadata_ptr(metadata),
579 metadata.len(),
580 &mut operation_id,
581 )
582 })?;
583 self.start_operation(
584 operation_id,
585 maplibre_core::OfflineOperationKind::RegionCreate,
586 maplibre_core::OfflineOperationResultKind::Region,
587 )
588 }
589
590 pub fn start_offline_region(
592 &self,
593 region_id: i64,
594 ) -> Result<OfflineOperationHandle<Option<OfflineRegionInfo>>> {
595 let runtime = self.inner.as_ptr()?;
596 let mut operation_id: sys::mln_offline_operation_id = 0;
597 maplibre_core::check(unsafe {
599 sys::mln_runtime_offline_region_get_start(runtime, region_id, &mut operation_id)
600 })?;
601 self.start_operation(
602 operation_id,
603 maplibre_core::OfflineOperationKind::RegionGet,
604 maplibre_core::OfflineOperationResultKind::OptionalRegion,
605 )
606 }
607
608 pub fn start_offline_regions(&self) -> Result<OfflineOperationHandle<Vec<OfflineRegionInfo>>> {
610 let runtime = self.inner.as_ptr()?;
611 let mut operation_id: sys::mln_offline_operation_id = 0;
612 maplibre_core::check(unsafe {
614 sys::mln_runtime_offline_regions_list_start(runtime, &mut operation_id)
615 })?;
616 self.start_operation(
617 operation_id,
618 maplibre_core::OfflineOperationKind::RegionsList,
619 maplibre_core::OfflineOperationResultKind::RegionList,
620 )
621 }
622
623 pub fn start_merge_offline_regions_database(
625 &self,
626 path: &str,
627 ) -> Result<OfflineOperationHandle<Vec<OfflineRegionInfo>>> {
628 let runtime = self.inner.as_ptr()?;
629 let path = maplibre_core::string::c_string(path)?;
630 let mut operation_id: sys::mln_offline_operation_id = 0;
631 maplibre_core::check(unsafe {
634 sys::mln_runtime_offline_regions_merge_database_start(
635 runtime,
636 path.as_ptr(),
637 &mut operation_id,
638 )
639 })?;
640 self.start_operation(
641 operation_id,
642 maplibre_core::OfflineOperationKind::RegionsMergeDatabase,
643 maplibre_core::OfflineOperationResultKind::RegionList,
644 )
645 }
646
647 pub fn start_update_offline_region_metadata(
649 &self,
650 region_id: i64,
651 metadata: &[u8],
652 ) -> Result<OfflineOperationHandle<OfflineRegionInfo>> {
653 let runtime = self.inner.as_ptr()?;
654 let mut operation_id: sys::mln_offline_operation_id = 0;
655 maplibre_core::check(unsafe {
658 sys::mln_runtime_offline_region_update_metadata_start(
659 runtime,
660 region_id,
661 maplibre_core::runtime::metadata_ptr(metadata),
662 metadata.len(),
663 &mut operation_id,
664 )
665 })?;
666 self.start_operation(
667 operation_id,
668 maplibre_core::OfflineOperationKind::RegionUpdateMetadata,
669 maplibre_core::OfflineOperationResultKind::Region,
670 )
671 }
672
673 pub fn start_offline_region_status(
675 &self,
676 region_id: i64,
677 ) -> Result<OfflineOperationHandle<OfflineRegionStatus>> {
678 let runtime = self.inner.as_ptr()?;
679 let mut operation_id: sys::mln_offline_operation_id = 0;
680 maplibre_core::check(unsafe {
682 sys::mln_runtime_offline_region_get_status_start(runtime, region_id, &mut operation_id)
683 })?;
684 self.start_operation(
685 operation_id,
686 maplibre_core::OfflineOperationKind::RegionGetStatus,
687 maplibre_core::OfflineOperationResultKind::RegionStatus,
688 )
689 }
690
691 pub fn start_set_offline_region_observed(
693 &self,
694 region_id: i64,
695 observed: bool,
696 ) -> Result<OfflineOperationHandle<()>> {
697 let runtime = self.inner.as_ptr()?;
698 let mut operation_id: sys::mln_offline_operation_id = 0;
699 maplibre_core::check(unsafe {
700 sys::mln_runtime_offline_region_set_observed_start(
701 runtime,
702 region_id,
703 observed,
704 &mut operation_id,
705 )
706 })?;
707 self.start_operation(
708 operation_id,
709 maplibre_core::OfflineOperationKind::RegionSetObserved,
710 maplibre_core::OfflineOperationResultKind::None,
711 )
712 }
713
714 pub fn start_set_offline_region_download_state(
716 &self,
717 region_id: i64,
718 state: OfflineRegionDownloadState,
719 ) -> Result<OfflineOperationHandle<()>> {
720 let runtime = self.inner.as_ptr()?;
721 let state = state.raw_for_set()?;
722 let mut operation_id: sys::mln_offline_operation_id = 0;
723 maplibre_core::check(unsafe {
724 sys::mln_runtime_offline_region_set_download_state_start(
725 runtime,
726 region_id,
727 state,
728 &mut operation_id,
729 )
730 })?;
731 self.start_operation(
732 operation_id,
733 maplibre_core::OfflineOperationKind::RegionSetDownloadState,
734 maplibre_core::OfflineOperationResultKind::None,
735 )
736 }
737
738 pub fn start_invalidate_offline_region(
740 &self,
741 region_id: i64,
742 ) -> Result<OfflineOperationHandle<()>> {
743 let runtime = self.inner.as_ptr()?;
744 let mut operation_id: sys::mln_offline_operation_id = 0;
745 maplibre_core::check(unsafe {
746 sys::mln_runtime_offline_region_invalidate_start(runtime, region_id, &mut operation_id)
747 })?;
748 self.start_operation(
749 operation_id,
750 maplibre_core::OfflineOperationKind::RegionInvalidate,
751 maplibre_core::OfflineOperationResultKind::None,
752 )
753 }
754
755 pub fn start_delete_offline_region(
757 &self,
758 region_id: i64,
759 ) -> Result<OfflineOperationHandle<()>> {
760 let runtime = self.inner.as_ptr()?;
761 let mut operation_id: sys::mln_offline_operation_id = 0;
762 maplibre_core::check(unsafe {
763 sys::mln_runtime_offline_region_delete_start(runtime, region_id, &mut operation_id)
764 })?;
765 self.start_operation(
766 operation_id,
767 maplibre_core::OfflineOperationKind::RegionDelete,
768 maplibre_core::OfflineOperationResultKind::None,
769 )
770 }
771
772 pub fn run_once(&self) -> Result<()> {
774 let runtime = self.inner.as_ptr()?;
775 maplibre_core::check(unsafe { sys::mln_runtime_run_once(runtime) })
777 }
778
779 pub fn poll_event(&self) -> Result<Option<RuntimeEvent>> {
781 let runtime = self.inner.as_ptr()?;
782 let mut event = empty_runtime_event();
783 let mut has_event = false;
784
785 maplibre_core::check(unsafe {
788 sys::mln_runtime_poll_event(runtime, &mut event, &mut has_event)
789 })?;
790 if !has_event {
791 return Ok(None);
792 }
793
794 let raw_event = event;
795 let source = self.inner.source_for_event(&raw_event);
796 let event = RuntimeEvent::from_native(&raw_event, source)?;
797 self.inner.apply_event_side_effects(&raw_event);
798 Ok(Some(event))
799 }
800
801 pub fn discard_one_event(&self) -> Result<bool> {
803 let runtime = self.inner.as_ptr()?;
804 let mut event = empty_runtime_event();
805 let mut has_event = false;
806
807 maplibre_core::check(unsafe {
812 sys::mln_runtime_poll_event(runtime, &mut event, &mut has_event)
813 })?;
814 if has_event {
815 self.inner.apply_event_side_effects(&event);
816 }
817 Ok(has_event)
818 }
819
820 pub fn drain_events(&self) -> Result<usize> {
822 let mut count = 0;
823 while self.discard_one_event()? {
824 count += 1;
825 }
826 Ok(count)
827 }
828
829 pub fn close(self) -> std::result::Result<(), HandleOperationError<Self>> {
835 if self.inner.is_closed() {
836 return Ok(());
837 }
838 if Rc::strong_count(&self.inner) > 1 {
839 return Err(HandleOperationError::new(
840 Error::new(
841 ErrorKind::InvalidState,
842 None,
843 "RuntimeHandle cannot close while child handles are live",
844 ),
845 self,
846 ));
847 }
848 self.inner
849 .close()
850 .map_err(|error| HandleOperationError::new(error, self))
851 }
852}
853
854#[cfg(test)]
855mod tests {
856 use std::sync::Arc;
857 use std::sync::atomic::{AtomicUsize, Ordering};
858 use std::time::{Duration, SystemTime, UNIX_EPOCH};
859
860 use super::*;
861 use crate::{
862 ErrorKind, OfflineOperationCompletedEvent, ResourceKind, ResourceProviderDecision,
863 ResourceResponse, RuntimeEventPayload, RuntimeEventSource, RuntimeEventType,
864 };
865
866 const PROVIDER_STYLE_JSON: &str = r#"{"version":8,"sources":{},"layers":[]}"#;
867
868 fn wait_for_operation<T>(
869 runtime: &RuntimeHandle,
870 operation: &OfflineOperationHandle<T>,
871 ) -> Result<OfflineOperationCompletedEvent> {
872 loop {
873 runtime.run_once()?;
874 while let Some(event) = runtime.poll_event()? {
875 let RuntimeEventPayload::OfflineOperationCompleted(completed) = event.payload
876 else {
877 continue;
878 };
879 if completed.operation_id != operation.id() {
880 continue;
881 }
882 assert_eq!(completed.operation_kind, operation.operation_kind());
883 assert_eq!(
884 completed.raw_operation_kind,
885 operation.operation_kind().raw_value()
886 );
887 assert_eq!(completed.result_kind, operation.result_kind());
888 assert_eq!(
889 completed.raw_result_kind,
890 operation.result_kind().raw_value()
891 );
892 if completed.result_status != sys::MLN_STATUS_OK {
893 return Err(Error::from_status_and_diagnostic(
894 completed.result_status,
895 event.message.unwrap_or_default(),
896 ));
897 }
898 return Ok(completed);
899 }
900 std::thread::sleep(Duration::from_millis(1));
901 }
902 }
903
904 #[test]
905 fn runtime_ambient_cache_operations_use_real_c_abi() {
906 let base = TempDir::new("maplibre-rust-ambient-cache");
907 let cache = base.path().join("ambient.db");
908
909 let runtime = RuntimeHandle::with_options(
910 &RuntimeOptions::new()
911 .with_cache_path(cache.to_string_lossy())
912 .with_maximum_cache_size(0),
913 )
914 .unwrap();
915
916 for operation in [
917 AmbientCacheOperation::PackDatabase,
918 AmbientCacheOperation::Invalidate,
919 AmbientCacheOperation::Clear,
920 AmbientCacheOperation::ResetDatabase,
921 ] {
922 let operation = runtime.start_ambient_cache_operation(operation).unwrap();
923 let completed = wait_for_operation(&runtime, &operation).unwrap();
924 assert_eq!(completed.operation_id, operation.id());
925 operation.discard().unwrap();
926 }
927
928 runtime.close().unwrap();
929 }
930
931 #[test]
932 fn offline_region_apis_use_real_c_abi() {
933 let runtime =
934 RuntimeHandle::with_options(&RuntimeOptions::new().with_cache_path(":memory:"))
935 .unwrap();
936 let definition = test_offline_region_definition("custom://offline-style.json");
937
938 let create = runtime
939 .start_create_offline_region(&definition, b"abc")
940 .unwrap();
941 wait_for_operation(&runtime, &create).unwrap();
942 let created = create.take().unwrap();
943 assert_eq!(created.definition, definition);
944 assert_eq!(created.metadata, b"abc");
945
946 let geometry_definition = OfflineRegionDefinition::GeometryRegion {
947 style_url: "custom://offline-geometry-style.json".into(),
948 geometry: Geometry::Point(crate::LatLng::new(37.5, -122.5)),
949 min_zoom: 0.0,
950 max_zoom: 1.0,
951 pixel_ratio: 1.0,
952 include_ideographs: false,
953 };
954 let create_geometry = runtime
955 .start_create_offline_region(&geometry_definition, b"geo")
956 .unwrap();
957 wait_for_operation(&runtime, &create_geometry).unwrap();
958 let geometry_region = create_geometry.take().unwrap();
959 assert_eq!(geometry_region.definition, geometry_definition);
960 assert_eq!(geometry_region.metadata, b"geo");
961
962 let get = runtime.start_offline_region(created.id).unwrap();
963 wait_for_operation(&runtime, &get).unwrap();
964 let fetched = get.take().unwrap().unwrap();
965 assert_eq!(fetched, created);
966
967 let list = runtime.start_offline_regions().unwrap();
968 wait_for_operation(&runtime, &list).unwrap();
969 let listed = list.take().unwrap();
970 assert!(listed.iter().any(|region| region.id == created.id));
971
972 let update = runtime
973 .start_update_offline_region_metadata(created.id, b"")
974 .unwrap();
975 wait_for_operation(&runtime, &update).unwrap();
976 let updated = update.take().unwrap();
977 assert_eq!(updated.id, created.id);
978 assert!(updated.metadata.is_empty());
979
980 let status_operation = runtime.start_offline_region_status(created.id).unwrap();
981 wait_for_operation(&runtime, &status_operation).unwrap();
982 let status = status_operation.take().unwrap();
983 assert!(matches!(
984 status.download_state,
985 OfflineRegionDownloadState::Inactive | OfflineRegionDownloadState::Active
986 ));
987
988 let set_inactive = runtime
989 .start_set_offline_region_download_state(
990 created.id,
991 OfflineRegionDownloadState::Inactive,
992 )
993 .unwrap();
994 wait_for_operation(&runtime, &set_inactive).unwrap();
995 set_inactive.discard().unwrap();
996 let error = runtime
997 .start_set_offline_region_download_state(
998 created.id,
999 OfflineRegionDownloadState::Unknown(99),
1000 )
1001 .unwrap_err();
1002 assert_eq!(error.kind(), ErrorKind::InvalidArgument);
1003
1004 let observe = runtime
1005 .start_set_offline_region_observed(created.id, true)
1006 .unwrap();
1007 wait_for_operation(&runtime, &observe).unwrap();
1008 observe.discard().unwrap();
1009 let unobserve = runtime
1010 .start_set_offline_region_observed(created.id, false)
1011 .unwrap();
1012 wait_for_operation(&runtime, &unobserve).unwrap();
1013 unobserve.discard().unwrap();
1014 let invalidate = runtime.start_invalidate_offline_region(created.id).unwrap();
1015 wait_for_operation(&runtime, &invalidate).unwrap();
1016 invalidate.discard().unwrap();
1017 let delete = runtime.start_delete_offline_region(created.id).unwrap();
1018 wait_for_operation(&runtime, &delete).unwrap();
1019 delete.discard().unwrap();
1020 let delete_geometry = runtime
1021 .start_delete_offline_region(geometry_region.id)
1022 .unwrap();
1023 wait_for_operation(&runtime, &delete_geometry).unwrap();
1024 delete_geometry.discard().unwrap();
1025
1026 let missing_created = runtime.start_offline_region(created.id).unwrap();
1027 wait_for_operation(&runtime, &missing_created).unwrap();
1028 assert!(missing_created.take().unwrap().is_none());
1029 let missing_geometry = runtime.start_offline_region(geometry_region.id).unwrap();
1030 wait_for_operation(&runtime, &missing_geometry).unwrap();
1031 assert!(missing_geometry.take().unwrap().is_none());
1032
1033 runtime.close().unwrap();
1034 }
1035
1036 #[test]
1037 fn offline_region_merge_database_uses_real_c_abi() {
1038 let base = TempDir::new("maplibre-rust-offline-merge");
1039 let main_cache = base.path().join("main.db");
1040 let side_cache = base.path().join("side.db");
1041
1042 let definition = test_offline_region_definition("custom://merge-style.json");
1043 {
1044 let side_runtime = RuntimeHandle::with_options(
1045 &RuntimeOptions::new().with_cache_path(side_cache.to_string_lossy()),
1046 )
1047 .unwrap();
1048 let create = side_runtime
1049 .start_create_offline_region(&definition, b"merge")
1050 .unwrap();
1051 wait_for_operation(&side_runtime, &create).unwrap();
1052 create.take().unwrap();
1053 side_runtime.close().unwrap();
1054 }
1055
1056 let main_runtime = RuntimeHandle::with_options(
1057 &RuntimeOptions::new().with_cache_path(main_cache.to_string_lossy()),
1058 )
1059 .unwrap();
1060 let merge = main_runtime
1061 .start_merge_offline_regions_database(&side_cache.to_string_lossy())
1062 .unwrap();
1063 wait_for_operation(&main_runtime, &merge).unwrap();
1064 let merged = merge.take().unwrap();
1065 assert_eq!(merged.len(), 1);
1066 assert_eq!(merged[0].definition, definition);
1067 assert_eq!(merged[0].metadata, b"merge");
1068 main_runtime.close().unwrap();
1069 }
1070
1071 fn test_offline_region_definition(style_url: &str) -> OfflineRegionDefinition {
1072 OfflineRegionDefinition::TilePyramid {
1073 style_url: style_url.into(),
1074 bounds: LatLngBounds::new(
1075 crate::LatLng::new(37.0, -123.0),
1076 crate::LatLng::new(38.0, -122.0),
1077 ),
1078 min_zoom: 0.0,
1079 max_zoom: 1.0,
1080 pixel_ratio: 1.0,
1081 include_ideographs: false,
1082 }
1083 }
1084
1085 struct TempDir {
1086 path: std::path::PathBuf,
1087 }
1088
1089 impl TempDir {
1090 fn new(prefix: &str) -> Self {
1091 let nanos = SystemTime::now()
1092 .duration_since(UNIX_EPOCH)
1093 .unwrap()
1094 .as_nanos();
1095 let path =
1096 std::env::temp_dir().join(format!("{prefix}-{}-{nanos}", std::process::id()));
1097 std::fs::create_dir_all(&path).unwrap();
1098 Self { path }
1099 }
1100
1101 fn path(&self) -> &std::path::Path {
1102 &self.path
1103 }
1104 }
1105
1106 impl Drop for TempDir {
1107 fn drop(&mut self) {
1108 let _ = std::fs::remove_dir_all(&self.path);
1109 }
1110 }
1111
1112 #[test]
1113 fn runtime_create_with_explicit_options_uses_real_c_abi() {
1114 let runtime = RuntimeHandle::with_options(
1115 &RuntimeOptions::new()
1116 .with_asset_path("")
1117 .with_cache_path("")
1118 .with_maximum_cache_size(0),
1119 )
1120 .unwrap();
1121
1122 runtime.run_once().unwrap();
1123 runtime.close().unwrap();
1124 }
1125
1126 fn wait_for_runtime_event(runtime: &RuntimeHandle, event_type: RuntimeEventType) -> bool {
1127 for _ in 0..100 {
1128 let _ = runtime.run_once();
1129 while let Ok(Some(event)) = runtime.poll_event() {
1130 if event.event_type == event_type {
1131 return true;
1132 }
1133 }
1134 std::thread::sleep(Duration::from_millis(10));
1135 }
1136 false
1137 }
1138
1139 #[test]
1140 fn runtime_create_run_poll_drain_and_close() {
1141 let runtime = RuntimeHandle::new().unwrap();
1142
1143 runtime.run_once().unwrap();
1144 let _ = runtime.poll_event().unwrap();
1145 let _ = runtime.discard_one_event().unwrap();
1146 runtime.drain_events().unwrap();
1147 runtime.close().unwrap();
1148 }
1149
1150 #[test]
1151 fn resource_provider_installs_replaces_and_releases_state() {
1152 let runtime = RuntimeHandle::new().unwrap();
1153 let first = Arc::new(());
1154 let first_callback = Arc::clone(&first);
1155
1156 runtime
1157 .set_resource_provider(move |_, _| {
1158 let _ = &first_callback;
1159 crate::ResourceProviderDecision::PassThrough
1160 })
1161 .unwrap();
1162 assert_eq!(Arc::strong_count(&first), 2);
1163
1164 let second = Arc::new(());
1165 let second_callback = Arc::clone(&second);
1166 runtime
1167 .set_resource_provider(move |_, _| {
1168 let _ = &second_callback;
1169 crate::ResourceProviderDecision::PassThrough
1170 })
1171 .unwrap();
1172 assert_eq!(Arc::strong_count(&first), 1);
1173 assert_eq!(Arc::strong_count(&second), 2);
1174
1175 runtime.close().unwrap();
1176 assert_eq!(Arc::strong_count(&second), 1);
1177 }
1178
1179 #[test]
1180 fn resource_provider_replacement_rolls_back_when_native_install_fails() {
1181 let runtime = RuntimeHandle::new().unwrap();
1182 let first = Arc::new(());
1183 let first_callback = Arc::clone(&first);
1184 runtime
1185 .set_resource_provider(move |_, _| {
1186 let _ = &first_callback;
1187 crate::ResourceProviderDecision::PassThrough
1188 })
1189 .unwrap();
1190 let map = runtime.create_map().unwrap();
1191
1192 let second = Arc::new(());
1193 let second_callback = Arc::clone(&second);
1194 let error = runtime
1195 .set_resource_provider(move |_, _| {
1196 let _ = &second_callback;
1197 crate::ResourceProviderDecision::PassThrough
1198 })
1199 .unwrap_err();
1200
1201 assert_eq!(error.kind(), ErrorKind::InvalidState);
1202 assert_eq!(Arc::strong_count(&first), 2);
1203 assert_eq!(Arc::strong_count(&second), 1);
1204
1205 map.close().unwrap();
1206 runtime.close().unwrap();
1207 assert_eq!(Arc::strong_count(&first), 1);
1208 }
1209
1210 #[test]
1211 fn resource_provider_rejects_install_after_map_was_closed() {
1212 let runtime = RuntimeHandle::new().unwrap();
1213 let map = runtime.create_map().unwrap();
1214 map.close().unwrap();
1215
1216 let error = runtime
1217 .set_resource_provider(|_, _| ResourceProviderDecision::PassThrough)
1218 .unwrap_err();
1219
1220 assert_eq!(error.kind(), ErrorKind::InvalidState);
1221 assert_eq!(error.raw_status(), None);
1222 runtime.close().unwrap();
1223 }
1224
1225 #[test]
1226 fn resource_provider_completes_style_request_inline_through_c_abi() {
1227 let runtime = RuntimeHandle::new().unwrap();
1228 let calls = Arc::new(AtomicUsize::new(0));
1229 let callback_calls = Arc::clone(&calls);
1230 runtime
1231 .set_resource_provider(move |request, handle| {
1232 if request.url != "custom://style.json" {
1233 return ResourceProviderDecision::PassThrough;
1234 }
1235 callback_calls.fetch_add(1, Ordering::SeqCst);
1236 assert_eq!(request.kind, ResourceKind::Style);
1237 handle
1238 .complete(ResourceResponse::ok(
1239 PROVIDER_STYLE_JSON.as_bytes().to_vec(),
1240 ))
1241 .unwrap();
1242 ResourceProviderDecision::PassThrough
1243 })
1244 .unwrap();
1245
1246 let map = runtime.create_map().unwrap();
1247 map.set_style_url("custom://style.json").unwrap();
1248
1249 assert!(wait_for_runtime_event(
1250 &runtime,
1251 RuntimeEventType::MapStyleLoaded
1252 ));
1253 assert_eq!(calls.load(Ordering::SeqCst), 1);
1254 map.close().unwrap();
1255 runtime.close().unwrap();
1256 }
1257
1258 #[test]
1259 fn resource_provider_completes_style_request_from_another_thread() {
1260 let runtime = RuntimeHandle::new().unwrap();
1261 let (sender, receiver) = std::sync::mpsc::channel();
1262 runtime
1263 .set_resource_provider(move |request, handle| {
1264 if request.url == "custom://async-style.json" {
1265 sender.send(handle).unwrap();
1266 ResourceProviderDecision::Handle
1267 } else {
1268 ResourceProviderDecision::PassThrough
1269 }
1270 })
1271 .unwrap();
1272
1273 let map = runtime.create_map().unwrap();
1274 map.set_style_url("custom://async-style.json").unwrap();
1275 let handle = receiver
1276 .recv_timeout(Duration::from_secs(5))
1277 .expect("provider should send handled request");
1278 assert!(!handle.is_cancelled().unwrap());
1279 std::thread::spawn(move || {
1280 handle
1281 .complete(ResourceResponse::ok(
1282 PROVIDER_STYLE_JSON.as_bytes().to_vec(),
1283 ))
1284 .unwrap();
1285 })
1286 .join()
1287 .unwrap();
1288
1289 assert!(wait_for_runtime_event(
1290 &runtime,
1291 RuntimeEventType::MapStyleLoaded
1292 ));
1293 map.close().unwrap();
1294 runtime.close().unwrap();
1295 }
1296
1297 #[test]
1298 fn resource_transform_installs_replaces_clears_and_releases_state() {
1299 let runtime = RuntimeHandle::new().unwrap();
1300 let first = Arc::new(());
1301 let first_callback = Arc::clone(&first);
1302
1303 runtime
1304 .set_resource_transform(move |request| {
1305 let _ = &first_callback;
1306 assert!(matches!(
1307 request.kind,
1308 ResourceKind::Style | ResourceKind::UnknownRaw(_)
1309 ));
1310 None
1311 })
1312 .unwrap();
1313 assert_eq!(Arc::strong_count(&first), 2);
1314
1315 let second = Arc::new(());
1316 let second_callback = Arc::clone(&second);
1317 runtime
1318 .set_resource_transform(move |_| {
1319 let _ = &second_callback;
1320 Some("https://example.test/replacement".to_owned())
1321 })
1322 .unwrap();
1323 assert_eq!(Arc::strong_count(&first), 1);
1324 assert_eq!(Arc::strong_count(&second), 2);
1325
1326 runtime.clear_resource_transform().unwrap();
1327 assert_eq!(Arc::strong_count(&second), 1);
1328 runtime.close().unwrap();
1329 }
1330
1331 #[test]
1332 fn resource_transform_replacement_after_map_creation_releases_previous_state() {
1333 let runtime = RuntimeHandle::new().unwrap();
1334 let first = Arc::new(());
1335 let first_callback = Arc::clone(&first);
1336 runtime
1337 .set_resource_transform(move |_| {
1338 let _ = &first_callback;
1339 None
1340 })
1341 .unwrap();
1342 let map = runtime.create_map().unwrap();
1343
1344 let second = Arc::new(());
1345 let second_callback = Arc::clone(&second);
1346 runtime
1347 .set_resource_transform(move |_| {
1348 let _ = &second_callback;
1349 None
1350 })
1351 .unwrap();
1352
1353 assert_eq!(Arc::strong_count(&first), 1);
1354 assert_eq!(Arc::strong_count(&second), 2);
1355
1356 map.close().unwrap();
1357 runtime.close().unwrap();
1358 assert_eq!(Arc::strong_count(&second), 1);
1359 }
1360
1361 #[test]
1362 fn runtime_teardown_releases_resource_transform_state() {
1363 let runtime = RuntimeHandle::new().unwrap();
1364 let token = Arc::new(());
1365 let callback_token = Arc::clone(&token);
1366 runtime
1367 .set_resource_transform(move |_| {
1368 let _ = &callback_token;
1369 None
1370 })
1371 .unwrap();
1372 assert_eq!(Arc::strong_count(&token), 2);
1373
1374 runtime.close().unwrap();
1375
1376 assert_eq!(Arc::strong_count(&token), 1);
1377 }
1378
1379 #[test]
1380 fn resource_transform_installs_after_map_creation() {
1381 let runtime = RuntimeHandle::new().unwrap();
1382 let map = runtime.create_map().unwrap();
1383
1384 runtime.set_resource_transform(|_| None).unwrap();
1385
1386 map.close().unwrap();
1387 runtime.close().unwrap();
1388 }
1389
1390 #[test]
1391 fn resource_transform_clears_after_map_was_closed_and_releases_state() {
1392 let runtime = RuntimeHandle::new().unwrap();
1393 let token = Arc::new(());
1394 let callback_token = Arc::clone(&token);
1395 runtime
1396 .set_resource_transform(move |_| {
1397 let _ = &callback_token;
1398 None
1399 })
1400 .unwrap();
1401 assert_eq!(Arc::strong_count(&token), 2);
1402
1403 let map = runtime.create_map().unwrap();
1404 map.close().unwrap();
1405
1406 runtime.clear_resource_transform().unwrap();
1407
1408 assert_eq!(Arc::strong_count(&token), 1);
1409
1410 runtime.close().unwrap();
1411 }
1412
1413 #[test]
1414 fn poll_event_returns_owned_map_event_and_source_id() {
1415 let runtime = RuntimeHandle::new().unwrap();
1416 let map = runtime.create_map().unwrap();
1417 let map_id = map.id();
1418
1419 let error = map.set_style_json("{").unwrap_err();
1420 assert!(matches!(
1421 error.kind(),
1422 ErrorKind::InvalidArgument | ErrorKind::NativeError
1423 ));
1424
1425 let mut loading_failed = None;
1426 for _ in 0..8 {
1427 let Some(event) = runtime.poll_event().unwrap() else {
1428 break;
1429 };
1430 if event.event_type == RuntimeEventType::MapLoadingFailed {
1431 loading_failed = Some(event);
1432 break;
1433 }
1434 }
1435 let event = loading_failed.expect("malformed style should enqueue loading-failed event");
1436 let copied_message = event.message.clone();
1437
1438 let _ = runtime.poll_event().unwrap();
1439
1440 assert_eq!(event.source, RuntimeEventSource::Map(map_id));
1441 assert_eq!(event.event_type, RuntimeEventType::MapLoadingFailed);
1442 assert_eq!(event.message, copied_message);
1443 assert!(
1444 event
1445 .message
1446 .as_deref()
1447 .is_some_and(|message| !message.is_empty())
1448 );
1449
1450 map.close().unwrap();
1451 runtime.close().unwrap();
1452 }
1453
1454 #[test]
1455 fn runtime_close_with_live_map_is_rust_invalid_state_and_retryable() {
1456 let runtime = RuntimeHandle::new().unwrap();
1457 let map = runtime.create_map().unwrap();
1458
1459 let error = runtime.close().unwrap_err();
1460 assert_eq!(error.kind(), ErrorKind::InvalidState);
1461 assert_eq!(error.raw_status(), None);
1462 let runtime = error.into_handle();
1463
1464 runtime.run_once().unwrap();
1465 map.close().unwrap();
1466 runtime.close().unwrap();
1467 }
1468}