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
This commit is contained in:
Mike Morris
2014-10-10 12:24:45 -04:00
115 changed files with 5035 additions and 866 deletions

View File

@@ -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;
// Setup async notifications
async_terminate = new uv_async_t();
uv_async_init(**loop, async_terminate, terminate);
async_terminate->data = **loop;
// Reset the flag.
is_stopped = false;
async_render = new uv_async_t();
uv_async_init(**loop, async_render, render);
// Setup async notifications
uv_async_init(**loop, async_terminate.get(), terminate);
async_terminate->data = this;
uv_async_init(**loop, async_render.get(), render);
async_render->data = this;
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.

View File

@@ -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();
}

View File

@@ -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;
}

View File

@@ -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)));
}
}
});

View File

@@ -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();
},

View File

@@ -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);
}

View File

@@ -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;
});
}

View File

@@ -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()) {

View File

@@ -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;
});
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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;

View File

@@ -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);
}

View File

@@ -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;

View File

@@ -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);

View File

@@ -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();

View File

@@ -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;

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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();
}
}
}

View File

@@ -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();
}
}

View File

@@ -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;
}
}

105
src/storage/file_source.cpp Normal file
View File

@@ -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();
}
}
}
}

View File

@@ -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
}

View File

@@ -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() {
}
}

49
src/storage/request.cpp Normal file
View File

@@ -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();
}
}

22
src/storage/response.cpp Normal file
View File

@@ -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;
}
}

View File

@@ -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;
});
}
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -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);

View File

@@ -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;

81
src/util/compression.cpp Normal file
View File

@@ -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;
}
}
}

View File

@@ -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);
}
}
}

689
src/util/parsedate.c Normal file
View File

@@ -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

View File

@@ -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)
{}

166
src/util/sqlite3.cpp Normal file
View File

@@ -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);
}
}
}

69
src/util/uv-channel.c Normal file
View File

@@ -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);
}

74
src/util/uv-messenger.c Normal file
View File

@@ -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);
}

166
src/util/uv-worker.c Normal file
View File

@@ -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);
}
}

View File

@@ -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;
});
}
}