summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorMike Morris <michael.patrick.morris@gmail.com>2014-10-10 12:24:45 -0400
committerMike Morris <michael.patrick.morris@gmail.com>2014-10-10 12:24:45 -0400
commit2d1219fa5154c489cd856bedd04b84573d45ac04 (patch)
treea8e42e6acd79f73aac228e0fe6876917067db8c4 /src
parent8f6e8eead12c6b2c2de0ce76fa7df39ca2445006 (diff)
parentf390dab0ea7d449bdd89855c84e47f4a07606fe4 (diff)
downloadqtlocation-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')
-rw-r--r--src/map/map.cpp217
-rw-r--r--src/map/raster_tile_data.cpp6
-rw-r--r--src/map/source.cpp39
-rw-r--r--src/map/sprite.cpp30
-rw-r--r--src/map/tile_data.cpp35
-rw-r--r--src/map/tile_parser.cpp39
-rw-r--r--src/map/transform.cpp2
-rw-r--r--src/map/vector_tile_data.cpp10
-rw-r--r--src/platform/request.cpp54
-rw-r--r--src/renderer/debug_bucket.cpp2
-rw-r--r--src/renderer/fill_bucket.cpp2
-rw-r--r--src/renderer/line_bucket.cpp2
-rw-r--r--src/renderer/painter.cpp4
-rw-r--r--src/renderer/painter_clipping.cpp4
-rw-r--r--src/renderer/painter_fill.cpp2
-rw-r--r--src/renderer/painter_line.cpp4
-rw-r--r--src/renderer/painter_raster.cpp4
-rw-r--r--src/renderer/painter_symbol.cpp2
-rw-r--r--src/renderer/raster_bucket.cpp4
-rw-r--r--src/renderer/symbol_bucket.cpp2
-rw-r--r--src/storage/base_request.cpp86
-rw-r--r--src/storage/file_request.cpp37
-rw-r--r--src/storage/file_request_baton.cpp142
-rw-r--r--src/storage/file_source.cpp105
-rw-r--r--src/storage/http_request.cpp270
-rw-r--r--src/storage/http_request_baton.cpp12
-rw-r--r--src/storage/request.cpp49
-rw-r--r--src/storage/response.cpp22
-rw-r--r--src/storage/sqlite_store.cpp227
-rw-r--r--src/style/style_layer_group.cpp6
-rw-r--r--src/style/style_parser.cpp32
-rw-r--r--src/text/glyph.cpp3
-rw-r--r--src/text/glyph_store.cpp43
-rw-r--r--src/util/compression.cpp81
-rw-r--r--src/util/filesource.cpp58
-rw-r--r--src/util/parsedate.c689
-rw-r--r--src/util/raster.cpp13
-rw-r--r--src/util/sqlite3.cpp166
-rw-r--r--src/util/uv-channel.c69
-rw-r--r--src/util/uv-messenger.c74
-rw-r--r--src/util/uv-worker.c166
-rw-r--r--src/util/uv.cpp12
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;
+ });
+}
+
}