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#[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,
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 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 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 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}