Merge pull request #1588 from mapbox/1565-failed_requests

Notify renderStill() of request failures
This commit is contained in:
Thiago Marcos P. Santos
2015-05-22 10:55:46 +03:00
29 changed files with 515 additions and 50 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

Binary file not shown.

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

File diff suppressed because one or more lines are too long

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

Binary file not shown.

View File

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

View 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*) {
}
}

View 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

View 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

View 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, &param](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"));

View File

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