Merge pull request #1588 from mapbox/1565-failed_requests
Notify renderStill() of request failures
This commit is contained in:
@@ -111,7 +111,16 @@ int main(int argc, char *argv[]) {
|
||||
util::write_file(output, png);
|
||||
});
|
||||
|
||||
map.renderStill([async](std::unique_ptr<const StillImage> image) {
|
||||
map.renderStill([async](std::exception_ptr error, std::unique_ptr<const StillImage> image) {
|
||||
try {
|
||||
if (error) {
|
||||
std::rethrow_exception(error);
|
||||
}
|
||||
} catch(std::exception& e) {
|
||||
std::cout << "Error: " << e.what() << std::endl;
|
||||
exit(1);
|
||||
}
|
||||
|
||||
async->data = const_cast<StillImage *>(image.release());
|
||||
uv_async_send(async);
|
||||
});
|
||||
|
||||
@@ -42,7 +42,7 @@ public:
|
||||
|
||||
// Register a callback that will get called (on the render thread) when all resources have
|
||||
// been loaded and a complete render occurs.
|
||||
using StillImageCallback = std::function<void(std::unique_ptr<const StillImage>)>;
|
||||
using StillImageCallback = std::function<void(std::exception_ptr, std::unique_ptr<const StillImage>)>;
|
||||
void renderStill(StillImageCallback callback);
|
||||
|
||||
// Triggers a synchronous or asynchronous render.
|
||||
|
||||
@@ -29,6 +29,7 @@ enum class Event : uint8_t {
|
||||
ParseStyle,
|
||||
ParseTile,
|
||||
Render,
|
||||
ResourceLoader,
|
||||
Database,
|
||||
HttpRequest,
|
||||
Sprite,
|
||||
@@ -46,6 +47,7 @@ MBGL_DEFINE_ENUM_CLASS(EventClass, Event, {
|
||||
{ Event::ParseStyle, "ParseStyle" },
|
||||
{ Event::ParseTile, "ParseTile" },
|
||||
{ Event::Render, "Render" },
|
||||
{ Event::ResourceLoader, "ResourceLoader" },
|
||||
{ Event::Database, "Database" },
|
||||
{ Event::HttpRequest, "HttpRequest" },
|
||||
{ Event::Sprite, "Sprite" },
|
||||
|
||||
@@ -11,6 +11,11 @@ struct Exception : std::runtime_error {
|
||||
inline Exception(const std::string &msg) : std::runtime_error(msg) {}
|
||||
};
|
||||
|
||||
struct GlyphRangeLoadingException : Exception {
|
||||
inline GlyphRangeLoadingException(const char *msg) : Exception(msg) {}
|
||||
inline GlyphRangeLoadingException(const std::string &msg) : Exception(msg) {}
|
||||
};
|
||||
|
||||
struct MisuseException : Exception {
|
||||
inline MisuseException(const char *msg) : Exception(msg) {}
|
||||
inline MisuseException(const std::string &msg) : Exception(msg) {}
|
||||
@@ -21,6 +26,21 @@ struct ShaderException : Exception {
|
||||
inline ShaderException(const std::string &msg) : Exception(msg) {}
|
||||
};
|
||||
|
||||
struct SourceLoadingException : Exception {
|
||||
inline SourceLoadingException(const char *msg) : Exception(msg) {}
|
||||
inline SourceLoadingException(const std::string &msg) : Exception(msg) {}
|
||||
};
|
||||
|
||||
struct SpriteLoadingException : Exception {
|
||||
inline SpriteLoadingException(const char *msg) : Exception(msg) {}
|
||||
inline SpriteLoadingException(const std::string &msg) : Exception(msg) {}
|
||||
};
|
||||
|
||||
struct TileLoadingException : Exception {
|
||||
inline TileLoadingException(const char *msg) : Exception(msg) {}
|
||||
inline TileLoadingException(const std::string &msg) : Exception(msg) {}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -232,7 +232,7 @@ void MapContext::render() {
|
||||
painter->render(*style, transformState, data.getAnimationTime());
|
||||
|
||||
if (data.mode == MapMode::Still) {
|
||||
callback(view.readStillImage());
|
||||
callback(nullptr, view.readStillImage());
|
||||
callback = nullptr;
|
||||
}
|
||||
|
||||
@@ -274,4 +274,13 @@ void MapContext::onTileDataChanged() {
|
||||
triggerUpdate();
|
||||
}
|
||||
|
||||
void MapContext::onResourceLoadingFailed(std::exception_ptr error) {
|
||||
assert(Environment::currentlyOn(ThreadType::Map));
|
||||
|
||||
if (data.mode == MapMode::Still && callback) {
|
||||
callback(error, nullptr);
|
||||
callback = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ public:
|
||||
|
||||
void resize(uint16_t width, uint16_t height, float ratio);
|
||||
|
||||
using StillImageCallback = std::function<void(std::unique_ptr<const StillImage>)>;
|
||||
using StillImageCallback = std::function<void(std::exception_ptr, std::unique_ptr<const StillImage>)>;
|
||||
void renderStill(StillImageCallback callback);
|
||||
|
||||
void triggerUpdate(Update = Update::Nothing);
|
||||
@@ -61,6 +61,7 @@ public:
|
||||
|
||||
// ResourceLoader::Observer implementation.
|
||||
void onTileDataChanged() override;
|
||||
void onResourceLoadingFailed(std::exception_ptr error) override;
|
||||
|
||||
private:
|
||||
void updateTiles();
|
||||
|
||||
@@ -104,10 +104,18 @@ void ResourceLoader::onGlyphRangeLoaded() {
|
||||
emitTileDataChanged();
|
||||
}
|
||||
|
||||
void ResourceLoader::onGlyphRangeLoadingFailed(std::exception_ptr error) {
|
||||
emitResourceLoadingFailed(error);
|
||||
}
|
||||
|
||||
void ResourceLoader::onSourceLoaded() {
|
||||
emitTileDataChanged();
|
||||
}
|
||||
|
||||
void ResourceLoader::onSourceLoadingFailed(std::exception_ptr error) {
|
||||
emitResourceLoadingFailed(error);
|
||||
}
|
||||
|
||||
void ResourceLoader::onTileLoaded(bool isNewTile) {
|
||||
if (isNewTile) {
|
||||
shouldReparsePartialTiles_ = true;
|
||||
@@ -116,12 +124,20 @@ void ResourceLoader::onTileLoaded(bool isNewTile) {
|
||||
emitTileDataChanged();
|
||||
}
|
||||
|
||||
void ResourceLoader::onTileLoadingFailed(std::exception_ptr error) {
|
||||
emitResourceLoadingFailed(error);
|
||||
}
|
||||
|
||||
void ResourceLoader::onSpriteLoaded() {
|
||||
shouldReparsePartialTiles_ = true;
|
||||
|
||||
emitTileDataChanged();
|
||||
}
|
||||
|
||||
void ResourceLoader::onSpriteLoadingFailed(std::exception_ptr error) {
|
||||
emitResourceLoadingFailed(error);
|
||||
}
|
||||
|
||||
void ResourceLoader::emitTileDataChanged() {
|
||||
assert(Environment::currentlyOn(ThreadType::Map));
|
||||
|
||||
@@ -130,4 +146,20 @@ void ResourceLoader::emitTileDataChanged() {
|
||||
}
|
||||
}
|
||||
|
||||
void ResourceLoader::emitResourceLoadingFailed(std::exception_ptr error) {
|
||||
assert(Environment::currentlyOn(ThreadType::Map));
|
||||
|
||||
try {
|
||||
if (error) {
|
||||
std::rethrow_exception(error);
|
||||
}
|
||||
} catch(const std::exception& e) {
|
||||
Log::Error(Event::ResourceLoader, e.what());
|
||||
}
|
||||
|
||||
if (observer_) {
|
||||
observer_->onResourceLoadingFailed(error);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ public:
|
||||
virtual ~Observer() = default;
|
||||
|
||||
virtual void onTileDataChanged() = 0;
|
||||
virtual void onResourceLoadingFailed(std::exception_ptr error) = 0;
|
||||
};
|
||||
|
||||
ResourceLoader();
|
||||
@@ -62,16 +63,21 @@ public:
|
||||
|
||||
// GlyphStore::Observer implementation.
|
||||
void onGlyphRangeLoaded() override;
|
||||
void onGlyphRangeLoadingFailed(std::exception_ptr error) override;
|
||||
|
||||
// Source::Observer implementation.
|
||||
void onSourceLoaded() override;
|
||||
void onSourceLoadingFailed(std::exception_ptr error) override;
|
||||
void onTileLoaded(bool isNewTile) override;
|
||||
void onTileLoadingFailed(std::exception_ptr error) override;
|
||||
|
||||
// Sprite::Observer implementation.
|
||||
void onSpriteLoaded() override;
|
||||
void onSpriteLoadingFailed(std::exception_ptr error) override;
|
||||
|
||||
private:
|
||||
void emitTileDataChanged();
|
||||
void emitResourceLoadingFailed(std::exception_ptr error);
|
||||
|
||||
bool shouldReparsePartialTiles_ = false;
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include <mbgl/map/transform.hpp>
|
||||
#include <mbgl/map/tile.hpp>
|
||||
#include <mbgl/renderer/painter.hpp>
|
||||
#include <mbgl/util/exception.hpp>
|
||||
#include <mbgl/util/constants.hpp>
|
||||
#include <mbgl/storage/resource.hpp>
|
||||
#include <mbgl/storage/response.hpp>
|
||||
@@ -150,7 +151,9 @@ void Source::load(const std::string& accessToken) {
|
||||
req = nullptr;
|
||||
|
||||
if (res.status != Response::Successful) {
|
||||
Log::Warning(Event::General, "Failed to load source TileJSON: %s", res.message.c_str());
|
||||
std::stringstream message;
|
||||
message << "Failed to load [" << info.url << "]: " << res.message;
|
||||
emitSourceLoadingFailed(message.str());
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -158,7 +161,9 @@ void Source::load(const std::string& accessToken) {
|
||||
d.Parse<0>(res.data.c_str());
|
||||
|
||||
if (d.HasParseError()) {
|
||||
Log::Error(Event::General, "Invalid source TileJSON; Parse Error at %d: %s", d.GetErrorOffset(), d.GetParseError());
|
||||
std::stringstream message;
|
||||
message << "Failed to parse [" << info.url << "]: " << d.GetErrorOffset() << " - " << d.GetParseError();
|
||||
emitSourceLoadingFailed(message.str());
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -282,22 +287,26 @@ TileData::State Source::addTile(MapData& data,
|
||||
new_tile.data = cache.get(normalized_id.to_uint64());
|
||||
}
|
||||
|
||||
auto callback = std::bind(&Source::emitTileLoaded, this, true);
|
||||
auto successCallback = std::bind(&Source::emitTileLoaded, this, true);
|
||||
auto failureCallback = std::bind(&Source::emitTileLoadingFailed, this, std::placeholders::_1);
|
||||
|
||||
if (!new_tile.data) {
|
||||
// If we don't find working tile data, we're just going to load it.
|
||||
if (info.type == SourceType::Vector) {
|
||||
new_tile.data =
|
||||
std::make_shared<VectorTileData>(normalized_id, data.transform.getMaxZoom(), style, glyphAtlas,
|
||||
glyphStore, spriteAtlas, sprite, info);
|
||||
new_tile.data->request(style.workers, transformState.getPixelRatio(), callback);
|
||||
new_tile.data->request(
|
||||
style.workers, transformState.getPixelRatio(), successCallback, failureCallback);
|
||||
} else if (info.type == SourceType::Raster) {
|
||||
new_tile.data = std::make_shared<RasterTileData>(normalized_id, texturePool, info);
|
||||
new_tile.data->request(style.workers, transformState.getPixelRatio(), callback);
|
||||
new_tile.data->request(
|
||||
style.workers, transformState.getPixelRatio(), successCallback, failureCallback);
|
||||
} else if (info.type == SourceType::Annotations) {
|
||||
new_tile.data = std::make_shared<LiveTileData>(normalized_id, data.annotationManager,
|
||||
data.transform.getMaxZoom(), style, glyphAtlas,
|
||||
glyphStore, spriteAtlas, sprite, info);
|
||||
new_tile.data->reparse(style.workers, callback);
|
||||
new_tile.data->reparse(style.workers, successCallback);
|
||||
} else {
|
||||
throw std::runtime_error("source type not implemented");
|
||||
}
|
||||
@@ -536,10 +545,28 @@ void Source::emitSourceLoaded() {
|
||||
}
|
||||
}
|
||||
|
||||
void Source::emitSourceLoadingFailed(const std::string& message) {
|
||||
if (!observer_) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto error = std::make_exception_ptr(util::SourceLoadingException(message));
|
||||
observer_->onSourceLoadingFailed(error);
|
||||
}
|
||||
|
||||
void Source::emitTileLoaded(bool isNewTile) {
|
||||
if (observer_) {
|
||||
observer_->onTileLoaded(isNewTile);
|
||||
}
|
||||
}
|
||||
|
||||
void Source::emitTileLoadingFailed(const std::string& message) {
|
||||
if (!observer_) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto error = std::make_exception_ptr(util::TileLoadingException(message));
|
||||
observer_->onTileLoadingFailed(error);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -58,7 +58,10 @@ public:
|
||||
virtual ~Observer() = default;
|
||||
|
||||
virtual void onSourceLoaded() = 0;
|
||||
virtual void onSourceLoadingFailed(std::exception_ptr error) = 0;
|
||||
|
||||
virtual void onTileLoaded(bool isNewTile) = 0;
|
||||
virtual void onTileLoadingFailed(std::exception_ptr error) = 0;
|
||||
};
|
||||
|
||||
Source();
|
||||
@@ -102,7 +105,9 @@ public:
|
||||
|
||||
private:
|
||||
void emitSourceLoaded();
|
||||
void emitSourceLoadingFailed(const std::string& message);
|
||||
void emitTileLoaded(bool isNewTile);
|
||||
void emitTileLoadingFailed(const std::string& message);
|
||||
|
||||
bool handlePartialTile(const TileID &id, Worker &worker);
|
||||
bool findLoadedChildren(const TileID& id, int32_t maxCoveringZoom, std::forward_list<TileID>& retain);
|
||||
|
||||
@@ -1,17 +1,20 @@
|
||||
#include <mbgl/map/sprite.hpp>
|
||||
#include <mbgl/util/raster.hpp>
|
||||
#include <mbgl/platform/log.hpp>
|
||||
|
||||
#include <string>
|
||||
#include <mbgl/platform/platform.hpp>
|
||||
#include <mbgl/map/environment.hpp>
|
||||
#include <mbgl/platform/log.hpp>
|
||||
#include <mbgl/platform/platform.hpp>
|
||||
#include <mbgl/storage/resource.hpp>
|
||||
#include <mbgl/storage/response.hpp>
|
||||
#include <mbgl/util/uv_detail.hpp>
|
||||
#include <mbgl/util/exception.hpp>
|
||||
#include <mbgl/util/raster.hpp>
|
||||
#include <mbgl/util/std.hpp>
|
||||
#include <mbgl/util/uv_detail.hpp>
|
||||
|
||||
#include <rapidjson/document.h>
|
||||
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
|
||||
using namespace mbgl;
|
||||
|
||||
SpritePosition::SpritePosition(uint16_t x_, uint16_t y_, uint16_t width_, uint16_t height_, float pixelRatio_, bool sdf_)
|
||||
@@ -39,25 +42,31 @@ Sprite::Sprite(const std::string& baseUrl, float pixelRatio_)
|
||||
std::string spriteURL(baseUrl + (pixelRatio_ > 1 ? "@2x" : "") + ".png");
|
||||
std::string jsonURL(baseUrl + (pixelRatio_ > 1 ? "@2x" : "") + ".json");
|
||||
|
||||
jsonRequest = env.request({ Resource::Kind::JSON, jsonURL }, [this](const Response &res) {
|
||||
jsonRequest = env.request({ Resource::Kind::JSON, jsonURL }, [this, jsonURL](const Response &res) {
|
||||
jsonRequest = nullptr;
|
||||
if (res.status == Response::Successful) {
|
||||
body = res.data;
|
||||
parseJSON();
|
||||
} else {
|
||||
Log::Warning(Event::Sprite, "Failed to load sprite info: %s", res.message.c_str());
|
||||
std::stringstream message;
|
||||
message << "Failed to load [" << jsonURL << "]: " << res.message;
|
||||
emitSpriteLoadingFailed(message.str());
|
||||
return;
|
||||
}
|
||||
loadedJSON = true;
|
||||
emitSpriteLoadedIfComplete();
|
||||
});
|
||||
|
||||
spriteRequest = env.request({ Resource::Kind::Image, spriteURL }, [this](const Response &res) {
|
||||
spriteRequest = env.request({ Resource::Kind::Image, spriteURL }, [this, spriteURL](const Response &res) {
|
||||
spriteRequest = nullptr;
|
||||
if (res.status == Response::Successful) {
|
||||
image = res.data;
|
||||
parseImage();
|
||||
} else {
|
||||
Log::Warning(Event::Sprite, "Failed to load sprite image: %s", res.message.c_str());
|
||||
std::stringstream message;
|
||||
message << "Failed to load [" << spriteURL << "]: " << res.message;
|
||||
emitSpriteLoadingFailed(message.str());
|
||||
return;
|
||||
}
|
||||
loadedImage = true;
|
||||
emitSpriteLoadedIfComplete();
|
||||
@@ -80,6 +89,15 @@ void Sprite::emitSpriteLoadedIfComplete() {
|
||||
}
|
||||
}
|
||||
|
||||
void Sprite::emitSpriteLoadingFailed(const std::string& message) {
|
||||
if (!observer) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto error = std::make_exception_ptr(util::SpriteLoadingException(message));
|
||||
observer->onSpriteLoadingFailed(error);
|
||||
}
|
||||
|
||||
bool Sprite::isLoaded() const {
|
||||
return loadedImage && loadedJSON;
|
||||
}
|
||||
|
||||
@@ -39,6 +39,7 @@ public:
|
||||
virtual ~Observer() = default;
|
||||
|
||||
virtual void onSpriteLoaded() = 0;
|
||||
virtual void onSpriteLoadingFailed(std::exception_ptr error) = 0;
|
||||
};
|
||||
|
||||
Sprite(const std::string& baseUrl, float pixelRatio);
|
||||
@@ -56,6 +57,7 @@ public:
|
||||
void setObserver(Observer* observer);
|
||||
private:
|
||||
void emitSpriteLoadedIfComplete();
|
||||
void emitSpriteLoadingFailed(const std::string& message);
|
||||
|
||||
void parseJSON();
|
||||
void parseImage();
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
#include <mbgl/map/tile_data.hpp>
|
||||
|
||||
#include <mbgl/map/environment.hpp>
|
||||
#include <mbgl/map/source.hpp>
|
||||
|
||||
#include <mbgl/storage/file_source.hpp>
|
||||
#include <mbgl/util/worker.hpp>
|
||||
#include <mbgl/util/work_request.hpp>
|
||||
#include <mbgl/platform/log.hpp>
|
||||
#include <mbgl/storage/file_source.hpp>
|
||||
#include <mbgl/util/work_request.hpp>
|
||||
#include <mbgl/util/worker.hpp>
|
||||
|
||||
#include <sstream>
|
||||
|
||||
using namespace mbgl;
|
||||
|
||||
@@ -34,15 +36,20 @@ void TileData::setState(const State& state_) {
|
||||
state = state_;
|
||||
}
|
||||
|
||||
void TileData::request(Worker& worker, float pixelRatio, std::function<void()> callback) {
|
||||
void TileData::request(Worker& worker,
|
||||
float pixelRatio,
|
||||
const std::function<void()>& successCallback,
|
||||
const std::function<void(const std::string& message)>& failureCallback) {
|
||||
std::string url = source.tileURL(id, pixelRatio);
|
||||
state = State::loading;
|
||||
|
||||
req = env.request({ Resource::Kind::Tile, url }, [url, callback, &worker, this](const Response &res) {
|
||||
req = env.request({ Resource::Kind::Tile, url }, [url, successCallback, failureCallback, &worker, this](const Response &res) {
|
||||
req = nullptr;
|
||||
|
||||
if (res.status != Response::Successful) {
|
||||
Log::Error(Event::HttpRequest, "[%s] tile loading failed: %s", url.c_str(), res.message.c_str());
|
||||
std::stringstream message;
|
||||
message << "Failed to load [" << url << "]: " << res.message;
|
||||
failureCallback(message.str());
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -50,7 +57,7 @@ void TileData::request(Worker& worker, float pixelRatio, std::function<void()> c
|
||||
data = res.data;
|
||||
|
||||
// Schedule tile parsing in another thread
|
||||
reparse(worker, callback);
|
||||
reparse(worker, successCallback);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -44,7 +44,10 @@ public:
|
||||
TileData(const TileID&, const SourceInfo&);
|
||||
~TileData();
|
||||
|
||||
void request(Worker&, float pixelRatio, std::function<void ()> callback);
|
||||
void request(Worker&,
|
||||
float pixelRatio,
|
||||
const std::function<void()>& successCallback,
|
||||
const std::function<void(const std::string& message)>& failureCallback);
|
||||
|
||||
// Schedule a tile reparse on a worker thread and call the callback on
|
||||
// completion. It will return true if the work was schedule or false it was
|
||||
|
||||
@@ -1,20 +1,23 @@
|
||||
#include <mbgl/text/glyph_store.hpp>
|
||||
|
||||
#include <mbgl/map/environment.hpp>
|
||||
#include <mbgl/util/std.hpp>
|
||||
#include <mbgl/util/string.hpp>
|
||||
#include <mbgl/util/utf.hpp>
|
||||
#include <mbgl/util/pbf.hpp>
|
||||
#include <mbgl/util/url.hpp>
|
||||
#include <mbgl/util/constants.hpp>
|
||||
#include <mbgl/util/token.hpp>
|
||||
#include <mbgl/util/math.hpp>
|
||||
#include <mbgl/util/uv_detail.hpp>
|
||||
#include <mbgl/storage/file_source.hpp>
|
||||
#include <mbgl/platform/log.hpp>
|
||||
#include <mbgl/platform/platform.hpp>
|
||||
#include <mbgl/storage/file_source.hpp>
|
||||
#include <mbgl/util/constants.hpp>
|
||||
#include <mbgl/util/exception.hpp>
|
||||
#include <mbgl/util/math.hpp>
|
||||
#include <mbgl/util/pbf.hpp>
|
||||
#include <mbgl/util/std.hpp>
|
||||
#include <mbgl/util/string.hpp>
|
||||
#include <mbgl/util/token.hpp>
|
||||
#include <mbgl/util/url.hpp>
|
||||
#include <mbgl/util/utf.hpp>
|
||||
#include <mbgl/util/uv_detail.hpp>
|
||||
#include <mbgl/util/uv_detail.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <sstream>
|
||||
|
||||
namespace mbgl {
|
||||
|
||||
@@ -139,7 +142,8 @@ GlyphPBF::GlyphPBF(const std::string& glyphURL,
|
||||
const std::string& fontStack,
|
||||
GlyphRange glyphRange,
|
||||
Environment& env_,
|
||||
const GlyphLoadedCallback& callback)
|
||||
const GlyphLoadedCallback& successCallback,
|
||||
const GlyphLoadingFailedCallback& failureCallback)
|
||||
: parsed(false), env(env_) {
|
||||
// Load the glyph set URL
|
||||
std::string url = util::replaceTokens(glyphURL, [&](const std::string &name) -> std::string {
|
||||
@@ -149,20 +153,20 @@ GlyphPBF::GlyphPBF(const std::string& glyphURL,
|
||||
});
|
||||
|
||||
// The prepare call jumps back to the main thread.
|
||||
req = env.request({ Resource::Kind::Glyphs, url }, [&, url, callback](const Response &res) {
|
||||
req = env.request({ Resource::Kind::Glyphs, url }, [&, url, successCallback, failureCallback](const Response &res) {
|
||||
req = nullptr;
|
||||
|
||||
if (res.status != Response::Successful) {
|
||||
// Something went wrong with loading the glyph pbf.
|
||||
const std::string msg = std::string { "[ERROR] failed to load glyphs: " } + url + " message: " + res.message;
|
||||
Log::Error(Event::HttpRequest, msg);
|
||||
std::stringstream message;
|
||||
message << "Failed to load [" << url << "]: " << res.message;
|
||||
failureCallback(message.str());
|
||||
} 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;
|
||||
callback(this);
|
||||
successCallback(this);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -234,8 +238,10 @@ bool GlyphPBF::isParsed() const {
|
||||
GlyphStore::GlyphStore(uv_loop_t* loop, Environment& env_)
|
||||
: env(env_),
|
||||
asyncEmitGlyphRangeLoaded(util::make_unique<uv::async>(loop, [this] { emitGlyphRangeLoaded(); })),
|
||||
asyncEmitGlyphRangeLoadedingFailed(util::make_unique<uv::async>(loop, [this] { emitGlyphRangeLoadingFailed(); })),
|
||||
observer(nullptr) {
|
||||
asyncEmitGlyphRangeLoaded->unref();
|
||||
asyncEmitGlyphRangeLoadedingFailed->unref();
|
||||
}
|
||||
|
||||
GlyphStore::~GlyphStore() {
|
||||
@@ -254,19 +260,26 @@ bool GlyphStore::requestGlyphRangesIfNeeded(const std::string& fontStackName,
|
||||
return requestIsNeeded;
|
||||
}
|
||||
|
||||
auto callback = [this, fontStackName](GlyphPBF* glyph) {
|
||||
auto successCallback = [this, fontStackName](GlyphPBF* glyph) {
|
||||
auto fontStack = createFontStack(fontStackName);
|
||||
glyph->parse(**fontStack);
|
||||
asyncEmitGlyphRangeLoaded->send();
|
||||
};
|
||||
|
||||
auto failureCallback = [this](const std::string& message) {
|
||||
std::lock_guard<std::mutex> lock(errorMessageMutex);
|
||||
errorMessage = message;
|
||||
asyncEmitGlyphRangeLoadedingFailed->send();
|
||||
};
|
||||
|
||||
std::lock_guard<std::mutex> lock(rangesMutex);
|
||||
auto& rangeSets = ranges[fontStackName];
|
||||
|
||||
for (const auto& range : glyphRanges) {
|
||||
const auto& rangeSets_it = rangeSets.find(range);
|
||||
if (rangeSets_it == rangeSets.end()) {
|
||||
auto glyph = util::make_unique<GlyphPBF>(glyphURL, fontStackName, range, env, callback);
|
||||
auto glyph = util::make_unique<GlyphPBF>(glyphURL, fontStackName, range, env,
|
||||
successCallback, failureCallback);
|
||||
rangeSets.emplace(range, std::move(glyph));
|
||||
requestIsNeeded = true;
|
||||
continue;
|
||||
@@ -312,4 +325,14 @@ void GlyphStore::emitGlyphRangeLoaded() {
|
||||
}
|
||||
}
|
||||
|
||||
void GlyphStore::emitGlyphRangeLoadingFailed() {
|
||||
if (!observer) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(errorMessageMutex);
|
||||
auto error = std::make_exception_ptr(util::GlyphRangeLoadingException(errorMessage));
|
||||
observer->onGlyphRangeLoadingFailed(error);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -59,12 +59,14 @@ private:
|
||||
class GlyphPBF {
|
||||
public:
|
||||
using GlyphLoadedCallback = std::function<void(GlyphPBF*)>;
|
||||
using GlyphLoadingFailedCallback = std::function<void(const std::string&)>;
|
||||
|
||||
GlyphPBF(const std::string &glyphURL,
|
||||
const std::string &fontStack,
|
||||
GlyphRange glyphRange,
|
||||
Environment &env,
|
||||
const GlyphLoadedCallback& callback);
|
||||
const GlyphLoadedCallback& successCallback,
|
||||
const GlyphLoadingFailedCallback& failureCallback);
|
||||
~GlyphPBF();
|
||||
|
||||
void parse(FontStack &stack);
|
||||
@@ -91,6 +93,7 @@ public:
|
||||
virtual ~Observer() = default;
|
||||
|
||||
virtual void onGlyphRangeLoaded() = 0;
|
||||
virtual void onGlyphRangeLoadingFailed(std::exception_ptr error) = 0;
|
||||
};
|
||||
|
||||
GlyphStore(uv_loop_t* loop, Environment &);
|
||||
@@ -110,6 +113,7 @@ public:
|
||||
|
||||
private:
|
||||
void emitGlyphRangeLoaded();
|
||||
void emitGlyphRangeLoadingFailed();
|
||||
|
||||
util::exclusive<FontStack> createFontStack(const std::string &fontStack);
|
||||
|
||||
@@ -122,7 +126,11 @@ private:
|
||||
std::unordered_map<std::string, std::unique_ptr<FontStack>> stacks;
|
||||
std::mutex stacksMutex;
|
||||
|
||||
std::string errorMessage;
|
||||
std::mutex errorMessageMutex;
|
||||
|
||||
std::unique_ptr<uv::async> asyncEmitGlyphRangeLoaded;
|
||||
std::unique_ptr<uv::async> asyncEmitGlyphRangeLoadedingFailed;
|
||||
|
||||
Observer* observer;
|
||||
};
|
||||
|
||||
@@ -28,7 +28,7 @@ TEST(API, RepeatedRender) {
|
||||
map.resize(128, 512, 1);
|
||||
map.setStyleJSON(style, "test/suite");
|
||||
std::promise<std::unique_ptr<const StillImage>> promise;
|
||||
map.renderStill([&promise](std::unique_ptr<const StillImage> image) {
|
||||
map.renderStill([&promise](std::exception_ptr, std::unique_ptr<const StillImage> image) {
|
||||
promise.set_value(std::move(image));
|
||||
});
|
||||
auto result = promise.get_future().get();
|
||||
@@ -42,7 +42,7 @@ TEST(API, RepeatedRender) {
|
||||
map.resize(512, 512, 2);
|
||||
map.setStyleJSON(style, "TEST_DATA/suite");
|
||||
std::promise<std::unique_ptr<const StillImage>> promise;
|
||||
map.renderStill([&promise](std::unique_ptr<const StillImage> image) {
|
||||
map.renderStill([&promise](std::exception_ptr, std::unique_ptr<const StillImage> image) {
|
||||
promise.set_value(std::move(image));
|
||||
});
|
||||
auto result = promise.get_future().get();
|
||||
|
||||
BIN
test/fixtures/resources/glyphs.pbf
vendored
Normal file
BIN
test/fixtures/resources/glyphs.pbf
vendored
Normal file
Binary file not shown.
1
test/fixtures/resources/source.json
vendored
Normal file
1
test/fixtures/resources/source.json
vendored
Normal file
File diff suppressed because one or more lines are too long
1
test/fixtures/resources/sprite.json
vendored
Normal file
1
test/fixtures/resources/sprite.json
vendored
Normal file
File diff suppressed because one or more lines are too long
BIN
test/fixtures/resources/sprite.png
vendored
Normal file
BIN
test/fixtures/resources/sprite.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 38 KiB |
31
test/fixtures/resources/style.json
vendored
Normal file
31
test/fixtures/resources/style.json
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"version": 1,
|
||||
"name": "Test",
|
||||
"sources": {
|
||||
"compositedsource": {
|
||||
"url": "test/fixtures/resources/source.json",
|
||||
"type": "vector"
|
||||
}
|
||||
},
|
||||
"sprite": "test/fixtures/resources/sprite",
|
||||
"glyphs": "test/fixtures/resources/glyphs.pbf",
|
||||
"layers": [{
|
||||
"id": "road",
|
||||
"type": "symbol",
|
||||
"source": "compositedsource",
|
||||
"source-layer": "road_label",
|
||||
"layout": {
|
||||
"text-font": "Open Sans Regular, Arial Unicode MS Regular",
|
||||
"text-field": "{name_en}"
|
||||
}
|
||||
}, {
|
||||
"id": "poi",
|
||||
"type": "symbol",
|
||||
"source": "compositedsource",
|
||||
"source-layer": "poi_label",
|
||||
"layout": {
|
||||
"text-font": "Open Sans Regular, Arial Unicode MS Regular",
|
||||
"icon-image": "{maki}_icon"
|
||||
}
|
||||
}]
|
||||
}
|
||||
BIN
test/fixtures/resources/vector.pbf
vendored
Normal file
BIN
test/fixtures/resources/vector.pbf
vendored
Normal file
Binary file not shown.
@@ -152,7 +152,7 @@ TEST_P(HeadlessTest, render) {
|
||||
map.setLatLngZoom(mbgl::LatLng(latitude, longitude), zoom);
|
||||
map.setBearing(bearing);
|
||||
|
||||
map.renderStill([&](std::unique_ptr<const StillImage> image) {
|
||||
map.renderStill([&](std::exception_ptr, std::unique_ptr<const StillImage> image) {
|
||||
const std::string png = util::compress_png(image->width, image->height, image->pixels.get());
|
||||
util::write_file("test/suite/tests/" + base + "/" + name + "/actual.png", png);
|
||||
promise.set_value();
|
||||
|
||||
47
test/resources/mock_file_source.cpp
Normal file
47
test/resources/mock_file_source.cpp
Normal file
@@ -0,0 +1,47 @@
|
||||
#include "mock_file_source.hpp"
|
||||
|
||||
#include <mbgl/storage/request.hpp>
|
||||
#include <mbgl/util/io.hpp>
|
||||
#include <mbgl/util/thread.hpp>
|
||||
|
||||
namespace mbgl {
|
||||
|
||||
class MockFileSource::Impl {
|
||||
public:
|
||||
Impl(uv_loop_t*, const std::string& matchFail) : matchFail_(matchFail) {}
|
||||
|
||||
void handleRequest(Request* req) const;
|
||||
|
||||
private:
|
||||
std::string matchFail_;
|
||||
};
|
||||
|
||||
void MockFileSource::Impl::handleRequest(Request* req) const {
|
||||
const Resource& resource = req->resource;
|
||||
|
||||
std::shared_ptr<Response> response = std::make_shared<Response>();
|
||||
if (matchFail_.empty() || resource.url.find(matchFail_) == std::string::npos) {
|
||||
response->status = Response::Status::Successful;
|
||||
response->data = util::read_file(resource.url.c_str());
|
||||
} else {
|
||||
response->message = "Failed by the test case";
|
||||
}
|
||||
|
||||
req->notify(response);
|
||||
}
|
||||
|
||||
MockFileSource::MockFileSource(const std::string& matchFail)
|
||||
: thread_(util::make_unique<util::Thread<Impl>>("FileSource", util::ThreadPriority::Low, matchFail)) {
|
||||
}
|
||||
|
||||
Request* MockFileSource::request(const Resource& resource, uv_loop_t* loop, Callback callback) {
|
||||
Request* req = new Request(resource, loop, std::move(callback));
|
||||
thread_->invoke(&Impl::handleRequest, req);
|
||||
|
||||
return req;
|
||||
}
|
||||
|
||||
void MockFileSource::cancel(Request*) {
|
||||
}
|
||||
|
||||
}
|
||||
33
test/resources/mock_file_source.hpp
Normal file
33
test/resources/mock_file_source.hpp
Normal file
@@ -0,0 +1,33 @@
|
||||
#ifndef TEST_RESOURCES_MOCK_FILE_SOURCE
|
||||
#define TEST_RESOURCES_MOCK_FILE_SOURCE
|
||||
|
||||
#include <mbgl/storage/file_source.hpp>
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace mbgl {
|
||||
|
||||
namespace util {
|
||||
template <typename T> class Thread;
|
||||
}
|
||||
|
||||
// This mock FileSource will read data from the disk and will fail
|
||||
// the request if the URL matches the 'matchFail' string.
|
||||
class MockFileSource : public FileSource {
|
||||
public:
|
||||
class Impl;
|
||||
|
||||
MockFileSource(const std::string& matchFail);
|
||||
~MockFileSource() override = default;
|
||||
|
||||
// FileSource implementation.
|
||||
Request* request(const Resource&, uv_loop_t*, Callback) override;
|
||||
void cancel(Request*) override;
|
||||
|
||||
private:
|
||||
const std::unique_ptr<util::Thread<Impl>> thread_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
21
test/resources/mock_view.hpp
Normal file
21
test/resources/mock_view.hpp
Normal file
@@ -0,0 +1,21 @@
|
||||
#ifndef TEST_RESOURCES_MOCK_VIEW
|
||||
#define TEST_RESOURCES_MOCK_VIEW
|
||||
|
||||
#include <mbgl/map/view.hpp>
|
||||
|
||||
namespace mbgl {
|
||||
|
||||
class MockView : public View {
|
||||
public:
|
||||
MockView() = default;
|
||||
|
||||
// View implementation.
|
||||
void activate() override {};
|
||||
void deactivate() override {};
|
||||
void notify() override {};
|
||||
void invalidate(std::function<void()>) override {};
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
154
test/resources/resource_loader.cpp
Normal file
154
test/resources/resource_loader.cpp
Normal file
@@ -0,0 +1,154 @@
|
||||
#include "../fixtures/util.hpp"
|
||||
#include "mock_file_source.hpp"
|
||||
#include "mock_view.hpp"
|
||||
|
||||
#include <mbgl/geometry/glyph_atlas.hpp>
|
||||
#include <mbgl/geometry/sprite_atlas.hpp>
|
||||
#include <mbgl/map/environment.hpp>
|
||||
#include <mbgl/map/map_data.hpp>
|
||||
#include <mbgl/map/resource_loader.hpp>
|
||||
#include <mbgl/map/transform_state.hpp>
|
||||
#include <mbgl/style/style.hpp>
|
||||
#include <mbgl/text/glyph_store.hpp>
|
||||
#include <mbgl/util/exception.hpp>
|
||||
#include <mbgl/util/io.hpp>
|
||||
#include <mbgl/util/run_loop.hpp>
|
||||
#include <mbgl/util/texture_pool.hpp>
|
||||
#include <mbgl/util/thread.hpp>
|
||||
|
||||
using namespace mbgl;
|
||||
|
||||
namespace {
|
||||
|
||||
class MockMapContext : public ResourceLoader::Observer {
|
||||
public:
|
||||
MockMapContext(uv_loop_t* loop,
|
||||
View& view,
|
||||
FileSource& fileSource,
|
||||
const std::function<void(std::exception_ptr error)>& callback)
|
||||
: env_(fileSource),
|
||||
envScope_(env_, ThreadType::Map, "Map"),
|
||||
data_(view, MapMode::Still),
|
||||
glyphStore_(util::make_unique<GlyphStore>(loop, env_)),
|
||||
glyphAtlas_(util::make_unique<GlyphAtlas>(1024, 1024)),
|
||||
spriteAtlas_(util::make_unique<SpriteAtlas>(512, 512)),
|
||||
texturePool_(util::make_unique<TexturePool>()),
|
||||
style_(util::make_unique<Style>()),
|
||||
resourceLoader_(util::make_unique<ResourceLoader>()),
|
||||
asyncUpdate(util::make_unique<uv::async>(loop, [this] { update(); })),
|
||||
callback_(callback) {
|
||||
asyncUpdate->unref();
|
||||
|
||||
data_.transform.resize(1000, 1000, 1.0, 1000, 1000);
|
||||
data_.transform.setLatLngZoom({0, 0}, 16);
|
||||
|
||||
const std::string style = util::read_file("test/fixtures/resources/style.json");
|
||||
style_->loadJSON(reinterpret_cast<const uint8_t *>(style.c_str()));
|
||||
|
||||
glyphStore_->setURL(style_->glyph_url);
|
||||
|
||||
resourceLoader_->setGlyphStore(glyphStore_.get());
|
||||
resourceLoader_->setObserver(this);
|
||||
resourceLoader_->setStyle(style_.get());
|
||||
}
|
||||
|
||||
~MockMapContext() {
|
||||
resourceLoader_.reset();
|
||||
style_.reset();
|
||||
texturePool_.reset();
|
||||
spriteAtlas_.reset();
|
||||
glyphAtlas_.reset();
|
||||
glyphStore_.reset();
|
||||
|
||||
env_.performCleanup();
|
||||
}
|
||||
|
||||
void update() {
|
||||
const auto now = Clock::now();
|
||||
|
||||
data_.setAnimationTime(now);
|
||||
data_.transform.updateTransitions(now);
|
||||
|
||||
transformState_ = data_.transform.currentState();
|
||||
|
||||
resourceLoader_->update(
|
||||
data_, transformState_, *glyphAtlas_, *spriteAtlas_, *texturePool_);
|
||||
}
|
||||
|
||||
// ResourceLoader::Observer implementation.
|
||||
void onTileDataChanged() override {
|
||||
util::ptr<Sprite> sprite = resourceLoader_->getSprite();
|
||||
if (sprite && sprite->isLoaded() && style_->isLoaded()) {
|
||||
callback_(nullptr);
|
||||
}
|
||||
|
||||
asyncUpdate->send();
|
||||
};
|
||||
|
||||
void onResourceLoadingFailed(std::exception_ptr error) override {
|
||||
callback_(error);
|
||||
}
|
||||
|
||||
private:
|
||||
Environment env_;
|
||||
EnvironmentScope envScope_;
|
||||
|
||||
MapData data_;
|
||||
TransformState transformState_;
|
||||
|
||||
std::unique_ptr<GlyphStore> glyphStore_;
|
||||
std::unique_ptr<GlyphAtlas> glyphAtlas_;
|
||||
std::unique_ptr<SpriteAtlas> spriteAtlas_;
|
||||
std::unique_ptr<TexturePool> texturePool_;
|
||||
std::unique_ptr<Style> style_;
|
||||
std::unique_ptr<ResourceLoader> resourceLoader_;
|
||||
|
||||
std::unique_ptr<uv::async> asyncUpdate;
|
||||
|
||||
std::function<void(std::exception_ptr error)> callback_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
class ResourceLoaderTest : public ::testing::TestWithParam<std::string> {
|
||||
};
|
||||
|
||||
TEST_P(ResourceLoaderTest, RequestFailure) {
|
||||
util::RunLoop loop(uv_default_loop());
|
||||
|
||||
const std::string param(GetParam());
|
||||
|
||||
MockView view;
|
||||
MockFileSource fileSource(param);
|
||||
|
||||
auto callback = [&loop, ¶m](std::exception_ptr error) {
|
||||
try {
|
||||
if (error) {
|
||||
std::rethrow_exception(error);
|
||||
} else {
|
||||
// Control test, should succeed.
|
||||
EXPECT_TRUE(param.empty());
|
||||
}
|
||||
} catch (const util::GlyphRangeLoadingException&) {
|
||||
EXPECT_EQ(param, "glyphs.pbf");
|
||||
} catch (const util::SourceLoadingException&) {
|
||||
EXPECT_EQ(param, "source.json");
|
||||
} catch (const util::SpriteLoadingException&) {
|
||||
EXPECT_TRUE(param == "sprite.png" || param == "sprite.json");
|
||||
} catch (const util::TileLoadingException&) {
|
||||
EXPECT_EQ(param, "vector.pbf");
|
||||
} catch (const std::exception&) {
|
||||
EXPECT_TRUE(false) << "Unhandled exception.";
|
||||
}
|
||||
|
||||
loop.stop();
|
||||
};
|
||||
|
||||
util::Thread<MockMapContext> context(
|
||||
"Map", util::ThreadPriority::Regular, view, fileSource, callback);
|
||||
|
||||
uv_run(loop.get(), UV_RUN_DEFAULT);
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(ResourceLoader, ResourceLoaderTest,
|
||||
::testing::Values("", "source.json", "sprite.json", "sprite.png", "vector.pbf", "glyphs.pbf"));
|
||||
@@ -59,6 +59,11 @@
|
||||
'miscellaneous/variant.cpp',
|
||||
'miscellaneous/worker.cpp',
|
||||
|
||||
'resources/mock_file_source.cpp',
|
||||
'resources/mock_file_source.hpp',
|
||||
'resources/mock_view.hpp',
|
||||
'resources/resource_loader.cpp',
|
||||
|
||||
'storage/storage.hpp',
|
||||
'storage/storage.cpp',
|
||||
'storage/cache_response.cpp',
|
||||
|
||||
Reference in New Issue
Block a user