From 666f593803d29ff88173714e373a35886b51efe1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konstantin=20Ka=CC=88fer?= Date: Fri, 25 Apr 2014 14:26:59 +0200 Subject: [PATCH] use an event loop per map that is not the render thread --- common/curl_request.cpp | 14 +++--- common/foundation_request.mm | 20 ++++---- common/glfw_view.cpp | 73 ++++++------------------------ common/glfw_view.hpp | 3 +- include/llmr/map/map.hpp | 23 +++++++++- include/llmr/map/tile_data.hpp | 9 ++-- include/llmr/map/tile_parser.hpp | 3 ++ include/llmr/platform/platform.hpp | 5 +- include/llmr/platform/request.hpp | 7 ++- include/llmr/renderer/painter.hpp | 1 + include/llmr/style/sprite.hpp | 5 +- src/map/map.cpp | 58 ++++++++++++++++++++---- src/map/source.cpp | 2 +- src/map/tile_data.cpp | 22 +++++---- src/map/tile_parser.cpp | 1 + src/platform/request.cpp | 8 ++-- src/renderer/painter.cpp | 1 + src/style/sprite.cpp | 9 ++-- 18 files changed, 152 insertions(+), 112 deletions(-) diff --git a/common/curl_request.cpp b/common/curl_request.cpp index cca50447f..e00e80578 100644 --- a/common/curl_request.cpp +++ b/common/curl_request.cpp @@ -88,9 +88,10 @@ static std::queue curl_handle_cache; class CURLRequest : public llmr::platform::Request { public: CURLRequest(const std::string &url, - std::function background_function, - std::function foreground_callback) - : Request(url, background_function, foreground_callback) {} + std::function background_function, + std::function foreground_callback, + uv_loop_t *loop) + : Request(url, background_function, foreground_callback, loop) {} CURL *curl = nullptr; }; @@ -177,7 +178,7 @@ void curl_perform(uv_poll_t *req, int /*status*/, int events) { // Executes the background_function in a libuv thread pool, and the after_work_cb back // in the *main* event loop. - uv_queue_work(uv_default_loop(), work, Request::work_callback, Request::after_work_callback); + uv_queue_work((*req)->loop, work, Request::work_callback, Request::after_work_callback); CURL *handle = message->easy_handle; remove_curl_handle(handle); @@ -367,10 +368,11 @@ void thread_init_cb() { std::shared_ptr platform::request_http(const std::string &url, std::function background_function, - std::function foreground_callback) { + std::function foreground_callback, + uv_loop_t *loop) { using namespace request; init_thread_once(thread_init_cb); - std::shared_ptr req = std::make_shared(url, background_function, foreground_callback); + std::shared_ptr req = std::make_shared(url, background_function, foreground_callback, loop); // Note that we are creating a new shared_ptr pointer(!) because the lockless queue can't store // objects with nontrivial destructors. We have to make absolutely sure that we manually delete diff --git a/common/foundation_request.mm b/common/foundation_request.mm index 2bd651808..c0b12cc73 100644 --- a/common/foundation_request.mm +++ b/common/foundation_request.mm @@ -41,8 +41,9 @@ class FoundationRequest : public llmr::platform::Request { public: FoundationRequest(const std::string &url, std::function background_function, - std::function foreground_callback) - : Request(url, background_function, foreground_callback) { + std::function foreground_callback, + uv_loop_t *loop) + : Request(url, background_function, foreground_callback, loop) { #if TARGET_OS_IPHONE active_tasks++; dispatch_async(dispatch_get_main_queue(), ^(void) { @@ -68,11 +69,12 @@ public: std::shared_ptr llmr::platform::request_http(const std::string &url, std::function background_function, - std::function foreground_callback) { + std::function foreground_callback, + uv_loop_t *loop) { uv_once(&request_initialize, request_initialize_cb); std::shared_ptr req = - std::make_shared(url, background_function, foreground_callback); + std::make_shared(url, background_function, foreground_callback, loop); // Note that we are creating a new shared_ptr pointer(!) to make sure there is at least one // shared_ptr in existence while the NSURLSession is loading our data. We are making sure in the @@ -91,13 +93,13 @@ llmr::platform::request_http(const std::string &url, return; } - req->res = std::make_unique(); + (*req_ptr)->res = std::make_unique(); if (!error && [response isKindOfClass:[NSHTTPURLResponse class]]) { - req->res->code = [(NSHTTPURLResponse *)response statusCode]; - req->res->body = {(const char *)[data bytes], [data length]}; + (*req_ptr)->res->code = [(NSHTTPURLResponse *)response statusCode]; + (*req_ptr)->res->body = {(const char *)[data bytes], [data length]}; } else { - req->res->error_message = [[error localizedDescription] UTF8String]; + (*req_ptr)->res->error_message = [[error localizedDescription] UTF8String]; } // We're currently in the request thread. We're going to schedule a uv_work request that @@ -111,7 +113,7 @@ llmr::platform::request_http(const std::string &url, // Executes the background_function in a libuv thread pool, and the after_work_cb back in // the *main* event loop. - uv_queue_work(uv_default_loop(), work, Request::work_callback, + uv_queue_work((*req_ptr)->loop, work, Request::work_callback, Request::after_work_callback); }]; diff --git a/common/glfw_view.cpp b/common/glfw_view.cpp index 82e9aad74..d3034016f 100644 --- a/common/glfw_view.cpp +++ b/common/glfw_view.cpp @@ -33,8 +33,7 @@ MapView::MapView(llmr::Settings &settings, bool fullscreen) - : fullscreen(fullscreen), settings(settings), map(settings), - stop_event_listener(false) {} + : fullscreen(fullscreen), settings(settings), map(settings) {} MapView::~MapView() { glfwTerminate(); } @@ -196,69 +195,27 @@ void MapView::mousemove(GLFWwindow *window, double x, double y) { mapView->last_y = y; } -void MapView::eventloop(void *arg) { - MapView *view = static_cast(arg); - int r = 0; - int fd = 0; - int timeout; - - while (!view->stop_event_listener) { - fd = uv_backend_fd(uv_default_loop()); - timeout = uv_backend_timeout(uv_default_loop()); - - do { -#if defined(HAVE_KQUEUE) - struct timespec ts; - ts.tv_sec = timeout / 1000; - ts.tv_nsec = (timeout % 1000) * 1000000; - r = kevent(fd, NULL, 0, NULL, 0, &ts); -#elif defined(HAVE_EPOLL) - { - struct epoll_event ev; - r = epoll_wait(fd, &ev, 1, timeout); - } -#endif - } while (r == -1 && errno == EINTR); - glfwPostEmptyEvent(); - uv_sem_wait(&view->event_listener); - } -} - int MapView::run() { - uv_thread_t embed_thread; - - uv_run(uv_default_loop(), UV_RUN_NOWAIT); - - /* Start worker that will interrupt external loop */ - stop_event_listener = false; - uv_sem_init(&event_listener, 0); - uv_thread_create(&embed_thread, eventloop, this); + map.start(); while (!glfwWindowShouldClose(window)) { - - bool dirty = false; - try { - dirty = map.render(); - } - catch (std::exception &ex) { - fprintf(stderr, "exception: %s\n", ex.what()); - } - glfwSwapBuffers(window); - fps(); - - if (dirty) { - glfwPollEvents(); - } else { - glfwWaitEvents(); + if (map.clean.test_and_set() == false) { + // This branch is executed when the previous value of the "clean" + // flag is false (i.e. it is unclean == dirty). + try { + map.render(); + } + catch (std::exception &ex) { + fprintf(stderr, "exception: %s\n", ex.what()); + } + glfwSwapBuffers(window); + fps(); } - uv_run(uv_default_loop(), UV_RUN_NOWAIT); - uv_sem_post(&event_listener); + glfwWaitEvents(); } - stop_event_listener = true; - - uv_thread_join(&embed_thread); + map.stop(); return 0; } diff --git a/common/glfw_view.hpp b/common/glfw_view.hpp index 8647c8ad6..5dbb64efb 100644 --- a/common/glfw_view.hpp +++ b/common/glfw_view.hpp @@ -37,8 +37,7 @@ public: llmr::Settings &settings; llmr::Map map; - uv_sem_t event_listener; - std::atomic stop_event_listener; + uv_loop_t *loop = nullptr; }; #endif diff --git a/include/llmr/map/map.hpp b/include/llmr/map/map.hpp index b7562bb83..245aa2992 100644 --- a/include/llmr/map/map.hpp +++ b/include/llmr/map/map.hpp @@ -1,6 +1,8 @@ #ifndef LLMR_MAP_MAP #define LLMR_MAP_MAP +#include + #include #include #include @@ -24,6 +26,9 @@ public: explicit Map(Settings& settings); ~Map(); + void start(); + void stop(); + /* setup */ void setup(); void loadStyle(const uint8_t *const data, uint32_t bytes); @@ -33,7 +38,7 @@ public: /* callback */ void update(); - bool render(); + void render(); void cancelAnimations(); /* position */ @@ -64,16 +69,27 @@ public: void startRotating(); void stopRotating(); + + void redraw(); + void toggleDebug(); void toggleRaster(); box cornersToBox(uint32_t z) const; + + +public: // Getters float getPixelRatio() const; Style& getStyle(); GlyphAtlas& getGlyphAtlas(); + uv_loop_t *getLoop(); private: bool updateTiles(); + static void eventloop(void *arg); + +public: + std::atomic_flag clean = ATOMIC_FLAG_INIT; private: Settings& settings; @@ -84,6 +100,11 @@ private: Painter painter; std::map> sources; + +private: + uv_loop_t *loop = nullptr; + uv_thread_t thread; + uv_async_t async_terminate; }; } diff --git a/include/llmr/map/tile_data.hpp b/include/llmr/map/tile_data.hpp index c6283e370..fc03a8507 100644 --- a/include/llmr/map/tile_data.hpp +++ b/include/llmr/map/tile_data.hpp @@ -5,7 +5,6 @@ #include #include #include -#include #include #include #include @@ -30,7 +29,7 @@ namespace llmr { -class Style; +class Map; class Raster; class LayerDescription; class BucketDescription; @@ -56,7 +55,7 @@ public: }; public: - TileData(Tile::ID id, const Style& style, GlyphAtlas& glyphAtlas, const std::string url, const bool is_raster); + TileData(Tile::ID id, Map &map, const std::string url, const bool is_raster); ~TileData(); void request(); @@ -91,8 +90,8 @@ private: const std::string url; const bool is_raster = false; std::string data; - const Style& style; - GlyphAtlas& glyphAtlas; + + Map ↦ // Stores a request that is in progress. std::weak_ptr req; diff --git a/include/llmr/map/tile_parser.hpp b/include/llmr/map/tile_parser.hpp index ee623f1ff..013d650de 100644 --- a/include/llmr/map/tile_parser.hpp +++ b/include/llmr/map/tile_parser.hpp @@ -6,6 +6,9 @@ namespace llmr { +class Style; +class GlyphAtlas; + class TileParser { public: TileParser(const std::string& data, TileData& tile, const Style& style, GlyphAtlas& glyphAtlas, bool is_raster = false); diff --git a/include/llmr/platform/platform.hpp b/include/llmr/platform/platform.hpp index 4536bf045..b8ec955c7 100644 --- a/include/llmr/platform/platform.hpp +++ b/include/llmr/platform/platform.hpp @@ -5,6 +5,8 @@ #include #include +typedef struct uv_loop_s uv_loop_t; + namespace llmr { extern const char *kSpriteURL; @@ -30,7 +32,8 @@ struct Response { // Returns a cancellable request. std::shared_ptr request_http(const std::string &url, std::function background_function, - std::function foreground_callback); + std::function foreground_callback, + uv_loop_t *loop); // Cancels an HTTP request. void cancel_request_http(const std::shared_ptr &req); diff --git a/include/llmr/platform/request.hpp b/include/llmr/platform/request.hpp index 7ba1daf14..0aa8fc639 100644 --- a/include/llmr/platform/request.hpp +++ b/include/llmr/platform/request.hpp @@ -10,6 +10,7 @@ // Forward definition. typedef struct uv_work_s uv_work_t; typedef struct uv_check_s uv_check_t; +typedef struct uv_loop_s uv_loop_t; namespace llmr { namespace platform { @@ -20,7 +21,8 @@ class Request : public std::enable_shared_from_this, private util::nonc public: Request(const std::string &url, std::function background_function, - std::function foreground_callback); + std::function foreground_callback, + uv_loop_t *loop); ~Request(); public: @@ -34,8 +36,9 @@ public: std::unique_ptr res; bool cancelled = false; -private: +public: uv_check_t *check = nullptr; + uv_loop_t *loop = nullptr; }; } } diff --git a/include/llmr/renderer/painter.hpp b/include/llmr/renderer/painter.hpp index feb875d8d..4f56dbbbb 100644 --- a/include/llmr/renderer/painter.hpp +++ b/include/llmr/renderer/painter.hpp @@ -22,6 +22,7 @@ namespace llmr { class Transform; class Style; class Tile; +class GlyphAtlas; class FillBucket; class LineBucket; diff --git a/include/llmr/style/sprite.hpp b/include/llmr/style/sprite.hpp index 3ac81b39d..450b47aee 100644 --- a/include/llmr/style/sprite.hpp +++ b/include/llmr/style/sprite.hpp @@ -11,6 +11,8 @@ namespace llmr { +class Map; + class SpritePosition { public: SpritePosition(uint16_t x, uint16_t y, uint16_t width, uint16_t height, uint8_t pixelRatio = 1); @@ -33,7 +35,7 @@ public: class Sprite : public std::enable_shared_from_this { public: - Sprite(float pixelRatio = 1); + Sprite(Map &map, float pixelRatio = 1); void load(const std::string& base_url); @@ -49,6 +51,7 @@ private: void parseJSON(const std::string& data); private: + Map ↦ mutable std::mutex mtx; bool loaded = false; std::map pos; diff --git a/src/map/map.cpp b/src/map/map.cpp index 6fb5b608a..661b41cdf 100644 --- a/src/map/map.cpp +++ b/src/map/map.cpp @@ -17,13 +17,38 @@ Map::Map(Settings& settings) texturepool(), style(), glyphAtlas(1024, 1024), - painter(transform, style, glyphAtlas) { + painter(transform, style, glyphAtlas), + loop(uv_loop_new()) { + clean.clear(); } Map::~Map() { settings.sync(); } +void Map::start() { + uv_async_init(loop, &async_terminate, [](uv_async_t *async) { + uv_stop(static_cast(async->data)); + }); + async_terminate.data = loop; + + uv_thread_create(&thread, eventloop, this); +} + +void Map::stop() { + uv_async_send(&async_terminate); + uv_thread_join(&thread); + + uv_loop_delete(loop); + loop = nullptr; +} + +void Map::eventloop(void *arg) { + Map *map = static_cast(arg); + + uv_run(map->loop, UV_RUN_DEFAULT); +} + void Map::setup() { painter.setup(); @@ -78,7 +103,7 @@ void Map::resize(uint16_t width, uint16_t height, float ratio, uint16_t fb_width painter.resize(); if (!style.sprite || style.sprite->pixelRatio != transform.getPixelRatio()) { - style.sprite = std::make_shared(transform.getPixelRatio()); + style.sprite = std::make_shared(*this, transform.getPixelRatio()); style.sprite->load(kSpriteURL); } } @@ -99,8 +124,13 @@ GlyphAtlas& Map::getGlyphAtlas() { return glyphAtlas; } +uv_loop_t *Map::getLoop() { + return loop; +} + void Map::moveBy(double dx, double dy, double duration) { transform.moveBy(dx, dy, duration); + update(); transform.getLonLat(settings.longitude, settings.latitude); @@ -109,12 +139,12 @@ void Map::moveBy(double dx, double dy, double duration) { void Map::startPanning() { transform.startPanning(); - platform::restart(); + redraw(); } void Map::stopPanning() { transform.stopPanning(); - platform::restart(); + redraw(); } void Map::scaleBy(double ds, double cx, double cy, double duration) { @@ -128,12 +158,12 @@ void Map::scaleBy(double ds, double cx, double cy, double duration) { void Map::startScaling() { transform.startScaling(); - platform::restart(); + redraw(); } void Map::stopScaling() { transform.stopScaling(); - platform::restart(); + redraw(); } void Map::rotateBy(double sx, double sy, double ex, double ey, double duration) { @@ -146,14 +176,15 @@ void Map::rotateBy(double sx, double sy, double ex, double ey, double duration) void Map::startRotating() { transform.startRotating(); - platform::restart(); + redraw(); } void Map::stopRotating() { transform.stopRotating(); - platform::restart(); + redraw(); } + void Map::setLonLat(double lon, double lat, double duration) { transform.setLonLat(lon, lat, duration); update(); @@ -294,6 +325,11 @@ void Map::update() { style.cascade(transform.getNormalizedZoom()); // Schedules a repaint + redraw(); +} + +void Map::redraw() { + clean.clear(); platform::restart(); } @@ -310,7 +346,7 @@ bool Map::updateTiles() { return changed; } -bool Map::render() { +void Map::render() { double animationTime = platform::elapsed(); if (transform.needsAnimation()) { @@ -360,5 +396,7 @@ bool Map::render() { bool transition = transform.needsAnimation(); bool animation = painter.needsAnimation(); - return changed || transition || animation; + if (changed || transition || animation) { + redraw(); + } } diff --git a/src/map/source.cpp b/src/map/source.cpp index ca6dea980..be464aaf2 100644 --- a/src/map/source.cpp +++ b/src/map/source.cpp @@ -116,7 +116,7 @@ TileData::State Source::addTile(const Tile::ID& id) { formed_url = util::sprintf<256>(url, normalized_id.z, normalized_id.x, normalized_id.y, (map.getPixelRatio() > 1.0 ? "@2x" : "")); } - new_tile.data = std::make_shared(normalized_id, map.getStyle(), map.getGlyphAtlas(), formed_url, (type == Source::Type::raster)); + new_tile.data = std::make_shared(normalized_id, map, formed_url, (type == Source::Type::raster)); new_tile.data->request(); tile_data.push_front(new_tile.data); } diff --git a/src/map/tile_data.cpp b/src/map/tile_data.cpp index 3c379bd1a..905f5c745 100644 --- a/src/map/tile_data.cpp +++ b/src/map/tile_data.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include @@ -9,24 +10,24 @@ #include #include +#include + using namespace llmr; -TileData::TileData(Tile::ID id, const Style& style, GlyphAtlas& glyphAtlas, const std::string url, const bool is_raster) +TileData::TileData(Tile::ID id, Map &map, const std::string url, const bool is_raster) : id(id), state(State::initial), raster(), url(url), is_raster(is_raster), - style(style), - glyphAtlas(glyphAtlas) { - + map(map) { // Initialize tile debug coordinates const std::string str = util::sprintf<32>("%d/%d/%d", id.z, id.x, id.y); debugFontBuffer.addText(str.c_str(), 50, 200, 5); } TileData::~TileData() { - glyphAtlas.removeGlyphs((uint64_t)id); + map.getGlyphAtlas().removeGlyphs((uint64_t)id); cancel(); } @@ -51,9 +52,12 @@ void TileData::request() { } else { fprintf(stderr, "[%s] tile loading failed: %d, %s\n", tile->url.c_str(), res->code, res->error_message.c_str()); } - }, []() { - platform::restart(); - }); + }, [weak_tile]() { + std::shared_ptr tile = weak_tile.lock(); + if (tile) { + tile->map.redraw(); + } + }, map.getLoop()); } void TileData::cancel() { @@ -72,7 +76,7 @@ bool TileData::parse() { // Parsing creates state that is encapsulated in TileParser. While parsing, // the TileParser object writes results into this objects. All other state // is going to be discarded afterwards. - TileParser parser(data, *this, style, glyphAtlas, is_raster); + TileParser parser(data, *this, map.getStyle(), map.getGlyphAtlas(), is_raster); } catch (const std::exception& ex) { fprintf(stderr, "[%p] exception [%d/%d/%d]... failed: %s\n", this, id.z, id.x, id.y, ex.what()); cancel(); diff --git a/src/map/tile_parser.cpp b/src/map/tile_parser.cpp index 9e75fe3f7..ed8ad14e4 100644 --- a/src/map/tile_parser.cpp +++ b/src/map/tile_parser.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include diff --git a/src/platform/request.cpp b/src/platform/request.cpp index c5a5922e5..36b2dc77e 100644 --- a/src/platform/request.cpp +++ b/src/platform/request.cpp @@ -6,10 +6,12 @@ using namespace llmr::platform; Request::Request(const std::string &url, std::function background_function, - std::function foreground_callback) + std::function foreground_callback, + uv_loop_t *loop) : url(url), background_function(background_function), - foreground_callback(foreground_callback) { + foreground_callback(foreground_callback), + loop(loop) { // Add a check handle without a callback to keep the default loop running. // We don't have a real handler attached to the default loop right from the @@ -17,7 +19,7 @@ Request::Request(const std::string &url, // request in the request thread. Only after the request is complete, we // create an actual work request that is attached to the default loop. check = new uv_check_t(); - uv_check_init(uv_default_loop(), check); + uv_check_init(loop, check); uv_check_start(check, [](uv_check_t *) {}); } diff --git a/src/renderer/painter.cpp b/src/renderer/painter.cpp index 2ae81d071..fc1cc481b 100644 --- a/src/renderer/painter.cpp +++ b/src/renderer/painter.cpp @@ -13,6 +13,7 @@ #include #include +#include #include #include #include diff --git a/src/style/sprite.cpp b/src/style/sprite.cpp index e7d48e5b7..f46457cb9 100644 --- a/src/style/sprite.cpp +++ b/src/style/sprite.cpp @@ -1,4 +1,5 @@ #include +#include #include #include @@ -20,7 +21,7 @@ ImagePosition::ImagePosition(const vec2& size, vec2 tl, vec2()) {} +Sprite::Sprite(Map &map, float pixelRatio) : pixelRatio(pixelRatio), raster(std::make_shared()), map(map) {} void Sprite::load(const std::string& base_url) { std::shared_ptr sprite = shared_from_this(); @@ -29,7 +30,7 @@ void Sprite::load(const std::string& base_url) { std::lock_guard lock(sprite->mtx); if (*sprite->raster && sprite->pos.size()) { sprite->loaded = true; - platform::restart(); + sprite->map.redraw(); fprintf(stderr, "sprite loaded\n"); } }; @@ -42,7 +43,7 @@ void Sprite::load(const std::string& base_url) { } else { fprintf(stderr, "failed to load sprite\n"); } - }, complete); + }, complete, map.getLoop()); platform::request_http(base_url + suffix + ".png", [sprite](const platform::Response *res) { if (res->code == 200) { @@ -50,7 +51,7 @@ void Sprite::load(const std::string& base_url) { } else { fprintf(stderr, "failed to load sprite image\n"); } - }, complete); + }, complete, map.getLoop()); } Sprite::operator bool() const {