From e2dc956eb63b8bd2582d2e666b412bf00ba92623 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Wed, 28 Oct 2015 14:51:16 -0700 Subject: [PATCH] [core] Merge LiveTileData into VectorTileData To encapsulate the differences, this introduces an abstract GeometryTileMonitor class, with concrete VectorTileMonitor and AnnotationTileMonitor implementations, and makes pbf parsing for VectorTile data happen lazily in getLayer, since we want that to happen on the worker thread, rather than when VectorTile is created. Fixes #2792 --- src/mbgl/annotation/annotation_tile.cpp | 10 ++ src/mbgl/annotation/annotation_tile.hpp | 12 ++ src/mbgl/map/geometry_tile.hpp | 16 +++ src/mbgl/map/live_tile_data.cpp | 133 ---------------------- src/mbgl/map/live_tile_data.hpp | 51 --------- src/mbgl/map/source.cpp | 30 +++-- src/mbgl/map/vector_tile.cpp | 61 ++++++++-- src/mbgl/map/vector_tile.hpp | 20 +++- src/mbgl/map/vector_tile_data.cpp | 141 +++++++++++------------- src/mbgl/map/vector_tile_data.hpp | 23 ++-- src/mbgl/util/worker.cpp | 69 ++++-------- src/mbgl/util/worker.hpp | 19 ++-- 12 files changed, 233 insertions(+), 352 deletions(-) delete mode 100644 src/mbgl/map/live_tile_data.cpp delete mode 100644 src/mbgl/map/live_tile_data.hpp diff --git a/src/mbgl/annotation/annotation_tile.cpp b/src/mbgl/annotation/annotation_tile.cpp index 71a6870cd..81f7662b4 100644 --- a/src/mbgl/annotation/annotation_tile.cpp +++ b/src/mbgl/annotation/annotation_tile.cpp @@ -1,5 +1,6 @@ #include #include +#include namespace mbgl { @@ -25,4 +26,13 @@ util::ptr AnnotationTile::getLayer(const std::string& name) c return nullptr; } +AnnotationTileMonitor::AnnotationTileMonitor(const TileID& id, MapData& data) + : tile(data.getAnnotationManager()->getTile(id)) { +} + +Request* AnnotationTileMonitor::monitorTile(std::function)> callback) { + callback(nullptr, std::move(tile)); + return nullptr; +} + } diff --git a/src/mbgl/annotation/annotation_tile.hpp b/src/mbgl/annotation/annotation_tile.hpp index bb7ec5d04..52955da33 100644 --- a/src/mbgl/annotation/annotation_tile.hpp +++ b/src/mbgl/annotation/annotation_tile.hpp @@ -38,6 +38,18 @@ public: std::map> layers; }; +class MapData; + +class AnnotationTileMonitor : public GeometryTileMonitor { +public: + AnnotationTileMonitor(const TileID&, MapData&); + + Request* monitorTile(std::function)>) override; + +private: + std::unique_ptr tile; +}; + } #endif diff --git a/src/mbgl/map/geometry_tile.hpp b/src/mbgl/map/geometry_tile.hpp index 37d7bf142..242476da6 100644 --- a/src/mbgl/map/geometry_tile.hpp +++ b/src/mbgl/map/geometry_tile.hpp @@ -12,6 +12,7 @@ #include #include #include +#include namespace mbgl { @@ -42,6 +43,21 @@ public: virtual util::ptr getLayer(const std::string&) const = 0; }; +class Request; + +class GeometryTileMonitor : private util::noncopyable { +public: + /* + * Monitor the tile held by this object for changes. When the tile is loaded for the first time, + * or updates, the callback is executed. If an error occurs, the first parameter will be set. + * Otherwise it will be null. If there is no data for the requested tile, the second parameter + * will be null. + * + * To cease monitoring, release the returned Request. + */ + virtual Request* monitorTile(std::function)>) = 0; +}; + class GeometryTileFeatureExtractor { public: GeometryTileFeatureExtractor(const GeometryTileFeature& feature_) diff --git a/src/mbgl/map/live_tile_data.cpp b/src/mbgl/map/live_tile_data.cpp deleted file mode 100644 index 89b1d6fda..000000000 --- a/src/mbgl/map/live_tile_data.cpp +++ /dev/null @@ -1,133 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -using namespace mbgl; - -LiveTileData::LiveTileData(const TileID& id_, - std::unique_ptr tile_, - Style& style, - const SourceInfo& source, - std::function callback) - : TileData(id_), - worker(style.workers), - tileWorker(id, source.source_id, style, style.layers, state), - tile(std::move(tile_)) { - state = State::loaded; - - if (!tile) { - state = State::parsed; - return; - } - - parsePending(callback); -} - -bool LiveTileData::parsePending(std::function callback) { - if (workRequest || (state != State::loaded && state != State::partial)) { - return false; - } - - workRequest.reset(); - workRequest = worker.parseLiveTile(tileWorker, *tile, targetConfig, [this, callback, config = targetConfig] (TileParseResult result) { - workRequest.reset(); - - if (result.is()) { - auto& resultBuckets = result.get(); - state = resultBuckets.state; - - // Persist the configuration we just placed so that we can later check whether we need - // to place again in case the configuration has changed. - placedConfig = config; - - // Move over all buckets we received in this parse request, potentially overwriting - // existing buckets in case we got a refresh parse. - for (auto& bucket : resultBuckets.buckets) { - buckets[bucket.first] = std::move(bucket.second); - } - - // The target configuration could have changed since we started placement. In this case, - // we're starting another placement run. - if (placedConfig != targetConfig) { - redoPlacement(); - } - } else { - error = result.get(); - state = State::obsolete; - } - - callback(); - - // The target configuration could have changed since we started placement. In this case, - // we're starting another placement run. - if (!workRequest && placedConfig != targetConfig) { - redoPlacement(); - } - }); - - return true; -} - -LiveTileData::~LiveTileData() { - cancel(); -} - -Bucket* LiveTileData::getBucket(const StyleLayer& layer) { - if (!isReady() || !layer.bucket) { - return nullptr; - } - - const auto it = buckets.find(layer.bucket->name); - if (it == buckets.end()) { - return nullptr; - } - - assert(it->second); - return it->second.get(); -} - -void LiveTileData::cancel() { - state = State::obsolete; - workRequest.reset(); -} - -void LiveTileData::redoPlacement(const PlacementConfig newConfig) { - if (newConfig != placedConfig) { - targetConfig = newConfig; - - if (!workRequest) { - // Don't start a new placement request when the current one hasn't completed yet, or - // when we are parsing buckets. - redoPlacement(); - } - } -} - -void LiveTileData::redoPlacement() { - workRequest.reset(); - workRequest = worker.redoPlacement(tileWorker, buckets, targetConfig, [this, config = targetConfig] { - workRequest.reset(); - - // Persist the configuration we just placed so that we can later check whether we need to - // place again in case the configuration has changed. - placedConfig = config; - - for (auto& bucket : buckets) { - bucket.second->swapRenderData(); - } - - // The target configuration could have changed since we started placement. In this case, - // we're starting another placement run. - if (placedConfig != targetConfig) { - redoPlacement(); - } - }); -} diff --git a/src/mbgl/map/live_tile_data.hpp b/src/mbgl/map/live_tile_data.hpp deleted file mode 100644 index 958f1db50..000000000 --- a/src/mbgl/map/live_tile_data.hpp +++ /dev/null @@ -1,51 +0,0 @@ -#ifndef MBGL_MAP_LIVE_TILE_DATA -#define MBGL_MAP_LIVE_TILE_DATA - -#include -#include - -namespace mbgl { - -class Style; -class SourceInfo; -class WorkRequest; -class AnnotationTile; - -class LiveTileData : public TileData { -public: - LiveTileData(const TileID&, - std::unique_ptr, - Style&, - const SourceInfo&, - std::function callback); - ~LiveTileData(); - - bool parsePending(std::function callback) override; - - void redoPlacement(PlacementConfig config) override; - void redoPlacement(); - - void cancel() override; - Bucket* getBucket(const StyleLayer&) override; - -private: - Worker& worker; - TileWorker tileWorker; - std::unique_ptr workRequest; - std::unique_ptr tile; - - // Contains all the Bucket objects for the tile. Buckets are render - // objects and they get added by tile parsing operations. - std::unordered_map> buckets; - - // Stores the placement configuration of the text that is currently placed on the screen. - PlacementConfig placedConfig; - - // Stores the placement configuration of how the text should be placed. This isn't necessarily - // the one that is being displayed. - PlacementConfig targetConfig; -}; - -} - -#endif diff --git a/src/mbgl/map/source.cpp b/src/mbgl/map/source.cpp index 7af58788e..3cd73734f 100644 --- a/src/mbgl/map/source.cpp +++ b/src/mbgl/map/source.cpp @@ -2,6 +2,8 @@ #include #include #include +#include +#include #include #include #include @@ -22,8 +24,6 @@ #include #include -#include -#include #include #include @@ -285,20 +285,28 @@ TileData::State Source::addTile(MapData& data, auto callback = std::bind(&Source::tileLoadingCompleteCallback, this, normalized_id, transformState, data.getCollisionDebug()); // If we don't find working tile data, we're just going to load it. - if (info.type == SourceType::Vector) { - auto tileData = std::make_shared(normalized_id, style, info); - tileData->request(data.pixelRatio, callback); - new_tile.data = tileData; - } else if (info.type == SourceType::Raster) { + if (info.type == SourceType::Raster) { auto tileData = std::make_shared(normalized_id, texturePool, info, style.workers); tileData->request(data.pixelRatio, callback); new_tile.data = tileData; - } else if (info.type == SourceType::Annotations) { - new_tile.data = std::make_shared(normalized_id, - data.getAnnotationManager()->getTile(normalized_id), style, info, callback); } else { - throw std::runtime_error("source type not implemented"); + std::unique_ptr monitor; + + if (info.type == SourceType::Vector) { + monitor = std::make_unique(info, normalized_id, data.pixelRatio); + } else if (info.type == SourceType::Annotations) { + monitor = std::make_unique(normalized_id, data); + } else { + throw std::runtime_error("source type not implemented"); + } + + new_tile.data = std::make_shared(normalized_id, + std::move(monitor), + info.source_id, + style, + callback); } + tile_data.emplace(new_tile.data->id, new_tile.data); } diff --git a/src/mbgl/map/vector_tile.cpp b/src/mbgl/map/vector_tile.cpp index 01d52a348..7e301c91d 100644 --- a/src/mbgl/map/vector_tile.cpp +++ b/src/mbgl/map/vector_tile.cpp @@ -1,4 +1,12 @@ #include +#include +#include +#include +#include +#include +#include + +#include namespace mbgl { @@ -122,22 +130,29 @@ GeometryCollection VectorTileFeature::getGeometries() const { return lines; } -VectorTile::VectorTile(pbf tile_pbf) { - while (tile_pbf.next()) { - if (tile_pbf.tag == 3) { // layer - util::ptr layer = std::make_shared(tile_pbf.message()); - layers.emplace(layer->name, layer); - } else { - tile_pbf.skip(); - } - } +VectorTile::VectorTile(std::shared_ptr data_) + : data(data_) { } util::ptr VectorTile::getLayer(const std::string& name) const { + if (!parsed) { + parsed = true; + pbf tile_pbf(reinterpret_cast(data->c_str()), data->size()); + while (tile_pbf.next()) { + if (tile_pbf.tag == 3) { // layer + util::ptr layer = std::make_shared(tile_pbf.message()); + layers.emplace(layer->name, layer); + } else { + tile_pbf.skip(); + } + } + } + auto layer_it = layers.find(name); if (layer_it != layers.end()) { return layer_it->second; } + return nullptr; } @@ -163,4 +178,32 @@ util::ptr VectorTileLayer::getFeature(std::size_t i) return std::make_shared(features.at(i), *this); } +VectorTileMonitor::VectorTileMonitor(const SourceInfo& source, const TileID& id, float pixelRatio) + : url(source.tileURL(id, pixelRatio)) { +} + +Request* VectorTileMonitor::monitorTile(std::function)> callback) { + return util::ThreadContext::getFileSource()->request({ Resource::Kind::Tile, url }, util::RunLoop::getLoop(), [callback, this](const Response& res) { + if (res.data && data == res.data) { + // We got the same data again. Abort early. + return; + } + + if (res.status == Response::NotFound) { + callback(nullptr, nullptr); + return; + } + + if (res.status != Response::Successful) { + std::stringstream message; + message << "Failed to load [" << url << "]: " << res.message; + callback(std::make_exception_ptr(std::runtime_error(message.str())), nullptr); + return; + } + + data = res.data; + callback(nullptr, std::make_unique(data)); + }); +} + } diff --git a/src/mbgl/map/vector_tile.hpp b/src/mbgl/map/vector_tile.hpp index 457f01c7b..68ed6c262 100644 --- a/src/mbgl/map/vector_tile.hpp +++ b/src/mbgl/map/vector_tile.hpp @@ -46,12 +46,28 @@ private: class VectorTile : public GeometryTile { public: - VectorTile(pbf); + VectorTile(std::shared_ptr data); util::ptr getLayer(const std::string&) const override; private: - std::map> layers; + std::shared_ptr data; + mutable bool parsed = false; + mutable std::map> layers; +}; + +class SourceInfo; +class TileID; + +class VectorTileMonitor : public GeometryTileMonitor { +public: + VectorTileMonitor(const SourceInfo&, const TileID&, float pixelRatio); + + Request* monitorTile(std::function)>) override; + +private: + std::string url; + std::shared_ptr data; }; } diff --git a/src/mbgl/map/vector_tile_data.cpp b/src/mbgl/map/vector_tile_data.cpp index 3c831250c..0241b82b9 100644 --- a/src/mbgl/map/vector_tile_data.cpp +++ b/src/mbgl/map/vector_tile_data.cpp @@ -1,112 +1,97 @@ #include +#include #include #include -#include -#include -#include -#include -#include #include #include #include -using namespace mbgl; +#include + +namespace mbgl { VectorTileData::VectorTileData(const TileID& id_, + std::unique_ptr monitor_, + std::string sourceID, Style& style_, - const SourceInfo& source_) + const std::function& callback) : TileData(id_), worker(style_.workers), tileWorker(id_, - source_.source_id, + sourceID, style_, style_.layers, state), - source(source_) { -} - -VectorTileData::~VectorTileData() { - cancel(); -} - -void VectorTileData::request(float pixelRatio, const std::function& callback) { - std::string url = source.tileURL(id, pixelRatio); + monitor(std::move(monitor_)) +{ state = State::loading; - - FileSource* fs = util::ThreadContext::getFileSource(); - req = fs->request({ Resource::Kind::Tile, url }, util::RunLoop::getLoop(), [url, callback, this](const Response &res) { - // Do not cancel the request here; we want to get notified about tiles that expire. - - if (res.data && data == res.data) { - // We got the same data again. Abort early. - return; + req = monitor->monitorTile([callback, sourceID, this](std::exception_ptr err, std::unique_ptr tile) { + if (err) { + try { + std::rethrow_exception(err); + } catch (const std::exception& e) { + error = e.what(); + state = State::obsolete; + callback(); + return; + } } - if (res.status == Response::NotFound) { + if (!tile) { state = State::parsed; callback(); return; } - if (res.status != Response::Successful) { - std::stringstream message; - message << "Failed to load [" << url << "]: " << res.message; - error = message.str(); - state = State::obsolete; - callback(); - return; - } - if (state == State::loading) { state = State::loaded; } else if (isReady()) { state = State::partial; } - data = res.data; - parse(callback); + // Kick off a fresh parse of this tile. This happens when the tile is new, or + // when tile data changed. Replacing the workdRequest will cancel a pending work + // request in case there is one. + workRequest.reset(); + workRequest = worker.parseGeometryTile(tileWorker, std::move(tile), targetConfig, [callback, sourceID, this, config = targetConfig] (TileParseResult result) { + workRequest.reset(); + if (state == State::obsolete) { + return; + } + + if (result.is()) { + auto& resultBuckets = result.get(); + state = resultBuckets.state; + + // Persist the configuration we just placed so that we can later check whether we need to + // place again in case the configuration has changed. + placedConfig = config; + + // Move over all buckets we received in this parse request, potentially overwriting + // existing buckets in case we got a refresh parse. + for (auto& bucket : resultBuckets.buckets) { + buckets[bucket.first] = std::move(bucket.second); + } + + // The target configuration could have changed since we started placement. In this case, + // we're starting another placement run. + if (placedConfig != targetConfig) { + redoPlacement(); + } + } else { + std::stringstream message; + message << "Failed to parse [" << std::string(id) << "]: " << result.get(); + error = message.str(); + state = State::obsolete; + } + + callback(); + }); }); } -void VectorTileData::parse(std::function callback) { - // Kick off a fresh parse of this tile. This happens when the tile is new, or - // when tile data changed. Replacing the workdRequest will cancel a pending work - // request in case there is one. - workRequest.reset(); - workRequest = worker.parseVectorTile(tileWorker, data, targetConfig, [this, callback, config = targetConfig] (TileParseResult result) { - workRequest.reset(); - if (state == State::obsolete) { - return; - } - - if (result.is()) { - auto& resultBuckets = result.get(); - state = resultBuckets.state; - - // Persist the configuration we just placed so that we can later check whether we need to - // place again in case the configuration has changed. - placedConfig = config; - - // Move over all buckets we received in this parse request, potentially overwriting - // existing buckets in case we got a refresh parse. - for (auto& bucket : resultBuckets.buckets) { - buckets[bucket.first] = std::move(bucket.second); - } - - // The target configuration could have changed since we started placement. In this case, - // we're starting another placement run. - if (placedConfig != targetConfig) { - redoPlacement(); - } - } else { - std::stringstream message; - message << "Failed to parse [" << std::string(id) << "]: " << result.get(); - error = message.str(); - state = State::obsolete; - } - - callback(); - }); +VectorTileData::~VectorTileData() { + cancel(); } bool VectorTileData::parsePending(std::function callback) { @@ -116,7 +101,7 @@ bool VectorTileData::parsePending(std::function callback) { } workRequest.reset(); - workRequest = worker.parsePendingVectorTileLayers(tileWorker, [this, callback] (TileParseResult result) { + workRequest = worker.parsePendingGeometryTileLayers(tileWorker, [this, callback] (TileParseResult result) { workRequest.reset(); if (state == State::obsolete) { return; @@ -204,3 +189,5 @@ void VectorTileData::cancel() { req = nullptr; workRequest.reset(); } + +} diff --git a/src/mbgl/map/vector_tile_data.hpp b/src/mbgl/map/vector_tile_data.hpp index 9faffab83..1a082fc61 100644 --- a/src/mbgl/map/vector_tile_data.hpp +++ b/src/mbgl/map/vector_tile_data.hpp @@ -7,25 +7,27 @@ #include #include +#include +#include namespace mbgl { class Style; -class SourceInfo; class WorkRequest; -class Request; +class GeometryTileMonitor; class VectorTileData : public TileData { public: - VectorTileData( - const TileID&, Style&, const SourceInfo&); + VectorTileData(const TileID&, + std::unique_ptr monitor, + std::string sourceID, + Style&, + const std::function& callback); + ~VectorTileData(); Bucket* getBucket(const StyleLayer&) override; - void request(float pixelRatio, const std::function& callback); - - void parse(std::function callback); bool parsePending(std::function callback) override; void redoPlacement(PlacementConfig config) override; @@ -36,16 +38,15 @@ public: private: Worker& worker; TileWorker tileWorker; + + std::unique_ptr monitor; std::unique_ptr workRequest; + RequestHolder req; // Contains all the Bucket objects for the tile. Buckets are render // objects and they get added by tile parsing operations. std::unordered_map> buckets; - const SourceInfo& source; - RequestHolder req; - std::shared_ptr data; - // Stores the placement configuration of the text that is currently placed on the screen. PlacementConfig placedConfig; diff --git a/src/mbgl/util/worker.cpp b/src/mbgl/util/worker.cpp index 4dd6740ec..6d4336709 100644 --- a/src/mbgl/util/worker.cpp +++ b/src/mbgl/util/worker.cpp @@ -2,10 +2,8 @@ #include #include #include -#include -#include -#include #include +#include #include #include @@ -35,31 +33,10 @@ public: callback(std::move(result)); } - void parseVectorTile(TileWorker* worker, - const std::shared_ptr data, - PlacementConfig config, - std::function callback) { - try { - pbf tilePBF(reinterpret_cast(data->data()), data->size()); - callback(worker->parseAllLayers(VectorTile(tilePBF), config)); - } catch (const std::exception& ex) { - callback(TileParseResult(ex.what())); - } - } - - void parsePendingVectorTileLayers(TileWorker* worker, - std::function callback) { - try { - callback(worker->parsePendingLayers()); - } catch (const std::exception& ex) { - callback(TileParseResult(ex.what())); - } - } - - void parseLiveTile(TileWorker* worker, - const AnnotationTile* tile, - PlacementConfig config, - std::function callback) { + void parseGeometryTile(TileWorker* worker, + std::unique_ptr tile, + PlacementConfig config, + std::function callback) { try { callback(worker->parseAllLayers(*tile, config)); } catch (const std::exception& ex) { @@ -67,6 +44,15 @@ public: } } + void parsePendingGeometryTileLayers(TileWorker* worker, + std::function callback) { + try { + callback(worker->parsePendingLayers()); + } catch (const std::exception& ex) { + callback(TileParseResult(ex.what())); + } + } + void redoPlacement(TileWorker* worker, const std::unordered_map>* buckets, PlacementConfig config, @@ -95,32 +81,23 @@ Worker::parseRasterTile(std::unique_ptr bucket, } std::unique_ptr -Worker::parseVectorTile(TileWorker& worker, - const std::shared_ptr data, - PlacementConfig config, - std::function callback) { +Worker::parseGeometryTile(TileWorker& worker, + std::unique_ptr tile, + PlacementConfig config, + std::function callback) { current = (current + 1) % threads.size(); - return threads[current]->invokeWithCallback(&Worker::Impl::parseVectorTile, callback, &worker, - data, config); + return threads[current]->invokeWithCallback(&Worker::Impl::parseGeometryTile, callback, &worker, + std::move(tile), config); } std::unique_ptr -Worker::parsePendingVectorTileLayers(TileWorker& worker, - std::function callback) { +Worker::parsePendingGeometryTileLayers(TileWorker& worker, + std::function callback) { current = (current + 1) % threads.size(); - return threads[current]->invokeWithCallback(&Worker::Impl::parsePendingVectorTileLayers, + return threads[current]->invokeWithCallback(&Worker::Impl::parsePendingGeometryTileLayers, callback, &worker); } -std::unique_ptr Worker::parseLiveTile(TileWorker& worker, - const AnnotationTile& tile, - PlacementConfig config, - std::function callback) { - current = (current + 1) % threads.size(); - return threads[current]->invokeWithCallback(&Worker::Impl::parseLiveTile, callback, &worker, - &tile, config); -} - std::unique_ptr Worker::redoPlacement(TileWorker& worker, const std::unordered_map>& buckets, diff --git a/src/mbgl/util/worker.hpp b/src/mbgl/util/worker.hpp index 3d2665e71..6bf16216d 100644 --- a/src/mbgl/util/worker.hpp +++ b/src/mbgl/util/worker.hpp @@ -12,7 +12,7 @@ namespace mbgl { class WorkRequest; class RasterBucket; -class AnnotationTile; +class GeometryTileLoader; class Worker : public mbgl::util::noncopyable { public: @@ -35,18 +35,13 @@ public: std::shared_ptr data, std::function callback); - Request parseVectorTile(TileWorker&, - std::shared_ptr data, - PlacementConfig config, - std::function callback); + Request parseGeometryTile(TileWorker&, + std::unique_ptr, + PlacementConfig, + std::function callback); - Request parsePendingVectorTileLayers(TileWorker&, - std::function callback); - - Request parseLiveTile(TileWorker&, - const AnnotationTile&, - PlacementConfig config, - std::function callback); + Request parsePendingGeometryTileLayers(TileWorker&, + std::function callback); Request redoPlacement(TileWorker&, const std::unordered_map>&,