#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define _USE_MATH_DEFINES #include #include // Check libuv library version. const static bool uvVersionCheck = []() { const unsigned int version = uv_version(); const unsigned int major = (version >> 16) & 0xFF; const unsigned int minor = (version >> 8) & 0xFF; const unsigned int patch = version & 0xFF; #ifndef UV_VERSION_PATCH // 0.10 doesn't have UV_VERSION_PATCH defined, so we "fake" it by using the library patch level. const unsigned int UV_VERSION_PATCH = version & 0xFF; #endif if (major != UV_VERSION_MAJOR || minor != UV_VERSION_MINOR || patch != UV_VERSION_PATCH) { throw std::runtime_error(mbgl::util::sprintf<96>( "libuv version mismatch: headers report %d.%d.%d, but library reports %d.%d.%d", UV_VERSION_MAJOR, UV_VERSION_MINOR, UV_VERSION_PATCH, major, minor, patch)); } return true; }(); using namespace mbgl; Map::Map(View& view_, FileSource& fileSource_) : env(util::make_unique(fileSource_)), view(view_), mainThread(std::this_thread::get_id()), mapThread(mainThread), transform(view_), fileSource(fileSource_), glyphAtlas(util::make_unique(1024, 1024)), glyphStore(std::make_shared(*env)), spriteAtlas(util::make_unique(512, 512)), lineAtlas(util::make_unique(512, 512)), texturePool(std::make_shared()), painter(util::make_unique(*spriteAtlas, *glyphAtlas, *lineAtlas)), annotationManager(util::make_unique()) { view.initialize(this); } Map::~Map() { if (mode == Mode::Continuous) { stop(); } // Explicitly reset all pointers. activeSources.clear(); sprite.reset(); glyphStore.reset(); style.reset(); texturePool.reset(); workers.reset(); uv_run(env->loop, UV_RUN_DEFAULT); } uv::worker &Map::getWorker() { assert(workers); return *workers; } void Map::start(bool startPaused) { assert(std::this_thread::get_id() == mainThread); assert(mode == Mode::None); // When starting map rendering in another thread, we perform async/continuously // updated rendering. Only in these cases, we attach the async handlers. mode = Mode::Continuous; // Reset the flag. isStopped = false; // Setup async notifications asyncTerminate = util::make_unique(env->loop, [this]() { assert(std::this_thread::get_id() == mapThread); // Remove all of these to make sure they are destructed in the correct thread. style.reset(); workers.reset(); activeSources.clear(); terminating = true; // Closes all open handles on the loop. This means that the loop will automatically terminate. asyncRender.reset(); asyncUpdate.reset(); asyncTerminate.reset(); }); asyncUpdate = util::make_unique(env->loop, [this] { assert(std::this_thread::get_id() == mapThread); if (state.hasSize()) { prepare(); } }); asyncRender = util::make_unique(env->loop, [this] { // Must be called in Map thread. assert(std::this_thread::get_id() == mapThread); render(); // Finally, notify all listeners that we have finished rendering this frame. { std::lock_guard lk(mutexRendered); rendered = true; } condRendered.notify_all(); }); // Do we need to pause first? if (startPaused) { pause(); } thread = std::thread([this]() { #ifdef DEBUG mapThread = std::this_thread::get_id(); #endif #ifdef __APPLE__ pthread_setname_np("Map"); #endif run(); #ifdef DEBUG mapThread = std::thread::id(); #endif // Make sure that the stop() function knows when to stop invoking the callback function. isStopped = true; view.notify(); }); } void Map::stop(std::function callback) { assert(std::this_thread::get_id() == mainThread); assert(mainThread != mapThread); assert(mode == Mode::Continuous); asyncTerminate->send(); resume(); if (callback) { // Wait until the render thread stopped. We are using this construct instead of plainly // relying on the thread_join because the system might need to run things in the current // thread that is required for the render thread to terminate correctly. This is for example // the case with Cocoa's NSURLRequest. Otherwise, we will eventually deadlock because this // thread (== main thread) is blocked. The callback function should use an efficient waiting // function to avoid a busy waiting loop. while (!isStopped) { callback(); } } // If a callback function was provided, this should return immediately because the thread has // already finished executing. thread.join(); mode = Mode::None; } void Map::pause(bool waitForPause) { assert(std::this_thread::get_id() == mainThread); assert(mode == Mode::Continuous); mutexRun.lock(); pausing = true; mutexRun.unlock(); uv_stop(env->loop); triggerUpdate(); // Needed to ensure uv_stop is seen and uv_run exits, otherwise we deadlock on wait_for_pause if (waitForPause) { std::unique_lock lockPause (mutexPause); while (!isPaused) { condPause.wait(lockPause); } } } void Map::resume() { assert(std::this_thread::get_id() == mainThread); assert(mode == Mode::Continuous); mutexRun.lock(); pausing = false; condRun.notify_all(); mutexRun.unlock(); } void Map::run() { if (mode == Mode::None) { #ifdef DEBUG mapThread = mainThread; #endif mode = Mode::Static; } assert(std::this_thread::get_id() == mapThread); if (mode == Mode::Continuous) { checkForPause(); } if (mode == Mode::Static && !style && styleURL.empty()) { throw util::Exception("Style is not set"); } view.activate(); workers = util::make_unique(env->loop, 4, "Tile Worker"); env->setup(); setup(); prepare(); if (mode == Mode::Continuous) { terminating = false; while(!terminating) { uv_run(env->loop, UV_RUN_DEFAULT); checkForPause(); } } else { uv_run(env->loop, UV_RUN_DEFAULT); } // Run the event loop once more to make sure our async delete handlers are called. uv_run(env->loop, UV_RUN_ONCE); // If the map rendering wasn't started asynchronously, we perform one render // *after* all events have been processed. if (mode == Mode::Static) { render(); #ifdef DEBUG mapThread = std::thread::id(); #endif mode = Mode::None; } view.deactivate(); } void Map::renderSync() { // Must be called in UI thread. assert(std::this_thread::get_id() == mainThread); triggerRender(); std::unique_lock lock(mutexRendered); condRendered.wait(lock, [this] { return rendered; }); rendered = false; } void Map::triggerUpdate() { if (mode == Mode::Static) { prepare(); } else if (asyncUpdate) { asyncUpdate->send(); } } void Map::triggerRender() { assert(asyncRender); asyncRender->send(); } void Map::checkForPause() { std::unique_lock lockRun (mutexRun); while (pausing) { view.deactivate(); mutexPause.lock(); isPaused = true; condPause.notify_all(); mutexPause.unlock(); condRun.wait(lockRun); view.activate(); } mutexPause.lock(); isPaused = false; mutexPause.unlock(); } void Map::terminate() { assert(painter); painter->terminate(); view.deactivate(); } #pragma mark - Setup void Map::setup() { assert(std::this_thread::get_id() == mapThread); assert(painter); painter->setup(); } void Map::setStyleURL(const std::string &url) { // TODO: Make threadsafe. styleURL = url; if (mode == Mode::Continuous) { stop(); start(); } } void Map::setStyleJSON(std::string newStyleJSON, const std::string &base) { // TODO: Make threadsafe. styleJSON.swap(newStyleJSON); sprite.reset(); if (!style) { style = std::make_shared