Skip to main content

maplibre_native/
events.rs

1use maplibre_native_core as maplibre_core;
2use maplibre_native_sys as sys;
3
4use crate::Result;
5pub use maplibre_core::events::{
6    CameraTransitionFinishedEvent, OfflineOperationCompletedEvent, OfflineRegionResponseErrorEvent,
7    OfflineRegionStatus, OfflineRegionStatusEvent, OfflineRegionTileCountLimitEvent,
8    RenderFrameEvent, RenderMapEvent, RenderingStats, RuntimeEventPayload, StyleImageMissingEvent,
9    TileActionEvent, TileId, UnknownRuntimeEventPayload,
10};
11pub(crate) use maplibre_core::{OfflineRegionDownloadState, RuntimeEventType};
12
13/// Rust-assigned identity for a map owned by a runtime.
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
15pub struct MapId(u64);
16
17impl MapId {
18    pub(crate) const fn new(value: u64) -> Self {
19        Self(value)
20    }
21
22    /// Returns the runtime-local numeric map identity.
23    pub const fn get(self) -> u64 {
24        self.0
25    }
26}
27
28/// Source object that emitted a runtime event.
29#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
30#[non_exhaustive]
31pub enum RuntimeEventSource {
32    Runtime,
33    Map(MapId),
34    UnknownMap,
35    Unknown(u32),
36}
37
38/// Owned runtime event copied from native poll storage.
39#[derive(Debug, Clone, PartialEq)]
40#[non_exhaustive]
41pub struct RuntimeEvent {
42    pub event_type: RuntimeEventType,
43    pub source: RuntimeEventSource,
44    /// Secondary event detail whose meaning `event_type` selects.
45    ///
46    /// Camera will-change and did-change events carry a
47    /// [`CameraChangeMode`](crate::CameraChangeMode), which decodes as
48    /// `CameraChangeMode::from_raw(code as u32)`. Offline operation-completion
49    /// events carry the operation's native status value. Map loading-failure
50    /// events carry the ordinal of MapLibre Native's internal load error kind,
51    /// whose text is in `message`. Every other event type carries 0.
52    pub code: i32,
53    pub message: Option<String>,
54    pub payload: RuntimeEventPayload,
55}
56
57impl RuntimeEvent {
58    pub(crate) fn from_native(
59        raw: &sys::mln_runtime_event,
60        source: RuntimeEventSource,
61    ) -> Result<Self> {
62        // SAFETY: raw is borrowed from the latest runtime poll result and is
63        // copied before another poll can invalidate event-owned storage.
64        let copied = unsafe { maplibre_core::events::runtime_event_from_native(raw) }?;
65        Ok(Self {
66            event_type: copied.event_type,
67            source,
68            code: copied.code,
69            message: copied.message,
70            payload: copied.payload,
71        })
72    }
73}
74
75pub(crate) fn empty_runtime_event() -> sys::mln_runtime_event {
76    maplibre_core::events::empty_runtime_event()
77}
78
79#[cfg(test)]
80mod tests {
81    use std::mem;
82    use std::ptr;
83
84    use crate::ResourceErrorReason;
85
86    use super::*;
87
88    #[test]
89    // Spec coverage: BND-086.
90    fn public_runtime_event_applies_rust_source_policy_to_copied_event() {
91        let bytes = [1u8, 2, 3, 4];
92        let message = b"future payload";
93        let source = RuntimeEventSource::Map(MapId::new(42));
94        let raw = sys::mln_runtime_event {
95            size: mem::size_of::<sys::mln_runtime_event>() as u32,
96            type_: 999_001,
97            source_type: sys::MLN_RUNTIME_EVENT_SOURCE_MAP,
98            source: ptr::null_mut(),
99            code: -7,
100            payload_type: 999_002,
101            payload: bytes.as_ptr().cast(),
102            payload_size: bytes.len(),
103            message: message.as_ptr().cast(),
104            message_size: message.len(),
105        };
106
107        let event = RuntimeEvent::from_native(&raw, source).unwrap();
108
109        assert_eq!(event.event_type, RuntimeEventType::Unknown(999_001));
110        assert_eq!(event.source, source);
111        assert_eq!(event.code, -7);
112        assert_eq!(event.message.as_deref(), Some("future payload"));
113        let RuntimeEventPayload::Unknown(payload) = event.payload else {
114            panic!("expected unknown payload");
115        };
116        assert_eq!(payload.raw_type, 999_002);
117        assert_eq!(payload.bytes, bytes.to_vec());
118    }
119
120    #[test]
121    // Spec coverage: BND-085.
122    fn public_runtime_event_copies_offline_region_status_and_error_payloads() {
123        let mut status = maplibre_core::events::empty_offline_region_status_native();
124        status.download_state = sys::MLN_OFFLINE_REGION_DOWNLOAD_ACTIVE;
125        status.completed_resource_count = 3;
126        status.complete = true;
127        let status_payload = sys::mln_runtime_event_offline_region_status {
128            size: mem::size_of::<sys::mln_runtime_event_offline_region_status>() as u32,
129            region_id: 7,
130            status,
131        };
132        let raw_status = sys::mln_runtime_event {
133            size: mem::size_of::<sys::mln_runtime_event>() as u32,
134            type_: sys::MLN_RUNTIME_EVENT_OFFLINE_REGION_STATUS_CHANGED,
135            source_type: sys::MLN_RUNTIME_EVENT_SOURCE_RUNTIME,
136            source: ptr::null_mut(),
137            code: 0,
138            payload_type: sys::MLN_RUNTIME_EVENT_PAYLOAD_OFFLINE_REGION_STATUS,
139            payload: ptr::addr_of!(status_payload).cast(),
140            payload_size: mem::size_of_val(&status_payload),
141            message: ptr::null(),
142            message_size: 0,
143        };
144
145        let event = RuntimeEvent::from_native(&raw_status, RuntimeEventSource::Runtime).unwrap();
146
147        assert_eq!(
148            event.event_type,
149            RuntimeEventType::OfflineRegionStatusChanged
150        );
151        let RuntimeEventPayload::OfflineRegionStatus(status_event) = event.payload else {
152            panic!("expected offline region status payload");
153        };
154        assert_eq!(status_event.region_id, 7);
155        assert_eq!(
156            status_event.status.download_state,
157            OfflineRegionDownloadState::Active
158        );
159        assert_eq!(status_event.status.completed_resource_count, 3);
160        assert!(status_event.status.complete);
161
162        let mut message = b"offline failed".to_vec();
163        let error_payload = sys::mln_runtime_event_offline_region_response_error {
164            size: mem::size_of::<sys::mln_runtime_event_offline_region_response_error>() as u32,
165            region_id: 7,
166            reason: sys::MLN_RESOURCE_ERROR_REASON_OTHER,
167        };
168        let raw_error = sys::mln_runtime_event {
169            size: mem::size_of::<sys::mln_runtime_event>() as u32,
170            type_: sys::MLN_RUNTIME_EVENT_OFFLINE_REGION_RESPONSE_ERROR,
171            source_type: sys::MLN_RUNTIME_EVENT_SOURCE_RUNTIME,
172            source: ptr::null_mut(),
173            code: -1,
174            payload_type: sys::MLN_RUNTIME_EVENT_PAYLOAD_OFFLINE_REGION_RESPONSE_ERROR,
175            payload: ptr::addr_of!(error_payload).cast(),
176            payload_size: mem::size_of_val(&error_payload),
177            message: message.as_ptr().cast(),
178            message_size: message.len(),
179        };
180
181        let event = RuntimeEvent::from_native(&raw_error, RuntimeEventSource::Runtime).unwrap();
182        message.fill(b'x');
183
184        assert_eq!(
185            event.event_type,
186            RuntimeEventType::OfflineRegionResponseError
187        );
188        assert_eq!(event.message.as_deref(), Some("offline failed"));
189        let RuntimeEventPayload::OfflineRegionResponseError(error_event) = event.payload else {
190            panic!("expected offline region response-error payload");
191        };
192        assert_eq!(error_event.region_id, 7);
193        assert_eq!(error_event.reason, ResourceErrorReason::Other);
194    }
195}