#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 using namespace mbgl; Map::Map(View& view) : loop(std::make_shared()), thread(std::make_unique()), async_terminate(new uv_async_t()), async_render(new uv_async_t()), async_cleanup(new uv_async_t()), view(view), #ifndef NDEBUG main_thread(uv_thread_self()), #endif transform(view), glyphAtlas(std::make_shared(1024, 1024)), spriteAtlas(std::make_shared(512, 512)), texturepool(std::make_shared()), painter(*this) { view.initialize(this); // Make sure that we're doing an initial drawing in all cases. is_clean.clear(); is_rendered.clear(); is_swapped.test_and_set(); } Map::~Map() { if (async) { stop(); } // Explicitly reset all pointers. texturepool.reset(); sprite.reset(); spriteAtlas.reset(); glyphStore.reset(); glyphAtlas.reset(); style.reset(); fileSource.reset(); workers.reset(); uv_run(**loop, UV_RUN_DEFAULT); } uv::worker &Map::getWorker() { if (!workers) { workers = std::make_unique(**loop, 4, "Tile Worker"); } return *workers; } void Map::start() { assert(uv_thread_self() == main_thread); assert(!async); // When starting map rendering in another thread, we perform async/continuously // updated rendering. Only in these cases, we attach the async handlers. async = true; // Reset the flag. is_stopped = false; // Setup async notifications uv_async_init(**loop, async_terminate.get(), terminate); async_terminate->data = this; uv_async_init(**loop, async_render.get(), render); async_render->data = this; uv_async_init(**loop, async_cleanup.get(), cleanup); async_cleanup->data = this; uv_thread_create(*thread, [](void *arg) { Map *map = static_cast(arg); #ifndef NDEBUG map->map_thread = uv_thread_self(); #endif #ifdef __APPLE__ pthread_setname_np("Map"); #endif map->run(); #ifndef NDEBUG map->map_thread = -1; #endif // Make sure that the stop() function knows when to stop invoking the callback function. map->is_stopped = true; map->view.notify(); }, this); } void Map::stop(stop_callback cb, void *data) { assert(uv_thread_self() == main_thread); assert(main_thread != map_thread); assert(async); uv_async_send(async_terminate.get()); if (cb) { // 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 (!is_stopped) { cb(data); } } // If a callback function was provided, this should return immediately because the thread has // already finished executing. uv_thread_join(*thread); async = false; } void Map::delete_async(uv_handle_t *handle, int status) { delete (uv_async_t *)handle; } void Map::run() { #ifndef NDEBUG if (!async) { map_thread = main_thread; } #endif assert(uv_thread_self() == map_thread); setup(); prepare(); uv_run(**loop, UV_RUN_DEFAULT); // Run the event loop once more to make sure our async delete handlers are called. uv_run(**loop, UV_RUN_ONCE); // If the map rendering wasn't started asynchronously, we perform one render // *after* all events have been processed. if (!async) { render(); #ifndef NDEBUG map_thread = -1; #endif } } void Map::rerender() { // We only send render events if we want to continuously update the map // (== async rendering). if (async) { uv_async_send(async_render.get()); } } void Map::update() { is_clean.clear(); rerender(); } bool Map::needsSwap() { return is_swapped.test_and_set() == false; } void Map::swapped() { is_rendered.clear(); rerender(); } void Map::cleanup() { if (async_cleanup != nullptr) { uv_async_send(async_cleanup.get()); } } void Map::cleanup(uv_async_t *async, int status) { Map *map = static_cast(async->data); map->painter.cleanup(); } void Map::terminate() { painter.terminate(); } void Map::setReachability(bool reachable) { // Note: This function may be called from *any* thread. if (reachable) { if (fileSource) { fileSource->prepare([&]() { fileSource->retryAllPending(); }); } } } void Map::render(uv_async_t *async, int status) { Map *map = static_cast(async->data); assert(uv_thread_self() == map->map_thread); if (map->state.hasSize()) { if (map->is_rendered.test_and_set() == false) { map->prepare(); if (map->is_clean.test_and_set() == false) { map->render(); map->is_swapped.clear(); map->view.swap(); } else { // We set the rendered flag in the test above, so we have to reset it // now that we're not actually rendering because the map is clean. map->is_rendered.clear(); } } } } void Map::terminate(uv_async_t *async, int status) { // Closes all open handles on the loop. This means that the loop will automatically terminate. Map *map = static_cast(async->data); assert(uv_thread_self() == map->map_thread); // Remove all of these to make sure they are destructed in the correct thread. map->glyphStore.reset(); map->fileSource.reset(); map->style.reset(); map->workers.reset(); map->activeSources.clear(); uv_close((uv_handle_t *)map->async_cleanup.get(), nullptr); uv_close((uv_handle_t *)map->async_render.get(), nullptr); uv_close((uv_handle_t *)map->async_terminate.get(), nullptr); } #pragma mark - Setup void Map::setup() { assert(uv_thread_self() == map_thread); view.make_active(); painter.setup(); view.make_inactive(); } void Map::setStyleURL(const std::string &url) { // TODO: Make threadsafe. styleURL = url; } void Map::setStyleJSON(std::string newStyleJSON, const std::string &base) { // TODO: Make threadsafe. styleJSON.swap(newStyleJSON); sprite.reset(); if (!style) { style = std::make_shared