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:
219
src/map/map.cpp
219
src/map/map.cpp
@@ -19,11 +19,10 @@
|
||||
#include <mbgl/style/style_bucket.hpp>
|
||||
#include <mbgl/util/texturepool.hpp>
|
||||
#include <mbgl/geometry/sprite_atlas.hpp>
|
||||
#include <mbgl/util/filesource.hpp>
|
||||
#include <mbgl/storage/file_source.hpp>
|
||||
#include <mbgl/platform/log.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include <iostream>
|
||||
|
||||
#define _USE_MATH_DEFINES
|
||||
@@ -34,12 +33,15 @@ using namespace mbgl;
|
||||
Map::Map(View& view)
|
||||
: loop(std::make_shared<uv::loop>()),
|
||||
thread(std::make_unique<uv::thread>()),
|
||||
async_terminate(new uv_async_t()),
|
||||
async_render(new uv_async_t()),
|
||||
async_cleanup(new uv_async_t()),
|
||||
view(view),
|
||||
#ifndef NDEBUG
|
||||
main_thread(uv_thread_self()),
|
||||
#endif
|
||||
transform(view),
|
||||
fileSource(std::make_shared<FileSource>()),
|
||||
style(std::make_shared<Style>()),
|
||||
glyphAtlas(std::make_shared<GlyphAtlas>(1024, 1024)),
|
||||
glyphStore(std::make_shared<GlyphStore>(fileSource)),
|
||||
spriteAtlas(std::make_shared<SpriteAtlas>(512, 512)),
|
||||
texturepool(std::make_shared<Texturepool>()),
|
||||
painter(*this) {
|
||||
@@ -53,49 +55,93 @@ Map::Map(View& view)
|
||||
}
|
||||
|
||||
Map::~Map() {
|
||||
// Clear the style first before the rest of the constructor deletes members of this object.
|
||||
// This is required because members of the style reference the Map object in their destructors.
|
||||
style.reset();
|
||||
|
||||
if (async) {
|
||||
stop();
|
||||
}
|
||||
|
||||
// Explicitly reset all pointers.
|
||||
texturepool.reset();
|
||||
sprite.reset();
|
||||
spriteAtlas.reset();
|
||||
glyphStore.reset();
|
||||
glyphAtlas.reset();
|
||||
style.reset();
|
||||
fileSource.reset();
|
||||
workers.reset();
|
||||
|
||||
uv_run(**loop, UV_RUN_DEFAULT);
|
||||
}
|
||||
|
||||
uv::worker &Map::getWorker() {
|
||||
if (!workers) {
|
||||
workers = std::make_unique<uv::worker>(**loop, 4, "Tile Worker");
|
||||
}
|
||||
return *workers;
|
||||
}
|
||||
|
||||
void Map::start() {
|
||||
assert(uv_thread_self() == main_thread);
|
||||
assert(!async);
|
||||
|
||||
// When starting map rendering in another thread, we perform async/continuously
|
||||
// updated rendering. Only in these cases, we attach the async handlers.
|
||||
async = true;
|
||||
|
||||
// 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.
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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)));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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();
|
||||
},
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
86
src/storage/base_request.cpp
Normal file
86
src/storage/base_request.cpp
Normal 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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
37
src/storage/file_request.cpp
Normal file
37
src/storage/file_request.cpp
Normal 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();
|
||||
}
|
||||
|
||||
}
|
||||
142
src/storage/file_request_baton.cpp
Normal file
142
src/storage/file_request_baton.cpp
Normal 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
105
src/storage/file_source.cpp
Normal 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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
270
src/storage/http_request.cpp
Normal file
270
src/storage/http_request.cpp
Normal 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
|
||||
|
||||
}
|
||||
12
src/storage/http_request_baton.cpp
Normal file
12
src/storage/http_request_baton.cpp
Normal 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
49
src/storage/request.cpp
Normal 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
22
src/storage/response.cpp
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
227
src/storage/sqlite_store.cpp
Normal file
227
src/storage/sqlite_store.cpp
Normal 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;
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
81
src/util/compression.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
689
src/util/parsedate.c
Normal 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
|
||||
@@ -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
166
src/util/sqlite3.cpp
Normal 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
69
src/util/uv-channel.c
Normal 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
74
src/util/uv-messenger.c
Normal 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
166
src/util/uv-worker.c
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user