diff options
Diffstat (limited to 'platform/default')
19 files changed, 601 insertions, 810 deletions
diff --git a/platform/default/application_root.cpp b/platform/default/application_root.cpp deleted file mode 100644 index 6669a049a4..0000000000 --- a/platform/default/application_root.cpp +++ /dev/null @@ -1,28 +0,0 @@ -#include <mbgl/platform/platform.hpp> - -#include <uv.h> -#include <libgen.h> - -namespace mbgl { -namespace platform { - -// Returns the path to the root folder of the application. -const std::string &applicationRoot() { - static const std::string root = []() -> std::string { - size_t max = 0; - std::string dir; - do { - // Gradually increase the length of the string in case the path was truncated. - max += 256; - dir.resize(max); - uv_exepath(const_cast<char *>(dir.data()), &max); - } while (max == dir.size()); - dir.resize(max - 1); - dir = dirname(const_cast<char *>(dir.c_str())); - return dir; - }(); - return root; -} - -} -} diff --git a/platform/default/asset_file_source.cpp b/platform/default/asset_file_source.cpp index 2573966c72..3a47f349fe 100644 --- a/platform/default/asset_file_source.cpp +++ b/platform/default/asset_file_source.cpp @@ -12,15 +12,6 @@ namespace mbgl { -class AssetFileRequest : public FileRequest { -public: - AssetFileRequest(std::unique_ptr<WorkRequest> workRequest_) - : workRequest(std::move(workRequest_)) { - } - - std::unique_ptr<WorkRequest> workRequest; -}; - class AssetFileSource::Impl { public: Impl(const std::string& root_) @@ -66,14 +57,14 @@ private: AssetFileSource::AssetFileSource(const std::string& root) : thread(std::make_unique<util::Thread<Impl>>( - util::ThreadContext{"AssetFileSource", util::ThreadType::Worker, util::ThreadPriority::Regular}, + util::ThreadContext{"AssetFileSource"}, root)) { } AssetFileSource::~AssetFileSource() = default; -std::unique_ptr<FileRequest> AssetFileSource::request(const Resource& resource, Callback callback) { - return std::make_unique<AssetFileRequest>(thread->invokeWithCallback(&Impl::request, callback, resource.url)); +std::unique_ptr<AsyncRequest> AssetFileSource::request(const Resource& resource, Callback callback) { + return thread->invokeWithCallback(&Impl::request, callback, resource.url); } } // namespace mbgl diff --git a/platform/default/default_file_source.cpp b/platform/default/default_file_source.cpp index 36839b2381..9d369210f8 100644 --- a/platform/default/default_file_source.cpp +++ b/platform/default/default_file_source.cpp @@ -25,29 +25,6 @@ namespace mbgl { class DefaultFileSource::Impl { public: - class Task { - public: - Task(Resource resource, FileSource::Callback callback, DefaultFileSource::Impl* impl) { - auto offlineResponse = impl->offlineDatabase.get(resource); - - Resource revalidation = resource; - - if (offlineResponse) { - revalidation.priorModified = offlineResponse->modified; - revalidation.priorExpires = offlineResponse->expires; - revalidation.priorEtag = offlineResponse->etag; - callback(*offlineResponse); - } - - onlineRequest = impl->onlineFileSource.request(revalidation, [=] (Response onlineResponse) { - impl->offlineDatabase.put(revalidation, onlineResponse); - callback(onlineResponse); - }); - } - - std::unique_ptr<FileRequest> onlineRequest; - }; - Impl(const std::string& cachePath, uint64_t maximumCacheSize) : offlineDatabase(cachePath, maximumCacheSize) { } @@ -104,11 +81,39 @@ public: getDownload(regionID).setState(state); } - void request(FileRequest* req, Resource resource, Callback callback) { - tasks[req] = std::make_unique<Task>(resource, callback, this); + 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"); + } + + if (offlineResponse) { + revalidation.priorModified = offlineResponse->modified; + revalidation.priorExpires = offlineResponse->expires; + revalidation.priorEtag = offlineResponse->etag; + callback(*offlineResponse); + } + } + + if (resource.necessity == Resource::Required) { + tasks[req] = onlineFileSource.request(revalidation, [=] (Response onlineResponse) { + this->offlineDatabase.put(revalidation, onlineResponse); + callback(onlineResponse); + }); + } } - void cancel(FileRequest* req) { + void cancel(AsyncRequest* req) { tasks.erase(req); } @@ -132,14 +137,14 @@ private: OfflineDatabase offlineDatabase; OnlineFileSource onlineFileSource; - std::unordered_map<FileRequest*, std::unique_ptr<Task>> tasks; + std::unordered_map<AsyncRequest*, std::unique_ptr<AsyncRequest>> tasks; std::unordered_map<int64_t, std::unique_ptr<OfflineDownload>> downloads; }; DefaultFileSource::DefaultFileSource(const std::string& cachePath, const std::string& assetRoot, uint64_t maximumCacheSize) - : thread(std::make_unique<util::Thread<Impl>>(util::ThreadContext{"DefaultFileSource", util::ThreadType::Unknown, util::ThreadPriority::Low}, + : thread(std::make_unique<util::Thread<Impl>>(util::ThreadContext{"DefaultFileSource", util::ThreadPriority::Low}, cachePath, maximumCacheSize)), assetFileSource(std::make_unique<AssetFileSource>(assetRoot)) { } @@ -154,20 +159,20 @@ std::string DefaultFileSource::getAccessToken() const { return thread->invokeSync<std::string>(&Impl::getAccessToken); } -std::unique_ptr<FileRequest> DefaultFileSource::request(const Resource& resource, Callback callback) { - class DefaultFileRequest : public FileRequest { +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, callback_, this, resource_)) { } - ~DefaultFileRequest() { + ~DefaultFileRequest() override { thread.invoke(&DefaultFileSource::Impl::cancel, this); } util::Thread<DefaultFileSource::Impl>& thread; - std::unique_ptr<WorkRequest> workRequest; + std::unique_ptr<AsyncRequest> workRequest; }; if (isAssetURL(resource.url)) { diff --git a/platform/default/glfw_view.cpp b/platform/default/glfw_view.cpp index 790b0a4dd7..67e9748a70 100644 --- a/platform/default/glfw_view.cpp +++ b/platform/default/glfw_view.cpp @@ -1,7 +1,7 @@ #include <mbgl/platform/default/glfw_view.hpp> -#include <mbgl/annotation/point_annotation.hpp> -#include <mbgl/annotation/shape_annotation.hpp> +#include <mbgl/annotation/annotation.hpp> #include <mbgl/sprite/sprite_image.hpp> +#include <mbgl/style/transition_options.hpp> #include <mbgl/gl/gl.hpp> #include <mbgl/gl/gl_values.hpp> #include <mbgl/gl/gl_helper.hpp> @@ -9,6 +9,7 @@ #include <mbgl/platform/platform.hpp> #include <mbgl/util/string.hpp> #include <mbgl/util/chrono.hpp> +#include <mbgl/map/camera.hpp> #include <cassert> #include <cstdlib> @@ -94,6 +95,8 @@ GLFWView::GLFWView(bool fullscreen_, bool benchmark_) printf("- Press `N` to reset north\n"); printf("- Press `R` to toggle any available `night` style class\n"); printf("- Press `Z` to cycle through north orientations\n"); + printf("- Prezz `X` to cycle through the viewport modes\n"); + printf("- Press `A` to cycle through Mapbox offices in the world + dateline monument\n"); printf("\n"); printf("- Press `1` through `6` to add increasing numbers of point annotations for testing\n"); printf("- Press `7` through `0` to add increasing numbers of shape annotations for testing\n"); @@ -137,20 +140,17 @@ void GLFWView::onKey(GLFWwindow *window, int key, int /*scancode*/, int action, if (!mods) view->map->resetPosition(); break; - case GLFW_KEY_C: - view->toggleClipMasks(); - break; case GLFW_KEY_S: if (view->changeStyleCallback) view->changeStyleCallback(); break; case GLFW_KEY_R: if (!mods) { - view->map->setDefaultTransitionDuration(mbgl::Milliseconds(300)); + static const mbgl::style::TransitionOptions transition { { mbgl::Milliseconds(300) } }; if (view->map->hasClass("night")) { - view->map->removeClass("night"); + view->map->removeClass("night", transition); } else { - view->map->addClass("night"); + view->map->addClass("night", transition); } } break; @@ -167,6 +167,26 @@ void GLFWView::onKey(GLFWwindow *window, int key, int /*scancode*/, int action, case GLFW_KEY_P: { view->addRandomCustomPointAnnotations(1); } break; + case GLFW_KEY_A: { + // XXX Fix precision loss in flyTo: + // https://github.com/mapbox/mapbox-gl-native/issues/4298 + static const std::vector<mbgl::LatLng> places = { + mbgl::LatLng { -16.796665, -179.999983 }, // Dateline monument + mbgl::LatLng { 12.9810542, 77.6345551 }, // Mapbox Bengaluru, India + mbgl::LatLng { -13.15607,-74.21773 }, // Mapbox Peru + mbgl::LatLng { 37.77572, -122.4158818 }, // Mapbox SF, USA + mbgl::LatLng { 38.91318,-77.03255 }, // Mapbox DC, USA + }; + static size_t nextPlace = 0; + mbgl::CameraOptions cameraOptions; + cameraOptions.center = places[nextPlace++]; + cameraOptions.zoom = 20; + cameraOptions.pitch = 30; + + mbgl::AnimationOptions animationOptions(mbgl::Seconds(10)); + view->map->flyTo(cameraOptions, animationOptions); + nextPlace = nextPlace % places.size(); + } break; } } @@ -187,14 +207,11 @@ void GLFWView::onKey(GLFWwindow *window, int key, int /*scancode*/, int action, } } -mbgl::LatLng GLFWView::makeRandomPoint() const { - const auto nw = map->latLngForPixel({ 0, 0 }); - const auto se = map->latLngForPixel({ double(width), double(height) }); - - const double lon = nw.longitude + (se.longitude - nw.longitude) * (double(std::rand()) / RAND_MAX); - const double lat = se.latitude + (nw.latitude - se.latitude) * (double(std::rand()) / RAND_MAX); - - return { lat, lon }; +mbgl::Point<double> GLFWView::makeRandomPoint() const { + const double x = width * double(std::rand()) / RAND_MAX; + const double y = height * double(std::rand()) / RAND_MAX; + mbgl::LatLng latLng = map->latLngForPixel({ x, y }); + return { latLng.longitude, latLng.latitude }; } std::shared_ptr<const mbgl::SpriteImage> @@ -236,65 +253,35 @@ void GLFWView::nextOrientation() { } } -void GLFWView::toggleClipMasks() { - showClipMasks = !showClipMasks; - map->update(mbgl::Update::Repaint); -} - void GLFWView::addRandomCustomPointAnnotations(int count) { - std::vector<mbgl::PointAnnotation> points; - for (int i = 0; i < count; i++) { static int spriteID = 1; const auto name = std::string{ "marker-" } + mbgl::util::toString(spriteID++); map->addAnnotationIcon(name, makeSpriteImage(22, 22, 1)); spriteIDs.push_back(name); - points.emplace_back(makeRandomPoint(), name); + annotationIDs.push_back(map->addAnnotation(mbgl::SymbolAnnotation { makeRandomPoint(), name })); } - - auto newIDs = map->addPointAnnotations(points); - annotationIDs.insert(annotationIDs.end(), newIDs.begin(), newIDs.end()); } void GLFWView::addRandomPointAnnotations(int count) { - std::vector<mbgl::PointAnnotation> points; - for (int i = 0; i < count; i++) { - points.emplace_back(makeRandomPoint(), "default_marker"); + annotationIDs.push_back(map->addAnnotation(mbgl::SymbolAnnotation { makeRandomPoint(), "default_marker" })); } - - auto newIDs = map->addPointAnnotations(points); - annotationIDs.insert(annotationIDs.end(), newIDs.begin(), newIDs.end()); } void GLFWView::addRandomShapeAnnotations(int count) { - std::vector<mbgl::ShapeAnnotation> shapes; - - mbgl::FillAnnotationProperties properties; - properties.opacity = .1; - for (int i = 0; i < count; i++) { - mbgl::AnnotationSegment triangle; - triangle.push_back(makeRandomPoint()); - triangle.push_back(makeRandomPoint()); - triangle.push_back(makeRandomPoint()); - - mbgl::AnnotationSegments segments; - segments.push_back(triangle); - - shapes.emplace_back(segments, properties); + mbgl::Polygon<double> triangle; + triangle.push_back({ makeRandomPoint(), makeRandomPoint(), makeRandomPoint() }); + annotationIDs.push_back(map->addAnnotation(mbgl::FillAnnotation { triangle, .1 })); } - - auto newIDs = map->addShapeAnnotations(shapes); - annotationIDs.insert(annotationIDs.end(), newIDs.begin(), newIDs.end()); } void GLFWView::clearAnnotations() { - if (annotationIDs.empty()) { - return; + for (const auto& id : annotationIDs) { + map->removeAnnotation(id); } - map->removeAnnotations(annotationIDs); annotationIDs.clear(); } @@ -326,7 +313,7 @@ void GLFWView::onScroll(GLFWwindow *window, double /*xOffset*/, double yOffset) scale = 1.0 / scale; } - view->map->scaleBy(scale, { view->lastX, view->lastY }); + view->map->scaleBy(scale, mbgl::ScreenCoordinate { view->lastX, view->lastY }); } void GLFWView::onWindowResize(GLFWwindow *window, int width, int height) { @@ -363,9 +350,9 @@ void GLFWView::onMouseClick(GLFWwindow *window, int button, int action, int modi double now = glfwGetTime(); if (now - view->lastClick < 0.4 /* ms */) { if (modifiers & GLFW_MOD_SHIFT) { - view->map->scaleBy(0.5, { view->lastX, view->lastY }, mbgl::Milliseconds(500)); + view->map->scaleBy(0.5, mbgl::ScreenCoordinate { view->lastX, view->lastY }, mbgl::Milliseconds(500)); } else { - view->map->scaleBy(2.0, { view->lastX, view->lastY }, mbgl::Milliseconds(500)); + view->map->scaleBy(2.0, mbgl::ScreenCoordinate { view->lastX, view->lastY }, mbgl::Milliseconds(500)); } } view->lastClick = now; @@ -396,18 +383,39 @@ void GLFWView::onMouseMove(GLFWwindow *window, double x, double y) { } void GLFWView::run() { - while (!glfwWindowShouldClose(window)) { - glfwWaitEvents(); - const bool dirty = !clean.test_and_set(); + auto callback = [&] { + if (glfwWindowShouldClose(window)) { + runLoop.stop(); + return; + } + + glfwPollEvents(); + if (dirty) { const double started = glfwGetTime(); - map->renderSync(); + + glfwMakeContextCurrent(window); + glViewport(0, 0, fbWidth, fbHeight); + + map->render(); + + glfwSwapBuffers(window); + report(1000 * (glfwGetTime() - started)); if (benchmark) { map->update(mbgl::Update::Repaint); } + + dirty = false; } - } + }; + + frameTick.start(mbgl::Duration::zero(), mbgl::Milliseconds(1000 / 60), callback); +#if defined(__APPLE__) + while (!glfwWindowShouldClose(window)) runLoop.run(); +#else + runLoop.run(); +#endif } float GLFWView::getPixelRatio() const { @@ -430,70 +438,11 @@ void GLFWView::deactivate() { glfwMakeContextCurrent(nullptr); } -void GLFWView::notify() { - glfwPostEmptyEvent(); -} - void GLFWView::invalidate() { - clean.clear(); + dirty = true; glfwPostEmptyEvent(); } -void GLFWView::beforeRender() { - // This is called from the map thread but `width` and `height` - // can be accessed with no race because the main thread is blocked - // when we render. This will be more straightforward when we move - // rendering to the main thread. - glViewport(0, 0, fbWidth, fbHeight); -} - -void GLFWView::afterRender() { - if (showClipMasks) { - renderClipMasks(); - } - - glfwSwapBuffers(window); -} - -void GLFWView::renderClipMasks() { - // Read the stencil buffer - auto pixels = std::make_unique<uint8_t[]>(fbWidth * fbHeight); - glReadPixels(0, // GLint x - 0, // GLint y - fbWidth, // GLsizei width - fbHeight, // GLsizei height - GL_STENCIL_INDEX, // GLenum format - GL_UNSIGNED_BYTE, // GLenum type - pixels.get() // GLvoid * data - ); - - // Scale the Stencil buffer to cover the entire color space. - auto it = pixels.get(); - auto end = it + fbWidth * fbHeight; - const auto factor = 255.0f / *std::max_element(it, end); - for (; it != end; ++it) { - *it *= factor; - } - - using namespace mbgl::gl; - Preserve<PixelZoom> pixelZoom; - Preserve<RasterPos> rasterPos; - Preserve<StencilTest> stencilTest; - Preserve<DepthTest> depthTest; - Preserve<Program> program; - Preserve<ColorMask> colorMask; - - MBGL_CHECK_ERROR(glPixelZoom(1.0f, 1.0f)); - MBGL_CHECK_ERROR(glRasterPos2f(-1.0f, -1.0f)); - MBGL_CHECK_ERROR(glDisable(GL_STENCIL_TEST)); - MBGL_CHECK_ERROR(glDisable(GL_DEPTH_TEST)); - MBGL_CHECK_ERROR(glUseProgram(0)); - MBGL_CHECK_ERROR(glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE)); - MBGL_CHECK_ERROR(glWindowPos2i(0, 0)); - - MBGL_CHECK_ERROR(glDrawPixels(fbWidth, fbHeight, GL_LUMINANCE, GL_UNSIGNED_BYTE, pixels.get())); -} - void GLFWView::report(float duration) { frames++; frameTime += duration; diff --git a/platform/default/headless_display.cpp b/platform/default/headless_display.cpp index fccc91c727..8b9f3fe04b 100644 --- a/platform/default/headless_display.cpp +++ b/platform/default/headless_display.cpp @@ -16,7 +16,6 @@ HeadlessDisplay::HeadlessDisplay() { CGLPixelFormatAttribute attributes[] = { kCGLPFAOpenGLProfile, static_cast<CGLPixelFormatAttribute>(kCGLOGLPVersion_Legacy), - kCGLPFAAccelerated, static_cast<CGLPixelFormatAttribute>(0) }; diff --git a/platform/default/headless_view.cpp b/platform/default/headless_view.cpp index 7fd3c37f3a..13ea78a709 100644 --- a/platform/default/headless_view.cpp +++ b/platform/default/headless_view.cpp @@ -1,163 +1,34 @@ #include <mbgl/platform/default/headless_view.hpp> #include <mbgl/platform/default/headless_display.hpp> -#include <mbgl/platform/log.hpp> -#include <stdexcept> -#include <sstream> -#include <string> -#include <cstring> #include <cassert> -#include <utility> - -#ifdef MBGL_USE_CGL -#include <CoreFoundation/CoreFoundation.h> -#elif MBGL_USE_GLX -#include <GL/glx.h> -#endif +#include <cstring> namespace mbgl { HeadlessView::HeadlessView(float pixelRatio_, uint16_t width, uint16_t height) - : display(std::make_shared<HeadlessDisplay>()), pixelRatio(pixelRatio_) { - resize(width, height); + : display(std::make_shared<HeadlessDisplay>()) + , pixelRatio(pixelRatio_) + , dimensions({{ width, height }}) + , needsResize(true) { } HeadlessView::HeadlessView(std::shared_ptr<HeadlessDisplay> display_, float pixelRatio_, uint16_t width, uint16_t height) - : display(std::move(display_)), pixelRatio(pixelRatio_) { - resize(width, height); + : display(std::move(display_)) + , pixelRatio(pixelRatio_) + , dimensions({{ width, height }}) + , needsResize(true) { } -void HeadlessView::loadExtensions() { - if (extensionsLoaded) { - return; - } - -#ifdef MBGL_USE_CGL - gl::InitializeExtensions([](const char * name) { - static CFBundleRef framework = CFBundleGetBundleWithIdentifier(CFSTR("com.apple.opengl")); - if (!framework) { - throw std::runtime_error("Failed to load OpenGL framework."); - } - - CFStringRef str = CFStringCreateWithCString(kCFAllocatorDefault, name, kCFStringEncodingASCII); - void* symbol = CFBundleGetFunctionPointerForName(framework, str); - CFRelease(str); - - return reinterpret_cast<gl::glProc>(symbol); - }); -#endif - -#ifdef MBGL_USE_GLX - gl::InitializeExtensions([](const char * name) { - return glXGetProcAddress(reinterpret_cast<const GLubyte *>(name)); - }); -#endif - - extensionsLoaded = true; -} - -void HeadlessView::createContext() { - if (!display) { - throw std::runtime_error("Display is not set"); - } - -#if MBGL_USE_CGL - CGLError error = CGLCreateContext(display->pixelFormat, NULL, &glContext); - if (error != kCGLNoError) { - throw std::runtime_error(std::string("Error creating GL context object:") + CGLErrorString(error) + "\n"); - } - - error = CGLEnable(glContext, kCGLCEMPEngine); - if (error != kCGLNoError) { - throw std::runtime_error(std::string("Error enabling OpenGL multithreading:") + CGLErrorString(error) + "\n"); - } -#endif - -#if MBGL_USE_GLX - xDisplay = display->xDisplay; - fbConfigs = display->fbConfigs; - - if (!glContext) { - // Try to create a legacy context - glContext = glXCreateNewContext(xDisplay, fbConfigs[0], GLX_RGBA_TYPE, None, True); - if (glContext) { - if (!glXIsDirect(xDisplay, glContext)) { - Log::Error(Event::OpenGL, "failed to create direct OpenGL Legacy context"); - glXDestroyContext(xDisplay, glContext); - glContext = 0; - } - } - } - - if (glContext == 0) { - throw std::runtime_error("Error creating GL context object."); - } - - // Create a dummy pbuffer. We will render to framebuffers anyway, but we need a pbuffer to - // activate the context. - int pbufferAttributes[] = { - GLX_PBUFFER_WIDTH, 8, - GLX_PBUFFER_HEIGHT, 8, - None - }; - glxPbuffer = glXCreatePbuffer(xDisplay, fbConfigs[0], pbufferAttributes); -#endif -} - -bool HeadlessView::isActive() const { - return std::this_thread::get_id() == thread; -} - -void HeadlessView::resizeFramebuffer() { - assert(isActive()); - - if (!needsResize) return; - +HeadlessView::~HeadlessView() { + activate(); clearBuffers(); + deactivate(); - const unsigned int w = dimensions[0] * pixelRatio; - const unsigned int h = dimensions[1] * pixelRatio; - - // Create depth/stencil buffer - MBGL_CHECK_ERROR(glGenRenderbuffersEXT(1, &fboDepthStencil)); - MBGL_CHECK_ERROR(glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, fboDepthStencil)); - MBGL_CHECK_ERROR(glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_DEPTH24_STENCIL8_EXT, w, h)); - MBGL_CHECK_ERROR(glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, 0)); - - MBGL_CHECK_ERROR(glGenRenderbuffersEXT(1, &fboColor)); - MBGL_CHECK_ERROR(glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, fboColor)); - MBGL_CHECK_ERROR(glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_RGBA8, w, h)); - MBGL_CHECK_ERROR(glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, 0)); - - MBGL_CHECK_ERROR(glGenFramebuffersEXT(1, &fbo)); - MBGL_CHECK_ERROR(glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo)); - - MBGL_CHECK_ERROR(glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_RENDERBUFFER_EXT, fboColor)); - MBGL_CHECK_ERROR(glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER_EXT, fboDepthStencil)); - - GLenum status = MBGL_CHECK_ERROR(glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT)); - - if (status != GL_FRAMEBUFFER_COMPLETE_EXT) { - std::string error("Couldn't create framebuffer: "); - switch (status) { - case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_EXT: (error += "incomplete attachment"); break; - case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_EXT: error += "incomplete missing attachment"; break; - case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT: error += "incomplete dimensions"; break; - case GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT: error += "incomplete formats"; break; - case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER_EXT: error += "incomplete draw buffer"; break; - case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_EXT: error += "incomplete read buffer"; break; - case GL_FRAMEBUFFER_UNSUPPORTED: error += "unsupported"; break; - default: error += "other"; break; - } - throw std::runtime_error(error); - } - - MBGL_CHECK_ERROR(glViewport(0, 0, w, h)); - - needsResize = false; + destroyContext(); } void HeadlessView::resize(const uint16_t width, const uint16_t height) { @@ -170,7 +41,7 @@ void HeadlessView::resize(const uint16_t width, const uint16_t height) { } PremultipliedImage HeadlessView::readStillImage() { - assert(isActive()); + assert(active); const unsigned int w = dimensions[0] * pixelRatio; const unsigned int h = dimensions[1] * pixelRatio; @@ -178,7 +49,7 @@ PremultipliedImage HeadlessView::readStillImage() { PremultipliedImage image { w, h }; MBGL_CHECK_ERROR(glReadPixels(0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, image.data.get())); - const int stride = image.stride(); + const auto stride = image.stride(); auto tmp = std::make_unique<uint8_t[]>(stride); uint8_t* rgba = image.data.get(); for (int i = 0, j = h - 1; i < j; i++, j--) { @@ -190,50 +61,6 @@ PremultipliedImage HeadlessView::readStillImage() { return image; } -void HeadlessView::clearBuffers() { - assert(isActive()); - - MBGL_CHECK_ERROR(glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0)); - - if (fbo) { - MBGL_CHECK_ERROR(glDeleteFramebuffersEXT(1, &fbo)); - fbo = 0; - } - - if (fboColor) { - MBGL_CHECK_ERROR(glDeleteRenderbuffersEXT(1, &fboColor)); - fboColor = 0; - } - - if (fboDepthStencil) { - MBGL_CHECK_ERROR(glDeleteRenderbuffersEXT(1, &fboDepthStencil)); - fboDepthStencil = 0; - } -} - -HeadlessView::~HeadlessView() { - activate(); - clearBuffers(); - deactivate(); - -#if MBGL_USE_CGL - CGLDestroyContext(glContext); -#endif - -#if MBGL_USE_GLX - if (glxPbuffer) { - glXDestroyPbuffer(xDisplay, glxPbuffer); - glxPbuffer = 0; - } - - glXDestroyContext(xDisplay, glContext); -#endif -} - -void HeadlessView::notify() { - // no-op -} - float HeadlessView::getPixelRatio() const { return pixelRatio; } @@ -248,61 +75,36 @@ std::array<uint16_t, 2> HeadlessView::getFramebufferSize() const { } void HeadlessView::activate() { - if (thread != std::thread::id()) { - throw std::runtime_error("OpenGL context was already current"); - } - thread = std::this_thread::get_id(); + active = true; if (!glContext) { + if (!display) { + throw std::runtime_error("Display is not set"); + } createContext(); } -#if MBGL_USE_CGL - CGLError error = CGLSetCurrentContext(glContext); - if (error != kCGLNoError) { - throw std::runtime_error(std::string("Switching OpenGL context failed:") + CGLErrorString(error) + "\n"); - } -#endif + activateContext(); -#if MBGL_USE_GLX - if (!glXMakeContextCurrent(xDisplay, glxPbuffer, glxPbuffer, glContext)) { - throw std::runtime_error("Switching OpenGL context failed.\n"); + if (!extensionsLoaded) { + gl::InitializeExtensions(initializeExtension); + extensionsLoaded = true; } -#endif - loadExtensions(); + if (needsResize) { + clearBuffers(); + resizeFramebuffer(); + needsResize = false; + } } void HeadlessView::deactivate() { - if (thread == std::thread::id()) { - throw std::runtime_error("OpenGL context was not current"); - } - thread = std::thread::id(); - -#if MBGL_USE_CGL - CGLError error = CGLSetCurrentContext(nullptr); - if (error != kCGLNoError) { - throw std::runtime_error(std::string("Removing OpenGL context failed:") + CGLErrorString(error) + "\n"); - } -#endif - -#if MBGL_USE_GLX - if (!glXMakeContextCurrent(xDisplay, 0, 0, nullptr)) { - throw std::runtime_error("Removing OpenGL context failed.\n"); - } -#endif + deactivateContext(); + active = false; } void HeadlessView::invalidate() { - // no-op -} - -void HeadlessView::beforeRender() { - resizeFramebuffer(); -} - -void HeadlessView::afterRender() { - // no-op + assert(false); } } // namespace mbgl diff --git a/platform/default/headless_view_glx.cpp b/platform/default/headless_view_glx.cpp new file mode 100644 index 0000000000..3b719ab43a --- /dev/null +++ b/platform/default/headless_view_glx.cpp @@ -0,0 +1,128 @@ +#include <mbgl/platform/default/headless_view.hpp> +#include <mbgl/platform/default/headless_display.hpp> +#include <mbgl/platform/log.hpp> + +#include <cassert> + +#include <GL/glx.h> + +namespace mbgl { + +gl::glProc HeadlessView::initializeExtension(const char* name) { + return glXGetProcAddress(reinterpret_cast<const GLubyte*>(name)); +} + +void HeadlessView::createContext() { + xDisplay = display->xDisplay; + fbConfigs = display->fbConfigs; + + if (!glContext) { + // Try to create a legacy context + glContext = glXCreateNewContext(xDisplay, fbConfigs[0], GLX_RGBA_TYPE, None, True); + if (glContext) { + if (!glXIsDirect(xDisplay, glContext)) { + Log::Error(Event::OpenGL, "failed to create direct OpenGL Legacy context"); + glXDestroyContext(xDisplay, glContext); + glContext = 0; + } + } + } + + if (glContext == 0) { + throw std::runtime_error("Error creating GL context object."); + } + + // Create a dummy pbuffer. We will render to framebuffers anyway, but we need a pbuffer to + // activate the context. + int pbufferAttributes[] = { + GLX_PBUFFER_WIDTH, 8, + GLX_PBUFFER_HEIGHT, 8, + None + }; + glxPbuffer = glXCreatePbuffer(xDisplay, fbConfigs[0], pbufferAttributes); +} + +void HeadlessView::destroyContext() { + if (glxPbuffer) { + glXDestroyPbuffer(xDisplay, glxPbuffer); + glxPbuffer = 0; + } + + glXDestroyContext(xDisplay, glContext); +} + +void HeadlessView::resizeFramebuffer() { + const unsigned int w = dimensions[0] * pixelRatio; + const unsigned int h = dimensions[1] * pixelRatio; + + // Create depth/stencil buffer + MBGL_CHECK_ERROR(glGenRenderbuffersEXT(1, &fboDepthStencil)); + MBGL_CHECK_ERROR(glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, fboDepthStencil)); + MBGL_CHECK_ERROR(glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_DEPTH24_STENCIL8_EXT, w, h)); + MBGL_CHECK_ERROR(glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, 0)); + + MBGL_CHECK_ERROR(glGenRenderbuffersEXT(1, &fboColor)); + MBGL_CHECK_ERROR(glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, fboColor)); + MBGL_CHECK_ERROR(glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_RGBA8, w, h)); + MBGL_CHECK_ERROR(glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, 0)); + + MBGL_CHECK_ERROR(glGenFramebuffersEXT(1, &fbo)); + MBGL_CHECK_ERROR(glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo)); + + MBGL_CHECK_ERROR(glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_RENDERBUFFER_EXT, fboColor)); + MBGL_CHECK_ERROR(glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER_EXT, fboDepthStencil)); + + GLenum status = MBGL_CHECK_ERROR(glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT)); + + if (status != GL_FRAMEBUFFER_COMPLETE_EXT) { + std::string error("Couldn't create framebuffer: "); + switch (status) { + case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_EXT: (error += "incomplete attachment"); break; + case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_EXT: error += "incomplete missing attachment"; break; + case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT: error += "incomplete dimensions"; break; + case GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT: error += "incomplete formats"; break; + case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER_EXT: error += "incomplete draw buffer"; break; + case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_EXT: error += "incomplete read buffer"; break; + case GL_FRAMEBUFFER_UNSUPPORTED: error += "unsupported"; break; + default: error += "other"; break; + } + throw std::runtime_error(error); + } + + MBGL_CHECK_ERROR(glViewport(0, 0, w, h)); +} + +void HeadlessView::clearBuffers() { + assert(active); + + MBGL_CHECK_ERROR(glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0)); + + if (fbo) { + MBGL_CHECK_ERROR(glDeleteFramebuffersEXT(1, &fbo)); + fbo = 0; + } + + if (fboColor) { + MBGL_CHECK_ERROR(glDeleteRenderbuffersEXT(1, &fboColor)); + fboColor = 0; + } + + if (fboDepthStencil) { + MBGL_CHECK_ERROR(glDeleteRenderbuffersEXT(1, &fboDepthStencil)); + fboDepthStencil = 0; + } +} + +void HeadlessView::activateContext() { + if (!glXMakeContextCurrent(xDisplay, glxPbuffer, glxPbuffer, glContext)) { + throw std::runtime_error("Switching OpenGL context failed.\n"); + } +} + +void HeadlessView::deactivateContext() { + if (!glXMakeContextCurrent(xDisplay, 0, 0, nullptr)) { + throw std::runtime_error("Removing OpenGL context failed.\n"); + } +} + +} // namespace mbgl diff --git a/platform/default/http_request_curl.cpp b/platform/default/http_file_source.cpp index 58c574fee1..e83ecfbfc9 100644 --- a/platform/default/http_request_curl.cpp +++ b/platform/default/http_file_source.cpp @@ -1,5 +1,4 @@ -#include <mbgl/storage/http_context_base.hpp> -#include <mbgl/storage/http_request_base.hpp> +#include <mbgl/storage/http_file_source.hpp> #include <mbgl/storage/resource.hpp> #include <mbgl/storage/response.hpp> #include <mbgl/platform/log.hpp> @@ -9,28 +8,23 @@ #include <mbgl/util/string.hpp> #include <mbgl/util/timer.hpp> #include <mbgl/util/chrono.hpp> +#include <mbgl/util/http_header.hpp> #include <curl/curl.h> -#ifdef __ANDROID__ -#include <mbgl/android/jni.hpp> -#include <zip.h> -#include <openssl/ssl.h> -#endif - #include <queue> #include <map> #include <cassert> #include <cstring> #include <cstdio> -void handleError(CURLMcode code) { +static void handleError(CURLMcode code) { if (code != CURLM_OK) { throw std::runtime_error(std::string("CURL multi error: ") + curl_multi_strerror(code)); } } -void handleError(CURLcode code) { +static void handleError(CURLcode code) { if (code != CURLE_OK) { throw std::runtime_error(std::string("CURL easy error: ") + curl_easy_strerror(code)); } @@ -38,20 +32,14 @@ void handleError(CURLcode code) { namespace mbgl { -class HTTPCURLRequest; - -class HTTPCURLContext : public HTTPContextBase { - MBGL_STORE_THREAD(tid) - +class HTTPFileSource::Impl { public: - HTTPCURLContext(); - ~HTTPCURLContext(); - - HTTPRequestBase* createRequest(const Resource&, HTTPRequestBase::Callback) final; + Impl(); + ~Impl(); static int handleSocket(CURL *handle, curl_socket_t s, int action, void *userp, void *socketp); static int startTimeout(CURLM *multi, long timeout_ms, void *userp); - static void onTimeout(HTTPCURLContext *context); + static void onTimeout(HTTPFileSource::Impl *context); void perform(curl_socket_t s, util::RunLoop::Event event); CURL *getHandle(); @@ -73,14 +61,10 @@ public: std::queue<CURL *> handles; }; -class HTTPCURLRequest : public HTTPRequestBase { - MBGL_STORE_THREAD(tid) - +class HTTPRequest : public AsyncRequest { public: - HTTPCURLRequest(HTTPCURLContext*, const Resource&, Callback); - ~HTTPCURLRequest(); - - void cancel() final; + HTTPRequest(HTTPFileSource::Impl*, const Resource&, FileSource::Callback); + ~HTTPRequest(); void handleResult(CURLcode code); @@ -88,24 +72,21 @@ private: static size_t headerCallback(char *const buffer, const size_t size, const size_t nmemb, void *userp); static size_t writeCallback(void *const contents, const size_t size, const size_t nmemb, void *userp); - HTTPCURLContext *context = nullptr; + HTTPFileSource::Impl* context = nullptr; + Resource resource; + FileSource::Callback callback; // Will store the current response. std::shared_ptr<std::string> data; std::unique_ptr<Response> response; - // In case of revalidation requests, this will store the old response. - const std::shared_ptr<const Response> existingResponse; - CURL *handle = nullptr; curl_slist *headers = nullptr; - char error[CURL_ERROR_SIZE]; + char error[CURL_ERROR_SIZE] = { 0 }; }; -// ------------------------------------------------------------------------------------------------- - -HTTPCURLContext::HTTPCURLContext() { +HTTPFileSource::Impl::Impl() { if (curl_global_init(CURL_GLOBAL_ALL)) { throw std::runtime_error("Could not init cURL"); } @@ -119,7 +100,7 @@ HTTPCURLContext::HTTPCURLContext() { handleError(curl_multi_setopt(multi, CURLMOPT_TIMERDATA, this)); } -HTTPCURLContext::~HTTPCURLContext() { +HTTPFileSource::Impl::~Impl() { while (!handles.empty()) { curl_easy_cleanup(handles.front()); handles.pop(); @@ -134,11 +115,7 @@ HTTPCURLContext::~HTTPCURLContext() { timeout.stop(); } -HTTPRequestBase* HTTPCURLContext::createRequest(const Resource& resource, HTTPRequestBase::Callback callback) { - return new HTTPCURLRequest(this, resource, callback); -} - -CURL *HTTPCURLContext::getHandle() { +CURL *HTTPFileSource::Impl::getHandle() { if (!handles.empty()) { auto handle = handles.front(); handles.pop(); @@ -148,20 +125,19 @@ CURL *HTTPCURLContext::getHandle() { } } -void HTTPCURLContext::returnHandle(CURL *handle) { +void HTTPFileSource::Impl::returnHandle(CURL *handle) { curl_easy_reset(handle); handles.push(handle); } -void HTTPCURLContext::checkMultiInfo() { - MBGL_VERIFY_THREAD(tid); +void HTTPFileSource::Impl::checkMultiInfo() { CURLMsg *message = nullptr; int pending = 0; while ((message = curl_multi_info_read(multi, &pending))) { switch (message->msg) { case CURLMSG_DONE: { - HTTPCURLRequest *baton = nullptr; + HTTPRequest *baton = nullptr; curl_easy_getinfo(message->easy_handle, CURLINFO_PRIVATE, (char *)&baton); assert(baton); baton->handleResult(message->data.result); @@ -174,9 +150,7 @@ void HTTPCURLContext::checkMultiInfo() { } } -void HTTPCURLContext::perform(curl_socket_t s, util::RunLoop::Event events) { - MBGL_VERIFY_THREAD(tid); - +void HTTPFileSource::Impl::perform(curl_socket_t s, util::RunLoop::Event events) { int flags = 0; if (events == util::RunLoop::Event::Read) { @@ -192,23 +166,22 @@ void HTTPCURLContext::perform(curl_socket_t s, util::RunLoop::Event events) { checkMultiInfo(); } -int HTTPCURLContext::handleSocket(CURL * /* handle */, curl_socket_t s, int action, void *userp, +int HTTPFileSource::Impl::handleSocket(CURL * /* handle */, curl_socket_t s, int action, void *userp, void * /* socketp */) { assert(userp); - auto context = reinterpret_cast<HTTPCURLContext *>(userp); - MBGL_VERIFY_THREAD(context->tid); + auto context = reinterpret_cast<Impl *>(userp); switch (action) { case CURL_POLL_IN: { using namespace std::placeholders; util::RunLoop::Get()->addWatch(s, util::RunLoop::Event::Read, - std::bind(&HTTPCURLContext::perform, context, _1, _2)); + std::bind(&Impl::perform, context, _1, _2)); break; } case CURL_POLL_OUT: { using namespace std::placeholders; util::RunLoop::Get()->addWatch(s, util::RunLoop::Event::Write, - std::bind(&HTTPCURLContext::perform, context, _1, _2)); + std::bind(&Impl::perform, context, _1, _2)); break; } case CURL_POLL_REMOVE: @@ -221,8 +194,7 @@ int HTTPCURLContext::handleSocket(CURL * /* handle */, curl_socket_t s, int acti return 0; } -void HTTPCURLContext::onTimeout(HTTPCURLContext *context) { - MBGL_VERIFY_THREAD(context->tid); +void HTTPFileSource::Impl::onTimeout(Impl *context) { int running_handles; CURLMcode error = curl_multi_socket_action(context->multi, CURL_SOCKET_TIMEOUT, 0, &running_handles); if (error != CURLM_OK) { @@ -231,127 +203,27 @@ void HTTPCURLContext::onTimeout(HTTPCURLContext *context) { context->checkMultiInfo(); } -int HTTPCURLContext::startTimeout(CURLM * /* multi */, long timeout_ms, void *userp) { +int HTTPFileSource::Impl::startTimeout(CURLM * /* multi */, long timeout_ms, void *userp) { assert(userp); - auto context = reinterpret_cast<HTTPCURLContext *>(userp); - MBGL_VERIFY_THREAD(context->tid); + auto context = reinterpret_cast<Impl *>(userp); + if (timeout_ms < 0) { // A timeout of 0 ms means that the timer will invoked in the next loop iteration. timeout_ms = 0; } + context->timeout.stop(); context->timeout.start(mbgl::Milliseconds(timeout_ms), Duration::zero(), - std::bind(&HTTPCURLContext::onTimeout, context)); + std::bind(&Impl::onTimeout, context)); return 0; } -// ------------------------------------------------------------------------------------------------- - -#ifdef __ANDROID__ - -// This function is called to load the CA bundle -// from http://curl.haxx.se/libcurl/c/cacertinmem.html¯ -static CURLcode sslctx_function(CURL * /* curl */, void *sslctx, void * /* parm */) { - - int error = 0; - struct zip *apk = zip_open(mbgl::android::apkPath.c_str(), 0, &error); - if (apk == nullptr) { - return CURLE_SSL_CACERT_BADFILE; - } - - struct zip_file *apkFile = zip_fopen(apk, "assets/ca-bundle.crt", ZIP_FL_NOCASE); - if (apkFile == nullptr) { - zip_close(apk); - apk = nullptr; - return CURLE_SSL_CACERT_BADFILE; - } - - struct zip_stat stat; - if (zip_stat(apk, "assets/ca-bundle.crt", ZIP_FL_NOCASE, &stat) != 0) { - zip_fclose(apkFile); - apkFile = nullptr; - zip_close(apk); - apk = nullptr; - return CURLE_SSL_CACERT_BADFILE; - } - - if (stat.size > std::numeric_limits<int>::max()) { - zip_fclose(apkFile); - apkFile = nullptr; - zip_close(apk); - apk = nullptr; - return CURLE_SSL_CACERT_BADFILE; - } - - const auto pem = std::make_unique<char[]>(stat.size); - - if (static_cast<zip_uint64_t>(zip_fread(apkFile, reinterpret_cast<void *>(pem.get()), stat.size)) != stat.size) { - zip_fclose(apkFile); - apkFile = nullptr; - zip_close(apk); - apk = nullptr; - return CURLE_SSL_CACERT_BADFILE; - } - - // get a pointer to the X509 certificate store (which may be empty!) - X509_STORE *store = SSL_CTX_get_cert_store((SSL_CTX *)sslctx); - if (store == nullptr) { - return CURLE_SSL_CACERT_BADFILE; - } - - // get a BIO - BIO *bio = BIO_new_mem_buf(pem.get(), static_cast<int>(stat.size)); - if (bio == nullptr) { - store = nullptr; - return CURLE_SSL_CACERT_BADFILE; - } - - // use it to read the PEM formatted certificate from memory into an X509 - // structure that SSL can use - X509 *cert = nullptr; - while (PEM_read_bio_X509(bio, &cert, 0, nullptr) != nullptr) { - if (cert == nullptr) { - BIO_free(bio); - bio = nullptr; - store = nullptr; - return CURLE_SSL_CACERT_BADFILE; - } - - // add our certificate to this store - if (X509_STORE_add_cert(store, cert) == 0) { - X509_free(cert); - cert = nullptr; - BIO_free(bio); - bio = nullptr; - store = nullptr; - return CURLE_SSL_CACERT_BADFILE; - } - - X509_free(cert); - cert = nullptr; - } - - // decrease reference counts - BIO_free(bio); - bio = nullptr; - - zip_fclose(apkFile); - apkFile = nullptr; - zip_close(apk); - apk = nullptr; - - // all set to go - return CURLE_OK; -} -#endif - -HTTPCURLRequest::HTTPCURLRequest(HTTPCURLContext* context_, const Resource& resource_, Callback callback_) - : HTTPRequestBase(resource_, callback_), - context(context_), +HTTPRequest::HTTPRequest(HTTPFileSource::Impl* context_, const Resource& resource_, FileSource::Callback callback_) + : context(context_), + resource(resource_), + callback(callback_), handle(context->getHandle()) { - // Zero out the error buffer. - memset(error, 0, sizeof(error)); // If there's already a response, set the correct etags/modified headers to make sure we are // getting a 304 response if possible. This avoids redownloading unchanged data. @@ -370,12 +242,7 @@ HTTPCURLRequest::HTTPCURLRequest(HTTPCURLContext* context_, const Resource& reso handleError(curl_easy_setopt(handle, CURLOPT_PRIVATE, this)); handleError(curl_easy_setopt(handle, CURLOPT_ERRORBUFFER, error)); -#ifdef __ANDROID__ - handleError(curl_easy_setopt(handle, CURLOPT_SSLCERTTYPE, "PEM")); - handleError(curl_easy_setopt(handle, CURLOPT_SSL_CTX_FUNCTION, sslctx_function)); -#else handleError(curl_easy_setopt(handle, CURLOPT_CAINFO, "ca-bundle.crt")); -#endif 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)); @@ -394,9 +261,7 @@ HTTPCURLRequest::HTTPCURLRequest(HTTPCURLContext* context_, const Resource& reso handleError(curl_multi_add_handle(context->multi, handle)); } -HTTPCURLRequest::~HTTPCURLRequest() { - MBGL_VERIFY_THREAD(tid); - +HTTPRequest::~HTTPRequest() { handleError(curl_multi_remove_handle(context->multi, handle)); context->returnHandle(handle); handle = nullptr; @@ -407,16 +272,11 @@ HTTPCURLRequest::~HTTPCURLRequest() { } } -void HTTPCURLRequest::cancel() { - delete this; -} - // This function is called when we have new data for a request. We just append it to the string // containing the previous data. -size_t HTTPCURLRequest::writeCallback(void *const contents, const size_t size, const size_t nmemb, void *userp) { +size_t HTTPRequest::writeCallback(void *const contents, const size_t size, const size_t nmemb, void *userp) { assert(userp); - auto impl = reinterpret_cast<HTTPCURLRequest *>(userp); - MBGL_VERIFY_THREAD(impl->tid); + auto impl = reinterpret_cast<HTTPRequest *>(userp); if (!impl->data) { impl->data = std::make_shared<std::string>(); @@ -442,10 +302,9 @@ size_t headerMatches(const char *const header, const char *const buffer, const s return i == headerLength ? i : std::string::npos; } -size_t HTTPCURLRequest::headerCallback(char *const buffer, const size_t size, const size_t nmemb, void *userp) { +size_t HTTPRequest::headerCallback(char *const buffer, const size_t size, const size_t nmemb, void *userp) { assert(userp); - auto baton = reinterpret_cast<HTTPCURLRequest *>(userp); - MBGL_VERIFY_THREAD(baton->tid); + auto baton = reinterpret_cast<HTTPRequest *>(userp); if (!baton->response) { baton->response = std::make_unique<Response>(); @@ -457,30 +316,21 @@ size_t HTTPCURLRequest::headerCallback(char *const buffer, const size_t size, co // 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 = SystemClock::from_time_t(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) { const std::string value { buffer + begin, length - begin - 2 }; // remove \r\n - baton->response->expires = parseCacheControl(value.c_str()); + baton->response->expires = http::CacheControl::parse(value.c_str()).toTimePoint(); } 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 = SystemClock::from_time_t(curl_getdate(value.c_str(), nullptr)); + baton->response->expires = Timestamp{ Seconds(curl_getdate(value.c_str(), nullptr)) }; } return length; } -void HTTPCURLRequest::handleResult(CURLcode code) { - MBGL_VERIFY_THREAD(tid); - - if (cancelled) { - // In this case, it doesn't make sense to even process the response even further since - // the request was canceled anyway. - delete this; - return; - } - +void HTTPRequest::handleResult(CURLcode code) { // Make sure a response object exists in case we haven't got any headers or content. if (!response) { response = std::make_unique<Response>(); @@ -525,24 +375,31 @@ void HTTPCURLRequest::handleResult(CURLcode code) { } else if (responseCode >= 500 && responseCode < 600) { response->error = std::make_unique<Error>(Error::Reason::Server, std::string{ "HTTP status code " } + - std::to_string(responseCode)); + util::toString(responseCode)); } else { response->error = std::make_unique<Error>(Error::Reason::Other, std::string{ "HTTP status code " } + - std::to_string(responseCode)); + util::toString(responseCode)); } } - // Actually return the response. - notify(*response); - delete this; + // Calling `callback` may result in deleting `this`. Copy data to temporaries first. + auto callback_ = callback; + auto response_ = *response; + callback_(response_); } -std::unique_ptr<HTTPContextBase> HTTPContextBase::createContext() { - return std::make_unique<HTTPCURLContext>(); +HTTPFileSource::HTTPFileSource() + : impl(std::make_unique<Impl>()) { +} + +HTTPFileSource::~HTTPFileSource() = default; + +std::unique_ptr<AsyncRequest> HTTPFileSource::request(const Resource& resource, Callback callback) { + return std::make_unique<HTTPRequest>(impl.get(), resource, callback); } -uint32_t HTTPContextBase::maximumConcurrentRequests() { +uint32_t HTTPFileSource::maximumConcurrentRequests() { return 20; } diff --git a/platform/default/image.cpp b/platform/default/image.cpp index 71fb5414b3..988d2f4a4e 100644 --- a/platform/default/image.cpp +++ b/platform/default/image.cpp @@ -4,20 +4,24 @@ #include <png.h> -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wunused-variable" -// Check png library version. -const static bool png_version_check = []() { +template<size_t max, typename... Args> +inline static std::string sprintf(const char *msg, Args... args) { + char res[max]; + int len = snprintf(res, sizeof(res), msg, args...); + return std::string(res, len); +} + +const static bool png_version_check __attribute__((unused)) = []() { const png_uint_32 version = png_access_version_number(); if (version != PNG_LIBPNG_VER) { - throw std::runtime_error(mbgl::util::sprintf<96>( + throw std::runtime_error(sprintf<96>( "libpng version mismatch: headers report %d.%d.%d, but library reports %d.%d.%d", PNG_LIBPNG_VER / 10000, (PNG_LIBPNG_VER / 100) % 100, PNG_LIBPNG_VER % 100, version / 10000, (version / 100) % 100, version % 100)); } return true; }(); -#pragma GCC diagnostic pop + namespace mbgl { std::string encodePNG(const PremultipliedImage& pre) { diff --git a/platform/default/mbgl/storage/offline.cpp b/platform/default/mbgl/storage/offline.cpp index 931e079771..d8e0357ae2 100644 --- a/platform/default/mbgl/storage/offline.cpp +++ b/platform/default/mbgl/storage/offline.cpp @@ -1,6 +1,6 @@ #include <mbgl/storage/offline.hpp> #include <mbgl/util/tile_cover.hpp> -#include <mbgl/source/source_info.hpp> +#include <mbgl/util/tileset.hpp> #include <rapidjson/document.h> #include <rapidjson/stringbuffer.h> @@ -23,20 +23,20 @@ OfflineTilePyramidRegionDefinition::OfflineTilePyramidRegionDefinition( } } -std::vector<TileID> OfflineTilePyramidRegionDefinition::tileCover(SourceType type, uint16_t tileSize, const SourceInfo& info) const { - double minZ = std::max<double>(coveringZoomLevel(minZoom, type, tileSize), info.minZoom); - double maxZ = std::min<double>(coveringZoomLevel(maxZoom, type, tileSize), info.maxZoom); +std::vector<CanonicalTileID> OfflineTilePyramidRegionDefinition::tileCover(SourceType type, uint16_t tileSize, const Tileset& tileset) const { + double minZ = std::max<double>(util::coveringZoomLevel(minZoom, type, tileSize), tileset.minZoom); + double maxZ = std::min<double>(util::coveringZoomLevel(maxZoom, type, tileSize), tileset.maxZoom); assert(minZ >= 0); assert(maxZ >= 0); assert(minZ < std::numeric_limits<uint8_t>::max()); assert(maxZ < std::numeric_limits<uint8_t>::max()); - std::vector<TileID> result; + std::vector<CanonicalTileID> result; for (uint8_t z = minZ; z <= maxZ; z++) { - for (const auto& tile : mbgl::tileCover(bounds, z, z)) { - result.push_back(tile.normalized()); + for (const auto& tile : util::tileCover(bounds, z)) { + result.emplace_back(tile.canonical); } } diff --git a/platform/default/mbgl/storage/offline_database.cpp b/platform/default/mbgl/storage/offline_database.cpp index 0b8dec01bf..3193909294 100644 --- a/platform/default/mbgl/storage/offline_database.cpp +++ b/platform/default/mbgl/storage/offline_database.cpp @@ -4,7 +4,6 @@ #include <mbgl/util/io.hpp> #include <mbgl/util/string.hpp> #include <mbgl/util/chrono.hpp> -#include <mbgl/map/tile_id.hpp> #include <mbgl/platform/log.hpp> #include "sqlite3.hpp" @@ -158,7 +157,7 @@ std::pair<bool, uint64_t> OfflineDatabase::putInternal(const Resource& resource, } if (evict_ && !evict(size)) { - Log::Warning(Event::Database, "Unable to make space for entry"); + Log::Debug(Event::Database, "Unable to make space for entry"); return { false, 0 }; } @@ -182,7 +181,7 @@ optional<std::pair<Response, uint64_t>> OfflineDatabase::getResource(const Resou Statement accessedStmt = getStatement( "UPDATE resources SET accessed = ?1 WHERE url = ?2"); - accessedStmt->bind(1, SystemClock::now()); + accessedStmt->bind(1, util::now()); accessedStmt->bind(2, resource.url); accessedStmt->run(); @@ -202,8 +201,8 @@ optional<std::pair<Response, uint64_t>> OfflineDatabase::getResource(const Resou uint64_t size = 0; response.etag = stmt->get<optional<std::string>>(0); - response.expires = stmt->get<optional<SystemTimePoint>>(1); - response.modified = stmt->get<optional<SystemTimePoint>>(2); + response.expires = stmt->get<optional<Timestamp>>(1); + response.modified = stmt->get<optional<Timestamp>>(2); optional<std::string> data = stmt->get<optional<std::string>>(3); if (!data) { @@ -230,7 +229,7 @@ bool OfflineDatabase::putResource(const Resource& resource, " expires = ?2 " "WHERE url = ?3 "); - update->bind(1, SystemClock::now()); + update->bind(1, util::now()); update->bind(2, response.expires); update->bind(3, resource.url); update->run(); @@ -239,6 +238,10 @@ 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. + Transaction transaction(*db, Transaction::Immediate); + Statement update = getStatement( "UPDATE resources " "SET kind = ?1, " @@ -254,7 +257,7 @@ bool OfflineDatabase::putResource(const Resource& resource, update->bind(2, response.etag); update->bind(3, response.expires); update->bind(4, response.modified); - update->bind(5, SystemClock::now()); + update->bind(5, util::now()); update->bind(8, resource.url); if (response.noContent) { @@ -267,6 +270,7 @@ bool OfflineDatabase::putResource(const Resource& resource, update->run(); if (db->changes() != 0) { + transaction.commit(); return false; } @@ -279,7 +283,7 @@ bool OfflineDatabase::putResource(const Resource& resource, insert->bind(3, response.etag); insert->bind(4, response.expires); insert->bind(5, response.modified); - insert->bind(6, SystemClock::now()); + insert->bind(6, util::now()); if (response.noContent) { insert->bind(7, nullptr); @@ -290,6 +294,8 @@ bool OfflineDatabase::putResource(const Resource& resource, } insert->run(); + transaction.commit(); + return true; } @@ -303,7 +309,7 @@ optional<std::pair<Response, uint64_t>> OfflineDatabase::getTile(const Resource: " AND y = ?5 " " AND z = ?6 "); - accessedStmt->bind(1, SystemClock::now()); + accessedStmt->bind(1, util::now()); accessedStmt->bind(2, tile.urlTemplate); accessedStmt->bind(3, tile.pixelRatio); accessedStmt->bind(4, tile.x); @@ -335,8 +341,8 @@ optional<std::pair<Response, uint64_t>> OfflineDatabase::getTile(const Resource: uint64_t size = 0; response.etag = stmt->get<optional<std::string>>(0); - response.expires = stmt->get<optional<SystemTimePoint>>(1); - response.modified = stmt->get<optional<SystemTimePoint>>(2); + response.expires = stmt->get<optional<Timestamp>>(1); + response.modified = stmt->get<optional<Timestamp>>(2); optional<std::string> data = stmt->get<optional<std::string>>(3); if (!data) { @@ -367,7 +373,7 @@ bool OfflineDatabase::putTile(const Resource::TileData& tile, " AND y = ?6 " " AND z = ?7 "); - update->bind(1, SystemClock::now()); + update->bind(1, util::now()); update->bind(2, response.expires); update->bind(3, tile.urlTemplate); update->bind(4, tile.pixelRatio); @@ -380,6 +386,10 @@ 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. + Transaction transaction(*db, Transaction::Immediate); + Statement update = getStatement( "UPDATE tiles " "SET modified = ?1, " @@ -397,7 +407,7 @@ bool OfflineDatabase::putTile(const Resource::TileData& tile, update->bind(1, response.modified); update->bind(2, response.etag); update->bind(3, response.expires); - update->bind(4, SystemClock::now()); + update->bind(4, util::now()); update->bind(7, tile.urlTemplate); update->bind(8, tile.pixelRatio); update->bind(9, tile.x); @@ -414,6 +424,7 @@ bool OfflineDatabase::putTile(const Resource::TileData& tile, update->run(); if (db->changes() != 0) { + transaction.commit(); return false; } @@ -429,7 +440,7 @@ bool OfflineDatabase::putTile(const Resource::TileData& tile, insert->bind(6, response.modified); insert->bind(7, response.etag); insert->bind(8, response.expires); - insert->bind(9, SystemClock::now()); + insert->bind(9, util::now()); if (response.noContent) { insert->bind(10, nullptr); @@ -440,6 +451,8 @@ bool OfflineDatabase::putTile(const Resource::TileData& tile, } insert->run(); + transaction.commit(); + return true; } @@ -594,26 +607,37 @@ OfflineRegionDefinition OfflineDatabase::getRegionDefinition(int64_t regionID) { OfflineRegionStatus OfflineDatabase::getRegionCompletedStatus(int64_t regionID) { OfflineRegionStatus result; - Statement stmt = getStatement( - "SELECT COUNT(*), SUM(size) FROM ( " - " SELECT LENGTH(data) as size " - " FROM region_resources, resources " - " WHERE region_id = ?1 " - " AND resource_id = resources.id " - " UNION ALL " - " SELECT LENGTH(data) as size " - " FROM region_tiles, tiles " - " WHERE region_id = ?1 " - " AND tile_id = tiles.id " - ") "); + std::tie(result.completedResourceCount, result.completedResourceSize) + = getCompletedResourceCountAndSize(regionID); + std::tie(result.completedTileCount, result.completedTileSize) + = getCompletedTileCountAndSize(regionID); + + result.completedResourceCount += result.completedTileCount; + result.completedResourceSize += result.completedTileSize; + + return result; +} +std::pair<int64_t, int64_t> OfflineDatabase::getCompletedResourceCountAndSize(int64_t regionID) { + Statement stmt = getStatement( + "SELECT COUNT(*), SUM(LENGTH(data)) " + "FROM region_resources, resources " + "WHERE region_id = ?1 " + "AND resource_id = resources.id "); stmt->bind(1, regionID); stmt->run(); + return { stmt->get<int64_t>(0), stmt->get<int64_t>(1) }; +} - result.completedResourceCount = stmt->get<int64_t>(0); - result.completedResourceSize = stmt->get<int64_t>(1); - - return result; +std::pair<int64_t, int64_t> OfflineDatabase::getCompletedTileCountAndSize(int64_t regionID) { + Statement stmt = getStatement( + "SELECT COUNT(*), SUM(LENGTH(data)) " + "FROM region_tiles, tiles " + "WHERE region_id = ?1 " + "AND tile_id = tiles.id "); + stmt->bind(1, regionID); + stmt->run(); + return { stmt->get<int64_t>(0), stmt->get<int64_t>(1) }; } template <class T> diff --git a/platform/default/mbgl/storage/offline_database.hpp b/platform/default/mbgl/storage/offline_database.hpp index 1e77d560d4..1706c6ba5a 100644 --- a/platform/default/mbgl/storage/offline_database.hpp +++ b/platform/default/mbgl/storage/offline_database.hpp @@ -1,5 +1,4 @@ -#ifndef MBGL_OFFLINE_DATABASE -#define MBGL_OFFLINE_DATABASE +#pragma once #include <mbgl/storage/resource.hpp> #include <mbgl/storage/offline.hpp> @@ -92,6 +91,9 @@ private: // Return value is true iff the resource was previously unused by any other regions. bool markUsed(int64_t regionID, const Resource&); + std::pair<int64_t, int64_t> getCompletedResourceCountAndSize(int64_t regionID); + std::pair<int64_t, int64_t> getCompletedTileCountAndSize(int64_t regionID); + const std::string path; std::unique_ptr<::mapbox::sqlite::Database> db; std::unordered_map<const char *, std::unique_ptr<::mapbox::sqlite::Statement>> statements; @@ -108,5 +110,3 @@ private: }; } // namespace mbgl - -#endif diff --git a/platform/default/mbgl/storage/offline_download.cpp b/platform/default/mbgl/storage/offline_download.cpp index cda00bf8df..11ca862925 100644 --- a/platform/default/mbgl/storage/offline_download.cpp +++ b/platform/default/mbgl/storage/offline_download.cpp @@ -3,8 +3,7 @@ #include <mbgl/storage/file_source.hpp> #include <mbgl/storage/resource.hpp> #include <mbgl/storage/response.hpp> -#include <mbgl/style/style_parser.hpp> -#include <mbgl/layer/symbol_layer.hpp> +#include <mbgl/style/parser.hpp> #include <mbgl/text/glyph.hpp> #include <mbgl/util/tile_cover.hpp> #include <mbgl/util/mapbox.hpp> @@ -47,7 +46,7 @@ void OfflineDownload::setState(OfflineRegionDownloadState state) { observer->statusChanged(status); } -std::vector<Resource> OfflineDownload::spriteResources(const StyleParser& parser) const { +std::vector<Resource> OfflineDownload::spriteResources(const style::Parser& parser) const { std::vector<Resource> result; if (!parser.spriteURL.empty()) { @@ -58,7 +57,7 @@ std::vector<Resource> OfflineDownload::spriteResources(const StyleParser& parser return result; } -std::vector<Resource> OfflineDownload::glyphResources(const StyleParser& parser) const { +std::vector<Resource> OfflineDownload::glyphResources(const style::Parser& parser) const { std::vector<Resource> result; if (!parser.glyphURL.empty()) { @@ -72,11 +71,11 @@ std::vector<Resource> OfflineDownload::glyphResources(const StyleParser& parser) return result; } -std::vector<Resource> OfflineDownload::tileResources(SourceType type, uint16_t tileSize, const SourceInfo& info) const { +std::vector<Resource> OfflineDownload::tileResources(SourceType type, uint16_t tileSize, const Tileset& tileset) const { std::vector<Resource> result; - for (const auto& tile : definition.tileCover(type, tileSize, info)) { - result.push_back(Resource::tile(info.tiles[0], definition.pixelRatio, tile.x, tile.y, tile.z)); + for (const auto& tile : definition.tileCover(type, tileSize, tileset)) { + result.push_back(Resource::tile(tileset.tiles[0], definition.pixelRatio, tile.x, tile.y, tile.z)); } return result; @@ -95,7 +94,7 @@ OfflineRegionStatus OfflineDownload::getStatus() const { return result; } - StyleParser parser; + style::Parser parser; parser.parse(*styleResponse->data); result.requiredResourceCountIsPrecise = true; @@ -104,14 +103,14 @@ OfflineRegionStatus OfflineDownload::getStatus() const { switch (source->type) { case SourceType::Vector: case SourceType::Raster: - if (source->getInfo()) { - result.requiredResourceCount += tileResources(source->type, source->tileSize, *source->getInfo()).size(); + if (source->getTileset()) { + result.requiredResourceCount += tileResources(source->type, source->tileSize, *source->getTileset()).size(); } else { result.requiredResourceCount += 1; optional<Response> sourceResponse = offlineDatabase.get(Resource::source(source->url)); if (sourceResponse) { result.requiredResourceCount += tileResources(source->type, source->tileSize, - *StyleParser::parseTileJSON(*sourceResponse->data, source->url, source->type, source->tileSize)).size(); + *style::parseTileJSON(*sourceResponse->data, source->url, source->type, source->tileSize)).size(); } else { result.requiredResourceCountIsPrecise = false; } @@ -145,7 +144,7 @@ void OfflineDownload::activateDownload() { ensureResource(Resource::style(definition.styleURL), [&] (Response styleResponse) { status.requiredResourceCountIsPrecise = true; - StyleParser parser; + style::Parser parser; parser.parse(*styleResponse.data); for (const auto& source : parser.sources) { @@ -156,14 +155,14 @@ void OfflineDownload::activateDownload() { switch (type) { case SourceType::Vector: case SourceType::Raster: - if (source->getInfo()) { - ensureTiles(type, tileSize, *source->getInfo()); + if (source->getTileset()) { + ensureTiles(type, tileSize, *source->getTileset()); } else { status.requiredResourceCountIsPrecise = false; requiredSourceURLs.insert(url); ensureResource(Resource::source(url), [=] (Response sourceResponse) { - ensureTiles(type, tileSize, *StyleParser::parseTileJSON(*sourceResponse.data, url, type, tileSize)); + ensureTiles(type, tileSize, *style::parseTileJSON(*sourceResponse.data, url, type, tileSize)); requiredSourceURLs.erase(url); if (requiredSourceURLs.empty()) { @@ -196,11 +195,10 @@ void OfflineDownload::activateDownload() { } void OfflineDownload::deactivateDownload() { - workRequests.clear(); - fileRequests.clear(); + requests.clear(); } -void OfflineDownload::ensureTiles(SourceType type, uint16_t tileSize, const SourceInfo& info) { +void OfflineDownload::ensureTiles(SourceType type, uint16_t tileSize, const Tileset& info) { for (const auto& resource : tileResources(type, tileSize, info)) { ensureResource(resource); } @@ -209,9 +207,9 @@ void OfflineDownload::ensureTiles(SourceType type, uint16_t tileSize, const Sour void OfflineDownload::ensureResource(const Resource& resource, std::function<void (Response)> callback) { status.requiredResourceCount++; - auto workRequestsIt = workRequests.insert(workRequests.begin(), nullptr); + auto workRequestsIt = requests.insert(requests.begin(), nullptr); *workRequestsIt = util::RunLoop::Get()->invokeCancellable([=] () { - workRequests.erase(workRequestsIt); + requests.erase(workRequestsIt); optional<std::pair<Response, uint64_t>> offlineResponse = offlineDatabase.getRegionResource(id, resource); if (offlineResponse) { @@ -221,6 +219,10 @@ void OfflineDownload::ensureResource(const Resource& resource, std::function<voi status.completedResourceCount++; status.completedResourceSize += offlineResponse->second; + if (resource.kind == Resource::Kind::Tile) { + status.completedTileSize += offlineResponse->second; + } + observer->statusChanged(status); if (status.complete()) { @@ -234,21 +236,25 @@ void OfflineDownload::ensureResource(const Resource& resource, std::function<voi return; } - auto fileRequestsIt = fileRequests.insert(fileRequests.begin(), nullptr); + auto fileRequestsIt = requests.insert(requests.begin(), nullptr); *fileRequestsIt = onlineFileSource.request(resource, [=] (Response onlineResponse) { if (onlineResponse.error) { observer->responseError(*onlineResponse.error); return; } - fileRequests.erase(fileRequestsIt); + requests.erase(fileRequestsIt); if (callback) { callback(onlineResponse); } status.completedResourceCount++; - status.completedResourceSize += offlineDatabase.putRegionResource(id, resource, onlineResponse); + uint64_t resourceSize = offlineDatabase.putRegionResource(id, resource, onlineResponse); + status.completedResourceSize += resourceSize; + if (resource.kind == Resource::Kind::Tile) { + status.completedTileSize += resourceSize; + } observer->statusChanged(status); diff --git a/platform/default/mbgl/storage/offline_download.hpp b/platform/default/mbgl/storage/offline_download.hpp index be8e90b251..1a0d7536d8 100644 --- a/platform/default/mbgl/storage/offline_download.hpp +++ b/platform/default/mbgl/storage/offline_download.hpp @@ -1,7 +1,6 @@ - #pragma once +#pragma once #include <mbgl/storage/offline.hpp> -#include <mbgl/style/types.hpp> #include <list> #include <set> @@ -11,13 +10,14 @@ namespace mbgl { class OfflineDatabase; class FileSource; -class WorkRequest; -class FileRequest; +class AsyncRequest; class Resource; class Response; -class SourceInfo; -class StyleParser; -class Source; +class Tileset; + +namespace style { +class Parser; +} /** * Coordinates the request and storage of all resources for an offline region. @@ -38,9 +38,9 @@ private: void activateDownload(); void deactivateDownload(); - std::vector<Resource> spriteResources(const StyleParser&) const; - std::vector<Resource> glyphResources(const StyleParser&) const; - std::vector<Resource> tileResources(SourceType, uint16_t, const SourceInfo&) const; + std::vector<Resource> spriteResources(const style::Parser&) const; + std::vector<Resource> glyphResources(const style::Parser&) const; + std::vector<Resource> tileResources(SourceType, uint16_t, const Tileset&) const; /* * Ensure that the resource is stored in the database, requesting it if necessary. @@ -48,7 +48,7 @@ private: * is deactivated, all in progress requests are cancelled. */ void ensureResource(const Resource&, std::function<void (Response)> = {}); - void ensureTiles(SourceType, uint16_t, const SourceInfo&); + void ensureTiles(SourceType, uint16_t, const Tileset&); bool checkTileCountLimit(const Resource& resource); int64_t id; @@ -57,8 +57,7 @@ private: FileSource& onlineFileSource; OfflineRegionStatus status; std::unique_ptr<OfflineRegionObserver> observer; - std::list<std::unique_ptr<WorkRequest>> workRequests; - std::list<std::unique_ptr<FileRequest>> fileRequests; + std::list<std::unique_ptr<AsyncRequest>> requests; std::set<std::string> requiredSourceURLs; }; diff --git a/platform/default/online_file_source.cpp b/platform/default/online_file_source.cpp index 944bcd56d5..a4ac2c2b2b 100644 --- a/platform/default/online_file_source.cpp +++ b/platform/default/online_file_source.cpp @@ -1,5 +1,5 @@ #include <mbgl/storage/online_file_source.hpp> -#include <mbgl/storage/http_context_base.hpp> +#include <mbgl/storage/http_file_source.hpp> #include <mbgl/storage/network_status.hpp> #include <mbgl/storage/response.hpp> @@ -22,20 +22,20 @@ namespace mbgl { -class OnlineFileRequestImpl : public util::noncopyable { +class OnlineFileRequest : public AsyncRequest { public: using Callback = std::function<void (Response)>; - OnlineFileRequestImpl(FileRequest*, const Resource&, Callback, OnlineFileSource::Impl&); - ~OnlineFileRequestImpl(); + OnlineFileRequest(const Resource&, Callback, OnlineFileSource::Impl&); + ~OnlineFileRequest(); - void networkIsReachableAgain(OnlineFileSource::Impl&); - void schedule(OnlineFileSource::Impl&, optional<SystemTimePoint> expires); - void completed(OnlineFileSource::Impl&, Response); + void networkIsReachableAgain(); + void schedule(optional<Timestamp> expires); + void completed(Response); - FileRequest* key; + OnlineFileSource::Impl& impl; Resource resource; - HTTPRequestBase* request = nullptr; + std::unique_ptr<AsyncRequest> request; util::Timer timer; Callback callback; @@ -52,8 +52,7 @@ public: class OnlineFileSource::Impl { public: - // Dummy parameter is a workaround for a gcc 4.9 bug. - Impl(int) { + Impl() { NetworkStatus::Subscribe(&reachability); } @@ -61,16 +60,16 @@ public: NetworkStatus::Unsubscribe(&reachability); } - void request(FileRequest* key, Resource resource, Callback callback) { - allRequests[key] = std::make_unique<OnlineFileRequestImpl>(key, resource, callback, *this); + void add(OnlineFileRequest* request) { + allRequests.insert(request); } - void cancel(FileRequest* key) { - allRequests.erase(key); - if (activeRequests.erase(key)) { + void remove(OnlineFileRequest* request) { + allRequests.erase(request); + if (activeRequests.erase(request)) { activatePendingRequest(); } else { - auto it = pendingRequestsMap.find(key); + auto it = pendingRequestsMap.find(request); if (it != pendingRequestsMap.end()) { pendingRequestsList.erase(it->second); pendingRequestsMap.erase(it); @@ -78,30 +77,30 @@ public: } } - void activateOrQueueRequest(OnlineFileRequestImpl* impl) { - assert(allRequests.find(impl->key) != allRequests.end()); - assert(activeRequests.find(impl->key) == activeRequests.end()); - assert(!impl->request); + void activateOrQueueRequest(OnlineFileRequest* request) { + assert(allRequests.find(request) != allRequests.end()); + assert(activeRequests.find(request) == activeRequests.end()); + assert(!request->request); - if (activeRequests.size() >= HTTPContextBase::maximumConcurrentRequests()) { - queueRequest(impl); + if (activeRequests.size() >= HTTPFileSource::maximumConcurrentRequests()) { + queueRequest(request); } else { - activateRequest(impl); + activateRequest(request); } } - void queueRequest(OnlineFileRequestImpl* impl) { - auto it = pendingRequestsList.insert(pendingRequestsList.end(), impl->key); - pendingRequestsMap.emplace(impl->key, std::move(it)); + void queueRequest(OnlineFileRequest* request) { + auto it = pendingRequestsList.insert(pendingRequestsList.end(), request); + pendingRequestsMap.emplace(request, std::move(it)); } - void activateRequest(OnlineFileRequestImpl* impl) { - activeRequests.insert(impl->key); - impl->request = httpContext->createRequest(impl->resource, [=] (Response response) { - impl->request = nullptr; - activeRequests.erase(impl->key); + void activateRequest(OnlineFileRequest* request) { + activeRequests.insert(request); + request->request = httpFileSource.request(request->resource, [=] (Response response) { + activeRequests.erase(request); activatePendingRequest(); - impl->completed(*this, response); + request->request.reset(); + request->completed(response); }); } @@ -110,20 +109,18 @@ public: return; } - FileRequest* key = pendingRequestsList.front(); + OnlineFileRequest* request = pendingRequestsList.front(); pendingRequestsList.pop_front(); - pendingRequestsMap.erase(key); + pendingRequestsMap.erase(request); - auto it = allRequests.find(key); - assert(it != allRequests.end()); - activateRequest(it->second.get()); + activateRequest(request); } private: void networkIsReachableAgain() { - for (auto& req : allRequests) { - req.second->networkIsReachableAgain(*this); + for (auto& request : allRequests) { + request->networkIsReachableAgain(); } } @@ -138,23 +135,22 @@ private: * Requests in any state are in `allRequests`. Requests in the pending state are in * `pendingRequests`. Requests in the active state are in `activeRequests`. */ - std::unordered_map<FileRequest*, std::unique_ptr<OnlineFileRequestImpl>> allRequests; - std::list<FileRequest*> pendingRequestsList; - std::unordered_map<FileRequest*, std::list<FileRequest*>::iterator> pendingRequestsMap; - std::unordered_set<FileRequest*> activeRequests; + std::unordered_set<OnlineFileRequest*> allRequests; + std::list<OnlineFileRequest*> pendingRequestsList; + std::unordered_map<OnlineFileRequest*, std::list<OnlineFileRequest*>::iterator> pendingRequestsMap; + std::unordered_set<OnlineFileRequest*> activeRequests; - const std::unique_ptr<HTTPContextBase> httpContext { HTTPContextBase::createContext() }; + HTTPFileSource httpFileSource; util::AsyncTask reachability { std::bind(&Impl::networkIsReachableAgain, this) }; }; OnlineFileSource::OnlineFileSource() - : thread(std::make_unique<util::Thread<Impl>>( - util::ThreadContext{ "OnlineFileSource", util::ThreadType::Unknown, util::ThreadPriority::Low }, 0)) { + : impl(std::make_unique<Impl>()) { } OnlineFileSource::~OnlineFileSource() = default; -std::unique_ptr<FileRequest> OnlineFileSource::request(const Resource& resource, Callback callback) { +std::unique_ptr<AsyncRequest> OnlineFileSource::request(const Resource& resource, Callback callback) { Resource res = resource; switch (resource.kind) { @@ -183,40 +179,25 @@ std::unique_ptr<FileRequest> OnlineFileSource::request(const Resource& resource, break; } - class OnlineFileRequest : public FileRequest { - public: - OnlineFileRequest(Resource resource_, FileSource::Callback callback_, util::Thread<OnlineFileSource::Impl>& thread_) - : thread(thread_), - workRequest(thread.invokeWithCallback(&OnlineFileSource::Impl::request, callback_, this, resource_)) { - } - - ~OnlineFileRequest() { - thread.invoke(&OnlineFileSource::Impl::cancel, this); - } - - util::Thread<OnlineFileSource::Impl>& thread; - std::unique_ptr<WorkRequest> workRequest; - }; - - return std::make_unique<OnlineFileRequest>(res, callback, *thread); + return std::make_unique<OnlineFileRequest>(res, callback, *impl); } -OnlineFileRequestImpl::OnlineFileRequestImpl(FileRequest* key_, const Resource& resource_, Callback callback_, OnlineFileSource::Impl& impl) - : key(key_), +OnlineFileRequest::OnlineFileRequest(const Resource& resource_, Callback callback_, OnlineFileSource::Impl& impl_) + : impl(impl_), resource(resource_), callback(std::move(callback_)) { + impl.add(this); + // Force an immediate first request if we don't have an expiration time. if (resource.priorExpires) { - schedule(impl, resource.priorExpires); + schedule(resource.priorExpires); } else { - schedule(impl, SystemClock::now()); + schedule(util::now()); } } -OnlineFileRequestImpl::~OnlineFileRequestImpl() { - if (request) { - request->cancel(); - } +OnlineFileRequest::~OnlineFileRequest() { + impl.remove(this); } static Duration errorRetryTimeout(Response::Error::Reason failedRequestReason, uint32_t failedRequests) { @@ -233,20 +214,20 @@ static Duration errorRetryTimeout(Response::Error::Reason failedRequestReason, u } } -static Duration expirationTimeout(optional<SystemTimePoint> expires, uint32_t expiredRequests) { +static Duration expirationTimeout(optional<Timestamp> expires, uint32_t expiredRequests) { if (expiredRequests) { return Seconds(1 << std::min(expiredRequests - 1, 31u)); } else if (expires) { - return std::max(SystemDuration::zero(), *expires - SystemClock::now()); + return std::max(Seconds::zero(), *expires - util::now()); } else { return Duration::max(); } } -SystemTimePoint interpolateExpiration(const SystemTimePoint& current, - optional<SystemTimePoint> prior, - bool& expired) { - auto now = SystemClock::now(); +Timestamp interpolateExpiration(const Timestamp& current, + optional<Timestamp> prior, + bool& expired) { + auto now = util::now(); if (current > now) { return current; } @@ -275,10 +256,10 @@ SystemTimePoint interpolateExpiration(const SystemTimePoint& current, // Assume that either the client or server clock is wrong and // try to interpolate a valid expiration date (from the client POV) // observing a minimum timeout. - return now + std::max<SystemDuration>(delta, util::CLOCK_SKEW_RETRY_TIMEOUT); + return now + std::max<Seconds>(delta, util::CLOCK_SKEW_RETRY_TIMEOUT); } -void OnlineFileRequestImpl::schedule(OnlineFileSource::Impl& impl, optional<SystemTimePoint> expires) { +void OnlineFileRequest::schedule(optional<Timestamp> expires) { if (request) { // There's already a request in progress; don't start another one. return; @@ -307,7 +288,7 @@ void OnlineFileRequestImpl::schedule(OnlineFileSource::Impl& impl, optional<Syst }); } -void OnlineFileRequestImpl::completed(OnlineFileSource::Impl& impl, Response response) { +void OnlineFileRequest::completed(Response response) { // If we didn't get various caching headers in the response, continue using the // previous values. Otherwise, update the previous values to the new values. @@ -345,15 +326,20 @@ void OnlineFileRequestImpl::completed(OnlineFileSource::Impl& impl, Response res failedRequestReason = Response::Error::Reason::Success; } - callback(response); - schedule(impl, response.expires); + schedule(response.expires); + + // Calling the callback may result in `this` being deleted. It needs to be done last, + // and needs to make a local copy of the callback to ensure that it remains valid for + // the duration of the call. + auto callback_ = callback; + callback_(response); } -void OnlineFileRequestImpl::networkIsReachableAgain(OnlineFileSource::Impl& impl) { +void OnlineFileRequest::networkIsReachableAgain() { // We need all requests to fail at least once before we are going to start retrying // them, and we only immediately restart request that failed due to connection issues. if (failedRequestReason == Response::Error::Reason::Connection) { - schedule(impl, SystemClock::now()); + schedule(util::now()); } } diff --git a/platform/default/png_reader.cpp b/platform/default/png_reader.cpp index 2b9bbbeb46..096596ee2e 100644 --- a/platform/default/png_reader.cpp +++ b/platform/default/png_reader.cpp @@ -83,8 +83,8 @@ PremultipliedImage decodePNG(const uint8_t* data, size_t size) { png_set_sig_bytes(png_ptr, 8); png_read_info(png_ptr, info_ptr); - unsigned width = 0; - unsigned height = 0; + png_uint_32 width = 0; + png_uint_32 height = 0; int bit_depth = 0; int color_type = 0; png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth, &color_type, 0, 0, 0); diff --git a/platform/default/sqlite3.cpp b/platform/default/sqlite3.cpp index bf0bbfc683..b6db71a752 100644 --- a/platform/default/sqlite3.cpp +++ b/platform/default/sqlite3.cpp @@ -7,10 +7,7 @@ #include <chrono> #include <experimental/optional> -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wunused-variable" -// Check sqlite3 library version. -const static bool sqliteVersionCheck = []() { +const static bool sqliteVersionCheck __attribute__((unused)) = []() { if (sqlite3_libversion_number() / 1000000 != SQLITE_VERSION_NUMBER / 1000000) { char message[96]; snprintf(message, 96, @@ -21,7 +18,6 @@ const static bool sqliteVersionCheck = []() { return true; }(); -#pragma GCC diagnostic pop namespace mapbox { namespace sqlite { @@ -222,7 +218,9 @@ void Statement::bindBlob(int offset, const std::vector<uint8_t>& value, bool ret bindBlob(offset, value.data(), value.size(), retain); } -template <> void Statement::bind(int offset, std::chrono::system_clock::time_point value) { +template <> +void Statement::bind( + int offset, std::chrono::time_point<std::chrono::system_clock, std::chrono::seconds> value) { assert(stmt); check(sqlite3_bind_int64(stmt, offset, std::chrono::system_clock::to_time_t(value))); } @@ -235,7 +233,10 @@ template <> void Statement::bind(int offset, optional<std::string> value) { } } -template <> void Statement::bind(int offset, optional<std::chrono::system_clock::time_point> value) { +template <> +void Statement::bind( + int offset, + optional<std::chrono::time_point<std::chrono::system_clock, std::chrono::seconds>> value) { if (!value) { bind(offset, nullptr); } else { @@ -287,9 +288,12 @@ template <> std::vector<uint8_t> Statement::get(int offset) { return { begin, end }; } -template <> std::chrono::system_clock::time_point Statement::get(int offset) { +template <> +std::chrono::time_point<std::chrono::system_clock, std::chrono::seconds> +Statement::get(int offset) { assert(stmt); - return std::chrono::system_clock::from_time_t(sqlite3_column_int64(stmt, offset)); + return std::chrono::time_point_cast<std::chrono::seconds>( + std::chrono::system_clock::from_time_t(sqlite3_column_int64(stmt, offset))); } template <> optional<int64_t> Statement::get(int offset) { @@ -319,12 +323,15 @@ template <> optional<std::string> Statement::get(int offset) { } } -template <> optional<std::chrono::system_clock::time_point> Statement::get(int offset) { +template <> +optional<std::chrono::time_point<std::chrono::system_clock, std::chrono::seconds>> +Statement::get(int offset) { assert(stmt); if (sqlite3_column_type(stmt, offset) == SQLITE_NULL) { - return optional<std::chrono::system_clock::time_point>(); + return {}; } else { - return get<std::chrono::system_clock::time_point>(offset); + return get<std::chrono::time_point<std::chrono::system_clock, std::chrono::seconds>>( + offset); } } @@ -338,5 +345,40 @@ void Statement::clearBindings() { sqlite3_clear_bindings(stmt); } +Transaction::Transaction(Database& db_, Mode mode) + : db(db_) { + switch (mode) { + case Deferred: + db.exec("BEGIN DEFERRED TRANSACTION"); + break; + case Immediate: + db.exec("BEGIN IMMEDIATE TRANSACTION"); + break; + case Exclusive: + db.exec("BEGIN EXCLUSIVE TRANSACTION"); + break; + } +} + +Transaction::~Transaction() { + if (needRollback) { + try { + rollback(); + } catch (...) { + // Ignore failed rollbacks in destructor. + } + } +} + +void Transaction::commit() { + needRollback = false; + db.exec("COMMIT TRANSACTION"); +} + +void Transaction::rollback() { + needRollback = false; + db.exec("ROLLBACK TRANSACTION"); +} + } // namespace sqlite } // namespace mapbox diff --git a/platform/default/sqlite3.hpp b/platform/default/sqlite3.hpp index abe83a2d44..57ee18e9f3 100644 --- a/platform/default/sqlite3.hpp +++ b/platform/default/sqlite3.hpp @@ -88,5 +88,29 @@ private: sqlite3_stmt *stmt = nullptr; }; +class Transaction { +private: + Transaction(const Transaction&) = delete; + Transaction(Transaction&&) = delete; + Transaction& operator=(const Transaction&) = delete; + +public: + enum Mode { + Deferred, + Immediate, + Exclusive + }; + + Transaction(Database&, Mode = Deferred); + ~Transaction(); + + void commit(); + void rollback(); + +private: + Database& db; + bool needRollback = true; +}; + } } diff --git a/platform/default/webp_reader.cpp b/platform/default/webp_reader.cpp index 37d23da110..5b0eaf4741 100644 --- a/platform/default/webp_reader.cpp +++ b/platform/default/webp_reader.cpp @@ -15,8 +15,11 @@ PremultipliedImage decodeWebP(const uint8_t* data, size_t size) { throw std::runtime_error("failed to retrieve WebP basic header information"); } - std::unique_ptr<uint8_t[]> webp(WebPDecodeRGBA(data, size, &width, &height)); - if (!webp) { + int stride = width * 4; + size_t webpSize = stride * height; + auto webp = std::make_unique<uint8_t[]>(webpSize); + + if (!WebPDecodeRGBAInto(data, size, webp.get(), webpSize, stride)) { throw std::runtime_error("failed to decode WebP data"); } |