summaryrefslogtreecommitdiff
path: root/platform
diff options
context:
space:
mode:
authorSudarsana Babu Nagineni <sudarsana.babu@mapbox.com>2018-07-30 15:30:57 +0300
committerSudarsana Babu Nagineni <sudarsana.babu@mapbox.com>2018-08-06 16:55:30 +0300
commit0130c58fdd74edbbfe668f3125a9377554d7d605 (patch)
tree6be1c1d25d146e9273f04f40f2283e18048c17ac /platform
parent7373abef92ed4911b91f9fca3d97784ed0d9e02e (diff)
downloadqtlocation-mapboxgl-0130c58fdd74edbbfe668f3125a9377554d7d605.tar.gz
Bump Mapbox GL Native
Bump version. mapbox-gl-native @ 377a6e42d687c419e6ae1012b8626336f5dfc1b6
Diffstat (limited to 'platform')
-rw-r--r--platform/default/collator.cpp79
-rw-r--r--platform/default/default_file_source.cpp21
-rw-r--r--platform/default/http_file_source.cpp165
-rw-r--r--platform/default/mbgl/map/map_snapshotter.cpp75
-rw-r--r--platform/default/mbgl/map/map_snapshotter.hpp14
-rw-r--r--platform/default/mbgl/storage/.clang-tidy2
-rw-r--r--platform/default/mbgl/storage/offline_database.cpp168
-rw-r--r--platform/default/mbgl/storage/offline_database.hpp12
-rw-r--r--platform/default/mbgl/storage/offline_download.cpp40
-rw-r--r--platform/default/mbgl/storage/offline_download.hpp4
-rw-r--r--platform/default/mbgl/storage/offline_schema.hpp (renamed from platform/default/mbgl/storage/offline_schema.cpp.include)12
-rw-r--r--platform/default/mbgl/storage/offline_schema.js21
-rw-r--r--platform/default/mbgl/storage/offline_schema.sql64
-rw-r--r--platform/default/sqlite3.cpp146
-rw-r--r--platform/default/sqlite3.hpp19
-rw-r--r--platform/default/string_stdlib.cpp5
-rw-r--r--platform/default/unaccent.cpp43
-rw-r--r--platform/default/unaccent.hpp13
-rw-r--r--platform/qt/include/qmapboxgl.hpp4
-rw-r--r--platform/qt/src/qmapboxgl.cpp108
-rw-r--r--platform/qt/src/qmapboxgl_map_renderer.cpp67
-rw-r--r--platform/qt/src/qmapboxgl_map_renderer.hpp11
-rw-r--r--platform/qt/src/qmapboxgl_scheduler.cpp38
-rw-r--r--platform/qt/src/qmapboxgl_scheduler.hpp34
-rw-r--r--platform/qt/src/qt_geojson.cpp7
-rw-r--r--platform/qt/src/sqlite3.cpp107
26 files changed, 914 insertions, 365 deletions
diff --git a/platform/default/collator.cpp b/platform/default/collator.cpp
new file mode 100644
index 0000000000..b7f256756e
--- /dev/null
+++ b/platform/default/collator.cpp
@@ -0,0 +1,79 @@
+#include <mbgl/style/expression/collator.hpp>
+#include <mbgl/util/platform.hpp>
+#include <libnu/strcoll.h>
+#include <unaccent.hpp>
+
+/*
+ The default implementation of Collator ignores locale.
+ Case sensitivity and collation order are based on
+ Default Unicode Collation Element Table (DUCET).
+
+ Diacritic-insensitivity is implemented with nunicode's
+ non-standard "unaccent" functionality, which is tailored
+ to European languages.
+
+ It would be possible to implement locale awareness using ICU,
+ but would require bundling locale data.
+*/
+
+namespace mbgl {
+namespace style {
+namespace expression {
+
+class Collator::Impl {
+public:
+ Impl(bool caseSensitive_, bool diacriticSensitive_, optional<std::string>)
+ : caseSensitive(caseSensitive_)
+ , diacriticSensitive(diacriticSensitive_)
+ {}
+
+ bool operator==(const Impl& other) const {
+ return caseSensitive == other.caseSensitive &&
+ diacriticSensitive == other.diacriticSensitive;
+ }
+
+ int compare(const std::string& lhs, const std::string& rhs) const {
+ if (caseSensitive && diacriticSensitive) {
+ return nu_strcoll(lhs.c_str(), rhs.c_str(),
+ nu_utf8_read, nu_utf8_read);
+ } else if (!caseSensitive && diacriticSensitive) {
+ return nu_strcasecoll(lhs.c_str(), rhs.c_str(),
+ nu_utf8_read, nu_utf8_read);
+ } else if (caseSensitive && !diacriticSensitive) {
+ return nu_strcoll(platform::unaccent(lhs).c_str(), platform::unaccent(rhs).c_str(),
+ nu_utf8_read, nu_utf8_read);
+ } else {
+ return nu_strcasecoll(platform::unaccent(lhs).c_str(), platform::unaccent(rhs).c_str(),
+ nu_utf8_read, nu_utf8_read);
+ }
+ }
+
+ std::string resolvedLocale() const {
+ return "";
+ }
+private:
+ bool caseSensitive;
+ bool diacriticSensitive;
+};
+
+
+Collator::Collator(bool caseSensitive, bool diacriticSensitive, optional<std::string> locale_)
+ : impl(std::make_shared<Impl>(caseSensitive, diacriticSensitive, std::move(locale_)))
+{}
+
+bool Collator::operator==(const Collator& other) const {
+ return *impl == *(other.impl);
+}
+
+int Collator::compare(const std::string& lhs, const std::string& rhs) const {
+ return impl->compare(lhs, rhs);
+}
+
+std::string Collator::resolvedLocale() const {
+ return impl->resolvedLocale();
+}
+
+
+} // namespace expression
+} // namespace style
+} // namespace mbgl
diff --git a/platform/default/default_file_source.cpp b/platform/default/default_file_source.cpp
index cb602995a4..f070121497 100644
--- a/platform/default/default_file_source.cpp
+++ b/platform/default/default_file_source.cpp
@@ -11,6 +11,7 @@
#include <mbgl/util/url.hpp>
#include <mbgl/util/thread.hpp>
#include <mbgl/util/work_request.hpp>
+#include <mbgl/util/stopwatch.hpp>
#include <cassert>
@@ -18,15 +19,10 @@ namespace mbgl {
class DefaultFileSource::Impl {
public:
- Impl(ActorRef<Impl> self, std::shared_ptr<FileSource> assetFileSource_, const std::string& cachePath, uint64_t maximumCacheSize)
+ Impl(std::shared_ptr<FileSource> assetFileSource_, std::string cachePath, uint64_t maximumCacheSize)
: assetFileSource(assetFileSource_)
- , localFileSource(std::make_unique<LocalFileSource>()) {
- // Initialize the Database asynchronously so as to not block Actor creation.
- self.invoke(&Impl::initializeOfflineDatabase, cachePath, maximumCacheSize);
- }
-
- void initializeOfflineDatabase(std::string cachePath, uint64_t maximumCacheSize) {
- offlineDatabase = std::make_unique<OfflineDatabase>(cachePath, maximumCacheSize);
+ , localFileSource(std::make_unique<LocalFileSource>())
+ , offlineDatabase(std::make_unique<OfflineDatabase>(cachePath, maximumCacheSize)) {
}
void setAPIBaseURL(const std::string& url) {
@@ -151,8 +147,17 @@ public:
// Get from the online file source
if (resource.hasLoadingMethod(Resource::LoadingMethod::Network)) {
+ MBGL_TIMING_START(watch);
tasks[req] = onlineFileSource.request(resource, [=] (Response onlineResponse) mutable {
this->offlineDatabase->put(resource, onlineResponse);
+ if (resource.kind == Resource::Kind::Tile) {
+ // onlineResponse.data will be null if data not modified
+ MBGL_TIMING_FINISH(watch,
+ " Action: " << "Requesting," <<
+ " URL: " << resource.url.c_str() <<
+ " Size: " << (onlineResponse.data != nullptr ? onlineResponse.data->size() : 0) << "B," <<
+ " Time")
+ }
callback(onlineResponse);
});
}
diff --git a/platform/default/http_file_source.cpp b/platform/default/http_file_source.cpp
index a9c442c2de..9166d92fb7 100644
--- a/platform/default/http_file_source.cpp
+++ b/platform/default/http_file_source.cpp
@@ -13,6 +13,85 @@
#include <curl/curl.h>
+// Dynamically load all cURL functions. Debian-derived systems upgraded the OpenSSL version linked
+// to in https://salsa.debian.org/debian/curl/commit/95c94957bb7e89e36e78b995fed468c42f64d18d
+// They state:
+// Rename libcurl3 to libcurl4, because libcurl exposes an SSL_CTX via
+// CURLOPT_SSL_CTX_FUNCTION, and this object changes incompatibly between
+// openssl 1.0 and openssl 1.1.
+// Since we are not accessing the underlying OpenSSL context, we don't care whether we're linking
+// against libcurl3 or libcurl4; both use the ABI version 4 which hasn't changed since 2006
+// (see https://curl.haxx.se/libcurl/abi.html). In fact, cURL's ABI compatibility is very good as
+// shown on https://abi-laboratory.pro/tracker/timeline/curl/
+// Therefore, we're dynamically loading the cURL symbols we need to avoid linking against versioned
+// symbols.
+#include <dlfcn.h>
+
+namespace curl {
+
+#define CURL_FUNCTIONS \
+ X(global_init) \
+ X(getdate) \
+ X(easy_strerror) \
+ X(easy_init) \
+ X(easy_setopt) \
+ X(easy_cleanup) \
+ X(easy_getinfo) \
+ X(easy_reset) \
+ X(multi_init) \
+ X(multi_add_handle) \
+ X(multi_remove_handle) \
+ X(multi_cleanup) \
+ X(multi_info_read) \
+ X(multi_strerror) \
+ X(multi_socket_action) \
+ X(multi_setopt) \
+ X(share_init) \
+ X(share_cleanup) \
+ X(slist_append) \
+ X(slist_free_all)
+
+#define X(name) static decltype(&curl_ ## name) name = nullptr;
+CURL_FUNCTIONS
+#undef X
+
+static void* handle = nullptr;
+
+static void* load(const char* name) {
+ void* symbol = dlsym(handle, name);
+ if (const char* error = dlerror()) {
+ fprintf(stderr, "Cannot load symbol '%s': %s\n", name, error);
+ dlclose(handle);
+ handle = nullptr;
+ abort();
+ }
+ return symbol;
+}
+
+__attribute__((constructor))
+static void load() {
+ assert(!handle);
+ handle = dlopen("libcurl.so.4", RTLD_LAZY | RTLD_LOCAL);
+ if (!handle) {
+ fprintf(stderr, "Could not open shared library '%s'\n", "libcurl.so.4");
+ abort();
+ }
+
+ #define X(name) name = (decltype(&curl_ ## name))load("curl_" #name);
+ CURL_FUNCTIONS
+ #undef X
+}
+
+__attribute__((constructor))
+static void unload() {
+ if (handle) {
+ dlclose(handle);
+ }
+}
+
+} // namespace curl
+
+
#include <queue>
#include <map>
#include <cassert>
@@ -21,13 +100,13 @@
static void handleError(CURLMcode code) {
if (code != CURLM_OK) {
- throw std::runtime_error(std::string("CURL multi error: ") + curl_multi_strerror(code));
+ throw std::runtime_error(std::string("CURL multi error: ") + curl::multi_strerror(code));
}
}
static void handleError(CURLcode code) {
if (code != CURLE_OK) {
- throw std::runtime_error(std::string("CURL easy error: ") + curl_easy_strerror(code));
+ throw std::runtime_error(std::string("CURL easy error: ") + curl::easy_strerror(code));
}
}
@@ -91,29 +170,29 @@ private:
};
HTTPFileSource::Impl::Impl() {
- if (curl_global_init(CURL_GLOBAL_ALL)) {
+ if (curl::global_init(CURL_GLOBAL_ALL)) {
throw std::runtime_error("Could not init cURL");
}
- share = curl_share_init();
+ share = curl::share_init();
- multi = curl_multi_init();
- handleError(curl_multi_setopt(multi, CURLMOPT_SOCKETFUNCTION, handleSocket));
- handleError(curl_multi_setopt(multi, CURLMOPT_SOCKETDATA, this));
- handleError(curl_multi_setopt(multi, CURLMOPT_TIMERFUNCTION, startTimeout));
- handleError(curl_multi_setopt(multi, CURLMOPT_TIMERDATA, this));
+ multi = curl::multi_init();
+ handleError(curl::multi_setopt(multi, CURLMOPT_SOCKETFUNCTION, handleSocket));
+ handleError(curl::multi_setopt(multi, CURLMOPT_SOCKETDATA, this));
+ handleError(curl::multi_setopt(multi, CURLMOPT_TIMERFUNCTION, startTimeout));
+ handleError(curl::multi_setopt(multi, CURLMOPT_TIMERDATA, this));
}
HTTPFileSource::Impl::~Impl() {
while (!handles.empty()) {
- curl_easy_cleanup(handles.front());
+ curl::easy_cleanup(handles.front());
handles.pop();
}
- curl_multi_cleanup(multi);
+ curl::multi_cleanup(multi);
multi = nullptr;
- curl_share_cleanup(share);
+ curl::share_cleanup(share);
share = nullptr;
timeout.stop();
@@ -125,12 +204,12 @@ CURL *HTTPFileSource::Impl::getHandle() {
handles.pop();
return handle;
} else {
- return curl_easy_init();
+ return curl::easy_init();
}
}
void HTTPFileSource::Impl::returnHandle(CURL *handle) {
- curl_easy_reset(handle);
+ curl::easy_reset(handle);
handles.push(handle);
}
@@ -138,11 +217,11 @@ void HTTPFileSource::Impl::checkMultiInfo() {
CURLMsg *message = nullptr;
int pending = 0;
- while ((message = curl_multi_info_read(multi, &pending))) {
+ while ((message = curl::multi_info_read(multi, &pending))) {
switch (message->msg) {
case CURLMSG_DONE: {
HTTPRequest *baton = nullptr;
- curl_easy_getinfo(message->easy_handle, CURLINFO_PRIVATE, (char *)&baton);
+ curl::easy_getinfo(message->easy_handle, CURLINFO_PRIVATE, (char *)&baton);
assert(baton);
baton->handleResult(message->data.result);
} break;
@@ -166,7 +245,7 @@ void HTTPFileSource::Impl::perform(curl_socket_t s, util::RunLoop::Event events)
int running_handles = 0;
- curl_multi_socket_action(multi, s, flags, &running_handles);
+ curl::multi_socket_action(multi, s, flags, &running_handles);
checkMultiInfo();
}
@@ -200,9 +279,9 @@ int HTTPFileSource::Impl::handleSocket(CURL * /* handle */, curl_socket_t s, int
void HTTPFileSource::Impl::onTimeout(Impl *context) {
int running_handles;
- CURLMcode error = curl_multi_socket_action(context->multi, CURL_SOCKET_TIMEOUT, 0, &running_handles);
+ CURLMcode error = curl::multi_socket_action(context->multi, CURL_SOCKET_TIMEOUT, 0, &running_handles);
if (error != CURLM_OK) {
- throw std::runtime_error(std::string("CURL multi error: ") + curl_multi_strerror(error));
+ throw std::runtime_error(std::string("CURL multi error: ") + curl::multi_strerror(error));
}
context->checkMultiInfo();
}
@@ -233,45 +312,45 @@ HTTPRequest::HTTPRequest(HTTPFileSource::Impl* context_, Resource resource_, Fil
// getting a 304 response if possible. This avoids redownloading unchanged data.
if (resource.priorEtag) {
const std::string header = std::string("If-None-Match: ") + *resource.priorEtag;
- headers = curl_slist_append(headers, header.c_str());
+ headers = curl::slist_append(headers, header.c_str());
} else if (resource.priorModified) {
const std::string time =
std::string("If-Modified-Since: ") + util::rfc1123(*resource.priorModified);
- headers = curl_slist_append(headers, time.c_str());
+ headers = curl::slist_append(headers, time.c_str());
}
if (headers) {
- curl_easy_setopt(handle, CURLOPT_HTTPHEADER, headers);
+ curl::easy_setopt(handle, CURLOPT_HTTPHEADER, headers);
}
- handleError(curl_easy_setopt(handle, CURLOPT_PRIVATE, this));
- handleError(curl_easy_setopt(handle, CURLOPT_ERRORBUFFER, error));
- handleError(curl_easy_setopt(handle, CURLOPT_CAINFO, "ca-bundle.crt"));
- handleError(curl_easy_setopt(handle, CURLOPT_FOLLOWLOCATION, 1));
- handleError(curl_easy_setopt(handle, CURLOPT_URL, resource.url.c_str()));
- handleError(curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, writeCallback));
- handleError(curl_easy_setopt(handle, CURLOPT_WRITEDATA, this));
- handleError(curl_easy_setopt(handle, CURLOPT_HEADERFUNCTION, headerCallback));
- handleError(curl_easy_setopt(handle, CURLOPT_HEADERDATA, this));
+ handleError(curl::easy_setopt(handle, CURLOPT_PRIVATE, this));
+ handleError(curl::easy_setopt(handle, CURLOPT_ERRORBUFFER, error));
+ handleError(curl::easy_setopt(handle, CURLOPT_CAINFO, "ca-bundle.crt"));
+ handleError(curl::easy_setopt(handle, CURLOPT_FOLLOWLOCATION, 1));
+ handleError(curl::easy_setopt(handle, CURLOPT_URL, resource.url.c_str()));
+ handleError(curl::easy_setopt(handle, CURLOPT_WRITEFUNCTION, writeCallback));
+ handleError(curl::easy_setopt(handle, CURLOPT_WRITEDATA, this));
+ handleError(curl::easy_setopt(handle, CURLOPT_HEADERFUNCTION, headerCallback));
+ handleError(curl::easy_setopt(handle, CURLOPT_HEADERDATA, this));
#if LIBCURL_VERSION_NUM >= ((7) << 16 | (21) << 8 | 6) // Renamed in 7.21.6
- handleError(curl_easy_setopt(handle, CURLOPT_ACCEPT_ENCODING, "gzip, deflate"));
+ handleError(curl::easy_setopt(handle, CURLOPT_ACCEPT_ENCODING, "gzip, deflate"));
#else
- handleError(curl_easy_setopt(handle, CURLOPT_ENCODING, "gzip, deflate"));
+ handleError(curl::easy_setopt(handle, CURLOPT_ENCODING, "gzip, deflate"));
#endif
- handleError(curl_easy_setopt(handle, CURLOPT_USERAGENT, "MapboxGL/1.0"));
- handleError(curl_easy_setopt(handle, CURLOPT_SHARE, context->share));
+ handleError(curl::easy_setopt(handle, CURLOPT_USERAGENT, "MapboxGL/1.0"));
+ handleError(curl::easy_setopt(handle, CURLOPT_SHARE, context->share));
// Start requesting the information.
- handleError(curl_multi_add_handle(context->multi, handle));
+ handleError(curl::multi_add_handle(context->multi, handle));
}
HTTPRequest::~HTTPRequest() {
- handleError(curl_multi_remove_handle(context->multi, handle));
+ handleError(curl::multi_remove_handle(context->multi, handle));
context->returnHandle(handle);
handle = nullptr;
if (headers) {
- curl_slist_free_all(headers);
+ curl::slist_free_all(headers);
headers = nullptr;
}
}
@@ -320,7 +399,7 @@ size_t HTTPRequest::headerCallback(char *const buffer, const size_t size, const
// Always overwrite the modification date; We might already have a value here from the
// Date header, but this one is more accurate.
const std::string value { buffer + begin, length - begin - 2 }; // remove \r\n
- baton->response->modified = Timestamp{ Seconds(curl_getdate(value.c_str(), nullptr)) };
+ baton->response->modified = Timestamp{ Seconds(curl::getdate(value.c_str(), nullptr)) };
} else if ((begin = headerMatches("etag: ", buffer, length)) != std::string::npos) {
baton->response->etag = std::string(buffer + begin, length - begin - 2); // remove \r\n
} else if ((begin = headerMatches("cache-control: ", buffer, length)) != std::string::npos) {
@@ -330,7 +409,7 @@ size_t HTTPRequest::headerCallback(char *const buffer, const size_t size, const
baton->response->mustRevalidate = cc.mustRevalidate;
} else if ((begin = headerMatches("expires: ", buffer, length)) != std::string::npos) {
const std::string value { buffer + begin, length - begin - 2 }; // remove \r\n
- baton->response->expires = Timestamp{ Seconds(curl_getdate(value.c_str(), nullptr)) };
+ baton->response->expires = Timestamp{ Seconds(curl::getdate(value.c_str(), nullptr)) };
} else if ((begin = headerMatches("retry-after: ", buffer, length)) != std::string::npos) {
baton->retryAfter = std::string(buffer + begin, length - begin - 2); // remove \r\n
} else if ((begin = headerMatches("x-rate-limit-reset: ", buffer, length)) != std::string::npos) {
@@ -357,17 +436,17 @@ void HTTPRequest::handleResult(CURLcode code) {
case CURLE_OPERATION_TIMEDOUT:
response->error = std::make_unique<Error>(
- Error::Reason::Connection, std::string{ curl_easy_strerror(code) } + ": " + error);
+ Error::Reason::Connection, std::string{ curl::easy_strerror(code) } + ": " + error);
break;
default:
response->error = std::make_unique<Error>(
- Error::Reason::Other, std::string{ curl_easy_strerror(code) } + ": " + error);
+ Error::Reason::Other, std::string{ curl::easy_strerror(code) } + ": " + error);
break;
}
} else {
long responseCode = 0;
- curl_easy_getinfo(handle, CURLINFO_RESPONSE_CODE, &responseCode);
+ curl::easy_getinfo(handle, CURLINFO_RESPONSE_CODE, &responseCode);
if (responseCode == 200) {
if (data) {
diff --git a/platform/default/mbgl/map/map_snapshotter.cpp b/platform/default/mbgl/map/map_snapshotter.cpp
index 9341c23cfd..565f72930d 100644
--- a/platform/default/mbgl/map/map_snapshotter.cpp
+++ b/platform/default/mbgl/map/map_snapshotter.cpp
@@ -13,18 +13,21 @@ namespace mbgl {
class MapSnapshotter::Impl {
public:
- Impl(FileSource&,
- Scheduler&,
- const std::string& styleURL,
+ Impl(FileSource*,
+ std::shared_ptr<Scheduler>,
+ const std::pair<bool, std::string> style,
const Size&,
const float pixelRatio,
- const CameraOptions&,
+ const optional<CameraOptions> cameraOptions,
const optional<LatLngBounds> region,
const optional<std::string> programCacheDir);
void setStyleURL(std::string styleURL);
std::string getStyleURL() const;
+ void setStyleJSON(std::string styleJSON);
+ std::string getStyleJSON() const;
+
void setSize(Size);
Size getSize() const;
@@ -37,24 +40,32 @@ public:
void snapshot(ActorRef<MapSnapshotter::Callback>);
private:
+ std::shared_ptr<Scheduler> scheduler;
HeadlessFrontend frontend;
Map map;
};
-MapSnapshotter::Impl::Impl(FileSource& fileSource,
- Scheduler& scheduler,
- const std::string& styleURL,
+MapSnapshotter::Impl::Impl(FileSource* fileSource,
+ std::shared_ptr<Scheduler> scheduler_,
+ const std::pair<bool, std::string> style,
const Size& size,
const float pixelRatio,
- const CameraOptions& cameraOptions,
+ const optional<CameraOptions> cameraOptions,
const optional<LatLngBounds> region,
const optional<std::string> programCacheDir)
- : frontend(size, pixelRatio, fileSource, scheduler, programCacheDir)
- , map(frontend, MapObserver::nullObserver(), size, pixelRatio, fileSource, scheduler, MapMode::Static) {
-
- map.getStyle().loadURL(styleURL);
+ : scheduler(std::move(scheduler_))
+ , frontend(size, pixelRatio, *fileSource, *scheduler, programCacheDir)
+ , map(frontend, MapObserver::nullObserver(), size, pixelRatio, *fileSource, *scheduler, MapMode::Static) {
+
+ if (style.first) {
+ map.getStyle().loadJSON(style.second);
+ } else{
+ map.getStyle().loadURL(style.second);
+ }
- map.jumpTo(cameraOptions);
+ if (cameraOptions) {
+ map.jumpTo(*cameraOptions);
+ }
// Set region, if specified
if (region) {
@@ -76,6 +87,15 @@ void MapSnapshotter::Impl::snapshot(ActorRef<MapSnapshotter::Callback> callback)
return transform.latLngToScreenCoordinate(unwrappedLatLng);
}};
+ // Create lambda that captures the current transform state
+ // and can be used to translate for geographic to screen
+ // coordinates
+ assert (frontend.getTransformState());
+ LatLngForFn latLngForFn { [=, transformState = *frontend.getTransformState()] (const ScreenCoordinate& screenCoordinate) {
+ Transform transform { transformState };
+ return transform.screenCoordinateToLatLng(screenCoordinate);
+ }};
+
// Collect all source attributions
std::vector<std::string> attributions;
for (auto source : map.getStyle().getSources()) {
@@ -91,7 +111,8 @@ void MapSnapshotter::Impl::snapshot(ActorRef<MapSnapshotter::Callback> callback)
error,
error ? PremultipliedImage() : frontend.readStillImage(),
std::move(attributions),
- std::move(pointForFn)
+ std::move(pointForFn),
+ std::move(latLngForFn)
);
});
}
@@ -104,6 +125,14 @@ std::string MapSnapshotter::Impl::getStyleURL() const {
return map.getStyle().getURL();
}
+void MapSnapshotter::Impl::setStyleJSON(std::string styleJSON) {
+ map.getStyle().loadJSON(styleJSON);
+}
+
+std::string MapSnapshotter::Impl::getStyleJSON() const {
+ return map.getStyle().getJSON();
+}
+
void MapSnapshotter::Impl::setSize(Size size) {
map.setSize(size);
frontend.setSize(size);
@@ -132,15 +161,15 @@ LatLngBounds MapSnapshotter::Impl::getRegion() const {
return map.latLngBoundsForCamera(getCameraOptions());
}
-MapSnapshotter::MapSnapshotter(FileSource& fileSource,
- Scheduler& scheduler,
- const std::string& styleURL,
+MapSnapshotter::MapSnapshotter(FileSource* fileSource,
+ std::shared_ptr<Scheduler> scheduler,
+ const std::pair<bool, std::string> style,
const Size& size,
const float pixelRatio,
- const CameraOptions& cameraOptions,
+ const optional<CameraOptions> cameraOptions,
const optional<LatLngBounds> region,
const optional<std::string> programCacheDir)
- : impl(std::make_unique<util::Thread<MapSnapshotter::Impl>>("Map Snapshotter", fileSource, scheduler, styleURL, size, pixelRatio, cameraOptions, region, programCacheDir)) {
+ : impl(std::make_unique<util::Thread<MapSnapshotter::Impl>>("Map Snapshotter", fileSource, std::move(scheduler), style, size, pixelRatio, cameraOptions, region, programCacheDir)) {
}
MapSnapshotter::~MapSnapshotter() = default;
@@ -157,6 +186,14 @@ std::string MapSnapshotter::getStyleURL() const {
return impl->actor().ask(&Impl::getStyleURL).get();
}
+void MapSnapshotter::setStyleJSON(const std::string& styleJSON) {
+ impl->actor().invoke(&Impl::setStyleJSON, styleJSON);
+}
+
+std::string MapSnapshotter::getStyleJSON() const {
+ return impl->actor().ask(&Impl::getStyleJSON).get();
+}
+
void MapSnapshotter::setSize(const Size& size) {
impl->actor().invoke(&Impl::setSize, size);
}
diff --git a/platform/default/mbgl/map/map_snapshotter.hpp b/platform/default/mbgl/map/map_snapshotter.hpp
index 985396e5a3..264f74541d 100644
--- a/platform/default/mbgl/map/map_snapshotter.hpp
+++ b/platform/default/mbgl/map/map_snapshotter.hpp
@@ -25,12 +25,12 @@ class Style;
class MapSnapshotter {
public:
- MapSnapshotter(FileSource& fileSource,
- Scheduler& scheduler,
- const std::string& styleURL,
+ MapSnapshotter(FileSource* fileSource,
+ std::shared_ptr<Scheduler> scheduler,
+ const std::pair<bool, std::string> style,
const Size&,
const float pixelRatio,
- const CameraOptions&,
+ const optional<CameraOptions> cameraOptions,
const optional<LatLngBounds> region,
const optional<std::string> cacheDir = {});
@@ -39,6 +39,9 @@ public:
void setStyleURL(const std::string& styleURL);
std::string getStyleURL() const;
+ void setStyleJSON(const std::string& styleJSON);
+ std::string getStyleJSON() const;
+
void setSize(const Size&);
Size getSize() const;
@@ -49,8 +52,9 @@ public:
LatLngBounds getRegion() const;
using PointForFn = std::function<ScreenCoordinate (const LatLng&)>;
+ using LatLngForFn = std::function<LatLng (const ScreenCoordinate&)>;
using Attributions = std::vector<std::string>;
- using Callback = std::function<void (std::exception_ptr, PremultipliedImage, Attributions, PointForFn)>;
+ using Callback = std::function<void (std::exception_ptr, PremultipliedImage, Attributions, PointForFn, LatLngForFn)>;
void snapshot(ActorRef<Callback>);
private:
diff --git a/platform/default/mbgl/storage/.clang-tidy b/platform/default/mbgl/storage/.clang-tidy
new file mode 100644
index 0000000000..b4ecd26b95
--- /dev/null
+++ b/platform/default/mbgl/storage/.clang-tidy
@@ -0,0 +1,2 @@
+Checks: 'modernize-*,misc-static-assert,llvm-namespace-comment,-clang-analyzer-security.insecureAPI.rand,-clang-analyzer-core.uninitialized.UndefReturn,-clang-analyzer-core.StackAddressEscape,-clang-analyzer-core.CallAndMessage,-clang-diagnostic-unused-command-line-argument,-clang-analyzer-core.uninitialized.*,-clang-analyzer-core.NullDereference,-clang-analyzer-cplusplus.NewDelete,-clang-analyzer-unix.MismatchedDeallocator,-clang-analyzer-unix.cstring.NullArg,-clang-analyzer-cplusplus.NewDeleteLeaks,-clang-analyzer-unix.Malloc,-clang-analyzer-core.NonNullParamChecker'
+HeaderFilterRegex: '\/mbgl\/'
diff --git a/platform/default/mbgl/storage/offline_database.cpp b/platform/default/mbgl/storage/offline_database.cpp
index 4611e69f43..8f7f0965f4 100644
--- a/platform/default/mbgl/storage/offline_database.cpp
+++ b/platform/default/mbgl/storage/offline_database.cpp
@@ -6,6 +6,8 @@
#include <mbgl/util/chrono.hpp>
#include <mbgl/util/logging.hpp>
+#include "offline_schema.hpp"
+
#include "sqlite3.hpp"
namespace mbgl {
@@ -27,58 +29,73 @@ OfflineDatabase::~OfflineDatabase() {
}
}
-void OfflineDatabase::connect(int flags) {
- db = std::make_unique<mapbox::sqlite::Database>(path.c_str(), flags);
- db->setBusyTimeout(Milliseconds::max());
- db->exec("PRAGMA foreign_keys = ON");
-}
-
void OfflineDatabase::ensureSchema() {
- if (path != ":memory:") {
- try {
- connect(mapbox::sqlite::ReadWrite);
-
- switch (userVersion()) {
- case 0: break; // cache-only database; ok to delete
- case 1: break; // cache-only database; ok to delete
- case 2: migrateToVersion3(); // fall through
- case 3: // no-op and fall through
- case 4: migrateToVersion5(); // fall through
- case 5: migrateToVersion6(); // fall through
- case 6: return;
- default: break; // downgrade, delete the database
- }
-
+ auto result = mapbox::sqlite::Database::tryOpen(path, mapbox::sqlite::ReadWriteCreate);
+ if (result.is<mapbox::sqlite::Exception>()) {
+ const auto& ex = result.get<mapbox::sqlite::Exception>();
+ if (ex.code == mapbox::sqlite::ResultCode::NotADB) {
+ // Corrupted; blow it away.
removeExisting();
- connect(mapbox::sqlite::ReadWrite | mapbox::sqlite::Create);
- } catch (mapbox::sqlite::Exception& ex) {
- if (ex.code != mapbox::sqlite::ResultCode::CantOpen && ex.code != mapbox::sqlite::ResultCode::NotADB) {
- Log::Error(Event::Database, "Unexpected error connecting to database: %s", ex.what());
- throw;
- }
-
- try {
- if (ex.code == mapbox::sqlite::ResultCode::NotADB) {
- removeExisting();
- }
- connect(mapbox::sqlite::ReadWrite | mapbox::sqlite::Create);
- } catch (...) {
- Log::Error(Event::Database, "Unexpected error creating database: %s", util::toString(std::current_exception()).c_str());
- throw;
- }
+ result = mapbox::sqlite::Database::open(path, mapbox::sqlite::ReadWriteCreate);
+ } else {
+ Log::Error(Event::Database, "Unexpected error connecting to database: %s", ex.what());
+ throw ex;
}
}
try {
- #include "offline_schema.cpp.include"
+ assert(result.is<mapbox::sqlite::Database>());
+ db = std::make_unique<mapbox::sqlite::Database>(std::move(result.get<mapbox::sqlite::Database>()));
+ db->setBusyTimeout(Milliseconds::max());
+ db->exec("PRAGMA foreign_keys = ON");
+
+ switch (userVersion()) {
+ case 0:
+ case 1:
+ // Newly created database, or old cache-only database; remove old table if it exists.
+ removeOldCacheTable();
+ break;
+ case 2:
+ migrateToVersion3();
+ // fall through
+ case 3:
+ case 4:
+ migrateToVersion5();
+ // fall through
+ case 5:
+ migrateToVersion6();
+ // fall through
+ case 6:
+ // happy path; we're done
+ return;
+ default:
+ // downgrade, delete the database
+ removeExisting();
+ break;
+ }
+ } catch (const mapbox::sqlite::Exception& ex) {
+ // Unfortunately, SQLITE_NOTADB is not always reported upon opening the database.
+ // Apparently sometimes it is delayed until the first read operation.
+ if (ex.code == mapbox::sqlite::ResultCode::NotADB) {
+ removeExisting();
+ } else {
+ throw;
+ }
+ }
- connect(mapbox::sqlite::ReadWrite | mapbox::sqlite::Create);
+ try {
+ // When downgrading the database, or when the database is corrupt, we've deleted the old database handle,
+ // so we need to reopen it.
+ if (!db) {
+ db = std::make_unique<mapbox::sqlite::Database>(mapbox::sqlite::Database::open(path, mapbox::sqlite::ReadWriteCreate));
+ db->setBusyTimeout(Milliseconds::max());
+ db->exec("PRAGMA foreign_keys = ON");
+ }
- // If you change the schema you must write a migration from the previous version.
db->exec("PRAGMA auto_vacuum = INCREMENTAL");
db->exec("PRAGMA journal_mode = DELETE");
db->exec("PRAGMA synchronous = FULL");
- db->exec(schema);
+ db->exec(offlineDatabaseSchema);
db->exec("PRAGMA user_version = 6");
} catch (...) {
Log::Error(Event::Database, "Unexpected error creating database schema: %s", util::toString(std::current_exception()).c_str());
@@ -93,6 +110,7 @@ int OfflineDatabase::userVersion() {
void OfflineDatabase::removeExisting() {
Log::Warning(Event::Database, "Removing existing incompatible offline database");
+ statements.clear();
db.reset();
try {
@@ -102,6 +120,11 @@ void OfflineDatabase::removeExisting() {
}
}
+void OfflineDatabase::removeOldCacheTable() {
+ db->exec("DROP TABLE IF EXISTS http_cache");
+ db->exec("VACUUM");
+}
+
void OfflineDatabase::migrateToVersion3() {
db->exec("PRAGMA auto_vacuum = INCREMENTAL");
db->exec("VACUUM");
@@ -160,7 +183,10 @@ optional<int64_t> OfflineDatabase::hasInternal(const Resource& resource) {
}
std::pair<bool, uint64_t> OfflineDatabase::put(const Resource& resource, const Response& response) {
- return putInternal(resource, response, true);
+ mapbox::sqlite::Transaction transaction(*db, mapbox::sqlite::Transaction::Immediate);
+ auto result = putInternal(resource, response, true);
+ transaction.commit();
+ return result;
}
std::pair<bool, uint64_t> OfflineDatabase::putInternal(const Resource& resource, const Response& response, bool evict_) {
@@ -179,7 +205,7 @@ std::pair<bool, uint64_t> OfflineDatabase::putInternal(const Resource& resource,
}
if (evict_ && !evict(size)) {
- Log::Debug(Event::Database, "Unable to make space for entry");
+ Log::Info(Event::Database, "Unable to make space for entry");
return { false, 0 };
}
@@ -277,11 +303,6 @@ bool OfflineDatabase::putResource(const Resource& resource,
}
// We can't use REPLACE because it would change the id value.
-
- // Begin an immediate-mode transaction to ensure that two writers do not attempt
- // to INSERT a resource at the same moment.
- mapbox::sqlite::Transaction transaction(*db, mapbox::sqlite::Transaction::Immediate);
-
// clang-format off
mapbox::sqlite::Query updateQuery{ getStatement(
"UPDATE resources "
@@ -314,7 +335,6 @@ bool OfflineDatabase::putResource(const Resource& resource,
updateQuery.run();
if (updateQuery.changes() != 0) {
- transaction.commit();
return false;
}
@@ -341,7 +361,6 @@ bool OfflineDatabase::putResource(const Resource& resource,
}
insertQuery.run();
- transaction.commit();
return true;
}
@@ -469,10 +488,6 @@ bool OfflineDatabase::putTile(const Resource::TileData& tile,
// We can't use REPLACE because it would change the id value.
- // Begin an immediate-mode transaction to ensure that two writers do not attempt
- // to INSERT a resource at the same moment.
- mapbox::sqlite::Transaction transaction(*db, mapbox::sqlite::Transaction::Immediate);
-
// clang-format off
mapbox::sqlite::Query updateQuery{ getStatement(
"UPDATE tiles "
@@ -511,7 +526,6 @@ bool OfflineDatabase::putTile(const Resource::TileData& tile,
updateQuery.run();
if (updateQuery.changes() != 0) {
- transaction.commit();
return false;
}
@@ -541,7 +555,6 @@ bool OfflineDatabase::putTile(const Resource::TileData& tile,
}
insertQuery.run();
- transaction.commit();
return true;
}
@@ -624,6 +637,43 @@ optional<int64_t> OfflineDatabase::hasRegionResource(int64_t regionID, const Res
}
uint64_t OfflineDatabase::putRegionResource(int64_t regionID, const Resource& resource, const Response& response) {
+ mapbox::sqlite::Transaction transaction(*db);
+ auto size = putRegionResourceInternal(regionID, resource, response);
+ transaction.commit();
+ return size;
+}
+
+void OfflineDatabase::putRegionResources(int64_t regionID, const std::list<std::tuple<Resource, Response>>& resources, OfflineRegionStatus& status) {
+ mapbox::sqlite::Transaction transaction(*db);
+
+ for (const auto& elem : resources) {
+ const auto& resource = std::get<0>(elem);
+ const auto& response = std::get<1>(elem);
+
+ try {
+ uint64_t resourceSize = putRegionResourceInternal(regionID, resource, response);
+ status.completedResourceCount++;
+ status.completedResourceSize += resourceSize;
+ if (resource.kind == Resource::Kind::Tile) {
+ status.completedTileCount += 1;
+ status.completedTileSize += resourceSize;
+ }
+ } catch (const MapboxTileLimitExceededException&) {
+ // Commit the rest of the batch and retrow
+ transaction.commit();
+ throw;
+ }
+ }
+
+ // Commit the completed batch
+ transaction.commit();
+}
+
+uint64_t OfflineDatabase::putRegionResourceInternal(int64_t regionID, const Resource& resource, const Response& response) {
+ if (exceedsOfflineMapboxTileCountLimit(resource)) {
+ throw MapboxTileLimitExceededException();
+ }
+
uint64_t size = putInternal(resource, response, false).second;
bool previouslyUnused = markUsed(regionID, resource);
@@ -894,4 +944,10 @@ uint64_t OfflineDatabase::getOfflineMapboxTileCount() {
return *offlineMapboxTileCount;
}
+bool OfflineDatabase::exceedsOfflineMapboxTileCountLimit(const Resource& resource) {
+ return resource.kind == Resource::Kind::Tile
+ && util::mapbox::isMapboxURL(resource.url)
+ && offlineMapboxTileCountLimitExceeded();
+}
+
} // namespace mbgl
diff --git a/platform/default/mbgl/storage/offline_database.hpp b/platform/default/mbgl/storage/offline_database.hpp
index 9673ad8212..38eb3783ba 100644
--- a/platform/default/mbgl/storage/offline_database.hpp
+++ b/platform/default/mbgl/storage/offline_database.hpp
@@ -2,6 +2,7 @@
#include <mbgl/storage/resource.hpp>
#include <mbgl/storage/offline.hpp>
+#include <mbgl/util/exception.hpp>
#include <mbgl/util/noncopyable.hpp>
#include <mbgl/util/optional.hpp>
#include <mbgl/util/constants.hpp>
@@ -10,6 +11,7 @@
#include <unordered_map>
#include <memory>
#include <string>
+#include <list>
namespace mapbox {
namespace sqlite {
@@ -24,6 +26,10 @@ namespace mbgl {
class Response;
class TileID;
+struct MapboxTileLimitExceededException : util::Exception {
+ MapboxTileLimitExceededException() : util::Exception("Mapbox tile limit exceeded") {}
+};
+
class OfflineDatabase : private util::noncopyable {
public:
// Limits affect ambient caching (put) only; resources required by offline
@@ -49,6 +55,7 @@ public:
optional<std::pair<Response, uint64_t>> getRegionResource(int64_t regionID, const Resource&);
optional<int64_t> hasRegionResource(int64_t regionID, const Resource&);
uint64_t putRegionResource(int64_t regionID, const Resource&, const Response&);
+ void putRegionResources(int64_t regionID, const std::list<std::tuple<Resource, Response>>&, OfflineRegionStatus&);
OfflineRegionDefinition getRegionDefinition(int64_t regionID);
OfflineRegionStatus getRegionCompletedStatus(int64_t regionID);
@@ -57,12 +64,13 @@ public:
uint64_t getOfflineMapboxTileCountLimit();
bool offlineMapboxTileCountLimitExceeded();
uint64_t getOfflineMapboxTileCount();
+ bool exceedsOfflineMapboxTileCountLimit(const Resource&);
private:
- void connect(int flags);
int userVersion();
void ensureSchema();
void removeExisting();
+ void removeOldCacheTable();
void migrateToVersion3();
void migrateToVersion5();
void migrateToVersion6();
@@ -79,6 +87,8 @@ private:
bool putResource(const Resource&, const Response&,
const std::string&, bool compressed);
+ uint64_t putRegionResourceInternal(int64_t regionID, const Resource&, const Response&);
+
optional<std::pair<Response, uint64_t>> getInternal(const Resource&);
optional<int64_t> hasInternal(const Resource&);
std::pair<bool, uint64_t> putInternal(const Resource&, const Response&, bool evict);
diff --git a/platform/default/mbgl/storage/offline_download.cpp b/platform/default/mbgl/storage/offline_download.cpp
index ba504c1f9b..4da51db655 100644
--- a/platform/default/mbgl/storage/offline_download.cpp
+++ b/platform/default/mbgl/storage/offline_download.cpp
@@ -330,7 +330,8 @@ void OfflineDownload::ensureResource(const Resource& resource,
return;
}
- if (checkTileCountLimit(resource)) {
+ if (offlineDatabase.exceedsOfflineMapboxTileCountLimit(resource)) {
+ onMapboxTileCountLimitExceeded();
return;
}
@@ -347,17 +348,24 @@ void OfflineDownload::ensureResource(const Resource& resource,
callback(onlineResponse);
}
- status.completedResourceCount++;
- uint64_t resourceSize = offlineDatabase.putRegionResource(id, resource, onlineResponse);
- status.completedResourceSize += resourceSize;
- if (resource.kind == Resource::Kind::Tile) {
- status.completedTileCount += 1;
- status.completedTileSize += resourceSize;
- }
+ // Queue up for batched insertion
+ buffer.emplace_back(resource, onlineResponse);
- observer->statusChanged(status);
+ // Flush buffer periodically
+ if (buffer.size() == 64 || resourcesRemaining.size() == 0) {
+ try {
+ offlineDatabase.putRegionResources(id, buffer, status);
+ } catch (const MapboxTileLimitExceededException&) {
+ onMapboxTileCountLimitExceeded();
+ return;
+ }
- if (checkTileCountLimit(resource)) {
+ buffer.clear();
+ observer->statusChanged(status);
+ }
+
+ if (offlineDatabase.exceedsOfflineMapboxTileCountLimit(resource)) {
+ onMapboxTileCountLimitExceeded();
return;
}
@@ -366,15 +374,9 @@ void OfflineDownload::ensureResource(const Resource& resource,
});
}
-bool OfflineDownload::checkTileCountLimit(const Resource& resource) {
- if (resource.kind == Resource::Kind::Tile && util::mapbox::isMapboxURL(resource.url) &&
- offlineDatabase.offlineMapboxTileCountLimitExceeded()) {
- observer->mapboxTileCountLimitExceeded(offlineDatabase.getOfflineMapboxTileCountLimit());
- setState(OfflineRegionDownloadState::Inactive);
- return true;
- }
-
- return false;
+void OfflineDownload::onMapboxTileCountLimitExceeded() {
+ observer->mapboxTileCountLimitExceeded(offlineDatabase.getOfflineMapboxTileCountLimit());
+ setState(OfflineRegionDownloadState::Inactive);
}
} // namespace mbgl
diff --git a/platform/default/mbgl/storage/offline_download.hpp b/platform/default/mbgl/storage/offline_download.hpp
index 437f221c11..cffac1665b 100644
--- a/platform/default/mbgl/storage/offline_download.hpp
+++ b/platform/default/mbgl/storage/offline_download.hpp
@@ -46,7 +46,8 @@ private:
* is deactivated, all in progress requests are cancelled.
*/
void ensureResource(const Resource&, std::function<void (Response)> = {});
- bool checkTileCountLimit(const Resource& resource);
+
+ void onMapboxTileCountLimitExceeded();
int64_t id;
OfflineRegionDefinition definition;
@@ -58,6 +59,7 @@ private:
std::list<std::unique_ptr<AsyncRequest>> requests;
std::unordered_set<std::string> requiredSourceURLs;
std::deque<Resource> resourcesRemaining;
+ std::list<std::tuple<Resource, Response>> buffer;
void queueResource(Resource);
void queueTiles(style::SourceType, uint16_t tileSize, const Tileset&);
diff --git a/platform/default/mbgl/storage/offline_schema.cpp.include b/platform/default/mbgl/storage/offline_schema.hpp
index 41af81e55b..e177d0dbd3 100644
--- a/platform/default/mbgl/storage/offline_schema.cpp.include
+++ b/platform/default/mbgl/storage/offline_schema.hpp
@@ -1,5 +1,11 @@
-/* THIS IS A GENERATED FILE; EDIT offline_schema.sql INSTEAD */
-static const char * schema =
+#pragma once
+
+// THIS IS A GENERATED FILE; EDIT offline_schema.sql INSTEAD
+// To regenerate, run `node platform/default/mbgl/storage/offline_schema.js`
+
+namespace mbgl {
+
+static constexpr const char* offlineDatabaseSchema =
"CREATE TABLE resources (\n"
" id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,\n"
" url TEXT NOT NULL,\n"
@@ -53,3 +59,5 @@ static const char * schema =
"CREATE INDEX region_tiles_tile_id\n"
"ON region_tiles (tile_id);\n"
;
+
+} // namespace mbgl
diff --git a/platform/default/mbgl/storage/offline_schema.js b/platform/default/mbgl/storage/offline_schema.js
new file mode 100644
index 0000000000..fdb7dc6405
--- /dev/null
+++ b/platform/default/mbgl/storage/offline_schema.js
@@ -0,0 +1,21 @@
+var fs = require('fs');
+fs.writeFileSync('platform/default/mbgl/storage/offline_schema.hpp', `#pragma once
+
+// THIS IS A GENERATED FILE; EDIT offline_schema.sql INSTEAD
+// To regenerate, run \`node platform/default/mbgl/storage/offline_schema.js\`
+
+namespace mbgl {
+
+static constexpr const char* offlineDatabaseSchema =
+${fs.readFileSync('platform/default/mbgl/storage/offline_schema.sql', 'utf8')
+ .replace(/ *--.*/g, '')
+ .split('\n')
+ .filter(a => a)
+ .map(line => '"' + line + '\\n"')
+ .join('\n')
+}
+;
+
+} // namespace mbgl
+`);
+
diff --git a/platform/default/mbgl/storage/offline_schema.sql b/platform/default/mbgl/storage/offline_schema.sql
new file mode 100644
index 0000000000..722b0e0451
--- /dev/null
+++ b/platform/default/mbgl/storage/offline_schema.sql
@@ -0,0 +1,64 @@
+CREATE TABLE resources ( -- Generic table for style, source, sprite, and glyph resources.
+ id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
+ url TEXT NOT NULL,
+ kind INTEGER NOT NULL,
+ expires INTEGER,
+ modified INTEGER,
+ etag TEXT,
+ data BLOB,
+ compressed INTEGER NOT NULL DEFAULT 0,
+ accessed INTEGER NOT NULL,
+ must_revalidate INTEGER NOT NULL DEFAULT 0,
+ UNIQUE (url)
+);
+
+CREATE TABLE tiles (
+ id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
+ url_template TEXT NOT NULL,
+ pixel_ratio INTEGER NOT NULL,
+ z INTEGER NOT NULL,
+ x INTEGER NOT NULL,
+ y INTEGER NOT NULL,
+ expires INTEGER,
+ modified INTEGER,
+ etag TEXT,
+ data BLOB,
+ compressed INTEGER NOT NULL DEFAULT 0,
+ accessed INTEGER NOT NULL,
+ must_revalidate INTEGER NOT NULL DEFAULT 0,
+ UNIQUE (url_template, pixel_ratio, z, x, y)
+);
+
+CREATE TABLE regions (
+ id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
+ definition TEXT NOT NULL, -- JSON formatted definition of region. Regions may be of variant types:
+ -- e.g. bbox and zoom range, route path, flyTo parameters, etc. Note that
+ -- the set of tiles required for a region may span multiple sources.
+ description BLOB -- User provided data in user-defined format
+);
+
+CREATE TABLE region_resources (
+ region_id INTEGER NOT NULL REFERENCES regions(id) ON DELETE CASCADE,
+ resource_id INTEGER NOT NULL REFERENCES resources(id),
+ UNIQUE (region_id, resource_id)
+);
+
+CREATE TABLE region_tiles (
+ region_id INTEGER NOT NULL REFERENCES regions(id) ON DELETE CASCADE,
+ tile_id INTEGER NOT NULL REFERENCES tiles(id),
+ UNIQUE (region_id, tile_id)
+);
+
+-- Indexes for efficient eviction queries
+
+CREATE INDEX resources_accessed
+ON resources (accessed);
+
+CREATE INDEX tiles_accessed
+ON tiles (accessed);
+
+CREATE INDEX region_resources_resource_id
+ON region_resources (resource_id);
+
+CREATE INDEX region_tiles_tile_id
+ON region_tiles (tile_id);
diff --git a/platform/default/sqlite3.cpp b/platform/default/sqlite3.cpp
index 8f9c34191f..2e76f6eec4 100644
--- a/platform/default/sqlite3.cpp
+++ b/platform/default/sqlite3.cpp
@@ -14,27 +14,23 @@ namespace sqlite {
class DatabaseImpl {
public:
- DatabaseImpl(const char* filename, int flags)
+ DatabaseImpl(sqlite3* db_)
+ : db(db_)
{
- const int error = sqlite3_open_v2(filename, &db, flags, nullptr);
- if (error != SQLITE_OK) {
- const auto message = sqlite3_errmsg(db);
- db = nullptr;
- throw Exception { error, message };
- }
}
~DatabaseImpl()
{
- if (!db) return;
-
const int error = sqlite3_close(db);
if (error != SQLITE_OK) {
mbgl::Log::Error(mbgl::Event::Database, "%s (Code %i)", sqlite3_errmsg(db), error);
}
}
- sqlite3* db = nullptr;
+ void setBusyTimeout(std::chrono::milliseconds timeout);
+ void exec(const std::string& sql);
+
+ sqlite3* db;
};
class StatementImpl {
@@ -69,84 +65,8 @@ public:
template <typename T>
using optional = std::experimental::optional<T>;
-static const char* codeToString(const int err) {
- switch (err) {
- case SQLITE_OK: return "SQLITE_OK";
- case SQLITE_ERROR: return "SQLITE_ERROR";
- case SQLITE_INTERNAL: return "SQLITE_INTERNAL";
- case SQLITE_PERM: return "SQLITE_PERM";
- case SQLITE_ABORT: return "SQLITE_ABORT";
- case SQLITE_BUSY: return "SQLITE_BUSY";
- case SQLITE_LOCKED: return "SQLITE_LOCKED";
- case SQLITE_NOMEM: return "SQLITE_NOMEM";
- case SQLITE_READONLY: return "SQLITE_READONLY";
- case SQLITE_INTERRUPT: return "SQLITE_INTERRUPT";
- case SQLITE_IOERR: return "SQLITE_IOERR";
- case SQLITE_CORRUPT: return "SQLITE_CORRUPT";
- case SQLITE_NOTFOUND: return "SQLITE_NOTFOUND";
- case SQLITE_FULL: return "SQLITE_FULL";
- case SQLITE_CANTOPEN: return "SQLITE_CANTOPEN";
- case SQLITE_PROTOCOL: return "SQLITE_PROTOCOL";
- case SQLITE_EMPTY: return "SQLITE_EMPTY";
- case SQLITE_SCHEMA: return "SQLITE_SCHEMA";
- case SQLITE_TOOBIG: return "SQLITE_TOOBIG";
- case SQLITE_CONSTRAINT: return "SQLITE_CONSTRAINT";
- case SQLITE_MISMATCH: return "SQLITE_MISMATCH";
- case SQLITE_MISUSE: return "SQLITE_MISUSE";
- case SQLITE_NOLFS: return "SQLITE_NOLFS";
- case SQLITE_AUTH: return "SQLITE_AUTH";
- case SQLITE_FORMAT: return "SQLITE_FORMAT";
- case SQLITE_RANGE: return "SQLITE_RANGE";
- case SQLITE_NOTADB: return "SQLITE_NOTADB";
- case SQLITE_NOTICE: return "SQLITE_NOTICE";
- case SQLITE_WARNING: return "SQLITE_WARNING";
- case SQLITE_ROW: return "SQLITE_ROW";
- case SQLITE_DONE: return "SQLITE_DONE";
- default: return "<unknown>";
- }
-}
-
static void errorLogCallback(void *, const int err, const char *msg) {
- auto severity = mbgl::EventSeverity::Info;
-
- switch (err) {
- case SQLITE_ERROR: // Generic error
- case SQLITE_INTERNAL: // Internal logic error in SQLite
- case SQLITE_PERM: // Access permission denied
- case SQLITE_ABORT: // Callback routine requested an abort
- case SQLITE_BUSY: // The database file is locked
- case SQLITE_LOCKED: // A table in the database is locked
- case SQLITE_NOMEM: // A malloc() failed
- case SQLITE_READONLY: // Attempt to write a readonly database
- case SQLITE_INTERRUPT: // Operation terminated by sqlite3_interrupt(
- case SQLITE_IOERR: // Some kind of disk I/O error occurred
- case SQLITE_CORRUPT: // The database disk image is malformed
- case SQLITE_NOTFOUND: // Unknown opcode in sqlite3_file_control()
- case SQLITE_FULL: // Insertion failed because database is full
- case SQLITE_CANTOPEN: // Unable to open the database file
- case SQLITE_PROTOCOL: // Database lock protocol error
- case SQLITE_EMPTY: // Internal use only
- case SQLITE_SCHEMA: // The database schema changed
- case SQLITE_TOOBIG: // String or BLOB exceeds size limit
- case SQLITE_CONSTRAINT: // Abort due to constraint violation
- case SQLITE_MISMATCH: // Data type mismatch
- case SQLITE_MISUSE: // Library used incorrectly
- case SQLITE_NOLFS: // Uses OS features not supported on host
- case SQLITE_AUTH: // Authorization denied
- case SQLITE_FORMAT: // Not used
- case SQLITE_RANGE: // 2nd parameter to sqlite3_bind out of range
- case SQLITE_NOTADB: // File opened that is not a database file
- severity = mbgl::EventSeverity::Error;
- break;
- case SQLITE_WARNING: // Warnings from sqlite3_log()
- severity = mbgl::EventSeverity::Warning;
- break;
- case SQLITE_NOTICE: // Notifications from sqlite3_log()
- default:
- break;
- }
-
- mbgl::Log::Record(severity, mbgl::Event::Database, "%s (%s)", msg, codeToString(err));
+ mbgl::Log::Record(mbgl::EventSeverity::Info, mbgl::Event::Database, err, "%s", msg);
}
const static bool sqliteVersionCheck __attribute__((unused)) = []() {
@@ -164,11 +84,29 @@ const static bool sqliteVersionCheck __attribute__((unused)) = []() {
return true;
}();
-Database::Database(const std::string &filename, int flags)
- : impl(std::make_unique<DatabaseImpl>(filename.c_str(), flags))
-{
+mapbox::util::variant<Database, Exception> Database::tryOpen(const std::string &filename, int flags) {
+ sqlite3* db = nullptr;
+ const int error = sqlite3_open_v2(filename.c_str(), &db, flags, nullptr);
+ if (error != SQLITE_OK) {
+ const auto message = sqlite3_errmsg(db);
+ return Exception { error, message };
+ }
+ return Database(std::make_unique<DatabaseImpl>(db));
+}
+
+Database Database::open(const std::string &filename, int flags) {
+ auto result = tryOpen(filename, flags);
+ if (result.is<Exception>()) {
+ throw result.get<Exception>();
+ } else {
+ return std::move(result.get<Database>());
+ }
}
+Database::Database(std::unique_ptr<DatabaseImpl> impl_)
+ : impl(std::move(impl_))
+{}
+
Database::Database(Database &&other)
: impl(std::move(other.impl)) {}
@@ -181,23 +119,31 @@ Database::~Database() = default;
void Database::setBusyTimeout(std::chrono::milliseconds timeout) {
assert(impl);
- const int err = sqlite3_busy_timeout(impl->db,
+ impl->setBusyTimeout(timeout);
+}
+
+void DatabaseImpl::setBusyTimeout(std::chrono::milliseconds timeout) {
+ const int err = sqlite3_busy_timeout(db,
int(std::min<std::chrono::milliseconds::rep>(timeout.count(), std::numeric_limits<int>::max())));
if (err != SQLITE_OK) {
- throw Exception { err, sqlite3_errmsg(impl->db) };
+ throw Exception { err, sqlite3_errmsg(db) };
}
}
void Database::exec(const std::string &sql) {
assert(impl);
+ impl->exec(sql);
+}
+
+void DatabaseImpl::exec(const std::string& sql) {
char *msg = nullptr;
- const int err = sqlite3_exec(impl->db, sql.c_str(), nullptr, nullptr, &msg);
+ const int err = sqlite3_exec(db, sql.c_str(), nullptr, nullptr, &msg);
if (msg) {
const std::string message = msg;
sqlite3_free(msg);
throw Exception { err, message };
} else if (err != SQLITE_OK) {
- throw Exception { err, sqlite3_errmsg(impl->db) };
+ throw Exception { err, sqlite3_errmsg(db) };
}
}
@@ -470,16 +416,16 @@ uint64_t Query::changes() const {
}
Transaction::Transaction(Database& db_, Mode mode)
- : db(db_) {
+ : dbImpl(*db_.impl) {
switch (mode) {
case Deferred:
- db.exec("BEGIN DEFERRED TRANSACTION");
+ dbImpl.exec("BEGIN DEFERRED TRANSACTION");
break;
case Immediate:
- db.exec("BEGIN IMMEDIATE TRANSACTION");
+ dbImpl.exec("BEGIN IMMEDIATE TRANSACTION");
break;
case Exclusive:
- db.exec("BEGIN EXCLUSIVE TRANSACTION");
+ dbImpl.exec("BEGIN EXCLUSIVE TRANSACTION");
break;
}
}
@@ -496,12 +442,12 @@ Transaction::~Transaction() {
void Transaction::commit() {
needRollback = false;
- db.exec("COMMIT TRANSACTION");
+ dbImpl.exec("COMMIT TRANSACTION");
}
void Transaction::rollback() {
needRollback = false;
- db.exec("ROLLBACK TRANSACTION");
+ dbImpl.exec("ROLLBACK TRANSACTION");
}
} // namespace sqlite
diff --git a/platform/default/sqlite3.hpp b/platform/default/sqlite3.hpp
index 20d09b550c..612e92acc6 100644
--- a/platform/default/sqlite3.hpp
+++ b/platform/default/sqlite3.hpp
@@ -5,18 +5,14 @@
#include <stdexcept>
#include <chrono>
#include <memory>
+#include <mapbox/variant.hpp>
namespace mapbox {
namespace sqlite {
enum OpenFlag : int {
- ReadOnly = 0x00000001,
- ReadWrite = 0x00000002,
- Create = 0x00000004,
- NoMutex = 0x00008000,
- FullMutex = 0x00010000,
- SharedCache = 0x00020000,
- PrivateCache = 0x00040000,
+ ReadOnly = 0b001,
+ ReadWriteCreate = 0b110,
};
enum class ResultCode : int {
@@ -68,14 +64,18 @@ class DatabaseImpl;
class Statement;
class StatementImpl;
class Query;
+class Transaction;
class Database {
private:
+ Database(std::unique_ptr<DatabaseImpl>);
Database(const Database &) = delete;
Database &operator=(const Database &) = delete;
public:
- Database(const std::string &filename, int flags = 0);
+ static mapbox::util::variant<Database, Exception> tryOpen(const std::string &filename, int flags = 0);
+ static Database open(const std::string &filename, int flags = 0);
+
Database(Database &&);
~Database();
Database &operator=(Database &&);
@@ -87,6 +87,7 @@ private:
std::unique_ptr<DatabaseImpl> impl;
friend class Statement;
+ friend class Transaction;
};
// A Statement object represents a prepared statement that can be run repeatedly run with a Query object.
@@ -169,7 +170,7 @@ public:
void rollback();
private:
- Database& db;
+ DatabaseImpl& dbImpl;
bool needRollback = true;
};
diff --git a/platform/default/string_stdlib.cpp b/platform/default/string_stdlib.cpp
index 2642e88aff..103444df1c 100644
--- a/platform/default/string_stdlib.cpp
+++ b/platform/default/string_stdlib.cpp
@@ -1,8 +1,5 @@
#include <mbgl/util/platform.hpp>
-#define NU_WITH_TOUPPER
-#define NU_WITH_TOLOWER
-#define NU_WITH_UTF8_WRITER
-#include <libnu/libnu.h>
+#include <libnu/casemap.h>
#include <cstring>
#include <sstream>
diff --git a/platform/default/unaccent.cpp b/platform/default/unaccent.cpp
new file mode 100644
index 0000000000..faefb4b4cd
--- /dev/null
+++ b/platform/default/unaccent.cpp
@@ -0,0 +1,43 @@
+#include <mbgl/util/platform.hpp>
+#include <libnu/unaccent.h>
+#include <unaccent.hpp>
+
+#include <cstring>
+#include <sstream>
+
+namespace mbgl { namespace platform {
+
+std::string unaccent(const std::string& str)
+{
+ std::stringstream output;
+ char const *itr = str.c_str(), *nitr;
+ char const *end = itr + str.length();
+ char lo[5] = { 0 };
+
+ for (; itr < end; itr = nitr)
+ {
+ uint32_t code_point = 0;
+ char const* buf = nullptr;
+
+ nitr = _nu_tounaccent(itr, end, nu_utf8_read, &code_point, &buf, nullptr);
+ if (buf != nullptr)
+ {
+ do
+ {
+ buf = NU_CASEMAP_DECODING_FUNCTION(buf, &code_point);
+ if (code_point == 0) break;
+ output.write(lo, nu_utf8_write(code_point, lo) - lo);
+ }
+ while (code_point != 0);
+ }
+ else
+ {
+ output.write(itr, nitr - itr);
+ }
+ }
+
+ return output.str();
+}
+
+} // namespace platform
+} // namespace mbgl
diff --git a/platform/default/unaccent.hpp b/platform/default/unaccent.hpp
new file mode 100644
index 0000000000..85ac37a7de
--- /dev/null
+++ b/platform/default/unaccent.hpp
@@ -0,0 +1,13 @@
+#pragma once
+
+#include <string>
+
+namespace mbgl {
+namespace platform {
+
+// Non-locale-aware diacritic folding based on nunicode
+// Used as a fallback when locale-aware comparisons aren't available
+std::string unaccent(const std::string &string);
+
+} // namespace platform
+} // namespace mbgl
diff --git a/platform/qt/include/qmapboxgl.hpp b/platform/qt/include/qmapboxgl.hpp
index 79eb672d1f..a699b77cec 100644
--- a/platform/qt/include/qmapboxgl.hpp
+++ b/platform/qt/include/qmapboxgl.hpp
@@ -236,8 +236,10 @@ public:
bool layerExists(const QString &id);
void removeLayer(const QString &id);
- void setFilter(const QString &layer, const QVariant &filter);
+ QList<QString> layerIds() const;
+ void setFilter(const QString &layer, const QVariant &filter);
+ QVariant getFilter(const QString &layer) const;
// When rendering on a different thread,
// should be called on the render thread.
void createRenderer();
diff --git a/platform/qt/src/qmapboxgl.cpp b/platform/qt/src/qmapboxgl.cpp
index 8c3355dc09..09479581bb 100644
--- a/platform/qt/src/qmapboxgl.cpp
+++ b/platform/qt/src/qmapboxgl.cpp
@@ -55,6 +55,10 @@
#include <QString>
#include <QStringList>
#include <QThreadStorage>
+#include <QVariant>
+#include <QVariantList>
+#include <QVariantMap>
+#include <QColor>
#include <memory>
@@ -703,7 +707,7 @@ double QMapboxGL::scale() const
void QMapboxGL::setScale(double scale_, const QPointF &center)
{
- d_ptr->mapObj->setZoom(mbgl::util::log2(scale_), mbgl::ScreenCoordinate { center.x(), center.y() });
+ d_ptr->mapObj->setZoom(::log2(scale_), mbgl::ScreenCoordinate { center.x(), center.y() });
}
/*!
@@ -1111,7 +1115,7 @@ void QMapboxGL::moveBy(const QPointF &offset)
can be used for implementing a pinch gesture.
*/
void QMapboxGL::scaleBy(double scale_, const QPointF &center) {
- d_ptr->mapObj->setZoom(d_ptr->mapObj->getZoom() + mbgl::util::log2(scale_), mbgl::ScreenCoordinate { center.x(), center.y() });
+ d_ptr->mapObj->setZoom(d_ptr->mapObj->getZoom() + ::log2(scale_), mbgl::ScreenCoordinate { center.x(), center.y() });
}
/*!
@@ -1466,6 +1470,23 @@ void QMapboxGL::removeLayer(const QString& id)
}
/*!
+ List of all existing layer ids from the current style.
+*/
+QList<QString> QMapboxGL::layerIds() const
+{
+ const auto &layers = d_ptr->mapObj->getStyle().getLayers();
+
+ QList<QString> layerIds;
+ layerIds.reserve(layers.size());
+
+ for (const mbgl::style::Layer *layer : layers) {
+ layerIds.append(QString::fromStdString(layer->getID()));
+ }
+
+ return layerIds;
+}
+
+/*!
Adds the \a image with the identifier \a id that can be used
later by a symbol layer.
@@ -1492,7 +1513,7 @@ void QMapboxGL::removeImage(const QString &id)
/*!
Adds a \a filter to a style \a layer using the format described in the \l
- {https://www.mapbox.com/mapbox-gl-style-spec/#types-filter}{Mapbox style specification}.
+ {https://www.mapbox.com/mapbox-gl-js/style-spec/#other-filter}{Mapbox style specification}.
Given a layer \c marker from an arbitrary GeoJSON source containing features of type \b
"Point" and \b "LineString", this example shows how to make sure the layer will only tag
@@ -1500,14 +1521,14 @@ void QMapboxGL::removeImage(const QString &id)
\code
QVariantList filterExpression;
- filterExpression.append("==");
- filterExpression.append("$type");
- filterExpression.append("Point");
+ filterExpression.push_back(QLatin1String("=="));
+ filterExpression.push_back(QLatin1String("$type"));
+ filterExpression.push_back(QLatin1String("Point"));
QVariantList filter;
- filter.append(filterExpression);
+ filter.push_back(filterExpression);
- map->setFilter("marker", filter);
+ map->setFilter(QLatin1String("marker"), filter);
\endcode
*/
void QMapboxGL::setFilter(const QString& layer, const QVariant& filter)
@@ -1555,6 +1576,77 @@ void QMapboxGL::setFilter(const QString& layer, const QVariant& filter)
qWarning() << "Layer doesn't support filters";
}
+QVariant QVariantFromValue(const mbgl::Value &value) {
+ return value.match(
+ [](const mbgl::NullValue) {
+ return QVariant();
+ }, [](const bool value_) {
+ return QVariant(value_);
+ }, [](const float value_) {
+ return QVariant(value_);
+ }, [](const int64_t value_) {
+ return QVariant(static_cast<qlonglong>(value_));
+ }, [](const double value_) {
+ return QVariant(value_);
+ }, [](const std::string &value_) {
+ return QVariant(value_.c_str());
+ }, [](const mbgl::Color &value_) {
+ return QColor(value_.r, value_.g, value_.b, value_.a);
+ }, [&](const std::vector<mbgl::Value> &vector) {
+ QVariantList list;
+ list.reserve(vector.size());
+ for (const auto &value_ : vector) {
+ list.push_back(QVariantFromValue(value_));
+ }
+ return list;
+ }, [&](const std::unordered_map<std::string, mbgl::Value> &map) {
+ QVariantMap varMap;
+ for (auto &item : map) {
+ varMap.insert(item.first.c_str(), QVariantFromValue(item.second));
+ }
+ return varMap;
+ }, [](const auto &) {
+ return QVariant();
+ });
+}
+
+/*!
+ Returns the current \a expression-based filter value applied to a style
+ \layer, if any.
+
+ Filter value types are described in the {https://www.mapbox.com/mapbox-gl-js/style-spec/#types}{Mapbox style specification}.
+*/
+QVariant QMapboxGL::getFilter(const QString &layer) const {
+ using namespace mbgl::style;
+ using namespace mbgl::style::conversion;
+
+ Layer* layer_ = d_ptr->mapObj->getStyle().getLayer(layer.toStdString());
+ if (!layer_) {
+ qWarning() << "Layer not found:" << layer;
+ return QVariant();
+ }
+
+ Filter filter_;
+
+ if (layer_->is<FillLayer>()) {
+ filter_ = layer_->as<FillLayer>()->getFilter();
+ } else if (layer_->is<LineLayer>()) {
+ filter_ = layer_->as<LineLayer>()->getFilter();
+ } else if (layer_->is<SymbolLayer>()) {
+ filter_ = layer_->as<SymbolLayer>()->getFilter();
+ } else if (layer_->is<CircleLayer>()) {
+ filter_ = layer_->as<CircleLayer>()->getFilter();
+ } else if (layer_->is<FillExtrusionLayer>()) {
+ filter_ = layer_->as<FillExtrusionLayer>()->getFilter();
+ } else {
+ qWarning() << "Layer doesn't support filters";
+ return QVariant();
+ }
+
+ auto serialized = filter_.serialize();
+ return QVariantFromValue(serialized);
+}
+
/*!
Creates the infrastructure needed for rendering the map. It
should be called before any call to render().
diff --git a/platform/qt/src/qmapboxgl_map_renderer.cpp b/platform/qt/src/qmapboxgl_map_renderer.cpp
index 7a9d1f6f78..acc4194498 100644
--- a/platform/qt/src/qmapboxgl_map_renderer.cpp
+++ b/platform/qt/src/qmapboxgl_map_renderer.cpp
@@ -1,12 +1,47 @@
#include "qmapboxgl_map_renderer.hpp"
+#include "qmapboxgl_scheduler.hpp"
+#include <QThreadStorage>
#include <QtGlobal>
+static bool needsToForceScheduler() {
+ static QThreadStorage<bool> force;
+
+ if (!force.hasLocalData()) {
+ force.setLocalData(mbgl::Scheduler::GetCurrent() == nullptr);
+ }
+
+ return force.localData();
+};
+
+static auto *getScheduler() {
+ static QThreadStorage<std::shared_ptr<QMapboxGLScheduler>> scheduler;
+
+ if (!scheduler.hasLocalData()) {
+ scheduler.setLocalData(std::make_shared<QMapboxGLScheduler>());
+ }
+
+ return scheduler.localData().get();
+};
+
QMapboxGLMapRenderer::QMapboxGLMapRenderer(qreal pixelRatio,
mbgl::DefaultFileSource &fs, mbgl::ThreadPool &tp, QMapboxGLSettings::GLContextMode mode)
: m_renderer(std::make_unique<mbgl::Renderer>(m_backend, pixelRatio, fs, tp, static_cast<mbgl::GLContextMode>(mode)))
- , m_threadWithScheduler(Scheduler::GetCurrent() != nullptr)
+ , m_forceScheduler(needsToForceScheduler())
{
+ // If we don't have a Scheduler on this thread, which
+ // is usually the case for render threads, use a shared
+ // dummy scheduler that needs to be explicitly forced to
+ // process events.
+ if (m_forceScheduler) {
+ auto scheduler = getScheduler();
+
+ if (mbgl::Scheduler::GetCurrent() == nullptr) {
+ mbgl::Scheduler::SetCurrent(scheduler);
+ }
+
+ connect(scheduler, SIGNAL(needsProcessing()), this, SIGNAL(needsRendering()));
+ }
}
QMapboxGLMapRenderer::~QMapboxGLMapRenderer()
@@ -14,16 +49,6 @@ QMapboxGLMapRenderer::~QMapboxGLMapRenderer()
MBGL_VERIFY_THREAD(tid);
}
-void QMapboxGLMapRenderer::schedule(std::weak_ptr<mbgl::Mailbox> mailbox)
-{
- std::lock_guard<std::mutex> lock(m_taskQueueMutex);
- m_taskQueue.push(mailbox);
-
- // Need to force the main thread to wake
- // up this thread and process the events.
- emit needsRendering();
-}
-
void QMapboxGLMapRenderer::updateParameters(std::shared_ptr<mbgl::UpdateParameters> newParameters)
{
std::lock_guard<std::mutex> lock(m_updateMutex);
@@ -57,26 +82,10 @@ void QMapboxGLMapRenderer::render()
// The OpenGL implementation automatically enables the OpenGL context for us.
mbgl::BackendScope scope(m_backend, mbgl::BackendScope::ScopeType::Implicit);
- // If we don't have a Scheduler on this thread, which
- // is usually the case for render threads, use this
- // object as scheduler.
- if (!m_threadWithScheduler) {
- Scheduler::SetCurrent(this);
- }
-
m_renderer->render(*params);
- if (!m_threadWithScheduler) {
- std::queue<std::weak_ptr<mbgl::Mailbox>> taskQueue;
- {
- std::unique_lock<std::mutex> lock(m_taskQueueMutex);
- std::swap(taskQueue, m_taskQueue);
- }
-
- while (!taskQueue.empty()) {
- mbgl::Mailbox::maybeReceive(taskQueue.front());
- taskQueue.pop();
- }
+ if (m_forceScheduler) {
+ getScheduler()->processEvents();
}
}
diff --git a/platform/qt/src/qmapboxgl_map_renderer.hpp b/platform/qt/src/qmapboxgl_map_renderer.hpp
index adba11de51..0b17542e2f 100644
--- a/platform/qt/src/qmapboxgl_map_renderer.hpp
+++ b/platform/qt/src/qmapboxgl_map_renderer.hpp
@@ -14,7 +14,6 @@
#include <memory>
#include <mutex>
-#include <queue>
namespace mbgl {
class Renderer;
@@ -23,7 +22,7 @@ class UpdateParameters;
class QMapboxGLRendererBackend;
-class QMapboxGLMapRenderer : public QObject, public mbgl::Scheduler
+class QMapboxGLMapRenderer : public QObject
{
Q_OBJECT
@@ -32,9 +31,6 @@ public:
mbgl::ThreadPool &, QMapboxGLSettings::GLContextMode);
virtual ~QMapboxGLMapRenderer();
- // mbgl::Scheduler implementation.
- void schedule(std::weak_ptr<mbgl::Mailbox> scheduled) final;
-
void render();
void updateFramebuffer(quint32 fbo, const mbgl::Size &size);
void setObserver(std::shared_ptr<mbgl::RendererObserver>);
@@ -56,8 +52,5 @@ private:
QMapboxGLRendererBackend m_backend;
std::unique_ptr<mbgl::Renderer> m_renderer;
- std::mutex m_taskQueueMutex;
- std::queue<std::weak_ptr<mbgl::Mailbox>> m_taskQueue;
-
- bool m_threadWithScheduler;
+ bool m_forceScheduler;
};
diff --git a/platform/qt/src/qmapboxgl_scheduler.cpp b/platform/qt/src/qmapboxgl_scheduler.cpp
new file mode 100644
index 0000000000..e2d39703ee
--- /dev/null
+++ b/platform/qt/src/qmapboxgl_scheduler.cpp
@@ -0,0 +1,38 @@
+#include "qmapboxgl_scheduler.hpp"
+
+#include <mbgl/util/util.hpp>
+
+#include <cassert>
+
+QMapboxGLScheduler::QMapboxGLScheduler()
+{
+}
+
+QMapboxGLScheduler::~QMapboxGLScheduler()
+{
+ MBGL_VERIFY_THREAD(tid);
+}
+
+void QMapboxGLScheduler::schedule(std::weak_ptr<mbgl::Mailbox> mailbox)
+{
+ std::lock_guard<std::mutex> lock(m_taskQueueMutex);
+ m_taskQueue.push(mailbox);
+
+ // Need to force the main thread to wake
+ // up this thread and process the events.
+ emit needsProcessing();
+}
+
+void QMapboxGLScheduler::processEvents()
+{
+ std::queue<std::weak_ptr<mbgl::Mailbox>> taskQueue;
+ {
+ std::unique_lock<std::mutex> lock(m_taskQueueMutex);
+ std::swap(taskQueue, m_taskQueue);
+ }
+
+ while (!taskQueue.empty()) {
+ mbgl::Mailbox::maybeReceive(taskQueue.front());
+ taskQueue.pop();
+ }
+}
diff --git a/platform/qt/src/qmapboxgl_scheduler.hpp b/platform/qt/src/qmapboxgl_scheduler.hpp
new file mode 100644
index 0000000000..68636d0d11
--- /dev/null
+++ b/platform/qt/src/qmapboxgl_scheduler.hpp
@@ -0,0 +1,34 @@
+#pragma once
+
+#include <mbgl/actor/mailbox.hpp>
+#include <mbgl/actor/scheduler.hpp>
+#include <mbgl/util/util.hpp>
+
+#include <QObject>
+
+#include <memory>
+#include <mutex>
+#include <queue>
+
+class QMapboxGLScheduler : public QObject, public mbgl::Scheduler
+{
+ Q_OBJECT
+
+public:
+ QMapboxGLScheduler();
+ virtual ~QMapboxGLScheduler();
+
+ // mbgl::Scheduler implementation.
+ void schedule(std::weak_ptr<mbgl::Mailbox> scheduled) final;
+
+ void processEvents();
+
+signals:
+ void needsProcessing();
+
+private:
+ MBGL_STORE_THREAD(tid);
+
+ std::mutex m_taskQueueMutex;
+ std::queue<std::weak_ptr<mbgl::Mailbox>> m_taskQueue;
+};
diff --git a/platform/qt/src/qt_geojson.cpp b/platform/qt/src/qt_geojson.cpp
index 80377de64d..9d0a3e96eb 100644
--- a/platform/qt/src/qt_geojson.cpp
+++ b/platform/qt/src/qt_geojson.cpp
@@ -72,10 +72,8 @@ mbgl::Value asMapboxGLPropertyValue(const QVariant &value) {
auto valueMap = [](const QVariantMap &map) {
std::unordered_map<std::string, mbgl::Value> mbglMap;
mbglMap.reserve(map.size());
- auto it = map.constBegin();
- while (it != map.constEnd()) {
+ for (auto it = map.constBegin(); it != map.constEnd(); ++it) {
mbglMap.emplace(std::make_pair(it.key().toStdString(), asMapboxGLPropertyValue(it.value())));
- ++it;
}
return mbglMap;
};
@@ -132,8 +130,7 @@ mbgl::FeatureIdentifier asMapboxGLFeatureIdentifier(const QVariant &id) {
mbgl::Feature asMapboxGLFeature(const QMapbox::Feature &feature) {
mbgl::PropertyMap properties;
properties.reserve(feature.properties.size());
- auto it = feature.properties.constBegin();
- while (it != feature.properties.constEnd()) {
+ for (auto it = feature.properties.constBegin(); it != feature.properties.constEnd(); ++it) {
properties.emplace(std::make_pair(it.key().toStdString(), asMapboxGLPropertyValue(it.value())));
}
diff --git a/platform/qt/src/sqlite3.cpp b/platform/qt/src/sqlite3.cpp
index 4bcaea0e31..2ca09fd3ad 100644
--- a/platform/qt/src/sqlite3.cpp
+++ b/platform/qt/src/sqlite3.cpp
@@ -52,15 +52,6 @@ void checkDatabaseError(const QSqlDatabase &db) {
}
}
-void checkDatabaseOpenError(const QSqlDatabase &db) {
- // Assume every error when opening the data as CANTOPEN. Qt
- // always returns -1 for `nativeErrorCode()` on database errors.
- QSqlError lastError = db.lastError();
- if (lastError.type() != QSqlError::NoError) {
- throw Exception { ResultCode::CantOpen, "Error opening the database." };
- }
-}
-
namespace {
QString incrementCounter() {
static QAtomicInt count = 0;
@@ -70,32 +61,9 @@ namespace {
class DatabaseImpl {
public:
- DatabaseImpl(const char* filename, int flags)
- : connectionName(QString::number(uint64_t(QThread::currentThread())) + incrementCounter())
+ DatabaseImpl(QString connectionName_)
+ : connectionName(std::move(connectionName_))
{
- if (!QSqlDatabase::drivers().contains("QSQLITE")) {
- throw Exception { ResultCode::CantOpen, "SQLite driver not found." };
- }
-
- assert(!QSqlDatabase::contains(connectionName));
- auto db = QSqlDatabase::addDatabase("QSQLITE", connectionName);
-
- QString connectOptions = db.connectOptions();
- if (flags & OpenFlag::ReadOnly) {
- if (!connectOptions.isEmpty()) connectOptions.append(';');
- connectOptions.append("QSQLITE_OPEN_READONLY");
- }
- if (flags & OpenFlag::SharedCache) {
- if (!connectOptions.isEmpty()) connectOptions.append(';');
- connectOptions.append("QSQLITE_ENABLE_SHARED_CACHE");
- }
-
- db.setConnectOptions(connectOptions);
- db.setDatabaseName(QString(filename));
-
- if (!db.open()) {
- checkDatabaseOpenError(db);
- }
}
~DatabaseImpl() {
@@ -104,6 +72,9 @@ public:
checkDatabaseError(db);
}
+ void setBusyTimeout(std::chrono::milliseconds timeout);
+ void exec(const std::string& sql);
+
QString connectionName;
};
@@ -127,12 +98,47 @@ public:
template <typename T>
using optional = std::experimental::optional<T>;
+mapbox::util::variant<Database, Exception> Database::tryOpen(const std::string &filename, int flags) {
+ if (!QSqlDatabase::drivers().contains("QSQLITE")) {
+ return Exception { ResultCode::CantOpen, "SQLite driver not found." };
+ }
-Database::Database(const std::string& file, int flags)
- : impl(std::make_unique<DatabaseImpl>(file.c_str(), flags)) {
- assert(impl);
+ QString connectionName = QString::number(uint64_t(QThread::currentThread())) + incrementCounter();
+
+ assert(!QSqlDatabase::contains(connectionName));
+ auto db = QSqlDatabase::addDatabase("QSQLITE", connectionName);
+
+ QString connectOptions = db.connectOptions();
+ if (flags & OpenFlag::ReadOnly) {
+ if (!connectOptions.isEmpty()) connectOptions.append(';');
+ connectOptions.append("QSQLITE_OPEN_READONLY");
+ }
+
+ db.setConnectOptions(connectOptions);
+ db.setDatabaseName(QString(filename.c_str()));
+
+ if (!db.open()) {
+ // Assume every error when opening the data as CANTOPEN. Qt
+ // always returns -1 for `nativeErrorCode()` on database errors.
+ return Exception { ResultCode::CantOpen, "Error opening the database." };
+ }
+
+ return Database(std::make_unique<DatabaseImpl>(connectionName));
}
+Database Database::open(const std::string &filename, int flags) {
+ auto result = tryOpen(filename, flags);
+ if (result.is<Exception>()) {
+ throw result.get<Exception>();
+ } else {
+ return std::move(result.get<Database>());
+ }
+}
+
+Database::Database(std::unique_ptr<DatabaseImpl> impl_)
+ : impl(std::move(impl_))
+{}
+
Database::Database(Database &&other)
: impl(std::move(other.impl)) {
assert(impl);
@@ -149,12 +155,15 @@ Database::~Database() {
void Database::setBusyTimeout(std::chrono::milliseconds timeout) {
assert(impl);
+ impl->setBusyTimeout(timeout);
+}
+void DatabaseImpl::setBusyTimeout(std::chrono::milliseconds timeout) {
// std::chrono::milliseconds.count() is a long and Qt will cast
// internally to int, so we need to make sure the limits apply.
std::string timeoutStr = mbgl::util::toString(timeout.count() & INT_MAX);
- auto db = QSqlDatabase::database(impl->connectionName);
+ auto db = QSqlDatabase::database(connectionName);
QString connectOptions = db.connectOptions();
if (connectOptions.isEmpty()) {
if (!connectOptions.isEmpty()) connectOptions.append(';');
@@ -165,19 +174,25 @@ void Database::setBusyTimeout(std::chrono::milliseconds timeout) {
}
db.setConnectOptions(connectOptions);
if (!db.open()) {
- checkDatabaseOpenError(db);
+ // Assume every error when opening the data as CANTOPEN. Qt
+ // always returns -1 for `nativeErrorCode()` on database errors.
+ throw Exception { ResultCode::CantOpen, "Error opening the database." };
}
}
void Database::exec(const std::string &sql) {
assert(impl);
+ impl->exec(sql);
+}
+
+void DatabaseImpl::exec(const std::string& sql) {
QStringList statements = QString::fromStdString(sql).split(';', QString::SkipEmptyParts);
statements.removeAll("\n");
for (QString statement : statements) {
if (!statement.endsWith(';')) {
statement.append(';');
}
- QSqlQuery query(QSqlDatabase::database(impl->connectionName));
+ QSqlQuery query(QSqlDatabase::database(connectionName));
query.prepare(statement);
if (!query.exec()) {
@@ -424,16 +439,16 @@ uint64_t Query::changes() const {
}
Transaction::Transaction(Database& db_, Mode mode)
- : db(db_) {
+ : dbImpl(*db_.impl) {
switch (mode) {
case Deferred:
- db.exec("BEGIN DEFERRED TRANSACTION");
+ dbImpl.exec("BEGIN DEFERRED TRANSACTION");
break;
case Immediate:
- db.exec("BEGIN IMMEDIATE TRANSACTION");
+ dbImpl.exec("BEGIN IMMEDIATE TRANSACTION");
break;
case Exclusive:
- db.exec("BEGIN EXCLUSIVE TRANSACTION");
+ dbImpl.exec("BEGIN EXCLUSIVE TRANSACTION");
break;
}
}
@@ -450,12 +465,12 @@ Transaction::~Transaction() {
void Transaction::commit() {
needRollback = false;
- db.exec("COMMIT TRANSACTION");
+ dbImpl.exec("COMMIT TRANSACTION");
}
void Transaction::rollback() {
needRollback = false;
- db.exec("ROLLBACK TRANSACTION");
+ dbImpl.exec("ROLLBACK TRANSACTION");
}
} // namespace sqlite