Skip to main content

maplibre_native_ffi/
lib.rs

1//! Safe Rust binding for the MapLibre Native C API.
2//!
3//! This crate owns Rust-specific ergonomics and safety policy: thread-affine
4//! public handles, parent retention, owner-thread `Drop`, Rust errors,
5//! callback closure APIs, and lifetime-scoped render resources. Shared C ABI
6//! adaptation lives in `maplibre-native-ffi-core`.
7
8#![deny(unsafe_op_in_unsafe_fn)]
9
10mod camera;
11mod custom_geometry;
12mod custom_mvt_vector;
13mod events;
14mod geojson;
15mod handle;
16mod logging;
17mod map;
18mod options;
19mod projection;
20mod render;
21mod resource;
22mod runtime;
23mod values;
24
25use crate::values::NativeValue;
26use maplibre_native_ffi_core as maplibre_core;
27use maplibre_native_ffi_sys as sys;
28
29pub use camera::{
30    AnimationOptions, BoundOptions, BoundsConstraint, CameraFitOptions, CameraOptions,
31    FreeCameraOptions, ProjectionMode,
32};
33pub use custom_geometry::{CanonicalTileId, CustomGeometrySourceOptions};
34pub use custom_mvt_vector::CustomMvtVectorSourceOptions;
35pub use events::{
36    CameraTransitionFinishedEvent, MapId, OfflineOperationCompletedEvent,
37    OfflineRegionResponseErrorEvent, OfflineRegionStatus, OfflineRegionStatusEvent,
38    OfflineRegionTileCountLimitEvent, RenderFrameEvent, RenderMapEvent, RenderingStats,
39    RuntimeEvent, RuntimeEventBatch, RuntimeEventPayload, RuntimeEventRef, RuntimeEventSource,
40    TileActionEvent, TileId, UnknownRuntimeEventPayload,
41};
42pub use geojson::GeoJsonSourceDataHandle;
43pub use logging::{LogRecord, clear_log_callback, set_async_log_severity_mask, set_log_callback};
44pub use map::{
45    GeoJsonSourceOptions, ImageContent, ImageStretch, LocationIndicatorImageKind, MapAttachRef,
46    MapHandle, RasterDemEncoding, SourceInfo, SourceType, StyleImage, StyleImageInfo,
47    StyleImageOptions, StyleImageTextFit, StyleLayerVisibility, StyleTransitionOptions,
48    TileJsonInfo, TileScheme, TileSourceOptions, VectorTileEncoding,
49};
50pub use maplibre_core::{
51    AmbientCacheOperation, CameraChangeMode, ConstrainMode, Error, ErrorKind, LogEvent,
52    LogSeverity, LogSeverityMask, MapDebugOptions, MapMode, MapOptions, MapTileOptions,
53    MapViewportOptions, NetworkStatus, NorthOrientation, OfflineOperationKind,
54    OfflineOperationResultKind, OfflineRegionDownloadState, OpenGLClientApi,
55    OpenGLContextOwnership, OpenGLContextProviderMask, RenderBackendMask, RenderMode, RenderResult,
56    ResourceErrorReason, ResourceKind, ResourceLoadingMethod, ResourcePriority,
57    ResourceResponseStatus, ResourceStoragePolicy, ResourceUsage, Result, RuntimeEventMask,
58    RuntimeEventType, TileLodMode, TileOperation, ViewportMode,
59};
60pub use maplibre_native_ffi_core::handle::{NativeHandleLeak, set_leak_reporter};
61pub use projection::MapProjectionHandle;
62pub use render::{
63    DetachedRenderSessionHandle, EglContextDescriptor, FeatureStateSelector, FrameNativePointer,
64    FrameOpenGLTextureName, FrameVulkanHandle, MetalBorrowedTextureDescriptor,
65    MetalContextDescriptor, MetalOwnedTextureDescriptor, MetalOwnedTextureFrame,
66    MetalOwnedTextureFrameHandle, MetalSurfaceDescriptor, NativePointer,
67    OpenGLBorrowedTextureDescriptor, OpenGLContextDescriptor, OpenGLOwnedTextureDescriptor,
68    OpenGLOwnedTextureFrame, OpenGLOwnedTextureFrameHandle, OpenGLSurfaceDescriptor,
69    PremultipliedRgba8Image, QueriedFeature, RenderSessionHandle, RenderTargetExtent, RenderUpdate,
70    RenderedFeatureQueryOptions, RenderedQueryGeometry, SourceFeatureQueryOptions,
71    TextureImageInfo, VulkanBorrowedTextureDescriptor, VulkanContextDescriptor, VulkanHandle,
72    VulkanOwnedTextureDescriptor, VulkanOwnedTextureFrame, VulkanOwnedTextureFrameHandle,
73    VulkanSurfaceDescriptor, WebGlContextDescriptor, WebGpuBorrowedTextureDescriptor,
74    WebGpuContextDescriptor, WebGpuOwnedTextureDescriptor, WebGpuOwnedTextureFrame,
75    WebGpuOwnedTextureFrameHandle, WebGpuSurfaceDescriptor, WglContextDescriptor,
76};
77pub use resource::{
78    ByteRange, HttpHeader, HttpHeaderTransformRequest, ResourceProviderDecision, ResourceRequest,
79    ResourceRequestHandle, ResourceResponse, ResourceTransformRequest,
80};
81pub use runtime::{
82    OfflineOperationHandle, OfflineRegionDefinition, OfflineRegionInfo, RuntimeHandle,
83    RuntimeOptions, WakeSource,
84};
85pub use values::{
86    EdgeInsets, LatLng, LatLngBounds, ProjectedMeters, Quaternion, ScreenBox, ScreenPoint,
87    UnitBezier, Vec3,
88};
89
90/// Error returned by consuming one-shot handle operations when the handle
91/// remains live and the operation can be retried.
92#[derive(Debug)]
93pub struct HandleOperationError<T> {
94    error: Error,
95    handle: T,
96}
97
98impl<T> HandleOperationError<T> {
99    pub(crate) fn new(error: Error, handle: T) -> Self {
100        Self { error, handle }
101    }
102
103    /// Returns the operation error.
104    pub fn error(&self) -> &Error {
105        &self.error
106    }
107
108    /// Returns the stable category for the operation error.
109    pub fn kind(&self) -> ErrorKind {
110        self.error.kind()
111    }
112
113    /// Returns the raw C status for native operation errors, when available.
114    pub fn raw_status(&self) -> Option<i32> {
115        self.error.raw_status()
116    }
117
118    /// Returns the copied diagnostic message for the operation error.
119    pub fn diagnostic(&self) -> &str {
120        self.error.diagnostic()
121    }
122
123    /// Returns the operation error, dropping the still-live handle.
124    pub fn into_error(self) -> Error {
125        self.error
126    }
127
128    /// Returns the still-live handle so the operation can be retried.
129    pub fn into_handle(self) -> T {
130        self.handle
131    }
132
133    /// Splits this error into the operation error and still-live handle.
134    pub fn into_parts(self) -> (Error, T) {
135        (self.error, self.handle)
136    }
137}
138
139impl<T> std::fmt::Display for HandleOperationError<T> {
140    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
141        self.error.fmt(f)
142    }
143}
144
145impl<T: std::fmt::Debug> std::error::Error for HandleOperationError<T> {}
146
147/// Error returned by offline operation result transfers.
148#[derive(Debug)]
149pub enum OfflineOperationTakeError<T> {
150    /// The native transfer failed before consuming the operation result.
151    Retryable(HandleOperationError<T>),
152    /// The native result was consumed, but copying it into Rust-owned data failed.
153    Consumed(Error),
154}
155
156impl<T> OfflineOperationTakeError<T> {
157    pub(crate) fn retryable(error: Error, handle: T) -> Self {
158        Self::Retryable(HandleOperationError::new(error, handle))
159    }
160
161    pub(crate) fn consumed(error: Error) -> Self {
162        Self::Consumed(error)
163    }
164
165    /// Returns the operation error.
166    pub fn error(&self) -> &Error {
167        match self {
168            Self::Retryable(error) => error.error(),
169            Self::Consumed(error) => error,
170        }
171    }
172
173    /// Returns the stable category for the operation error.
174    pub fn kind(&self) -> ErrorKind {
175        self.error().kind()
176    }
177
178    /// Returns the raw C status for native operation errors, when available.
179    pub fn raw_status(&self) -> Option<i32> {
180        self.error().raw_status()
181    }
182
183    /// Returns the copied diagnostic message for the operation error.
184    pub fn diagnostic(&self) -> &str {
185        self.error().diagnostic()
186    }
187
188    /// Returns the retryable error and still-live handle, if the operation was not consumed.
189    pub fn into_retryable(self) -> Option<HandleOperationError<T>> {
190        match self {
191            Self::Retryable(error) => Some(error),
192            Self::Consumed(_) => None,
193        }
194    }
195
196    /// Returns the operation error, dropping any retryable handle.
197    pub fn into_error(self) -> Error {
198        match self {
199            Self::Retryable(error) => error.into_error(),
200            Self::Consumed(error) => error,
201        }
202    }
203}
204
205impl<T> std::fmt::Display for OfflineOperationTakeError<T> {
206    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
207        self.error().fmt(f)
208    }
209}
210
211impl<T: std::fmt::Debug> std::error::Error for OfflineOperationTakeError<T> {}
212
213/// Returns the native C ABI contract version.
214pub fn c_version() -> u32 {
215    // SAFETY: mln_c_version takes no arguments and returns the process-global C
216    // ABI version for the linked native library.
217    unsafe { sys::mln_c_version() }
218}
219
220/// Returns the render backends compiled into the linked native library.
221pub fn supported_render_backends() -> RenderBackendMask {
222    // SAFETY: mln_supported_render_backend_mask takes no arguments and returns
223    // a value mask.
224    let mask = unsafe { sys::mln_supported_render_backend_mask() };
225    RenderBackendMask::from_bits_retain(mask)
226}
227
228/// Returns the OpenGL context providers compiled into the linked native library.
229pub fn supported_opengl_context_providers() -> OpenGLContextProviderMask {
230    // SAFETY: mln_opengl_supported_context_provider_mask takes no arguments and
231    // returns a value mask.
232    let mask = unsafe { sys::mln_opengl_supported_context_provider_mask() };
233    OpenGLContextProviderMask::from_bits_retain(mask)
234}
235
236/// Converts a geographic coordinate to Spherical Mercator projected meters.
237pub fn projected_meters_for_lat_lng(coordinate: LatLng) -> Result<ProjectedMeters> {
238    let mut raw_meters = sys::mln_projected_meters {
239        northing: 0.0,
240        easting: 0.0,
241    };
242    // SAFETY: coordinate is passed by value. out_meters points to valid
243    // writable storage for one projected-meter value.
244    maplibre_core::check(unsafe {
245        sys::mln_projected_meters_for_lat_lng(coordinate.to_native(), &mut raw_meters)
246    })?;
247    Ok(ProjectedMeters::from_native(raw_meters))
248}
249
250/// Converts Spherical Mercator projected meters to a geographic coordinate.
251pub fn lat_lng_for_projected_meters(meters: ProjectedMeters) -> Result<LatLng> {
252    let mut raw_coordinate = sys::mln_lat_lng {
253        latitude: 0.0,
254        longitude: 0.0,
255    };
256    // SAFETY: meters is passed by value. out_coordinate points to valid
257    // writable storage for one coordinate value.
258    maplibre_core::check(unsafe {
259        sys::mln_lat_lng_for_projected_meters(meters.to_native(), &mut raw_coordinate)
260    })?;
261    Ok(LatLng::from_native(raw_coordinate))
262}
263
264/// Reads MapLibre Native's process-global network status.
265pub fn network_status() -> Result<NetworkStatus> {
266    maplibre_core::network_status()
267}
268
269/// Sets MapLibre Native's process-global network status.
270pub fn set_network_status(status: NetworkStatus) -> Result<()> {
271    maplibre_core::set_network_status(status)
272}
273
274#[cfg(test)]
275fn set_network_status_raw(raw_status: u32) -> Result<()> {
276    maplibre_core::set_network_status_raw(raw_status)
277}
278
279#[cfg(test)]
280mod tests {
281    use static_assertions::{assert_impl_all, assert_not_impl_any};
282
283    use super::*;
284
285    assert_not_impl_any!(RuntimeHandle: Send, Sync);
286    assert_not_impl_any!(MapHandle: Send, Sync);
287    assert_impl_all!(MapProjectionHandle: Send, Sync);
288    assert_not_impl_any!(NativePointer: Send, Sync);
289    assert_not_impl_any!(FrameNativePointer<'static>: Send, Sync);
290    assert_not_impl_any!(VulkanHandle: Send, Sync);
291    assert_not_impl_any!(FrameVulkanHandle<'static>: Send, Sync);
292    assert_not_impl_any!(RenderSessionHandle: Send, Sync);
293    assert_not_impl_any!(DetachedRenderSessionHandle: Send, Sync);
294    // The one map value that crosses threads: a copied handle id that lets a
295    // render thread name a map owned elsewhere.
296    assert_impl_all!(MapAttachRef: Send, Sync);
297
298    #[test]
299    // Spec coverage: BND-103.
300    fn projected_meter_helpers_round_trip() {
301        let coordinate = LatLng::new(45.0, -122.0);
302        let meters = projected_meters_for_lat_lng(coordinate).unwrap();
303        let round_tripped = lat_lng_for_projected_meters(meters).unwrap();
304
305        assert!((round_tripped.latitude - coordinate.latitude).abs() < 1e-9);
306        assert!((round_tripped.longitude - coordinate.longitude).abs() < 1e-9);
307    }
308
309    #[test]
310    // Spec coverage: BND-020.
311    fn invalid_network_status_reports_public_error() {
312        let error = set_network_status_raw(999_999).unwrap_err();
313
314        assert_eq!(error.kind(), ErrorKind::InvalidArgument);
315        assert_eq!(error.raw_status(), Some(sys::MLN_STATUS_INVALID_ARGUMENT));
316        assert!(error.diagnostic().contains("network status"));
317    }
318
319    #[test]
320    // Spec coverage: BND-025 and BND-068.
321    fn unknown_network_status_is_rejected_before_calling_c() {
322        let error = set_network_status(NetworkStatus::Unknown(999_999)).unwrap_err();
323
324        assert_eq!(error.kind(), ErrorKind::InvalidArgument);
325        assert_eq!(error.raw_status(), None);
326        assert!(error.diagnostic().contains("cannot be set"));
327    }
328}