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#[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 pub const fn get(self) -> u64 {
24 self.0
25 }
26}
27
28#[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#[derive(Debug, Clone, PartialEq)]
40#[non_exhaustive]
41pub struct RuntimeEvent {
42 pub event_type: RuntimeEventType,
43 pub source: RuntimeEventSource,
44 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 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 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 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}