diff options
author | Mike Morris <michael.patrick.morris@gmail.com> | 2014-10-10 12:24:45 -0400 |
---|---|---|
committer | Mike Morris <michael.patrick.morris@gmail.com> | 2014-10-10 12:24:45 -0400 |
commit | 2d1219fa5154c489cd856bedd04b84573d45ac04 (patch) | |
tree | a8e42e6acd79f73aac228e0fe6876917067db8c4 /src | |
parent | 8f6e8eead12c6b2c2de0ce76fa7df39ca2445006 (diff) | |
parent | f390dab0ea7d449bdd89855c84e47f4a07606fe4 (diff) | |
download | qtlocation-mapboxgl-2d1219fa5154c489cd856bedd04b84573d45ac04.tar.gz |
Merge branch 'master' into libuv-0.10-headless-display
Conflicts:
common/curl_request.cpp
common/glfw_view.cpp
common/glfw_view.hpp
include/mbgl/platform/request.hpp
ios/mapbox-gl-cocoa
setup-libraries.sh
src/map/map.cpp
src/platform/request.cpp
test/fixtures/fixture_request.cpp
Diffstat (limited to 'src')
42 files changed, 2518 insertions, 308 deletions
diff --git a/src/map/map.cpp b/src/map/map.cpp index ad8749ff08..9034c6d5ac 100644 --- a/src/map/map.cpp +++ b/src/map/map.cpp @@ -19,11 +19,10 @@ #include <mbgl/style/style_bucket.hpp> #include <mbgl/util/texturepool.hpp> #include <mbgl/geometry/sprite_atlas.hpp> -#include <mbgl/util/filesource.hpp> +#include <mbgl/storage/file_source.hpp> #include <mbgl/platform/log.hpp> #include <algorithm> -#include <memory> #include <iostream> #define _USE_MATH_DEFINES @@ -34,12 +33,15 @@ using namespace mbgl; Map::Map(View& view) : loop(std::make_shared<uv::loop>()), thread(std::make_unique<uv::thread>()), + 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), - fileSource(std::make_shared<FileSource>()), - style(std::make_shared<Style>()), glyphAtlas(std::make_shared<GlyphAtlas>(1024, 1024)), - glyphStore(std::make_shared<GlyphStore>(fileSource)), spriteAtlas(std::make_shared<SpriteAtlas>(512, 512)), texturepool(std::make_shared<Texturepool>()), painter(*this) { @@ -53,49 +55,93 @@ Map::Map(View& view) } Map::~Map() { - // Clear the style first before the rest of the constructor deletes members of this object. - // This is required because members of the style reference the Map object in their destructors. - style.reset(); - 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<uv::worker>(**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 - async_terminate = new uv_async_t(); - uv_async_init(**loop, async_terminate, terminate); - async_terminate->data = **loop; + uv_async_init(**loop, async_terminate.get(), terminate); + async_terminate->data = this; - async_render = new uv_async_t(); - uv_async_init(**loop, async_render, render); + uv_async_init(**loop, async_render.get(), render); async_render->data = this; - async_cleanup = new uv_async_t(); - uv_async_init(**loop, async_cleanup, cleanup); + uv_async_init(**loop, async_cleanup.get(), cleanup); async_cleanup->data = this; uv_thread_create(*thread, [](void *arg) { Map *map = static_cast<Map *>(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() { - if (async_terminate != nullptr) { - uv_async_send(async_terminate); +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); - // Run the event loop once to make sure our async delete handlers are called. - uv_run(**loop, UV_RUN_ONCE); - async = false; } @@ -104,22 +150,35 @@ void Map::delete_async(uv_handle_t *handle, int status) { } 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 && async_render != nullptr) { - uv_async_send(async_render); + if (async) { + uv_async_send(async_render.get()); } } @@ -139,7 +198,7 @@ void Map::swapped() { void Map::cleanup() { if (async_cleanup != nullptr) { - uv_async_send(async_cleanup); + uv_async_send(async_cleanup.get()); } } @@ -153,9 +212,20 @@ 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<Map *>(async->data); - + assert(uv_thread_self() == map->map_thread); if (map->state.hasSize()) { if (map->is_rendered.test_and_set() == false) { @@ -175,44 +245,48 @@ void Map::render(uv_async_t *async, int status) { void Map::terminate(uv_async_t *async, int status) { // Closes all open handles on the loop. This means that the loop will automatically terminate. - uv_loop_t *loop = static_cast<uv_loop_t *>(async->data); - uv_walk(loop, [](uv_handle_t *handle, void */*arg*/) { - if (!uv_is_closing(handle)) { - uv_close(handle, NULL); - } - }, NULL); + Map *map = static_cast<Map *>(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) { - fileSource->load(ResourceType::JSON, url, [&](platform::Response *res) { - if (res->code == 200) { - // Calculate the base - const size_t pos = url.rfind('/'); - std::string base = ""; - if (pos != std::string::npos) { - base = url.substr(0, pos + 1); - } - - this->setStyleJSON(res->body, base); - } else { - Log::Error(Event::Setup, "loading style failed: %d (%s)", res->code, res->error_message.c_str()); - } - }, loop); + // 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<Style>(); + } style->loadJSON((const uint8_t *)styleJSON.c_str()); + if (!fileSource) { + fileSource = std::make_shared<FileSource>(**loop, platform::defaultCacheDatabase()); + glyphStore = std::make_shared<GlyphStore>(fileSource); + } fileSource->setBase(base); glyphStore->setURL(util::mapbox::normalizeGlyphsURL(style->glyph_url, getAccessToken())); update(); @@ -223,6 +297,7 @@ std::string Map::getStyleJSON() const { } void Map::setAccessToken(std::string access_token) { + // TODO: Make threadsafe. accessToken.swap(access_token); } @@ -230,7 +305,7 @@ std::string Map::getAccessToken() const { return accessToken; } -std::shared_ptr<Sprite> Map::getSprite() { +util::ptr<Sprite> Map::getSprite() { const float pixelRatio = state.getPixelRatio(); const std::string &sprite_url = style->getSpriteURL(); if (!sprite || sprite->pixelRatio != pixelRatio) { @@ -460,8 +535,10 @@ void Map::setDefaultTransitionDuration(uint64_t duration_milliseconds) { } void Map::updateSources() { + assert(uv_thread_self() == map_thread); + // First, disable all existing sources. - for (const std::shared_ptr<StyleSource> &source : activeSources) { + for (const util::ptr<StyleSource> &source : activeSources) { source->enabled = false; } @@ -469,7 +546,7 @@ void Map::updateSources() { updateSources(style->layers); // Then, construct or destroy the actual source object, depending on enabled state. - for (const std::shared_ptr<StyleSource> &style_source : activeSources) { + for (const util::ptr<StyleSource> &style_source : activeSources) { if (style_source->enabled) { if (!style_source->source) { style_source->source = std::make_shared<Source>(style_source->info); @@ -481,20 +558,20 @@ void Map::updateSources() { } // Finally, remove all sources that are disabled. - util::erase_if(activeSources, [](std::shared_ptr<StyleSource> source){ + util::erase_if(activeSources, [](util::ptr<StyleSource> source){ return !source->enabled; }); } -const std::set<std::shared_ptr<StyleSource>> Map::getActiveSources() const { +const std::set<util::ptr<StyleSource>> Map::getActiveSources() const { return activeSources; } -void Map::updateSources(const std::shared_ptr<StyleLayerGroup> &group) { +void Map::updateSources(const util::ptr<StyleLayerGroup> &group) { if (!group) { return; } - for (const std::shared_ptr<StyleLayer> &layer : group->layers) { + for (const util::ptr<StyleLayer> &layer : group->layers) { if (!layer) continue; if (layer->bucket) { if (layer->bucket->style_source) { @@ -507,7 +584,7 @@ void Map::updateSources(const std::shared_ptr<StyleLayerGroup> &group) { } void Map::updateTiles() { - for (const std::shared_ptr<StyleSource> &source : getActiveSources()) { + for (const util::ptr<StyleSource> &source : getActiveSources()) { source->source->update(*this); } } @@ -515,13 +592,37 @@ void Map::updateTiles() { void Map::updateRenderState() { // Update all clipping IDs. ClipIDGenerator generator; - for (const std::shared_ptr<StyleSource> &source : getActiveSources()) { + for (const util::ptr<StyleSource> &source : getActiveSources()) { generator.update(source->source->getLoadedTiles()); source->source->updateMatrices(painter.projMatrix, state); } } void Map::prepare() { + if (!fileSource) { + fileSource = std::make_shared<FileSource>(**loop, platform::defaultCacheDatabase()); + glyphStore = std::make_shared<GlyphStore>(fileSource); + } + + if (!style) { + style = std::make_shared<Style>(); + + fileSource->request(ResourceType::JSON, styleURL)->onload([&](const Response &res) { + if (res.code == 200) { + // Calculate the base + const size_t pos = styleURL.rfind('/'); + std::string base = ""; + if (pos != std::string::npos) { + base = styleURL.substr(0, pos + 1); + } + + setStyleJSON(res.data, base); + } else { + Log::Error(Event::Setup, "loading style failed: %ld (%s)", res.code, res.message.c_str()); + } + }); + } + // Update transform transitions. animationTime = util::now(); if (transform.needsTransition()) { @@ -568,7 +669,7 @@ void Map::render() { // This guarantees that we have at least one function per tile called. // When only rendering layers via the stylesheet, it's possible that we don't // ever visit a tile during rendering. - for (const std::shared_ptr<StyleSource> &source : getActiveSources()) { + for (const util::ptr<StyleSource> &source : getActiveSources()) { source->source->finishRender(painter); } @@ -582,7 +683,7 @@ void Map::render() { view.make_inactive(); } -void Map::renderLayers(std::shared_ptr<StyleLayerGroup> group) { +void Map::renderLayers(util::ptr<StyleLayerGroup> group) { if (!group) { // Make sure that we actually do have a layer group. return; @@ -625,7 +726,7 @@ void Map::renderLayers(std::shared_ptr<StyleLayerGroup> group) { } } -void Map::renderLayer(std::shared_ptr<StyleLayer> layer_desc, RenderPass pass, const Tile::ID* id, const mat4* matrix) { +void Map::renderLayer(util::ptr<StyleLayer> layer_desc, RenderPass pass, const Tile::ID* id, const mat4* matrix) { if (layer_desc->type == StyleLayerType::Background) { // This layer defines a background color/image. diff --git a/src/map/raster_tile_data.cpp b/src/map/raster_tile_data.cpp index 182a614393..e7725b7abd 100644 --- a/src/map/raster_tile_data.cpp +++ b/src/map/raster_tile_data.cpp @@ -5,7 +5,7 @@ using namespace mbgl; -RasterTileData::RasterTileData(Tile::ID id, Map &map, const SourceInfo &source) +RasterTileData::RasterTileData(Tile::ID id, Map &map, const util::ptr<SourceInfo> &source) : TileData(id, map, source), bucket(map.getTexturepool(), properties) { } @@ -25,10 +25,10 @@ void RasterTileData::parse() { } } -void RasterTileData::render(Painter &painter, std::shared_ptr<StyleLayer> layer_desc, const mat4 &matrix) { +void RasterTileData::render(Painter &painter, util::ptr<StyleLayer> layer_desc, const mat4 &matrix) { bucket.render(painter, layer_desc, id, matrix); } -bool RasterTileData::hasData(std::shared_ptr<StyleLayer> /*layer_desc*/) const { +bool RasterTileData::hasData(util::ptr<StyleLayer> /*layer_desc*/) const { return bucket.hasData(); } diff --git a/src/map/source.cpp b/src/map/source.cpp index 083e931b7a..36f1a71c84 100644 --- a/src/map/source.cpp +++ b/src/map/source.cpp @@ -6,7 +6,7 @@ #include <mbgl/util/raster.hpp> #include <mbgl/util/string.hpp> #include <mbgl/util/texturepool.hpp> -#include <mbgl/util/filesource.hpp> +#include <mbgl/storage/file_source.hpp> #include <mbgl/util/vec.hpp> #include <mbgl/util/math.hpp> #include <mbgl/util/std.hpp> @@ -20,7 +20,7 @@ namespace mbgl { -Source::Source(SourceInfo& info) +Source::Source(const util::ptr<SourceInfo>& info) : info(info) { } @@ -29,32 +29,33 @@ Source::Source(SourceInfo& info) // The reason this isn't part of the constructor is that calling shared_from_this() in // the constructor fails. void Source::load(Map& map) { - if (info.url.empty()) { + if (info->url.empty()) { loaded = true; return; } - std::string url = util::mapbox::normalizeSourceURL(info.url, map.getAccessToken()); - std::shared_ptr<Source> source = shared_from_this(); + std::string url = util::mapbox::normalizeSourceURL(info->url, map.getAccessToken()); + util::ptr<Source> source = shared_from_this(); - map.getFileSource()->load(ResourceType::JSON, url, [source, &map](platform::Response *res) { - if (res->code != 200) { + map.getFileSource()->request(ResourceType::JSON, url)->onload([source, &map](const Response &res) { + if (res.code != 200) { Log::Warning(Event::General, "failed to load source TileJSON"); return; } rapidjson::Document d; - d.Parse<0>(res->body.c_str()); + d.Parse<0>(res.data.c_str()); if (d.HasParseError()) { Log::Warning(Event::General, "invalid source TileJSON"); return; } - source->info.parseTileJSONProperties(d); + source->info->parseTileJSONProperties(d); source->loaded = true; map.update(); + }); } @@ -98,7 +99,7 @@ void Source::drawClippingMasks(Painter &painter) { } } -void Source::render(Painter &painter, std::shared_ptr<StyleLayer> layer_desc) { +void Source::render(Painter &painter, util::ptr<StyleLayer> layer_desc) { gl::group group(std::string("layer: ") + layer_desc->id); for (const std::pair<const Tile::ID, std::unique_ptr<Tile>> &pair : tiles) { Tile &tile = *pair.second; @@ -108,7 +109,7 @@ void Source::render(Painter &painter, std::shared_ptr<StyleLayer> layer_desc) { } } -void Source::render(Painter &painter, std::shared_ptr<StyleLayer> layer_desc, const Tile::ID &id, const mat4 &matrix) { +void Source::render(Painter &painter, util::ptr<StyleLayer> layer_desc, const Tile::ID &id, const mat4 &matrix) { auto it = tiles.find(id); if (it != tiles.end() && it->second->data && it->second->data->state == TileData::State::parsed) { painter.renderTileLayer(*it->second, layer_desc, matrix); @@ -183,9 +184,9 @@ TileData::State Source::addTile(Map &map, const Tile::ID& id) { if (!new_tile.data) { // If we don't find working tile data, we're just going to load it. - if (info.type == SourceType::Vector) { + if (info->type == SourceType::Vector) { new_tile.data = std::make_shared<VectorTileData>(normalized_id, map, info); - } else if (info.type == SourceType::Raster) { + } else if (info->type == SourceType::Raster) { new_tile.data = std::make_shared<RasterTileData>(normalized_id, map, info); } else { throw std::runtime_error("source type not implemented"); @@ -199,7 +200,7 @@ TileData::State Source::addTile(Map &map, const Tile::ID& id) { } double Source::getZoom(const TransformState& state) const { - double offset = std::log(util::tileSize / info.tile_size) / std::log(2); + double offset = std::log(util::tileSize / info->tile_size) / std::log(2); offset += (state.getPixelRatio() > 1.0 ? 1 :0); return state.getZoom() + offset; } @@ -211,8 +212,8 @@ int32_t Source::coveringZoomLevel(const TransformState& state) const { std::forward_list<Tile::ID> Source::coveringTiles(const TransformState& state) const { int32_t z = coveringZoomLevel(state); - if (z < info.min_zoom) return {{}}; - if (z > info.max_zoom) z = info.max_zoom; + if (z < info->min_zoom) return {{}}; + if (z > info->max_zoom) z = info->max_zoom; // Map four viewport corners to pixel coordinates box points = state.cornersToBox(z); @@ -285,8 +286,8 @@ bool Source::updateTiles(Map &map) { std::forward_list<Tile::ID> required = coveringTiles(map.getState()); // Determine the overzooming/underzooming amounts. - int32_t minCoveringZoom = util::clamp<int32_t>(zoom - 10, info.min_zoom, info.max_zoom); - int32_t maxCoveringZoom = util::clamp<int32_t>(zoom + 1, info.min_zoom, info.max_zoom); + int32_t minCoveringZoom = util::clamp<int32_t>(zoom - 10, info->min_zoom, info->max_zoom); + int32_t maxCoveringZoom = util::clamp<int32_t>(zoom + 1, info->min_zoom, info->max_zoom); // Retain is a list of tiles that we shouldn't delete, even if they are not // the most ideal tile for the current viewport. This may include tiles like @@ -333,7 +334,7 @@ bool Source::updateTiles(Map &map) { // Remove all the expired pointers from the set. util::erase_if(tile_data, [&retain_data](std::pair<const Tile::ID, std::weak_ptr<TileData>> &pair) { - const std::shared_ptr<TileData> tile = pair.second.lock(); + const util::ptr<TileData> tile = pair.second.lock(); if (!tile) { return true; } diff --git a/src/map/sprite.cpp b/src/map/sprite.cpp index af9413a0e3..c069ece45a 100644 --- a/src/map/sprite.cpp +++ b/src/map/sprite.cpp @@ -5,7 +5,7 @@ #include <string> #include <mbgl/platform/platform.hpp> -#include <mbgl/util/filesource.hpp> +#include <mbgl/storage/file_source.hpp> #include <mbgl/util/uv_detail.hpp> #include <mbgl/util/std.hpp> @@ -22,8 +22,8 @@ SpritePosition::SpritePosition(uint16_t x, uint16_t y, uint16_t width, uint16_t sdf(sdf) { } -std::shared_ptr<Sprite> Sprite::Create(const std::string& base_url, float pixelRatio, const std::shared_ptr<FileSource> &fileSource) { - std::shared_ptr<Sprite> sprite(std::make_shared<Sprite>(Key(), base_url, pixelRatio)); +util::ptr<Sprite> Sprite::Create(const std::string& base_url, float pixelRatio, const util::ptr<FileSource> &fileSource) { + util::ptr<Sprite> sprite(std::make_shared<Sprite>(Key(), base_url, pixelRatio)); sprite->load(fileSource); return sprite; } @@ -51,7 +51,7 @@ Sprite::operator bool() const { // Note: This is a separate function that must be called exactly once after creation // The reason this isn't part of the constructor is that calling shared_from_this() in // the constructor fails. -void Sprite::load(const std::shared_ptr<FileSource> &fileSource) { +void Sprite::load(const util::ptr<FileSource> &fileSource) { if (!valid) { // Treat a non-existent sprite as a successfully loaded empty sprite. loadedImage = true; @@ -60,30 +60,30 @@ void Sprite::load(const std::shared_ptr<FileSource> &fileSource) { return; } - std::shared_ptr<Sprite> sprite = shared_from_this(); + util::ptr<Sprite> sprite = shared_from_this(); - fileSource->load(ResourceType::JSON, jsonURL, [sprite](platform::Response *res) { - if (res->code == 200) { - sprite->body.swap(res->body); + fileSource->request(ResourceType::JSON, jsonURL)->onload([sprite](const Response &res) { + if (res.code == 200) { + sprite->body = res.data; sprite->parseJSON(); sprite->complete(); } else { - Log::Warning(Event::Sprite, "Failed to load sprite info: Error %d: %s", res->code, res->error_message.c_str()); + Log::Warning(Event::Sprite, "Failed to load sprite info: Error %d: %s", res.code, res.message.c_str()); if (!sprite->future.valid()) { - sprite->promise.set_exception(std::make_exception_ptr(std::runtime_error(res->error_message))); + sprite->promise.set_exception(std::make_exception_ptr(std::runtime_error(res.message))); } } }); - fileSource->load(ResourceType::Image, spriteURL, [sprite](platform::Response *res) { - if (res->code == 200) { - sprite->image.swap(res->body); + fileSource->request(ResourceType::Image, spriteURL)->onload([sprite](const Response &res) { + if (res.code == 200) { + sprite->image = res.data; sprite->parseImage(); sprite->complete(); } else { - Log::Warning(Event::Sprite, "Failed to load sprite image: Error %d: %s", res->code, res->error_message.c_str()); + Log::Warning(Event::Sprite, "Failed to load sprite image: Error %d: %s", res.code, res.message.c_str()); if (!sprite->future.valid()) { - sprite->promise.set_exception(std::make_exception_ptr(std::runtime_error(res->error_message))); + sprite->promise.set_exception(std::make_exception_ptr(std::runtime_error(res.message))); } } }); diff --git a/src/map/tile_data.cpp b/src/map/tile_data.cpp index 57afb8dadb..44e3826bf5 100644 --- a/src/map/tile_data.cpp +++ b/src/map/tile_data.cpp @@ -4,12 +4,12 @@ #include <mbgl/util/token.hpp> #include <mbgl/util/string.hpp> -#include <mbgl/util/filesource.hpp> +#include <mbgl/storage/file_source.hpp> #include <mbgl/util/uv_detail.hpp> using namespace mbgl; -TileData::TileData(Tile::ID id, Map &map, const SourceInfo &source) +TileData::TileData(Tile::ID id, Map &map, const util::ptr<SourceInfo> &source) : id(id), state(State::initial), map(map), @@ -29,10 +29,10 @@ const std::string TileData::toString() const { } void TileData::request() { - if (source.tiles.empty()) + if (source->tiles.empty()) return; - std::string url = source.tiles[(id.x + id.y) % source.tiles.size()]; + std::string url = source->tiles[(id.x + id.y) % source->tiles.size()]; url = util::replaceTokens(url, [&](const std::string &token) -> std::string { if (token == "z") return std::to_string(id.z); if (token == "x") return std::to_string(id.x); @@ -45,21 +45,28 @@ void TileData::request() { // Note: Somehow this feels slower than the change to request_http() std::weak_ptr<TileData> weak_tile = shared_from_this(); - map.getFileSource()->load(ResourceType::Tile, url, [weak_tile, url](platform::Response *res) { - std::shared_ptr<TileData> tile = weak_tile.lock(); + req = map.getFileSource()->request(ResourceType::Tile, url); + req->onload([weak_tile, url](const Response &res) { + util::ptr<TileData> tile = weak_tile.lock(); if (!tile || tile->state == State::obsolete) { // noop. Tile is obsolete and we're now just waiting for the refcount // to drop to zero for destruction. - } else if (res->code == 200) { + return; + } + + // Clear the request object. + tile->req.reset(); + + if (res.code == 200) { tile->state = State::loaded; - tile->data.swap(res->body); + tile->data = res.data; // Schedule tile parsing in another thread tile->reparse(); } else { #if defined(DEBUG) - fprintf(stderr, "[%s] tile loading failed: %d, %s\n", url.c_str(), res->code, res->error_message.c_str()); + fprintf(stderr, "[%s] tile loading failed: %ld, %s\n", url.c_str(), res.code, res.message.c_str()); #endif } }); @@ -68,7 +75,7 @@ void TileData::request() { void TileData::cancel() { if (state != State::obsolete) { state = State::obsolete; - platform::cancel_request_http(req.lock()); + req.reset(); } } @@ -79,12 +86,12 @@ void TileData::reparse() { // We're creating a new work request. The work request deletes itself after it executed // the after work handler - new uv::work<std::shared_ptr<TileData>>( - map.getLoop(), - [](std::shared_ptr<TileData> &tile) { + new uv::work<util::ptr<TileData>>( + map.getWorker(), + [](util::ptr<TileData> &tile) { tile->parse(); }, - [](std::shared_ptr<TileData> &tile) { + [](util::ptr<TileData> &tile) { tile->afterParse(); tile->map.update(); }, diff --git a/src/map/tile_parser.cpp b/src/map/tile_parser.cpp index 6923c1a422..3dc5cb9cef 100644 --- a/src/map/tile_parser.cpp +++ b/src/map/tile_parser.cpp @@ -38,19 +38,26 @@ namespace mbgl { TileParser::~TileParser() = default; TileParser::TileParser(const std::string &data, VectorTileData &tile, - const std::shared_ptr<const Style> &style, - const std::shared_ptr<GlyphAtlas> &glyphAtlas, - const std::shared_ptr<GlyphStore> &glyphStore, - const std::shared_ptr<SpriteAtlas> &spriteAtlas, - const std::shared_ptr<Sprite> &sprite) + const util::ptr<const Style> &style_, + const util::ptr<GlyphAtlas> &glyphAtlas_, + const util::ptr<GlyphStore> &glyphStore_, + const util::ptr<SpriteAtlas> &spriteAtlas_, + const util::ptr<Sprite> &sprite_) : vector_data(pbf((const uint8_t *)data.data(), data.size())), tile(tile), - style(style), - glyphAtlas(glyphAtlas), - glyphStore(glyphStore), - spriteAtlas(spriteAtlas), - sprite(sprite), - collision(std::make_unique<Collision>(tile.id.z, 4096, tile.source.tile_size, tile.depth)) { + style(style_), + glyphAtlas(glyphAtlas_), + glyphStore(glyphStore_), + spriteAtlas(spriteAtlas_), + sprite(sprite_), + collision(std::make_unique<Collision>(tile.id.z, 4096, tile.source->tile_size, tile.depth)) { + assert(&tile != nullptr); + assert(style); + assert(glyphAtlas); + assert(glyphStore); + assert(spriteAtlas); + assert(sprite); + assert(collision); } void TileParser::parse() { @@ -59,12 +66,12 @@ void TileParser::parse() { bool TileParser::obsolete() const { return tile.state == TileData::State::obsolete; } -void TileParser::parseStyleLayers(std::shared_ptr<StyleLayerGroup> group) { +void TileParser::parseStyleLayers(util::ptr<StyleLayerGroup> group) { if (!group) { return; } - for (const std::shared_ptr<StyleLayer> &layer_desc : group->layers) { + for (const util::ptr<StyleLayer> &layer_desc : group->layers) { // Cancel early when parsing. if (obsolete()) { return; @@ -96,14 +103,14 @@ void TileParser::parseStyleLayers(std::shared_ptr<StyleLayerGroup> group) { } } -std::unique_ptr<Bucket> TileParser::createBucket(std::shared_ptr<StyleBucket> bucket_desc) { +std::unique_ptr<Bucket> TileParser::createBucket(util::ptr<StyleBucket> bucket_desc) { if (!bucket_desc) { fprintf(stderr, "missing bucket desc\n"); return nullptr; } // Skip this bucket if we are to not render this - if (tile.id.z < std::floor(bucket_desc->min_zoom) && std::floor(bucket_desc->min_zoom) < tile.source.max_zoom) return nullptr; + if (tile.id.z < std::floor(bucket_desc->min_zoom) && std::floor(bucket_desc->min_zoom) < tile.source->max_zoom) return nullptr; if (tile.id.z >= std::ceil(bucket_desc->max_zoom)) return nullptr; auto layer_it = vector_data.layers.find(bucket_desc->source_layer); @@ -157,7 +164,7 @@ std::unique_ptr<Bucket> TileParser::createFillBucket(const VectorTileLayer& laye return obsolete() ? nullptr : std::move(bucket); } -std::unique_ptr<Bucket> TileParser::createRasterBucket(const std::shared_ptr<Texturepool> &texturepool, const StyleBucketRaster &raster) { +std::unique_ptr<Bucket> TileParser::createRasterBucket(const util::ptr<Texturepool> &texturepool, const StyleBucketRaster &raster) { std::unique_ptr<RasterBucket> bucket = std::make_unique<RasterBucket>(texturepool, raster); return obsolete() ? nullptr : std::move(bucket); } diff --git a/src/map/transform.cpp b/src/map/transform.cpp index 6c5e70cc2f..baa615b94a 100644 --- a/src/map/transform.cpp +++ b/src/map/transform.cpp @@ -466,7 +466,7 @@ bool Transform::needsTransition() const { void Transform::updateTransitions(const timestamp now) { uv::writelock lock(mtx); - transitions.remove_if([now](const std::shared_ptr<util::transition> &transition) { + transitions.remove_if([now](const util::ptr<util::transition> &transition) { return transition->update(now) == util::transition::complete; }); } diff --git a/src/map/vector_tile_data.cpp b/src/map/vector_tile_data.cpp index 1f74cab1d7..48b46059a5 100644 --- a/src/map/vector_tile_data.cpp +++ b/src/map/vector_tile_data.cpp @@ -8,13 +8,13 @@ using namespace mbgl; -VectorTileData::VectorTileData(Tile::ID id, Map &map, const SourceInfo &source) +VectorTileData::VectorTileData(Tile::ID id, Map &map, const util::ptr<SourceInfo> &source) : TileData(id, map, source), - depth(id.z >= source.max_zoom ? map.getMaxZoom() - id.z : 1) { + depth(id.z >= source->max_zoom ? map.getMaxZoom() - id.z : 1) { } VectorTileData::~VectorTileData() { - std::shared_ptr<GlyphAtlas> glyphAtlas = map.getGlyphAtlas(); + util::ptr<GlyphAtlas> glyphAtlas = map.getGlyphAtlas(); if (glyphAtlas) { glyphAtlas->removeGlyphs(id.to_uint64()); } @@ -52,7 +52,7 @@ void VectorTileData::afterParse() { parser.reset(); } -void VectorTileData::render(Painter &painter, std::shared_ptr<StyleLayer> layer_desc, const mat4 &matrix) { +void VectorTileData::render(Painter &painter, util::ptr<StyleLayer> layer_desc, const mat4 &matrix) { if (state == State::parsed && layer_desc->bucket) { auto databucket_it = buckets.find(layer_desc->bucket->name); if (databucket_it != buckets.end()) { @@ -62,7 +62,7 @@ void VectorTileData::render(Painter &painter, std::shared_ptr<StyleLayer> layer_ } } -bool VectorTileData::hasData(std::shared_ptr<StyleLayer> layer_desc) const { +bool VectorTileData::hasData(util::ptr<StyleLayer> layer_desc) const { if (state == State::parsed && layer_desc->bucket) { auto databucket_it = buckets.find(layer_desc->bucket->name); if (databucket_it != buckets.end()) { diff --git a/src/platform/request.cpp b/src/platform/request.cpp deleted file mode 100644 index 82e80b9a7c..0000000000 --- a/src/platform/request.cpp +++ /dev/null @@ -1,54 +0,0 @@ -#include <mbgl/platform/request.hpp> -#include <mbgl/platform/platform.hpp> -#include <mbgl/util/std.hpp> -#include <mbgl/util/uv_detail.hpp> - -using namespace mbgl::platform; - -Request::Request(const std::string &url, - std::function<void(Response *)> callback, - std::shared_ptr<uv::loop> loop) - : url(url), - res(std::make_unique<Response>(callback)), - cancelled(false), - loop(loop) { - if (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 - // beginning, because we're using asynchronous messaging to perform the actual - // request in the request thread. Only after the request is complete, we - // create an actual work request that is attached to the default loop. - async = new uv_async_t(); - async->data = new std::unique_ptr<Response>(); - uv_async_init(**loop, async, &complete); - } -} - -Request::~Request() { -} - -void Request::complete() { - if (loop) { - // We're scheduling the response callback to be invoked in the event loop. - // Since the Request object will be deleted before the callback is invoked, - // we move over the Response object to be owned by the async handle. - ((std::unique_ptr<Response> *)async->data)->swap(res); - uv_async_send(async); - } else { - // We're calling the response callback immediately. We're currently on an - // arbitrary thread, but that's okay. - res->callback(res.get()); - } -} - -void Request::complete(uv_async_t *async, int status) { - Response *res = static_cast<std::unique_ptr<Response> *>(async->data)->get(); - - res->callback(res); - - // We need to remove our async handle again to allow the main event loop to exit. - uv_close((uv_handle_t *)async, [](uv_handle_t *handle) { - delete static_cast<std::unique_ptr<Response> *>(handle->data); - delete (uv_async_t *)handle; - }); -} diff --git a/src/renderer/debug_bucket.cpp b/src/renderer/debug_bucket.cpp index a3ac329f49..699c1c1db9 100644 --- a/src/renderer/debug_bucket.cpp +++ b/src/renderer/debug_bucket.cpp @@ -14,7 +14,7 @@ DebugBucket::DebugBucket(DebugFontBuffer& fontBuffer) : fontBuffer(fontBuffer) { } -void DebugBucket::render(Painter& painter, std::shared_ptr<StyleLayer> /*layer_desc*/, const Tile::ID& /*id*/, const mat4 &matrix) { +void DebugBucket::render(Painter& painter, util::ptr<StyleLayer> /*layer_desc*/, const Tile::ID& /*id*/, const mat4 &matrix) { painter.renderDebugText(*this, matrix); } diff --git a/src/renderer/fill_bucket.cpp b/src/renderer/fill_bucket.cpp index 875cc279b9..5358cae0b2 100644 --- a/src/renderer/fill_bucket.cpp +++ b/src/renderer/fill_bucket.cpp @@ -204,7 +204,7 @@ void FillBucket::tessellate() { lineGroup.vertex_length += total_vertex_count; } -void FillBucket::render(Painter& painter, std::shared_ptr<StyleLayer> layer_desc, const Tile::ID& id, const mat4 &matrix) { +void FillBucket::render(Painter& painter, util::ptr<StyleLayer> layer_desc, const Tile::ID& id, const mat4 &matrix) { painter.renderFill(*this, layer_desc, id, matrix); } diff --git a/src/renderer/line_bucket.cpp b/src/renderer/line_bucket.cpp index 2e89a7c35d..3ef7411be6 100644 --- a/src/renderer/line_bucket.cpp +++ b/src/renderer/line_bucket.cpp @@ -341,7 +341,7 @@ void LineBucket::addGeometry(const std::vector<Coordinate>& vertices) { } } -void LineBucket::render(Painter& painter, std::shared_ptr<StyleLayer> layer_desc, const Tile::ID& id, const mat4 &matrix) { +void LineBucket::render(Painter& painter, util::ptr<StyleLayer> layer_desc, const Tile::ID& id, const mat4 &matrix) { painter.renderLine(*this, layer_desc, id, matrix); } diff --git a/src/renderer/painter.cpp b/src/renderer/painter.cpp index 0d01fd1662..8988112585 100644 --- a/src/renderer/painter.cpp +++ b/src/renderer/painter.cpp @@ -197,7 +197,7 @@ void Painter::prepareTile(const Tile& tile) { glStencilFunc(GL_EQUAL, ref, mask); } -void Painter::renderTileLayer(const Tile& tile, std::shared_ptr<StyleLayer> layer_desc, const mat4 &matrix) { +void Painter::renderTileLayer(const Tile& tile, util::ptr<StyleLayer> layer_desc, const mat4 &matrix) { assert(tile.data); if (tile.data->hasData(layer_desc) || layer_desc->type == StyleLayerType::Raster) { gl::group group(util::sprintf<32>("render %d/%d/%d\n", tile.id.z, tile.id.y, tile.id.z)); @@ -206,7 +206,7 @@ void Painter::renderTileLayer(const Tile& tile, std::shared_ptr<StyleLayer> laye } } -void Painter::renderBackground(std::shared_ptr<StyleLayer> layer_desc) { +void Painter::renderBackground(util::ptr<StyleLayer> layer_desc) { const BackgroundProperties& properties = layer_desc->getProperties<BackgroundProperties>(); Color color = properties.color; diff --git a/src/renderer/painter_clipping.cpp b/src/renderer/painter_clipping.cpp index 45b14a2f78..dc625ded4e 100644 --- a/src/renderer/painter_clipping.cpp +++ b/src/renderer/painter_clipping.cpp @@ -6,7 +6,7 @@ using namespace mbgl; -void Painter::drawClippingMasks(const std::set<std::shared_ptr<StyleSource>> &sources) { +void Painter::drawClippingMasks(const std::set<util::ptr<StyleSource>> &sources) { gl::group group("clipping masks"); useProgram(plainShader->program); @@ -17,7 +17,7 @@ void Painter::drawClippingMasks(const std::set<std::shared_ptr<StyleSource>> &so coveringPlainArray.bind(*plainShader, tileStencilBuffer, BUFFER_OFFSET(0)); - for (const std::shared_ptr<StyleSource> &source : sources) { + for (const util::ptr<StyleSource> &source : sources) { source->source->drawClippingMasks(*this); } diff --git a/src/renderer/painter_fill.cpp b/src/renderer/painter_fill.cpp index 6d56b9077e..6c17ab037a 100644 --- a/src/renderer/painter_fill.cpp +++ b/src/renderer/painter_fill.cpp @@ -10,7 +10,7 @@ using namespace mbgl; -void Painter::renderFill(FillBucket& bucket, std::shared_ptr<StyleLayer> layer_desc, const Tile::ID& id, const mat4 &matrix) { +void Painter::renderFill(FillBucket& bucket, util::ptr<StyleLayer> layer_desc, const Tile::ID& id, const mat4 &matrix) { // Abort early. if (!bucket.hasData()) return; diff --git a/src/renderer/painter_line.cpp b/src/renderer/painter_line.cpp index 161abcd6ff..cd973b46d6 100644 --- a/src/renderer/painter_line.cpp +++ b/src/renderer/painter_line.cpp @@ -8,7 +8,7 @@ using namespace mbgl; -void Painter::renderLine(LineBucket& bucket, std::shared_ptr<StyleLayer> layer_desc, const Tile::ID& id, const mat4 &matrix) { +void Painter::renderLine(LineBucket& bucket, util::ptr<StyleLayer> layer_desc, const Tile::ID& id, const mat4 &matrix) { // Abort early. if (pass == RenderPass::Opaque) return; if (!bucket.hasData()) return; @@ -62,7 +62,7 @@ void Painter::renderLine(LineBucket& bucket, std::shared_ptr<StyleLayer> layer_d bucket.drawPoints(*linejoinShader); } - const std::shared_ptr<Sprite> &sprite = map.getSprite(); + const util::ptr<Sprite> &sprite = map.getSprite(); if (properties.image.size() && sprite) { SpriteAtlasPosition imagePos = map.getSpriteAtlas()->getPosition(properties.image, *sprite); diff --git a/src/renderer/painter_raster.cpp b/src/renderer/painter_raster.cpp index 7f7afc3e84..c362042e55 100644 --- a/src/renderer/painter_raster.cpp +++ b/src/renderer/painter_raster.cpp @@ -9,7 +9,7 @@ using namespace mbgl; -void Painter::renderRaster(RasterBucket& bucket, std::shared_ptr<StyleLayer> layer_desc, const Tile::ID& id, const mat4 &matrix) { +void Painter::renderRaster(RasterBucket& bucket, util::ptr<StyleLayer> layer_desc, const Tile::ID& id, const mat4 &matrix) { if (pass != RenderPass::Translucent) return; const RasterProperties &properties = layer_desc->getProperties<RasterProperties>(); @@ -34,7 +34,7 @@ void Painter::renderRaster(RasterBucket& bucket, std::shared_ptr<StyleLayer> lay // call updateTiles to get parsed data for sublayers map.updateTiles(); - for (const std::shared_ptr<StyleLayer> &layer : layer_desc->layers->layers) { + for (const util::ptr<StyleLayer> &layer : layer_desc->layers->layers) { setOpaque(); map.renderLayer(layer, RenderPass::Opaque, &id, &preMatrix); setTranslucent(); diff --git a/src/renderer/painter_symbol.cpp b/src/renderer/painter_symbol.cpp index 4fce66217f..415dead70e 100644 --- a/src/renderer/painter_symbol.cpp +++ b/src/renderer/painter_symbol.cpp @@ -112,7 +112,7 @@ void Painter::renderSDF(SymbolBucket &bucket, } } -void Painter::renderSymbol(SymbolBucket &bucket, std::shared_ptr<StyleLayer> layer_desc, const Tile::ID &id, const mat4 &matrix) { +void Painter::renderSymbol(SymbolBucket &bucket, util::ptr<StyleLayer> layer_desc, const Tile::ID &id, const mat4 &matrix) { // Abort early. if (pass == RenderPass::Opaque) { return; diff --git a/src/renderer/raster_bucket.cpp b/src/renderer/raster_bucket.cpp index 492eea980d..fc5e3dd3c8 100644 --- a/src/renderer/raster_bucket.cpp +++ b/src/renderer/raster_bucket.cpp @@ -3,13 +3,13 @@ using namespace mbgl; -RasterBucket::RasterBucket(const std::shared_ptr<Texturepool> &texturepool, const StyleBucketRaster& properties) +RasterBucket::RasterBucket(const util::ptr<Texturepool> &texturepool, const StyleBucketRaster& properties) : properties(properties), texture(properties), raster(texturepool) { } -void RasterBucket::render(Painter &painter, std::shared_ptr<StyleLayer> layer_desc, const Tile::ID &id, const mat4 &matrix) { +void RasterBucket::render(Painter &painter, util::ptr<StyleLayer> layer_desc, const Tile::ID &id, const mat4 &matrix) { painter.renderRaster(*this, layer_desc, id, matrix); } diff --git a/src/renderer/symbol_bucket.cpp b/src/renderer/symbol_bucket.cpp index 374ea2dc26..3a4b017ef3 100644 --- a/src/renderer/symbol_bucket.cpp +++ b/src/renderer/symbol_bucket.cpp @@ -22,7 +22,7 @@ namespace mbgl { SymbolBucket::SymbolBucket(const StyleBucketSymbol &properties, Collision &collision) : properties(properties), collision(collision) {} -void SymbolBucket::render(Painter &painter, std::shared_ptr<StyleLayer> layer_desc, +void SymbolBucket::render(Painter &painter, util::ptr<StyleLayer> layer_desc, const Tile::ID &id, const mat4 &matrix) { painter.renderSymbol(*this, layer_desc, id, matrix); } diff --git a/src/storage/base_request.cpp b/src/storage/base_request.cpp new file mode 100644 index 0000000000..e87679ae1b --- /dev/null +++ b/src/storage/base_request.cpp @@ -0,0 +1,86 @@ +#include <mbgl/storage/base_request.hpp> +#include <mbgl/storage/response.hpp> +#include <mbgl/storage/request.hpp> + +#include <uv.h> + +#include <cassert> + +namespace mbgl { + +template <typename T, typename ...Args> +void invoke(const std::forward_list<std::unique_ptr<Callback>> &list, Args&& ...args) { + for (const std::unique_ptr<Callback> &callback : list) { + assert(callback); + if (callback->is<T>()) { + callback->get<T>()(::std::forward<Args>(args)...); + } + } +} + +BaseRequest::BaseRequest(const std::string &path_) : thread_id(uv_thread_self()), path(path_) { +} + +// A base request can only be "canceled" by destroying the object. In that case, we'll have to +// notify all cancel callbacks. +BaseRequest::~BaseRequest() { + assert(thread_id == uv_thread_self()); + notify(); +} + +void BaseRequest::retryImmediately() { + // no-op. override in child class. +} + +void BaseRequest::notify() { + assert(thread_id == uv_thread_self()); + + // The parameter exists solely so that any calls to ->remove() + // are not going to cause deallocation of this object while this call is in progress. + util::ptr<BaseRequest> retain = self; + + // Swap the lists so that it's safe for callbacks to call ->cancel() + // on the request object, which would modify the list. + const std::forward_list<std::unique_ptr<Callback>> list = std::move(callbacks); + callbacks.clear(); + + if (response) { + invoke<CompletedCallback>(list, *response); + } else { + invoke<AbortedCallback>(list); + } + + self.reset(); +} + +Callback *BaseRequest::add(Callback &&callback, const util::ptr<BaseRequest> &request) { + assert(thread_id == uv_thread_self()); + assert(this == request.get()); + + if (response) { + // We already have a response. Notify right away. + if (callback.is<CompletedCallback>()) { + callback.get<CompletedCallback>()(*response); + } else { + // We already know that this request was successful. The AbortedCallback will be discarded + // here since it would never be called. + } + return nullptr; + } else { + self = request; + callbacks.push_front(std::unique_ptr<Callback>(new Callback(std::move(callback)))); + return callbacks.front().get(); + } +} + +void BaseRequest::remove(Callback *callback) { + assert(thread_id == uv_thread_self()); + callbacks.remove_if([=](const std::unique_ptr<Callback> &cb) { + return cb.get() == callback; + }); + if (callbacks.empty()) { + self.reset(); + } +} + +} diff --git a/src/storage/file_request.cpp b/src/storage/file_request.cpp new file mode 100644 index 0000000000..33398d5ef1 --- /dev/null +++ b/src/storage/file_request.cpp @@ -0,0 +1,37 @@ +#include <mbgl/storage/file_request.hpp> +#include <mbgl/storage/file_request_baton.hpp> +#include <mbgl/storage/response.hpp> + +#include <uv.h> + +#include <cassert> + +#include <unistd.h> + +namespace mbgl { + +FileRequest::FileRequest(const std::string &path_, uv_loop_t *loop) + : BaseRequest(path_), ptr(new FileRequestBaton(this, path, loop)) { +} + +void FileRequest::cancel() { + assert(thread_id == uv_thread_self()); + + if (ptr) { + ptr->cancel(); + + // When deleting a FileRequest object with a uv_fs_* call is in progress, we are making sure + // that the callback doesn't accidentally reference this object again. + ptr->request = nullptr; + ptr = nullptr; + } + + notify(); +} + +FileRequest::~FileRequest() { + assert(thread_id == uv_thread_self()); + cancel(); +} + +} diff --git a/src/storage/file_request_baton.cpp b/src/storage/file_request_baton.cpp new file mode 100644 index 0000000000..127c78f3e5 --- /dev/null +++ b/src/storage/file_request_baton.cpp @@ -0,0 +1,142 @@ +#include <mbgl/storage/file_request_baton.hpp> +#include <mbgl/storage/file_request.hpp> +#include <mbgl/storage/response.hpp> + +namespace mbgl { + +FileRequestBaton::FileRequestBaton(FileRequest *request_, const std::string &path, uv_loop_t *loop) + : thread_id(uv_thread_self()), request(request_) { + req.data = this; + uv_fs_open(loop, &req, path.c_str(), O_RDONLY, S_IRUSR, file_opened); +} + +FileRequestBaton::~FileRequestBaton() { +} + +void FileRequestBaton::cancel() { + canceled = true; + + // uv_cancel fails frequently when the request has already been started. + // In that case, we have to let it complete and check the canceled bool + // instead. + uv_cancel((uv_req_t *)&req); +} + +void FileRequestBaton::notify_error(uv_fs_t *req) { + FileRequestBaton *ptr = (FileRequestBaton *)req->data; + assert(ptr->thread_id == uv_thread_self()); + + if (ptr->request && req->result < 0 && !ptr->canceled && req->result != UV_ECANCELED) { + ptr->request->response = std::unique_ptr<Response>(new Response); + ptr->request->response->code = req->result == UV_ENOENT ? 404 : 500; + ptr->request->response->message = uv_strerror(int(req->result)); + ptr->request->notify(); + } +} + +void FileRequestBaton::file_opened(uv_fs_t *req) { + FileRequestBaton *ptr = (FileRequestBaton *)req->data; + assert(ptr->thread_id == uv_thread_self()); + + if (req->result < 0) { + // Opening failed or was canceled. There isn't much left we can do. + notify_error(req); + cleanup(req); + } else { + const uv_file fd = uv_file(req->result); + + // We're going to reuse this handle, so we need to cleanup first. + uv_fs_req_cleanup(req); + + if (ptr->canceled || !ptr->request) { + // Either the FileRequest object has been destructed, or the + // request was canceled. + uv_fs_close(req->loop, req, fd, file_closed); + } else { + ptr->fd = fd; + uv_fs_fstat(req->loop, req, fd, file_stated); + } + } +} + +void FileRequestBaton::file_stated(uv_fs_t *req) { + FileRequestBaton *ptr = (FileRequestBaton *)req->data; + assert(ptr->thread_id == uv_thread_self()); + + if (req->result < 0 || ptr->canceled || !ptr->request) { + // Stating failed or was canceled. We already have an open file handle + // though, which we'll have to close. + notify_error(req); + + uv_fs_req_cleanup(req); + uv_fs_close(req->loop, req, ptr->fd, file_closed); + } else { + if (static_cast<const uv_stat_t *>(req->ptr)->st_size > std::numeric_limits<int>::max()) { + // File is too large for us to open this way because uv_buf's only support unsigned + // ints as maximum size. + if (ptr->request) { + ptr->request->response = std::unique_ptr<Response>(new Response); + ptr->request->response->code = UV_EFBIG; + ptr->request->response->message = uv_strerror(UV_EFBIG); + ptr->request->notify(); + } + + uv_fs_req_cleanup(req); + uv_fs_close(req->loop, req, ptr->fd, file_closed); + } else { + const unsigned int size = + (unsigned int)(static_cast<const uv_stat_t *>(req->ptr)->st_size); + ptr->body.resize(size); + ptr->buffer = uv_buf_init(const_cast<char *>(ptr->body.data()), size); + uv_fs_req_cleanup(req); + uv_fs_read(req->loop, req, ptr->fd, &ptr->buffer, 1, 0, file_read); + } + } +} + +void FileRequestBaton::file_read(uv_fs_t *req) { + FileRequestBaton *ptr = (FileRequestBaton *)req->data; + assert(ptr->thread_id == uv_thread_self()); + + if (req->result < 0 || ptr->canceled || !ptr->request) { + // Stating failed or was canceled. We already have an open file handle + // though, which we'll have to close. + notify_error(req); + } else { + // File was successfully read. + if (ptr->request) { + ptr->request->response = std::unique_ptr<Response>(new Response); + ptr->request->response->code = 200; + ptr->request->response->data = std::move(ptr->body); + ptr->request->notify(); + } + } + + uv_fs_req_cleanup(req); + uv_fs_close(req->loop, req, ptr->fd, file_closed); +} + +void FileRequestBaton::file_closed(uv_fs_t *req) { + FileRequestBaton *ptr = (FileRequestBaton *)req->data; + assert(ptr->thread_id == uv_thread_self()); + + if (req->result < 0) { + // Closing the file failed. But there isn't anything we can do. + } + + cleanup(req); +} + +void FileRequestBaton::cleanup(uv_fs_t *req) { + FileRequestBaton *ptr = (FileRequestBaton *)req->data; + assert(ptr->thread_id == uv_thread_self()); + + if (ptr->request) { + ptr->request->ptr = nullptr; + } + + uv_fs_req_cleanup(req); + delete ptr; +} + +} diff --git a/src/storage/file_source.cpp b/src/storage/file_source.cpp new file mode 100644 index 0000000000..12d924416c --- /dev/null +++ b/src/storage/file_source.cpp @@ -0,0 +1,105 @@ +#include <mbgl/storage/file_source.hpp> +#include <mbgl/storage/file_request.hpp> +#include <mbgl/storage/http_request.hpp> +#include <mbgl/storage/sqlite_store.hpp> +#include <mbgl/util/uv-messenger.h> + +#include <uv.h> + +namespace mbgl { + +FileSource::FileSource(uv_loop_t *loop_, const std::string &path) + : thread_id(uv_thread_self()), + store(!path.empty() ? util::ptr<SQLiteStore>(new SQLiteStore(loop_, path)) : nullptr), + loop(loop_), + queue(new uv_messenger_t) { + + uv_messenger_init(loop, queue, [](void *ptr) { + std::unique_ptr<std::function<void()>> fn { reinterpret_cast<std::function<void()> *>(ptr) }; + (*fn)(); + }); + uv_unref((uv_handle_t *)&queue->async); +} + +FileSource::~FileSource() { + assert(thread_id == uv_thread_self()); + uv_messenger_stop(queue); + // NOTE: We don't need to delete the messenger since it will be deleted by the + // uv_messenger_stop() function. + + util::ptr<BaseRequest> request; + + // Send a cancel() message to all requests that we are still holding. + for (const std::pair<std::string, std::weak_ptr<BaseRequest>> &pair : pending) { + if ((request = pair.second.lock())) { + request->cancel(); + } + } +} + +void FileSource::setBase(const std::string &value) { + assert(thread_id == uv_thread_self()); + base = value; +} + +const std::string &FileSource::getBase() const { + assert(thread_id == uv_thread_self()); + return base; +} + +std::unique_ptr<Request> FileSource::request(ResourceType type, const std::string &url) { + assert(thread_id == uv_thread_self()); + + // Make URL absolute. + const std::string absoluteURL = [&]() -> std::string { + const size_t separator = url.find("://"); + if (separator == std::string::npos) { + // Relative URL. + return base + url; + } else { + return url; + } + }(); + + util::ptr<BaseRequest> request; + + // First, try to find an existing Request object. + auto it = pending.find(absoluteURL); + if (it != pending.end()) { + request = it->second.lock(); + } + + if (!request) { + if (absoluteURL.substr(0, 7) == "file://") { + request = std::make_shared<FileRequest>(absoluteURL.substr(7), loop); + } else { + request = std::make_shared<HTTPRequest>(type, absoluteURL, loop, store); + } + + pending.emplace(absoluteURL, request); + } + + return std::unique_ptr<Request>(new Request(request)); +} + +void FileSource::prepare(std::function<void()> fn) { + if (thread_id == uv_thread_self()) { + fn(); + } else { + uv_messenger_send(queue, new std::function<void()>(std::move(fn))); + } +} + +void FileSource::retryAllPending() { + assert(thread_id == uv_thread_self()); + + util::ptr<BaseRequest> request; + for (const std::pair<std::string, std::weak_ptr<BaseRequest>> &pair : pending) { + if ((request = pair.second.lock())) { + request->retryImmediately(); + } + } + +} + +}
\ No newline at end of file diff --git a/src/storage/http_request.cpp b/src/storage/http_request.cpp new file mode 100644 index 0000000000..1b799d4895 --- /dev/null +++ b/src/storage/http_request.cpp @@ -0,0 +1,270 @@ +#include <mbgl/storage/http_request.hpp> +#include <mbgl/storage/sqlite_store.hpp> +#include <mbgl/storage/http_request_baton.hpp> + +#include <uv.h> + +#include <cassert> +#include <chrono> + +namespace mbgl { + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdisabled-macro-expansion" +#pragma clang diagnostic ignored "-Wexit-time-destructors" +#pragma clang diagnostic ignored "-Wglobal-constructors" + +struct CacheRequestBaton { + HTTPRequest *request = nullptr; + std::string path; + util::ptr<SQLiteStore> store; +}; + +HTTPRequest::HTTPRequest(ResourceType type_, const std::string &path, uv_loop_t *loop_, util::ptr<SQLiteStore> store_) + : BaseRequest(path), thread_id(uv_thread_self()), loop(loop_), store(store_), type(type_) { + if (store) { + startCacheRequest(); + } else { + startHTTPRequest(nullptr); + } +} + +void HTTPRequest::startCacheRequest() { + assert(uv_thread_self() == thread_id); + + cache_baton = new CacheRequestBaton; + cache_baton->request = this; + cache_baton->path = path; + cache_baton->store = store; + store->get(path, [](std::unique_ptr<Response> &&response, void *ptr) { + // Wrap in a unique_ptr, so it'll always get auto-destructed. + std::unique_ptr<CacheRequestBaton> baton((CacheRequestBaton *)ptr); + if (baton->request) { + baton->request->cache_baton = nullptr; + baton->request->handleCacheResponse(std::move(response)); + } + }, cache_baton); +} + +void HTTPRequest::handleCacheResponse(std::unique_ptr<Response> &&res) { + assert(uv_thread_self() == thread_id); + + if (res) { + // This entry was stored in the cache. Now determine if we need to revalidate. + const int64_t now = std::chrono::duration_cast<std::chrono::seconds>( + std::chrono::system_clock::now().time_since_epoch()).count(); + if (res->expires > now) { + response = std::move(res); + notify(); + // Note: after calling notify(), the request object may cease to exist. + // This HTTPRequest is completed. + return; + } else { + // TODO: notify with preliminary results. + } + } + + startHTTPRequest(std::move(res)); +} + +void HTTPRequest::startHTTPRequest(std::unique_ptr<Response> &&res) { + assert(uv_thread_self() == thread_id); + assert(!http_baton); + + http_baton = std::make_shared<HTTPRequestBaton>(path); + http_baton->request = this; + http_baton->async = new uv_async_t; + http_baton->response = std::move(res); + http_baton->async->data = new util::ptr<HTTPRequestBaton>(http_baton); + + uv_async_init(loop, http_baton->async, [](uv_async_t *async) { + util::ptr<HTTPRequestBaton> &http_baton = *(util::ptr<HTTPRequestBaton> *)async->data; + + if (http_baton->request) { + HTTPRequest *request = http_baton->request; + request->http_baton.reset(); + http_baton->request = nullptr; + request->handleHTTPResponse(http_baton->type, std::move(http_baton->response)); + } + + delete (util::ptr<HTTPRequestBaton> *)async->data; + uv_close((uv_handle_t *)async, [](uv_handle_t *handle) { + uv_async_t *async = (uv_async_t *)handle; + delete async; + }); + }); + attempts++; + HTTPRequestBaton::start(http_baton); +} + + + +void HTTPRequest::handleHTTPResponse(HTTPResponseType responseType, std::unique_ptr<Response> &&res) { + assert(uv_thread_self() == thread_id); + assert(!http_baton); + assert(!response); + + switch (responseType) { + // This error was caused by a temporary error and it is likely that it will be resolved + // immediately. We are going to try again right away. This is like the TemporaryError, + // except that we will not perform exponential back-off. + case HTTPResponseType::SingularError: + if (attempts >= 4) { + // Report as error after 4 attempts. + response = std::move(res); + notify(); + } else if (attempts >= 2) { + // Switch to the back-off algorithm after the second failure. + retryHTTPRequest(std::move(res), (1 << attempts) * 1000); + return; + } else { + startHTTPRequest(std::move(res)); + } + break; + + // This error might be resolved by waiting some time (e.g. server issues). + // We are going to do an exponential back-off and will try again in a few seconds. + case HTTPResponseType::TemporaryError: + if (attempts >= 4) { + // Report error back after it failed completely. + response = std::move(res); + notify(); + } else { + retryHTTPRequest(std::move(res), (1 << attempts) * 1000); + } + break; + + // This error might be resolved once the network reachability status changes. + // We are going to watch the network status for changes and will retry as soon as the + // operating system notifies us of a network status change. + case HTTPResponseType::ConnectionError: + + if (attempts >= 4) { + // Report error back after it failed completely. + response = std::move(res); + notify(); + } else { + // By default, we will retry every 60 seconds. + retryHTTPRequest(std::move(res), 60000); + } + break; + + // The request was canceled programatically. + case HTTPResponseType::Canceled: + response.reset(); + notify(); + break; + + // This error probably won't be resolved by retrying anytime soon. We are giving up. + case HTTPResponseType::PermanentError: + response = std::move(res); + notify(); + break; + + // The request returned data successfully. We retrieved and decoded the data successfully. + case HTTPResponseType::Successful: + if (store) { + store->put(path, type, *res); + } + response = std::move(res); + notify(); + break; + + // The request confirmed that the data wasn't changed. We already have the data. + case HTTPResponseType::NotModified: + if (store) { + store->updateExpiration(path, res->expires); + } + response = std::move(res); + notify(); + break; + + default: + assert(!"Response wasn't set"); + break; + } +} + +using RetryBaton = std::pair<HTTPRequest *, std::unique_ptr<Response>>; + +void HTTPRequest::retryHTTPRequest(std::unique_ptr<Response> &&res, uint64_t timeout) { + assert(uv_thread_self() == thread_id); + assert(!backoff_timer); + backoff_timer = new uv_timer_t(); + uv_timer_init(loop, backoff_timer); + backoff_timer->data = new RetryBaton(this, std::move(res)); + uv_timer_start(backoff_timer, [](uv_timer_t *timer) { + std::unique_ptr<RetryBaton> pair { static_cast<RetryBaton *>(timer->data) }; + pair->first->startHTTPRequest(std::move(pair->second)); + pair->first->backoff_timer = nullptr; + uv_timer_stop(timer); + uv_close((uv_handle_t *)timer, [](uv_handle_t *handle) { delete (uv_timer_t *)handle; }); + }, timeout, 0); +} + +void HTTPRequest::removeHTTPBaton() { + assert(uv_thread_self() == thread_id); + if (http_baton) { + http_baton->request = nullptr; + HTTPRequestBaton::stop(http_baton); + http_baton.reset(); + } +} + +void HTTPRequest::removeCacheBaton() { + assert(uv_thread_self() == thread_id); + if (cache_baton) { + // Make sre that this object doesn't accidentally get accessed when it is destructed before + // the callback returned. They are being run in the same thread, so just setting it to + // null is sufficient. + // Note: We don't manually delete the CacheRequestBaton since it'll be deleted by the + // callback. + cache_baton->request = nullptr; + cache_baton = nullptr; + } +} + +void HTTPRequest::removeBackoffTimer() { + assert(uv_thread_self() == thread_id); + if (backoff_timer) { + delete static_cast<RetryBaton *>(backoff_timer->data); + uv_timer_stop(backoff_timer); + uv_close((uv_handle_t *)backoff_timer, [](uv_handle_t *handle) { delete (uv_timer_t *)handle; }); + backoff_timer = nullptr; + } +} + +void HTTPRequest::retryImmediately() { + assert(uv_thread_self() == thread_id); + if (!cache_baton && !http_baton) { + if (backoff_timer) { + // Retry immediately. + uv_timer_stop(backoff_timer); + std::unique_ptr<RetryBaton> pair { static_cast<RetryBaton *>(backoff_timer->data) }; + assert(pair->first == this); + startHTTPRequest(std::move(pair->second)); + uv_close((uv_handle_t *)backoff_timer, [](uv_handle_t *handle) { delete (uv_timer_t *)handle; }); + backoff_timer = nullptr; + } else { + assert(!"We should always have a backoff_timer when there are no batons"); + } + } +} + +void HTTPRequest::cancel() { + assert(uv_thread_self() == thread_id); + removeCacheBaton(); + removeHTTPBaton(); + removeBackoffTimer(); + notify(); +} + + +HTTPRequest::~HTTPRequest() { + assert(uv_thread_self() == thread_id); + cancel(); +} + +#pragma clang diagnostic pop + +} diff --git a/src/storage/http_request_baton.cpp b/src/storage/http_request_baton.cpp new file mode 100644 index 0000000000..02edae748f --- /dev/null +++ b/src/storage/http_request_baton.cpp @@ -0,0 +1,12 @@ +#include <mbgl/storage/http_request_baton.hpp> +#include <uv.h> + +namespace mbgl { + +HTTPRequestBaton::HTTPRequestBaton(const std::string &path_) : thread_id(uv_thread_self()), path(path_) { +} + +HTTPRequestBaton::~HTTPRequestBaton() { +} + +} diff --git a/src/storage/request.cpp b/src/storage/request.cpp new file mode 100644 index 0000000000..42bf87a849 --- /dev/null +++ b/src/storage/request.cpp @@ -0,0 +1,49 @@ +#include <mbgl/storage/request.hpp> +#include <mbgl/storage/base_request.hpp> + +#include <uv.h> + +#include <cassert> + +namespace mbgl { + +Request::Request(const util::ptr<BaseRequest> &base_) + : thread_id(uv_thread_self()), base(base_) { +} + +Request::~Request() { + assert(thread_id == uv_thread_self()); +} + +void Request::onload(CompletedCallback cb) { + assert(thread_id == uv_thread_self()); + if (base) { + Callback *callback = base->add(std::move(cb), base); + if (callback) { + callbacks.push_front(callback); + } + } +} + +void Request::oncancel(AbortedCallback cb) { + assert(thread_id == uv_thread_self()); + if (base) { + Callback *callback = base->add(std::move(cb), base); + if (callback) { + callbacks.push_front(callback); + } + } +} + +void Request::cancel() { + assert(thread_id == uv_thread_self()); + if (base) { + for (Callback *callback : callbacks) { + base->remove(callback); + } + base.reset(); + } + callbacks.clear(); +} + +} diff --git a/src/storage/response.cpp b/src/storage/response.cpp new file mode 100644 index 0000000000..cdaf33e4e4 --- /dev/null +++ b/src/storage/response.cpp @@ -0,0 +1,22 @@ +#include <mbgl/storage/response.hpp> + +#include <chrono> + +namespace mbgl { + +int64_t Response::parseCacheControl(const char *value) { + if (value) { + uint64_t seconds = 0; + // TODO: cache-control may contain other information as well: + // http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9 + if (std::sscanf(value, "max-age=%llu", &seconds) == 1) { + return std::chrono::duration_cast<std::chrono::seconds>( + std::chrono::system_clock::now().time_since_epoch()).count() + + seconds; + } + } + + return -1; +} + +} diff --git a/src/storage/sqlite_store.cpp b/src/storage/sqlite_store.cpp new file mode 100644 index 0000000000..763100f411 --- /dev/null +++ b/src/storage/sqlite_store.cpp @@ -0,0 +1,227 @@ +#include <mbgl/storage/sqlite_store.hpp> +#include <mbgl/util/compression.hpp> +#include <mbgl/util/sqlite3.hpp> + +#include <mbgl/util/uv-worker.h> + +#include <cassert> + +using namespace mapbox::sqlite; + +std::string removeAccessTokenFromURL(const std::string &url) { + const size_t token_start = url.find("access_token="); + // Ensure that token exists, isn't at the front and is preceded by either & or ?. + if (token_start == std::string::npos || token_start == 0 || !(url[token_start - 1] == '&' || url[token_start - 1] == '?')) { + return url; + } + + const size_t token_end = url.find_first_of('&', token_start); + if (token_end == std::string::npos) { + // The token is the last query argument. We slice away the "&access_token=..." part + return url.substr(0, token_start - 1); + } else { + // We slice away the "access_token=...&" part. + return url.substr(0, token_start) + url.substr(token_end + 1); + } +} + +std::string convertMapboxDomainsToProtocol(const std::string &url) { + const size_t protocol_separator = url.find("://"); + if (protocol_separator == std::string::npos) { + return url; + } + + const std::string protocol = url.substr(0, protocol_separator); + if (!(protocol == "http" || protocol == "https")) { + return url; + } + + const size_t domain_begin = protocol_separator + 3; + const size_t path_separator = url.find("/", domain_begin); + if (path_separator == std::string::npos) { + return url; + } + + const std::string domain = url.substr(domain_begin, path_separator - domain_begin); + if (domain.find(".tiles.mapbox.com") != std::string::npos) { + return "mapbox://" + url.substr(path_separator + 1); + } else { + return url; + } +} + +std::string unifyMapboxURLs(const std::string &url) { + return removeAccessTokenFromURL(convertMapboxDomainsToProtocol(url)); +} + +namespace mbgl { + +SQLiteStore::SQLiteStore(uv_loop_t *loop, const std::string &path) + : thread_id(uv_thread_self()), + db(std::make_shared<Database>(path.c_str(), ReadWrite | Create)) { + createSchema(); + worker = new uv_worker_t; + uv_worker_init(worker, loop, 1, "SQLite"); +} + +SQLiteStore::~SQLiteStore() { + // Nothing to do. This function needs to be here because we're forward-declaring + // Database, so we need the actual definition here to be able to properly destruct it. + if (worker) { + uv_worker_close(worker, [](uv_worker_t *worker) { + delete worker; + }); + } +} + +void SQLiteStore::createSchema() { + if (!db || !*db) { + return; + } + + db->exec("CREATE TABLE IF NOT EXISTS `http_cache` (" + " `url` TEXT PRIMARY KEY NOT NULL," + " `code` INTEGER NOT NULL," + " `type` INTEGER NOT NULL," + " `modified` INTEGER," + " `etag` TEXT," + " `expires` INTEGER," + " `data` BLOB," + " `compressed` INTEGER NOT NULL DEFAULT 0" + ");" + "CREATE INDEX IF NOT EXISTS `http_cache_type_idx` ON `http_cache` (`type`);"); +} + +struct GetBaton { + util::ptr<Database> db; + std::string path; + ResourceType type; + void *ptr = nullptr; + SQLiteStore::GetCallback callback = nullptr; + std::unique_ptr<Response> response; +}; + +void SQLiteStore::get(const std::string &path, GetCallback callback, void *ptr) { + assert(uv_thread_self() == thread_id); + if (!db || !*db) { + if (callback) { + callback(nullptr, ptr); + } + return; + } + + GetBaton *get_baton = new GetBaton; + get_baton->db = db; + get_baton->path = path; + get_baton->ptr = ptr; + get_baton->callback = callback; + + uv_worker_send(worker, get_baton, [](void *data) { + GetBaton *baton = (GetBaton *)data; + const std::string url = unifyMapboxURLs(baton->path); + // 0 1 2 + Statement stmt = baton->db->prepare("SELECT `code`, `type`, `modified`, " + // 3 4 5 6 + "`etag`, `expires`, `data`, `compressed` FROM `http_cache` WHERE `url` = ?"); + + stmt.bind(1, url.c_str()); + if (stmt.run()) { + // There is data. + baton->response = std::unique_ptr<Response>(new Response); + + baton->response->code = stmt.get<int>(0); + baton->type = ResourceType(stmt.get<int>(1)); + baton->response->modified = stmt.get<int64_t>(2); + baton->response->etag = stmt.get<std::string>(3); + baton->response->expires = stmt.get<int64_t>(4); + baton->response->data = stmt.get<std::string>(5); + if (stmt.get<int>(6)) { // == compressed + baton->response->data = util::decompress(baton->response->data); + } + } else { + // There is no data. + // This is a noop. + } + }, [](void *data) { + std::unique_ptr<GetBaton> baton { (GetBaton *)data }; + if (baton->callback) { + baton->callback(std::move(baton->response), baton->ptr); + } + }); +} + + +struct PutBaton { + util::ptr<Database> db; + std::string path; + ResourceType type; + Response response; +}; + +void SQLiteStore::put(const std::string &path, ResourceType type, const Response &response) { + assert(uv_thread_self() == thread_id); + if (!db) return; + + PutBaton *put_baton = new PutBaton; + put_baton->db = db; + put_baton->path = path; + put_baton->type = type; + put_baton->response = response; + + uv_worker_send(worker, put_baton, [](void *data) { + PutBaton *baton = (PutBaton *)data; + const std::string url = unifyMapboxURLs(baton->path); + Statement stmt = baton->db->prepare("REPLACE INTO `http_cache` (" + // 1 2 3 4 5 6 7 8 + "`url`, `code`, `type`, `modified`, `etag`, `expires`, `data`, `compressed`" + ") VALUES(?, ?, ?, ?, ?, ?, ?, ?)"); + stmt.bind(1, url.c_str()); + stmt.bind(2, int(baton->response.code)); + stmt.bind(3, int(baton->type)); + stmt.bind(4, baton->response.modified); + stmt.bind(5, baton->response.etag.c_str()); + stmt.bind(6, baton->response.expires); + + if (baton->type == ResourceType::Image) { + stmt.bind(7, baton->response.data, false); // do not retain the string internally. + stmt.bind(8, false); + } else { + stmt.bind(7, util::compress(baton->response.data), true); // retain the string internally. + stmt.bind(8, true); + } + + stmt.run(); + }, [](void *data) { + delete (PutBaton *)data; + }); +} + +struct ExpirationBaton { + util::ptr<Database> db; + std::string path; + int64_t expires; +}; + +void SQLiteStore::updateExpiration(const std::string &path, int64_t expires) { + assert(uv_thread_self() == thread_id); + if (!db || !*db) return; + + ExpirationBaton *expiration_baton = new ExpirationBaton; + expiration_baton->db = db; + expiration_baton->path = path; + expiration_baton->expires = expires; + + uv_worker_send(worker, expiration_baton, [](void *data) { + ExpirationBaton *baton = (ExpirationBaton *)data; + const std::string url = unifyMapboxURLs(baton->path); + Statement stmt = // 1 2 + baton->db->prepare("UPDATE `http_cache` SET `expires` = ? WHERE `url` = ?"); + stmt.bind<int64_t>(1, baton->expires); + stmt.bind(2, url.c_str()); + stmt.run(); + }, [](void *data) { + delete (ExpirationBaton *)data; + }); +} + +} diff --git a/src/style/style_layer_group.cpp b/src/style/style_layer_group.cpp index c7e4360d21..a731aebdcb 100644 --- a/src/style/style_layer_group.cpp +++ b/src/style/style_layer_group.cpp @@ -5,7 +5,7 @@ namespace mbgl { void StyleLayerGroup::setClasses(const std::vector<std::string> &class_names, timestamp now, const PropertyTransition &defaultTransition) { - for (const std::shared_ptr<StyleLayer> &layer : layers) { + for (const util::ptr<StyleLayer> &layer : layers) { if (layer) { layer->setClasses(class_names, now, defaultTransition); } @@ -13,7 +13,7 @@ void StyleLayerGroup::setClasses(const std::vector<std::string> &class_names, ti } void StyleLayerGroup::updateProperties(float z, timestamp t) { - for (const std::shared_ptr<StyleLayer> &layer: layers) { + for (const util::ptr<StyleLayer> &layer: layers) { if (layer) { layer->updateProperties(z, t); } @@ -21,7 +21,7 @@ void StyleLayerGroup::updateProperties(float z, timestamp t) { } bool StyleLayerGroup::hasTransitions() const { - for (const std::shared_ptr<const StyleLayer> &layer: layers) { + for (const util::ptr<const StyleLayer> &layer: layers) { if (layer) { if (layer->hasTransitions()) { return true; diff --git a/src/style/style_parser.cpp b/src/style/style_parser.cpp index ad7694d9a9..2a64ab38f8 100644 --- a/src/style/style_parser.cpp +++ b/src/style/style_parser.cpp @@ -176,12 +176,12 @@ void StyleParser::parseSources(JSVal value) { rapidjson::Value::ConstMemberIterator itr = value.MemberBegin(); for (; itr != value.MemberEnd(); ++itr) { std::string name { itr->name.GetString(), itr->name.GetStringLength() }; - SourceInfo info; + util::ptr<SourceInfo> info = std::make_shared<SourceInfo>(); - parseRenderProperty<SourceTypeClass>(itr->value, info.type, "type"); - parseRenderProperty(itr->value, info.url, "url"); - parseRenderProperty(itr->value, info.tile_size, "tileSize"); - info.parseTileJSONProperties(itr->value); + parseRenderProperty<SourceTypeClass>(itr->value, info->type, "type"); + parseRenderProperty(itr->value, info->url, "url"); + parseRenderProperty(itr->value, info->tile_size, "tileSize"); + info->parseTileJSONProperties(itr->value); sources.emplace(std::move(name), std::make_shared<StyleSource>(info)); } @@ -451,7 +451,7 @@ std::unique_ptr<StyleLayerGroup> StyleParser::createLayers(JSVal value) { if (value.IsArray()) { std::unique_ptr<StyleLayerGroup> group = std::make_unique<StyleLayerGroup>(); for (rapidjson::SizeType i = 0; i < value.Size(); ++i) { - std::shared_ptr<StyleLayer> layer = createLayer(value[i]); + util::ptr<StyleLayer> layer = createLayer(value[i]); if (layer) { group->layers.emplace_back(layer); } @@ -463,7 +463,7 @@ std::unique_ptr<StyleLayerGroup> StyleParser::createLayers(JSVal value) { } } -std::shared_ptr<StyleLayer> StyleParser::createLayer(JSVal value) { +util::ptr<StyleLayer> StyleParser::createLayer(JSVal value) { if (value.IsObject()) { if (!value.HasMember("id")) { Log::Warning(Event::ParseStyle, "layer must have an id"); @@ -487,7 +487,7 @@ std::shared_ptr<StyleLayer> StyleParser::createLayer(JSVal value) { std::map<ClassID, ClassProperties> styles; parseStyles(value, styles); - std::shared_ptr<StyleLayer> layer = std::make_shared<StyleLayer>( + util::ptr<StyleLayer> layer = std::make_shared<StyleLayer>( layer_id, std::move(styles)); if (value.HasMember("layers")) { @@ -495,7 +495,7 @@ std::shared_ptr<StyleLayer> StyleParser::createLayer(JSVal value) { } // Store the layer ID so we can reference it later. - layers.emplace(layer_id, std::pair<JSVal, std::shared_ptr<StyleLayer>> { value, layer }); + layers.emplace(layer_id, std::pair<JSVal, util::ptr<StyleLayer>> { value, layer }); return layer; } else { @@ -505,14 +505,14 @@ std::shared_ptr<StyleLayer> StyleParser::createLayer(JSVal value) { } void StyleParser::parseLayers() { - for (std::pair<const std::string, std::pair<JSVal, std::shared_ptr<StyleLayer>>> &pair : layers) { + for (std::pair<const std::string, std::pair<JSVal, util::ptr<StyleLayer>>> &pair : layers) { parseLayer(pair.second); } } -void StyleParser::parseLayer(std::pair<JSVal, std::shared_ptr<StyleLayer>> &pair) { +void StyleParser::parseLayer(std::pair<JSVal, util::ptr<StyleLayer>> &pair) { JSVal value = pair.first; - std::shared_ptr<StyleLayer> &layer = pair.second; + util::ptr<StyleLayer> &layer = pair.second; if (value.HasMember("type")) { JSVal type = value["type"]; @@ -642,7 +642,7 @@ void StyleParser::parseStyle(JSVal value, ClassProperties &klass) { parseOptionalProperty<Function<Color>>("background-color", Key::BackgroundColor, klass, value); } -void StyleParser::parseReference(JSVal value, std::shared_ptr<StyleLayer> &layer) { +void StyleParser::parseReference(JSVal value, util::ptr<StyleLayer> &layer) { if (!value.IsString()) { Log::Warning(Event::ParseStyle, "layer ref of '%s' must be a string", layer->id.c_str()); return; @@ -661,7 +661,7 @@ void StyleParser::parseReference(JSVal value, std::shared_ptr<StyleLayer> &layer stack.pop_front(); - std::shared_ptr<StyleLayer> reference = it->second.second; + util::ptr<StyleLayer> reference = it->second.second; layer->type = reference->type; @@ -676,7 +676,7 @@ void StyleParser::parseReference(JSVal value, std::shared_ptr<StyleLayer> &layer #pragma mark - Parse Bucket -void StyleParser::parseBucket(JSVal value, std::shared_ptr<StyleLayer> &layer) { +void StyleParser::parseBucket(JSVal value, util::ptr<StyleLayer> &layer) { layer->bucket = std::make_shared<StyleBucket>(layer->type); // We name the buckets according to the layer that defined it. @@ -822,7 +822,7 @@ std::vector<Value> StyleParser::parseValues(JSVal value) { return values; } -void StyleParser::parseRender(JSVal value, std::shared_ptr<StyleLayer> &layer) { +void StyleParser::parseRender(JSVal value, util::ptr<StyleLayer> &layer) { if (!value.IsObject()) { Log::Warning(Event::ParseStyle, "render property of layer '%s' must be an object", layer->id.c_str()); return; diff --git a/src/text/glyph.cpp b/src/text/glyph.cpp index 7dea7246d7..f02c710db2 100644 --- a/src/text/glyph.cpp +++ b/src/text/glyph.cpp @@ -3,9 +3,6 @@ namespace mbgl { // Note: this only works for the BMP -// Note: we could use a binary lookup table to get averaged constant time lookups, however, -// most of our lookups are going to be within the first 3 ranges listed here, so this is -// likely faster. GlyphRange getGlyphRange(char32_t glyph) { unsigned start = (glyph/256) * 256; unsigned end = (start + 255); diff --git a/src/text/glyph_store.cpp b/src/text/glyph_store.cpp index 783710d929..1723bd3d94 100644 --- a/src/text/glyph_store.cpp +++ b/src/text/glyph_store.cpp @@ -8,7 +8,7 @@ #include <mbgl/util/constants.hpp> #include <mbgl/util/token.hpp> #include <mbgl/util/math.hpp> -#include <mbgl/util/filesource.hpp> +#include <mbgl/storage/file_source.hpp> #include <mbgl/platform/platform.hpp> #include <mbgl/util/uv_detail.hpp> #include <algorithm> @@ -137,7 +137,7 @@ void FontStack::lineWrap(Shaping &shaping, const float lineHeight, const float m align(shaping, justify, horizontalAlign, verticalAlign, maxLineLength, lineHeight, line); } -GlyphPBF::GlyphPBF(const std::string &glyphURL, const std::string &fontStack, GlyphRange glyphRange, const std::shared_ptr<FileSource> &fileSource) +GlyphPBF::GlyphPBF(const std::string &glyphURL, const std::string &fontStack, GlyphRange glyphRange, const util::ptr<FileSource> &fileSource) : future(promise.get_future().share()) { // Load the glyph set URL @@ -147,23 +147,26 @@ GlyphPBF::GlyphPBF(const std::string &glyphURL, const std::string &fontStack, Gl return ""; }); -#if defined(DEBUG) - fprintf(stderr, "%s\n", url.c_str()); -#endif - - fileSource->load(ResourceType::Glyphs, url, [&](platform::Response *res) { - if (res->code != 200) { - // Something went wrong with loading the glyph pbf. Pass on the error to the future listeners. - const std::string msg = util::sprintf<255>("[ERROR] failed to load glyphs (%d): %s\n", res->code, res->error_message.c_str()); - promise.set_exception(std::make_exception_ptr(std::runtime_error(msg))); - } else { - // Transfer the data to the GlyphSet and signal its availability. - // Once it is available, the caller will need to call parse() to actually - // parse the data we received. We are not doing this here since this callback is being - // called from another (unknown) thread. - data.swap(res->body); - promise.set_value(*this); - } + // The prepare call jumps back to the main thread. + fileSource->prepare([&, url, fileSource] { + auto request = fileSource->request(ResourceType::Glyphs, url); + request->onload([&, url](const Response &res) { + if (res.code != 200) { + // Something went wrong with loading the glyph pbf. Pass on the error to the future listeners. + const std::string msg = util::sprintf<255>("[ERROR] failed to load glyphs (%d): %s\n", res.code, res.message.c_str()); + promise.set_exception(std::make_exception_ptr(std::runtime_error(msg))); + } else { + // Transfer the data to the GlyphSet and signal its availability. + // Once it is available, the caller will need to call parse() to actually + // parse the data we received. We are not doing this here since this callback is being + // called from another (unknown) thread. + data = res.data; + promise.set_value(*this); + } + }); + request->oncancel([&]() { + promise.set_exception(std::make_exception_ptr(std::runtime_error("Loading glyphs was canceled"))); + }); }); } @@ -225,7 +228,7 @@ void GlyphPBF::parse(FontStack &stack) { data.clear(); } -GlyphStore::GlyphStore(const std::shared_ptr<FileSource> &fileSource) : fileSource(fileSource) {} +GlyphStore::GlyphStore(const util::ptr<FileSource> &fileSource) : fileSource(fileSource) {} void GlyphStore::setURL(const std::string &url) { glyphURL = url; diff --git a/src/util/compression.cpp b/src/util/compression.cpp new file mode 100644 index 0000000000..de4e09764c --- /dev/null +++ b/src/util/compression.cpp @@ -0,0 +1,81 @@ +#include <mbgl/util/compression.hpp> + +#include <zlib.h> + +#include <cstring> +#include <stdexcept> + +namespace mbgl { +namespace util { + +std::string compress(const std::string &raw) { + z_stream deflate_stream; + memset(&deflate_stream, 0, sizeof(deflate_stream)); + + // TODO: reuse z_streams + if (deflateInit(&deflate_stream, Z_DEFAULT_COMPRESSION) != Z_OK) { + throw std::runtime_error("failed to initialize deflate"); + } + + deflate_stream.next_in = (Bytef *)raw.data(); + deflate_stream.avail_in = raw.size(); + + std::string result; + char out[16384]; + + int code; + do { + deflate_stream.next_out = reinterpret_cast<Bytef *>(out); + deflate_stream.avail_out = sizeof(out); + code = deflate(&deflate_stream, Z_FINISH); + if (result.size() < deflate_stream.total_out) { + // append the block to the output string + result.append(out, deflate_stream.total_out - result.size()); + } + } while (code == Z_OK); + + deflateEnd(&deflate_stream); + + if (code != Z_STREAM_END) { + throw std::runtime_error(deflate_stream.msg); + } + + return result; +} + +std::string decompress(const std::string &raw) { + z_stream inflate_stream; + memset(&inflate_stream, 0, sizeof(inflate_stream)); + + // TODO: reuse z_streams + if (inflateInit(&inflate_stream) != Z_OK) { + throw std::runtime_error("failed to initialize inflate"); + } + + inflate_stream.next_in = (Bytef *)raw.data(); + inflate_stream.avail_in = raw.size(); + + std::string result; + char out[15384]; + + int code; + do { + inflate_stream.next_out = reinterpret_cast<Bytef *>(out); + inflate_stream.avail_out = sizeof(out); + code = inflate(&inflate_stream, 0); + // result.append(out, sizeof(out) - inflate_stream.avail_out); + if (result.size() < inflate_stream.total_out) { + result.append(out, inflate_stream.total_out - result.size()); + } + } while (code == Z_OK); + + inflateEnd(&inflate_stream); + + if (code != Z_STREAM_END) { + throw std::runtime_error(inflate_stream.msg); + } + + return result; +} +} +} diff --git a/src/util/filesource.cpp b/src/util/filesource.cpp deleted file mode 100644 index 503d4dfb1d..0000000000 --- a/src/util/filesource.cpp +++ /dev/null @@ -1,58 +0,0 @@ -#include <mbgl/util/filesource.hpp> -#include <mbgl/platform/platform.hpp> - -#include <fstream> -#include <sstream> - -namespace mbgl { - -FileSource::FileSource() {} - - -void FileSource::setBase(const std::string &value) { - base = value; -} - -const std::string &FileSource::getBase() const { - return base; -} - -void FileSource::load(ResourceType /*type*/, const std::string &url, std::function<void(platform::Response *)> callback, const std::shared_ptr<uv::loop> loop) { - // convert relative URLs to absolute URLs - - const std::string absoluteURL = [&]() -> std::string { - const size_t separator = url.find("://"); - if (separator == std::string::npos) { - // Relative URL. - return base + url; - } else { - return url; - } - }(); - - const size_t separator = absoluteURL.find("://"); - const std::string protocol = separator != std::string::npos ? absoluteURL.substr(0, separator) : ""; - - if (protocol == "file") { - // load from disk - const std::string path = absoluteURL.substr(separator + 3); - std::ifstream file(path); - - platform::Response response(callback); - if (!file.good()) { - response.error_message = "file not found (" + path + ")"; - } else { - std::stringstream data; - data << file.rdbuf(); - response.code = 200; - response.body = data.str(); - } - - callback(&response); - } else { - // load from the internet - platform::request_http(absoluteURL, callback, loop); - } -} - -}
\ No newline at end of file diff --git a/src/util/parsedate.c b/src/util/parsedate.c new file mode 100644 index 0000000000..123c5c4e5f --- /dev/null +++ b/src/util/parsedate.c @@ -0,0 +1,689 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2014, Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at http://curl.haxx.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ***************************************************************************/ +/* + A brief summary of the date string formats this parser groks: + + RFC 2616 3.3.1 + + Sun, 06 Nov 1994 08:49:37 GMT ; RFC 822, updated by RFC 1123 + Sunday, 06-Nov-94 08:49:37 GMT ; RFC 850, obsoleted by RFC 1036 + Sun Nov 6 08:49:37 1994 ; ANSI C's asctime() format + + we support dates without week day name: + + 06 Nov 1994 08:49:37 GMT + 06-Nov-94 08:49:37 GMT + Nov 6 08:49:37 1994 + + without the time zone: + + 06 Nov 1994 08:49:37 + 06-Nov-94 08:49:37 + + weird order: + + 1994 Nov 6 08:49:37 (GNU date fails) + GMT 08:49:37 06-Nov-94 Sunday + 94 6 Nov 08:49:37 (GNU date fails) + + time left out: + + 1994 Nov 6 + 06-Nov-94 + Sun Nov 6 94 + + unusual separators: + + 1994.Nov.6 + Sun/Nov/6/94/GMT + + commonly used time zone names: + + Sun, 06 Nov 1994 08:49:37 CET + 06 Nov 1994 08:49:37 EST + + time zones specified using RFC822 style: + + Sun, 12 Sep 2004 15:05:58 -0700 + Sat, 11 Sep 2004 21:32:11 +0200 + + compact numerical date strings: + + 20040912 15:05:58 -0700 + 20040911 +0200 + +*/ + +#include <mbgl/util/parsedate.h> + + + +#ifdef __cplusplus +extern "C" { +#endif + +#include <limits.h> +#include <stdbool.h> +#include <errno.h> +#include <string.h> +#include <ctype.h> +#include <stdlib.h> +#include <stdio.h> + + +#define ERRNO (errno) +#define SET_ERRNO(x) (errno = (x)) + + +/* Portable, consistent toupper (remember EBCDIC). Do not use toupper() because + its behavior is altered by the current locale. */ +char raw_toupper(char in) +{ + switch (in) { + case 'a': + return 'A'; + case 'b': + return 'B'; + case 'c': + return 'C'; + case 'd': + return 'D'; + case 'e': + return 'E'; + case 'f': + return 'F'; + case 'g': + return 'G'; + case 'h': + return 'H'; + case 'i': + return 'I'; + case 'j': + return 'J'; + case 'k': + return 'K'; + case 'l': + return 'L'; + case 'm': + return 'M'; + case 'n': + return 'N'; + case 'o': + return 'O'; + case 'p': + return 'P'; + case 'q': + return 'Q'; + case 'r': + return 'R'; + case 's': + return 'S'; + case 't': + return 'T'; + case 'u': + return 'U'; + case 'v': + return 'V'; + case 'w': + return 'W'; + case 'x': + return 'X'; + case 'y': + return 'Y'; + case 'z': + return 'Z'; + } + return in; +} + +/* + * raw_equal() is for doing "raw" case insensitive strings. This is meant + * to be locale independent and only compare strings we know are safe for + * this. See http://daniel.haxx.se/blog/2008/10/15/strcasecmp-in-turkish/ for + * some further explanation to why this function is necessary. + * + * The function is capable of comparing a-z case insensitively even for + * non-ascii. + */ + +int raw_equal(const char *first, const char *second) +{ + while(*first && *second) { + if(raw_toupper(*first) != raw_toupper(*second)) + /* get out of the loop as soon as they don't match */ + break; + first++; + second++; + } + /* we do the comparison here (possibly again), just to make sure that if the + loop above is skipped because one of the strings reached zero, we must not + return this as a successful match */ + return (raw_toupper(*first) == raw_toupper(*second)); +} + +#define ISSPACE(x) (isspace((int) ((unsigned char)x))) +#define ISDIGIT(x) (isdigit((int) ((unsigned char)x))) +#define ISALNUM(x) (isalnum((int) ((unsigned char)x))) +#define ISALPHA(x) (isalpha((int) ((unsigned char)x))) + + +/* + * Redefine TRUE and FALSE too, to catch current use. With this + * change, 'bool found = 1' will give a warning on MIPSPro, but + * 'bool found = TRUE' will not. Change tested on IRIX/MIPSPro, + * AIX 5.1/Xlc, Tru64 5.1/cc, w/make test too. + */ + +#ifndef TRUE +#define TRUE true +#endif +#ifndef FALSE +#define FALSE false +#endif + + + +/* +** signed long to signed int +*/ + +int clamp_to_int(long slnum) +{ + return slnum > INT_MAX ? INT_MAX : slnum < INT_MIN ? INT_MIN : slnum; +} + + +const char * const wkday[] = +{"Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"}; +static const char * const weekday[] = +{ "Monday", "Tuesday", "Wednesday", "Thursday", + "Friday", "Saturday", "Sunday" }; +const char * const month[]= +{ "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; + +struct tzinfo { + char name[5]; + int offset; /* +/- in minutes */ +}; + +/* + * parsedate() + * + * Returns: + * + * PARSEDATE_OK - a fine conversion + * PARSEDATE_FAIL - failed to convert + * PARSEDATE_LATER - time overflow at the far end of time_t + * PARSEDATE_SOONER - time underflow at the low end of time_t + */ + +static int parsedate(const char *date, time_t *output); + +#define PARSEDATE_OK 0 +#define PARSEDATE_FAIL -1 +#define PARSEDATE_LATER 1 +#define PARSEDATE_SOONER 2 + +/* Here's a bunch of frequently used time zone names. These were supported + by the old getdate parser. */ +#define tDAYZONE -60 /* offset for daylight savings time */ +static const struct tzinfo tz[]= { + {"GMT", 0}, /* Greenwich Mean */ + {"UTC", 0}, /* Universal (Coordinated) */ + {"WET", 0}, /* Western European */ + {"BST", 0 tDAYZONE}, /* British Summer */ + {"WAT", 60}, /* West Africa */ + {"AST", 240}, /* Atlantic Standard */ + {"ADT", 240 tDAYZONE}, /* Atlantic Daylight */ + {"EST", 300}, /* Eastern Standard */ + {"EDT", 300 tDAYZONE}, /* Eastern Daylight */ + {"CST", 360}, /* Central Standard */ + {"CDT", 360 tDAYZONE}, /* Central Daylight */ + {"MST", 420}, /* Mountain Standard */ + {"MDT", 420 tDAYZONE}, /* Mountain Daylight */ + {"PST", 480}, /* Pacific Standard */ + {"PDT", 480 tDAYZONE}, /* Pacific Daylight */ + {"YST", 540}, /* Yukon Standard */ + {"YDT", 540 tDAYZONE}, /* Yukon Daylight */ + {"HST", 600}, /* Hawaii Standard */ + {"HDT", 600 tDAYZONE}, /* Hawaii Daylight */ + {"CAT", 600}, /* Central Alaska */ + {"AHST", 600}, /* Alaska-Hawaii Standard */ + {"NT", 660}, /* Nome */ + {"IDLW", 720}, /* International Date Line West */ + {"CET", -60}, /* Central European */ + {"MET", -60}, /* Middle European */ + {"MEWT", -60}, /* Middle European Winter */ + {"MEST", -60 tDAYZONE}, /* Middle European Summer */ + {"CEST", -60 tDAYZONE}, /* Central European Summer */ + {"MESZ", -60 tDAYZONE}, /* Middle European Summer */ + {"FWT", -60}, /* French Winter */ + {"FST", -60 tDAYZONE}, /* French Summer */ + {"EET", -120}, /* Eastern Europe, USSR Zone 1 */ + {"WAST", -420}, /* West Australian Standard */ + {"WADT", -420 tDAYZONE}, /* West Australian Daylight */ + {"CCT", -480}, /* China Coast, USSR Zone 7 */ + {"JST", -540}, /* Japan Standard, USSR Zone 8 */ + {"EAST", -600}, /* Eastern Australian Standard */ + {"EADT", -600 tDAYZONE}, /* Eastern Australian Daylight */ + {"GST", -600}, /* Guam Standard, USSR Zone 9 */ + {"NZT", -720}, /* New Zealand */ + {"NZST", -720}, /* New Zealand Standard */ + {"NZDT", -720 tDAYZONE}, /* New Zealand Daylight */ + {"IDLE", -720}, /* International Date Line East */ + /* Next up: Military timezone names. RFC822 allowed these, but (as noted in + RFC 1123) had their signs wrong. Here we use the correct signs to match + actual military usage. + */ + {"A", +1 * 60}, /* Alpha */ + {"B", +2 * 60}, /* Bravo */ + {"C", +3 * 60}, /* Charlie */ + {"D", +4 * 60}, /* Delta */ + {"E", +5 * 60}, /* Echo */ + {"F", +6 * 60}, /* Foxtrot */ + {"G", +7 * 60}, /* Golf */ + {"H", +8 * 60}, /* Hotel */ + {"I", +9 * 60}, /* India */ + /* "J", Juliet is not used as a timezone, to indicate the observer's local + time */ + {"K", +10 * 60}, /* Kilo */ + {"L", +11 * 60}, /* Lima */ + {"M", +12 * 60}, /* Mike */ + {"N", -1 * 60}, /* November */ + {"O", -2 * 60}, /* Oscar */ + {"P", -3 * 60}, /* Papa */ + {"Q", -4 * 60}, /* Quebec */ + {"R", -5 * 60}, /* Romeo */ + {"S", -6 * 60}, /* Sierra */ + {"T", -7 * 60}, /* Tango */ + {"U", -8 * 60}, /* Uniform */ + {"V", -9 * 60}, /* Victor */ + {"W", -10 * 60}, /* Whiskey */ + {"X", -11 * 60}, /* X-ray */ + {"Y", -12 * 60}, /* Yankee */ + {"Z", 0}, /* Zulu, zero meridian, a.k.a. UTC */ +}; + +/* returns: + -1 no day + 0 monday - 6 sunday +*/ + +static int checkday(const char *check, size_t len) +{ + int i; + const char * const *what; + bool found= FALSE; + if(len > 3) + what = &weekday[0]; + else + what = &wkday[0]; + for(i=0; i<7; i++) { + if(raw_equal(check, what[0])) { + found=TRUE; + break; + } + what++; + } + return found?i:-1; +} + +static int checkmonth(const char *check) +{ + int i; + const char * const *what; + bool found= FALSE; + + what = &month[0]; + for(i=0; i<12; i++) { + if(raw_equal(check, what[0])) { + found=TRUE; + break; + } + what++; + } + return found?i:-1; /* return the offset or -1, no real offset is -1 */ +} + +/* return the time zone offset between GMT and the input one, in number + of seconds or -1 if the timezone wasn't found/legal */ + +static int checktz(const char *check) +{ + unsigned int i; + const struct tzinfo *what; + bool found= FALSE; + + what = tz; + for(i=0; i< sizeof(tz)/sizeof(tz[0]); i++) { + if(raw_equal(check, what->name)) { + found=TRUE; + break; + } + what++; + } + return found?what->offset*60:-1; +} + +static void skip(const char **date) +{ + /* skip everything that aren't letters or digits */ + while(**date && !ISALNUM(**date)) + (*date)++; +} + +enum assume { + DATE_MDAY, + DATE_YEAR, + DATE_TIME +}; + +/* this is a clone of 'struct tm' but with all fields we don't need or use + cut out */ +struct my_tm { + int tm_sec; + int tm_min; + int tm_hour; + int tm_mday; + int tm_mon; + int tm_year; +}; + +/* struct tm to time since epoch in GMT time zone. + * This is similar to the standard mktime function but for GMT only, and + * doesn't suffer from the various bugs and portability problems that + * some systems' implementations have. + */ +static time_t my_timegm(struct my_tm *tm) +{ + static const int month_days_cumulative [12] = + { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 }; + int month, year, leap_days; + + if(tm->tm_year < 70) + /* we don't support years before 1970 as they will cause this function + to return a negative value */ + return -1; + + year = tm->tm_year + 1900; + month = tm->tm_mon; + if(month < 0) { + year += (11 - month) / 12; + month = 11 - (11 - month) % 12; + } + else if(month >= 12) { + year -= month / 12; + month = month % 12; + } + + leap_days = year - (tm->tm_mon <= 1); + leap_days = ((leap_days / 4) - (leap_days / 100) + (leap_days / 400) + - (1969 / 4) + (1969 / 100) - (1969 / 400)); + + return ((((time_t) (year - 1970) * 365 + + leap_days + month_days_cumulative [month] + tm->tm_mday - 1) * 24 + + tm->tm_hour) * 60 + tm->tm_min) * 60 + tm->tm_sec; +} + +/* + * parsedate() + * + * Returns: + * + * PARSEDATE_OK - a fine conversion + * PARSEDATE_FAIL - failed to convert + * PARSEDATE_LATER - time overflow at the far end of time_t + * PARSEDATE_SOONER - time underflow at the low end of time_t + */ + +static int parsedate(const char *date, time_t *output) +{ + time_t t = 0; + int wdaynum=-1; /* day of the week number, 0-6 (mon-sun) */ + int monnum=-1; /* month of the year number, 0-11 */ + int mdaynum=-1; /* day of month, 1 - 31 */ + int hournum=-1; + int minnum=-1; + int secnum=-1; + int yearnum=-1; + int tzoff=-1; + struct my_tm tm; + enum assume dignext = DATE_MDAY; + const char *indate = date; /* save the original pointer */ + int part = 0; /* max 6 parts */ + + while(*date && (part < 6)) { + bool found=FALSE; + + skip(&date); + + if(ISALPHA(*date)) { + /* a name coming up */ + char buf[32]=""; + size_t len; + if(sscanf(date, "%31[ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz]", buf)) + len = strlen(buf); + else + len = 0; + + if(wdaynum == -1) { + wdaynum = checkday(buf, len); + if(wdaynum != -1) + found = TRUE; + } + if(!found && (monnum == -1)) { + monnum = checkmonth(buf); + if(monnum != -1) + found = TRUE; + } + + if(!found && (tzoff == -1)) { + /* this just must be a time zone string */ + tzoff = checktz(buf); + if(tzoff != -1) + found = TRUE; + } + + if(!found) + return PARSEDATE_FAIL; /* bad string */ + + date += len; + } + else if(ISDIGIT(*date)) { + /* a digit */ + int val; + char *end; + if((secnum == -1) && + (3 == sscanf(date, "%02d:%02d:%02d", &hournum, &minnum, &secnum))) { + /* time stamp! */ + date += 8; + } + else if((secnum == -1) && + (2 == sscanf(date, "%02d:%02d", &hournum, &minnum))) { + /* time stamp without seconds */ + date += 5; + secnum = 0; + } + else { + long lval; + int error; + int old_errno; + + old_errno = ERRNO; + SET_ERRNO(0); + lval = strtol(date, &end, 10); + error = ERRNO; + if(error != old_errno) + SET_ERRNO(old_errno); + + if(error) + return PARSEDATE_FAIL; + +#if LONG_MAX != INT_MAX + if((lval > (long)INT_MAX) || (lval < (long)INT_MIN)) + return PARSEDATE_FAIL; +#endif + + val = clamp_to_int(lval); + + if((tzoff == -1) && + ((end - date) == 4) && + (val <= 1400) && + (indate< date) && + ((date[-1] == '+' || date[-1] == '-'))) { + /* four digits and a value less than or equal to 1400 (to take into + account all sorts of funny time zone diffs) and it is preceded + with a plus or minus. This is a time zone indication. 1400 is + picked since +1300 is frequently used and +1400 is mentioned as + an edge number in the document "ISO C 200X Proposal: Timezone + Functions" at http://david.tribble.com/text/c0xtimezone.html If + anyone has a more authoritative source for the exact maximum time + zone offsets, please speak up! */ + found = TRUE; + tzoff = (val/100 * 60 + val%100)*60; + + /* the + and - prefix indicates the local time compared to GMT, + this we need ther reversed math to get what we want */ + tzoff = date[-1]=='+'?-tzoff:tzoff; + } + + if(((end - date) == 8) && + (yearnum == -1) && + (monnum == -1) && + (mdaynum == -1)) { + /* 8 digits, no year, month or day yet. This is YYYYMMDD */ + found = TRUE; + yearnum = val/10000; + monnum = (val%10000)/100-1; /* month is 0 - 11 */ + mdaynum = val%100; + } + + if(!found && (dignext == DATE_MDAY) && (mdaynum == -1)) { + if((val > 0) && (val<32)) { + mdaynum = val; + found = TRUE; + } + dignext = DATE_YEAR; + } + + if(!found && (dignext == DATE_YEAR) && (yearnum == -1)) { + yearnum = val; + found = TRUE; + if(yearnum < 1900) { + if(yearnum > 70) + yearnum += 1900; + else + yearnum += 2000; + } + if(mdaynum == -1) + dignext = DATE_MDAY; + } + + if(!found) + return PARSEDATE_FAIL; + + date = end; + } + } + + part++; + } + + if(-1 == secnum) + secnum = minnum = hournum = 0; /* no time, make it zero */ + + if((-1 == mdaynum) || + (-1 == monnum) || + (-1 == yearnum)) + /* lacks vital info, fail */ + return PARSEDATE_FAIL; + +#if SIZEOF_TIME_T < 5 + /* 32 bit time_t can only hold dates to the beginning of 2038 */ + if(yearnum > 2037) { + *output = 0x7fffffff; + return PARSEDATE_LATER; + } +#endif + + if(yearnum < 1970) { + *output = 0; + return PARSEDATE_SOONER; + } + + if((mdaynum > 31) || (monnum > 11) || + (hournum > 23) || (minnum > 59) || (secnum > 60)) + return PARSEDATE_FAIL; /* clearly an illegal date */ + + tm.tm_sec = secnum; + tm.tm_min = minnum; + tm.tm_hour = hournum; + tm.tm_mday = mdaynum; + tm.tm_mon = monnum; + tm.tm_year = yearnum - 1900; + + /* my_timegm() returns a time_t. time_t is often 32 bits, even on many + architectures that feature 64 bit 'long'. + + Some systems have 64 bit time_t and deal with years beyond 2038. However, + even on some of the systems with 64 bit time_t mktime() returns -1 for + dates beyond 03:14:07 UTC, January 19, 2038. (Such as AIX 5100-06) + */ + t = my_timegm(&tm); + + /* time zone adjust (cast t to int to compare to negative one) */ + if(-1 != (int)t) { + + /* Add the time zone diff between local time zone and GMT. */ + long delta = (long)(tzoff!=-1?tzoff:0); + + if((delta>0) && (t > LONG_MAX - delta)) + return -1; /* time_t overflow */ + + t += delta; + } + + *output = t; + + return PARSEDATE_OK; +} + +time_t parse_date(const char *p) +{ + time_t parsed; + int rc = parsedate(p, &parsed); + + switch(rc) { + case PARSEDATE_OK: + case PARSEDATE_LATER: + case PARSEDATE_SOONER: + return parsed; + } + /* everything else is fail */ + return -1; +} + +#ifdef __cplusplus +} +#endif diff --git a/src/util/raster.cpp b/src/util/raster.cpp index 9a71573d01..7b52c51037 100644 --- a/src/util/raster.cpp +++ b/src/util/raster.cpp @@ -1,20 +1,19 @@ -#include <mbgl/util/raster.hpp> - -#include <memory> -#include <cassert> -#include <cstring> - #include <mbgl/platform/platform.hpp> #include <mbgl/platform/gl.hpp> + +#include <mbgl/util/raster.hpp> #include <mbgl/util/time.hpp> #include <mbgl/util/uv_detail.hpp> #include <mbgl/util/std.hpp> #include <png.h> +#include <cassert> +#include <cstring> + using namespace mbgl; -Raster::Raster(const std::shared_ptr<Texturepool> &texturepool) +Raster::Raster(const util::ptr<Texturepool> &texturepool) : texturepool(texturepool) {} diff --git a/src/util/sqlite3.cpp b/src/util/sqlite3.cpp new file mode 100644 index 0000000000..19e0ce9c79 --- /dev/null +++ b/src/util/sqlite3.cpp @@ -0,0 +1,166 @@ +#include <mbgl/util/sqlite3.hpp> +#include <sqlite3.h> + +#include <cassert> + +namespace mapbox { +namespace sqlite { + +Database::Database(const std::string &filename, int flags) { + const int err = sqlite3_open_v2(filename.c_str(), &db, flags, nullptr); + if (err != SQLITE_OK) { + Exception ex { err, sqlite3_errmsg(db) }; + db = nullptr; + throw ex; + } +} + +Database::Database(Database &&other) { + *this = std::move(other); +} + +Database &Database::operator=(Database &&other) { + std::swap(db, other.db); + return *this; +} + +Database::~Database() { + if (db) { + const int err = sqlite3_close(db); + if (err != SQLITE_OK) { + throw Exception { err, sqlite3_errmsg(db) }; + } + } +} + +Database::operator bool() const { + return db != nullptr; +} + +void Database::exec(const std::string &sql) { + assert(db); + char *msg = nullptr; + const int err = sqlite3_exec(db, sql.c_str(), nullptr, nullptr, &msg); + if (msg) { + Exception ex { err, msg }; + sqlite3_free(msg); + throw ex; + } else if (err != SQLITE_OK) { + throw Exception { err, sqlite3_errmsg(db) }; + } +} + +Statement Database::prepare(const char *query) { + assert(db); + return std::move(Statement(db, query)); +} + +Statement::Statement(sqlite3 *db, const char *sql) { + const int err = sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr); + if (err != SQLITE_OK) { + stmt = nullptr; + throw Exception { err, sqlite3_errmsg(db) }; + } +} + +#define CHECK_SQLITE_OK(err) \ + if (err != SQLITE_OK) { \ + throw Exception { err, sqlite3_errmsg(sqlite3_db_handle(stmt)) }; \ + } + +Statement::Statement(Statement &&other) { + *this = std::move(other); +} + +Statement &Statement::operator=(Statement &&other) { + std::swap(stmt, other.stmt); + return *this; +} + +Statement::~Statement() { + if (stmt) { + const int err = sqlite3_finalize(stmt); + CHECK_SQLITE_OK(err) + } +} + +Statement::operator bool() const { + return stmt != nullptr; +} + +#define BIND_3(type, value) \ + assert(stmt); \ + const int err = sqlite3_bind_##type(stmt, offset, value); \ + CHECK_SQLITE_OK(err) + +#define BIND_5(type, value, length, param) \ + assert(stmt); \ + const int err = sqlite3_bind_##type(stmt, offset, value, length, param); \ + CHECK_SQLITE_OK(err) + +template <> void Statement::bind(int offset, int value) { + BIND_3(int, value) +} + +template <> void Statement::bind(int offset, int64_t value) { + BIND_3(int64, value) +} + +template <> void Statement::bind(int offset, double value) { + BIND_3(double, value) +} + +template <> void Statement::bind(int offset, bool value) { + BIND_3(int, value) +} + +template <> void Statement::bind(int offset, const char *value) { + BIND_5(text, value, -1, nullptr) +} + +void Statement::bind(int offset, const std::string &value, bool retain) { + BIND_5(blob, value.data(), int(value.size()), retain ? SQLITE_TRANSIENT : SQLITE_STATIC) +} + +bool Statement::run() { + assert(stmt); + const int err = sqlite3_step(stmt); + if (err == SQLITE_DONE) { + return false; + } else if (err == SQLITE_ROW) { + return true; + } else { + throw std::runtime_error("failed to run statement"); + } +} + +template <> int Statement::get(int offset) { + assert(stmt); + return sqlite3_column_int(stmt, offset); +} + +template <> int64_t Statement::get(int offset) { + assert(stmt); + return sqlite3_column_int64(stmt, offset); +} + +template <> double Statement::get(int offset) { + assert(stmt); + return sqlite3_column_double(stmt, offset); +} + +template <> std::string Statement::get(int offset) { + assert(stmt); + return { + reinterpret_cast<const char *>(sqlite3_column_blob(stmt, offset)), + size_t(sqlite3_column_bytes(stmt, offset)) + }; +} + +void Statement::reset() { + assert(stmt); + sqlite3_reset(stmt); +} + +} +} diff --git a/src/util/uv-channel.c b/src/util/uv-channel.c new file mode 100644 index 0000000000..4e3b9fa5ff --- /dev/null +++ b/src/util/uv-channel.c @@ -0,0 +1,69 @@ +#include <mbgl/util/uv-channel.h> +#include <mbgl/util/queue.h> + +#include <stdlib.h> + +// Taken from http://navaneeth.github.io/blog/2013/08/02/channels-in-libuv/ + +typedef struct { + void *data; + void *active_queue[2]; +} uv__chan_item_t; + +int uv_chan_init(uv_chan_t *chan) { + int r = uv_mutex_init(&chan->mutex); + if (r == -1) + return r; + + QUEUE_INIT(&chan->q); + + return uv_cond_init(&chan->cond); +} + +void uv_chan_send(uv_chan_t *chan, void *data) { + uv__chan_item_t *item = (uv__chan_item_t *)malloc(sizeof(uv__chan_item_t)); + item->data = data; + + uv_mutex_lock(&chan->mutex); + QUEUE_INSERT_TAIL(&chan->q, &item->active_queue); + uv_cond_signal(&chan->cond); + uv_mutex_unlock(&chan->mutex); +} + +void *uv_chan_receive(uv_chan_t *chan) { + uv__chan_item_t *item; + QUEUE *head; + void *data = NULL; + + uv_mutex_lock(&chan->mutex); + while (QUEUE_EMPTY(&chan->q)) { + uv_cond_wait(&chan->cond, &chan->mutex); + } + + head = QUEUE_HEAD(&chan->q); + item = QUEUE_DATA(head, uv__chan_item_t, active_queue); + data = item->data; + QUEUE_REMOVE(head); + free(item); + uv_mutex_unlock(&chan->mutex); + return data; +} + +void uv_chan_clear(uv_chan_t *chan) { + uv_mutex_lock(&chan->mutex); + uv__chan_item_t *item = NULL; + QUEUE *head = NULL; + while (!QUEUE_EMPTY(&chan->q)) { + head = QUEUE_HEAD(&chan->q); + item = QUEUE_DATA(head, uv__chan_item_t, active_queue); + QUEUE_REMOVE(head); + free(item); + } + uv_mutex_unlock(&chan->mutex); +} + +void uv_chan_destroy(uv_chan_t *chan) { + uv_chan_clear(chan); + uv_cond_destroy(&chan->cond); + uv_mutex_destroy(&chan->mutex); +} diff --git a/src/util/uv-messenger.c b/src/util/uv-messenger.c new file mode 100644 index 0000000000..a25c84dc59 --- /dev/null +++ b/src/util/uv-messenger.c @@ -0,0 +1,74 @@ +#include <mbgl/util/uv-messenger.h> +#include <mbgl/util/queue.h> + +#include <stdlib.h> + +typedef struct { + void *data; + void *queue[2]; +} uv__messenger_item_t; + +void uv__messenger_callback(uv_async_t *async) { + uv_messenger_t *msgr = (uv_messenger_t *)async->data; + + uv__messenger_item_t *item; + QUEUE *head; + + while (1) { + uv_mutex_lock(&msgr->mutex); + if (QUEUE_EMPTY(&msgr->queue)) { + uv_mutex_unlock(&msgr->mutex); + break; + } + + head = QUEUE_HEAD(&msgr->queue); + item = QUEUE_DATA(head, uv__messenger_item_t, queue); + QUEUE_REMOVE(head); + uv_mutex_unlock(&msgr->mutex); + + msgr->callback(item->data); + + free(item); + } +} + +int uv_messenger_init(uv_loop_t *loop, uv_messenger_t *msgr, uv_messenger_cb callback) { + int ret = uv_mutex_init(&msgr->mutex); + if (ret < 0) { + return ret; + } + + msgr->callback = callback; + + QUEUE_INIT(&msgr->queue); + + msgr->async.data = msgr; + return uv_async_init(loop, &msgr->async, uv__messenger_callback); +} + +void uv_messenger_send(uv_messenger_t *msgr, void *data) { + uv__messenger_item_t *item = (uv__messenger_item_t *)malloc(sizeof(uv__messenger_item_t)); + item->data = data; + + uv_mutex_lock(&msgr->mutex); + QUEUE_INSERT_TAIL(&msgr->queue, &item->queue); + uv_mutex_unlock(&msgr->mutex); + + uv_async_send(&msgr->async); +} + +void uv_messenger_ref(uv_messenger_t *msgr) { + uv_ref((uv_handle_t *)&msgr->async); +} + +void uv_messenger_unref(uv_messenger_t *msgr) { + uv_unref((uv_handle_t *)&msgr->async); +} + +void uv__messenger_stop_callback(uv_handle_t *handle) { + free((uv_messenger_t *)handle->data); +} + +void uv_messenger_stop(uv_messenger_t *msgr) { + uv_close((uv_handle_t *)&msgr->async, uv__messenger_stop_callback); +} diff --git a/src/util/uv-worker.c b/src/util/uv-worker.c new file mode 100644 index 0000000000..8b0cc6dda7 --- /dev/null +++ b/src/util/uv-worker.c @@ -0,0 +1,166 @@ +#include <mbgl/util/uv-worker.h> +#include <mbgl/util/uv-messenger.h> + +#include <stdio.h> +#include <assert.h> + +typedef struct uv__worker_item_s uv__worker_item_t; +struct uv__worker_item_s { + uv_worker_t *worker; + void *data; + uv_worker_cb work_cb; + uv_worker_after_cb after_work_cb; +}; + +typedef struct uv__worker_thread_s uv__worker_thread_t; +struct uv__worker_thread_s { + uv_worker_t *worker; + uv_thread_t thread; +}; + +void uv__worker_thread_finished(uv__worker_thread_t *worker_thread) { + uv_worker_t *worker = worker_thread->worker; + +#ifndef NDEBUG + assert(uv_thread_self() == worker->thread_id); +#endif + + // This should at most block very briefly. We are sending the termination + // notification as the last thing in the worker thread, so by now the thread + // has probably terminated already. If not, the waiting time should be + // extremely short. + uv_thread_join(&worker_thread->thread); + + assert(worker->count > 0); + worker->count--; + if (worker->count == 0) { + uv_chan_destroy(&worker->chan); + uv_messenger_stop(worker->msgr); + if (worker->close_cb) { + worker->close_cb(worker); + } + } +} + +void uv__worker_after(void *ptr) { + uv__worker_item_t *item = (uv__worker_item_t *)ptr; + + if (item->work_cb) { + // We are finishing a regular work request. + if (item->after_work_cb) { + assert(item->after_work_cb); + item->after_work_cb(item->data); + } + uv_worker_t *worker = item->worker; + assert(worker->active_items > 0); + if (--worker->active_items == 0) { + uv_messenger_unref(worker->msgr); + } + } else { + // This is a worker thread termination. + uv__worker_thread_t *worker_thread = (uv__worker_thread_t *)item->data; + uv__worker_thread_finished(worker_thread); + free(worker_thread); + } + + free(item); +} + +void uv__worker_thread_loop(void *ptr) { + uv__worker_thread_t *worker_thread = (uv__worker_thread_t *)ptr; + uv_worker_t *worker = worker_thread->worker; + +#ifdef __APPLE__ + if (worker->name) { + pthread_setname_np(worker->name); + } +#endif + + uv__worker_item_t *item = NULL; + while ((item = (uv__worker_item_t *)uv_chan_receive(&worker->chan)) != NULL) { + assert(item->work_cb); + item->work_cb(item->data); + + // Trigger the after callback in the main thread. + uv_messenger_send(worker->msgr, item); + } + + // Make sure to close all other workers too. + uv_chan_send(&worker->chan, NULL); + + // Create a new worker item that acts as a terminate flag for this thread. + item = (uv__worker_item_t *)malloc(sizeof(uv__worker_item_t)); + item->data = worker_thread; + item->work_cb = NULL; + item->after_work_cb = NULL; + uv_messenger_send(worker->msgr, item); +} + +int uv_worker_init(uv_worker_t *worker, uv_loop_t *loop, int count, const char *name) { +#ifndef NDEBUG + worker->thread_id = uv_thread_self(); +#endif + worker->loop = loop; + worker->name = name; + worker->count = 0; + worker->close_cb = NULL; + worker->active_items = 0; + worker->msgr = (uv_messenger_t *)malloc(sizeof(uv_messenger_t)); + int ret = uv_messenger_init(loop, worker->msgr, uv__worker_after); + if (ret < 0) { + free(worker->msgr); + return ret; + } + uv_messenger_unref(worker->msgr); + ret = uv_chan_init(&worker->chan); + if (ret < 0) return ret; + + // Initialize all worker threads. + int i; + for (i = 0; i < count; i++) { + uv__worker_thread_t *worker_thread = (uv__worker_thread_t *)malloc(sizeof(uv__worker_thread_t)); + worker_thread->worker = worker; + ret = uv_thread_create(&worker_thread->thread, uv__worker_thread_loop, worker_thread); + if (ret < 0) return ret; + worker->count++; + } + + return 0; +} + +void uv_worker_send(uv_worker_t *worker, void *data, uv_worker_cb work_cb, + uv_worker_after_cb after_work_cb) { +#ifndef NDEBUG + assert(uv_thread_self() == worker->thread_id); +#endif + + // It doesn't make sense to not provide a work callback. On the other hand, the after_work_cb + // may be NULL. In that case, there will be no callback called in the current thread and the + // worker item will instead be freed in the worker thread. + assert(work_cb); + + uv__worker_item_t *item = (uv__worker_item_t *)malloc(sizeof(uv__worker_item_t)); + item->worker = worker; + item->work_cb = work_cb; + item->after_work_cb = after_work_cb; + item->data = data; + uv_chan_send(&worker->chan, item); + if (worker->active_items++ == 0) { + uv_messenger_ref(worker->msgr); + } +} + +void uv_worker_close(uv_worker_t *worker, uv_worker_close_cb close_cb) { +#ifndef NDEBUG + assert(uv_thread_self() == worker->thread_id); +#endif + + // Prevent double calling. + assert(worker->close_cb == NULL); + + worker->close_cb = close_cb; + uv_chan_send(&worker->chan, NULL); + if (worker->active_items++ == 0) { + uv_messenger_ref(worker->msgr); + } +} diff --git a/src/util/uv.cpp b/src/util/uv.cpp index 94f074bfa1..6e15ac4537 100644 --- a/src/util/uv.cpp +++ b/src/util/uv.cpp @@ -16,4 +16,16 @@ std::string cwd() { return dir; } +void deleter::operator()(uv_async_t *async) { + uv_close((uv_handle_t *)async, [](uv_handle_t *handle) { + delete (uv_async_t *)handle; + }); +} + +void deleter::operator()(uv_timer_t *timer) { + uv_close((uv_handle_t *)timer, [](uv_handle_t *handle) { + delete (uv_timer_t *)handle; + }); +} + } |