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    OfflineOperationCompletedEvent, OfflineRegionResponseErrorEvent, OfflineRegionStatus,
7    OfflineRegionStatusEvent, OfflineRegionTileCountLimitEvent, RenderFrameEvent, RenderMapEvent,
8    RenderingStats, RuntimeEventPayload, StyleImageMissingEvent, TileActionEvent, TileId,
9    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    pub code: i32,
45    pub message: Option<String>,
46    pub payload: RuntimeEventPayload,
47}
48
49impl RuntimeEvent {
50    pub(crate) fn from_native(
51        raw: &sys::mln_runtime_event,
52        source: RuntimeEventSource,
53    ) -> Result<Self> {
54        // SAFETY: raw is borrowed from the latest runtime poll result and is
55        // copied before another poll can invalidate event-owned storage.
56        let copied = unsafe { maplibre_core::events::runtime_event_from_native(raw) }?;
57        Ok(Self {
58            event_type: copied.event_type,
59            source,
60            code: copied.code,
61            message: copied.message,
62            payload: copied.payload,
63        })
64    }
65}
66
67pub(crate) fn empty_runtime_event() -> sys::mln_runtime_event {
68    maplibre_core::events::empty_runtime_event()
69}
70
71#[cfg(test)]
72mod tests {
73    use std::mem;
74    use std::ptr;
75
76    use crate::ResourceErrorReason;
77
78    use super::*;
79
80    #[test]
81    // Spec coverage: BND-086.
82    fn public_runtime_event_applies_rust_source_policy_to_copied_event() {
83        let bytes = [1u8, 2, 3, 4];
84        let message = b"future payload";
85        let source = RuntimeEventSource::Map(MapId::new(42));
86        let raw = sys::mln_runtime_event {
87            size: mem::size_of::<sys::mln_runtime_event>() as u32,
88            type_: 999_001,
89            source_type: sys::MLN_RUNTIME_EVENT_SOURCE_MAP,
90            source: ptr::null_mut(),
91            code: -7,
92            payload_type: 999_002,
93            payload: bytes.as_ptr().cast(),
94            payload_size: bytes.len(),
95            message: message.as_ptr().cast(),
96            message_size: message.len(),
97        };
98
99        let event = RuntimeEvent::from_native(&raw, source).unwrap();
100
101        assert_eq!(event.event_type, RuntimeEventType::Unknown(999_001));
102        assert_eq!(event.source, source);
103        assert_eq!(event.code, -7);
104        assert_eq!(event.message.as_deref(), Some("future payload"));
105        let RuntimeEventPayload::Unknown(payload) = event.payload else {
106            panic!("expected unknown payload");
107        };
108        assert_eq!(payload.raw_type, 999_002);
109        assert_eq!(payload.bytes, bytes.to_vec());
110    }
111
112    #[test]
113    // Spec coverage: BND-085.
114    fn public_runtime_event_copies_offline_region_status_and_error_payloads() {
115        let mut status = maplibre_core::events::empty_offline_region_status_native();
116        status.download_state = sys::MLN_OFFLINE_REGION_DOWNLOAD_ACTIVE;
117        status.completed_resource_count = 3;
118        status.complete = true;
119        let status_payload = sys::mln_runtime_event_offline_region_status {
120            size: mem::size_of::<sys::mln_runtime_event_offline_region_status>() as u32,
121            region_id: 7,
122            status,
123        };
124        let raw_status = sys::mln_runtime_event {
125            size: mem::size_of::<sys::mln_runtime_event>() as u32,
126            type_: sys::MLN_RUNTIME_EVENT_OFFLINE_REGION_STATUS_CHANGED,
127            source_type: sys::MLN_RUNTIME_EVENT_SOURCE_RUNTIME,
128            source: ptr::null_mut(),
129            code: 0,
130            payload_type: sys::MLN_RUNTIME_EVENT_PAYLOAD_OFFLINE_REGION_STATUS,
131            payload: ptr::addr_of!(status_payload).cast(),
132            payload_size: mem::size_of_val(&status_payload),
133            message: ptr::null(),
134            message_size: 0,
135        };
136
137        let event = RuntimeEvent::from_native(&raw_status, RuntimeEventSource::Runtime).unwrap();
138
139        assert_eq!(
140            event.event_type,
141            RuntimeEventType::OfflineRegionStatusChanged
142        );
143        let RuntimeEventPayload::OfflineRegionStatus(status_event) = event.payload else {
144            panic!("expected offline region status payload");
145        };
146        assert_eq!(status_event.region_id, 7);
147        assert_eq!(
148            status_event.status.download_state,
149            OfflineRegionDownloadState::Active
150        );
151        assert_eq!(status_event.status.completed_resource_count, 3);
152        assert!(status_event.status.complete);
153
154        let mut message = b"offline failed".to_vec();
155        let error_payload = sys::mln_runtime_event_offline_region_response_error {
156            size: mem::size_of::<sys::mln_runtime_event_offline_region_response_error>() as u32,
157            region_id: 7,
158            reason: sys::MLN_RESOURCE_ERROR_REASON_OTHER,
159        };
160        let raw_error = sys::mln_runtime_event {
161            size: mem::size_of::<sys::mln_runtime_event>() as u32,
162            type_: sys::MLN_RUNTIME_EVENT_OFFLINE_REGION_RESPONSE_ERROR,
163            source_type: sys::MLN_RUNTIME_EVENT_SOURCE_RUNTIME,
164            source: ptr::null_mut(),
165            code: -1,
166            payload_type: sys::MLN_RUNTIME_EVENT_PAYLOAD_OFFLINE_REGION_RESPONSE_ERROR,
167            payload: ptr::addr_of!(error_payload).cast(),
168            payload_size: mem::size_of_val(&error_payload),
169            message: message.as_ptr().cast(),
170            message_size: message.len(),
171        };
172
173        let event = RuntimeEvent::from_native(&raw_error, RuntimeEventSource::Runtime).unwrap();
174        message.fill(b'x');
175
176        assert_eq!(
177            event.event_type,
178            RuntimeEventType::OfflineRegionResponseError
179        );
180        assert_eq!(event.message.as_deref(), Some("offline failed"));
181        let RuntimeEventPayload::OfflineRegionResponseError(error_event) = event.payload else {
182            panic!("expected offline region response-error payload");
183        };
184        assert_eq!(error_event.region_id, 7);
185        assert_eq!(error_event.reason, ResourceErrorReason::Other);
186    }
187}