Add action journal (#3409)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Bart Louwers <bart@emeel.net>
This commit is contained in:
2
.github/workflows/android-ci.yml
vendored
2
.github/workflows/android-ci.yml
vendored
@@ -53,7 +53,7 @@ jobs:
|
||||
if: startsWith(runner.name, 'GitHub Actions')
|
||||
uses: jlumbroso/free-disk-space@main
|
||||
with:
|
||||
tool-cache: false
|
||||
tool-cache: true
|
||||
android: false
|
||||
dotnet: true
|
||||
haskell: true
|
||||
|
||||
3
.gitmodules
vendored
3
.gitmodules
vendored
@@ -97,3 +97,6 @@
|
||||
[submodule "vendor/maplibre-native-base/deps/geojson.hpp"]
|
||||
path = vendor/maplibre-native-base/deps/geojson.hpp
|
||||
url = https://github.com/mapbox/geojson-cpp.git
|
||||
[submodule "vendor/maplibre-native-base/extras/filesystem"]
|
||||
path = vendor/maplibre-native-base/extras/filesystem
|
||||
url = https://github.com/gulrak/filesystem.git
|
||||
|
||||
@@ -14,6 +14,7 @@ load(
|
||||
"MLN_DRAWABLES_SOURCE",
|
||||
"MLN_GENERATED_OPENGL_SHADER_HEADERS",
|
||||
"MLN_GENERATED_SHADER_HEADERS",
|
||||
"MLN_GENERATED_SHADER_SOURCE",
|
||||
"MLN_GENERATED_STYLE_SOURCE",
|
||||
"MLN_OPENGL_HEADERS",
|
||||
"MLN_OPENGL_SOURCE",
|
||||
@@ -43,6 +44,7 @@ js_run_binary(
|
||||
allow_empty = False,
|
||||
),
|
||||
outs = MLN_GENERATED_SHADER_HEADERS +
|
||||
MLN_GENERATED_SHADER_SOURCE +
|
||||
MLN_GENERATED_OPENGL_SHADER_HEADERS,
|
||||
tool = ":generate-shader-code-script",
|
||||
)
|
||||
@@ -75,6 +77,7 @@ cc_library(
|
||||
cc_library(
|
||||
name = "mbgl-core",
|
||||
srcs = MLN_CORE_SOURCE +
|
||||
MLN_GENERATED_SHADER_SOURCE +
|
||||
MLN_GENERATED_STYLE_SOURCE + select({
|
||||
":drawable_renderer": MLN_OPENGL_SOURCE + MLN_DRAWABLES_SOURCE + MLN_DRAWABLES_GL_SOURCE,
|
||||
":metal_renderer": MLN_DRAWABLES_SOURCE + MLN_DRAWABLES_MTL_SOURCE,
|
||||
|
||||
@@ -279,6 +279,7 @@ list(APPEND INCLUDE_FILES
|
||||
${PROJECT_SOURCE_DIR}/include/mbgl/renderer/renderer_frontend.hpp
|
||||
${PROJECT_SOURCE_DIR}/include/mbgl/renderer/renderer_observer.hpp
|
||||
${PROJECT_SOURCE_DIR}/include/mbgl/renderer/renderer_state.hpp
|
||||
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/shader_source.hpp
|
||||
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/program_parameters.hpp
|
||||
${PROJECT_SOURCE_DIR}/include/mbgl/storage/database_file_source.hpp
|
||||
${PROJECT_SOURCE_DIR}/include/mbgl/storage/file_source.hpp
|
||||
@@ -386,6 +387,8 @@ list(APPEND INCLUDE_FILES
|
||||
${PROJECT_SOURCE_DIR}/include/mbgl/tile/tile_id.hpp
|
||||
${PROJECT_SOURCE_DIR}/include/mbgl/tile/tile_operation.hpp
|
||||
${PROJECT_SOURCE_DIR}/include/mbgl/tile/tile_necessity.hpp
|
||||
${PROJECT_SOURCE_DIR}/include/mbgl/util/action_journal.hpp
|
||||
${PROJECT_SOURCE_DIR}/include/mbgl/util/action_journal_options.hpp
|
||||
${PROJECT_SOURCE_DIR}/include/mbgl/util/async_request.hpp
|
||||
${PROJECT_SOURCE_DIR}/include/mbgl/util/async_task.hpp
|
||||
${PROJECT_SOURCE_DIR}/include/mbgl/util/char_array_buffer.hpp
|
||||
@@ -645,6 +648,7 @@ list(APPEND SRC_FILES
|
||||
${PROJECT_SOURCE_DIR}/src/mbgl/renderer/transition_parameters.hpp
|
||||
${PROJECT_SOURCE_DIR}/src/mbgl/renderer/update_parameters.hpp
|
||||
${PROJECT_SOURCE_DIR}/src/mbgl/renderer/upload_parameters.hpp
|
||||
${PROJECT_SOURCE_DIR}/src/mbgl/shaders/shader_source.cpp
|
||||
${PROJECT_SOURCE_DIR}/src/mbgl/shaders/attributes.hpp
|
||||
${PROJECT_SOURCE_DIR}/src/mbgl/shaders/program_parameters.cpp
|
||||
${PROJECT_SOURCE_DIR}/src/mbgl/shaders/segment.hpp
|
||||
@@ -881,10 +885,14 @@ list(APPEND SRC_FILES
|
||||
${PROJECT_SOURCE_DIR}/src/mbgl/tile/tile_loader_impl.hpp
|
||||
${PROJECT_SOURCE_DIR}/src/mbgl/tile/tile_loader_observer.hpp
|
||||
${PROJECT_SOURCE_DIR}/src/mbgl/tile/tile_observer.hpp
|
||||
${PROJECT_SOURCE_DIR}/src/mbgl/tile/tile_operation.cpp
|
||||
${PROJECT_SOURCE_DIR}/src/mbgl/tile/vector_tile.cpp
|
||||
${PROJECT_SOURCE_DIR}/src/mbgl/tile/vector_tile.hpp
|
||||
${PROJECT_SOURCE_DIR}/src/mbgl/tile/vector_tile_data.cpp
|
||||
${PROJECT_SOURCE_DIR}/src/mbgl/tile/vector_tile_data.hpp
|
||||
${PROJECT_SOURCE_DIR}/src/mbgl/util/action_journal.cpp
|
||||
${PROJECT_SOURCE_DIR}/src/mbgl/util/action_journal_impl.hpp
|
||||
${PROJECT_SOURCE_DIR}/src/mbgl/util/action_journal_impl.cpp
|
||||
${PROJECT_SOURCE_DIR}/src/mbgl/util/camera.cpp
|
||||
${PROJECT_SOURCE_DIR}/src/mbgl/util/bounding_volumes.hpp
|
||||
${PROJECT_SOURCE_DIR}/src/mbgl/util/bounding_volumes.cpp
|
||||
@@ -972,7 +980,6 @@ if(MLN_WITH_OPENGL)
|
||||
${PROJECT_SOURCE_DIR}/include/mbgl/gl/renderable_resource.hpp
|
||||
${PROJECT_SOURCE_DIR}/include/mbgl/gl/renderer_backend.hpp
|
||||
${PROJECT_SOURCE_DIR}/include/mbgl/platform/gl_functions.hpp
|
||||
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/shader_source.hpp
|
||||
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/shader_manifest.hpp
|
||||
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/gl/shader_group_gl.hpp
|
||||
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/gl/prelude.hpp
|
||||
@@ -1425,6 +1432,7 @@ set(EXPORT_TARGETS
|
||||
mbgl-core
|
||||
maplibre-native-base
|
||||
maplibre-native-base-cheap-ruler-cpp
|
||||
maplibre-native-base-extras-filesystem
|
||||
maplibre-native-base-extras-expected-lite
|
||||
maplibre-native-base-extras-kdbush.hpp
|
||||
maplibre-native-base-extras-rapidjson
|
||||
|
||||
@@ -54,6 +54,10 @@ MLN_GENERATED_SHADER_HEADERS = [
|
||||
"include/mbgl/shaders/shader_manifest.hpp",
|
||||
]
|
||||
|
||||
MLN_GENERATED_SHADER_SOURCE = [
|
||||
"src/mbgl/shaders/shader_source.cpp",
|
||||
]
|
||||
|
||||
MLN_GENERATED_OPENGL_SHADER_HEADERS = [
|
||||
"include/mbgl/shaders/gl/background.hpp",
|
||||
"include/mbgl/shaders/gl/background_pattern.hpp",
|
||||
@@ -498,10 +502,14 @@ MLN_CORE_SOURCE = [
|
||||
"src/mbgl/tile/tile_loader_impl.hpp",
|
||||
"src/mbgl/tile/tile_loader_observer.hpp",
|
||||
"src/mbgl/tile/tile_observer.hpp",
|
||||
"src/mbgl/tile/tile_operation.cpp",
|
||||
"src/mbgl/tile/vector_tile.cpp",
|
||||
"src/mbgl/tile/vector_tile.hpp",
|
||||
"src/mbgl/tile/vector_tile_data.cpp",
|
||||
"src/mbgl/tile/vector_tile_data.hpp",
|
||||
"src/mbgl/util/action_journal.cpp",
|
||||
"src/mbgl/util/action_journal_impl.hpp",
|
||||
"src/mbgl/util/action_journal_impl.cpp",
|
||||
"src/mbgl/util/camera.cpp",
|
||||
"src/mbgl/util/camera.hpp",
|
||||
"src/mbgl/util/bounding_volumes.hpp",
|
||||
@@ -743,6 +751,8 @@ MLN_CORE_HEADERS = [
|
||||
"include/mbgl/tile/tile_id.hpp",
|
||||
"include/mbgl/tile/tile_operation.hpp",
|
||||
"include/mbgl/tile/tile_necessity.hpp",
|
||||
"include/mbgl/util/action_journal.hpp",
|
||||
"include/mbgl/util/action_journal_options.hpp",
|
||||
"include/mbgl/util/async_request.hpp",
|
||||
"include/mbgl/util/async_task.hpp",
|
||||
"include/mbgl/util/bitmask_operations.hpp",
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include <mbgl/map/projection_mode.hpp>
|
||||
#include <mbgl/storage/resource_options.hpp>
|
||||
#include <mbgl/util/client_options.hpp>
|
||||
#include <mbgl/util/action_journal_options.hpp>
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
@@ -31,13 +32,18 @@ class Image;
|
||||
class Style;
|
||||
} // namespace style
|
||||
|
||||
namespace util {
|
||||
class ActionJournal;
|
||||
} // namespace util
|
||||
|
||||
class Map : private util::noncopyable {
|
||||
public:
|
||||
explicit Map(RendererFrontend&,
|
||||
MapObserver&,
|
||||
const MapOptions&,
|
||||
const ResourceOptions&,
|
||||
const ClientOptions& = ClientOptions());
|
||||
const ClientOptions& = ClientOptions(),
|
||||
const util::ActionJournalOptions& = util::ActionJournalOptions());
|
||||
~Map();
|
||||
|
||||
/// Register a callback that will get called (on the render thread) when all
|
||||
@@ -152,12 +158,16 @@ public:
|
||||
void setFreeCameraOptions(const FreeCameraOptions& camera);
|
||||
FreeCameraOptions getFreeCameraOptions() const;
|
||||
|
||||
ClientOptions getClientOptions() const;
|
||||
|
||||
const std::unique_ptr<util::ActionJournal>& getActionJournal();
|
||||
|
||||
protected:
|
||||
class Impl;
|
||||
const std::unique_ptr<Impl> impl;
|
||||
|
||||
// For testing only.
|
||||
Map(std::unique_ptr<Impl>);
|
||||
Map(std::unique_ptr<Impl>, const util::ActionJournalOptions& = {});
|
||||
};
|
||||
|
||||
} // namespace mbgl
|
||||
|
||||
68
include/mbgl/util/action_journal.hpp
Normal file
68
include/mbgl/util/action_journal.hpp
Normal file
@@ -0,0 +1,68 @@
|
||||
#pragma once
|
||||
|
||||
#include <mbgl/map/map.hpp>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <filesystem>
|
||||
|
||||
namespace mbgl {
|
||||
|
||||
namespace util {
|
||||
|
||||
class ActionJournalOptions;
|
||||
|
||||
/**
|
||||
* @brief Logs map events in a persistent rolling file format.
|
||||
* Each event is stored as a serialized json object with the event data.
|
||||
* Example
|
||||
* {
|
||||
* "name" : "onTileAction",
|
||||
* "time" : "2025-04-17T13:13:13.974Z",
|
||||
* "styleName" : "Streets",
|
||||
* "styleURL" : "maptiler://maps/streets",
|
||||
* "event" : {
|
||||
* "action" : "RequestedFromNetwork",
|
||||
* "tileX" : 0,
|
||||
* "tileY" : 0,
|
||||
* "tileZ" : 0,
|
||||
* "overscaledZ" : 0,
|
||||
* "sourceID" : "openmaptiles"
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
class ActionJournal {
|
||||
public:
|
||||
ActionJournal(const Map& map, const ActionJournalOptions& options);
|
||||
~ActionJournal();
|
||||
|
||||
/**
|
||||
* @brief Get the action journal umbrella directory containing all logs.
|
||||
* @return log directory
|
||||
*/
|
||||
std::string getLogDirectory() const;
|
||||
|
||||
/**
|
||||
* @brief Get the action journal log files.
|
||||
* @return list of log files
|
||||
*/
|
||||
std::vector<std::string> getLogFiles() const;
|
||||
|
||||
/**
|
||||
* @brief Get the action journal events from oldest to newest.
|
||||
* Each element contains a serialized json object with the event data.
|
||||
* @return List of serialized events
|
||||
*/
|
||||
std::vector<std::string> getLog();
|
||||
|
||||
/**
|
||||
* @brief Clear stored action journal events by removing logs files.
|
||||
*/
|
||||
void clearLog();
|
||||
|
||||
class Impl;
|
||||
const std::unique_ptr<Impl> impl;
|
||||
};
|
||||
|
||||
} // namespace util
|
||||
} // namespace mbgl
|
||||
101
include/mbgl/util/action_journal_options.hpp
Normal file
101
include/mbgl/util/action_journal_options.hpp
Normal file
@@ -0,0 +1,101 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace mbgl {
|
||||
|
||||
namespace util {
|
||||
|
||||
/**
|
||||
* @brief Holds values for Action Journal options.
|
||||
*/
|
||||
class ActionJournalOptions {
|
||||
public:
|
||||
ActionJournalOptions() = default;
|
||||
~ActionJournalOptions() = default;
|
||||
|
||||
/**
|
||||
* @brief Enable action journal event logging, defaults to false.
|
||||
*
|
||||
* @param value true to enable, false to disable.
|
||||
* @return ActionJournalOptions for chaining options together.
|
||||
*/
|
||||
ActionJournalOptions& enable(bool value = true) {
|
||||
enable_ = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Gets the previously set (or default) value.
|
||||
* @return Returns ActionJournal state
|
||||
*/
|
||||
bool enabled() const { return enable_; }
|
||||
|
||||
/**
|
||||
* @brief Set the log files location. An umbrela `action_journal` directory
|
||||
* will be created at the given location containing all files.
|
||||
*
|
||||
* @param path Action Journal log path.
|
||||
* @return ActionJournalOptions for chaining options together.
|
||||
*/
|
||||
ActionJournalOptions& withPath(const std::string& path) {
|
||||
path_ = path;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Gets the previously set (or default) log path.
|
||||
* @return Returns ActionJournal log path
|
||||
*/
|
||||
std::string path() const { return path_; }
|
||||
|
||||
/**
|
||||
* @brief Set the file size for the log files. The action journal uses a
|
||||
* rolling log with multiple files. Total log size is equal to
|
||||
* `logFileSize * logFileCount`.
|
||||
*
|
||||
* @param size Action Journal log file size.
|
||||
* @return ActionJournalOptions for chaining options together.
|
||||
*/
|
||||
ActionJournalOptions& withLogFileSize(const uint32_t size) {
|
||||
logFileSize_ = size;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Gets the previously set (or default) log file size.
|
||||
* @return Returns ActionJournal log file size
|
||||
*/
|
||||
uint32_t logFileSize() const { return logFileSize_; }
|
||||
|
||||
/**
|
||||
* @brief Set the number of log files. The action journal uses a
|
||||
* rolling log with multiple files. Total log size is equal to
|
||||
* `logFileSize * logFileCount`.
|
||||
*
|
||||
* @param count Action Journal log file count.
|
||||
* @return ActionJournalOptions for chaining options together.
|
||||
*/
|
||||
ActionJournalOptions& withLogFileCount(const uint32_t count) {
|
||||
logFileCount_ = count;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Gets the previously set (or default) number of log files.
|
||||
* @return Returns ActionJournal log file count
|
||||
*/
|
||||
uint32_t logFileCount() const { return logFileCount_; }
|
||||
|
||||
protected:
|
||||
bool enable_ = false;
|
||||
// path of the log
|
||||
std::string path_{"/tmp/"};
|
||||
// log file size
|
||||
uint32_t logFileSize_ = 1024 * 1024;
|
||||
// number of log files (each of `logFileSize` size)
|
||||
uint32_t logFileCount_ = 5;
|
||||
};
|
||||
|
||||
} // namespace util
|
||||
} // namespace mbgl
|
||||
@@ -32,6 +32,9 @@ std::string rfc1123(Timestamp);
|
||||
// YYYY-mm-dd HH:MM:SS e.g. "2015-11-26 16:11:23"
|
||||
std::string iso8601(Timestamp);
|
||||
|
||||
// YYYY-mm-ddTHH:MM:SS.MS e.g. "2015-11-26T16:11:23.324Z"
|
||||
std::string iso8601(std::chrono::time_point<std::chrono::system_clock, Milliseconds>);
|
||||
|
||||
Timestamp parseTimestamp(const char *);
|
||||
|
||||
Timestamp parseTimestamp(int32_t timestamp);
|
||||
|
||||
@@ -110,6 +110,8 @@ add_library(maplibre SHARED
|
||||
${PROJECT_SOURCE_DIR}/map_renderer_runnable.hpp
|
||||
${PROJECT_SOURCE_DIR}/native_map_view.cpp
|
||||
${PROJECT_SOURCE_DIR}/native_map_view.hpp
|
||||
${PROJECT_SOURCE_DIR}/native_map_options.cpp
|
||||
${PROJECT_SOURCE_DIR}/native_map_options.hpp
|
||||
${PROJECT_SOURCE_DIR}/offline/offline_manager.cpp
|
||||
${PROJECT_SOURCE_DIR}/offline/offline_manager.hpp
|
||||
${PROJECT_SOURCE_DIR}/offline/offline_region.cpp
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
#include "map_renderer_runnable.hpp"
|
||||
#include "maplibre.hpp"
|
||||
#include "native_map_view.hpp"
|
||||
#include "native_map_options.hpp"
|
||||
#include "util/tile_server_options.hpp"
|
||||
#ifndef MBGL_MODULE_OFFLINE_DISABLE
|
||||
#include "offline/offline_manager.hpp"
|
||||
@@ -109,6 +110,7 @@ void registerNatives(JavaVM* vm) {
|
||||
MapRenderer::registerNative(env);
|
||||
MapRendererRunnable::registerNative(env);
|
||||
NativeMapView::registerNative(env);
|
||||
NativeMapOptions::registerNative(env);
|
||||
|
||||
// Http
|
||||
RegisterNativeHTTPRequest(env);
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
#include "native_map_options.hpp"
|
||||
|
||||
namespace mbgl {
|
||||
namespace android {
|
||||
|
||||
void NativeMapOptions::registerNative(jni::JNIEnv &env) {
|
||||
jni::Class<NativeMapOptions>::Singleton(env);
|
||||
}
|
||||
|
||||
NativeMapOptions::NativeMapOptions(jni::JNIEnv &_env, const jni::Object<NativeMapOptions> &_obj) {}
|
||||
|
||||
NativeMapOptions::~NativeMapOptions() {}
|
||||
|
||||
util::ActionJournalOptions NativeMapOptions::getActionJournalOptions(jni::JNIEnv &env,
|
||||
const jni::Object<NativeMapOptions> &obj) {
|
||||
auto &javaClass = jni::Class<NativeMapOptions>::Singleton(env);
|
||||
|
||||
auto enabledField = javaClass.GetField<jni::jboolean>(env, "actionJournalEnabled");
|
||||
auto pathField = javaClass.GetField<jni::String>(env, "actionJournalPath");
|
||||
auto logFileSizeField = javaClass.GetField<jni::jlong>(env, "actionJournalLogFileSize");
|
||||
auto logFileCountField = javaClass.GetField<jni::jlong>(env, "actionJournalLogFileCount");
|
||||
|
||||
return util::ActionJournalOptions()
|
||||
.enable(obj.Get(env, enabledField))
|
||||
.withPath(jni::Make<std::string>(env, obj.Get(env, pathField)))
|
||||
.withLogFileSize(obj.Get(env, logFileSizeField))
|
||||
.withLogFileCount(obj.Get(env, logFileCountField));
|
||||
}
|
||||
|
||||
float NativeMapOptions::pixelRatio(jni::JNIEnv &env, const jni::Object<NativeMapOptions> &obj) {
|
||||
auto &javaClass = jni::Class<NativeMapOptions>::Singleton(env);
|
||||
auto pixelRatioField = javaClass.GetField<float>(env, "pixelRatio");
|
||||
return obj.Get(env, pixelRatioField);
|
||||
}
|
||||
|
||||
bool NativeMapOptions::crossSourceCollisions(jni::JNIEnv &env, const jni::Object<NativeMapOptions> &obj) {
|
||||
auto &javaClass = jni::Class<NativeMapOptions>::Singleton(env);
|
||||
auto crossSourceCollisionsField = javaClass.GetField<jni::jboolean>(env, "crossSourceCollisions");
|
||||
return obj.Get(env, crossSourceCollisionsField);
|
||||
}
|
||||
|
||||
} // namespace android
|
||||
} // namespace mbgl
|
||||
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
|
||||
#include <jni.h>
|
||||
#include <jni/jni.hpp>
|
||||
#include <mbgl/util/action_journal_options.hpp>
|
||||
|
||||
namespace mbgl {
|
||||
namespace android {
|
||||
|
||||
class NativeMapOptions {
|
||||
public:
|
||||
static constexpr auto Name() { return "org/maplibre/android/maps/NativeMapOptions"; };
|
||||
|
||||
static void registerNative(jni::JNIEnv&);
|
||||
|
||||
NativeMapOptions(jni::JNIEnv&, const jni::Object<NativeMapOptions>&);
|
||||
virtual ~NativeMapOptions();
|
||||
|
||||
static util::ActionJournalOptions getActionJournalOptions(jni::JNIEnv&, const jni::Object<NativeMapOptions>&);
|
||||
static float pixelRatio(jni::JNIEnv&, const jni::Object<NativeMapOptions>&);
|
||||
static bool crossSourceCollisions(jni::JNIEnv&, const jni::Object<NativeMapOptions>&);
|
||||
};
|
||||
|
||||
} // namespace android
|
||||
} // namespace mbgl
|
||||
@@ -16,6 +16,7 @@
|
||||
#include <mbgl/map/map.hpp>
|
||||
#include <mbgl/map/map_options.hpp>
|
||||
#include <mbgl/math/minmax.hpp>
|
||||
#include <mbgl/util/action_journal.hpp>
|
||||
#include <mbgl/util/constants.hpp>
|
||||
#include <mbgl/util/event.hpp>
|
||||
#include <mbgl/util/exception.hpp>
|
||||
@@ -62,11 +63,10 @@ NativeMapView::NativeMapView(jni::JNIEnv& _env,
|
||||
const jni::Object<NativeMapView>& _obj,
|
||||
const jni::Object<FileSource>& jFileSource,
|
||||
const jni::Object<MapRenderer>& jMapRenderer,
|
||||
jni::jfloat pixelRatio_,
|
||||
jni::jboolean crossSourceCollisions_)
|
||||
const jni::Object<NativeMapOptions>& jNativeMapOptions)
|
||||
: javaPeer(_env, _obj),
|
||||
mapRenderer(MapRenderer::getNativePeer(_env, jMapRenderer)),
|
||||
pixelRatio(pixelRatio_) {
|
||||
pixelRatio(NativeMapOptions::pixelRatio(_env, jNativeMapOptions)) {
|
||||
// Get a reference to the JavaVM for callbacks
|
||||
if (_env.GetJavaVM(&vm) < 0) {
|
||||
_env.ExceptionDescribe();
|
||||
@@ -83,14 +83,16 @@ NativeMapView::NativeMapView(jni::JNIEnv& _env,
|
||||
.withPixelRatio(pixelRatio)
|
||||
.withConstrainMode(ConstrainMode::HeightOnly)
|
||||
.withViewportMode(ViewportMode::Default)
|
||||
.withCrossSourceCollisions(crossSourceCollisions_);
|
||||
.withCrossSourceCollisions(NativeMapOptions::crossSourceCollisions(_env, jNativeMapOptions));
|
||||
|
||||
// Create the core map
|
||||
map = std::make_unique<mbgl::Map>(*rendererFrontend,
|
||||
*this,
|
||||
options,
|
||||
mbgl::android::FileSource::getSharedResourceOptions(_env, jFileSource),
|
||||
mbgl::android::FileSource::getSharedClientOptions(_env, jFileSource));
|
||||
map = std::make_unique<mbgl::Map>(
|
||||
*rendererFrontend,
|
||||
*this,
|
||||
options,
|
||||
mbgl::android::FileSource::getSharedResourceOptions(_env, jFileSource),
|
||||
mbgl::android::FileSource::getSharedClientOptions(_env, jFileSource),
|
||||
mbgl::android::NativeMapOptions::getActionJournalOptions(_env, jNativeMapOptions));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -693,6 +695,47 @@ jni::jboolean NativeMapView::getDebug(JNIEnv&) {
|
||||
return map->getDebug() != DebugOptions::NoDebug;
|
||||
}
|
||||
|
||||
jni::Local<jni::Array<jni::String>> NativeMapView::getActionJournalLogFiles(JNIEnv& env) {
|
||||
const auto& actionJournal = map->getActionJournal();
|
||||
if (!actionJournal) {
|
||||
return jni::Local<jni::Array<jni::String>>();
|
||||
}
|
||||
|
||||
const auto& files = actionJournal->getLogFiles();
|
||||
auto jFiles = jni::Array<jni::String>::New(env, files.size());
|
||||
|
||||
uint32_t index = 0;
|
||||
for (const auto& file : files) {
|
||||
jFiles.Set(env, index++, jni::Make<jni::String>(env, file));
|
||||
}
|
||||
|
||||
return jFiles;
|
||||
}
|
||||
|
||||
jni::Local<jni::Array<jni::String>> NativeMapView::getActionJournalLog(JNIEnv& env) {
|
||||
const auto& actionJournal = map->getActionJournal();
|
||||
if (!actionJournal) {
|
||||
return jni::Local<jni::Array<jni::String>>();
|
||||
}
|
||||
|
||||
const auto& events = actionJournal->getLog();
|
||||
auto jEvents = jni::Array<jni::String>::New(env, events.size());
|
||||
|
||||
uint32_t index = 0;
|
||||
for (const auto& event : events) {
|
||||
jEvents.Set(env, index++, jni::Make<jni::String>(env, event));
|
||||
}
|
||||
|
||||
return jEvents;
|
||||
}
|
||||
|
||||
void NativeMapView::clearActionJournalLog(JNIEnv&) {
|
||||
const auto& actionJournal = map->getActionJournal();
|
||||
if (actionJournal) {
|
||||
actionJournal->clearLog();
|
||||
}
|
||||
}
|
||||
|
||||
jni::jboolean NativeMapView::isFullyLoaded(JNIEnv&) {
|
||||
return map->isFullyLoaded();
|
||||
}
|
||||
@@ -1246,8 +1289,7 @@ void NativeMapView::registerNative(jni::JNIEnv& env) {
|
||||
const jni::Object<NativeMapView>&,
|
||||
const jni::Object<FileSource>&,
|
||||
const jni::Object<MapRenderer>&,
|
||||
jni::jfloat,
|
||||
jni::jboolean>,
|
||||
const jni::Object<NativeMapOptions>&>,
|
||||
"nativeInitialize",
|
||||
"nativeDestroy",
|
||||
METHOD(&NativeMapView::resizeView, "nativeResizeView"),
|
||||
@@ -1292,6 +1334,9 @@ void NativeMapView::registerNative(jni::JNIEnv& env) {
|
||||
METHOD(&NativeMapView::addMarkers, "nativeAddMarkers"),
|
||||
METHOD(&NativeMapView::setDebug, "nativeSetDebug"),
|
||||
METHOD(&NativeMapView::getDebug, "nativeGetDebug"),
|
||||
METHOD(&NativeMapView::getActionJournalLogFiles, "nativeGetActionJournalLogFiles"),
|
||||
METHOD(&NativeMapView::getActionJournalLog, "nativeGetActionJournalLog"),
|
||||
METHOD(&NativeMapView::clearActionJournalLog, "nativeClearActionJournalLog"),
|
||||
METHOD(&NativeMapView::isFullyLoaded, "nativeIsFullyLoaded"),
|
||||
METHOD(&NativeMapView::onLowMemory, "nativeOnLowMemory"),
|
||||
METHOD(&NativeMapView::getMetersPerPixelAtLatitude, "nativeGetMetersPerPixelAtLatitude"),
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#include "map/camera_position.hpp"
|
||||
#include "map/image.hpp"
|
||||
#include "style/light.hpp"
|
||||
#include "native_map_options.hpp"
|
||||
#include "bitmap.hpp"
|
||||
|
||||
#include <exception>
|
||||
@@ -49,8 +50,7 @@ public:
|
||||
const jni::Object<NativeMapView>&,
|
||||
const jni::Object<FileSource>&,
|
||||
const jni::Object<MapRenderer>&,
|
||||
jni::jfloat,
|
||||
jni::jboolean);
|
||||
const jni::Object<NativeMapOptions>&);
|
||||
|
||||
virtual ~NativeMapView();
|
||||
|
||||
@@ -199,6 +199,12 @@ public:
|
||||
|
||||
jni::jboolean getDebug(JNIEnv&);
|
||||
|
||||
jni::Local<jni::Array<jni::String>> getActionJournalLogFiles(JNIEnv&);
|
||||
|
||||
jni::Local<jni::Array<jni::String>> getActionJournalLog(JNIEnv&);
|
||||
|
||||
void clearActionJournalLog(JNIEnv&);
|
||||
|
||||
jni::jboolean isFullyLoaded(JNIEnv&);
|
||||
|
||||
jni::jdouble getMetersPerPixelAtLatitude(JNIEnv&, jni::jdouble, jni::jdouble);
|
||||
|
||||
@@ -897,6 +897,49 @@ public final class MapLibreMap {
|
||||
nativeMapView.setDebug(debugActive);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Get the list of action journal log files from oldest to newest.
|
||||
* </p>
|
||||
*/
|
||||
public String[] getActionJournalLogFiles() {
|
||||
return nativeMapView.getActionJournalLogFiles();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Get the action journal events from oldest to newest.
|
||||
* </p>
|
||||
* Each element contains a serialized json object with the event data.
|
||||
* Example
|
||||
* `{
|
||||
* "name" : "onTileAction",
|
||||
* "time" : "2025-04-17T13:13:13.974Z",
|
||||
* "styleName" : "Streets",
|
||||
* "styleURL" : "maptiler://maps/streets",
|
||||
* "event" : {
|
||||
* "action" : "RequestedFromNetwork",
|
||||
* "tileX" : 0,
|
||||
* "tileY" : 0,
|
||||
* "tileZ" : 0,
|
||||
* "overscaledZ" : 0,
|
||||
* "sourceID" : "openmaptiles"
|
||||
* }
|
||||
* }`
|
||||
*/
|
||||
public String[] getActionJournalLog() {
|
||||
return nativeMapView.getActionJournalLog();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Clear stored action journal events.
|
||||
* </p>
|
||||
*/
|
||||
public void clearActionJournalLog() {
|
||||
nativeMapView.clearActionJournalLog();
|
||||
}
|
||||
|
||||
//
|
||||
// API endpoint config
|
||||
//
|
||||
|
||||
@@ -94,6 +94,11 @@ public class MapLibreMapOptions implements Parcelable {
|
||||
|
||||
private boolean crossSourceCollisions = true;
|
||||
|
||||
private boolean actionJournalEnabled = false;
|
||||
private String actionJournalPath = "";
|
||||
private long actionJournalLogFileSize = 1024 * 1024;
|
||||
private long actionJournalLogFileCount = 5;
|
||||
|
||||
/**
|
||||
* Creates a new MapLibreMapOptions object.
|
||||
*
|
||||
@@ -151,6 +156,11 @@ public class MapLibreMapOptions implements Parcelable {
|
||||
pixelRatio = in.readFloat();
|
||||
foregroundLoadColor = in.readInt();
|
||||
crossSourceCollisions = in.readByte() != 0;
|
||||
|
||||
actionJournalEnabled = in.readByte() != 0;
|
||||
actionJournalPath = in.readString();
|
||||
actionJournalLogFileSize = in.readLong();
|
||||
actionJournalLogFileCount = in.readLong();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -182,6 +192,8 @@ public class MapLibreMapOptions implements Parcelable {
|
||||
@NonNull Context context,
|
||||
@Nullable TypedArray typedArray) {
|
||||
float pxlRatio = context.getResources().getDisplayMetrics().density;
|
||||
maplibreMapOptions.actionJournalPath(context.getFilesDir().getAbsolutePath());
|
||||
|
||||
try {
|
||||
maplibreMapOptions.camera(new CameraPosition.Builder(typedArray).build());
|
||||
|
||||
@@ -305,6 +317,16 @@ public class MapLibreMapOptions implements Parcelable {
|
||||
maplibreMapOptions.crossSourceCollisions(
|
||||
typedArray.getBoolean(R.styleable.maplibre_MapView_maplibre_cross_source_collisions, true)
|
||||
);
|
||||
|
||||
maplibreMapOptions.actionJournalEnabled(
|
||||
typedArray.getBoolean(R.styleable.maplibre_MapView_maplibre_actionJournalEnabled, false)
|
||||
);
|
||||
maplibreMapOptions.actionJournalLogFileSize(
|
||||
typedArray.getInteger(R.styleable.maplibre_MapView_maplibre_actionJournalLogFileSize, 1024 * 1024)
|
||||
);
|
||||
maplibreMapOptions.actionJournalLogFileCount(
|
||||
typedArray.getInteger(R.styleable.maplibre_MapView_maplibre_actionJournalLogFileCount, 5)
|
||||
);
|
||||
} finally {
|
||||
typedArray.recycle();
|
||||
}
|
||||
@@ -730,6 +752,65 @@ public class MapLibreMapOptions implements Parcelable {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable action journal event logging, defaults to false.
|
||||
* <p>
|
||||
* If set to true, enables map event file logging (obtainable via MapView#getActionJournalLog)
|
||||
* </p>
|
||||
*
|
||||
* @param actionJournalEnabled true to enable, false to disable
|
||||
* @return This
|
||||
*/
|
||||
@NonNull
|
||||
public MapLibreMapOptions actionJournalEnabled(boolean actionJournalEnabled) {
|
||||
this.actionJournalEnabled = actionJournalEnabled;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the action journal log path.
|
||||
*
|
||||
* @param actionJournalPath Path to be used
|
||||
* @return This
|
||||
*/
|
||||
@NonNull
|
||||
public MapLibreMapOptions actionJournalPath(@NonNull String actionJournalPath) {
|
||||
this.actionJournalPath = actionJournalPath;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the action journal log file size.
|
||||
* <p>
|
||||
* The action journal uses a rolling log with multiple files.
|
||||
* Total log size is equal to `actionJournalLogFileSize * actionJournalLogFileCount`.
|
||||
* </p>
|
||||
*
|
||||
* @param actionJournalLogFileSize maximum log file size
|
||||
* @return This
|
||||
*/
|
||||
@NonNull
|
||||
public MapLibreMapOptions actionJournalLogFileSize(long actionJournalLogFileSize) {
|
||||
this.actionJournalLogFileSize = actionJournalLogFileSize;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the action journal log file count.
|
||||
* <p>
|
||||
* The action journal uses a rolling log with multiple files.
|
||||
* Total log size is equal to `actionJournalLogFileSize * actionJournalLogFileCount`.
|
||||
* </p>
|
||||
*
|
||||
* @param actionJournalLogFileCount maximum number of log files
|
||||
* @return This
|
||||
*/
|
||||
@NonNull
|
||||
public MapLibreMapOptions actionJournalLogFileCount(long actionJournalLogFileCount) {
|
||||
this.actionJournalLogFileCount = actionJournalLogFileCount;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable local ideograph font family, defaults to true.
|
||||
*
|
||||
@@ -821,6 +902,44 @@ public class MapLibreMapOptions implements Parcelable {
|
||||
return crossSourceCollisions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether action journal logging is enabled.
|
||||
*
|
||||
* @return true if enabled
|
||||
*/
|
||||
public boolean getActionJournalEnabled() {
|
||||
return actionJournalEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current configured action journal log path.
|
||||
*
|
||||
* @return log file path
|
||||
*/
|
||||
public String getActionJournalPath() {
|
||||
return actionJournalPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current configured action journal log file size.
|
||||
* Total log size is equal to `actionJournalLogFileSize * actionJournalLogFileCount`.
|
||||
*
|
||||
* @return maximum file size
|
||||
*/
|
||||
public long getActionJournalLogFileSize() {
|
||||
return actionJournalLogFileSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current configured action journal log file count.
|
||||
* Total log size is equal to `actionJournalLogFileSize * actionJournalLogFileCount`.
|
||||
*
|
||||
* @return maximum log files used
|
||||
*/
|
||||
public long getActionJournalLogFileCount() {
|
||||
return actionJournalLogFileCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the flag to render the map surface on top of another surface.
|
||||
*
|
||||
@@ -1205,6 +1324,11 @@ public class MapLibreMapOptions implements Parcelable {
|
||||
dest.writeFloat(pixelRatio);
|
||||
dest.writeInt(foregroundLoadColor);
|
||||
dest.writeByte((byte) (crossSourceCollisions ? 1 : 0));
|
||||
|
||||
dest.writeByte((byte) (actionJournalEnabled ? 1 : 0));
|
||||
dest.writeString(actionJournalPath);
|
||||
dest.writeLong(actionJournalLogFileSize);
|
||||
dest.writeLong(actionJournalLogFileCount);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1325,6 +1449,22 @@ public class MapLibreMapOptions implements Parcelable {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (actionJournalEnabled != options.actionJournalEnabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (actionJournalPath.equals(options.actionJournalPath)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (actionJournalLogFileSize != options.actionJournalLogFileSize) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (actionJournalLogFileCount != options.actionJournalLogFileCount) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1372,6 +1512,10 @@ public class MapLibreMapOptions implements Parcelable {
|
||||
result = 31 * result + Arrays.hashCode(localIdeographFontFamilies);
|
||||
result = 31 * result + (int) pixelRatio;
|
||||
result = 31 * result + (crossSourceCollisions ? 1 : 0);
|
||||
result = 31 * result + (actionJournalEnabled ? 1 : 0);
|
||||
result = 31 * result + (actionJournalPath != null ? actionJournalPath.hashCode() : 0);
|
||||
result = 31 * result + (int) actionJournalLogFileSize;
|
||||
result = 31 * result + (int) actionJournalLogFileCount;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -306,15 +306,13 @@ public class MapView extends FrameLayout implements NativeMapView.ViewCallback {
|
||||
}
|
||||
|
||||
private void initializeDrawingSurface(MapLibreMapOptions options) {
|
||||
mapRenderer = MapRenderer.create(options, getContext(), () -> MapView.this.onSurfaceCreated());
|
||||
mapRenderer = MapRenderer.create(options, getContext(), MapView.this::onSurfaceCreated);
|
||||
renderView = mapRenderer.getView();
|
||||
|
||||
addView(renderView, 0);
|
||||
|
||||
boolean crossSourceCollisions = options.getCrossSourceCollisions();
|
||||
nativeMapView = new NativeMapView(
|
||||
getContext(), getPixelRatio(), crossSourceCollisions, this, mapChangeReceiver, mapRenderer
|
||||
);
|
||||
options.pixelRatio(getPixelRatio());
|
||||
nativeMapView = new NativeMapView(getContext(), options, this, mapChangeReceiver, mapRenderer);
|
||||
}
|
||||
|
||||
private void onSurfaceCreated() {
|
||||
|
||||
@@ -221,6 +221,12 @@ interface NativeMap {
|
||||
|
||||
boolean getDebug();
|
||||
|
||||
String[] getActionJournalLogFiles();
|
||||
|
||||
String[] getActionJournalLog();
|
||||
|
||||
void clearActionJournalLog();
|
||||
|
||||
void setReachability(boolean status);
|
||||
|
||||
void setApiBaseUrl(String baseUrl);
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
package org.maplibre.android.maps;
|
||||
|
||||
public class NativeMapOptions {
|
||||
|
||||
private final float pixelRatio;
|
||||
private final boolean crossSourceCollisions;
|
||||
|
||||
private final boolean actionJournalEnabled;
|
||||
private final String actionJournalPath;
|
||||
private final long actionJournalLogFileSize;
|
||||
private final long actionJournalLogFileCount;
|
||||
|
||||
public NativeMapOptions(MapLibreMapOptions options) {
|
||||
pixelRatio = options.getPixelRatio();
|
||||
crossSourceCollisions = options.getCrossSourceCollisions();
|
||||
|
||||
actionJournalEnabled = options.getActionJournalEnabled();
|
||||
actionJournalPath = options.getActionJournalPath();
|
||||
actionJournalLogFileSize = options.getActionJournalLogFileSize();
|
||||
actionJournalLogFileCount = options.getActionJournalLogFileCount();
|
||||
}
|
||||
|
||||
public NativeMapOptions(float pixelRatio, boolean crossSourceCollisions) {
|
||||
this.pixelRatio = pixelRatio;
|
||||
this.crossSourceCollisions = crossSourceCollisions;
|
||||
|
||||
actionJournalEnabled = false;
|
||||
actionJournalPath = "";
|
||||
actionJournalLogFileSize = 0;
|
||||
actionJournalLogFileCount = 0;
|
||||
}
|
||||
|
||||
public float pixelRatio() {
|
||||
return pixelRatio;
|
||||
}
|
||||
|
||||
public boolean crossSourceCollisions() {
|
||||
return crossSourceCollisions;
|
||||
}
|
||||
|
||||
public boolean actionJournalEnabled() {
|
||||
return actionJournalEnabled;
|
||||
}
|
||||
|
||||
public String actionJournalPath() {
|
||||
return actionJournalPath;
|
||||
}
|
||||
|
||||
public long actionJournalLogFileSize() {
|
||||
return actionJournalLogFileSize;
|
||||
}
|
||||
|
||||
public long actionJournalLogFileCount() {
|
||||
return actionJournalLogFileCount;
|
||||
}
|
||||
}
|
||||
@@ -91,23 +91,29 @@ final class NativeMapView implements NativeMap {
|
||||
// Constructors
|
||||
//
|
||||
|
||||
public NativeMapView(@NonNull final Context context, final boolean crossSourceCollisions,
|
||||
public NativeMapView(@NonNull final Context context,
|
||||
final ViewCallback viewCallback, final StateCallback stateCallback,
|
||||
final MapRenderer mapRenderer) {
|
||||
this(context, context.getResources().getDisplayMetrics().density, crossSourceCollisions, viewCallback,
|
||||
stateCallback, mapRenderer);
|
||||
this(context, new NativeMapOptions(context.getResources().getDisplayMetrics().density, false),
|
||||
viewCallback, stateCallback, mapRenderer);
|
||||
}
|
||||
|
||||
public NativeMapView(final Context context, final float pixelRatio, final boolean crossSourceCollisions,
|
||||
public NativeMapView(@NonNull final Context context, final MapLibreMapOptions options,
|
||||
final ViewCallback viewCallback, final StateCallback stateCallback,
|
||||
final MapRenderer mapRenderer) {
|
||||
this(context, new NativeMapOptions(options), viewCallback, stateCallback, mapRenderer);
|
||||
}
|
||||
|
||||
public NativeMapView(@NonNull final Context context, final NativeMapOptions nativeOptions,
|
||||
final ViewCallback viewCallback, final StateCallback stateCallback,
|
||||
final MapRenderer mapRenderer) {
|
||||
this.mapRenderer = mapRenderer;
|
||||
this.viewCallback = viewCallback;
|
||||
this.fileSource = FileSource.getInstance(context);
|
||||
this.pixelRatio = pixelRatio;
|
||||
this.pixelRatio = nativeOptions.pixelRatio();
|
||||
this.thread = Thread.currentThread();
|
||||
this.stateCallback = stateCallback;
|
||||
nativeInitialize(this, fileSource, mapRenderer, pixelRatio, crossSourceCollisions);
|
||||
nativeInitialize(this, fileSource, mapRenderer, nativeOptions);
|
||||
}
|
||||
|
||||
//
|
||||
@@ -646,6 +652,30 @@ final class NativeMapView implements NativeMap {
|
||||
return nativeGetDebug();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getActionJournalLogFiles() {
|
||||
if (checkState("getActionJournalLogFiles")) {
|
||||
return null;
|
||||
}
|
||||
return nativeGetActionJournalLogFiles();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getActionJournalLog() {
|
||||
if (checkState("getActionJournalLog")) {
|
||||
return null;
|
||||
}
|
||||
return nativeGetActionJournalLog();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearActionJournalLog() {
|
||||
if (checkState("clearActionJournalLog")) {
|
||||
return;
|
||||
}
|
||||
nativeClearActionJournalLog();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFullyLoaded() {
|
||||
if (checkState("isFullyLoaded")) {
|
||||
@@ -1275,8 +1305,7 @@ final class NativeMapView implements NativeMap {
|
||||
private native void nativeInitialize(NativeMapView nativeMap,
|
||||
FileSource fileSource,
|
||||
MapRenderer mapRenderer,
|
||||
float pixelRatio,
|
||||
boolean crossSourceCollisions);
|
||||
NativeMapOptions nativeOptions);
|
||||
|
||||
@Keep
|
||||
private native void nativeDestroy();
|
||||
@@ -1429,6 +1458,15 @@ final class NativeMapView implements NativeMap {
|
||||
@Keep
|
||||
private native boolean nativeGetDebug();
|
||||
|
||||
@Keep
|
||||
private native String[] nativeGetActionJournalLogFiles();
|
||||
|
||||
@Keep
|
||||
private native String[] nativeGetActionJournalLog();
|
||||
|
||||
@Keep
|
||||
private native void nativeClearActionJournalLog();
|
||||
|
||||
@Keep
|
||||
private native boolean nativeIsFullyLoaded();
|
||||
|
||||
|
||||
@@ -17,6 +17,11 @@
|
||||
<public name="maplibre_cross_source_collisions" type="attr" />
|
||||
<public name="maplibre_pixelRatio" type="float" />
|
||||
|
||||
<!--Action Journal-->
|
||||
<public name="maplibre_actionJournalEnabled" type="attr"/>
|
||||
<public name="maplibre_actionJournalLogFileSize" type="attr"/>
|
||||
<public name="maplibre_actionJournalLogFileCount" type="attr"/>
|
||||
|
||||
<!--Camera-->
|
||||
<public name="maplibre_cameraTargetLng" type="attr" />
|
||||
<public name="maplibre_cameraTargetLat" type="attr" />
|
||||
|
||||
@@ -12,6 +12,11 @@
|
||||
<attr name="maplibre_localIdeographFontFamilies" format="reference"/>
|
||||
<attr name="maplibre_cross_source_collisions" format="boolean"/>
|
||||
|
||||
<!--Action Journal-->
|
||||
<attr name="maplibre_actionJournalEnabled" format="boolean"/>
|
||||
<attr name="maplibre_actionJournalLogFileSize" format="integer"/>
|
||||
<attr name="maplibre_actionJournalLogFileCount" format="integer"/>
|
||||
|
||||
<!--Camera-->
|
||||
<attr name="maplibre_cameraTargetLat" format="float"/>
|
||||
<attr name="maplibre_cameraTargetLng" format="float"/>
|
||||
|
||||
@@ -18,6 +18,7 @@ import org.maplibre.android.maps.MapView
|
||||
import org.maplibre.android.utils.ConfigUtils.Companion.getMockedOptions
|
||||
import org.mockito.ArgumentMatchers
|
||||
import org.mockito.Mockito
|
||||
import java.io.File
|
||||
|
||||
class MapLibreTest {
|
||||
private var context: Context? = null
|
||||
@@ -68,8 +69,10 @@ class MapLibreTest {
|
||||
fun testNoInstance() {
|
||||
val displayMetrics = Mockito.mock(DisplayMetrics::class.java)
|
||||
val resources = Mockito.mock(Resources::class.java)
|
||||
val files = Mockito.mock(File::class.java)
|
||||
Mockito.`when`(resources.displayMetrics).thenReturn(displayMetrics)
|
||||
Mockito.`when`(context!!.resources).thenReturn(resources)
|
||||
Mockito.`when`(context!!.filesDir).thenReturn(files)
|
||||
val typedArray = Mockito.mock(TypedArray::class.java)
|
||||
Mockito.`when`(context!!.obtainStyledAttributes(ArgumentMatchers.nullable(AttributeSet::class.java), ArgumentMatchers.any(IntArray::class.java), ArgumentMatchers.anyInt(), ArgumentMatchers.anyInt()))
|
||||
.thenReturn(typedArray)
|
||||
|
||||
@@ -17,7 +17,7 @@ abstract class BaseLayerTest : AppCenter() {
|
||||
|
||||
fun before() {
|
||||
val context = InstrumentationRegistry.getInstrumentation().context
|
||||
nativeMapView = NativeMapView(context, false, null, null, NativeMapViewTest.DummyRenderer(context))
|
||||
nativeMapView = NativeMapView(context, null, null, NativeMapViewTest.DummyRenderer(context))
|
||||
nativeMapView.resizeView(WIDTH, HEIGHT)
|
||||
}
|
||||
|
||||
|
||||
@@ -46,8 +46,10 @@ class NativeMapViewTest : AppCenter() {
|
||||
fun before() {
|
||||
val context = InstrumentationRegistry.getInstrumentation().context
|
||||
val apiKey = MapLibre.getApiKey()
|
||||
val options = NativeMapOptions(2.0f, false)
|
||||
|
||||
MapLibre.getInstance(context, apiKey, WellKnownTileServer.MapTiler)
|
||||
nativeMapView = NativeMapView(context, 2.0f, false, null, null, DummyRenderer(context))
|
||||
nativeMapView = NativeMapView(context, options, null, null, DummyRenderer(context))
|
||||
nativeMapView.resizeView(WIDTH, HEIGHT)
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ package org.maplibre.android.testapp.activity.events
|
||||
import android.app.ActivityManager
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.view.WindowManager
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import org.maplibre.android.MapLibre
|
||||
@@ -18,7 +19,11 @@ import kotlin.time.TimeSource
|
||||
import kotlin.time.TimeSource.Monotonic
|
||||
import org.maplibre.android.log.Logger
|
||||
import org.maplibre.android.log.Logger.INFO
|
||||
import org.maplibre.android.maps.MapLibreMap
|
||||
|
||||
|
||||
|
||||
// # --8<-- [start:ObserverActivity]
|
||||
/**
|
||||
* Test activity showcasing logging observer actions from the core
|
||||
*/
|
||||
@@ -30,6 +35,8 @@ class ObserverActivity : AppCompatActivity(),
|
||||
MapView.OnGlyphsRequestedListener,
|
||||
MapView.OnSpriteLoadedListener,
|
||||
MapView.OnSpriteRequestedListener {
|
||||
// # --8<-- [end:ObserverActivity]
|
||||
|
||||
private lateinit var mapView: MapView
|
||||
|
||||
companion object {
|
||||
@@ -38,9 +45,11 @@ class ObserverActivity : AppCompatActivity(),
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_map_simple)
|
||||
setContentView(R.layout.activity_map_events)
|
||||
mapView = findViewById(R.id.mapView)
|
||||
mapView.onCreate(savedInstanceState)
|
||||
|
||||
// # --8<-- [start:addListeners]
|
||||
mapView.addOnPreCompileShaderListener(this)
|
||||
mapView.addOnPostCompileShaderListener(this)
|
||||
mapView.addOnTileActionListener(this)
|
||||
@@ -48,44 +57,67 @@ class ObserverActivity : AppCompatActivity(),
|
||||
mapView.addOnGlyphsRequestedListener(this)
|
||||
mapView.addOnSpriteLoadedListener(this)
|
||||
mapView.addOnSpriteRequestedListener(this)
|
||||
// # --8<-- [end:addListeners]
|
||||
|
||||
mapView.getMapAsync {
|
||||
it.setStyle(
|
||||
Style.Builder().fromUri(TestStyles.getPredefinedStyleWithFallback("Streets"))
|
||||
Style.Builder().fromUri(TestStyles.DEMOTILES)
|
||||
)
|
||||
}
|
||||
|
||||
// print and clear action journal
|
||||
val fab = findViewById<View>(R.id.fab)
|
||||
fab?.setOnClickListener { _: View? ->
|
||||
mapView.getMapAsync {
|
||||
printActionJournal(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// # --8<-- [start:printActionJournal]
|
||||
fun printActionJournal(map: MapLibreMap) {
|
||||
// configure using `MapLibreMapOptions.actionJournal*` methods
|
||||
|
||||
Logger.i(TAG,"ActionJournal files: \n${map.actionJournalLogFiles.joinToString("\n")}")
|
||||
Logger.i(TAG,"ActionJournal : \n${map.actionJournalLog.joinToString("\n")}")
|
||||
|
||||
// print only the newest events on each call
|
||||
map.clearActionJournalLog()
|
||||
}
|
||||
// # --8<-- [end:printActionJournal]
|
||||
|
||||
private val shaderTimes: HashMap<String, TimeMark> = HashMap()
|
||||
|
||||
public override fun onPreCompileShader(id: Int, type: Int, defines: String) {
|
||||
// # --8<-- [start:mapEvents]
|
||||
override fun onPreCompileShader(id: Int, type: Int, defines: String) {
|
||||
shaderTimes["${id}-${type}-${defines}"] = TimeSource.Monotonic.markNow()
|
||||
Logger.i(TAG, "A new shader is being compiled, shaderID:${id}, backend type:${type}, program configuration:${defines}")
|
||||
}
|
||||
|
||||
public override fun onPostCompileShader(id: Int, type: Int, defines: String) {
|
||||
override fun onPostCompileShader(id: Int, type: Int, defines: String) {
|
||||
val startTime = shaderTimes.get("${id}-${type}-${defines}")
|
||||
if (startTime != null) {
|
||||
Logger.i(TAG, "A shader has been compiled in ${startTime.elapsedNow()}, shaderID:${id}, backend type:${type}, program configuration:${defines}")
|
||||
}
|
||||
}
|
||||
|
||||
public override fun onGlyphsRequested(fontStack: Array<String>, rangeStart: Int, rangeEnd: Int) {
|
||||
override fun onGlyphsRequested(fontStack: Array<String>, rangeStart: Int, rangeEnd: Int) {
|
||||
Logger.i(TAG, "Glyphs are being requested for the font stack $fontStack, ranging from $rangeStart to $rangeEnd")
|
||||
}
|
||||
|
||||
public override fun onGlyphsLoaded(fontStack: Array<String>, rangeStart: Int, rangeEnd: Int) {
|
||||
override fun onGlyphsLoaded(fontStack: Array<String>, rangeStart: Int, rangeEnd: Int) {
|
||||
Logger.i(TAG, "Glyphs have been loaded for the font stack $fontStack, ranging from $rangeStart to $rangeEnd")
|
||||
}
|
||||
|
||||
public override fun onSpriteRequested(id: String, url: String) {
|
||||
override fun onSpriteRequested(id: String, url: String) {
|
||||
Logger.i(TAG, "The sprite $id has been requested from $url")
|
||||
}
|
||||
|
||||
public override fun onSpriteLoaded(id: String, url: String) {
|
||||
override fun onSpriteLoaded(id: String, url: String) {
|
||||
Logger.i(TAG, "The sprite $id has been loaded from $url")
|
||||
}
|
||||
|
||||
public override fun onTileAction(op: TileOperation, x: Int, y: Int, z: Int, wrap: Int, overscaledZ: Int, sourceID: String) {
|
||||
override fun onTileAction(op: TileOperation, x: Int, y: Int, z: Int, wrap: Int, overscaledZ: Int, sourceID: String) {
|
||||
val tile = "X:${x}, Y:${y}, Z:${z}, Wrap:${wrap}, OverscaledZ:${overscaledZ}, SourceID:${sourceID}"
|
||||
when (op) {
|
||||
TileOperation.RequestedFromCache -> Logger.i(TAG, "Requesting tile ${tile} from the cache")
|
||||
@@ -99,6 +131,7 @@ class ObserverActivity : AppCompatActivity(),
|
||||
TileOperation.NullOp -> Logger.e(TAG, "An unknown tile operation was emitted for tile ${tile}")
|
||||
}
|
||||
}
|
||||
// # --8<-- [end:mapEvents]
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:id="@+id/coordinator_layout">
|
||||
|
||||
<!-- -8<- [start:MapView] -->
|
||||
<org.maplibre.android.maps.MapView
|
||||
android:id="@id/mapView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:maplibre_actionJournalEnabled="true"
|
||||
app:maplibre_actionJournalLogFileSize="1024"
|
||||
app:maplibre_actionJournalLogFileCount="2"/>
|
||||
<!-- -8<- [end:MapView] -->
|
||||
|
||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
android:id="@+id/fab"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="end|bottom"
|
||||
android:layout_margin="@dimen/fab_margin"
|
||||
android:src="@drawable/ic_print"
|
||||
app:backgroundTint="@color/accent"/>
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
80
platform/android/docs/observability/action-journal.md
Normal file
80
platform/android/docs/observability/action-journal.md
Normal file
@@ -0,0 +1,80 @@
|
||||
# Action Journal
|
||||
|
||||
{{ activity_source_note("ObserverActivity.kt") }}
|
||||
|
||||
The Action Journal provides functionality for persistent logging of top level map events.
|
||||
|
||||
<!-- NOTE: keep this text in sync with platform/ios/MapLibre.docc/ActionJournalExample.md -->
|
||||
|
||||
Its primary use case is to assist in debugging problematic sessions and crashes by offering additional insight into the actions performed by the map at the time of failure. Data is stored in human readable format, which is useful for analyzing individual cases, but can also be easily translated and aggregated into a database, allowing for efficient analysis of multiple cases and helping to identify recurring patterns (Google BigQuery, AWS Glue + S3 + Athena, etc).
|
||||
|
||||
We are always interested in improving observability, so if you have a special use case, feel free to [open an issue or pull request](https://github.com/maplibre/maplibre-native) to extend the types of observability methods.
|
||||
|
||||
## Enabling the Action Journal
|
||||
|
||||
You can enable the action journal either through XML:
|
||||
|
||||
```xml
|
||||
--8<-- "MapLibreAndroidTestApp/src/main/res/layout/activity_map_events.xml:MapView"
|
||||
```
|
||||
|
||||
Or by passing the corresponding options with `MapLibreMapOptions` to `MapView`. For more information see [Configuration](../configuration.md).
|
||||
|
||||
## Logging implementation details
|
||||
|
||||
The logging is implemented using rolling files with a size based policy:
|
||||
|
||||
- A new file is created when the current log size exceeds `MapLibreMapOptions.actionJournalLogFileSize`.
|
||||
- When the maximum number of files exceeds `MapLibreMapOptions.actionJournalLogFileCount`:
|
||||
- The oldest one is deleted.
|
||||
- The remaining files are renamed sequentially to maintain the naming convention `action_journal.0.log` through `action_journal.{logFileCount - 1}.log`.
|
||||
- Each file contains one event per line.
|
||||
- All files are stored in an umbrella `action_journal` directory at `MapLibreMapOptions.actionJournalPath`.
|
||||
|
||||
See also: `MapLibreMap`, `MapLibreMapOptions`.
|
||||
|
||||
## Event format
|
||||
|
||||
Events are stored as JSON objects with the following format:
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
| :---- | :--: | :------: | :---------- |
|
||||
| name | string | true | event name |
|
||||
| time | string | true | event time ([ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) with milliseconds) |
|
||||
| styleName | string | false | currently loaded style name |
|
||||
| styleURL | string | false | currently loaded style URL |
|
||||
| clientName | string | false | |
|
||||
| clientVersion | string | false | |
|
||||
| event | object | false | event specific data - consists of encoded values of the parameters passed to their `MLNMapViewDelegate` counterparts
|
||||
|
||||
|
||||
```
|
||||
{
|
||||
"name" : "onTileAction",
|
||||
"time" : "2025-04-17T13:13:13.974Z",
|
||||
"styleName" : "Streets",
|
||||
"styleURL" : "maptiler://maps/streets",
|
||||
"clientName" : "App",
|
||||
"clientVersion" : "1.0",
|
||||
"event" : {
|
||||
"action" : "RequestedFromNetwork",
|
||||
"tileX" : 0,
|
||||
"tileY" : 0,
|
||||
"tileZ" : 0,
|
||||
"overscaledZ" : 0,
|
||||
"sourceID" : "openmaptiles"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```kotlin
|
||||
--8<-- "MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/events/ObserverActivity.kt:printActionJournal"
|
||||
```
|
||||
|
||||
## Alternative
|
||||
|
||||
The implementation is kept close to the core events to minimize additional locking and avoid platform-specific conversions and calls. As a result customization options and extensibility is limited.
|
||||
|
||||
For greater flexibility, consider using the `MapView` event interface (see also `MapChangeReceiver`). It provides hooks for most Action Journal events and allows for more customizable querying and storage of map data. However, this comes at the cost of added complexity. See [Observe Map Events](./observe-map-events.md) to learn about the map events that you can listen for, which mirror the events available in the action journal.
|
||||
21
platform/android/docs/observability/observe-map-events.md
Normal file
21
platform/android/docs/observability/observe-map-events.md
Normal file
@@ -0,0 +1,21 @@
|
||||
# Observe Map Events
|
||||
|
||||
{{ activity_source_note("ObserverActivity.kt") }}
|
||||
|
||||
You can observe-low level map events that are happening by registering listeners to a `MapView`. Below you can see the the map events that are currently available.
|
||||
|
||||
```kotlin
|
||||
--8<-- "MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/events/ObserverActivity.kt:mapEvents"
|
||||
```
|
||||
|
||||
You need to register them with these APIs:
|
||||
|
||||
```kotlin
|
||||
--8<-- "MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/events/ObserverActivity.kt:addListeners"
|
||||
```
|
||||
|
||||
In this case we implement them by implementing the interfaces below in the activity class, but you could also use lambdas.
|
||||
|
||||
```kotlin
|
||||
--8<-- "MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/events/ObserverActivity.kt:ObserverActivity"
|
||||
```
|
||||
@@ -50,6 +50,7 @@ MLN_GENERATED_DARWIN_TEST_CODE = [
|
||||
]
|
||||
|
||||
MLN_DARWIN_OBJC_HEADERS = [
|
||||
"src/MLNActionJournalOptions.h",
|
||||
"src/MLNAnnotation.h",
|
||||
"src/MLNAttributedExpression.h",
|
||||
"src/MLNAttributionInfo.h",
|
||||
@@ -71,6 +72,7 @@ MLN_DARWIN_OBJC_HEADERS = [
|
||||
"src/MLNLocationManager.h",
|
||||
"src/MLNLoggingConfiguration.h",
|
||||
"src/MLNMapCamera.h",
|
||||
"src/MLNMapOptions.h",
|
||||
"src/MLNMapSnapshotter.h",
|
||||
"src/MLNMultiPoint.h",
|
||||
"src/MLNNetworkConfiguration.h",
|
||||
@@ -126,6 +128,7 @@ MLN_DARWIN_OBJCPP_HEADERS = [
|
||||
]
|
||||
|
||||
MLN_DARWIN_PRIVATE_HEADERS = [
|
||||
"src/MLNActionJournalOptions_Private.h",
|
||||
"src/MLNAttributionInfo_Private.h",
|
||||
"src/MLNComputedShapeSource_Private.h",
|
||||
"src/MLNCustomStyleLayer_Private.h",
|
||||
@@ -161,6 +164,7 @@ MLN_DARWIN_PRIVATE_HEADERS = [
|
||||
]
|
||||
|
||||
MLN_DARWIN_PUBLIC_OBJCPP_SOURCE = [
|
||||
"src/MLNActionJournalOptions.mm",
|
||||
"src/MLNAttributionInfo.mm",
|
||||
"src/MLNBackendResource.mm",
|
||||
"src/MLNComputedShapeSource.mm",
|
||||
@@ -173,6 +177,7 @@ MLN_DARWIN_PUBLIC_OBJCPP_SOURCE = [
|
||||
"src/MLNImageSource.mm",
|
||||
"src/MLNLoggingConfiguration.mm",
|
||||
"src/MLNMapCamera.mm",
|
||||
"src/MLNMapOptions.mm",
|
||||
"src/MLNMapSnapshotter.mm",
|
||||
"src/MLNMultiPoint.mm",
|
||||
"src/MLNNetworkConfiguration.mm",
|
||||
|
||||
35
platform/darwin/src/MLNActionJournalOptions.h
Normal file
35
platform/darwin/src/MLNActionJournalOptions.h
Normal file
@@ -0,0 +1,35 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "MLNFoundation.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
The ``MLNActionJournalOptions`` defines action journal properties such as path, log size, etc...
|
||||
*/
|
||||
MLN_EXPORT
|
||||
@interface MLNActionJournalOptions : NSObject
|
||||
|
||||
/**
|
||||
* Enable/disable journal logging
|
||||
*/
|
||||
@property (nonatomic) BOOL enabled;
|
||||
|
||||
/**
|
||||
* Local file path.
|
||||
*/
|
||||
@property (nonatomic, nonnull) NSString* path;
|
||||
|
||||
/**
|
||||
* Log file size (total journal size is equal to `logFileSize * logFileCount`)
|
||||
*/
|
||||
@property (nonatomic) NSInteger logFileSize;
|
||||
|
||||
/**
|
||||
* Maximum number of log files
|
||||
*/
|
||||
@property (nonatomic) NSInteger logFileCount;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
54
platform/darwin/src/MLNActionJournalOptions.mm
Normal file
54
platform/darwin/src/MLNActionJournalOptions.mm
Normal file
@@ -0,0 +1,54 @@
|
||||
#import "MLNActionJournalOptions.h"
|
||||
#import <mbgl/util/action_journal_options.hpp>
|
||||
|
||||
@implementation MLNActionJournalOptions {
|
||||
std::unique_ptr<mbgl::util::ActionJournalOptions> _actionJournalOptionsInternal;
|
||||
}
|
||||
|
||||
- (instancetype)init {
|
||||
if (self = [super init]) {
|
||||
_actionJournalOptionsInternal = std::make_unique<mbgl::util::ActionJournalOptions>();
|
||||
|
||||
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
|
||||
_actionJournalOptionsInternal->withPath([paths.firstObject stringByAppendingPathComponent:@""].UTF8String);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (mbgl::util::ActionJournalOptions)getCoreOptions {
|
||||
return *_actionJournalOptionsInternal;
|
||||
}
|
||||
|
||||
- (void)setEnabled:(BOOL)value {
|
||||
_actionJournalOptionsInternal->enable(value);
|
||||
}
|
||||
|
||||
- (BOOL)enabled {
|
||||
return _actionJournalOptionsInternal->enabled();
|
||||
}
|
||||
|
||||
- (void)setPath:(NSString*)value {
|
||||
_actionJournalOptionsInternal->withPath(value.UTF8String);
|
||||
}
|
||||
|
||||
- (NSString*)path {
|
||||
return [NSString stringWithUTF8String:_actionJournalOptionsInternal->path().c_str()];
|
||||
}
|
||||
|
||||
- (void)setLogFileSize:(NSInteger)value {
|
||||
_actionJournalOptionsInternal->withLogFileSize(static_cast<uint32_t>(value));
|
||||
}
|
||||
|
||||
- (NSInteger)logFileSize {
|
||||
return _actionJournalOptionsInternal->logFileSize();
|
||||
}
|
||||
|
||||
- (void)setLogFileCount:(NSInteger)value {
|
||||
_actionJournalOptionsInternal->withLogFileCount(static_cast<uint32_t>(value));
|
||||
}
|
||||
|
||||
- (NSInteger)logFileCount {
|
||||
return _actionJournalOptionsInternal->logFileCount();
|
||||
}
|
||||
|
||||
@end
|
||||
12
platform/darwin/src/MLNActionJournalOptions_Private.h
Normal file
12
platform/darwin/src/MLNActionJournalOptions_Private.h
Normal file
@@ -0,0 +1,12 @@
|
||||
#import <mbgl/util/action_journal_options.hpp>
|
||||
#import "MLNActionJournalOptions.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface MLNActionJournalOptions (Private)
|
||||
|
||||
- (mbgl::util::ActionJournalOptions)getCoreOptions;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
37
platform/darwin/src/MLNMapOptions.h
Normal file
37
platform/darwin/src/MLNMapOptions.h
Normal file
@@ -0,0 +1,37 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "MLNActionJournalOptions.h"
|
||||
#import "MLNFoundation.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
The ``MLNMapOptions`` object provides a way to set map properties for each instance
|
||||
*/
|
||||
MLN_EXPORT
|
||||
@interface MLNMapOptions : NSObject
|
||||
|
||||
/**
|
||||
URL of the map style to display. The URL may be a full HTTP
|
||||
or HTTPS URL, a canonical URL or a path to a local file relative
|
||||
to the application’s resource path. Specify `nil` for the default style.
|
||||
*/
|
||||
@property (nonatomic, nullable) NSURL *styleURL;
|
||||
|
||||
/**
|
||||
JSON string of the map style to display. The JSON must conform to the
|
||||
<a href="https://maplibre.org/maplibre-style-spec/">MapLibre Style Specification</a>.
|
||||
Specify `nil` for the default style.
|
||||
Ignored if `styleURL` is set to a non-nil value.
|
||||
*/
|
||||
|
||||
@property (nonatomic, nullable) NSString *styleJSON;
|
||||
|
||||
/**
|
||||
Action journal options
|
||||
*/
|
||||
@property (nonatomic, nonnull) MLNActionJournalOptions *actionJournalOptions;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
23
platform/darwin/src/MLNMapOptions.mm
Normal file
23
platform/darwin/src/MLNMapOptions.mm
Normal file
@@ -0,0 +1,23 @@
|
||||
#import "MLNMapOptions.h"
|
||||
|
||||
@interface MLNMapOptions ()
|
||||
|
||||
@end
|
||||
|
||||
@implementation MLNMapOptions
|
||||
|
||||
-(instancetype _Nonnull)init
|
||||
{
|
||||
self = [super init];
|
||||
if (self)
|
||||
{
|
||||
_styleURL = nil;
|
||||
_styleJSON = nil;
|
||||
|
||||
_actionJournalOptions = [[MLNActionJournalOptions alloc] init];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -4,6 +4,7 @@ load("@rules_apple//apple:apple.bzl", "apple_static_xcframework", "apple_xcframe
|
||||
load("@rules_apple//apple:ios.bzl", "ios_application", "ios_framework")
|
||||
load("@rules_apple//apple:resources.bzl", "apple_resource_bundle")
|
||||
load("@rules_apple//apple:versioning.bzl", "apple_bundle_version")
|
||||
|
||||
load(
|
||||
"@rules_xcodeproj//xcodeproj:defs.bzl",
|
||||
"top_level_target",
|
||||
|
||||
84
platform/ios/MapLibre.docc/ActionJournalExample.md
Normal file
84
platform/ios/MapLibre.docc/ActionJournalExample.md
Normal file
@@ -0,0 +1,84 @@
|
||||
# Action Journal
|
||||
|
||||
Learn about the ``MLNMapView`` methods for logging and viewing map actions.
|
||||
|
||||
<!-- NOTE: keep this text in sync with platform/android/docs/observability/action-journal.md -->
|
||||
|
||||
The Action Journal provides functionality for persistent logging of top level map events.
|
||||
|
||||
Its primary use case is to assist in debugging problematic sessions and crashes by offering additional insight into the actions performed by the map at the time of failure. Data is stored in human readable format, which is useful for analyzing individual cases, but can also be easily translated and aggregated into a database, allowing for efficient analysis of multiple cases and helping to identify recurring patterns (Google BigQuery, AWS Glue + S3 + Athena, etc).
|
||||
|
||||
We are always interested in improving observability, so if you have a special use case, feel free to [open an issue or pull request](https://github.com/maplibre/maplibre-native) to extend the types of observability methods.
|
||||
|
||||
## Logging implementation details
|
||||
|
||||
The logging is implemented using rolling files with a size based policy:
|
||||
|
||||
- A new file is created when the current log size exceeds ``MLNActionJournalOptions/logFileSize``.
|
||||
- When the maximum number of files exceeds ``MLNActionJournalOptions/logFileCount``:
|
||||
- The oldest one is deleted.
|
||||
- The remaining files are renamed sequentially to maintain the naming convention `action_journal.0.log` through `action_journal.{logFileCount - 1}.log`.
|
||||
- Each file contains one event per line.
|
||||
- All files are stored in an umbrella "action_journal" directory at ``MLNActionJournalOptions/path``.
|
||||
|
||||
See also: ``MLNSettings``, ``MLNActionJournalOptions``.
|
||||
|
||||
## Event format
|
||||
|
||||
Events are stored as JSON objects with the following format:
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
| :---- | :--: | :------: | :---------- |
|
||||
| name | string | true | event name |
|
||||
| time | string | true | event time ([ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) with milliseconds) |
|
||||
| styleName | string | false | currently loaded style name |
|
||||
| styleURL | string | false | currently loaded style URL |
|
||||
| clientName | string | false | |
|
||||
| clientVersion | string | false | |
|
||||
| event | object | false | event specific data - consists of encoded values of the parameters passed to their ``MLNMapViewDelegate`` counterparts
|
||||
|
||||
```
|
||||
{
|
||||
"name" : "onTileAction",
|
||||
"time" : "2025-04-17T13:13:13.974Z",
|
||||
"styleName" : "Streets",
|
||||
"styleURL" : "maptiler://maps/streets",
|
||||
"clientName" : "App",
|
||||
"clientVersion" : "1.0",
|
||||
"event" : {
|
||||
"action" : "RequestedFromNetwork",
|
||||
"tileX" : 0,
|
||||
"tileY" : 0,
|
||||
"tileZ" : 0,
|
||||
"overscaledZ" : 0,
|
||||
"sourceID" : "openmaptiles"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
Enabling the action journal.
|
||||
|
||||
<!-- include-example(actionJournalOptions) -->
|
||||
|
||||
```swift
|
||||
MLNSettings.actionJournalOptions.enabled = true
|
||||
```
|
||||
|
||||
<!-- include-example(ObserverExampleActionJournal) -->
|
||||
|
||||
```swift
|
||||
@objc func printActionJournal() {
|
||||
print("ActionJournalLog files: \(mapView.getActionJournalLogFiles())")
|
||||
print("ActionJournalLog : \(mapView.getActionJournalLog())")
|
||||
// print only the newest events on each call
|
||||
mapView.clearActionJournalLog()
|
||||
}
|
||||
```
|
||||
|
||||
## Alternative
|
||||
|
||||
The implementation is kept close to the core events to minimize additional locking and avoid platform-specific conversions and calls. As a result customization options and extensibility is limited.
|
||||
|
||||
For greater flexibility, consider using the ``MLNMapViewDelegate`` interface. It provides hooks for most Action Journal events and allows for more customizable querying and storage of map data. However, this comes at the cost of added complexity. See [Observe Low-Level Events](./ObserverExample.md) to learn about the map events that you can listen for, which mirror the events available in the action journal.
|
||||
@@ -49,9 +49,13 @@ Powerful, free and open-source mapping toolkit with full control over data sourc
|
||||
- <doc:OfflinePackExample>
|
||||
- <doc:ManageOfflineRegionsExample>
|
||||
|
||||
### Advanced
|
||||
### Observability
|
||||
|
||||
- <doc:ObserverExample>
|
||||
- <doc:ActionJournalExample>
|
||||
|
||||
### Advanced
|
||||
|
||||
- <doc:CustomStyleLayerExample>
|
||||
|
||||
### Other Articles
|
||||
|
||||
@@ -4,11 +4,18 @@ import UIKit
|
||||
|
||||
class ObserverExampleView: UIViewController, MLNMapViewDelegate {
|
||||
var mapView: MLNMapView!
|
||||
var button: UIButton!
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
mapView = MLNMapView(frame: view.bounds, styleURL: AMERICANA_STYLE)
|
||||
// #-example-code(actionJournalOptions)
|
||||
let options = MLNMapOptions()
|
||||
options.actionJournalOptions.enabled = true
|
||||
options.styleURL = AMERICANA_STYLE
|
||||
mapView = MLNMapView(frame: view.bounds, options: options)
|
||||
// #-end-example-code
|
||||
|
||||
mapView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
||||
|
||||
mapView.setCenter(
|
||||
@@ -19,8 +26,25 @@ class ObserverExampleView: UIViewController, MLNMapViewDelegate {
|
||||
view.addSubview(mapView)
|
||||
|
||||
mapView.delegate = self
|
||||
|
||||
button = UIButton(frame: CGRect(x: view.bounds.width / 2 - 100, y: view.bounds.height - 200, width: 200, height: 30))
|
||||
button.setTitle("Print Action Journal", for: .normal)
|
||||
button.layer.cornerRadius = 15
|
||||
button.backgroundColor = UIColor(red: 0.96, green: 0.65, blue: 0.14, alpha: 1.0)
|
||||
button.addTarget(self, action: #selector(printActionJournal), for: .touchUpInside)
|
||||
view.addSubview(button)
|
||||
}
|
||||
|
||||
// #-example-code(ObserverExampleActionJournal)
|
||||
@objc func printActionJournal() {
|
||||
print("ActionJournalLog files: \(mapView.getActionJournalLogFiles())")
|
||||
print("ActionJournalLog : \(mapView.getActionJournalLog())")
|
||||
// print only the newest events on each call
|
||||
mapView.clearActionJournalLog()
|
||||
}
|
||||
|
||||
// #-end-example-code
|
||||
|
||||
// #-example-code(ObserverExampleShaders)
|
||||
func mapView(_: MLNMapView, shaderWillCompile id: Int, backend: Int, defines: String) {
|
||||
print("A new shader is being compiled - shaderID:\(id), backend type:\(backend), program configuration:\(defines)")
|
||||
|
||||
@@ -4,6 +4,12 @@ target_include_directories(
|
||||
${PROJECT_SOURCE_DIR}/platform/ios/src
|
||||
)
|
||||
|
||||
target_link_libraries(
|
||||
mbgl-core
|
||||
PRIVATE
|
||||
MapLibreNative::Base::Extras::filesystem
|
||||
)
|
||||
|
||||
file(GLOB_RECURSE IOS_SDK_SOURCE_FILES
|
||||
"${PROJECT_SOURCE_DIR}/platform/darwin/src/*.m"
|
||||
"${PROJECT_SOURCE_DIR}/platform/darwin/src/*.mm"
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#import "MLNFoundation.h"
|
||||
#import "MLNGeometry.h"
|
||||
#import "MLNMapCamera.h"
|
||||
#import "MLNMapOptions.h"
|
||||
#import "MLNStyle.h"
|
||||
#import "MLNTypes.h"
|
||||
|
||||
@@ -219,6 +220,16 @@ MLN_EXPORT
|
||||
*/
|
||||
- (instancetype)initWithFrame:(CGRect)frame styleJSON:(NSString *)styleJSON;
|
||||
|
||||
/**
|
||||
Initializes and returns a newly allocated map view with the specified frame
|
||||
and the default style.
|
||||
|
||||
@param frame The frame for the view, measured in points.
|
||||
@param options The map instance options
|
||||
@return An initialized map view.
|
||||
*/
|
||||
- (instancetype)initWithFrame:(CGRect)frame options:(MLNMapOptions *)options;
|
||||
|
||||
// MARK: Accessing the Delegate
|
||||
|
||||
/**
|
||||
@@ -2146,6 +2157,40 @@ vertically on the map.
|
||||
*/
|
||||
@property (nonatomic) MLNMapDebugMaskOptions debugMask;
|
||||
|
||||
/**
|
||||
Get the list of action journal log files from oldest to newest.
|
||||
|
||||
@return An array of log file paths.
|
||||
*/
|
||||
- (NSArray<NSString *> *)getActionJournalLogFiles;
|
||||
|
||||
/**
|
||||
Get the action journal events from oldest to newest.
|
||||
|
||||
Each element contains a serialized json object with the event data.
|
||||
Example
|
||||
`{
|
||||
"name" : "onTileAction",
|
||||
"time" : "2025-04-17T13:13:13.974Z",
|
||||
"styleName" : "Streets",
|
||||
"styleURL" : "maptiler://maps/streets",
|
||||
"event" : {
|
||||
"action" : "RequestedFromNetwork",
|
||||
"tileX" : 0,
|
||||
"tileY" : 0,
|
||||
"tileZ" : 0,
|
||||
"overscaledZ" : 0,
|
||||
"sourceID" : "openmaptiles"
|
||||
}
|
||||
}`
|
||||
*/
|
||||
- (NSArray<NSString *> *)getActionJournalLog;
|
||||
|
||||
/**
|
||||
Clear stored action journal events.
|
||||
*/
|
||||
- (void)clearActionJournalLog;
|
||||
|
||||
- (MLNBackendResource *)backendResource;
|
||||
|
||||
/**
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include <mbgl/style/layers/custom_layer.hpp>
|
||||
#include <mbgl/renderer/renderer.hpp>
|
||||
#include <mbgl/math/wrap.hpp>
|
||||
#include <mbgl/util/action_journal.hpp>
|
||||
#include <mbgl/util/client_options.hpp>
|
||||
#include <mbgl/util/exception.hpp>
|
||||
#include <mbgl/util/geo.hpp>
|
||||
@@ -68,6 +69,7 @@
|
||||
#import "MLNNetworkConfiguration_Private.h"
|
||||
#import "MLNReachability.h"
|
||||
#import "MLNSettings_Private.h"
|
||||
#import "MLNActionJournalOptions_Private.h"
|
||||
#import "MLNMapProjection.h"
|
||||
|
||||
#include <algorithm>
|
||||
@@ -503,7 +505,7 @@ public:
|
||||
{
|
||||
MLNLogInfo(@"Starting %@ initialization.", NSStringFromClass([self class]));
|
||||
MLNLogDebug(@"Initializing frame: %@", NSStringFromCGRect(frame));
|
||||
[self commonInit];
|
||||
[self commonInitWithOptions:nil];
|
||||
self.styleURL = nil;
|
||||
MLNLogInfo(@"Finalizing %@ initialization.", NSStringFromClass([self class]));
|
||||
}
|
||||
@@ -516,7 +518,7 @@ public:
|
||||
{
|
||||
MLNLogInfo(@"Starting %@ initialization.", NSStringFromClass([self class]));
|
||||
MLNLogDebug(@"Initializing frame: %@ styleURL: %@", NSStringFromCGRect(frame), styleURL);
|
||||
[self commonInit];
|
||||
[self commonInitWithOptions:nil];
|
||||
self.styleURL = styleURL;
|
||||
MLNLogInfo(@"Finalizing %@ initialization.", NSStringFromClass([self class]));
|
||||
}
|
||||
@@ -529,7 +531,7 @@ public:
|
||||
{
|
||||
MLNLogInfo(@"Starting %@ initialization.", NSStringFromClass([self class]));
|
||||
MLNLogDebug(@"Initializing frame: %@ styleJSON: %@", NSStringFromCGRect(frame), styleJSON);
|
||||
[self commonInit];
|
||||
[self commonInitWithOptions:nil];
|
||||
self.styleJSON = styleJSON;
|
||||
_initialStyleJSON = [styleJSON copy];
|
||||
MLNLogInfo(@"Finalizing %@ initialization.", NSStringFromClass([self class]));
|
||||
@@ -537,12 +539,39 @@ public:
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame options:(MLNMapOptions *)options
|
||||
{
|
||||
if (self = [super initWithFrame:frame])
|
||||
{
|
||||
MLNLogInfo(@"Starting %@ initialization.", NSStringFromClass([self class]));
|
||||
MLNLogDebug(@"Initializing frame: %@ with options", NSStringFromCGRect(frame));
|
||||
[self commonInitWithOptions:options];
|
||||
|
||||
if (options)
|
||||
{
|
||||
if (options.styleURL) {
|
||||
self.styleURL = options.styleURL;
|
||||
} else if (options.styleJSON) {
|
||||
self.styleJSON = options.styleJSON;
|
||||
_initialStyleJSON = [options.styleJSON copy];
|
||||
} else {
|
||||
self.styleURL = nil;
|
||||
}
|
||||
} else {
|
||||
self.styleURL = nil;
|
||||
}
|
||||
|
||||
MLNLogInfo(@"Finalizing %@ initialization.", NSStringFromClass([self class]));
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithCoder:(nonnull NSCoder *)decoder
|
||||
{
|
||||
if (self = [super initWithCoder:decoder])
|
||||
{
|
||||
MLNLogInfo(@"Starting %@ initialization.", NSStringFromClass([self class]));
|
||||
[self commonInit];
|
||||
[self commonInitWithOptions:nil];
|
||||
self.styleURL = nil;
|
||||
MLNLogInfo(@"Finalizing %@ initialization.", NSStringFromClass([self class]));
|
||||
}
|
||||
@@ -621,8 +650,13 @@ public:
|
||||
return _rendererFrontend->getRenderer();
|
||||
}
|
||||
|
||||
- (void)commonInit
|
||||
- (void)commonInitWithOptions:(MLNMapOptions*)mlnMapoptions
|
||||
{
|
||||
if (mlnMapoptions == nil)
|
||||
{
|
||||
mlnMapoptions = [[MLNMapOptions alloc] init];
|
||||
}
|
||||
|
||||
_opaque = NO;
|
||||
|
||||
// setup accessibility
|
||||
@@ -683,8 +717,11 @@ public:
|
||||
resourceOptions.withApiKey([apiKey UTF8String]);
|
||||
}
|
||||
|
||||
const mbgl::util::ActionJournalOptions& actionJournalOptions = [mlnMapoptions.actionJournalOptions getCoreOptions];
|
||||
|
||||
NSAssert(!_mbglMap, @"_mbglMap should be NULL");
|
||||
_mbglMap = std::make_unique<mbgl::Map>(*_rendererFrontend, *_mbglView, mapOptions, resourceOptions, clientOptions);
|
||||
_mbglMap = std::make_unique<mbgl::Map>(*_rendererFrontend, *_mbglView, mapOptions,
|
||||
resourceOptions, clientOptions, actionJournalOptions);
|
||||
|
||||
// start paused if launch into the background
|
||||
if (background) {
|
||||
@@ -7525,6 +7562,50 @@ static void *windowScreenContext = &windowScreenContext;
|
||||
_mbglMap->triggerRepaint();
|
||||
}
|
||||
|
||||
- (NSArray<NSString*>*)getActionJournalLogFiles
|
||||
{
|
||||
const auto& actionJournal = _mbglMap->getActionJournal();
|
||||
if (!actionJournal) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
const auto& files = actionJournal->getLogFiles();
|
||||
NSMutableArray<NSString*>* objcFiles = [NSMutableArray new];
|
||||
|
||||
for (const auto& file : files) {
|
||||
[objcFiles addObject:[NSString stringWithUTF8String:file.c_str()]];
|
||||
}
|
||||
|
||||
return objcFiles;
|
||||
}
|
||||
|
||||
- (NSArray<NSString*>*)getActionJournalLog
|
||||
{
|
||||
const auto& actionJournal = _mbglMap->getActionJournal();
|
||||
if (!actionJournal) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
const auto& log = actionJournal->getLog();
|
||||
NSMutableArray<NSString*>* objcLog = [NSMutableArray new];
|
||||
|
||||
for (const auto& event : log) {
|
||||
[objcLog addObject:[NSString stringWithUTF8String:event.c_str()]];
|
||||
}
|
||||
|
||||
return objcLog;
|
||||
}
|
||||
|
||||
- (void)clearActionJournalLog
|
||||
{
|
||||
const auto& actionJournal = _mbglMap->getActionJournal();
|
||||
if (!actionJournal) {
|
||||
return;
|
||||
}
|
||||
|
||||
actionJournal->clearLog();
|
||||
}
|
||||
|
||||
- (MLNBackendResource *)backendResource {
|
||||
return _mbglView->getObject();
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#import "MLNFoundation.h"
|
||||
#import "MLNGeometry.h"
|
||||
#import "MLNMapOptions.h"
|
||||
#import "MLNStyle.h"
|
||||
#import "MLNTypes.h"
|
||||
|
||||
@@ -90,6 +91,16 @@ MLN_EXPORT IB_DESIGNABLE @interface MLNMapView : NSView<MLNStylable>
|
||||
*/
|
||||
- (instancetype)initWithFrame:(NSRect)frame styleURL:(nullable NSURL *)styleURL;
|
||||
|
||||
/**
|
||||
Initializes and returns a newly allocated map view with the specified frame
|
||||
and the default style.
|
||||
|
||||
@param frame The frame for the view, measured in points.
|
||||
@param options The map instance options
|
||||
@return An initialized map view.
|
||||
*/
|
||||
- (instancetype)initWithFrame:(CGRect)frame options:(MLNMapOptions *)options;
|
||||
|
||||
// MARK: Accessing the Delegate
|
||||
|
||||
/**
|
||||
@@ -1270,6 +1281,17 @@ around the returned camera object if it were set as the receiver’s camera.
|
||||
*/
|
||||
@property (nonatomic) MLNMapDebugMaskOptions debugMask;
|
||||
|
||||
/**
|
||||
Get the action journal events from oldest to newest.
|
||||
Each element contains a serialized json object with the event data.
|
||||
*/
|
||||
- (NSArray<NSString *> *)getActionJournalLog;
|
||||
|
||||
/**
|
||||
Clear stored action journal events.
|
||||
*/
|
||||
- (void)clearActionJournalLog;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
#import <mbgl/storage/network_status.hpp>
|
||||
#import <mbgl/storage/resource_options.hpp>
|
||||
#import <mbgl/math/wrap.hpp>
|
||||
#import <mbgl/util/action_journal.hpp>
|
||||
#import <mbgl/util/client_options.hpp>
|
||||
#import <mbgl/util/constants.hpp>
|
||||
#import <mbgl/util/chrono.hpp>
|
||||
@@ -59,6 +60,7 @@
|
||||
#import "MLNNetworkConfiguration_Private.h"
|
||||
#import "MLNLoggingConfiguration_Private.h"
|
||||
#import "MLNReachability.h"
|
||||
#import "MLNActionJournalOptions_Private.h"
|
||||
#import "MLNSettings_Private.h"
|
||||
|
||||
#import <CoreImage/CIFilter.h>
|
||||
@@ -221,7 +223,7 @@ public:
|
||||
if (self = [super initWithFrame:frameRect]) {
|
||||
MLNLogInfo(@"Starting %@ initialization.", NSStringFromClass([self class]));
|
||||
MLNLogDebug(@"Initializing frame: %@", NSStringFromRect(frameRect));
|
||||
[self commonInit];
|
||||
[self commonInitWithOptions:nil];
|
||||
self.styleURL = nil;
|
||||
MLNLogInfo(@"Finalizing %@ initialization.", NSStringFromClass([self class]));
|
||||
}
|
||||
@@ -232,17 +234,38 @@ public:
|
||||
if (self = [super initWithFrame:frame]) {
|
||||
MLNLogInfo(@"Starting %@ initialization.", NSStringFromClass([self class]));
|
||||
MLNLogDebug(@"Initializing frame: %@ styleURL: %@", NSStringFromRect(frame), styleURL);
|
||||
[self commonInit];
|
||||
[self commonInitWithOptions:nil];
|
||||
self.styleURL = styleURL;
|
||||
MLNLogInfo(@"Finalizing %@ initialization.", NSStringFromClass([self class]));
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame options:(MLNMapOptions *)options
|
||||
{
|
||||
if (self = [super initWithFrame:frame])
|
||||
{
|
||||
MLNLogInfo(@"Starting %@ initialization.", NSStringFromClass([self class]));
|
||||
MLNLogDebug(@"Initializing frame: %@ with options", NSStringFromRect(frame));
|
||||
[self commonInitWithOptions:options];
|
||||
|
||||
if (options) {
|
||||
if (options.styleURL) {
|
||||
self.styleURL = options.styleURL;
|
||||
}
|
||||
} else {
|
||||
self.styleURL = nil;
|
||||
}
|
||||
|
||||
MLNLogInfo(@"Finalizing %@ initialization.", NSStringFromClass([self class]));
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithCoder:(nonnull NSCoder *)decoder {
|
||||
if (self = [super initWithCoder:decoder]) {
|
||||
MLNLogInfo(@"Starting %@ initialization.", NSStringFromClass([self class]));
|
||||
[self commonInit];
|
||||
[self commonInitWithOptions:nil];
|
||||
MLNLogInfo(@"Finalizing %@ initialization.", NSStringFromClass([self class]));
|
||||
}
|
||||
return self;
|
||||
@@ -262,7 +285,13 @@ public:
|
||||
return @[@"camera", @"debugMask"];
|
||||
}
|
||||
|
||||
- (void)commonInit {
|
||||
- (void)commonInitWithOptions:(MLNMapOptions*)mlnMapoptions
|
||||
{
|
||||
if (mlnMapoptions == nil)
|
||||
{
|
||||
mlnMapoptions = [[MLNMapOptions alloc] init];
|
||||
}
|
||||
|
||||
[MLNNetworkConfiguration sharedManager];
|
||||
|
||||
_isTargetingInterfaceBuilder = NSProcessInfo.processInfo.mgl_isInterfaceBuilderDesignablesAgent;
|
||||
@@ -308,7 +337,8 @@ public:
|
||||
resourceOptions.withApiKey([apiKey UTF8String]);
|
||||
}
|
||||
|
||||
_mbglMap = std::make_unique<mbgl::Map>(*_rendererFrontend, *_mbglView, mapOptions, resourceOptions, clientOptions);
|
||||
const mbgl::util::ActionJournalOptions& actionJournalOptions = [mlnMapoptions.actionJournalOptions getCoreOptions];
|
||||
_mbglMap = std::make_unique<mbgl::Map>(*_rendererFrontend, *_mbglView, mapOptions, resourceOptions, clientOptions, actionJournalOptions);
|
||||
|
||||
// Notify map object when network reachability status changes.
|
||||
_reachability = [MLNReachability reachabilityForInternetConnection];
|
||||
@@ -3195,4 +3225,31 @@ public:
|
||||
_mbglMap->setDebug(options);
|
||||
}
|
||||
|
||||
- (NSArray<NSString*>*)getActionJournalLog
|
||||
{
|
||||
const auto& actionJournal = _mbglMap->getActionJournal();
|
||||
if (!actionJournal) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
const auto& log = actionJournal->getLog();
|
||||
NSMutableArray<NSString*>* objcLog = [NSMutableArray new];
|
||||
|
||||
for (const auto& event : log) {
|
||||
[objcLog addObject:[NSString stringWithUTF8String:event.c_str()]];
|
||||
}
|
||||
|
||||
return objcLog;
|
||||
}
|
||||
|
||||
- (void)clearActionJournalLog
|
||||
{
|
||||
const auto& actionJournal = _mbglMap->getActionJournal();
|
||||
if (!actionJournal) {
|
||||
return;
|
||||
}
|
||||
|
||||
actionJournal->clearLog();
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -11,6 +11,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
@class MLNPolyline;
|
||||
@class MLNShape;
|
||||
@class MLNStyle;
|
||||
@class MLNSource;
|
||||
@protocol MLNAnnotation;
|
||||
|
||||
/**
|
||||
|
||||
@@ -220,6 +220,7 @@ const root = path.dirname(import.meta.dirname);
|
||||
const outLocation = args.out ? args.out : root;
|
||||
const shaderRoot = path.join(root, "shaders");
|
||||
const outputRoot = path.join(outLocation, "include/mbgl/shaders");
|
||||
const cppOutputRoot = path.join(outLocation, "src/mbgl/shaders");
|
||||
let generatedHeaders = [];
|
||||
let shaderNames = [];
|
||||
|
||||
@@ -317,4 +318,21 @@ struct ShaderSource<BuiltIn::None, gfx::Backend::Type::OpenGL> {
|
||||
} // namespace mbgl
|
||||
`);
|
||||
|
||||
// Generate shader_source.cpp
|
||||
fs.writeFileSync(path.join(cppOutputRoot, "shader_source.cpp"),
|
||||
`${generatedHeader}
|
||||
#include <mbgl/shaders/shader_source.hpp>
|
||||
#include <mbgl/util/enum.hpp>
|
||||
|
||||
namespace mbgl {
|
||||
|
||||
using namespace shaders;
|
||||
|
||||
MBGL_DEFINE_ENUM(BuiltIn, {
|
||||
{BuiltIn::None, "None"},${shaderNames.map(name => `\n{BuiltIn::` + name + `, "` + name + `"}`)}
|
||||
});
|
||||
|
||||
} // namespace mbgl
|
||||
`);
|
||||
|
||||
console.log("Shaders generated!");
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#include <mbgl/util/mapbox.hpp>
|
||||
#include <mbgl/util/math.hpp>
|
||||
#include <mbgl/util/tile_coordinate.hpp>
|
||||
#include <mbgl/util/action_journal.hpp>
|
||||
|
||||
#include <utility>
|
||||
|
||||
@@ -31,19 +32,30 @@ Map::Map(RendererFrontend& frontend,
|
||||
MapObserver& observer,
|
||||
const MapOptions& mapOptions,
|
||||
const ResourceOptions& resourceOptions,
|
||||
const ClientOptions& clientOptions)
|
||||
const ClientOptions& clientOptions,
|
||||
const util::ActionJournalOptions& actionJournalOptions)
|
||||
: impl(std::make_unique<Impl>(frontend,
|
||||
observer,
|
||||
FileSourceManager::get()
|
||||
? std::shared_ptr<FileSource>(FileSourceManager::get()->getFileSource(
|
||||
ResourceLoader, resourceOptions, clientOptions))
|
||||
: nullptr,
|
||||
mapOptions)) {}
|
||||
mapOptions)) {
|
||||
if (actionJournalOptions.enabled()) {
|
||||
impl->actionJournal = std::make_unique<util::ActionJournal>(*this, actionJournalOptions);
|
||||
}
|
||||
}
|
||||
|
||||
Map::Map(std::unique_ptr<Impl> impl_)
|
||||
: impl(std::move(impl_)) {}
|
||||
Map::Map(std::unique_ptr<Impl> impl_, const util::ActionJournalOptions& actionJournalOptions)
|
||||
: impl(std::move(impl_)) {
|
||||
if (actionJournalOptions.enabled()) {
|
||||
impl->actionJournal = std::make_unique<util::ActionJournal>(*this, actionJournalOptions);
|
||||
}
|
||||
}
|
||||
|
||||
Map::~Map() = default;
|
||||
Map::~Map() {
|
||||
impl->actionJournal.reset();
|
||||
}
|
||||
|
||||
void Map::renderStill(StillImageCallback callback) {
|
||||
if (!callback) {
|
||||
@@ -527,4 +539,12 @@ FreeCameraOptions Map::getFreeCameraOptions() const {
|
||||
return impl->transform.getFreeCameraOptions();
|
||||
}
|
||||
|
||||
ClientOptions Map::getClientOptions() const {
|
||||
return impl->fileSource ? impl->fileSource->getClientOptions() : ClientOptions();
|
||||
}
|
||||
|
||||
const std::unique_ptr<util::ActionJournal>& Map::getActionJournal() {
|
||||
return impl->actionJournal;
|
||||
}
|
||||
|
||||
} // namespace mbgl
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
#include <mbgl/util/exception.hpp>
|
||||
#include <mbgl/util/logging.hpp>
|
||||
#include <mbgl/util/traits.hpp>
|
||||
#include <mbgl/util/action_journal.hpp>
|
||||
#include <mbgl/util/action_journal_impl.hpp>
|
||||
|
||||
namespace mbgl {
|
||||
|
||||
@@ -47,7 +49,7 @@ Map::Impl::Impl(RendererFrontend& frontend_,
|
||||
const MapOptions& mapOptions)
|
||||
: observer(observer_),
|
||||
rendererFrontend(frontend_),
|
||||
transform(observer, mapOptions.constrainMode(), mapOptions.viewportMode()),
|
||||
transform(*this, mapOptions.constrainMode(), mapOptions.viewportMode()),
|
||||
mode(mapOptions.mapMode()),
|
||||
pixelRatio(mapOptions.pixelRatio()),
|
||||
crossSourceCollisions(mapOptions.crossSourceCollisions()),
|
||||
@@ -66,10 +68,38 @@ Map::Impl::~Impl() {
|
||||
rendererFrontend.reset();
|
||||
};
|
||||
|
||||
void Map::Impl::onCameraWillChange(MapObserver::CameraChangeMode cameraMode) {
|
||||
observer.onCameraWillChange(cameraMode);
|
||||
|
||||
if (actionJournal) {
|
||||
actionJournal->impl->onCameraWillChange(cameraMode);
|
||||
}
|
||||
}
|
||||
|
||||
void Map::Impl::onCameraIsChanging() {
|
||||
observer.onCameraIsChanging();
|
||||
|
||||
if (actionJournal) {
|
||||
actionJournal->impl->onCameraIsChanging();
|
||||
}
|
||||
}
|
||||
|
||||
void Map::Impl::onCameraDidChange(MapObserver::CameraChangeMode cameraMode) {
|
||||
observer.onCameraDidChange(cameraMode);
|
||||
|
||||
if (actionJournal) {
|
||||
actionJournal->impl->onCameraDidChange(cameraMode);
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Map::Impl StyleObserver
|
||||
|
||||
void Map::Impl::onSourceChanged(style::Source& source) {
|
||||
observer.onSourceChanged(source);
|
||||
|
||||
if (actionJournal) {
|
||||
actionJournal->impl->onSourceChanged(source);
|
||||
}
|
||||
}
|
||||
|
||||
void Map::Impl::onUpdate() {
|
||||
@@ -108,6 +138,10 @@ void Map::Impl::onStyleLoading() {
|
||||
loading = true;
|
||||
rendererFullyLoaded = false;
|
||||
observer.onWillStartLoadingMap();
|
||||
|
||||
if (actionJournal) {
|
||||
actionJournal->impl->onWillStartLoadingMap();
|
||||
}
|
||||
}
|
||||
|
||||
void Map::Impl::onStyleLoaded() {
|
||||
@@ -117,8 +151,13 @@ void Map::Impl::onStyleLoaded() {
|
||||
if (LayerManager::annotationsEnabled) {
|
||||
annotationManager.onStyleLoaded();
|
||||
}
|
||||
|
||||
observer.onDidFinishLoadingStyle();
|
||||
|
||||
if (actionJournal) {
|
||||
actionJournal->impl->onDidFinishLoadingStyle();
|
||||
}
|
||||
|
||||
#if !defined(NDEBUG)
|
||||
logStyleDependencies(EventSeverity::Info, Event::Style, *style);
|
||||
#endif
|
||||
@@ -145,18 +184,34 @@ void Map::Impl::onStyleError(std::exception_ptr error) {
|
||||
}
|
||||
|
||||
observer.onDidFailLoadingMap(type, description);
|
||||
|
||||
if (actionJournal) {
|
||||
actionJournal->impl->onDidFailLoadingMap(type, description);
|
||||
}
|
||||
}
|
||||
|
||||
void Map::Impl::onSpriteLoaded(const std::optional<style::Sprite>& sprite) {
|
||||
observer.onSpriteLoaded(sprite);
|
||||
|
||||
if (actionJournal) {
|
||||
actionJournal->impl->onSpriteLoaded(sprite);
|
||||
}
|
||||
}
|
||||
|
||||
void Map::Impl::onSpriteError(const std::optional<style::Sprite>& sprite, std::exception_ptr ex) {
|
||||
observer.onSpriteError(sprite, ex);
|
||||
|
||||
if (actionJournal) {
|
||||
actionJournal->impl->onSpriteError(sprite, ex);
|
||||
}
|
||||
}
|
||||
|
||||
void Map::Impl::onSpriteRequested(const std::optional<style::Sprite>& sprite) {
|
||||
observer.onSpriteRequested(sprite);
|
||||
|
||||
if (actionJournal) {
|
||||
actionJournal->impl->onSpriteRequested(sprite);
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Map::Impl RendererObserver
|
||||
@@ -175,6 +230,10 @@ void Map::Impl::onResourceError(std::exception_ptr error) {
|
||||
void Map::Impl::onWillStartRenderingFrame() {
|
||||
if (mode == MapMode::Continuous) {
|
||||
observer.onWillStartRenderingFrame();
|
||||
|
||||
if (actionJournal) {
|
||||
actionJournal->impl->onWillStartRenderingFrame();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -186,16 +245,23 @@ void Map::Impl::onDidFinishRenderingFrame(RenderMode renderMode,
|
||||
rendererFullyLoaded = renderMode == RenderMode::Full;
|
||||
|
||||
if (mode == MapMode::Continuous) {
|
||||
observer.onDidFinishRenderingFrame({MapObserver::RenderMode(renderMode),
|
||||
needsRepaint,
|
||||
placemenChanged,
|
||||
frameEncodingTime,
|
||||
frameRenderingTime});
|
||||
const MapObserver::RenderFrameStatus frameStatus{
|
||||
MapObserver::RenderMode(renderMode), needsRepaint, placemenChanged, frameEncodingTime, frameRenderingTime};
|
||||
|
||||
observer.onDidFinishRenderingFrame(frameStatus);
|
||||
|
||||
if (actionJournal) {
|
||||
actionJournal->impl->onDidFinishRenderingFrame(frameStatus);
|
||||
}
|
||||
|
||||
if (needsRepaint || transform.inTransition()) {
|
||||
onUpdate();
|
||||
} else if (rendererFullyLoaded) {
|
||||
observer.onDidBecomeIdle();
|
||||
|
||||
if (actionJournal) {
|
||||
actionJournal->impl->onDidBecomeIdle();
|
||||
}
|
||||
}
|
||||
} else if (stillImageRequest && rendererFullyLoaded) {
|
||||
auto request = std::move(stillImageRequest);
|
||||
@@ -206,15 +272,28 @@ void Map::Impl::onDidFinishRenderingFrame(RenderMode renderMode,
|
||||
void Map::Impl::onWillStartRenderingMap() {
|
||||
if (mode == MapMode::Continuous) {
|
||||
observer.onWillStartRenderingMap();
|
||||
|
||||
if (actionJournal) {
|
||||
actionJournal->impl->onWillStartRenderingMap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Map::Impl::onDidFinishRenderingMap() {
|
||||
if (mode == MapMode::Continuous && loading) {
|
||||
observer.onDidFinishRenderingMap(MapObserver::RenderMode::Full);
|
||||
|
||||
if (actionJournal) {
|
||||
actionJournal->impl->onDidFinishRenderingMap(MapObserver::RenderMode::Full);
|
||||
}
|
||||
|
||||
if (loading) {
|
||||
loading = false;
|
||||
observer.onDidFinishLoadingMap();
|
||||
|
||||
if (actionJournal) {
|
||||
actionJournal->impl->onDidFinishLoadingMap();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -226,7 +305,13 @@ void Map::Impl::jumpTo(const CameraOptions& camera) {
|
||||
}
|
||||
|
||||
void Map::Impl::onStyleImageMissing(const std::string& id, const std::function<void()>& done) {
|
||||
if (!style->getImage(id)) observer.onStyleImageMissing(id);
|
||||
if (!style->getImage(id)) {
|
||||
observer.onStyleImageMissing(id);
|
||||
|
||||
if (actionJournal) {
|
||||
actionJournal->impl->onStyleImageMissing(id);
|
||||
}
|
||||
}
|
||||
|
||||
done();
|
||||
onUpdate();
|
||||
@@ -242,40 +327,72 @@ void Map::Impl::onRemoveUnusedStyleImages(const std::vector<std::string>& unused
|
||||
|
||||
void Map::Impl::onRegisterShaders(gfx::ShaderRegistry& registry) {
|
||||
observer.onRegisterShaders(registry);
|
||||
|
||||
if (actionJournal) {
|
||||
actionJournal->impl->onRegisterShaders(registry);
|
||||
}
|
||||
}
|
||||
|
||||
void Map::Impl::onPreCompileShader(shaders::BuiltIn shaderID,
|
||||
gfx::Backend::Type type,
|
||||
const std::string& additionalDefines) {
|
||||
observer.onPreCompileShader(shaderID, type, additionalDefines);
|
||||
|
||||
if (actionJournal) {
|
||||
actionJournal->impl->onPreCompileShader(shaderID, type, additionalDefines);
|
||||
}
|
||||
}
|
||||
|
||||
void Map::Impl::onPostCompileShader(shaders::BuiltIn shaderID,
|
||||
gfx::Backend::Type type,
|
||||
const std::string& additionalDefines) {
|
||||
observer.onPostCompileShader(shaderID, type, additionalDefines);
|
||||
|
||||
if (actionJournal) {
|
||||
actionJournal->impl->onPostCompileShader(shaderID, type, additionalDefines);
|
||||
}
|
||||
}
|
||||
|
||||
void Map::Impl::onShaderCompileFailed(shaders::BuiltIn shaderID,
|
||||
gfx::Backend::Type type,
|
||||
const std::string& additionalDefines) {
|
||||
observer.onShaderCompileFailed(shaderID, type, additionalDefines);
|
||||
|
||||
if (actionJournal) {
|
||||
actionJournal->impl->onShaderCompileFailed(shaderID, type, additionalDefines);
|
||||
}
|
||||
}
|
||||
|
||||
void Map::Impl::onGlyphsLoaded(const FontStack& fontStack, const GlyphRange& ranges) {
|
||||
observer.onGlyphsLoaded(fontStack, ranges);
|
||||
|
||||
if (actionJournal) {
|
||||
actionJournal->impl->onGlyphsLoaded(fontStack, ranges);
|
||||
}
|
||||
}
|
||||
|
||||
void Map::Impl::onGlyphsError(const FontStack& fontStack, const GlyphRange& ranges, std::exception_ptr ex) {
|
||||
observer.onGlyphsError(fontStack, ranges, ex);
|
||||
|
||||
if (actionJournal) {
|
||||
actionJournal->impl->onGlyphsError(fontStack, ranges, ex);
|
||||
}
|
||||
}
|
||||
|
||||
void Map::Impl::onGlyphsRequested(const FontStack& fontStack, const GlyphRange& ranges) {
|
||||
observer.onGlyphsRequested(fontStack, ranges);
|
||||
|
||||
if (actionJournal) {
|
||||
actionJournal->impl->onGlyphsRequested(fontStack, ranges);
|
||||
}
|
||||
}
|
||||
|
||||
void Map::Impl::onTileAction(TileOperation op, const OverscaledTileID& id, const std::string& sourceID) {
|
||||
observer.onTileAction(op, id, sourceID);
|
||||
|
||||
if (actionJournal) {
|
||||
actionJournal->impl->onTileAction(op, id, sourceID);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace mbgl
|
||||
|
||||
@@ -23,6 +23,10 @@ namespace gfx {
|
||||
class ShaderRegistry;
|
||||
} // namespace gfx
|
||||
|
||||
namespace util {
|
||||
class ActionJournal;
|
||||
} // namespace util
|
||||
|
||||
struct StillImageRequest {
|
||||
StillImageRequest(Map::StillImageCallback&& callback_)
|
||||
: callback(std::move(callback_)) {}
|
||||
@@ -30,11 +34,16 @@ struct StillImageRequest {
|
||||
Map::StillImageCallback callback;
|
||||
};
|
||||
|
||||
class Map::Impl final : public style::Observer, public RendererObserver {
|
||||
class Map::Impl final : public TransformObserver, public style::Observer, public RendererObserver {
|
||||
public:
|
||||
Impl(RendererFrontend&, MapObserver&, std::shared_ptr<FileSource>, const MapOptions&);
|
||||
~Impl() final;
|
||||
|
||||
// TransformObserver
|
||||
void onCameraWillChange(MapObserver::CameraChangeMode) final;
|
||||
void onCameraIsChanging() final;
|
||||
void onCameraDidChange(MapObserver::CameraChangeMode) final;
|
||||
|
||||
// StyleObserver
|
||||
void onSourceChanged(style::Source&) final;
|
||||
void onUpdate() final;
|
||||
@@ -69,6 +78,7 @@ public:
|
||||
|
||||
MapObserver& observer;
|
||||
RendererFrontend& rendererFrontend;
|
||||
std::unique_ptr<util::ActionJournal> actionJournal;
|
||||
|
||||
Transform transform;
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ static double _normalizeAngle(double angle, double anchorAngle) {
|
||||
return angle;
|
||||
}
|
||||
|
||||
Transform::Transform(MapObserver& observer_, ConstrainMode constrainMode, ViewportMode viewportMode)
|
||||
Transform::Transform(TransformObserver& observer_, ConstrainMode constrainMode, ViewportMode viewportMode)
|
||||
: observer(observer_),
|
||||
state(constrainMode, viewportMode) {}
|
||||
|
||||
|
||||
@@ -16,14 +16,28 @@
|
||||
|
||||
namespace mbgl {
|
||||
|
||||
class TransformObserver {
|
||||
public:
|
||||
virtual ~TransformObserver() = default;
|
||||
|
||||
static TransformObserver& nullObserver() {
|
||||
static TransformObserver observer;
|
||||
return observer;
|
||||
}
|
||||
|
||||
virtual void onCameraWillChange(MapObserver::CameraChangeMode) {}
|
||||
virtual void onCameraIsChanging() {}
|
||||
virtual void onCameraDidChange(MapObserver::CameraChangeMode) {}
|
||||
};
|
||||
|
||||
class Transform : private util::noncopyable {
|
||||
public:
|
||||
Transform(MapObserver& = MapObserver::nullObserver(),
|
||||
Transform(TransformObserver& = TransformObserver::nullObserver(),
|
||||
ConstrainMode = ConstrainMode::HeightOnly,
|
||||
ViewportMode = ViewportMode::Default);
|
||||
|
||||
Transform(const TransformState& state_)
|
||||
: observer(MapObserver::nullObserver()),
|
||||
: observer(TransformObserver::nullObserver()),
|
||||
state(state_) {}
|
||||
|
||||
// Map view
|
||||
@@ -119,7 +133,7 @@ public:
|
||||
void setFreeCameraOptions(const FreeCameraOptions& options);
|
||||
|
||||
private:
|
||||
MapObserver& observer;
|
||||
TransformObserver& observer;
|
||||
TransformState state;
|
||||
|
||||
void startTransition(const CameraOptions&,
|
||||
|
||||
44
src/mbgl/shaders/shader_source.cpp
Normal file
44
src/mbgl/shaders/shader_source.cpp
Normal file
@@ -0,0 +1,44 @@
|
||||
// Generated code, do not modify this file!
|
||||
#include <mbgl/shaders/shader_source.hpp>
|
||||
#include <mbgl/util/enum.hpp>
|
||||
|
||||
namespace mbgl {
|
||||
|
||||
using namespace shaders;
|
||||
|
||||
MBGL_DEFINE_ENUM(BuiltIn,
|
||||
{{BuiltIn::None, "None"},
|
||||
{BuiltIn::Prelude, "Prelude"},
|
||||
{BuiltIn::ClippingMaskProgram, "ClippingMaskProgram"},
|
||||
{BuiltIn::BackgroundShader, "BackgroundShader"},
|
||||
{BuiltIn::BackgroundPatternShader, "BackgroundPatternShader"},
|
||||
{BuiltIn::CircleShader, "CircleShader"},
|
||||
{BuiltIn::CollisionBoxShader, "CollisionBoxShader"},
|
||||
{BuiltIn::CollisionCircleShader, "CollisionCircleShader"},
|
||||
{BuiltIn::CustomGeometryShader, "CustomGeometryShader"},
|
||||
{BuiltIn::CustomSymbolIconShader, "CustomSymbolIconShader"},
|
||||
{BuiltIn::DebugShader, "DebugShader"},
|
||||
{BuiltIn::FillShader, "FillShader"},
|
||||
{BuiltIn::FillOutlineShader, "FillOutlineShader"},
|
||||
{BuiltIn::FillPatternShader, "FillPatternShader"},
|
||||
{BuiltIn::FillOutlinePatternShader, "FillOutlinePatternShader"},
|
||||
{BuiltIn::FillOutlineTriangulatedShader, "FillOutlineTriangulatedShader"},
|
||||
{BuiltIn::FillExtrusionShader, "FillExtrusionShader"},
|
||||
{BuiltIn::FillExtrusionPatternShader, "FillExtrusionPatternShader"},
|
||||
{BuiltIn::HeatmapShader, "HeatmapShader"},
|
||||
{BuiltIn::HeatmapTextureShader, "HeatmapTextureShader"},
|
||||
{BuiltIn::HillshadePrepareShader, "HillshadePrepareShader"},
|
||||
{BuiltIn::HillshadeShader, "HillshadeShader"},
|
||||
{BuiltIn::LineShader, "LineShader"},
|
||||
{BuiltIn::LineGradientShader, "LineGradientShader"},
|
||||
{BuiltIn::LinePatternShader, "LinePatternShader"},
|
||||
{BuiltIn::LocationIndicatorShader, "LocationIndicatorShader"},
|
||||
{BuiltIn::LocationIndicatorTexturedShader, "LocationIndicatorTexturedShader"},
|
||||
{BuiltIn::LineSDFShader, "LineSDFShader"},
|
||||
{BuiltIn::RasterShader, "RasterShader"},
|
||||
{BuiltIn::SymbolIconShader, "SymbolIconShader"},
|
||||
{BuiltIn::SymbolSDFShader, "SymbolSDFShader"},
|
||||
{BuiltIn::SymbolTextAndIconShader, "SymbolTextAndIconShader"},
|
||||
{BuiltIn::WideVectorShader, "WideVectorShader"}});
|
||||
|
||||
} // namespace mbgl
|
||||
18
src/mbgl/tile/tile_operation.cpp
Normal file
18
src/mbgl/tile/tile_operation.cpp
Normal file
@@ -0,0 +1,18 @@
|
||||
#include <mbgl/tile/tile_operation.hpp>
|
||||
#include <mbgl/util/enum.hpp>
|
||||
|
||||
namespace mbgl {
|
||||
|
||||
MBGL_DEFINE_ENUM(TileOperation,
|
||||
{
|
||||
{TileOperation::RequestedFromCache, "RequestedFromCache"},
|
||||
{TileOperation::RequestedFromNetwork, "RequestedFromNetwork"},
|
||||
{TileOperation::LoadFromNetwork, "LoadFromNetwork"},
|
||||
{TileOperation::LoadFromCache, "LoadFromCache"},
|
||||
{TileOperation::StartParse, "StartParse"},
|
||||
{TileOperation::EndParse, "EndParse"},
|
||||
{TileOperation::Error, "Error"},
|
||||
{TileOperation::Cancelled, "Cancelled"},
|
||||
{TileOperation::NullOp, "NullOp"},
|
||||
});
|
||||
} // namespace mbgl
|
||||
33
src/mbgl/util/action_journal.cpp
Normal file
33
src/mbgl/util/action_journal.cpp
Normal file
@@ -0,0 +1,33 @@
|
||||
#include <mbgl/util/action_journal.hpp>
|
||||
#include <mbgl/util/action_journal_impl.hpp>
|
||||
|
||||
namespace mbgl {
|
||||
namespace util {
|
||||
|
||||
ActionJournal::ActionJournal(const Map& map, const ActionJournalOptions& options)
|
||||
: impl(std::make_unique<Impl>(map, options)) {
|
||||
impl->onMapCreate();
|
||||
}
|
||||
|
||||
ActionJournal::~ActionJournal() {
|
||||
impl->onMapDestroy();
|
||||
}
|
||||
|
||||
std::string ActionJournal::getLogDirectory() const {
|
||||
return impl->getLogDirectory();
|
||||
}
|
||||
|
||||
std::vector<std::string> ActionJournal::getLogFiles() const {
|
||||
return impl->getLogFiles();
|
||||
}
|
||||
|
||||
std::vector<std::string> ActionJournal::getLog() {
|
||||
return impl->getLog();
|
||||
}
|
||||
|
||||
void ActionJournal::clearLog() {
|
||||
impl->clearLog();
|
||||
}
|
||||
|
||||
} // namespace util
|
||||
} // namespace mbgl
|
||||
575
src/mbgl/util/action_journal_impl.cpp
Normal file
575
src/mbgl/util/action_journal_impl.cpp
Normal file
@@ -0,0 +1,575 @@
|
||||
#include <mbgl/util/action_journal_impl.hpp>
|
||||
#include <mbgl/util/logging.hpp>
|
||||
#include <mbgl/style/style.hpp>
|
||||
#include <mbgl/map/map.hpp>
|
||||
|
||||
#include <rapidjson/document.h>
|
||||
#include <rapidjson/stringbuffer.h>
|
||||
#include <rapidjson/writer.h>
|
||||
|
||||
#include <regex>
|
||||
|
||||
#ifdef __APPLE__
|
||||
#include <TargetConditionals.h>
|
||||
#if TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR
|
||||
|
||||
#define USE_GHC_FILESYSTEM
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef USE_GHC_FILESYSTEM
|
||||
|
||||
#include <ghc/filesystem.hpp>
|
||||
namespace mbgl {
|
||||
namespace filesystem = ghc::filesystem;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
#include <filesystem>
|
||||
namespace mbgl {
|
||||
namespace filesystem = std::filesystem;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
namespace mbgl {
|
||||
namespace util {
|
||||
|
||||
constexpr auto ACTION_JOURNAL_DIRECTORY_NAME = "action_journal";
|
||||
constexpr auto ACTION_JOURNAL_FILE_NAME = "action_journal";
|
||||
constexpr auto ACTION_JOURNAL_FILE_EXTENSION = "log";
|
||||
|
||||
class MapEnvironmentSnapshot {
|
||||
public:
|
||||
MapEnvironmentSnapshot(const ActionJournal::Impl& journal)
|
||||
: time(std::chrono::time_point_cast<std::chrono::milliseconds>(std::chrono::system_clock::now())),
|
||||
clientName(journal.getMap().getClientOptions().name()),
|
||||
clientVersion(journal.getMap().getClientOptions().version()),
|
||||
styleName(journal.getMap().getStyle().getName()),
|
||||
styleURL(journal.getMap().getStyle().getURL()) {}
|
||||
|
||||
const auto& getTime() const { return time; }
|
||||
const auto& getClientName() const { return clientName; }
|
||||
const auto& getClientVersion() const { return clientVersion; }
|
||||
const auto& getStyleName() const { return styleName; }
|
||||
const auto& getStyleURL() const { return styleURL; }
|
||||
|
||||
protected:
|
||||
const std::chrono::time_point<std::chrono::system_clock, std::chrono::milliseconds> time;
|
||||
const std::string clientName;
|
||||
const std::string clientVersion;
|
||||
const std::string styleName;
|
||||
const std::string styleURL;
|
||||
};
|
||||
|
||||
class ActionJournalEvent {
|
||||
public:
|
||||
ActionJournalEvent(const rapidjson::GenericStringRef<char>& name, const MapEnvironmentSnapshot& env)
|
||||
: json(rapidjson::kObjectType),
|
||||
eventJson(rapidjson::kObjectType) {
|
||||
json.AddMember("name", name, json.GetAllocator());
|
||||
json.AddMember("time", iso8601(env.getTime()), json.GetAllocator());
|
||||
|
||||
if (!env.getClientName().empty()) {
|
||||
json.AddMember("clientName", env.getClientName(), json.GetAllocator());
|
||||
}
|
||||
|
||||
if (!env.getClientVersion().empty()) {
|
||||
json.AddMember("clientVersion", env.getClientVersion(), json.GetAllocator());
|
||||
}
|
||||
|
||||
if (!env.getStyleName().empty()) {
|
||||
json.AddMember("styleName", env.getStyleName(), json.GetAllocator());
|
||||
}
|
||||
|
||||
if (!env.getStyleURL().empty()) {
|
||||
json.AddMember("styleURL", env.getStyleURL(), json.GetAllocator());
|
||||
}
|
||||
}
|
||||
|
||||
~ActionJournalEvent() = default;
|
||||
|
||||
template <typename T>
|
||||
ActionJournalEvent& addEvent(const rapidjson::GenericStringRef<char>& key, const T& value) {
|
||||
eventJson.AddMember(key, value, json.GetAllocator());
|
||||
return *this;
|
||||
}
|
||||
|
||||
ActionJournalEvent& addEventStringArray(const rapidjson::GenericStringRef<char>& key,
|
||||
const std::vector<std::string>& value) {
|
||||
rapidjson::Value arrayJson(rapidjson::kArrayType);
|
||||
|
||||
for (const auto& elem : value) {
|
||||
rapidjson::Value elemJson(rapidjson::kStringType);
|
||||
elemJson.SetString(elem.c_str(), static_cast<rapidjson::SizeType>(elem.size()));
|
||||
arrayJson.PushBack(elemJson, json.GetAllocator());
|
||||
}
|
||||
|
||||
eventJson.AddMember(key, arrayJson, json.GetAllocator());
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
ActionJournalEvent& addEventEnum(const rapidjson::GenericStringRef<char>& key, const T& value) {
|
||||
eventJson.AddMember(key, rapidjson::StringRef(Enum<T>::toString(value)), json.GetAllocator());
|
||||
return *this;
|
||||
}
|
||||
|
||||
std::string toString() {
|
||||
if (!eventJson.ObjectEmpty()) {
|
||||
json.AddMember("event", eventJson, json.GetAllocator());
|
||||
}
|
||||
|
||||
rapidjson::StringBuffer jsonBuffer;
|
||||
rapidjson::Writer<rapidjson::StringBuffer> jsonWriter(jsonBuffer);
|
||||
|
||||
json.Accept(jsonWriter);
|
||||
|
||||
return jsonBuffer.GetString();
|
||||
}
|
||||
|
||||
rapidjson::Document json;
|
||||
rapidjson::Value eventJson;
|
||||
};
|
||||
|
||||
ActionJournal::Impl::Impl(const Map& map_, const ActionJournalOptions& options_)
|
||||
: map(map_),
|
||||
options(options_),
|
||||
scheduler(Scheduler::GetSequenced()) {
|
||||
assert(!options.path().empty());
|
||||
assert(options.logFileSize() > 0);
|
||||
assert(options.logFileCount() > 1);
|
||||
|
||||
options.withPath((mbgl::filesystem::canonical(options.path()) / ACTION_JOURNAL_DIRECTORY_NAME).generic_string());
|
||||
|
||||
if (!openFile(detectFiles(), false)) {
|
||||
Log::Error(Event::General, "Failed to open Action Journal file");
|
||||
}
|
||||
}
|
||||
|
||||
ActionJournal::Impl::~Impl() {
|
||||
flush();
|
||||
}
|
||||
|
||||
std::string ActionJournal::Impl::getLogDirectory() const {
|
||||
return options.path();
|
||||
}
|
||||
|
||||
std::vector<std::string> ActionJournal::Impl::getLogFiles() const {
|
||||
std::set<std::string> files;
|
||||
for (const auto& entry : mbgl::filesystem::directory_iterator(options.path())) {
|
||||
if (!entry.is_regular_file()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
files.emplace(mbgl::filesystem::canonical(entry.path()).generic_string());
|
||||
}
|
||||
|
||||
return std::vector<std::string>(files.begin(), files.end());
|
||||
}
|
||||
|
||||
std::vector<std::string> ActionJournal::Impl::getLog() {
|
||||
std::lock_guard lock(fileMutex);
|
||||
std::vector<std::string> logEvents;
|
||||
|
||||
const auto readFile = [&](std::fstream& file) {
|
||||
if (!file) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (std::string line; std::getline(file, line);) {
|
||||
logEvents.emplace_back(line);
|
||||
}
|
||||
|
||||
file.clear();
|
||||
};
|
||||
|
||||
// read current file
|
||||
if (currentFile && currentFileIndex == 0) {
|
||||
currentFile.seekg(std::ios::beg);
|
||||
readFile(currentFile);
|
||||
return logEvents;
|
||||
}
|
||||
|
||||
const uint32_t maxFileIndex = currentFile ? currentFileIndex - 1 : detectFiles();
|
||||
|
||||
// read previous files
|
||||
for (uint32_t fileIndex = 0; fileIndex <= maxFileIndex; ++fileIndex) {
|
||||
const auto& filepath = getFilepath(fileIndex);
|
||||
std::fstream file(filepath, std::fstream::in);
|
||||
readFile(file);
|
||||
}
|
||||
|
||||
if (currentFile) {
|
||||
currentFile.seekg(std::ios::beg);
|
||||
readFile(currentFile);
|
||||
}
|
||||
|
||||
return logEvents;
|
||||
}
|
||||
|
||||
void ActionJournal::Impl::clearLog() {
|
||||
std::lock_guard lock(fileMutex);
|
||||
|
||||
// close current file
|
||||
currentFile.close();
|
||||
|
||||
currentFileIndex = 0;
|
||||
currentFileSize = 0;
|
||||
|
||||
if (!mbgl::filesystem::remove_all(options.path())) {
|
||||
Log::Error(Event::General, "Failed to clear ActionJournal");
|
||||
}
|
||||
|
||||
if (!openFile(0, false)) {
|
||||
Log::Error(Event::General, "Failed to open Action Journal file");
|
||||
}
|
||||
}
|
||||
|
||||
void ActionJournal::Impl::flush() {
|
||||
scheduler->waitForEmpty();
|
||||
}
|
||||
|
||||
std::string ActionJournal::Impl::getDirectoryName() {
|
||||
return ACTION_JOURNAL_DIRECTORY_NAME;
|
||||
}
|
||||
|
||||
void ActionJournal::Impl::onCameraWillChange(CameraChangeMode mode) {
|
||||
scheduler->schedule([=, this, env = MapEnvironmentSnapshot(*this)]() {
|
||||
log(ActionJournalEvent("onCameraWillChange", env).addEvent("cameraMode", static_cast<int>(mode)));
|
||||
});
|
||||
}
|
||||
|
||||
void ActionJournal::Impl::onCameraDidChange(CameraChangeMode mode) {
|
||||
scheduler->schedule([=, this, env = MapEnvironmentSnapshot(*this)]() {
|
||||
log(ActionJournalEvent("onCameraDidChange", env).addEvent("cameraMode", static_cast<int>(mode)));
|
||||
});
|
||||
}
|
||||
|
||||
void ActionJournal::Impl::onWillStartLoadingMap() {
|
||||
scheduler->schedule(
|
||||
[=, this, env = MapEnvironmentSnapshot(*this)]() { log(ActionJournalEvent("onWillStartLoadingMap", env)); });
|
||||
}
|
||||
|
||||
void ActionJournal::Impl::onDidFinishLoadingMap() {
|
||||
scheduler->schedule(
|
||||
[=, this, env = MapEnvironmentSnapshot(*this)]() { log(ActionJournalEvent("onDidFinishLoadingMap", env)); });
|
||||
}
|
||||
|
||||
void ActionJournal::Impl::onDidFailLoadingMap(MapLoadError error, const std::string& errorStr) {
|
||||
scheduler->schedule([=, this, env = MapEnvironmentSnapshot(*this)]() {
|
||||
log(ActionJournalEvent("onDidFailLoadingMap", env)
|
||||
.addEvent("error", errorStr)
|
||||
.addEvent("code", static_cast<int>(error)));
|
||||
});
|
||||
}
|
||||
|
||||
void ActionJournal::Impl::onWillStartRenderingMap() {
|
||||
scheduler->schedule(
|
||||
[=, this, env = MapEnvironmentSnapshot(*this)]() { log(ActionJournalEvent("onWillStartRenderingMap", env)); });
|
||||
}
|
||||
|
||||
void ActionJournal::Impl::onDidFinishRenderingMap(RenderMode) {
|
||||
scheduler->schedule(
|
||||
[=, this, env = MapEnvironmentSnapshot(*this)]() { log(ActionJournalEvent("onDidFinishRenderingMap", env)); });
|
||||
}
|
||||
|
||||
void ActionJournal::Impl::onDidFinishLoadingStyle() {
|
||||
scheduler->schedule(
|
||||
[=, this, env = MapEnvironmentSnapshot(*this)]() { log(ActionJournalEvent("onDidFinishLoadingStyle", env)); });
|
||||
}
|
||||
|
||||
void ActionJournal::Impl::onSourceChanged(style::Source& source) {
|
||||
scheduler->schedule([=, this, env = MapEnvironmentSnapshot(*this), type = source.getType(), id = source.getID()]() {
|
||||
log(ActionJournalEvent("onSourceChanged", env).addEventEnum("type", type).addEvent("id", id));
|
||||
});
|
||||
}
|
||||
|
||||
void ActionJournal::Impl::onDidBecomeIdle() {
|
||||
scheduler->schedule(
|
||||
[=, this, env = MapEnvironmentSnapshot(*this)]() { log(ActionJournalEvent("onDidBecomeIdle", env)); });
|
||||
}
|
||||
|
||||
void ActionJournal::Impl::onStyleImageMissing(const std::string& id) {
|
||||
scheduler->schedule([=, this, env = MapEnvironmentSnapshot(*this)]() {
|
||||
log(ActionJournalEvent("onStyleImageMissing", env).addEvent("id", id));
|
||||
});
|
||||
}
|
||||
|
||||
void ActionJournal::Impl::onRegisterShaders(gfx::ShaderRegistry&) {
|
||||
scheduler->schedule(
|
||||
[=, this, env = MapEnvironmentSnapshot(*this)]() { log(ActionJournalEvent("onRegisterShaders", env)); });
|
||||
}
|
||||
|
||||
void ActionJournal::Impl::onPreCompileShader(shaders::BuiltIn id, gfx::Backend::Type backend, const std::string&) {
|
||||
scheduler->schedule([=, this, env = MapEnvironmentSnapshot(*this)]() {
|
||||
log(ActionJournalEvent("onPreCompileShader", env)
|
||||
.addEventEnum("shader", id)
|
||||
.addEvent("backend", static_cast<int>(backend)));
|
||||
});
|
||||
}
|
||||
|
||||
void ActionJournal::Impl::onPostCompileShader(shaders::BuiltIn id, gfx::Backend::Type backend, const std::string&) {
|
||||
scheduler->schedule([=, this, env = MapEnvironmentSnapshot(*this)]() {
|
||||
log(ActionJournalEvent("onPostCompileShader", env)
|
||||
.addEventEnum("shader", id)
|
||||
.addEvent("backend", static_cast<int>(backend)));
|
||||
});
|
||||
}
|
||||
|
||||
void ActionJournal::Impl::onShaderCompileFailed(shaders::BuiltIn id,
|
||||
gfx::Backend::Type backend,
|
||||
const std::string& defines) {
|
||||
scheduler->schedule([=, this, env = MapEnvironmentSnapshot(*this)]() {
|
||||
log(ActionJournalEvent("onShaderCompileFailed", env)
|
||||
.addEventEnum("shader", id)
|
||||
.addEvent("backend", static_cast<int>(backend))
|
||||
.addEvent("defines", defines));
|
||||
});
|
||||
}
|
||||
|
||||
void ActionJournal::Impl::onGlyphsLoaded(const FontStack& fonts, const GlyphRange& range) {
|
||||
scheduler->schedule([=, this, env = MapEnvironmentSnapshot(*this)]() {
|
||||
log(ActionJournalEvent("onGlyphsLoaded", env)
|
||||
.addEventStringArray("fonts", fonts)
|
||||
.addEvent("rangeStart", range.first)
|
||||
.addEvent("rangeEnd", range.second));
|
||||
});
|
||||
}
|
||||
|
||||
void ActionJournal::Impl::onGlyphsError(const FontStack& fonts, const GlyphRange& range, std::exception_ptr error) {
|
||||
scheduler->schedule([=, this, env = MapEnvironmentSnapshot(*this)]() {
|
||||
ActionJournalEvent event("onGlyphsError", env);
|
||||
|
||||
event.addEventStringArray("fonts", fonts);
|
||||
event.addEvent("rangeStart", range.first);
|
||||
event.addEvent("rangeEnd", range.second);
|
||||
|
||||
if (error) {
|
||||
try {
|
||||
std::rethrow_exception(error);
|
||||
} catch (const std::exception& e) {
|
||||
event.addEvent("error", std::string(e.what()));
|
||||
}
|
||||
}
|
||||
|
||||
log(event);
|
||||
});
|
||||
}
|
||||
|
||||
void ActionJournal::Impl::onGlyphsRequested(const FontStack& fonts, const GlyphRange& range) {
|
||||
scheduler->schedule([=, this, env = MapEnvironmentSnapshot(*this)]() {
|
||||
log(ActionJournalEvent("onGlyphsRequested", env)
|
||||
.addEventStringArray("fonts", fonts)
|
||||
.addEvent("rangeStart", range.first)
|
||||
.addEvent("rangeEnd", range.second));
|
||||
});
|
||||
}
|
||||
|
||||
void ActionJournal::Impl::onTileAction(TileOperation op, const OverscaledTileID& id, const std::string& sourceID) {
|
||||
scheduler->schedule([=, this, env = MapEnvironmentSnapshot(*this)]() {
|
||||
log(ActionJournalEvent("onTileAction", env)
|
||||
.addEventEnum("action", op)
|
||||
.addEvent("tileX", id.canonical.x)
|
||||
.addEvent("tileY", id.canonical.y)
|
||||
.addEvent("tileZ", id.canonical.z)
|
||||
.addEvent("overscaledZ", id.overscaledZ)
|
||||
.addEvent("sourceID", sourceID));
|
||||
});
|
||||
}
|
||||
|
||||
void ActionJournal::Impl::onSpriteLoaded(const std::optional<style::Sprite>& sprite) {
|
||||
scheduler->schedule([=, this, env = MapEnvironmentSnapshot(*this)]() {
|
||||
ActionJournalEvent event("onSpriteLoaded", *this);
|
||||
|
||||
if (sprite) {
|
||||
event.addEvent("id", sprite.value().id);
|
||||
event.addEvent("url", sprite.value().spriteURL);
|
||||
}
|
||||
|
||||
log(event);
|
||||
});
|
||||
}
|
||||
|
||||
void ActionJournal::Impl::onSpriteError(const std::optional<style::Sprite>& sprite, std::exception_ptr error) {
|
||||
scheduler->schedule([=, this, env = MapEnvironmentSnapshot(*this)]() {
|
||||
ActionJournalEvent event("onSpriteError", env);
|
||||
|
||||
if (sprite) {
|
||||
event.addEvent("id", sprite.value().id);
|
||||
event.addEvent("url", sprite.value().spriteURL);
|
||||
}
|
||||
|
||||
if (error) {
|
||||
try {
|
||||
std::rethrow_exception(error);
|
||||
} catch (const std::exception& e) {
|
||||
event.addEvent("error", std::string(e.what()));
|
||||
}
|
||||
}
|
||||
|
||||
log(event);
|
||||
});
|
||||
}
|
||||
|
||||
void ActionJournal::Impl::onSpriteRequested(const std::optional<style::Sprite>& sprite) {
|
||||
scheduler->schedule([=, this, env = MapEnvironmentSnapshot(*this)]() {
|
||||
ActionJournalEvent event("onSpriteRequested", env);
|
||||
|
||||
if (sprite) {
|
||||
event.addEvent("id", sprite.value().id);
|
||||
event.addEvent("url", sprite.value().spriteURL);
|
||||
}
|
||||
|
||||
log(event);
|
||||
});
|
||||
}
|
||||
|
||||
void ActionJournal::Impl::onMapCreate() {
|
||||
scheduler->schedule(
|
||||
[=, this, env = MapEnvironmentSnapshot(*this)]() { log(ActionJournalEvent("onMapCreate", env)); });
|
||||
}
|
||||
|
||||
void ActionJournal::Impl::onMapDestroy() {
|
||||
scheduler->schedule(
|
||||
[=, this, env = MapEnvironmentSnapshot(*this)]() { log(ActionJournalEvent("onMapDestroy", env)); });
|
||||
}
|
||||
|
||||
void ActionJournal::Impl::log(ActionJournalEvent& value) {
|
||||
logToFile(value.toString());
|
||||
}
|
||||
|
||||
void ActionJournal::Impl::log(ActionJournalEvent&& value) {
|
||||
logToFile(value.toString());
|
||||
}
|
||||
|
||||
std::string ActionJournal::Impl::getFilepath(uint32_t fileIndex) const {
|
||||
return options.path() + "/" + ACTION_JOURNAL_FILE_NAME + "." + std::to_string(fileIndex) + "." +
|
||||
ACTION_JOURNAL_FILE_EXTENSION;
|
||||
}
|
||||
|
||||
uint32_t ActionJournal::Impl::detectFiles() const {
|
||||
if (!mbgl::filesystem::exists(options.path())) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::map<uint32_t, mbgl::filesystem::path> existingFiles;
|
||||
|
||||
const std::regex fileRegex(std::string(R"(.*\.([0-9]+)\.)") + ACTION_JOURNAL_FILE_EXTENSION);
|
||||
std::smatch fileMatch;
|
||||
|
||||
for (const auto& entry : mbgl::filesystem::directory_iterator(options.path())) {
|
||||
if (!entry.is_regular_file()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const std::string& path = entry.path().string();
|
||||
if (std::regex_match(path, fileMatch, fileRegex)) {
|
||||
if (fileMatch.size() == 2) {
|
||||
existingFiles.emplace(std::stoi(fileMatch[1].str()), path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (existingFiles.empty()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// removing extra files (old files or due to the file count changing)
|
||||
for (auto it = existingFiles.begin(); existingFiles.size() > options.logFileCount();) {
|
||||
mbgl::filesystem::remove(it->second);
|
||||
it = existingFiles.erase(it);
|
||||
}
|
||||
|
||||
uint32_t expectedIndex = 0;
|
||||
|
||||
// validate file index
|
||||
for (const auto& file : existingFiles) {
|
||||
if (file.first != expectedIndex) {
|
||||
mbgl::filesystem::rename(file.second, getFilepath(expectedIndex));
|
||||
}
|
||||
|
||||
++expectedIndex;
|
||||
}
|
||||
|
||||
return expectedIndex - 1;
|
||||
}
|
||||
|
||||
uint32_t ActionJournal::Impl::rollFiles() {
|
||||
// delete the oldest file
|
||||
const auto& oldestFilepath = getFilepath(0);
|
||||
if (mbgl::filesystem::exists(oldestFilepath)) {
|
||||
mbgl::filesystem::remove(oldestFilepath);
|
||||
}
|
||||
|
||||
// rename the rest
|
||||
uint32_t expectedIndex = 0;
|
||||
for (uint32_t index = 1; index < options.logFileCount(); ++index) {
|
||||
const auto& filepath = getFilepath(index);
|
||||
if (mbgl::filesystem::exists(filepath)) {
|
||||
mbgl::filesystem::rename(filepath, getFilepath(expectedIndex++));
|
||||
}
|
||||
}
|
||||
|
||||
return expectedIndex;
|
||||
}
|
||||
|
||||
bool ActionJournal::Impl::openFile(uint32_t fileIndex, bool truncate) {
|
||||
assert(fileIndex < options.logFileCount());
|
||||
|
||||
if (!mbgl::filesystem::exists(options.path())) {
|
||||
mbgl::filesystem::create_directories(options.path());
|
||||
}
|
||||
|
||||
const auto& filepath = getFilepath(fileIndex);
|
||||
const std::ios::openmode openMode = (truncate ? std::ios::trunc : std::ios::ate | std::ios::app) |
|
||||
std::fstream::out | std::fstream::in;
|
||||
|
||||
currentFile.open(filepath, openMode);
|
||||
|
||||
if (currentFile.is_open()) {
|
||||
currentFileIndex = fileIndex;
|
||||
currentFileSize = mbgl::filesystem::file_size(filepath);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ActionJournal::Impl::prepareFile(size_t size) {
|
||||
if (currentFileSize + size <= options.logFileSize() && currentFile) {
|
||||
currentFileSize += size;
|
||||
return true;
|
||||
}
|
||||
|
||||
// else roll file
|
||||
currentFile.close();
|
||||
|
||||
if (currentFileIndex + 1 >= options.logFileCount()) {
|
||||
currentFileIndex = rollFiles();
|
||||
} else {
|
||||
++currentFileIndex;
|
||||
}
|
||||
|
||||
return openFile(currentFileIndex, true);
|
||||
}
|
||||
|
||||
void ActionJournal::Impl::logToFile(const std::string& value) {
|
||||
if (value.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard lock(fileMutex);
|
||||
|
||||
if (!prepareFile(value.size() + 1)) {
|
||||
return;
|
||||
}
|
||||
|
||||
currentFile << value << "\n";
|
||||
currentFile.flush();
|
||||
}
|
||||
|
||||
} // namespace util
|
||||
} // namespace mbgl
|
||||
105
src/mbgl/util/action_journal_impl.hpp
Normal file
105
src/mbgl/util/action_journal_impl.hpp
Normal file
@@ -0,0 +1,105 @@
|
||||
#pragma once
|
||||
|
||||
#include <mbgl/util/action_journal.hpp>
|
||||
#include <mbgl/util/action_journal_options.hpp>
|
||||
#include <mbgl/map/map_observer.hpp>
|
||||
|
||||
#include <fstream>
|
||||
|
||||
namespace mbgl {
|
||||
|
||||
class Map;
|
||||
class Scheduler;
|
||||
|
||||
namespace util {
|
||||
|
||||
class ActionJournalEvent;
|
||||
|
||||
class ActionJournal::Impl : public MapObserver {
|
||||
public:
|
||||
Impl(const Map& map, const ActionJournalOptions& options);
|
||||
~Impl();
|
||||
|
||||
const Map& getMap() const { return map; }
|
||||
std::string getLogDirectory() const;
|
||||
std::vector<std::string> getLogFiles() const;
|
||||
std::vector<std::string> getLog();
|
||||
void clearLog();
|
||||
void flush();
|
||||
|
||||
static std::string getDirectoryName();
|
||||
|
||||
// Logged events
|
||||
|
||||
// MapObserver
|
||||
void onCameraWillChange(CameraChangeMode) override;
|
||||
void onCameraDidChange(CameraChangeMode) override;
|
||||
void onWillStartLoadingMap() override;
|
||||
void onDidFinishLoadingMap() override;
|
||||
void onDidFailLoadingMap(MapLoadError, const std::string&) override;
|
||||
void onWillStartRenderingMap() override;
|
||||
void onDidFinishRenderingMap(RenderMode) override;
|
||||
void onDidFinishLoadingStyle() override;
|
||||
void onSourceChanged(style::Source&) override;
|
||||
void onDidBecomeIdle() override;
|
||||
void onStyleImageMissing(const std::string&) override;
|
||||
void onRegisterShaders(gfx::ShaderRegistry&) override;
|
||||
void onPreCompileShader(shaders::BuiltIn, gfx::Backend::Type, const std::string&) override;
|
||||
void onPostCompileShader(shaders::BuiltIn, gfx::Backend::Type, const std::string&) override;
|
||||
void onShaderCompileFailed(shaders::BuiltIn, gfx::Backend::Type, const std::string&) override;
|
||||
void onGlyphsLoaded(const FontStack&, const GlyphRange&) override;
|
||||
void onGlyphsError(const FontStack&, const GlyphRange&, std::exception_ptr) override;
|
||||
void onGlyphsRequested(const FontStack&, const GlyphRange&) override;
|
||||
void onTileAction(TileOperation, const OverscaledTileID&, const std::string&) override;
|
||||
void onSpriteLoaded(const std::optional<style::Sprite>&) override;
|
||||
void onSpriteError(const std::optional<style::Sprite>&, std::exception_ptr) override;
|
||||
void onSpriteRequested(const std::optional<style::Sprite>&) override;
|
||||
|
||||
void onMapCreate();
|
||||
void onMapDestroy();
|
||||
|
||||
protected:
|
||||
void log(ActionJournalEvent& value);
|
||||
void log(ActionJournalEvent&& value);
|
||||
|
||||
// file operations
|
||||
|
||||
// generate the file path for the given index -> action_journal.{fileIndex}.log
|
||||
std::string getFilepath(uint32_t fileIndex) const;
|
||||
|
||||
// detect and validate files at `ActionJournalOptions.path()/ACTION_JOURNAL_DIRECTORY_NAME`
|
||||
uint32_t detectFiles() const;
|
||||
|
||||
// create a new log file with the highest unused index
|
||||
// delete the oldest one if `logFileCount` number of files was reached
|
||||
// rename the remaining files to match the index chain if a file was deleted
|
||||
//
|
||||
// action_journal.0.log -> deleted
|
||||
// action_journal.1.log -> action_journal.0.log
|
||||
// ...
|
||||
// action_journal.{logFileCount - 1}.log -> action_journal.{logFileCount - 2}.log
|
||||
// action_journal.{logFileCount - 1}.log -> new file
|
||||
uint32_t rollFiles();
|
||||
|
||||
// open the given index file
|
||||
bool openFile(uint32_t fileIndex, bool truncate = false);
|
||||
// check if the current file can log `size` bytes of info and roll files if needed
|
||||
bool prepareFile(size_t size);
|
||||
// log string to file and flush
|
||||
void logToFile(const std::string& value);
|
||||
|
||||
protected:
|
||||
const Map& map;
|
||||
|
||||
ActionJournalOptions options;
|
||||
|
||||
const std::shared_ptr<Scheduler> scheduler;
|
||||
|
||||
std::mutex fileMutex;
|
||||
std::fstream currentFile;
|
||||
uint32_t currentFileIndex{0};
|
||||
size_t currentFileSize{0};
|
||||
};
|
||||
|
||||
} // namespace util
|
||||
} // namespace mbgl
|
||||
@@ -49,6 +49,22 @@ std::string iso8601(Timestamp timestamp) {
|
||||
return buffer;
|
||||
}
|
||||
|
||||
std::string iso8601(std::chrono::time_point<std::chrono::system_clock, Milliseconds> timestamp) {
|
||||
std::time_t time = std::chrono::system_clock::to_time_t(timestamp);
|
||||
std::tm info;
|
||||
_gmtime(&time, &info);
|
||||
|
||||
long long ms =
|
||||
std::chrono::duration_cast<Milliseconds>(timestamp - std::chrono::system_clock::from_time_t(time)).count() %
|
||||
1000;
|
||||
|
||||
char buffer[sizeof("yyyy-mm-ddThh:mm:ss.000Z")];
|
||||
const std::size_t offset = std::strftime(buffer, sizeof(buffer), "%FT%T", &info);
|
||||
snprintf(buffer + offset, sizeof(buffer) - offset, ".%03lldZ", ms);
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
Timestamp parseTimestamp(const char *timestamp) {
|
||||
return std::chrono::time_point_cast<Seconds>(std::chrono::system_clock::from_time_t(parse_date(timestamp)));
|
||||
}
|
||||
|
||||
@@ -85,6 +85,7 @@ add_library(
|
||||
${PROJECT_SOURCE_DIR}/test/tile/tile_coordinate.test.cpp
|
||||
${PROJECT_SOURCE_DIR}/test/tile/tile_id.test.cpp
|
||||
${PROJECT_SOURCE_DIR}/test/tile/vector_tile.test.cpp
|
||||
${PROJECT_SOURCE_DIR}/test/util/action_journal.test.cpp
|
||||
${PROJECT_SOURCE_DIR}/test/util/async_task.test.cpp
|
||||
${PROJECT_SOURCE_DIR}/test/util/bounding_volumes.test.cpp
|
||||
${PROJECT_SOURCE_DIR}/test/util/camera.test.cpp
|
||||
|
||||
@@ -178,7 +178,7 @@ TEST(Transform, UnwrappedLatLng) {
|
||||
}
|
||||
|
||||
TEST(Transform, ConstrainHeightOnly) {
|
||||
Transform transform(MapObserver::nullObserver(), ConstrainMode::HeightOnly);
|
||||
Transform transform(TransformObserver::nullObserver(), ConstrainMode::HeightOnly);
|
||||
transform.resize({2, 2});
|
||||
|
||||
transform.jumpTo(CameraOptions().withCenter(LatLngBounds::world().southwest()).withZoom(util::MAX_ZOOM));
|
||||
@@ -191,7 +191,7 @@ TEST(Transform, ConstrainHeightOnly) {
|
||||
}
|
||||
|
||||
TEST(Transform, ConstrainWidthAndHeight) {
|
||||
Transform transform(MapObserver::nullObserver(), ConstrainMode::WidthAndHeight);
|
||||
Transform transform(TransformObserver::nullObserver(), ConstrainMode::WidthAndHeight);
|
||||
transform.resize({2, 2});
|
||||
|
||||
transform.jumpTo(CameraOptions().withCenter(LatLngBounds::world().southwest()).withZoom(util::MAX_ZOOM));
|
||||
@@ -541,7 +541,7 @@ TEST(Transform, IsPanning) {
|
||||
}
|
||||
|
||||
TEST(Transform, DefaultTransform) {
|
||||
struct TransformObserver : public mbgl::MapObserver {
|
||||
struct TransformObserver : public mbgl::TransformObserver {
|
||||
void onCameraWillChange(MapObserver::CameraChangeMode) final { cameraWillChangeCallback(); };
|
||||
|
||||
void onCameraDidChange(MapObserver::CameraChangeMode) final { cameraDidChangeCallback(); };
|
||||
|
||||
@@ -12,7 +12,7 @@ using namespace mbgl;
|
||||
|
||||
TEST(TileCoordinate, FromLatLng) {
|
||||
size_t changeCount = 0;
|
||||
struct TransformObserver : public mbgl::MapObserver {
|
||||
struct TransformObserver : public mbgl::TransformObserver {
|
||||
void onCameraWillChange(MapObserver::CameraChangeMode mode) final {
|
||||
if (mode == MapObserver::CameraChangeMode::Immediate && cameraWillChangeImmediateCallback) {
|
||||
cameraWillChangeImmediateCallback();
|
||||
|
||||
431
test/util/action_journal.test.cpp
Normal file
431
test/util/action_journal.test.cpp
Normal file
@@ -0,0 +1,431 @@
|
||||
#include <mbgl/util/action_journal.hpp>
|
||||
#include <mbgl/util/action_journal_impl.hpp>
|
||||
#include <mbgl/util/run_loop.hpp>
|
||||
#include <mbgl/util/io.hpp>
|
||||
#include <mbgl/util/rapidjson.hpp>
|
||||
#include <mbgl/util/chrono.hpp>
|
||||
#include <mbgl/style/style.hpp>
|
||||
#include <mbgl/style/sources/geojson_source.hpp>
|
||||
#include <mbgl/gfx/headless_frontend.hpp>
|
||||
#include <mbgl/gfx/shader_registry.hpp>
|
||||
#include <mbgl/map/map_impl.hpp>
|
||||
|
||||
#include <mbgl/test/util.hpp>
|
||||
#include <mbgl/test/stub_file_source.hpp>
|
||||
#include <mbgl/test/stub_map_observer.hpp>
|
||||
|
||||
#include <gmock/gmock-matchers.h>
|
||||
#include <gmock/gmock-more-matchers.h>
|
||||
|
||||
#include <regex>
|
||||
|
||||
using namespace mbgl;
|
||||
using namespace mbgl::util;
|
||||
using namespace ::testing;
|
||||
|
||||
class ActionJournalTest {
|
||||
public:
|
||||
class MapAdapter : public Map {
|
||||
public:
|
||||
MapAdapter(RendererFrontend& frontend,
|
||||
MapObserver& observer,
|
||||
std::shared_ptr<FileSource> fileSource,
|
||||
const MapOptions& options,
|
||||
const util::ActionJournalOptions& actionJournalOptions)
|
||||
: Map(std::make_unique<Map::Impl>(frontend, observer, std::move(fileSource), options),
|
||||
actionJournalOptions) {}
|
||||
|
||||
Map::Impl& getImpl() { return *impl; }
|
||||
};
|
||||
|
||||
ActionJournalOptions options;
|
||||
StubMapObserver observer;
|
||||
std::shared_ptr<StubFileSource> fileSource;
|
||||
std::unique_ptr<HeadlessFrontend> frontend;
|
||||
std::unique_ptr<MapAdapter> map;
|
||||
|
||||
util::RunLoop runLoop;
|
||||
|
||||
ActionJournalTest(const ActionJournalOptions& options_, MapMode mode = MapMode::Static)
|
||||
: options(options_) {
|
||||
clear();
|
||||
createMap(mode);
|
||||
}
|
||||
|
||||
~ActionJournalTest() {
|
||||
map.reset();
|
||||
frontend.reset();
|
||||
fileSource.reset();
|
||||
clear();
|
||||
}
|
||||
|
||||
void createMap(MapMode mode = MapMode::Static) {
|
||||
fileSource = std::make_shared<StubFileSource>(ResourceOptions::Default(),
|
||||
ClientOptions().withName("ActionJournalTest").withVersion("1.0"));
|
||||
frontend = std::make_unique<HeadlessFrontend>(1.0f);
|
||||
map = std::make_unique<MapAdapter>(
|
||||
*frontend, observer, fileSource, MapOptions().withMapMode(mode).withSize(frontend->getSize()), options);
|
||||
}
|
||||
|
||||
std::filesystem::path getDirectoryPath() {
|
||||
return std::filesystem::canonical(options.path()) / ActionJournal::Impl::getDirectoryName();
|
||||
}
|
||||
|
||||
void clear() { std::filesystem::remove_all(getDirectoryPath()); }
|
||||
};
|
||||
|
||||
TEST(ActionJournal, Create) {
|
||||
ActionJournalTest test(ActionJournalOptions().enable().withPath("."));
|
||||
|
||||
test.map->getActionJournal()->impl->flush();
|
||||
|
||||
// map creation logs `onMapCreate`
|
||||
EXPECT_EQ(test.map->getActionJournal()->getLogDirectory(), test.getDirectoryPath().generic_string());
|
||||
EXPECT_TRUE(std::filesystem::exists(test.map->getActionJournal()->getLogDirectory()));
|
||||
EXPECT_EQ(test.map->getActionJournal()->getLogFiles().size(), 1u);
|
||||
|
||||
const auto log = test.map->getActionJournal()->getLog();
|
||||
EXPECT_EQ(log.size(), 1u);
|
||||
EXPECT_THAT(log.front(), HasSubstr("onMapCreate"));
|
||||
}
|
||||
|
||||
TEST(ActionJournal, Clear) {
|
||||
ActionJournalTest test(ActionJournalOptions().enable().withPath("."));
|
||||
|
||||
test.map->getActionJournal()->impl->flush();
|
||||
test.map->getActionJournal()->clearLog();
|
||||
|
||||
EXPECT_TRUE(std::filesystem::exists(test.map->getActionJournal()->getLogDirectory()));
|
||||
EXPECT_EQ(test.map->getActionJournal()->getLogFiles().size(), 1u);
|
||||
EXPECT_THAT(test.map->getActionJournal()->getLog(), IsEmpty());
|
||||
}
|
||||
|
||||
TEST(ActionJournal, ReadPreviousSession) {
|
||||
// generate `onMapCreate` event
|
||||
ActionJournalTest test(ActionJournalOptions().enable().withPath("."));
|
||||
|
||||
// generate `onMapDestroy` event
|
||||
test.map.reset();
|
||||
|
||||
// generate `onMapCreate` event
|
||||
test.createMap();
|
||||
|
||||
test.map->getActionJournal()->impl->flush();
|
||||
|
||||
const auto log = test.map->getActionJournal()->getLog();
|
||||
EXPECT_EQ(log.size(), 3u);
|
||||
EXPECT_THAT(log, ElementsAre(HasSubstr("onMapCreate"), HasSubstr("onMapDestroy"), HasSubstr("onMapCreate")));
|
||||
}
|
||||
|
||||
TEST(ActionJournal, RollFiles) {
|
||||
// limit the number of files and the size of the file
|
||||
ActionJournalTest test(ActionJournalOptions().enable().withPath(".").withLogFileSize(1).withLogFileCount(2));
|
||||
|
||||
// generate multiple log events by loading a style and rendering
|
||||
test.map->getStyle().loadJSON(util::read_file("test/fixtures/api/empty.json"));
|
||||
|
||||
test.runLoop.runOnce();
|
||||
test.frontend->render(*test.map);
|
||||
|
||||
test.map->getActionJournal()->impl->flush();
|
||||
|
||||
// check if the files roll and respect the limits
|
||||
const auto files = test.map->getActionJournal()->getLogFiles();
|
||||
EXPECT_TRUE(std::filesystem::exists(test.map->getActionJournal()->getLogDirectory()));
|
||||
EXPECT_EQ(files.size(), 2u);
|
||||
EXPECT_EQ(test.map->getActionJournal()->getLog().size(), 2u);
|
||||
|
||||
// the newest event should be `onMapDestroy`
|
||||
test.map.reset();
|
||||
EXPECT_THAT(util::read_file(files.back()), HasSubstr("onMapDestroy"));
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
template <typename EventMethod, typename... Args>
|
||||
void validateEventList(ActionJournalTest& test,
|
||||
std::vector<std::pair<const char*, std::function<void(const mbgl::JSValue&)>>>&& generatedEvents,
|
||||
EventMethod&& eventGenerator,
|
||||
Args&&... args) {
|
||||
test.map->getActionJournal()->clearLog();
|
||||
(test.map->getImpl().*eventGenerator)(std::forward<Args>(args)...);
|
||||
test.map->getActionJournal()->impl->flush();
|
||||
|
||||
const auto& log = test.map->getActionJournal()->getLog();
|
||||
EXPECT_EQ(log.size(), generatedEvents.size());
|
||||
|
||||
auto logIt = log.begin();
|
||||
for (const auto& generatedEvent : generatedEvents) {
|
||||
mbgl::JSDocument document;
|
||||
document.Parse<rapidjson::kParseDefaultFlags>(*logIt++);
|
||||
|
||||
EXPECT_FALSE(document.HasParseError());
|
||||
EXPECT_TRUE(document.IsObject());
|
||||
|
||||
const auto& json = document.GetObject();
|
||||
|
||||
EXPECT_TRUE(json.HasMember("name"));
|
||||
EXPECT_TRUE(json["name"].IsString());
|
||||
EXPECT_STREQ(json["name"].GetString(), generatedEvent.first);
|
||||
|
||||
EXPECT_TRUE(json.HasMember("time"));
|
||||
EXPECT_TRUE(json["time"].IsString());
|
||||
|
||||
EXPECT_TRUE(json.HasMember("clientName"));
|
||||
EXPECT_TRUE(json["clientName"].IsString());
|
||||
EXPECT_STREQ(json["clientName"].GetString(), test.fileSource->getClientOptions().name().c_str());
|
||||
|
||||
EXPECT_TRUE(json.HasMember("clientVersion"));
|
||||
EXPECT_TRUE(json["clientVersion"].IsString());
|
||||
EXPECT_STREQ(json["clientVersion"].GetString(), test.fileSource->getClientOptions().version().c_str());
|
||||
|
||||
// `MatchesRegex(R"(\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\d\.\d\d\dZ)")` fails on linux gcc
|
||||
// https://github.com/google/googletest/issues/3084
|
||||
// `MatchesRegex(R"([0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]T[0-9][0-9]:[0-9][0-9]:[0-9][0-9]\.[0-9][0-9][0-9]Z)"))`
|
||||
// fails on windows CI
|
||||
// using std::regex for now
|
||||
std::cmatch match;
|
||||
const std::regex timeRegex(R"(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z)");
|
||||
EXPECT_TRUE(std::regex_match(json["time"].GetString(), match, timeRegex));
|
||||
|
||||
if (generatedEvent.second) {
|
||||
EXPECT_TRUE(json.HasMember("event"));
|
||||
EXPECT_TRUE(json["event"].IsObject());
|
||||
|
||||
generatedEvent.second(json["event"].GetObject());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename EventMethod, typename... Args>
|
||||
void validateEvent(ActionJournalTest& test,
|
||||
const char* generatedEvent,
|
||||
std::function<void(const mbgl::JSValue&)>&& checkOutput,
|
||||
EventMethod&& eventGenerator,
|
||||
Args&&... args) {
|
||||
validateEventList(test, {{generatedEvent, checkOutput}}, eventGenerator, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(ActionJournal, ValidateEvents) {
|
||||
ActionJournalTest test(
|
||||
ActionJournalOptions().enable().withPath(".").withLogFileCount(2).withLogFileSize(1024 * 1024 * 5),
|
||||
MapMode::Continuous);
|
||||
|
||||
test.map->getStyle().loadJSON(util::read_file("test/fixtures/api/empty.json"));
|
||||
test.map->getActionJournal()->impl->flush();
|
||||
|
||||
const auto testShaderRegistry = std::make_unique<gfx::ShaderRegistry>();
|
||||
const auto testSource = std::make_unique<style::GeoJSONSource>("testSource");
|
||||
const auto testSprite = style::Sprite("testID", "testURL");
|
||||
const auto testExceptionMessage = "test error";
|
||||
const auto testException = std::make_exception_ptr(std::runtime_error(testExceptionMessage));
|
||||
const auto testFontStack = FontStack{"Roboto Condensed Italic", "Noto Sans Italic"};
|
||||
const auto testGlyphRange = GlyphRange(0, 255);
|
||||
|
||||
validateEvent(
|
||||
test,
|
||||
"onCameraWillChange",
|
||||
[&](const mbgl::JSValue& json) {
|
||||
EXPECT_TRUE(json.HasMember("cameraMode"));
|
||||
EXPECT_TRUE(json["cameraMode"].IsInt());
|
||||
EXPECT_EQ(json["cameraMode"].GetInt(), static_cast<int>(MapObserver::CameraChangeMode::Immediate));
|
||||
},
|
||||
&TransformObserver::onCameraWillChange,
|
||||
MapObserver::CameraChangeMode::Immediate);
|
||||
|
||||
validateEvent(
|
||||
test,
|
||||
"onCameraDidChange",
|
||||
[&](const mbgl::JSValue& json) {
|
||||
EXPECT_TRUE(json.HasMember("cameraMode"));
|
||||
EXPECT_TRUE(json["cameraMode"].IsInt());
|
||||
EXPECT_EQ(json["cameraMode"].GetInt(), static_cast<int>(MapObserver::CameraChangeMode::Immediate));
|
||||
},
|
||||
&TransformObserver::onCameraDidChange,
|
||||
MapObserver::CameraChangeMode::Immediate);
|
||||
|
||||
validateEvent(test, "onWillStartLoadingMap", nullptr, &style::Observer::onStyleLoading);
|
||||
validateEvent(test, "onDidFinishLoadingStyle", nullptr, &style::Observer::onStyleLoaded);
|
||||
|
||||
validateEvent(
|
||||
test,
|
||||
"onSourceChanged",
|
||||
[&](const mbgl::JSValue& json) {
|
||||
EXPECT_TRUE(json.HasMember("type"));
|
||||
EXPECT_TRUE(json["type"].IsString());
|
||||
EXPECT_EQ(Enum<style::SourceType>::toEnum(json["type"].GetString()), style::SourceType::GeoJSON);
|
||||
|
||||
EXPECT_TRUE(json.HasMember("id"));
|
||||
EXPECT_TRUE(json["id"].IsString());
|
||||
EXPECT_STREQ(json["id"].GetString(), testSource->getID().c_str());
|
||||
},
|
||||
&style::Observer::onSourceChanged,
|
||||
*testSource);
|
||||
|
||||
validateEvent(test, "onRegisterShaders", nullptr, &RendererObserver::onRegisterShaders, *testShaderRegistry);
|
||||
|
||||
validateEvent(
|
||||
test,
|
||||
"onDidFailLoadingMap",
|
||||
[&](const mbgl::JSValue& json) {
|
||||
EXPECT_TRUE(json.HasMember("error"));
|
||||
EXPECT_TRUE(json["error"].IsString());
|
||||
EXPECT_STREQ(json["error"].GetString(), testExceptionMessage);
|
||||
|
||||
EXPECT_TRUE(json.HasMember("code"));
|
||||
EXPECT_TRUE(json["code"].IsInt());
|
||||
EXPECT_EQ(json["code"].GetInt(), static_cast<int>(MapLoadError::UnknownError));
|
||||
},
|
||||
&style::Observer::onStyleError,
|
||||
testException);
|
||||
|
||||
const auto checkSprite = [&](const mbgl::JSValue& json) {
|
||||
EXPECT_TRUE(json.HasMember("id"));
|
||||
EXPECT_TRUE(json["id"].IsString());
|
||||
EXPECT_EQ(json["id"].GetString(), testSprite.id);
|
||||
|
||||
EXPECT_TRUE(json.HasMember("url"));
|
||||
EXPECT_TRUE(json["url"].IsString());
|
||||
EXPECT_EQ(json["url"].GetString(), testSprite.spriteURL);
|
||||
};
|
||||
|
||||
validateEvent(test, "onSpriteLoaded", checkSprite, &style::Observer::onSpriteLoaded, testSprite);
|
||||
validateEvent(test, "onSpriteRequested", checkSprite, &style::Observer::onSpriteRequested, testSprite);
|
||||
validateEvent(
|
||||
test,
|
||||
"onSpriteError",
|
||||
[&](const mbgl::JSValue& json) {
|
||||
checkSprite(json);
|
||||
|
||||
EXPECT_TRUE(json.HasMember("error"));
|
||||
EXPECT_TRUE(json["error"].IsString());
|
||||
EXPECT_STREQ(json["error"].GetString(), testExceptionMessage);
|
||||
},
|
||||
&style::Observer::onSpriteError,
|
||||
testSprite,
|
||||
testException);
|
||||
|
||||
validateEvent(test, "onWillStartRenderingMap", nullptr, &RendererObserver::onWillStartRenderingMap);
|
||||
|
||||
// `onDidFinishRenderingMap` and `onDidFinishLoadingMap` are generated by the same callback
|
||||
validateEventList(test,
|
||||
{{"onDidFinishRenderingMap", nullptr}, {"onDidFinishLoadingMap", nullptr}},
|
||||
&RendererObserver::onDidFinishRenderingMap);
|
||||
|
||||
validateEvent(
|
||||
test,
|
||||
"onStyleImageMissing",
|
||||
[&](const mbgl::JSValue& json) {
|
||||
EXPECT_TRUE(json.HasMember("id"));
|
||||
EXPECT_TRUE(json["id"].IsString());
|
||||
EXPECT_STREQ(json["id"].GetString(), "image");
|
||||
},
|
||||
&RendererObserver::onStyleImageMissing,
|
||||
"image",
|
||||
[]() {});
|
||||
|
||||
const auto checkShader = [&](const mbgl::JSValue& json) {
|
||||
EXPECT_TRUE(json.HasMember("shader"));
|
||||
EXPECT_TRUE(json["shader"].IsString());
|
||||
EXPECT_EQ(Enum<shaders::BuiltIn>::toEnum(json["shader"].GetString()), shaders::BuiltIn::None);
|
||||
|
||||
EXPECT_TRUE(json.HasMember("backend"));
|
||||
EXPECT_TRUE(json["backend"].IsInt());
|
||||
EXPECT_EQ(json["backend"].GetInt(), static_cast<int>(gfx::Backend::Type::OpenGL));
|
||||
};
|
||||
|
||||
validateEvent(test,
|
||||
"onPreCompileShader",
|
||||
checkShader,
|
||||
&RendererObserver::onPreCompileShader,
|
||||
shaders::BuiltIn::None,
|
||||
gfx::Backend::Type::OpenGL,
|
||||
"defines");
|
||||
|
||||
validateEvent(test,
|
||||
"onPostCompileShader",
|
||||
checkShader,
|
||||
&RendererObserver::onPostCompileShader,
|
||||
shaders::BuiltIn::None,
|
||||
gfx::Backend::Type::OpenGL,
|
||||
"defines");
|
||||
|
||||
validateEvent(
|
||||
test,
|
||||
"onShaderCompileFailed",
|
||||
[&](const mbgl::JSValue& json) {
|
||||
checkShader(json);
|
||||
|
||||
EXPECT_TRUE(json.HasMember("defines"));
|
||||
EXPECT_TRUE(json["defines"].IsString());
|
||||
EXPECT_STREQ(json["defines"].GetString(), "defines");
|
||||
},
|
||||
&RendererObserver::onShaderCompileFailed,
|
||||
shaders::BuiltIn::None,
|
||||
gfx::Backend::Type::OpenGL,
|
||||
"defines");
|
||||
|
||||
const auto checkGlyphs = [&](const mbgl::JSValue& json) {
|
||||
EXPECT_TRUE(json.HasMember("fonts"));
|
||||
EXPECT_TRUE(json["fonts"].IsArray());
|
||||
EXPECT_EQ(json["fonts"].GetArray().Size(), testFontStack.size());
|
||||
|
||||
auto fontIt = testFontStack.begin();
|
||||
for (const auto& font : json["fonts"].GetArray()) {
|
||||
EXPECT_TRUE(font.IsString());
|
||||
EXPECT_EQ(font.GetString(), *fontIt++);
|
||||
}
|
||||
|
||||
EXPECT_TRUE(json.HasMember("rangeStart"));
|
||||
EXPECT_TRUE(json["rangeStart"].IsInt());
|
||||
EXPECT_EQ(json["rangeStart"].GetInt(), testGlyphRange.first);
|
||||
|
||||
EXPECT_TRUE(json.HasMember("rangeEnd"));
|
||||
EXPECT_TRUE(json["rangeEnd"].IsInt());
|
||||
EXPECT_EQ(json["rangeEnd"].GetInt(), testGlyphRange.second);
|
||||
};
|
||||
|
||||
validateEvent(
|
||||
test, "onGlyphsLoaded", checkGlyphs, &RendererObserver::onGlyphsLoaded, testFontStack, testGlyphRange);
|
||||
validateEvent(
|
||||
test, "onGlyphsRequested", checkGlyphs, &RendererObserver::onGlyphsRequested, testFontStack, testGlyphRange);
|
||||
|
||||
validateEvent(
|
||||
test,
|
||||
"onGlyphsError",
|
||||
[&](const mbgl::JSValue& json) {
|
||||
checkGlyphs(json);
|
||||
|
||||
EXPECT_TRUE(json.HasMember("error"));
|
||||
EXPECT_TRUE(json["error"].IsString());
|
||||
EXPECT_STREQ(json["error"].GetString(), testExceptionMessage);
|
||||
},
|
||||
&RendererObserver::onGlyphsError,
|
||||
testFontStack,
|
||||
testGlyphRange,
|
||||
testException);
|
||||
|
||||
validateEvent(test,
|
||||
"onTileAction",
|
||||
nullptr,
|
||||
&RendererObserver::onTileAction,
|
||||
TileOperation::StartParse,
|
||||
OverscaledTileID(0, 0, 0),
|
||||
"");
|
||||
|
||||
const auto onDidFinishRenderingFrame =
|
||||
static_cast<void (RendererObserver::*)(RendererObserver::RenderMode, bool, bool, double, double)>(
|
||||
&RendererObserver::onDidFinishRenderingFrame);
|
||||
|
||||
validateEvent(test,
|
||||
"onDidBecomeIdle",
|
||||
nullptr,
|
||||
onDidFinishRenderingFrame,
|
||||
RendererObserver::RenderMode::Full,
|
||||
false,
|
||||
false,
|
||||
0.0,
|
||||
0.0);
|
||||
}
|
||||
2
vendor/BUILD.bazel
vendored
2
vendor/BUILD.bazel
vendored
@@ -17,6 +17,7 @@ cc_library(
|
||||
"maplibre-native-base/deps/supercluster.hpp/include/*.hpp",
|
||||
"maplibre-native-base/deps/variant/include/mapbox/*.hpp",
|
||||
"maplibre-native-base/extras/expected-lite/include/**/*.hpp",
|
||||
"maplibre-native-base/extras/filesystem/include/ghc/*.hpp",
|
||||
"maplibre-native-base/extras/kdbush.hpp/include/*.hpp",
|
||||
"maplibre-native-base/extras/rapidjson/include/**/*.h",
|
||||
"maplibre-native-base/include/mapbox/**/*.hpp",
|
||||
@@ -37,6 +38,7 @@ cc_library(
|
||||
"maplibre-native-base/deps/variant/include",
|
||||
"maplibre-native-base/extras/args",
|
||||
"maplibre-native-base/extras/expected-lite/include",
|
||||
"maplibre-native-base/extras/filesystem/include",
|
||||
"maplibre-native-base/extras/kdbush.hpp/include",
|
||||
"maplibre-native-base/extras/rapidjson/include",
|
||||
"maplibre-native-base/include",
|
||||
|
||||
9
vendor/maplibre-native-base.cmake
vendored
9
vendor/maplibre-native-base.cmake
vendored
@@ -20,6 +20,15 @@ set_target_properties(
|
||||
INTERFACE_MAPLIBRE_LICENSE ${CMAKE_CURRENT_LIST_DIR}/maplibre-native-base/extras/expected-lite/LICENSE.txt
|
||||
)
|
||||
|
||||
set_target_properties(
|
||||
maplibre-native-base-extras-filesystem
|
||||
PROPERTIES
|
||||
INTERFACE_MAPLIBRE_NAME "filesystem"
|
||||
INTERFACE_MAPLIBRE_URL "https://github.com/gulrak/filesystem"
|
||||
INTERFACE_MAPLIBRE_AUTHOR "Steffen Schümann"
|
||||
INTERFACE_MAPLIBRE_LICENSE ${CMAKE_CURRENT_LIST_DIR}/maplibre-native-base/extras/filesystem/LICENSE.txt
|
||||
)
|
||||
|
||||
set_target_properties(
|
||||
maplibre-native-base-supercluster.hpp
|
||||
PROPERTIES
|
||||
|
||||
@@ -21,6 +21,7 @@ add_library(maplibre-native-base-extras INTERFACE)
|
||||
add_library(MapLibreNative::Base::Extras ALIAS maplibre-native-base-extras)
|
||||
|
||||
maplibre_native_base_extras_add_library(args ${CMAKE_CURRENT_LIST_DIR}/args)
|
||||
maplibre_native_base_extras_add_library(filesystem ${CMAKE_CURRENT_LIST_DIR}/filesystem/include)
|
||||
maplibre_native_base_extras_add_library(expected-lite ${CMAKE_CURRENT_LIST_DIR}/expected-lite/include)
|
||||
maplibre_native_base_extras_add_library(kdbush.hpp ${CMAKE_CURRENT_LIST_DIR}/kdbush.hpp/include)
|
||||
|
||||
|
||||
1
vendor/maplibre-native-base/extras/filesystem
vendored
Submodule
1
vendor/maplibre-native-base/extras/filesystem
vendored
Submodule
Submodule vendor/maplibre-native-base/extras/filesystem added at 9fda7b0afb
Reference in New Issue
Block a user