diff options
Diffstat (limited to 'platform/default')
30 files changed, 832 insertions, 375 deletions
diff --git a/platform/default/asset_file_source.cpp b/platform/default/asset_file_source.cpp index 1832818378..54dbb8d0f6 100644 --- a/platform/default/asset_file_source.cpp +++ b/platform/default/asset_file_source.cpp @@ -1,4 +1,5 @@ #include <mbgl/storage/asset_file_source.hpp> +#include <mbgl/storage/file_source_request.hpp> #include <mbgl/storage/response.hpp> #include <mbgl/util/string.hpp> #include <mbgl/util/thread.hpp> @@ -8,17 +9,16 @@ #include <sys/types.h> #include <sys/stat.h> -#include <unistd.h> namespace mbgl { class AssetFileSource::Impl { public: - Impl(std::string root_) + Impl(ActorRef<Impl>, std::string root_) : root(std::move(root_)) { } - void request(const std::string& url, FileSource::Callback callback) { + void request(const std::string& url, ActorRef<FileSourceRequest> req) { std::string path; if (url.size() <= 8 || url[8] == '/') { @@ -34,7 +34,7 @@ public: struct stat buf; int result = stat(path.c_str(), &buf); - if (result == 0 && S_ISDIR(buf.st_mode)) { + if (result == 0 && (S_IFDIR & buf.st_mode)) { response.error = std::make_unique<Response::Error>(Response::Error::Reason::NotFound); } else if (result == -1 && errno == ENOENT) { response.error = std::make_unique<Response::Error>(Response::Error::Reason::NotFound); @@ -48,7 +48,7 @@ public: } } - callback(response); + req.invoke(&FileSourceRequest::setResponse, response); } private: @@ -56,15 +56,17 @@ private: }; AssetFileSource::AssetFileSource(const std::string& root) - : thread(std::make_unique<util::Thread<Impl>>( - util::ThreadContext{"AssetFileSource", util::ThreadPriority::Low}, - root)) { + : impl(std::make_unique<util::Thread<Impl>>("AssetFileSource", root)) { } AssetFileSource::~AssetFileSource() = default; std::unique_ptr<AsyncRequest> AssetFileSource::request(const Resource& resource, Callback callback) { - return thread->invokeWithCallback(&Impl::request, resource.url, callback); + auto req = std::make_unique<FileSourceRequest>(std::move(callback)); + + impl->actor().invoke(&Impl::request, resource.url, req->actor()); + + return std::move(req); } } // namespace mbgl diff --git a/platform/default/async_task.cpp b/platform/default/async_task.cpp index 5fa9db8d33..50891056d8 100644 --- a/platform/default/async_task.cpp +++ b/platform/default/async_task.cpp @@ -16,7 +16,7 @@ public: : async(new uv_async_t), task(std::move(fn)) { - uv_loop_t* loop = reinterpret_cast<uv_loop_t*>(RunLoop::getLoopHandle()); + auto* loop = reinterpret_cast<uv_loop_t*>(RunLoop::getLoopHandle()); if (uv_async_init(loop, async, asyncCallback) != 0) { throw std::runtime_error("Failed to initialize async."); } diff --git a/platform/default/bidi.cpp b/platform/default/bidi.cpp index a76c4bcaac..d475c387b3 100644 --- a/platform/default/bidi.cpp +++ b/platform/default/bidi.cpp @@ -30,7 +30,7 @@ std::u16string applyArabicShaping(const std::u16string& input) { UErrorCode errorCode = U_ZERO_ERROR; const int32_t outputLength = - u_shapeArabic(mbgl::utf16char_cast<const UChar*>(input.c_str()), static_cast<int32_t>(input.size()), NULL, 0, + u_shapeArabic(mbgl::utf16char_cast<const UChar*>(input.c_str()), static_cast<int32_t>(input.size()), nullptr, 0, (U_SHAPE_LETTERS_SHAPE & U_SHAPE_LETTERS_MASK) | (U_SHAPE_TEXT_DIRECTION_LOGICAL & U_SHAPE_TEXT_DIRECTION_MASK), &errorCode); @@ -57,7 +57,7 @@ void BiDi::mergeParagraphLineBreaks(std::set<size_t>& lineBreakPoints) { for (int32_t i = 0; i < paragraphCount; i++) { UErrorCode errorCode = U_ZERO_ERROR; int32_t paragraphEndIndex; - ubidi_getParagraphByIndex(impl->bidiText, i, NULL, ¶graphEndIndex, NULL, &errorCode); + ubidi_getParagraphByIndex(impl->bidiText, i, nullptr, ¶graphEndIndex, nullptr, &errorCode); if (U_FAILURE(errorCode)) { throw std::runtime_error(std::string("ProcessedBiDiText::mergeParagraphLineBreaks: ") + @@ -92,7 +92,7 @@ std::vector<std::u16string> BiDi::processText(const std::u16string& input, UErrorCode errorCode = U_ZERO_ERROR; ubidi_setPara(impl->bidiText, mbgl::utf16char_cast<const UChar*>(input.c_str()), static_cast<int32_t>(input.size()), - UBIDI_DEFAULT_LTR, NULL, &errorCode); + UBIDI_DEFAULT_LTR, nullptr, &errorCode); if (U_FAILURE(errorCode)) { throw std::runtime_error(std::string("BiDi::processText: ") + u_errorName(errorCode)); diff --git a/platform/default/default_file_source.cpp b/platform/default/default_file_source.cpp index 7db0b85461..3fdb03e6b4 100644 --- a/platform/default/default_file_source.cpp +++ b/platform/default/default_file_source.cpp @@ -1,9 +1,11 @@ #include <mbgl/storage/default_file_source.hpp> #include <mbgl/storage/asset_file_source.hpp> +#include <mbgl/storage/file_source_request.hpp> #include <mbgl/storage/local_file_source.hpp> #include <mbgl/storage/online_file_source.hpp> #include <mbgl/storage/offline_database.hpp> #include <mbgl/storage/offline_download.hpp> +#include <mbgl/storage/resource_transform.hpp> #include <mbgl/util/platform.hpp> #include <mbgl/util/url.hpp> @@ -26,8 +28,15 @@ namespace mbgl { class DefaultFileSource::Impl { public: - Impl(const std::string& cachePath, uint64_t maximumCacheSize) - : offlineDatabase(cachePath, maximumCacheSize) { + Impl(ActorRef<Impl> self, std::shared_ptr<FileSource> assetFileSource_, const 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); } void setAPIBaseURL(const std::string& url) { @@ -46,13 +55,13 @@ public: return onlineFileSource.getAccessToken(); } - void setResourceTransform(OnlineFileSource::ResourceTransform&& transform) { + void setResourceTransform(optional<ActorRef<ResourceTransform>>&& transform) { onlineFileSource.setResourceTransform(std::move(transform)); } void listRegions(std::function<void (std::exception_ptr, optional<std::vector<OfflineRegion>>)> callback) { try { - callback({}, offlineDatabase.listRegions()); + callback({}, offlineDatabase->listRegions()); } catch (...) { callback(std::current_exception(), {}); } @@ -62,7 +71,7 @@ public: const OfflineRegionMetadata& metadata, std::function<void (std::exception_ptr, optional<OfflineRegion>)> callback) { try { - callback({}, offlineDatabase.createRegion(definition, metadata)); + callback({}, offlineDatabase->createRegion(definition, metadata)); } catch (...) { callback(std::current_exception(), {}); } @@ -72,7 +81,7 @@ public: const OfflineRegionMetadata& metadata, std::function<void (std::exception_ptr, optional<OfflineRegionMetadata>)> callback) { try { - callback({}, offlineDatabase.updateMetadata(regionID, metadata)); + callback({}, offlineDatabase->updateMetadata(regionID, metadata)); } catch (...) { callback(std::current_exception(), {}); } @@ -89,7 +98,7 @@ public: void deleteRegion(OfflineRegion&& region, std::function<void (std::exception_ptr)> callback) { try { downloads.erase(region.getID()); - offlineDatabase.deleteRegion(std::move(region)); + offlineDatabase->deleteRegion(std::move(region)); callback({}); } catch (...) { callback(std::current_exception()); @@ -104,36 +113,66 @@ public: getDownload(regionID).setState(state); } - void request(AsyncRequest* req, Resource resource, Callback callback) { - Resource revalidation = resource; - - const bool hasPrior = resource.priorEtag || resource.priorModified || resource.priorExpires; - if (!hasPrior || resource.necessity == Resource::Optional) { - auto offlineResponse = offlineDatabase.get(resource); - - if (resource.necessity == Resource::Optional && !offlineResponse) { - // Ensure there's always a response that we can send, so the caller knows that - // there's no optional data available in the cache. - offlineResponse.emplace(); - offlineResponse->noContent = true; - offlineResponse->error = std::make_unique<Response::Error>( - Response::Error::Reason::NotFound, "Not found in offline database"); + void request(AsyncRequest* req, Resource resource, ActorRef<FileSourceRequest> ref) { + auto callback = [ref] (const Response& res) mutable { + ref.invoke(&FileSourceRequest::setResponse, res); + }; + + if (isAssetURL(resource.url)) { + //Asset request + tasks[req] = assetFileSource->request(resource, callback); + } else if (LocalFileSource::acceptsURL(resource.url)) { + //Local file request + tasks[req] = localFileSource->request(resource, callback); + } else { + // Try the offline database + const bool hasPrior = resource.priorEtag || resource.priorModified || + resource.priorExpires || resource.priorData; + if (!hasPrior || resource.necessity == Resource::Optional) { + auto offlineResponse = offlineDatabase->get(resource); + + if (resource.necessity == Resource::Optional && !offlineResponse) { + // Ensure there's always a response that we can send, so the caller knows that + // there's no optional data available in the cache. + offlineResponse.emplace(); + offlineResponse->noContent = true; + offlineResponse->error = std::make_unique<Response::Error>( + Response::Error::Reason::NotFound, "Not found in offline database"); + } + + if (offlineResponse) { + resource.priorModified = offlineResponse->modified; + resource.priorExpires = offlineResponse->expires; + resource.priorEtag = offlineResponse->etag; + + // Don't return resources the server requested not to show when they're stale. + // Even if we can't directly use the response, we may still use it to send a + // conditional HTTP request. + if (offlineResponse->isUsable()) { + callback(*offlineResponse); + } else if (resource.necessity == Resource::Optional) { + // Instead of the data that we got, return a not found error so that + // underlying implementations know about the fact that we couldn't find + // usable cache data. + offlineResponse->error = std::make_unique<Response::Error>( + Response::Error::Reason::NotFound, "Cached resource is unusable"); + callback(*offlineResponse); + } else { + // Since we can't return the data immediately, we'll have to hold on so that + // we can return it later in case we get a 304 Not Modified response. + resource.priorData = offlineResponse->data; + } + } } - if (offlineResponse) { - revalidation.priorModified = offlineResponse->modified; - revalidation.priorExpires = offlineResponse->expires; - revalidation.priorEtag = offlineResponse->etag; - callback(*offlineResponse); + // Get from the online file source + if (resource.necessity == Resource::Required) { + tasks[req] = onlineFileSource.request(resource, [=] (Response onlineResponse) mutable { + this->offlineDatabase->put(resource, onlineResponse); + callback(onlineResponse); + }); } } - - if (resource.necessity == Resource::Required) { - tasks[req] = onlineFileSource.request(revalidation, [=] (Response onlineResponse) { - this->offlineDatabase.put(revalidation, onlineResponse); - callback(onlineResponse); - }); - } } void cancel(AsyncRequest* req) { @@ -141,11 +180,15 @@ public: } void setOfflineMapboxTileCountLimit(uint64_t limit) { - offlineDatabase.setOfflineMapboxTileCountLimit(limit); + offlineDatabase->setOfflineMapboxTileCountLimit(limit); + } + + void setOnlineStatus(const bool status) { + onlineFileSource.setOnlineStatus(status); } void put(const Resource& resource, const Response& response) { - offlineDatabase.put(resource, response); + offlineDatabase->put(resource, response); } private: @@ -155,10 +198,13 @@ private: return *it->second; } return *downloads.emplace(regionID, - std::make_unique<OfflineDownload>(regionID, offlineDatabase.getRegionDefinition(regionID), offlineDatabase, onlineFileSource)).first->second; + std::make_unique<OfflineDownload>(regionID, offlineDatabase->getRegionDefinition(regionID), *offlineDatabase, onlineFileSource)).first->second; } - OfflineDatabase offlineDatabase; + // shared so that destruction is done on the creating thread + const std::shared_ptr<FileSource> assetFileSource; + const std::unique_ptr<FileSource> localFileSource; + std::unique_ptr<OfflineDatabase> offlineDatabase; OnlineFileSource onlineFileSource; std::unordered_map<AsyncRequest*, std::unique_ptr<AsyncRequest>> tasks; std::unordered_map<int64_t, std::unique_ptr<OfflineDownload>> downloads; @@ -173,118 +219,106 @@ DefaultFileSource::DefaultFileSource(const std::string& cachePath, DefaultFileSource::DefaultFileSource(const std::string& cachePath, std::unique_ptr<FileSource>&& assetFileSource_, uint64_t maximumCacheSize) - : thread(std::make_unique<util::Thread<Impl>>(util::ThreadContext{"DefaultFileSource", util::ThreadPriority::Low}, - cachePath, maximumCacheSize)), - assetFileSource(std::move(assetFileSource_)), - localFileSource(std::make_unique<LocalFileSource>()) { + : assetFileSource(std::move(assetFileSource_)) + , impl(std::make_unique<util::Thread<Impl>>("DefaultFileSource", assetFileSource, cachePath, maximumCacheSize)) { } DefaultFileSource::~DefaultFileSource() = default; void DefaultFileSource::setAPIBaseURL(const std::string& baseURL) { - thread->invoke(&Impl::setAPIBaseURL, baseURL); - cachedBaseURL = baseURL; + impl->actor().invoke(&Impl::setAPIBaseURL, baseURL); + + { + std::lock_guard<std::mutex> lock(cachedBaseURLMutex); + cachedBaseURL = baseURL; + } } -std::string DefaultFileSource::getAPIBaseURL() const { +std::string DefaultFileSource::getAPIBaseURL() { + std::lock_guard<std::mutex> lock(cachedBaseURLMutex); return cachedBaseURL; } void DefaultFileSource::setAccessToken(const std::string& accessToken) { - thread->invoke(&Impl::setAccessToken, accessToken); - cachedAccessToken = accessToken; + impl->actor().invoke(&Impl::setAccessToken, accessToken); + + { + std::lock_guard<std::mutex> lock(cachedAccessTokenMutex); + cachedAccessToken = accessToken; + } } -std::string DefaultFileSource::getAccessToken() const { +std::string DefaultFileSource::getAccessToken() { + std::lock_guard<std::mutex> lock(cachedAccessTokenMutex); return cachedAccessToken; } -void DefaultFileSource::setResourceTransform(std::function<std::string(Resource::Kind, std::string&&)> transform) { - if (transform) { - auto loop = util::RunLoop::Get(); - thread->invoke(&Impl::setResourceTransform, [loop, transform](Resource::Kind kind_, std::string&& url_, auto callback_) { - return loop->invokeWithCallback([transform](Resource::Kind kind, std::string&& url, auto callback) { - callback(transform(kind, std::move(url))); - }, kind_, std::move(url_), callback_); - }); - } else { - thread->invoke(&Impl::setResourceTransform, nullptr); - } +void DefaultFileSource::setResourceTransform(optional<ActorRef<ResourceTransform>>&& transform) { + impl->actor().invoke(&Impl::setResourceTransform, std::move(transform)); } std::unique_ptr<AsyncRequest> DefaultFileSource::request(const Resource& resource, Callback callback) { - class DefaultFileRequest : public AsyncRequest { - public: - DefaultFileRequest(Resource resource_, FileSource::Callback callback_, util::Thread<DefaultFileSource::Impl>& thread_) - : thread(thread_), - workRequest(thread.invokeWithCallback(&DefaultFileSource::Impl::request, this, resource_, callback_)) { - } + auto req = std::make_unique<FileSourceRequest>(std::move(callback)); - ~DefaultFileRequest() override { - thread.invoke(&DefaultFileSource::Impl::cancel, this); - } + req->onCancel([fs = impl->actor(), req = req.get()] () mutable { fs.invoke(&Impl::cancel, req); }); - util::Thread<DefaultFileSource::Impl>& thread; - std::unique_ptr<AsyncRequest> workRequest; - }; + impl->actor().invoke(&Impl::request, req.get(), resource, req->actor()); - if (isAssetURL(resource.url)) { - return assetFileSource->request(resource, callback); - } else if (LocalFileSource::acceptsURL(resource.url)) { - return localFileSource->request(resource, callback); - } else { - return std::make_unique<DefaultFileRequest>(resource, callback, *thread); - } + return std::move(req); } void DefaultFileSource::listOfflineRegions(std::function<void (std::exception_ptr, optional<std::vector<OfflineRegion>>)> callback) { - thread->invoke(&Impl::listRegions, callback); + impl->actor().invoke(&Impl::listRegions, callback); } void DefaultFileSource::createOfflineRegion(const OfflineRegionDefinition& definition, const OfflineRegionMetadata& metadata, std::function<void (std::exception_ptr, optional<OfflineRegion>)> callback) { - thread->invoke(&Impl::createRegion, definition, metadata, callback); + impl->actor().invoke(&Impl::createRegion, definition, metadata, callback); } void DefaultFileSource::updateOfflineMetadata(const int64_t regionID, const OfflineRegionMetadata& metadata, std::function<void (std::exception_ptr, optional<OfflineRegionMetadata>)> callback) { - thread->invoke(&Impl::updateMetadata, regionID, metadata, callback); + impl->actor().invoke(&Impl::updateMetadata, regionID, metadata, callback); } void DefaultFileSource::deleteOfflineRegion(OfflineRegion&& region, std::function<void (std::exception_ptr)> callback) { - thread->invoke(&Impl::deleteRegion, std::move(region), callback); + impl->actor().invoke(&Impl::deleteRegion, std::move(region), callback); } void DefaultFileSource::setOfflineRegionObserver(OfflineRegion& region, std::unique_ptr<OfflineRegionObserver> observer) { - thread->invoke(&Impl::setRegionObserver, region.getID(), std::move(observer)); + impl->actor().invoke(&Impl::setRegionObserver, region.getID(), std::move(observer)); } void DefaultFileSource::setOfflineRegionDownloadState(OfflineRegion& region, OfflineRegionDownloadState state) { - thread->invoke(&Impl::setRegionDownloadState, region.getID(), state); + impl->actor().invoke(&Impl::setRegionDownloadState, region.getID(), state); } void DefaultFileSource::getOfflineRegionStatus(OfflineRegion& region, std::function<void (std::exception_ptr, optional<OfflineRegionStatus>)> callback) const { - thread->invoke(&Impl::getRegionStatus, region.getID(), callback); + impl->actor().invoke(&Impl::getRegionStatus, region.getID(), callback); } void DefaultFileSource::setOfflineMapboxTileCountLimit(uint64_t limit) const { - thread->invokeSync(&Impl::setOfflineMapboxTileCountLimit, limit); + impl->actor().invoke(&Impl::setOfflineMapboxTileCountLimit, limit); } void DefaultFileSource::pause() { - thread->pause(); + impl->pause(); } void DefaultFileSource::resume() { - thread->resume(); + impl->resume(); } // For testing only: +void DefaultFileSource::setOnlineStatus(const bool status) { + impl->actor().invoke(&Impl::setOnlineStatus, status); +} + void DefaultFileSource::put(const Resource& resource, const Response& response) { - thread->invokeSync(&Impl::put, resource, response); + impl->actor().invoke(&Impl::put, resource, response); } } // namespace mbgl diff --git a/platform/default/http_file_source.cpp b/platform/default/http_file_source.cpp index 867d85fa4d..a9c442c2de 100644 --- a/platform/default/http_file_source.cpp +++ b/platform/default/http_file_source.cpp @@ -325,7 +325,9 @@ size_t HTTPRequest::headerCallback(char *const buffer, const size_t size, const baton->response->etag = std::string(buffer + begin, length - begin - 2); // remove \r\n } else if ((begin = headerMatches("cache-control: ", buffer, length)) != std::string::npos) { const std::string value { buffer + begin, length - begin - 2 }; // remove \r\n - baton->response->expires = http::CacheControl::parse(value.c_str()).toTimePoint(); + const auto cc = http::CacheControl::parse(value.c_str()); + baton->response->expires = cc.toTimePoint(); + 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)) }; diff --git a/platform/default/image.cpp b/platform/default/image.cpp index ad9d83a08d..447c6bcd66 100644 --- a/platform/default/image.cpp +++ b/platform/default/image.cpp @@ -12,7 +12,7 @@ PremultipliedImage decodePNG(const uint8_t*, size_t); PremultipliedImage decodeJPEG(const uint8_t*, size_t); PremultipliedImage decodeImage(const std::string& string) { - const uint8_t* data = reinterpret_cast<const uint8_t*>(string.data()); + const auto* data = reinterpret_cast<const uint8_t*>(string.data()); const size_t size = string.size(); #if !defined(__ANDROID__) && !defined(__APPLE__) diff --git a/platform/default/jpeg_reader.cpp b/platform/default/jpeg_reader.cpp index c5e9d880c0..5f613f9423 100644 --- a/platform/default/jpeg_reader.cpp +++ b/platform/default/jpeg_reader.cpp @@ -21,12 +21,12 @@ struct jpeg_stream_wrapper { }; static void init_source(j_decompress_ptr cinfo) { - jpeg_stream_wrapper* wrap = reinterpret_cast<jpeg_stream_wrapper*>(cinfo->src); + auto* wrap = reinterpret_cast<jpeg_stream_wrapper*>(cinfo->src); wrap->stream->seekg(0, std::ios_base::beg); } static boolean fill_input_buffer(j_decompress_ptr cinfo) { - jpeg_stream_wrapper* wrap = reinterpret_cast<jpeg_stream_wrapper*>(cinfo->src); + auto* wrap = reinterpret_cast<jpeg_stream_wrapper*>(cinfo->src); wrap->stream->read(reinterpret_cast<char*>(&wrap->buffer[0]), BUF_SIZE); std::streamsize size = wrap->stream->gcount(); wrap->manager.next_input_byte = wrap->buffer.data(); @@ -36,7 +36,7 @@ static boolean fill_input_buffer(j_decompress_ptr cinfo) { static void skip(j_decompress_ptr cinfo, long count) { if (count <= 0) return; // A zero or negative skip count should be treated as a no-op. - jpeg_stream_wrapper* wrap = reinterpret_cast<jpeg_stream_wrapper*>(cinfo->src); + auto* wrap = reinterpret_cast<jpeg_stream_wrapper*>(cinfo->src); if (wrap->manager.bytes_in_buffer > 0 && count < static_cast<long>(wrap->manager.bytes_in_buffer)) { @@ -59,7 +59,7 @@ static void attach_stream(j_decompress_ptr cinfo, std::istream* in) { cinfo->src = (struct jpeg_source_mgr *) (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT, sizeof(jpeg_stream_wrapper)); } - jpeg_stream_wrapper * src = reinterpret_cast<jpeg_stream_wrapper*> (cinfo->src); + auto * src = reinterpret_cast<jpeg_stream_wrapper*> (cinfo->src); src->manager.init_source = init_source; src->manager.fill_input_buffer = fill_input_buffer; src->manager.skip_input_data = skip; diff --git a/platform/default/local_file_source.cpp b/platform/default/local_file_source.cpp index 93b42f5fa0..21a291d8d4 100644 --- a/platform/default/local_file_source.cpp +++ b/platform/default/local_file_source.cpp @@ -1,4 +1,5 @@ #include <mbgl/storage/local_file_source.hpp> +#include <mbgl/storage/file_source_request.hpp> #include <mbgl/storage/response.hpp> #include <mbgl/util/string.hpp> #include <mbgl/util/thread.hpp> @@ -21,7 +22,9 @@ namespace mbgl { class LocalFileSource::Impl { public: - void request(const std::string& url, FileSource::Callback callback) { + Impl(ActorRef<Impl>) {} + + void request(const std::string& url, ActorRef<FileSourceRequest> req) { // Cut off the protocol std::string path = mbgl::util::percentDecode(url.substr(protocolLength)); @@ -44,19 +47,23 @@ public: } } - callback(response); + req.invoke(&FileSourceRequest::setResponse, response); } }; LocalFileSource::LocalFileSource() - : thread(std::make_unique<util::Thread<Impl>>(util::ThreadContext{"LocalFileSource", util::ThreadPriority::Low})) { + : impl(std::make_unique<util::Thread<Impl>>("LocalFileSource")) { } LocalFileSource::~LocalFileSource() = default; std::unique_ptr<AsyncRequest> LocalFileSource::request(const Resource& resource, Callback callback) { - return thread->invokeWithCallback(&Impl::request, resource.url, callback); + auto req = std::make_unique<FileSourceRequest>(std::move(callback)); + + impl->actor().invoke(&Impl::request, resource.url, req->actor()); + + return std::move(req); } bool LocalFileSource::acceptsURL(const std::string& url) { diff --git a/platform/default/mbgl/gl/headless_backend.cpp b/platform/default/mbgl/gl/headless_backend.cpp index d2fbf4e4c7..edf637a560 100644 --- a/platform/default/mbgl/gl/headless_backend.cpp +++ b/platform/default/mbgl/gl/headless_backend.cpp @@ -1,7 +1,7 @@ #include <mbgl/gl/headless_backend.hpp> #include <mbgl/gl/headless_display.hpp> #include <mbgl/gl/context.hpp> -#include <mbgl/map/backend_scope.hpp> +#include <mbgl/renderer/backend_scope.hpp> #include <cassert> #include <stdexcept> @@ -9,15 +9,26 @@ namespace mbgl { -HeadlessBackend::HeadlessBackend() { -} +class HeadlessBackend::View { +public: + View(gl::Context& context, Size size_) + : color(context.createRenderbuffer<gl::RenderbufferType::RGBA>(size_)), + depthStencil(context.createRenderbuffer<gl::RenderbufferType::DepthStencil>(size_)), + framebuffer(context.createFramebuffer(color, depthStencil)) { + } + + gl::Renderbuffer<gl::RenderbufferType::RGBA> color; + gl::Renderbuffer<gl::RenderbufferType::DepthStencil> depthStencil; + gl::Framebuffer framebuffer; +}; -HeadlessBackend::HeadlessBackend(std::shared_ptr<HeadlessDisplay> display_) - : display(std::move(display_)) { +HeadlessBackend::HeadlessBackend(Size size_) + : size(size_) { } HeadlessBackend::~HeadlessBackend() { - BackendScope scope(*this); + BackendScope guard { *this }; + view.reset(); context.reset(); } @@ -41,12 +52,33 @@ void HeadlessBackend::deactivate() { active = false; } +void HeadlessBackend::bind() { + gl::Context& context_ = getContext(); + + if (!view) { + view = std::make_unique<View>(context_, size); + } + + context_.bindFramebuffer = view->framebuffer.framebuffer; + context_.scissorTest = false; + context_.viewport = { 0, 0, size }; +} + +Size HeadlessBackend::getFramebufferSize() const { + return size; +} + void HeadlessBackend::updateAssumedState() { // no-op } -void HeadlessBackend::invalidate() { - assert(false); +void HeadlessBackend::setSize(Size size_) { + size = size_; + view.reset(); +} + +PremultipliedImage HeadlessBackend::readStillImage() { + return getContext().readFramebuffer<PremultipliedImage>(size); } } // namespace mbgl diff --git a/platform/default/mbgl/gl/headless_backend.hpp b/platform/default/mbgl/gl/headless_backend.hpp index 128b579bd2..66f861e213 100644 --- a/platform/default/mbgl/gl/headless_backend.hpp +++ b/platform/default/mbgl/gl/headless_backend.hpp @@ -1,6 +1,6 @@ #pragma once -#include <mbgl/map/backend.hpp> +#include <mbgl/renderer/renderer_backend.hpp> #include <memory> #include <functional> @@ -9,18 +9,20 @@ namespace mbgl { class HeadlessDisplay; -class HeadlessBackend : public Backend { +class HeadlessBackend : public RendererBackend { public: - HeadlessBackend(); - HeadlessBackend(std::shared_ptr<HeadlessDisplay>); + HeadlessBackend(Size = { 256, 256 }); ~HeadlessBackend() override; + void bind() override; + Size getFramebufferSize() const override; void updateAssumedState() override; - void invalidate() override; + void setSize(Size); + PremultipliedImage readStillImage(); struct Impl { - virtual ~Impl() {} + virtual ~Impl() = default; virtual void activateContext() = 0; virtual void deactivateContext() {} }; @@ -40,7 +42,12 @@ private: std::shared_ptr<HeadlessDisplay> display; std::unique_ptr<Impl> impl; + Size size; + float pixelRatio; bool active = false; + + class View; + std::unique_ptr<View> view; }; } // namespace mbgl diff --git a/platform/default/mbgl/gl/headless_display.hpp b/platform/default/mbgl/gl/headless_display.hpp index a5c95085b8..8c294655e5 100644 --- a/platform/default/mbgl/gl/headless_display.hpp +++ b/platform/default/mbgl/gl/headless_display.hpp @@ -6,13 +6,27 @@ namespace mbgl { class HeadlessDisplay { public: - HeadlessDisplay(); + static std::shared_ptr<HeadlessDisplay> create() { + static std::weak_ptr<HeadlessDisplay> instance; + + auto shared = instance.lock(); + + if (!shared) { + instance = shared = std::shared_ptr<HeadlessDisplay>(new HeadlessDisplay()); + } + + return shared; + } + + ~HeadlessDisplay(); template <typename DisplayAttribute> DisplayAttribute attribute() const; private: + HeadlessDisplay(); + class Impl; std::unique_ptr<Impl> impl; }; diff --git a/platform/default/mbgl/gl/headless_frontend.cpp b/platform/default/mbgl/gl/headless_frontend.cpp new file mode 100644 index 0000000000..5d2932258a --- /dev/null +++ b/platform/default/mbgl/gl/headless_frontend.cpp @@ -0,0 +1,86 @@ +#include <mbgl/gl/headless_frontend.hpp> +#include <mbgl/renderer/renderer.hpp> +#include <mbgl/map/map.hpp> +#include <mbgl/util/run_loop.hpp> + +namespace mbgl { + +HeadlessFrontend::HeadlessFrontend(float pixelRatio_, FileSource& fileSource, Scheduler& scheduler, const optional<std::string> programCacheDir) + : HeadlessFrontend({ 256, 256 }, pixelRatio_, fileSource, scheduler, programCacheDir) { +} + +HeadlessFrontend::HeadlessFrontend(Size size_, float pixelRatio_, FileSource& fileSource, Scheduler& scheduler, const optional<std::string> programCacheDir) + : size(size_), + pixelRatio(pixelRatio_), + backend({ static_cast<uint32_t>(size.width * pixelRatio), + static_cast<uint32_t>(size.height * pixelRatio) }), + asyncInvalidate([this] { + if (renderer && updateParameters) { + mbgl::BackendScope guard { backend }; + renderer->render(*updateParameters); + } + }), + renderer(std::make_unique<Renderer>(backend, pixelRatio, fileSource, scheduler, GLContextMode::Unique, programCacheDir)) { +} + +HeadlessFrontend::~HeadlessFrontend() = default; + +void HeadlessFrontend::reset() { + assert(renderer); + renderer.reset(); +} + +void HeadlessFrontend::update(std::shared_ptr<UpdateParameters> updateParameters_) { + updateParameters = updateParameters_; + asyncInvalidate.send(); +} + +void HeadlessFrontend::setObserver(RendererObserver& observer_) { + assert(renderer); + renderer->setObserver(&observer_); +} + +Size HeadlessFrontend::getSize() const { + return size; +} + +Renderer* HeadlessFrontend::getRenderer() { + assert(renderer); + return renderer.get(); +} + +RendererBackend* HeadlessFrontend::getBackend() { + return &backend; +} + +void HeadlessFrontend::setSize(Size size_) { + if (size != size_) { + size = size_; + backend.setSize({ static_cast<uint32_t>(size_.width * pixelRatio), + static_cast<uint32_t>(size_.height * pixelRatio) }); + } +} + +PremultipliedImage HeadlessFrontend::readStillImage() { + return backend.readStillImage(); +} + +PremultipliedImage HeadlessFrontend::render(Map& map) { + PremultipliedImage result; + + map.renderStill([&](std::exception_ptr error) { + if (error) { + std::rethrow_exception(error); + } else { + result = backend.readStillImage(); + } + }); + + while (!result.valid()) { + util::RunLoop::Get()->runOnce(); + } + + return result; +} + +} // namespace mbgl diff --git a/platform/default/mbgl/gl/headless_frontend.hpp b/platform/default/mbgl/gl/headless_frontend.hpp new file mode 100644 index 0000000000..33503bc13b --- /dev/null +++ b/platform/default/mbgl/gl/headless_frontend.hpp @@ -0,0 +1,48 @@ +#pragma once + +#include <mbgl/renderer/renderer_frontend.hpp> +#include <mbgl/gl/headless_backend.hpp> +#include <mbgl/util/async_task.hpp> +#include <mbgl/util/optional.hpp> + +#include <memory> + +namespace mbgl { + +class FileSource; +class Scheduler; +class Renderer; +class RendererBackend; +class Map; + +class HeadlessFrontend : public RendererFrontend { +public: + HeadlessFrontend(float pixelRatio_, FileSource&, Scheduler&, const optional<std::string> programCacheDir = {}); + HeadlessFrontend(Size, float pixelRatio_, FileSource&, Scheduler&, const optional<std::string> programCacheDir = {}); + ~HeadlessFrontend() override; + + void reset() override; + void update(std::shared_ptr<UpdateParameters>) override; + void setObserver(RendererObserver&) override; + + Size getSize() const; + void setSize(Size); + + Renderer* getRenderer(); + RendererBackend* getBackend(); + + PremultipliedImage readStillImage(); + PremultipliedImage render(Map&); + +private: + Size size; + float pixelRatio; + + HeadlessBackend backend; + util::AsyncTask asyncInvalidate; + + std::unique_ptr<Renderer> renderer; + std::shared_ptr<UpdateParameters> updateParameters; +}; + +} // namespace mbgl diff --git a/platform/default/mbgl/gl/offscreen_view.cpp b/platform/default/mbgl/gl/offscreen_view.cpp deleted file mode 100644 index 5424e03c96..0000000000 --- a/platform/default/mbgl/gl/offscreen_view.cpp +++ /dev/null @@ -1,64 +0,0 @@ -#include <mbgl/gl/offscreen_view.hpp> -#include <mbgl/gl/context.hpp> -#include <mbgl/gl/framebuffer.hpp> -#include <mbgl/gl/renderbuffer.hpp> -#include <mbgl/util/optional.hpp> - -#include <cstring> -#include <cassert> - -namespace mbgl { - -class OffscreenView::Impl { -public: - Impl(gl::Context& context_, const Size size_) : context(context_), size(std::move(size_)) { - assert(!size.isEmpty()); - } - - void bind() { - if (!framebuffer) { - color = context.createRenderbuffer<gl::RenderbufferType::RGBA>(size); - depthStencil = context.createRenderbuffer<gl::RenderbufferType::DepthStencil>(size); - framebuffer = context.createFramebuffer(*color, *depthStencil); - } else { - context.bindFramebuffer = framebuffer->framebuffer; - } - - context.viewport = { 0, 0, size }; - } - - PremultipliedImage readStillImage() { - return context.readFramebuffer<PremultipliedImage>(size); - } - - const Size& getSize() const { - return size; - } - -private: - gl::Context& context; - const Size size; - optional<gl::Framebuffer> framebuffer; - optional<gl::Renderbuffer<gl::RenderbufferType::RGBA>> color; - optional<gl::Renderbuffer<gl::RenderbufferType::DepthStencil>> depthStencil; -}; - -OffscreenView::OffscreenView(gl::Context& context, const Size size) - : impl(std::make_unique<Impl>(context, std::move(size))) { -} - -OffscreenView::~OffscreenView() = default; - -void OffscreenView::bind() { - impl->bind(); -} - -PremultipliedImage OffscreenView::readStillImage() { - return impl->readStillImage(); -} - -const Size& OffscreenView::getSize() const { - return impl->getSize(); -} - -} // namespace mbgl diff --git a/platform/default/mbgl/gl/offscreen_view.hpp b/platform/default/mbgl/gl/offscreen_view.hpp deleted file mode 100644 index bf1a9889cd..0000000000 --- a/platform/default/mbgl/gl/offscreen_view.hpp +++ /dev/null @@ -1,28 +0,0 @@ -#pragma once - -#include <mbgl/map/view.hpp> -#include <mbgl/util/image.hpp> - -namespace mbgl { - -namespace gl { -class Context; -} // namespace gl - -class OffscreenView : public View { -public: - OffscreenView(gl::Context&, Size size = { 256, 256 }); - ~OffscreenView(); - - void bind() override; - - PremultipliedImage readStillImage(); - - const Size& getSize() const; - -private: - class Impl; - const std::unique_ptr<Impl> impl; -}; - -} // namespace mbgl diff --git a/platform/default/mbgl/map/map_snapshotter.cpp b/platform/default/mbgl/map/map_snapshotter.cpp new file mode 100644 index 0000000000..95c46344fe --- /dev/null +++ b/platform/default/mbgl/map/map_snapshotter.cpp @@ -0,0 +1,76 @@ +#include <mbgl/map/map_snapshotter.hpp> + +#include <mbgl/actor/actor_ref.hpp> +#include <mbgl/gl/headless_frontend.hpp> +#include <mbgl/map/map.hpp> +#include <mbgl/storage/file_source.hpp> +#include <mbgl/style/style.hpp> +#include <mbgl/util/event.hpp> + +namespace mbgl { + +class MapSnapshotter::Impl { +public: + Impl(FileSource&, + Scheduler&, + const std::string& styleURL, + const Size&, + const float pixelRatio, + const CameraOptions&, + const optional<LatLngBounds> region, + const optional<std::string> programCacheDir); + + void snapshot(ActorRef<MapSnapshotter::Callback>); + +private: + HeadlessFrontend frontend; + Map map; +}; + +MapSnapshotter::Impl::Impl(FileSource& fileSource, + Scheduler& scheduler, + const std::string& styleURL, + const Size& size, + const float pixelRatio, + const 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::Still) { + + map.getStyle().loadURL(styleURL); + + map.jumpTo(cameraOptions); + + // Set region, if specified + if (region) { + mbgl::EdgeInsets insets = { 0, 0, 0, 0 }; + std::vector<LatLng> latLngs = { region->southwest(), region->northeast() }; + map.jumpTo(map.cameraForLatLngs(latLngs, insets)); + } +} + +void MapSnapshotter::Impl::snapshot(ActorRef<MapSnapshotter::Callback> callback) { + map.renderStill([this, callback = std::move(callback)] (std::exception_ptr error) mutable { + callback.invoke(&MapSnapshotter::Callback::operator(), error, error ? PremultipliedImage() : frontend.readStillImage()); + }); +} + +MapSnapshotter::MapSnapshotter(FileSource& fileSource, + Scheduler& scheduler, + const std::string& styleURL, + const Size& size, + const float pixelRatio, + const 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)) { +} + +MapSnapshotter::~MapSnapshotter() = default; + +void MapSnapshotter::snapshot(ActorRef<MapSnapshotter::Callback> callback) { + impl->actor().invoke(&Impl::snapshot, std::move(callback)); +} + +} // namespace mbgl diff --git a/platform/default/mbgl/map/map_snapshotter.hpp b/platform/default/mbgl/map/map_snapshotter.hpp new file mode 100644 index 0000000000..0afa848fd8 --- /dev/null +++ b/platform/default/mbgl/map/map_snapshotter.hpp @@ -0,0 +1,40 @@ +#pragma once + +#include <mbgl/util/image.hpp> +#include <mbgl/util/thread.hpp> +#include <mbgl/util/optional.hpp> + +#include <exception> +#include <string> +#include <functional> + +namespace mbgl { + +template<class> class ActorRef; +struct CameraOptions; +class FileSource; +class Size; +class LatLngBounds; + +class MapSnapshotter { +public: + MapSnapshotter(FileSource& fileSource, + Scheduler& scheduler, + const std::string& styleURL, + const Size&, + const float pixelRatio, + const CameraOptions&, + const optional<LatLngBounds> region, + const optional<std::string> cacheDir = {}); + + ~MapSnapshotter(); + + using Callback = std::function<void (std::exception_ptr, PremultipliedImage)>; + void snapshot(ActorRef<Callback>); + +private: + class Impl; + std::unique_ptr<util::Thread<Impl>> impl; +}; + +} // namespace mbgl diff --git a/platform/default/mbgl/storage/offline.cpp b/platform/default/mbgl/storage/offline.cpp index fd2d47819b..9ec789f725 100644 --- a/platform/default/mbgl/storage/offline.cpp +++ b/platform/default/mbgl/storage/offline.cpp @@ -1,6 +1,7 @@ #include <mbgl/storage/offline.hpp> #include <mbgl/util/tile_cover.hpp> #include <mbgl/util/tileset.hpp> +#include <mbgl/util/projection.hpp> #include <rapidjson/document.h> #include <rapidjson/stringbuffer.h> @@ -24,17 +25,11 @@ OfflineTilePyramidRegionDefinition::OfflineTilePyramidRegionDefinition( } std::vector<CanonicalTileID> OfflineTilePyramidRegionDefinition::tileCover(SourceType type, uint16_t tileSize, const Range<uint8_t>& zoomRange) const { - double minZ = std::max<double>(util::coveringZoomLevel(minZoom, type, tileSize), zoomRange.min); - double maxZ = std::min<double>(util::coveringZoomLevel(maxZoom, type, tileSize), zoomRange.max); - - assert(minZ >= 0); - assert(maxZ >= 0); - assert(minZ < std::numeric_limits<uint8_t>::max()); - assert(maxZ < std::numeric_limits<uint8_t>::max()); + const Range<uint8_t> clampedZoomRange = coveringZoomRange(type, tileSize, zoomRange); std::vector<CanonicalTileID> result; - for (uint8_t z = minZ; z <= maxZ; z++) { + for (uint8_t z = clampedZoomRange.min; z <= clampedZoomRange.max; z++) { for (const auto& tile : util::tileCover(bounds, z)) { result.emplace_back(tile.canonical); } @@ -43,6 +38,28 @@ std::vector<CanonicalTileID> OfflineTilePyramidRegionDefinition::tileCover(Sourc return result; } +uint64_t OfflineTilePyramidRegionDefinition::tileCount(SourceType type, uint16_t tileSize, const Range<uint8_t>& zoomRange) const { + + const Range<uint8_t> clampedZoomRange = coveringZoomRange(type, tileSize, zoomRange); + unsigned long result = 0;; + for (uint8_t z = clampedZoomRange.min; z <= clampedZoomRange.max; z++) { + result += util::tileCount(bounds, z, tileSize); + } + + return result; +} + +Range<uint8_t> OfflineTilePyramidRegionDefinition::coveringZoomRange(SourceType type, uint16_t tileSize, const Range<uint8_t>& zoomRange) const { + double minZ = std::max<double>(util::coveringZoomLevel(minZoom, type, tileSize), zoomRange.min); + double maxZ = std::min<double>(util::coveringZoomLevel(maxZoom, type, tileSize), zoomRange.max); + + assert(minZ >= 0); + assert(maxZ >= 0); + assert(minZ < std::numeric_limits<uint8_t>::max()); + assert(maxZ < std::numeric_limits<uint8_t>::max()); + return { static_cast<uint8_t>(minZ), static_cast<uint8_t>(maxZ) }; +} + OfflineRegionDefinition decodeOfflineRegionDefinition(const std::string& region) { rapidjson::GenericDocument<rapidjson::UTF8<>, rapidjson::CrtAllocator> doc; doc.Parse<0>(region.c_str()); diff --git a/platform/default/mbgl/storage/offline_database.cpp b/platform/default/mbgl/storage/offline_database.cpp index 204ee04c92..65c2097182 100644 --- a/platform/default/mbgl/storage/offline_database.cpp +++ b/platform/default/mbgl/storage/offline_database.cpp @@ -49,7 +49,8 @@ void OfflineDatabase::ensureSchema() { case 2: migrateToVersion3(); // fall through case 3: // no-op and fall through case 4: migrateToVersion5(); // fall through - case 5: return; + case 5: migrateToVersion6(); // fall through + case 6: return; default: break; // downgrade, delete the database } @@ -83,7 +84,7 @@ void OfflineDatabase::ensureSchema() { db->exec("PRAGMA journal_mode = DELETE"); db->exec("PRAGMA synchronous = FULL"); db->exec(schema); - db->exec("PRAGMA user_version = 5"); + db->exec("PRAGMA user_version = 6"); } catch (...) { Log::Error(Event::Database, "Unexpected error creating database schema: %s", util::toString(std::current_exception()).c_str()); throw; @@ -126,6 +127,14 @@ void OfflineDatabase::migrateToVersion5() { db->exec("PRAGMA user_version = 5"); } +void OfflineDatabase::migrateToVersion6() { + mapbox::sqlite::Transaction transaction(*db); + db->exec("ALTER TABLE resources ADD COLUMN must_revalidate INTEGER NOT NULL DEFAULT 0"); + db->exec("ALTER TABLE tiles ADD COLUMN must_revalidate INTEGER NOT NULL DEFAULT 0"); + db->exec("PRAGMA user_version = 6"); + transaction.commit(); +} + OfflineDatabase::Statement OfflineDatabase::getStatement(const char * sql) { auto it = statements.find(sql); @@ -188,11 +197,11 @@ std::pair<bool, uint64_t> OfflineDatabase::putInternal(const Resource& resource, if (resource.kind == Resource::Kind::Tile) { assert(resource.tileData); inserted = putTile(*resource.tileData, response, - compressed ? compressedData : *response.data, + compressed ? compressedData : response.data ? *response.data : "", compressed); } else { inserted = putResource(resource, response, - compressed ? compressedData : *response.data, + compressed ? compressedData : response.data ? *response.data : "", compressed); } @@ -211,8 +220,8 @@ optional<std::pair<Response, uint64_t>> OfflineDatabase::getResource(const Resou // clang-format off Statement stmt = getStatement( - // 0 1 2 3 4 - "SELECT etag, expires, modified, data, compressed " + // 0 1 2 3 4 5 + "SELECT etag, expires, must_revalidate, modified, data, compressed " "FROM resources " "WHERE url = ?"); // clang-format on @@ -226,14 +235,15 @@ optional<std::pair<Response, uint64_t>> OfflineDatabase::getResource(const Resou Response response; uint64_t size = 0; - response.etag = stmt->get<optional<std::string>>(0); - response.expires = stmt->get<optional<Timestamp>>(1); - response.modified = stmt->get<optional<Timestamp>>(2); + response.etag = stmt->get<optional<std::string>>(0); + response.expires = stmt->get<optional<Timestamp>>(1); + response.mustRevalidate = stmt->get<bool>(2); + response.modified = stmt->get<optional<Timestamp>>(3); - optional<std::string> data = stmt->get<optional<std::string>>(3); + optional<std::string> data = stmt->get<optional<std::string>>(4); if (!data) { response.noContent = true; - } else if (stmt->get<int>(4)) { + } else if (stmt->get<bool>(5)) { response.data = std::make_shared<std::string>(util::decompress(*data)); size = data->length(); } else { @@ -265,14 +275,16 @@ bool OfflineDatabase::putResource(const Resource& resource, // clang-format off Statement update = getStatement( "UPDATE resources " - "SET accessed = ?1, " - " expires = ?2 " - "WHERE url = ?3 "); + "SET accessed = ?1, " + " expires = ?2, " + " must_revalidate = ?3 " + "WHERE url = ?4 "); // clang-format on update->bind(1, util::now()); update->bind(2, response.expires); - update->bind(3, resource.url); + update->bind(3, response.mustRevalidate); + update->bind(4, resource.url); update->run(); return false; } @@ -286,29 +298,31 @@ bool OfflineDatabase::putResource(const Resource& resource, // clang-format off Statement update = getStatement( "UPDATE resources " - "SET kind = ?1, " - " etag = ?2, " - " expires = ?3, " - " modified = ?4, " - " accessed = ?5, " - " data = ?6, " - " compressed = ?7 " - "WHERE url = ?8 "); + "SET kind = ?1, " + " etag = ?2, " + " expires = ?3, " + " must_revalidate = ?4, " + " modified = ?5, " + " accessed = ?6, " + " data = ?7, " + " compressed = ?8 " + "WHERE url = ?9 "); // clang-format on update->bind(1, int(resource.kind)); update->bind(2, response.etag); update->bind(3, response.expires); - update->bind(4, response.modified); - update->bind(5, util::now()); - update->bind(8, resource.url); + update->bind(4, response.mustRevalidate); + update->bind(5, response.modified); + update->bind(6, util::now()); + update->bind(9, resource.url); if (response.noContent) { - update->bind(6, nullptr); - update->bind(7, false); + update->bind(7, nullptr); + update->bind(8, false); } else { - update->bindBlob(6, data.data(), data.size(), false); - update->bind(7, compressed); + update->bindBlob(7, data.data(), data.size(), false); + update->bind(8, compressed); } update->run(); @@ -319,23 +333,24 @@ bool OfflineDatabase::putResource(const Resource& resource, // clang-format off Statement insert = getStatement( - "INSERT INTO resources (url, kind, etag, expires, modified, accessed, data, compressed) " - "VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8) "); + "INSERT INTO resources (url, kind, etag, expires, must_revalidate, modified, accessed, data, compressed) " + "VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9) "); // clang-format on insert->bind(1, resource.url); insert->bind(2, int(resource.kind)); insert->bind(3, response.etag); insert->bind(4, response.expires); - insert->bind(5, response.modified); - insert->bind(6, util::now()); + insert->bind(5, response.mustRevalidate); + insert->bind(6, response.modified); + insert->bind(7, util::now()); if (response.noContent) { - insert->bind(7, nullptr); - insert->bind(8, false); + insert->bind(8, nullptr); + insert->bind(9, false); } else { - insert->bindBlob(7, data.data(), data.size(), false); - insert->bind(8, compressed); + insert->bindBlob(8, data.data(), data.size(), false); + insert->bind(9, compressed); } insert->run(); @@ -366,8 +381,8 @@ optional<std::pair<Response, uint64_t>> OfflineDatabase::getTile(const Resource: // clang-format off Statement stmt = getStatement( - // 0 1 2 3 4 - "SELECT etag, expires, modified, data, compressed " + // 0 1 2, 3, 4, 5 + "SELECT etag, expires, must_revalidate, modified, data, compressed " "FROM tiles " "WHERE url_template = ?1 " " AND pixel_ratio = ?2 " @@ -389,14 +404,15 @@ optional<std::pair<Response, uint64_t>> OfflineDatabase::getTile(const Resource: Response response; uint64_t size = 0; - response.etag = stmt->get<optional<std::string>>(0); - response.expires = stmt->get<optional<Timestamp>>(1); - response.modified = stmt->get<optional<Timestamp>>(2); + response.etag = stmt->get<optional<std::string>>(0); + response.expires = stmt->get<optional<Timestamp>>(1); + response.mustRevalidate = stmt->get<bool>(2); + response.modified = stmt->get<optional<Timestamp>>(3); - optional<std::string> data = stmt->get<optional<std::string>>(3); + optional<std::string> data = stmt->get<optional<std::string>>(4); if (!data) { response.noContent = true; - } else if (stmt->get<int>(4)) { + } else if (stmt->get<bool>(5)) { response.data = std::make_shared<std::string>(util::decompress(*data)); size = data->length(); } else { @@ -440,22 +456,24 @@ bool OfflineDatabase::putTile(const Resource::TileData& tile, // clang-format off Statement update = getStatement( "UPDATE tiles " - "SET accessed = ?1, " - " expires = ?2 " - "WHERE url_template = ?3 " - " AND pixel_ratio = ?4 " - " AND x = ?5 " - " AND y = ?6 " - " AND z = ?7 "); + "SET accessed = ?1, " + " expires = ?2, " + " must_revalidate = ?3 " + "WHERE url_template = ?4 " + " AND pixel_ratio = ?5 " + " AND x = ?6 " + " AND y = ?7 " + " AND z = ?8 "); // clang-format on update->bind(1, util::now()); update->bind(2, response.expires); - update->bind(3, tile.urlTemplate); - update->bind(4, tile.pixelRatio); - update->bind(5, tile.x); - update->bind(6, tile.y); - update->bind(7, tile.z); + update->bind(3, response.mustRevalidate); + update->bind(4, tile.urlTemplate); + update->bind(5, tile.pixelRatio); + update->bind(6, tile.x); + update->bind(7, tile.y); + update->bind(8, tile.z); update->run(); return false; } @@ -469,35 +487,37 @@ bool OfflineDatabase::putTile(const Resource::TileData& tile, // clang-format off Statement update = getStatement( "UPDATE tiles " - "SET modified = ?1, " - " etag = ?2, " - " expires = ?3, " - " accessed = ?4, " - " data = ?5, " - " compressed = ?6 " - "WHERE url_template = ?7 " - " AND pixel_ratio = ?8 " - " AND x = ?9 " - " AND y = ?10 " - " AND z = ?11 "); + "SET modified = ?1, " + " etag = ?2, " + " expires = ?3, " + " must_revalidate = ?4, " + " accessed = ?5, " + " data = ?6, " + " compressed = ?7 " + "WHERE url_template = ?8 " + " AND pixel_ratio = ?9 " + " AND x = ?10 " + " AND y = ?11 " + " AND z = ?12 "); // clang-format on update->bind(1, response.modified); update->bind(2, response.etag); update->bind(3, response.expires); - update->bind(4, util::now()); - update->bind(7, tile.urlTemplate); - update->bind(8, tile.pixelRatio); - update->bind(9, tile.x); - update->bind(10, tile.y); - update->bind(11, tile.z); + update->bind(4, response.mustRevalidate); + update->bind(5, util::now()); + update->bind(8, tile.urlTemplate); + update->bind(9, tile.pixelRatio); + update->bind(10, tile.x); + update->bind(11, tile.y); + update->bind(12, tile.z); if (response.noContent) { - update->bind(5, nullptr); - update->bind(6, false); + update->bind(6, nullptr); + update->bind(7, false); } else { - update->bindBlob(5, data.data(), data.size(), false); - update->bind(6, compressed); + update->bindBlob(6, data.data(), data.size(), false); + update->bind(7, compressed); } update->run(); @@ -508,8 +528,8 @@ bool OfflineDatabase::putTile(const Resource::TileData& tile, // clang-format off Statement insert = getStatement( - "INSERT INTO tiles (url_template, pixel_ratio, x, y, z, modified, etag, expires, accessed, data, compressed) " - "VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11) "); + "INSERT INTO tiles (url_template, pixel_ratio, x, y, z, modified, must_revalidate, etag, expires, accessed, data, compressed) " + "VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12)"); // clang-format on insert->bind(1, tile.urlTemplate); @@ -518,16 +538,17 @@ bool OfflineDatabase::putTile(const Resource::TileData& tile, insert->bind(4, tile.y); insert->bind(5, tile.z); insert->bind(6, response.modified); - insert->bind(7, response.etag); - insert->bind(8, response.expires); - insert->bind(9, util::now()); + insert->bind(7, response.mustRevalidate); + insert->bind(8, response.etag); + insert->bind(9, response.expires); + insert->bind(10, util::now()); if (response.noContent) { - insert->bind(10, nullptr); - insert->bind(11, false); + insert->bind(11, nullptr); + insert->bind(12, false); } else { - insert->bindBlob(10, data.data(), data.size(), false); - insert->bind(11, compressed); + insert->bindBlob(11, data.data(), data.size(), false); + insert->bind(12, compressed); } insert->run(); diff --git a/platform/default/mbgl/storage/offline_database.hpp b/platform/default/mbgl/storage/offline_database.hpp index 57ffcee4eb..91b544a9e0 100644 --- a/platform/default/mbgl/storage/offline_database.hpp +++ b/platform/default/mbgl/storage/offline_database.hpp @@ -64,6 +64,7 @@ private: void removeExisting(); void migrateToVersion3(); void migrateToVersion5(); + void migrateToVersion6(); class Statement { public: diff --git a/platform/default/mbgl/storage/offline_download.cpp b/platform/default/mbgl/storage/offline_download.cpp index 901f996a4f..ff61114888 100644 --- a/platform/default/mbgl/storage/offline_download.cpp +++ b/platform/default/mbgl/storage/offline_download.cpp @@ -5,8 +5,10 @@ #include <mbgl/storage/response.hpp> #include <mbgl/storage/http_file_source.hpp> #include <mbgl/style/parser.hpp> -#include <mbgl/style/sources/geojson_source_impl.hpp> -#include <mbgl/style/tile_source_impl.hpp> +#include <mbgl/style/sources/vector_source.hpp> +#include <mbgl/style/sources/raster_source.hpp> +#include <mbgl/style/sources/geojson_source.hpp> +#include <mbgl/style/sources/image_source.hpp> #include <mbgl/style/conversion/json.hpp> #include <mbgl/style/conversion/tileset.hpp> #include <mbgl/text/glyph.hpp> @@ -19,6 +21,8 @@ namespace mbgl { +using namespace style; + OfflineDownload::OfflineDownload(int64_t id_, OfflineRegionDefinition&& definition_, OfflineDatabase& offlineDatabase_, @@ -71,40 +75,53 @@ OfflineRegionStatus OfflineDownload::getStatus() const { result.requiredResourceCountIsPrecise = true; for (const auto& source : parser.sources) { - SourceType type = source->baseImpl->type; - - switch (type) { - case SourceType::Vector: - case SourceType::Raster: { - style::TileSourceImpl* tileSource = - static_cast<style::TileSourceImpl*>(source->baseImpl.get()); - const variant<std::string, Tileset>& urlOrTileset = tileSource->getURLOrTileset(); - const uint16_t tileSize = tileSource->getTileSize(); + SourceType type = source->getType(); + auto handleTiledSource = [&] (const variant<std::string, Tileset>& urlOrTileset, const uint16_t tileSize) { if (urlOrTileset.is<Tileset>()) { result.requiredResourceCount += - definition.tileCover(type, tileSize, urlOrTileset.get<Tileset>().zoomRange).size(); + definition.tileCount(type, tileSize, urlOrTileset.get<Tileset>().zoomRange); } else { result.requiredResourceCount += 1; - const std::string& url = urlOrTileset.get<std::string>(); + const auto& url = urlOrTileset.get<std::string>(); optional<Response> sourceResponse = offlineDatabase.get(Resource::source(url)); if (sourceResponse) { style::conversion::Error error; optional<Tileset> tileset = style::conversion::convertJSON<Tileset>(*sourceResponse->data, error); if (tileset) { result.requiredResourceCount += - definition.tileCover(type, tileSize, (*tileset).zoomRange).size(); + definition.tileCount(type, tileSize, (*tileset).zoomRange); } } else { result.requiredResourceCountIsPrecise = false; } } + }; + + switch (type) { + case SourceType::Vector: { + const auto& vectorSource = *source->as<VectorSource>(); + handleTiledSource(vectorSource.getURLOrTileset(), util::tileSize); + break; + } + + case SourceType::Raster: { + const auto& rasterSource = *source->as<RasterSource>(); + handleTiledSource(rasterSource.getURLOrTileset(), rasterSource.getTileSize()); break; } case SourceType::GeoJSON: { - style::GeoJSONSource* geojsonSource = source->as<style::GeoJSONSource>(); - if (geojsonSource->getURL()) { + const auto& geojsonSource = *source->as<GeoJSONSource>(); + if (geojsonSource.getURL()) { + result.requiredResourceCount += 1; + } + break; + } + + case SourceType::Image: { + const auto& imageSource = *source->as<ImageSource>(); + if (imageSource.getURL()) { result.requiredResourceCount += 1; } break; @@ -138,20 +155,13 @@ void OfflineDownload::activateDownload() { parser.parse(*styleResponse.data); for (const auto& source : parser.sources) { - SourceType type = source->baseImpl->type; - - switch (type) { - case SourceType::Vector: - case SourceType::Raster: { - const style::TileSourceImpl* tileSource = - static_cast<style::TileSourceImpl*>(source->baseImpl.get()); - const variant<std::string, Tileset>& urlOrTileset = tileSource->getURLOrTileset(); - const uint16_t tileSize = tileSource->getTileSize(); + SourceType type = source->getType(); + auto handleTiledSource = [&] (const variant<std::string, Tileset>& urlOrTileset, const uint16_t tileSize) { if (urlOrTileset.is<Tileset>()) { queueTiles(type, tileSize, urlOrTileset.get<Tileset>()); } else { - const std::string& url = urlOrTileset.get<std::string>(); + const auto& url = urlOrTileset.get<std::string>(); status.requiredResourceCountIsPrecise = false; status.requiredResourceCount++; requiredSourceURLs.insert(url); @@ -170,15 +180,34 @@ void OfflineDownload::activateDownload() { } }); } + }; + + switch (type) { + case SourceType::Vector: { + const auto& vectorSource = *source->as<VectorSource>(); + handleTiledSource(vectorSource.getURLOrTileset(), util::tileSize); + break; + } + + case SourceType::Raster: { + const auto& rasterSource = *source->as<RasterSource>(); + handleTiledSource(rasterSource.getURLOrTileset(), rasterSource.getTileSize()); break; } case SourceType::GeoJSON: { - style::GeoJSONSource::Impl* geojsonSource = - static_cast<style::GeoJSONSource::Impl*>(source->baseImpl.get()); + const auto& geojsonSource = *source->as<GeoJSONSource>(); + if (geojsonSource.getURL()) { + queueResource(Resource::source(*geojsonSource.getURL())); + } + break; + } - if (geojsonSource->getURL()) { - queueResource(Resource::source(*geojsonSource->getURL())); + case SourceType::Image: { + const auto& imageSource = *source->as<ImageSource>(); + auto imageUrl = imageSource.getURL(); + if (imageUrl && !imageUrl->empty()) { + queueResource(Resource::image(*imageUrl)); } break; } diff --git a/platform/default/mbgl/storage/offline_schema.cpp.include b/platform/default/mbgl/storage/offline_schema.cpp.include index a80c7677e6..41af81e55b 100644 --- a/platform/default/mbgl/storage/offline_schema.cpp.include +++ b/platform/default/mbgl/storage/offline_schema.cpp.include @@ -10,6 +10,7 @@ static const char * schema = " data BLOB,\n" " compressed INTEGER NOT NULL DEFAULT 0,\n" " accessed INTEGER NOT NULL,\n" +" must_revalidate INTEGER NOT NULL DEFAULT 0,\n" " UNIQUE (url)\n" ");\n" "CREATE TABLE tiles (\n" @@ -25,6 +26,7 @@ static const char * schema = " data BLOB,\n" " compressed INTEGER NOT NULL DEFAULT 0,\n" " accessed INTEGER NOT NULL,\n" +" must_revalidate INTEGER NOT NULL DEFAULT 0,\n" " UNIQUE (url_template, pixel_ratio, z, x, y)\n" ");\n" "CREATE TABLE regions (\n" diff --git a/platform/default/mbgl/storage/offline_schema.sql b/platform/default/mbgl/storage/offline_schema.sql index 9df8fa6a89..722b0e0451 100644 --- a/platform/default/mbgl/storage/offline_schema.sql +++ b/platform/default/mbgl/storage/offline_schema.sql @@ -8,6 +8,7 @@ CREATE TABLE resources ( -- Generic table for style, source, s data BLOB, compressed INTEGER NOT NULL DEFAULT 0, accessed INTEGER NOT NULL, + must_revalidate INTEGER NOT NULL DEFAULT 0, UNIQUE (url) ); @@ -24,6 +25,7 @@ CREATE TABLE tiles ( 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) ); diff --git a/platform/default/online_file_source.cpp b/platform/default/online_file_source.cpp index a72b6f4efc..d685109b95 100644 --- a/platform/default/online_file_source.cpp +++ b/platform/default/online_file_source.cpp @@ -2,16 +2,18 @@ #include <mbgl/storage/http_file_source.hpp> #include <mbgl/storage/network_status.hpp> +#include <mbgl/storage/resource_transform.hpp> #include <mbgl/storage/response.hpp> #include <mbgl/util/logging.hpp> +#include <mbgl/actor/mailbox.hpp> #include <mbgl/util/constants.hpp> -#include <mbgl/util/thread.hpp> #include <mbgl/util/mapbox.hpp> #include <mbgl/util/exception.hpp> #include <mbgl/util/chrono.hpp> #include <mbgl/util/async_task.hpp> #include <mbgl/util/noncopyable.hpp> +#include <mbgl/util/run_loop.hpp> #include <mbgl/util/timer.hpp> #include <mbgl/util/http_timeout.hpp> @@ -35,12 +37,17 @@ public: void schedule(optional<Timestamp> expires); void completed(Response); + void setTransformedURL(const std::string&& url); + ActorRef<OnlineFileRequest> actor(); + OnlineFileSource::Impl& impl; Resource resource; std::unique_ptr<AsyncRequest> request; util::Timer timer; Callback callback; + std::shared_ptr<Mailbox> mailbox; + // Counts the number of times a response was already expired when received. We're using // this to add a delay when making a new request so we don't keep retrying immediately // in case of a server serving expired tiles. @@ -66,15 +73,12 @@ public: void add(OnlineFileRequest* request) { allRequests.insert(request); if (resourceTransform) { - // When there's a Resource transform callback set, replace the resource with the + // Request the ResourceTransform actor a new url and replace the resource url with the // transformed one before proceeding to schedule the request. - request->request = - resourceTransform(request->resource.kind, std::move(request->resource.url), - [request](std::string&& url) { - request->request.release(); - request->resource.url = std::move(url); - request->schedule(); - }); + resourceTransform->invoke(&ResourceTransform::transform, request->resource.kind, + std::move(request->resource.url), [ref = request->actor()](const std::string&& url) mutable { + ref.invoke(&OnlineFileRequest::setTransformedURL, std::move(url)); + }); } else { request->schedule(); } @@ -113,13 +117,24 @@ public: } void activateRequest(OnlineFileRequest* request) { - activeRequests.insert(request); - request->request = httpFileSource.request(request->resource, [=] (Response response) { + auto callback = [=](Response response) { activeRequests.erase(request); - activatePendingRequest(); request->request.reset(); request->completed(response); - }); + activatePendingRequest(); + }; + + activeRequests.insert(request); + + if (online) { + request->request = httpFileSource.request(request->resource, callback); + } else { + Response response; + response.error = std::make_unique<Response::Error>(Response::Error::Reason::Connection, + "Online connectivity is disabled."); + callback(response); + } + assert(pendingRequestsMap.size() == pendingRequestsList.size()); } @@ -145,10 +160,15 @@ public: return activeRequests.find(request) != activeRequests.end(); } - void setResourceTransform(ResourceTransform&& transform) { + void setResourceTransform(optional<ActorRef<ResourceTransform>>&& transform) { resourceTransform = std::move(transform); } + void setOnlineStatus(const bool status) { + online = status; + networkIsReachableAgain(); + } + private: void networkIsReachableAgain() { for (auto& request : allRequests) { @@ -156,7 +176,7 @@ private: } } - ResourceTransform resourceTransform; + optional<ActorRef<ResourceTransform>> resourceTransform; /** * The lifetime of a request is: @@ -174,6 +194,7 @@ private: std::unordered_map<OnlineFileRequest*, std::list<OnlineFileRequest*>::iterator> pendingRequestsMap; std::unordered_set<OnlineFileRequest*> activeRequests; + bool online = true; HTTPFileSource httpFileSource; util::AsyncTask reachability { std::bind(&Impl::networkIsReachableAgain, this) }; }; @@ -189,6 +210,7 @@ std::unique_ptr<AsyncRequest> OnlineFileSource::request(const Resource& resource switch (resource.kind) { case Resource::Kind::Unknown: + case Resource::Kind::Image: break; case Resource::Kind::Style: @@ -216,7 +238,7 @@ std::unique_ptr<AsyncRequest> OnlineFileSource::request(const Resource& resource return std::make_unique<OnlineFileRequest>(std::move(res), std::move(callback), *impl); } -void OnlineFileSource::setResourceTransform(ResourceTransform&& transform) { +void OnlineFileSource::setResourceTransform(optional<ActorRef<ResourceTransform>>&& transform) { impl->setResourceTransform(std::move(transform)); } @@ -315,6 +337,14 @@ void OnlineFileRequest::completed(Response response) { resource.priorModified = response.modified; } + if (response.notModified && resource.priorData) { + // When the priorData field is set, it indicates that we had to revalidate the request and + // that the requestor hasn't gotten data yet. If we get a 304 response, this means that we + // have send the cached data to give the requestor a chance to actually obtain the data. + response.data = std::move(resource.priorData); + response.notModified = false; + } + bool isExpired = false; if (response.expires) { @@ -361,4 +391,25 @@ void OnlineFileRequest::networkIsReachableAgain() { } } +void OnlineFileRequest::setTransformedURL(const std::string&& url) { + resource.url = std::move(url); + schedule(); +} + +ActorRef<OnlineFileRequest> OnlineFileRequest::actor() { + if (!mailbox) { + // Lazy constructed because this can be costly and + // the ResourceTransform is not used by many apps. + mailbox = std::make_shared<Mailbox>(*Scheduler::GetCurrent()); + } + + return ActorRef<OnlineFileRequest>(*this, mailbox); +} + +// For testing only: + +void OnlineFileSource::setOnlineStatus(const bool status) { + impl->setOnlineStatus(status); +} + } // namespace mbgl diff --git a/platform/default/png_reader.cpp b/platform/default/png_reader.cpp index 29ef3058e1..4d4ee29d1f 100644 --- a/platform/default/png_reader.cpp +++ b/platform/default/png_reader.cpp @@ -40,7 +40,7 @@ static void user_warning_fn(png_structp, png_const_charp warning_msg) { } static void png_read_data(png_structp png_ptr, png_bytep data, png_size_t length) { - std::istream* fin = reinterpret_cast<std::istream*>(png_get_io_ptr(png_ptr)); + auto* fin = reinterpret_cast<std::istream*>(png_get_io_ptr(png_ptr)); fin->read(reinterpret_cast<char*>(data), length); std::streamsize read_count = fin->gcount(); if (read_count < 0 || static_cast<png_size_t>(read_count) != length) diff --git a/platform/default/run_loop.cpp b/platform/default/run_loop.cpp index 98d1badcb5..6375dba78e 100644 --- a/platform/default/run_loop.cpp +++ b/platform/default/run_loop.cpp @@ -1,6 +1,7 @@ #include <mbgl/util/run_loop.hpp> #include <mbgl/util/async_task.hpp> #include <mbgl/util/thread_local.hpp> +#include <mbgl/actor/scheduler.hpp> #include <uv.h> @@ -10,9 +11,6 @@ namespace { -using namespace mbgl::util; -static ThreadLocal<RunLoop>& current = *new ThreadLocal<RunLoop>; - void dummyCallback(uv_async_t*) {} } // namespace @@ -53,7 +51,8 @@ struct Watch { }; RunLoop* RunLoop::Get() { - return current.get(); + assert(static_cast<RunLoop*>(Scheduler::GetCurrent())); + return static_cast<RunLoop*>(Scheduler::GetCurrent()); } class RunLoop::Impl { @@ -98,12 +97,12 @@ RunLoop::RunLoop(Type type) : impl(std::make_unique<Impl>()) { impl->type = type; - current.set(this); + Scheduler::SetCurrent(this); impl->async = std::make_unique<AsyncTask>(std::bind(&RunLoop::process, this)); } RunLoop::~RunLoop() { - current.set(nullptr); + Scheduler::SetCurrent(nullptr); // Close the dummy handle that we have // just to keep the main loop alive. @@ -127,7 +126,7 @@ RunLoop::~RunLoop() { } LOOP_HANDLE RunLoop::getLoopHandle() { - return current.get()->impl->loop; + return Get()->impl->loop; } void RunLoop::push(std::shared_ptr<WorkTask> task) { diff --git a/platform/default/sqlite3.cpp b/platform/default/sqlite3.cpp index 304d5e9aba..2e08354fdf 100644 --- a/platform/default/sqlite3.cpp +++ b/platform/default/sqlite3.cpp @@ -107,8 +107,7 @@ Database &Database::operator=(Database &&other) { return *this; } -Database::~Database() { -} +Database::~Database() = default; void Database::setBusyTimeout(std::chrono::milliseconds timeout) { assert(impl); @@ -151,8 +150,7 @@ Statement &Statement::operator=(Statement &&other) { return *this; } -Statement::~Statement() { -} +Statement::~Statement() = default; template <> void Statement::bind(int offset, std::nullptr_t) { assert(impl); @@ -289,6 +287,11 @@ bool Statement::run() { } } +template <> bool Statement::get(int offset) { + assert(impl); + return sqlite3_column_int(impl->stmt, offset); +} + template <> int Statement::get(int offset) { assert(impl); return sqlite3_column_int(impl->stmt, offset); @@ -314,7 +317,7 @@ template <> std::string Statement::get(int offset) { template <> std::vector<uint8_t> Statement::get(int offset) { assert(impl); - const uint8_t* begin = reinterpret_cast<const uint8_t*>(sqlite3_column_blob(impl->stmt, offset)); + const auto* begin = reinterpret_cast<const uint8_t*>(sqlite3_column_blob(impl->stmt, offset)); const uint8_t* end = begin + sqlite3_column_bytes(impl->stmt, offset); return { begin, end }; } @@ -383,8 +386,8 @@ int64_t Statement::lastInsertRowId() const { uint64_t Statement::changes() const { assert(impl); - auto changes = impl->changes; - return (changes < 0 ? 0 : changes); + auto changes_ = impl->changes; + return (changes_ < 0 ? 0 : changes_); } Transaction::Transaction(Database& db_, Mode mode) diff --git a/platform/default/thread_local.cpp b/platform/default/thread_local.cpp new file mode 100644 index 0000000000..db70773c12 --- /dev/null +++ b/platform/default/thread_local.cpp @@ -0,0 +1,66 @@ +#include <mbgl/util/thread_local.hpp> + +#include <mbgl/renderer/backend_scope.hpp> +#include <mbgl/util/logging.hpp> +#include <mbgl/util/run_loop.hpp> + +#include <stdexcept> +#include <cassert> + +#include <pthread.h> + +namespace mbgl { +namespace util { + +template <class T> +class ThreadLocal<T>::Impl { +public: + pthread_key_t key; +}; + +template <class T> +ThreadLocal<T>::ThreadLocal() : impl(std::make_unique<Impl>()) { + int ret = pthread_key_create(&impl->key, [](void *) {}); + + if (ret) { + throw std::runtime_error("Failed to init local storage key."); + } +} + +template <class T> +ThreadLocal<T>::~ThreadLocal() { + // ThreadLocal will not take ownership + // of the pointer it is managing. The pointer + // needs to be explicitly cleared before we + // destroy this object. + assert(!get()); + + if (pthread_key_delete(impl->key)) { + Log::Error(Event::General, "Failed to delete local storage key."); + assert(false); + } +} + +template <class T> +T* ThreadLocal<T>::get() { + auto* ret = reinterpret_cast<T*>(pthread_getspecific(impl->key)); + if (!ret) { + return nullptr; + } + + return ret; +} + +template <class T> +void ThreadLocal<T>::set(T* ptr) { + if (pthread_setspecific(impl->key, ptr)) { + throw std::runtime_error("Failed to set local storage."); + } +} + +template class ThreadLocal<BackendScope>; +template class ThreadLocal<Scheduler>; +template class ThreadLocal<int>; // For unit tests + +} // namespace util +} // namespace mbgl diff --git a/platform/default/timer.cpp b/platform/default/timer.cpp index cd0e6da6aa..90a85bfc1f 100644 --- a/platform/default/timer.cpp +++ b/platform/default/timer.cpp @@ -10,7 +10,7 @@ namespace util { class Timer::Impl { public: Impl() : timer(new uv_timer_t) { - uv_loop_t* loop = reinterpret_cast<uv_loop_t*>(RunLoop::getLoopHandle()); + auto* loop = reinterpret_cast<uv_loop_t*>(RunLoop::getLoopHandle()); if (uv_timer_init(loop, timer) != 0) { throw std::runtime_error("Failed to initialize timer."); } diff --git a/platform/default/utf.cpp b/platform/default/utf.cpp index ba9678c91f..8fc44a9ed3 100644 --- a/platform/default/utf.cpp +++ b/platform/default/utf.cpp @@ -2,14 +2,24 @@ #include <memory> #include <locale> + +// GCC 4.9 compatibility +#if !defined(__GNUC__) || __GNUC__ >= 5 #include <codecvt> +#else +#include <boost/locale/encoding_utf.hpp> +#endif namespace mbgl { namespace util { std::u16string utf8_to_utf16::convert(std::string const& utf8) { +#if !defined(__GNUC__) || __GNUC__ >= 5 std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t> converter; return converter.from_bytes(utf8); +#else + return boost::locale::conv::utf_to_utf<char16_t>(utf8.c_str(), utf8.c_str() + utf8.size()); +#endif } } // namespace util |