Events and logs
Events tell you what happens in a map as it runs, like a style finishing loading or a tile failing to load. This guide shows how to receive map events and the library’s logs.
Set an event handler
Section titled “Set an event handler”Events come from the runtime, so one event handler, set with
mln_runtime_set_event_handlerRuntimeHandle.setEventHandlerRuntimeHandle.SetEventHandlerAsyncRuntimeHandle.SetEventHandlermaplibre_native_ffi_runtime_set_event_handler_asyncRuntimeHandle.setEventHandlerRuntimeHandle.setEventHandlerRuntimeHandle.set_event_handlerRuntimeHandle::set_event_handlerRuntimeHandle.setEventHandlerRuntimeHandle.setEventHandlerRuntime.setEventHandler, receives the events of the runtime and
all its maps. Set it before you create a map, so it receives the map’s first
events.
Each event names its type and the map that sent it. The handler runs on a library thread, so hand longer work to another thread.
static void on_events( void* user_data, const mln_event_batch_view* batch) { (void)user_data; for (size_t i = 0; i < batch->event_count; i++) { const mln_runtime_event* event = MLN_STRIDED_AT( mln_runtime_event, batch->events, batch->event_size, i ); if (event->source != map) continue;
switch (event->type) { case MLN_RUNTIME_EVENT_MAP_STYLE_LOADED: on_style_loaded(); break; case MLN_RUNTIME_EVENT_MAP_LOADING_FAILED: // The message is valid only during this callback. on_loading_failed(event->message); break; default: break; } }}
mln_runtime_event_handler handler = { .size = sizeof(mln_runtime_event_handler), .callback = on_events,};mln_runtime_set_event_handler(runtime, &handler, NULL, NULL);// The handler runs in this isolate. The binding queues each call.runtime.discarding.setEventHandler( RuntimeEventHandler( callback: (events) { for (final event in events) { if (event.source != map?.id) continue;
switch (event.type) { case RuntimeEventType.mapStyleLoaded: onStyleLoaded(); case RuntimeEventType.mapLoadingFailed: onLoadingFailed(event.message); } } }, ),);// Runs on a library thread.private static void OnEvents(RuntimeEvent[] events){ foreach (var e in events) { if (e.Source != map?.Id) continue;
switch (e.Type) { case RuntimeEventType.MapStyleLoaded: OnStyleLoaded(); break; case RuntimeEventType.MapLoadingFailed: OnLoadingFailed(e.Message); break; } }}
runtime.Discarding.SetEventHandler( new RuntimeEventHandler(OnEvents));// Runs on a library thread.handleEvents := func(events []maplibre.RuntimeEvent) { for _, event := range events { if event.Source != m.ID() { continue }
switch event.Type { case maplibre.RuntimeEventTypeMapStyleLoaded: onStyleLoaded() case maplibre.RuntimeEventTypeMapLoadingFailed: onLoadingFailed(event.Message) } }}
err := runtime.Discarding().SetEventHandler( maplibre.RuntimeEventHandler{Callback: handleEvents},)if err != nil { return err}var handler = new RuntimeEventHandler ();// The handler runs on the main context of the thread that// set it.handler.set_callback ((events) => { foreach (var event in events) { if (map == null || !map.is_event_source (event)) { continue; } switch (event.get_event_type ()) { case RuntimeEventType.MAP_STYLE_LOADED: on_style_loaded (); break; case RuntimeEventType.MAP_LOADING_FAILED: on_loading_failed (event.get_message ()); break; default: break; } }});runtime.set_event_handler_async.begin (handler);private void handleEvents(List<RuntimeEvent> events) { for (RuntimeEvent event : events) { if (map == null || event.getSource() != map.getId()) continue;
switch (event.getType().getRawValue()) { case RuntimeEventType.MAP_STYLE_LOADED_VALUE -> onStyleLoaded(); case RuntimeEventType.MAP_LOADING_FAILED_VALUE -> onLoadingFailed(event.getMessage()); default -> {} } }}
// Runs on a library thread.runtime .getDiscarding() .setEventHandler( new RuntimeEventHandler(this::handleEvents));private fun handleEvents(events: List<RuntimeEvent>) { for (event in events) { if (event.source != map?.id) continue
when (event.type) { RuntimeEventType.MAP_STYLE_LOADED -> onStyleLoaded() RuntimeEventType.MAP_LOADING_FAILED -> onLoadingFailed(event.message) } }}
// Runs on a library thread.runtime.discarding.setEventHandler( RuntimeEventHandler(::handleEvents),)def on_events(events: tuple[mln.RuntimeEvent, ...]) -> None: for event in events: if map_handle is None or event.source != map_handle.id: continue
match event.type: case mln.RuntimeEventType.MAP_STYLE_LOADED: on_style_loaded() case mln.RuntimeEventType.MAP_LOADING_FAILED: on_loading_failed(event.message)
# The handler runs on the binding's dispatcher thread.runtime.discarding.set_event_handler( mln.RuntimeEventHandler(on_events))// The handler runs on a library thread.// It sends the events to your thread.let (sender, batches) = mpsc::channel();runtime.discarding().set_event_handler( RuntimeEventHandler::new(move |events| { let _ = sender.send(events); }),)?;
// On your thread, such as on each turn of your event loop:for event in batches.try_iter().flatten() { if event.source != map.id() { continue; }
match event.r#type { RuntimeEventType::MapStyleLoaded => on_style_loaded(), RuntimeEventType::MapLoadingFailed => { on_loading_failed(&event.message) } _ => {} }}@MainActor private func handleEvents(_ events: [RuntimeEvent]) { for event in events where event.source == map?.id { switch event.type { case .mapStyleLoaded: onStyleLoaded() case .mapLoadingFailed: onLoadingFailed(event.message) default: break } }}
// The handler runs on a library thread.// It hands each batch to the main actor.try runtime.discarding .setEventHandler(RuntimeEventHandler { events in Task { @MainActor in handleEvents(events) } })// The handler runs on this thread. The binding queues each call.runtime.discarding.setEventHandler({ callback: (events) => { for (const event of events) { if (event.source !== map?.id) continue;
switch (event.type) { case RuntimeEventType.mapStyleLoaded: onStyleLoaded(); break; case RuntimeEventType.mapLoadingFailed: onLoadingFailed(event.message); break; } } },});fn onEvents( _: ?*anyopaque, events: []const maplibre.RuntimeEvent,) anyerror!void { for (events) |event| { if (event.source != map.id()) continue;
switch (event.type) { .map_style_loaded => onStyleLoaded(), // The message is valid only during this callback. .map_loading_failed => onLoadingFailed(event.message), else => {}, } }}
try runtime.discarding().setEventHandler( .{ .callback = onEvents }, null,);Filter events
Section titled “Filter events”By default, a map sends every event. To get only the events you act on, set
mln_map_options.event_maskMapOptions.eventMaskMapOptions.EventMaskMapOptions.EventMaskMaplibreNativeFfiMapOptions:event-maskMapOptions.eventMaskMapOptions.eventMaskMapOptions.event_maskMapOptions::event_maskMapOptions.eventMaskMapOptions.eventMaskMapOptions.event_mask when you create the map, or change it later
with mln_map_set_event_maskMapHandle.setEventMaskMapHandle.SetEventMaskAsyncMapHandle.SetEventMaskmaplibre_native_ffi_map_set_event_mask_asyncMapHandle.setEventMaskMapHandle.setEventMaskMapHandle.set_event_maskMapHandle::set_event_maskMapHandle.setEventMaskMapHandle.setEventMaskMap.setEventMask. Events cover style and resource loading,
rendering, and camera changes, and mln_runtime_event_typeRuntimeEventTypeRuntimeEventTypeRuntimeEventTypeMaplibreNativeFfiRuntimeEventTypeRuntimeEventTypeRuntimeEventTypeRuntimeEventTypeRuntimeEventTypeRuntimeEventTypeRuntimeEventTypeRuntimeEventType lists them
all.
mln_map_options map_options = { .size = sizeof(mln_map_options), .fields = MLN_MAP_OPTION_EVENT_MASK, .initial_extent = {.width = 512, .height = 512, .scale_factor = 1.0}, .event_mask = MLN_RUNTIME_EVENT_FLAG_MAP_STYLE_LOADED | MLN_RUNTIME_EVENT_FLAG_MAP_LOADING_FAILED,};mln_runtime_create_map(runtime, &map_options, &map, NULL, NULL);map = runtime.createMap( MapOptions( initialExtent: const LogicalExtent( width: 512, height: 512, scaleFactor: 1, ), eventMask: RuntimeEventFlag.mapStyleLoaded | RuntimeEventFlag.mapLoadingFailed, ),);map = runtime.CreateMap( new MapOptions { InitialExtent = new LogicalExtent(512, 512, 1), EventMask = RuntimeEventFlag.MapStyleLoaded | RuntimeEventFlag.MapLoadingFailed, });m, created, err := runtime.CreateMap(maplibre.MapOptions{ InitialExtent: maplibre.LogicalExtent{ Width: 512, Height: 512, ScaleFactor: 1, }, EventMask: new(maplibre.RuntimeEventFlagMapStyleLoaded | maplibre.RuntimeEventFlagMapLoadingFailed),})if err != nil { return err}var options = new MapOptions () { initial_extent = { width: 512, height: 512, scale_factor: 1.0 }, event_mask = RuntimeEventFlag.MAP_STYLE_LOADED | RuntimeEventFlag.MAP_LOADING_FAILED,};map = runtime.create_map (options);MapHandle created = runtime.createMap( MapOptions.of(LogicalExtent.of(512, 512, 1.0)) .withEventMask( RuntimeEventFlag.MAP_STYLE_LOADED.or( RuntimeEventFlag.MAP_LOADING_FAILED)));val created = runtime.createMap( MapOptions( initialExtent = LogicalExtent( width = 512u, height = 512u, scaleFactor = 1.0, ), eventMask = RuntimeEventFlag.MAP_STYLE_LOADED or RuntimeEventFlag.MAP_LOADING_FAILED, ), )map_handle = runtime.create_map( mln.MapOptions( initial_extent=mln.LogicalExtent( width=512, height=512, scale_factor=1.0 ), event_mask=mln.RuntimeEventFlag.MAP_STYLE_LOADED | mln.RuntimeEventFlag.MAP_LOADING_FAILED, ))let map_options = MapOptions { event_mask: Some( RuntimeEventFlag::MAP_STYLE_LOADED | RuntimeEventFlag::MAP_LOADING_FAILED, ), ..MapOptions::new(LogicalExtent::new(512, 512, 1.0))};let (map, created) = runtime.create_map(&map_options)?;let created = try runtime.createMap(options: MapOptions( initialExtent: LogicalExtent( width: 512, height: 512, scaleFactor: 1 ), eventMask: [.mapStyleLoaded, .mapLoadingFailed]))map = runtime.createMap({ initialExtent: { width: 512, height: 512, scaleFactor: 1 }, eventMask: RuntimeEventFlag.mapStyleLoaded | RuntimeEventFlag.mapLoadingFailed,});var created = try runtime.createMap(.{ .initial_extent = .{ .width = 512, .height = 512, .scale_factor = 1.0, }, .event_mask = .{ .map_style_loaded = true, .map_loading_failed = true, },}, null);defer created.ready.deinit();map = created.map;Capture logs
Section titled “Capture logs”To send the library’s logs to your logger, set a log callback with
mln_log_set_callbacksetLogCallbackMaplibreNative.SetLogCallbackSetLogCallbackmaplibre_native_ffi_set_log_callbacksetLogCallbacksetLogCallbackset_log_callbackset_log_callbacksetLogCallbacksetLogCallbacksetLogCallback. One callback receives the logs of every runtime in
the process.
static void on_log( void* user_data, mln_log_severity severity, mln_log_event event, int64_t code, mln_buffer_view message) { (void)user_data; (void)event; (void)code; // The message is valid only during this callback. write_log(severity, message);}
mln_log_handler log_handler = { .size = sizeof(mln_log_handler), .callback = on_log,};mln_log_set_callback(&log_handler, NULL);// The callback runs in this isolate. The binding queues each call.setLogCallback( LogHandler( callback: (severity, _, _, message) { writeLog(severity, message); }, ),);// Runs on a library thread, and can't call the library.MaplibreNative.SetLogCallback( new LogHandler { Callback = (severity, _, _, message) => WriteLog(severity, message), });err = maplibre.SetLogCallback(maplibre.LogHandler{ // Runs on a library thread, and can't call the library. Callback: func( severity maplibre.LogSeverity, _ maplibre.LogEvent, _ int64, message string, ) { writeLog(severity, message) },})if err != nil { return err}var logs = new LogHandler ();// The callback runs on the main context of the thread that// set it.logs.set_callback ((severity, event, code, message) => { write_log (severity, message);});set_log_callback (logs);// Runs on a library thread, and can't call the library.MaplibreNative.setLogCallback( LogHandler.of( (severity, event, code, message) -> writeLog(severity, message)));// Runs on a library thread, and can't call the library.setLogCallback( LogHandler { severity, _, _, message -> writeLog(severity, message) },)# The callback runs on the binding's dispatcher thread.def on_log( severity: mln.LogSeverity, event: mln.LogEvent, code: int, message: str,) -> None: write_log(severity, message)
mln.set_log_callback(mln.LogHandler(on_log))// The callback runs on a library// thread, and can't call the library.set_log_callback(LogHandler::new( |severity, _event, _code, message| { write_log(severity, message); },))?;// Runs on a library thread, and can't call the library.try setLogCallback(handler: LogHandler { severity, _, _, message in writeLog(severity, message)})// The callback runs on this thread. The binding queues each call.setLogCallback({ callback: (severity, _event, _code, message) => { writeLog(severity, message); },});fn onLog( _: ?*anyopaque, severity: maplibre.LogSeverity, _: maplibre.LogEvent, _: i64, message: []const u8,) anyerror!void { // The message is valid only during this callback. writeLog(severity, message);}
try maplibre.setLogCallback(.{ .callback = onLog }, null);