From 5fdd449f3787c47bc263e55ee7324db11b240a99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konstantin=20Ka=CC=88fer?= Date: Wed, 4 Feb 2015 20:08:12 +0100 Subject: [PATCH 01/43] add target for static image rendering [skip ci] --- Makefile | 10 ++++++++++ include/mbgl/map/map.hpp | 5 +++++ src/mbgl/map/map.cpp | 4 ++++ 3 files changed, 19 insertions(+) diff --git a/Makefile b/Makefile index da9b2aae1ed..92e4f1511f8 100644 --- a/Makefile +++ b/Makefile @@ -181,6 +181,16 @@ android-deploy: $(ANDROID_ABIS) cd android/java/MapboxGLAndroidSDK && chmod ugo+x deploy.sh && ./deploy.sh +##### Render builds ############################################################ + +.PRECIOUS: Makefile/render +Makefile/render: bin/render.gyp config/$(HOST).gypi + deps/run_gyp bin/render.gyp $(CONFIG_$(HOST)) $(LIBS_$(HOST)) --generator-output=./build/$(HOST) -f make + +render: Makefile/render + $(MAKE) -C build/$(HOST) BUILDTYPE=$(BUILDTYPE) mbgl-render + + ##### Maintenace operations #################################################### .PHONY: clear_xcode_cache diff --git a/include/mbgl/map/map.hpp b/include/mbgl/map/map.hpp index 844970bb8f8..fed68cb08ad 100644 --- a/include/mbgl/map/map.hpp +++ b/include/mbgl/map/map.hpp @@ -37,6 +37,11 @@ class GlyphAtlas; class SpriteAtlas; class LineAtlas; +struct exception : std::runtime_error { + inline exception(const char *msg) : std::runtime_error(msg) { + } +}; + class Map : private util::noncopyable { public: explicit Map(View&, FileSource&); diff --git a/src/mbgl/map/map.cpp b/src/mbgl/map/map.cpp index ed20ec24a6e..7e0653644ec 100644 --- a/src/mbgl/map/map.cpp +++ b/src/mbgl/map/map.cpp @@ -241,6 +241,10 @@ void Map::run() { checkForPause(); } + if (!style) { + throw exception("Style is not set"); + } + view.activate(); workers = util::make_unique(**loop, 4, "Tile Worker"); From 0d7c9434ad7d313f622dd3ae250c69eccc8eac1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konstantin=20K=C3=A4fer?= Date: Thu, 5 Feb 2015 11:43:49 +0100 Subject: [PATCH 02/43] change default size of render executable [skip ci] --- bin/render.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bin/render.cpp b/bin/render.cpp index c791152d1dc..6e67ef21554 100644 --- a/bin/render.cpp +++ b/bin/render.cpp @@ -32,8 +32,8 @@ int main(int argc, char *argv[]) { double zoom = 0; double bearing = 0; - int width = 256; - int height = 256; + int width = 512; + int height = 512; double pixelRatio = 1.0; std::string output = "out.png"; std::string cache_file = "cache.sqlite"; From d0c43e29d1882aa49c7f7d674896eb1f039ff989 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konstantin=20K=C3=A4fer?= Date: Thu, 5 Feb 2015 11:50:03 +0100 Subject: [PATCH 03/43] we're not using rapidjson, so don't include it here [skip ci] --- bin/render.cpp | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/bin/render.cpp b/bin/render.cpp index 6e67ef21554..62ad70896a9 100644 --- a/bin/render.cpp +++ b/bin/render.cpp @@ -3,10 +3,6 @@ #include #include -#include -#include -#include - #include #include #include @@ -103,13 +99,11 @@ int main(int argc, char *argv[]) { map.setLatLonZoom(LatLng(lat, lon), zoom); map.setBearing(bearing); - std::unique_ptr pixels; - // Run the loop. It will terminate when we don't have any further listeners. map.run(); // Get the data from the GPU. - pixels = view.readPixels(); + auto pixels = view.readPixels(); const unsigned int w = width * pixelRatio; const unsigned int h = height * pixelRatio; From 27e3998d7215664424fe352907a573179d6a7437 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konstantin=20K=C3=A4fer?= Date: Thu, 5 Feb 2015 12:39:44 +0100 Subject: [PATCH 04/43] don't complain about a missing style if we have a style URL instead [skip ci] --- src/mbgl/map/map.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mbgl/map/map.cpp b/src/mbgl/map/map.cpp index 7e0653644ec..fd83cfbc6ee 100644 --- a/src/mbgl/map/map.cpp +++ b/src/mbgl/map/map.cpp @@ -241,7 +241,7 @@ void Map::run() { checkForPause(); } - if (!style) { + if (!style && styleURL.empty()) { throw exception("Style is not set"); } From 66094ef91952f02411b82f009ae8a5ec0c41d2e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konstantin=20K=C3=A4fer?= Date: Thu, 5 Feb 2015 14:58:20 +0100 Subject: [PATCH 05/43] make Map::resize() private [skip ci] they can only be called by View::resize --- include/mbgl/map/map.hpp | 10 ++++---- include/mbgl/map/view.hpp | 4 ++++ include/mbgl/platform/default/glfw_view.hpp | 10 ++++---- platform/default/glfw_view.cpp | 26 ++++++++++----------- platform/default/headless_view.cpp | 4 +++- src/mbgl/map/view.cpp | 11 +++++++++ test/headless/headless.cpp | 1 - 7 files changed, 42 insertions(+), 24 deletions(-) create mode 100644 src/mbgl/map/view.cpp diff --git a/include/mbgl/map/map.hpp b/include/mbgl/map/map.hpp index fed68cb08ad..ed156c5d224 100644 --- a/include/mbgl/map/map.hpp +++ b/include/mbgl/map/map.hpp @@ -43,6 +43,8 @@ struct exception : std::runtime_error { }; class Map : private util::noncopyable { + friend class View; + public: explicit Map(View&, FileSource&); ~Map(); @@ -80,10 +82,6 @@ class Map : private util::noncopyable { bool needsSwap(); void swapped(); - // Size - void resize(uint16_t width, uint16_t height, float ratio = 1); - void resize(uint16_t width, uint16_t height, float ratio, uint16_t fbWidth, uint16_t fbHeight); - // Styling void addClass(const std::string&); void removeClass(const std::string&); @@ -152,6 +150,10 @@ class Map : private util::noncopyable { inline std::chrono::steady_clock::time_point getTime() const { return animationTime; } private: + // This may only be called by the View object. + void resize(uint16_t width, uint16_t height, float ratio = 1); + void resize(uint16_t width, uint16_t height, float ratio, uint16_t fbWidth, uint16_t fbHeight); + util::ptr getSprite(); uv::worker& getWorker(); diff --git a/include/mbgl/map/view.hpp b/include/mbgl/map/view.hpp index b94b8c0b934..7d8c25f4459 100644 --- a/include/mbgl/map/view.hpp +++ b/include/mbgl/map/view.hpp @@ -46,6 +46,10 @@ class View { // Must not be called from the render thread. virtual void notifyMapChange(MapChange change, std::chrono::steady_clock::duration delay = std::chrono::steady_clock::duration::zero()) = 0; +protected: + // Resizes the view + void resize(uint16_t width, uint16_t height, float ratio, uint16_t fbWidth, uint16_t fbHeight); + protected: mbgl::Map *map = nullptr; }; diff --git a/include/mbgl/platform/default/glfw_view.hpp b/include/mbgl/platform/default/glfw_view.hpp index 7156d4ff1fe..8f5cfb7281f 100644 --- a/include/mbgl/platform/default/glfw_view.hpp +++ b/include/mbgl/platform/default/glfw_view.hpp @@ -20,11 +20,11 @@ class GLFWView : public mbgl::View { void notify(); void notifyMapChange(mbgl::MapChange change, std::chrono::steady_clock::duration delay = std::chrono::steady_clock::duration::zero()); - static void key(GLFWwindow *window, int key, int scancode, int action, int mods); - static void scroll(GLFWwindow *window, double xoffset, double yoffset); - static void resize(GLFWwindow *window, int width, int height); - static void mouseClick(GLFWwindow *window, int button, int action, int modifiers); - static void mouseMove(GLFWwindow *window, double x, double y); + static void onKey(GLFWwindow *window, int key, int scancode, int action, int mods); + static void onScroll(GLFWwindow *window, double xoffset, double yoffset); + static void onResize(GLFWwindow *window, int width, int height); + static void onMouseClick(GLFWwindow *window, int button, int action, int modifiers); + static void onMouseMove(GLFWwindow *window, double x, double y); static void eventloop(void *arg); diff --git a/platform/default/glfw_view.cpp b/platform/default/glfw_view.cpp index 8306229d4ae..865636aebf4 100644 --- a/platform/default/glfw_view.cpp +++ b/platform/default/glfw_view.cpp @@ -62,14 +62,14 @@ void GLFWView::initialize(mbgl::Map *map_) { int width, height; glfwGetWindowSize(window, &width, &height); - resize(window, width, height); + onResize(window, width, height); - glfwSetCursorPosCallback(window, mouseMove); - glfwSetMouseButtonCallback(window, mouseClick); - glfwSetWindowSizeCallback(window, resize); - glfwSetFramebufferSizeCallback(window, resize); - glfwSetScrollCallback(window, scroll); - glfwSetKeyCallback(window, key); + glfwSetCursorPosCallback(window, onMouseMove); + glfwSetMouseButtonCallback(window, onMouseClick); + glfwSetWindowSizeCallback(window, onResize); + glfwSetFramebufferSizeCallback(window, onResize); + glfwSetScrollCallback(window, onScroll); + glfwSetKeyCallback(window, onKey); const std::string extensions = reinterpret_cast(MBGL_CHECK_ERROR(glGetString(GL_EXTENSIONS))); { @@ -157,7 +157,7 @@ void GLFWView::initialize(mbgl::Map *map_) { glfwMakeContextCurrent(nullptr); } -void GLFWView::key(GLFWwindow *window, int key, int /*scancode*/, int action, int mods) { +void GLFWView::onKey(GLFWwindow *window, int key, int /*scancode*/, int action, int mods) { GLFWView *view = reinterpret_cast(glfwGetWindowUserPointer(window)); if (action == GLFW_RELEASE) { @@ -190,7 +190,7 @@ void GLFWView::key(GLFWwindow *window, int key, int /*scancode*/, int action, in } } -void GLFWView::scroll(GLFWwindow *window, double /*xOffset*/, double yOffset) { +void GLFWView::onScroll(GLFWwindow *window, double /*xOffset*/, double yOffset) { GLFWView *view = reinterpret_cast(glfwGetWindowUserPointer(window)); double delta = yOffset * 40; @@ -213,16 +213,16 @@ void GLFWView::scroll(GLFWwindow *window, double /*xOffset*/, double yOffset) { view->map->scaleBy(scale, view->lastX, view->lastY); } -void GLFWView::resize(GLFWwindow *window, int width, int height ) { +void GLFWView::onResize(GLFWwindow *window, int width, int height ) { GLFWView *view = reinterpret_cast(glfwGetWindowUserPointer(window)); int fbWidth, fbHeight; glfwGetFramebufferSize(window, &fbWidth, &fbHeight); - view->map->resize(width, height, static_cast(fbWidth) / static_cast(width), fbWidth, fbHeight); + view->resize(width, height, static_cast(fbWidth) / static_cast(width), fbWidth, fbHeight); } -void GLFWView::mouseClick(GLFWwindow *window, int button, int action, int modifiers) { +void GLFWView::onMouseClick(GLFWwindow *window, int button, int action, int modifiers) { GLFWView *view = reinterpret_cast(glfwGetWindowUserPointer(window)); if (button == GLFW_MOUSE_BUTTON_RIGHT || @@ -249,7 +249,7 @@ void GLFWView::mouseClick(GLFWwindow *window, int button, int action, int modifi } } -void GLFWView::mouseMove(GLFWwindow *window, double x, double y) { +void GLFWView::onMouseMove(GLFWwindow *window, double x, double y) { GLFWView *view = reinterpret_cast(glfwGetWindowUserPointer(window)); if (view->tracking) { double dx = x - view->lastX; diff --git a/platform/default/headless_view.cpp b/platform/default/headless_view.cpp index 656a7743905..a3128234ea2 100644 --- a/platform/default/headless_view.cpp +++ b/platform/default/headless_view.cpp @@ -131,7 +131,7 @@ void HeadlessView::createContext() { #endif } -void HeadlessView::resize(uint16_t width, uint16_t height, float pixelRatio) { +void HeadlessView::resize(const uint16_t width, const uint16_t height, const float pixelRatio) { clearBuffers(); width_ = width; @@ -177,6 +177,8 @@ void HeadlessView::resize(uint16_t width, uint16_t height, float pixelRatio) { throw std::runtime_error(error.str()); } + View::resize(width, height, pixelRatio, w, h); + deactivate(); } diff --git a/src/mbgl/map/view.cpp b/src/mbgl/map/view.cpp new file mode 100644 index 00000000000..3927652ba69 --- /dev/null +++ b/src/mbgl/map/view.cpp @@ -0,0 +1,11 @@ +#include +#include + +namespace mbgl { + +void View::resize(uint16_t width, uint16_t height, float ratio, uint16_t fbWidth, uint16_t fbHeight) { + assert(map); + map->resize(width, height, ratio, fbWidth, fbHeight); +} + +} diff --git a/test/headless/headless.cpp b/test/headless/headless.cpp index b92002aa236..bf04cbcb4b3 100644 --- a/test/headless/headless.cpp +++ b/test/headless/headless.cpp @@ -146,7 +146,6 @@ TEST_P(HeadlessTest, render) { map.setStyleJSON(style, "test/suite"); view.resize(width, height, pixelRatio); - map.resize(width, height, pixelRatio); map.setLatLngZoom(mbgl::LatLng(latitude, longitude), zoom); map.setBearing(bearing); From 3519befcf02116ce88dfe81d1c1439854380c9bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konstantin=20K=C3=A4fer?= Date: Tue, 10 Feb 2015 18:32:51 -0800 Subject: [PATCH 06/43] align threading in continuous rendering mode still image generation --- Makefile | 7 +- bin/render.cpp | 37 +- bin/{render.gyp => render.gypi} | 17 +- configure | 2 +- include/mbgl/map/map.hpp | 132 +++-- include/mbgl/map/still_image.hpp | 20 + include/mbgl/map/view.hpp | 10 +- .../mbgl/platform/default/headless_view.hpp | 3 +- include/mbgl/util/exception.hpp | 20 + mbgl.gyp | 3 +- platform/default/glfw_view.cpp | 9 +- platform/default/headless_view.cpp | 23 +- platform/ios/MGLMapView.mm | 22 +- src/mbgl/geometry/buffer.hpp | 3 + src/mbgl/map/map.cpp | 491 +++++++++--------- src/mbgl/map/source.cpp | 3 +- src/mbgl/map/view.cpp | 10 +- src/mbgl/shader/shader.cpp | 1 + src/mbgl/util/uv_detail.hpp | 8 + test/headless/headless.cpp | 37 +- test/test.gyp | 2 + 21 files changed, 471 insertions(+), 389 deletions(-) rename bin/{render.gyp => render.gypi} (77%) create mode 100644 include/mbgl/map/still_image.hpp create mode 100644 include/mbgl/util/exception.hpp diff --git a/Makefile b/Makefile index 92e4f1511f8..119207569e0 100644 --- a/Makefile +++ b/Makefile @@ -183,14 +183,9 @@ android-deploy: $(ANDROID_ABIS) ##### Render builds ############################################################ -.PRECIOUS: Makefile/render -Makefile/render: bin/render.gyp config/$(HOST).gypi - deps/run_gyp bin/render.gyp $(CONFIG_$(HOST)) $(LIBS_$(HOST)) --generator-output=./build/$(HOST) -f make - -render: Makefile/render +render: Makefile/mbgl $(MAKE) -C build/$(HOST) BUILDTYPE=$(BUILDTYPE) mbgl-render - ##### Maintenace operations #################################################### .PHONY: clear_xcode_cache diff --git a/bin/render.cpp b/bin/render.cpp index 62ad70896a9..1871adf67be 100644 --- a/bin/render.cpp +++ b/bin/render.cpp @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -17,12 +18,13 @@ #include namespace po = boost::program_options; +#include + #include #include #include int main(int argc, char *argv[]) { - std::string style_path; double lat = 0, lon = 0; double zoom = 0; @@ -31,7 +33,7 @@ int main(int argc, char *argv[]) { int width = 512; int height = 512; double pixelRatio = 1.0; - std::string output = "out.png"; + static std::string output = "out.png"; std::string cache_file = "cache.sqlite"; std::vector classes; std::string token; @@ -82,9 +84,10 @@ int main(int argc, char *argv[]) { } } - HeadlessView view; - Map map(view, fileSource); + view.resize(width, height, pixelRatio); + + Map map(view, fileSource, Map::RenderMode::Still); // Set access token if present if (token.size()) { @@ -94,19 +97,25 @@ int main(int argc, char *argv[]) { map.setStyleJSON(style, "."); map.setClasses(classes); - view.resize(width, height, pixelRatio); - map.resize(width, height, pixelRatio); map.setLatLonZoom(LatLng(lat, lon), zoom); map.setBearing(bearing); - // Run the loop. It will terminate when we don't have any further listeners. - map.run(); + uv_async_t *async = new uv_async_t; + uv_async_init(uv_default_loop(), async, [](uv_async_t *as, int) { + std::unique_ptr image(reinterpret_cast(as->data)); + uv_close(reinterpret_cast(as), [](uv_handle_t *handle) { + delete reinterpret_cast(handle); + }); + + const std::string png = util::compress_png(image->width, image->height, image->pixels.get()); + util::write_file(output, png); + }); - // Get the data from the GPU. - auto pixels = view.readPixels(); + map.renderStill([async](std::unique_ptr image) { + async->data = const_cast(image.release()); + uv_async_send(async); + }); - const unsigned int w = width * pixelRatio; - const unsigned int h = height * pixelRatio; - const std::string image = util::compress_png(w, h, pixels.get()); - util::write_file(output, image); + // This loop will terminate once the async was fired. + uv_run(uv_default_loop(), UV_RUN_DEFAULT); } diff --git a/bin/render.gyp b/bin/render.gypi similarity index 77% rename from bin/render.gyp rename to bin/render.gypi index 316a7f3ed0b..1e9d4c34f54 100644 --- a/bin/render.gyp +++ b/bin/render.gypi @@ -8,13 +8,13 @@ 'type': 'executable', 'dependencies': [ - '../mbgl.gyp:core', - '../mbgl.gyp:platform-<(platform_lib)', - '../mbgl.gyp:headless-<(headless_lib)', - '../mbgl.gyp:http-<(http_lib)', - '../mbgl.gyp:asset-<(asset_lib)', - '../mbgl.gyp:cache-<(cache_lib)', - '../mbgl.gyp:copy_certificate_bundle', + 'core', + 'platform-<(platform_lib)', + 'headless-<(headless_lib)', + 'http-<(http_lib)', + 'asset-<(asset_lib)', + 'cache-<(cache_lib)', + 'copy_certificate_bundle', ], 'include_dirs': [ @@ -28,15 +28,18 @@ 'variables' : { 'cflags_cc': [ '<@(glfw3_cflags)', + '<@(uv_cflags)', '<@(boost_cflags)', ], 'ldflags': [ '<@(glfw3_ldflags)', + '<@(uv_ldflags)', '<@(boost_ldflags)', '-lboost_program_options' ], 'libraries': [ '<@(glfw3_static_libs)', + '<@(uv_static_libs)', ], }, diff --git a/configure b/configure index 378adcf2922..f80c8b11c6f 100755 --- a/configure +++ b/configure @@ -37,7 +37,7 @@ case ${MASON_PLATFORM} in LIBZIP_VERSION=0.11.2 ;; *) - GLFW_VERSION=e1ae9af5 + GLFW_VERSION=3.1 SQLITE_VERSION=3.8.8.1 LIBPNG_VERSION=1.6.16 LIBJPEG_VERSION=v9a diff --git a/include/mbgl/map/map.hpp b/include/mbgl/map/map.hpp index ed156c5d224..f5b8ace70c9 100644 --- a/include/mbgl/map/map.hpp +++ b/include/mbgl/map/map.hpp @@ -36,51 +36,50 @@ class View; class GlyphAtlas; class SpriteAtlas; class LineAtlas; - -struct exception : std::runtime_error { - inline exception(const char *msg) : std::runtime_error(msg) { - } -}; +class StillImage; class Map : private util::noncopyable { friend class View; public: - explicit Map(View&, FileSource&); + enum class RenderMode : bool { + Continuous, // continually updating map + Still, // a one-off still image. + }; + + explicit Map(View&, FileSource&, RenderMode mode = RenderMode::Continuous); ~Map(); // Start the map render thread. It is asynchronous. - void start(bool startPaused = false); - - // Stop the map render thread. This call will block until the map rendering thread stopped. - // The optional callback function will be invoked repeatedly until the map thread is stopped. - // The callback function should wait until it is woken up again by view.notify(), otherwise - // this will be a busy waiting loop. - void stop(std::function callback = std::function()); + void start(); - // Pauses the render thread. The render thread will stop running but will not be terminated and will not lose state until resumed. - void pause(bool waitForPause = false); + // Stop the map render thread. The thread will not be joined, instead the map will cease to + // render any further things. + void stop(); - // Resumes a paused render thread - void resume(); + // Renders a still image in the background thread. + using StillImageCallback = std::function)>; + void renderStill(StillImageCallback callback); +private: // Runs the map event loop. ONLY run this function when you want to get render a single frame // with this map object. It will *not* spawn a separate thread and instead block until the // frame is completely rendered. void run(); - // Triggers a lazy rerender: only performs a render when the map is not clean. - void rerender(); + void runContinuous(); + void updatedContinuous(); - // Forces a map update: always triggers a rerender. - void update(); + void runStillImage(); + void updatedStillImage(); - // Releases resources immediately - void terminate(); - // Controls buffer swapping. - bool needsSwap(); - void swapped(); +public: + // Forces a map update: always triggers a rerender. Must be called from the UI thread. + void update(); + + // Triggers a rerender from the map thread. + void rerender(); // Styling void addClass(const std::string&); @@ -101,7 +100,7 @@ class Map : private util::noncopyable { // Position void moveBy(double dx, double dy, std::chrono::steady_clock::duration duration = std::chrono::steady_clock::duration::zero()); void setLatLng(LatLng latLng, std::chrono::steady_clock::duration duration = std::chrono::steady_clock::duration::zero()); - inline const LatLng getLatLng() const { return state.getLatLng(); } + const LatLng getLatLng() const; void startPanning(); void stopPanning(); void resetPosition(); @@ -151,18 +150,16 @@ class Map : private util::noncopyable { private: // This may only be called by the View object. - void resize(uint16_t width, uint16_t height, float ratio = 1); void resize(uint16_t width, uint16_t height, float ratio, uint16_t fbWidth, uint16_t fbHeight); util::ptr getSprite(); uv::worker& getWorker(); - // Checks if render thread needs to pause - void checkForPause(); - // Setup void setup(); + void loadStyleURL(); + void updateTiles(); void updateSources(); void updateSources(const util::ptr &group); @@ -171,82 +168,69 @@ class Map : private util::noncopyable { // the stylesheet. void prepare(); + // Unconditionally performs a render with the current map state. void render(); - enum class Mode : uint8_t { - None, // we're not doing any processing - Continuous, // continually updating map - Static, // a once-off static image. - }; + // Helper functions + bool inUIThread() const; + bool inMapThread() const; - Mode mode = Mode::None; + // This is invoked when the Map object is about to be destructed. + void terminate(); -public: // TODO: make private again - std::unique_ptr loop; + void updated(); private: - std::unique_ptr workers; - std::thread thread; - std::unique_ptr asyncTerminate; - std::unique_ptr asyncRender; - - bool terminating = false; - bool pausing = false; - bool isPaused = false; - std::mutex mutexRun; - std::condition_variable condRun; - std::mutex mutexPause; - std::condition_variable condPause; - - // If cleared, the next time the render thread attempts to render the map, it will *actually* - // render the map. - std::atomic_flag isClean = ATOMIC_FLAG_INIT; + View &view; + FileSource& fileSource; + const RenderMode renderMode; +public: // TODO: Make this private again. + std::unique_ptr loop; +private: + const std::thread::id uiThread; + std::thread::id mapThread; + Transform transform; - // If this flag is cleared, the current back buffer is ready for being swapped with the front - // buffer (i.e. it has rendered data). - std::atomic_flag isSwapped = ATOMIC_FLAG_INIT; + std::unique_ptr asyncTerminate; + std::unique_ptr asyncUpdate; - // This is cleared once the current front buffer has been presented and the back buffer is - // ready for rendering. - std::atomic_flag isRendered = ATOMIC_FLAG_INIT; + std::thread thread; - // Stores whether the map thread has been stopped already. - std::atomic_bool isStopped; - View &view; + std::atomic active; -#ifdef DEBUG - const std::thread::id mainThread; - std::thread::id mapThread; -#endif +private: + std::unique_ptr workers; - Transform transform; TransformState state; - FileSource& fileSource; util::ptr