diff options
author | John Firebaugh <john.firebaugh@gmail.com> | 2016-09-06 15:01:34 -0700 |
---|---|---|
committer | John Firebaugh <john.firebaugh@gmail.com> | 2016-09-16 12:01:06 -0700 |
commit | 41bbd4e4f7d66465433e370ca024ab0239fcace3 (patch) | |
tree | 8fe15fa31d97aafeb175a808e431b437297af88b /src/mbgl/tile | |
parent | 0bd66d40ddf9e75f860fe18e7c80de9c840f48ac (diff) | |
download | qtlocation-mapboxgl-41bbd4e4f7d66465433e370ca024ab0239fcace3.tar.gz |
[core] Use an actor model for tile worker concurrency
Diffstat (limited to 'src/mbgl/tile')
-rw-r--r-- | src/mbgl/tile/geojson_tile.cpp | 3 | ||||
-rw-r--r-- | src/mbgl/tile/geometry_tile.cpp | 222 | ||||
-rw-r--r-- | src/mbgl/tile/geometry_tile.hpp | 63 | ||||
-rw-r--r-- | src/mbgl/tile/geometry_tile_worker.cpp | 300 | ||||
-rw-r--r-- | src/mbgl/tile/geometry_tile_worker.hpp | 76 | ||||
-rw-r--r-- | src/mbgl/tile/raster_tile.cpp | 49 | ||||
-rw-r--r-- | src/mbgl/tile/raster_tile.hpp | 12 | ||||
-rw-r--r-- | src/mbgl/tile/raster_tile_worker.cpp | 27 | ||||
-rw-r--r-- | src/mbgl/tile/raster_tile_worker.hpp | 22 | ||||
-rw-r--r-- | src/mbgl/tile/tile.cpp | 2 | ||||
-rw-r--r-- | src/mbgl/tile/tile.hpp | 6 | ||||
-rw-r--r-- | src/mbgl/tile/tile_observer.hpp | 8 | ||||
-rw-r--r-- | src/mbgl/tile/tile_worker.cpp | 179 | ||||
-rw-r--r-- | src/mbgl/tile/tile_worker.hpp | 91 | ||||
-rw-r--r-- | src/mbgl/tile/vector_tile.cpp | 3 |
15 files changed, 551 insertions, 512 deletions
diff --git a/src/mbgl/tile/geojson_tile.cpp b/src/mbgl/tile/geojson_tile.cpp index 5dc099de69..2b302e14ec 100644 --- a/src/mbgl/tile/geojson_tile.cpp +++ b/src/mbgl/tile/geojson_tile.cpp @@ -1,6 +1,5 @@ #include <mbgl/tile/geojson_tile.hpp> #include <mbgl/tile/geometry_tile_data.hpp> -#include <mbgl/style/update_parameters.hpp> #include <mapbox/geojsonvt.hpp> #include <supercluster.hpp> @@ -80,7 +79,7 @@ GeoJSONTile::GeoJSONTile(const OverscaledTileID& overscaledTileID, std::string sourceID_, const style::UpdateParameters& parameters, const mapbox::geometry::feature_collection<int16_t>& features) - : GeometryTile(overscaledTileID, sourceID_, parameters.style, parameters.mode) { + : GeometryTile(overscaledTileID, sourceID_, parameters) { setData(std::make_unique<GeoJSONTileData>(features)); } diff --git a/src/mbgl/tile/geometry_tile.cpp b/src/mbgl/tile/geometry_tile.cpp index c432869fa5..ed9d4a551b 100644 --- a/src/mbgl/tile/geometry_tile.cpp +++ b/src/mbgl/tile/geometry_tile.cpp @@ -1,16 +1,17 @@ #include <mbgl/tile/geometry_tile.hpp> -#include <mbgl/tile/tile_observer.hpp> +#include <mbgl/tile/geometry_tile_worker.hpp> #include <mbgl/tile/geometry_tile_data.hpp> +#include <mbgl/tile/tile_observer.hpp> +#include <mbgl/style/update_parameters.hpp> #include <mbgl/style/layer_impl.hpp> #include <mbgl/style/layers/background_layer.hpp> #include <mbgl/style/layers/custom_layer.hpp> -#include <mbgl/util/worker.hpp> -#include <mbgl/util/work_request.hpp> #include <mbgl/style/style.hpp> #include <mbgl/storage/file_source.hpp> #include <mbgl/geometry/feature_index.hpp> #include <mbgl/text/collision_tile.hpp> #include <mbgl/map/transform_state.hpp> +#include <mbgl/util/run_loop.hpp> namespace mbgl { @@ -18,75 +19,53 @@ using namespace style; GeometryTile::GeometryTile(const OverscaledTileID& id_, std::string sourceID_, - Style& style_, - const MapMode mode_) + const style::UpdateParameters& parameters) : Tile(id_), sourceID(std::move(sourceID_)), - style(style_), - worker(style_.workers), - tileWorker(id_, - *style_.spriteStore, - *style_.glyphAtlas, - *style_.glyphStore, - obsolete, - mode_) { + style(parameters.style), + mailbox(std::make_shared<Mailbox>(*util::RunLoop::Get())), + worker(parameters.workerScheduler, + ActorRef<GeometryTile>(*this, mailbox), + id_, + *parameters.style.spriteStore, + *parameters.style.glyphAtlas, + *parameters.style.glyphStore, + obsolete, + parameters.mode) { } GeometryTile::~GeometryTile() { cancel(); } -void GeometryTile::setError(std::exception_ptr err) { - observer->onTileError(*this, err); +void GeometryTile::cancel() { + obsolete = true; } -std::vector<std::unique_ptr<Layer>> GeometryTile::cloneStyleLayers() const { - std::vector<std::unique_ptr<Layer>> result; - - for (const Layer* layer : style.getLayers()) { - // Avoid cloning and including irrelevant layers. - if (layer->is<BackgroundLayer>() || - layer->is<CustomLayer>() || - layer->baseImpl->source != sourceID || - id.overscaledZ < std::floor(layer->baseImpl->minZoom) || - id.overscaledZ >= std::ceil(layer->baseImpl->maxZoom) || - layer->baseImpl->visibility == VisibilityType::None) { - continue; - } - - result.push_back(layer->baseImpl->clone()); - } - - return result; +void GeometryTile::setError(std::exception_ptr err) { + availableData = DataAvailability::All; + observer->onTileError(*this, err); } void GeometryTile::setData(std::unique_ptr<const GeometryTileData> data_) { - if (!data_) { - // This is a 404 response. We're treating these as empty tiles. - workRequest.reset(); - availableData = DataAvailability::All; - buckets.clear(); - featureIndex.reset(); - data.reset(); - redoPlacement(); - observer->onTileLoaded(*this, TileLoadState::First); - return; - } - // Mark the tile as pending again if it was complete before to prevent signaling a complete // state despite pending parse operations. if (availableData == DataAvailability::All) { availableData = DataAvailability::Some; } - // 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, cloneStyleLayers(), std::move(data_), targetConfig, - [this, config = targetConfig] (TileParseResult result) { - tileLoaded(std::move(result), config); - }); + ++correlationID; + worker.invoke(&GeometryTileWorker::setData, std::move(data_), correlationID); + redoLayout(); +} + +void GeometryTile::setPlacementConfig(const PlacementConfig& desiredConfig) { + if (placedConfig == desiredConfig) { + return; + } + + ++correlationID; + worker.invoke(&GeometryTileWorker::setPlacementConfig, desiredConfig, correlationID); } void GeometryTile::redoLayout() { @@ -96,79 +75,44 @@ void GeometryTile::redoLayout() { availableData = DataAvailability::Some; } - workRequest.reset(); - workRequest = worker.redoLayout(tileWorker, cloneStyleLayers(), targetConfig, - [this, config = targetConfig] (TileParseResult result) { - tileLoaded(std::move(result), config); - }); -} - -void GeometryTile::tileLoaded(TileParseResult result, PlacementConfig config) { - workRequest.reset(); + std::vector<std::unique_ptr<Layer>> copy; - if (result.is<TileParseResultData>()) { - auto& resultBuckets = result.get<TileParseResultData>(); - availableData = resultBuckets.complete ? DataAvailability::All : DataAvailability::Some; + for (const Layer* layer : style.getLayers()) { + // Avoid cloning and including irrelevant layers. + if (layer->is<BackgroundLayer>() || + layer->is<CustomLayer>() || + layer->baseImpl->source != sourceID || + id.overscaledZ < std::floor(layer->baseImpl->minZoom) || + id.overscaledZ >= std::ceil(layer->baseImpl->maxZoom) || + layer->baseImpl->visibility == VisibilityType::None) { + continue; + } - // 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; + copy.push_back(layer->baseImpl->clone()); + } - // Move over all buckets we received in this parse request, potentially overwriting - // existing buckets in case we got a refresh parse. - buckets = std::move(resultBuckets.buckets); + ++correlationID; + worker.invoke(&GeometryTileWorker::setLayers, std::move(copy), correlationID); +} - if (isComplete()) { - featureIndex = std::move(resultBuckets.featureIndex); - data = std::move(resultBuckets.tileData); - } +void GeometryTile::onLayout(LayoutResult result) { + availableData = DataAvailability::Some; + buckets = std::move(result.buckets); + featureIndex = std::move(result.featureIndex); + data = std::move(result.tileData); + observer->onTileChanged(*this); +} - redoPlacement(); - observer->onTileLoaded(*this, TileLoadState::First); - } else { +void GeometryTile::onPlacement(PlacementResult result) { + if (result.correlationID == correlationID) { availableData = DataAvailability::All; - observer->onTileError(*this, result.get<std::exception_ptr>()); } -} - -bool GeometryTile::parsePending() { - if (workRequest) { - // There's already parsing or placement going on. - return false; + for (auto& bucket : result.buckets) { + buckets[bucket.first] = std::move(bucket.second); } - - workRequest.reset(); - workRequest = worker.parsePendingGeometryTileLayers(tileWorker, targetConfig, [this, config = targetConfig] (TileParseResult result) { - workRequest.reset(); - - if (result.is<TileParseResultData>()) { - auto& resultBuckets = result.get<TileParseResultData>(); - availableData = resultBuckets.complete ? DataAvailability::All : DataAvailability::Some; - - // 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); - } - - // 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; - - if (isComplete()) { - featureIndex = std::move(resultBuckets.featureIndex); - data = std::move(resultBuckets.tileData); - } - - redoPlacement(); - observer->onTileLoaded(*this, TileLoadState::Subsequent); - } else { - availableData = DataAvailability::All; - observer->onTileError(*this, result.get<std::exception_ptr>()); - } - }); - - return true; + featureIndex->setCollisionTile(std::move(result.collisionTile)); + placedConfig = result.placedConfig; + observer->onTileChanged(*this); } Bucket* GeometryTile::getBucket(const Layer& layer) { @@ -181,43 +125,6 @@ Bucket* GeometryTile::getBucket(const Layer& layer) { return it->second.get(); } -void GeometryTile::redoPlacement(const PlacementConfig newConfig) { - targetConfig = newConfig; - redoPlacement(); -} - -void GeometryTile::redoPlacement() { - // Don't start a new placement request when the current one hasn't completed yet, or when - // we are parsing buckets. - if (workRequest || targetConfig == placedConfig) { - return; - } - - workRequest = worker.redoPlacement(tileWorker, targetConfig, [this, config = targetConfig](TilePlacementResult result) { - 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 : result.buckets) { - buckets[bucket.first] = std::move(bucket.second); - } - - if (featureIndex) { - featureIndex->setCollisionTile(std::move(result.collisionTile)); - } - - // The target configuration could have changed since we started placement. In this case, - // we're starting another placement run. - if (placedConfig != targetConfig) { - redoPlacement(); - } else { - observer->onTileUpdated(*this); - } - }); -} - void GeometryTile::queryRenderedFeatures( std::unordered_map<std::string, std::vector<Feature>>& result, const GeometryCoordinates& queryGeometry, @@ -237,9 +144,4 @@ void GeometryTile::queryRenderedFeatures( style); } -void GeometryTile::cancel() { - obsolete = true; - workRequest.reset(); -} - } // namespace mbgl diff --git a/src/mbgl/tile/geometry_tile.hpp b/src/mbgl/tile/geometry_tile.hpp index e0db58325c..a644992376 100644 --- a/src/mbgl/tile/geometry_tile.hpp +++ b/src/mbgl/tile/geometry_tile.hpp @@ -1,9 +1,10 @@ #pragma once #include <mbgl/tile/tile.hpp> -#include <mbgl/tile/tile_worker.hpp> +#include <mbgl/tile/geometry_tile_worker.hpp> #include <mbgl/text/placement_config.hpp> #include <mbgl/util/feature.hpp> +#include <mbgl/actor/actor.hpp> #include <atomic> #include <memory> @@ -12,34 +13,31 @@ namespace mbgl { -class AsyncRequest; class GeometryTileData; class FeatureIndex; +class CollisionTile; namespace style { class Style; class Layer; +class UpdateParameters; } // namespace style class GeometryTile : public Tile { public: GeometryTile(const OverscaledTileID&, std::string sourceID, - style::Style&, - const MapMode); + const style::UpdateParameters&); ~GeometryTile() override; void setError(std::exception_ptr err); void setData(std::unique_ptr<const GeometryTileData>); + void setPlacementConfig(const PlacementConfig&) override; + void redoLayout() override; Bucket* getBucket(const style::Layer&) override; - bool parsePending() override; - - void redoLayout() override; - void redoPlacement(PlacementConfig) override; - void queryRenderedFeatures( std::unordered_map<std::string, std::vector<Feature>>& result, const GeometryCoordinates& queryGeometry, @@ -48,35 +46,40 @@ public: void cancel() override; -private: - std::vector<std::unique_ptr<style::Layer>> cloneStyleLayers() const; - void redoPlacement(); - - void tileLoaded(TileParseResult, PlacementConfig); + class LayoutResult { + public: + std::unordered_map<std::string, std::unique_ptr<Bucket>> buckets; + std::unique_ptr<FeatureIndex> featureIndex; + std::unique_ptr<GeometryTileData> tileData; + uint64_t correlationID; + }; + void onLayout(LayoutResult); + + class PlacementResult { + public: + std::unordered_map<std::string, std::unique_ptr<Bucket>> buckets; + std::unique_ptr<CollisionTile> collisionTile; + PlacementConfig placedConfig; + uint64_t correlationID; + }; + void onPlacement(PlacementResult); +private: const std::string sourceID; style::Style& style; - Worker& worker; - TileWorker tileWorker; - std::unique_ptr<AsyncRequest> workRequest; + // Used to signal the worker that it should abandon parsing this tile as soon as possible. + std::atomic<bool> obsolete { false }; - // Contains all the Bucket objects for the tile. Buckets are render - // objects and they get added by tile parsing operations. - std::unordered_map<std::string, std::unique_ptr<Bucket>> buckets; + std::shared_ptr<Mailbox> mailbox; + Actor<GeometryTileWorker> worker; + + uint64_t correlationID = 0; + optional<PlacementConfig> placedConfig; + std::unordered_map<std::string, std::unique_ptr<Bucket>> buckets; std::unique_ptr<FeatureIndex> featureIndex; std::unique_ptr<const GeometryTileData> data; - - // 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; - - // Used to signal the worker that it should abandon parsing this tile as soon as possible. - std::atomic<bool> obsolete { false }; }; } // namespace mbgl diff --git a/src/mbgl/tile/geometry_tile_worker.cpp b/src/mbgl/tile/geometry_tile_worker.cpp new file mode 100644 index 0000000000..35d15ae1fa --- /dev/null +++ b/src/mbgl/tile/geometry_tile_worker.cpp @@ -0,0 +1,300 @@ +#include <mbgl/tile/geometry_tile_worker.hpp> +#include <mbgl/tile/geometry_tile_data.hpp> +#include <mbgl/tile/geometry_tile.hpp> +#include <mbgl/text/collision_tile.hpp> +#include <mbgl/layout/symbol_layout.hpp> +#include <mbgl/style/bucket_parameters.hpp> +#include <mbgl/style/layers/symbol_layer.hpp> +#include <mbgl/style/layers/symbol_layer_impl.hpp> +#include <mbgl/sprite/sprite_atlas.hpp> +#include <mbgl/geometry/glyph_atlas.hpp> +#include <mbgl/renderer/symbol_bucket.hpp> +#include <mbgl/platform/log.hpp> +#include <mbgl/util/constants.hpp> +#include <mbgl/util/string.hpp> +#include <mbgl/util/exception.hpp> + +#include <unordered_set> + +namespace mbgl { + +using namespace style; + +GeometryTileWorker::GeometryTileWorker(ActorRef<GeometryTileWorker> self_, + ActorRef<GeometryTile> parent_, + OverscaledTileID id_, + SpriteStore& spriteStore_, + GlyphAtlas& glyphAtlas_, + GlyphStore& glyphStore_, + const std::atomic<bool>& obsolete_, + const MapMode mode_) + : self(std::move(self_)), + parent(std::move(parent_)), + id(std::move(id_)), + spriteStore(spriteStore_), + glyphAtlas(glyphAtlas_), + glyphStore(glyphStore_), + obsolete(obsolete_), + mode(mode_) { +} + +GeometryTileWorker::~GeometryTileWorker() { + glyphAtlas.removeGlyphs(reinterpret_cast<uintptr_t>(this)); +} + +/* + GeometryTileWorker is a state machine. This is its transition diagram. + States are indicated by [state], lines are transitions triggered by + messages, (parentheses) are actions taken on transition. + + [idle] <------------------. + | | + set{Data,Layers,Placement} | + | | + (do layout/placement; self-send "coalesced") | + v | + [coalescing] --- coalesced --. + | | + .-----------------. .---------------. + | | + .--- set{Data,Layers} setPlacement -----. + | | | | + | v v | + .-- [need layout] <-- set{Data,Layers} -- [need placement] --. + | | + coalesced coalesced + | | + v v + (do layout or placement; self-send "coalesced"; goto [coalescing]) + + The idea is that in the [idle] state, layout or placement happens immediately + in response to a "set" message. During this processing, multiple "set" messages + might get queued in the mailbox. At the end of processing, we self-send "coalesced", + read all the queued messages until we get to "coalesced", and then redo either + layout or placement if there were one or more "set"s (with layout taking priority, + since it will trigger placement when complete), or return to the [idle] state if not. +*/ + +void GeometryTileWorker::setData(std::unique_ptr<const GeometryTileData> data_, uint64_t correlationID_) { + try { + data = std::move(data_); + correlationID = correlationID_; + + switch (state) { + case Idle: + redoLayout(); + coalesce(); + break; + + case Coalescing: + case NeedLayout: + case NeedPlacement: + state = NeedLayout; + break; + } + } catch (...) { + parent.invoke(&GeometryTile::setError, std::current_exception()); + } +} + +void GeometryTileWorker::setLayers(std::vector<std::unique_ptr<Layer>> layers_, uint64_t correlationID_) { + try { + layers = std::move(layers_); + correlationID = correlationID_; + + switch (state) { + case Idle: + redoLayout(); + coalesce(); + break; + + case Coalescing: + case NeedPlacement: + state = NeedLayout; + break; + + case NeedLayout: + break; + } + } catch (...) { + parent.invoke(&GeometryTile::setError, std::current_exception()); + } +} + +void GeometryTileWorker::setPlacementConfig(PlacementConfig placementConfig_, uint64_t correlationID_) { + try { + placementConfig = std::move(placementConfig_); + correlationID = correlationID_; + + switch (state) { + case Idle: + attemptPlacement(); + coalesce(); + break; + + case Coalescing: + state = NeedPlacement; + break; + + case NeedPlacement: + case NeedLayout: + break; + } + } catch (...) { + parent.invoke(&GeometryTile::setError, std::current_exception()); + } +} + +void GeometryTileWorker::coalesced() { + try { + switch (state) { + case Idle: + assert(false); + break; + + case Coalescing: + state = Idle; + break; + + case NeedLayout: + redoLayout(); + coalesce(); + break; + + case NeedPlacement: + attemptPlacement(); + coalesce(); + break; + } + } catch (...) { + parent.invoke(&GeometryTile::setError, std::current_exception()); + } +} + +void GeometryTileWorker::coalesce() { + state = Coalescing; + self.invoke(&GeometryTileWorker::coalesced); +} + +void GeometryTileWorker::redoLayout() { + if (!data || !layers) { + return; + } + + // We're doing a fresh parse of the tile, because the underlying data or style has changed. + symbolLayouts.clear(); + + // We're storing a set of bucket names we've parsed to avoid parsing a bucket twice that is + // referenced from more than one layer + std::unordered_set<std::string> parsed; + std::unordered_map<std::string, std::unique_ptr<Bucket>> buckets; + auto featureIndex = std::make_unique<FeatureIndex>(); + + for (auto i = layers->rbegin(); i != layers->rend(); i++) { + if (obsolete) { + return; + } + + const Layer* layer = i->get(); + const std::string& bucketName = layer->baseImpl->bucketName(); + + featureIndex->addBucketLayerName(bucketName, layer->baseImpl->id); + + if (parsed.find(bucketName) != parsed.end()) { + continue; + } + + parsed.emplace(bucketName); + + if (!*data) { + continue; // Tile has no data. + } + + auto geometryLayer = (*data)->getLayer(layer->baseImpl->sourceLayer); + if (!geometryLayer) { + continue; + } + + BucketParameters parameters(id, + *geometryLayer, + obsolete, + reinterpret_cast<uintptr_t>(this), + spriteStore, + glyphAtlas, + glyphStore, + *featureIndex, + mode); + + if (layer->is<SymbolLayer>()) { + symbolLayouts.push_back(layer->as<SymbolLayer>()->impl->createLayout(parameters)); + } else { + std::unique_ptr<Bucket> bucket = layer->baseImpl->createBucket(parameters); + if (bucket->hasData()) { + buckets.emplace(layer->baseImpl->bucketName(), std::move(bucket)); + } + } + } + + parent.invoke(&GeometryTile::onLayout, GeometryTile::LayoutResult { + std::move(buckets), + std::move(featureIndex), + *data ? (*data)->clone() : nullptr, + correlationID + }); + + attemptPlacement(); +} + +void GeometryTileWorker::attemptPlacement() { + if (!data || !layers || !placementConfig) { + return; + } + + bool canPlace = true; + + // Prepare as many SymbolLayouts as possible. + for (auto& symbolLayout : symbolLayouts) { + if (obsolete) { + return; + } + + if (symbolLayout->state == SymbolLayout::Pending) { + if (symbolLayout->canPrepare(glyphStore, spriteStore)) { + symbolLayout->state = SymbolLayout::Prepared; + symbolLayout->prepare(reinterpret_cast<uintptr_t>(this), + glyphAtlas, + glyphStore); + } else { + canPlace = false; + } + } + } + + if (!canPlace) { + return; // We'll be notified (via `setPlacementConfig`) when it's time to try again. + } + + auto collisionTile = std::make_unique<CollisionTile>(*placementConfig); + std::unordered_map<std::string, std::unique_ptr<Bucket>> buckets; + + for (auto& symbolLayout : symbolLayouts) { + if (obsolete) { + return; + } + + symbolLayout->state = SymbolLayout::Placed; + if (symbolLayout->hasSymbolInstances()) { + buckets.emplace(symbolLayout->bucketName, + symbolLayout->place(*collisionTile)); + } + } + + parent.invoke(&GeometryTile::onPlacement, GeometryTile::PlacementResult { + std::move(buckets), + std::move(collisionTile), + *placementConfig, + correlationID + }); +} + +} // namespace mbgl diff --git a/src/mbgl/tile/geometry_tile_worker.hpp b/src/mbgl/tile/geometry_tile_worker.hpp new file mode 100644 index 0000000000..cc5e48f9b4 --- /dev/null +++ b/src/mbgl/tile/geometry_tile_worker.hpp @@ -0,0 +1,76 @@ +#pragma once + +#include <mbgl/map/mode.hpp> +#include <mbgl/tile/tile_id.hpp> +#include <mbgl/text/placement_config.hpp> +#include <mbgl/actor/actor_ref.hpp> +#include <mbgl/util/optional.hpp> + +#include <atomic> +#include <memory> +#include <unordered_map> + +namespace mbgl { + +class GeometryTile; +class GeometryTileData; +class SpriteStore; +class GlyphAtlas; +class GlyphStore; +class SymbolLayout; + +namespace style { +class Layer; +} // namespace style + +class GeometryTileWorker { +public: + GeometryTileWorker(ActorRef<GeometryTileWorker> self, + ActorRef<GeometryTile> parent, + OverscaledTileID, + SpriteStore&, + GlyphAtlas&, + GlyphStore&, + const std::atomic<bool>&, + const MapMode); + ~GeometryTileWorker(); + + void setLayers(std::vector<std::unique_ptr<style::Layer>>, uint64_t correlationID); + void setData(std::unique_ptr<const GeometryTileData>, uint64_t correlationID); + void setPlacementConfig(PlacementConfig, uint64_t correlationID); + +private: + void coalesce(); + void coalesced(); + void redoLayout(); + void attemptPlacement(); + + ActorRef<GeometryTileWorker> self; + ActorRef<GeometryTile> parent; + + const OverscaledTileID id; + SpriteStore& spriteStore; + GlyphAtlas& glyphAtlas; + GlyphStore& glyphStore; + const std::atomic<bool>& obsolete; + const MapMode mode; + + enum State { + Idle, + Coalescing, + NeedLayout, + NeedPlacement + }; + + State state = Idle; + uint64_t correlationID = 0; + + // Outer optional indicates whether we've received it or not. + optional<std::vector<std::unique_ptr<style::Layer>>> layers; + optional<std::unique_ptr<const GeometryTileData>> data; + optional<PlacementConfig> placementConfig; + + std::vector<std::unique_ptr<SymbolLayout>> symbolLayouts; +}; + +} // namespace mbgl diff --git a/src/mbgl/tile/raster_tile.cpp b/src/mbgl/tile/raster_tile.cpp index 9230c3d79d..303212da80 100644 --- a/src/mbgl/tile/raster_tile.cpp +++ b/src/mbgl/tile/raster_tile.cpp @@ -1,4 +1,5 @@ #include <mbgl/tile/raster_tile.hpp> +#include <mbgl/tile/raster_tile_worker.hpp> #include <mbgl/tile/tile_observer.hpp> #include <mbgl/tile/tile_loader_impl.hpp> #include <mbgl/style/source.hpp> @@ -6,8 +7,8 @@ #include <mbgl/storage/resource.hpp> #include <mbgl/storage/response.hpp> #include <mbgl/storage/file_source.hpp> -#include <mbgl/util/worker.hpp> -#include <mbgl/util/work_request.hpp> +#include <mbgl/renderer/raster_bucket.hpp> +#include <mbgl/util/run_loop.hpp> namespace mbgl { @@ -15,13 +16,20 @@ RasterTile::RasterTile(const OverscaledTileID& id_, const style::UpdateParameters& parameters, const Tileset& tileset) : Tile(id_), - worker(parameters.worker), - loader(*this, id_, parameters, tileset) { + loader(*this, id_, parameters, tileset), + mailbox(std::make_shared<Mailbox>(*util::RunLoop::Get())), + worker(parameters.workerScheduler, + ActorRef<RasterTile>(*this, mailbox)) { } RasterTile::~RasterTile() = default; +void RasterTile::cancel() { +} + void RasterTile::setError(std::exception_ptr err) { + bucket.reset(); + availableData = DataAvailability::All; observer->onTileError(*this, err); } @@ -30,30 +38,13 @@ void RasterTile::setData(std::shared_ptr<const std::string> data, optional<Timestamp> expires_) { modified = modified_; expires = expires_; + worker.invoke(&RasterTileWorker::parse, data); +} - if (!data) { - // This is a 404 response. We're treating these as empty tiles. - workRequest.reset(); - availableData = DataAvailability::All; - bucket.reset(); - observer->onTileLoaded(*this, TileLoadState::First); - return; - } - - workRequest.reset(); - workRequest = worker.parseRasterTile(std::make_unique<RasterBucket>(), data, [this] (RasterTileParseResult result) { - workRequest.reset(); - - availableData = DataAvailability::All; - - if (result.is<std::unique_ptr<Bucket>>()) { - bucket = std::move(result.get<std::unique_ptr<Bucket>>()); - observer->onTileLoaded(*this, TileLoadState::First); - } else { - bucket.reset(); - observer->onTileError(*this, result.get<std::exception_ptr>()); - } - }); +void RasterTile::onParsed(std::unique_ptr<Bucket> result) { + bucket = std::move(result); + availableData = DataAvailability::All; + observer->onTileChanged(*this); } Bucket* RasterTile::getBucket(const style::Layer&) { @@ -64,8 +55,4 @@ void RasterTile::setNecessity(Necessity necessity) { loader.setNecessity(necessity); } -void RasterTile::cancel() { - workRequest.reset(); -} - } // namespace mbgl diff --git a/src/mbgl/tile/raster_tile.hpp b/src/mbgl/tile/raster_tile.hpp index 496edda6b3..2b2e84d463 100644 --- a/src/mbgl/tile/raster_tile.hpp +++ b/src/mbgl/tile/raster_tile.hpp @@ -2,11 +2,11 @@ #include <mbgl/tile/tile.hpp> #include <mbgl/tile/tile_loader.hpp> -#include <mbgl/renderer/raster_bucket.hpp> +#include <mbgl/tile/raster_tile_worker.hpp> +#include <mbgl/actor/actor.hpp> namespace mbgl { -class AsyncRequest; class Tileset; namespace style { @@ -32,11 +32,13 @@ public: void cancel() override; Bucket* getBucket(const style::Layer&) override; -private: - Worker& worker; + void onParsed(std::unique_ptr<Bucket> result); +private: TileLoader<RasterTile> loader; - std::unique_ptr<AsyncRequest> workRequest; + + std::shared_ptr<Mailbox> mailbox; + Actor<RasterTileWorker> worker; // Contains the Bucket object for the tile. Buckets are render // objects and they get added by tile parsing operations. diff --git a/src/mbgl/tile/raster_tile_worker.cpp b/src/mbgl/tile/raster_tile_worker.cpp new file mode 100644 index 0000000000..838737d5b8 --- /dev/null +++ b/src/mbgl/tile/raster_tile_worker.cpp @@ -0,0 +1,27 @@ +#include <mbgl/tile/raster_tile_worker.hpp> +#include <mbgl/tile/raster_tile.hpp> +#include <mbgl/renderer/raster_bucket.cpp> +#include <mbgl/actor/actor.hpp> + +namespace mbgl { + +RasterTileWorker::RasterTileWorker(ActorRef<RasterTileWorker>, ActorRef<RasterTile> parent_) + : parent(std::move(parent_)) { +} + +void RasterTileWorker::parse(std::shared_ptr<const std::string> data) { + if (!data) { + parent.invoke(&RasterTile::onParsed, nullptr); // No data; empty tile. + return; + } + + try { + auto bucket = std::make_unique<RasterBucket>(); + bucket->setImage(decodeImage(*data)); + parent.invoke(&RasterTile::onParsed, std::move(bucket)); + } catch (...) { + parent.invoke(&RasterTile::setError, std::current_exception()); + } +} + +} // namespace mbgl diff --git a/src/mbgl/tile/raster_tile_worker.hpp b/src/mbgl/tile/raster_tile_worker.hpp new file mode 100644 index 0000000000..44bc37ca5d --- /dev/null +++ b/src/mbgl/tile/raster_tile_worker.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include <mbgl/actor/actor_ref.hpp> + +#include <memory> +#include <string> + +namespace mbgl { + +class RasterTile; + +class RasterTileWorker { +public: + RasterTileWorker(ActorRef<RasterTileWorker>, ActorRef<RasterTile>); + + void parse(std::shared_ptr<const std::string> data); + +private: + ActorRef<RasterTile> parent; +}; + +} // namespace mbgl diff --git a/src/mbgl/tile/tile.cpp b/src/mbgl/tile/tile.cpp index 632e271093..ec0540b1ad 100644 --- a/src/mbgl/tile/tile.cpp +++ b/src/mbgl/tile/tile.cpp @@ -18,7 +18,7 @@ void Tile::setObserver(TileObserver* observer_) { void Tile::setTriedOptional() { triedOptional = true; - observer->onTileUpdated(*this); + observer->onTileChanged(*this); } void Tile::dumpDebugLogs() const { diff --git a/src/mbgl/tile/tile.hpp b/src/mbgl/tile/tile.hpp index 740ced3898..be03608994 100644 --- a/src/mbgl/tile/tile.hpp +++ b/src/mbgl/tile/tile.hpp @@ -6,7 +6,6 @@ #include <mbgl/util/feature.hpp> #include <mbgl/tile/tile_id.hpp> #include <mbgl/renderer/bucket.hpp> -#include <mbgl/text/placement_config.hpp> #include <mbgl/tile/geometry_tile_data.hpp> #include <mbgl/storage/resource.hpp> @@ -17,10 +16,10 @@ namespace mbgl { -class Worker; class DebugBucket; class TransformState; class TileObserver; +class PlacementConfig; namespace style { class Layer; @@ -47,9 +46,8 @@ public: virtual Bucket* getBucket(const style::Layer&) = 0; - virtual bool parsePending() { return true; } + virtual void setPlacementConfig(const PlacementConfig&) {} virtual void redoLayout() {} - virtual void redoPlacement(PlacementConfig) {} virtual void queryRenderedFeatures( std::unordered_map<std::string, std::vector<Feature>>& result, diff --git a/src/mbgl/tile/tile_observer.hpp b/src/mbgl/tile/tile_observer.hpp index 94243cb1fa..837b47ae0b 100644 --- a/src/mbgl/tile/tile_observer.hpp +++ b/src/mbgl/tile/tile_observer.hpp @@ -6,18 +6,12 @@ namespace mbgl { class Tile; -enum class TileLoadState : bool { - First = true, - Subsequent = false, -}; - class TileObserver { public: virtual ~TileObserver() = default; - virtual void onTileLoaded(Tile&, TileLoadState) {} + virtual void onTileChanged(Tile&) {} virtual void onTileError(Tile&, std::exception_ptr) {} - virtual void onTileUpdated(Tile&) {} }; } // namespace mbgl diff --git a/src/mbgl/tile/tile_worker.cpp b/src/mbgl/tile/tile_worker.cpp deleted file mode 100644 index 97e682d697..0000000000 --- a/src/mbgl/tile/tile_worker.cpp +++ /dev/null @@ -1,179 +0,0 @@ -#include <mbgl/text/collision_tile.hpp> -#include <mbgl/tile/tile_worker.hpp> -#include <mbgl/tile/geometry_tile_data.hpp> -#include <mbgl/layout/symbol_layout.hpp> -#include <mbgl/style/bucket_parameters.hpp> -#include <mbgl/style/layers/symbol_layer.hpp> -#include <mbgl/style/layers/symbol_layer_impl.hpp> -#include <mbgl/sprite/sprite_atlas.hpp> -#include <mbgl/geometry/glyph_atlas.hpp> -#include <mbgl/renderer/symbol_bucket.hpp> -#include <mbgl/platform/log.hpp> -#include <mbgl/util/constants.hpp> -#include <mbgl/util/string.hpp> -#include <mbgl/util/exception.hpp> - -#include <unordered_set> - -namespace mbgl { - -using namespace style; - -TileWorker::TileWorker(OverscaledTileID id_, - SpriteStore& spriteStore_, - GlyphAtlas& glyphAtlas_, - GlyphStore& glyphStore_, - const std::atomic<bool>& obsolete_, - const MapMode mode_) - : id(std::move(id_)), - spriteStore(spriteStore_), - glyphAtlas(glyphAtlas_), - glyphStore(glyphStore_), - obsolete(obsolete_), - mode(mode_) { -} - -TileWorker::~TileWorker() { - glyphAtlas.removeGlyphs(reinterpret_cast<uintptr_t>(this)); -} - -TileParseResult TileWorker::parseAllLayers(std::vector<std::unique_ptr<Layer>> layers_, - std::unique_ptr<const GeometryTileData> tileData_, - const PlacementConfig& config) { - tileData = std::move(tileData_); - return redoLayout(std::move(layers_), config); -} - -TileParseResult TileWorker::redoLayout(std::vector<std::unique_ptr<Layer>> layers_, - const PlacementConfig& config) { - layers = std::move(layers_); - - // We're doing a fresh parse of the tile, because the underlying data or style has changed. - featureIndex = std::make_unique<FeatureIndex>(); - symbolLayouts.clear(); - - // We're storing a set of bucket names we've parsed to avoid parsing a bucket twice that is - // referenced from more than one layer - std::unordered_map<std::string, std::unique_ptr<Bucket>> buckets; - std::unordered_set<std::string> parsed; - - for (auto i = layers.rbegin(); i != layers.rend(); i++) { - if (obsolete) { - break; - } - - // Temporary prevention for crashing due to https://github.com/mapbox/mapbox-gl-native/issues/6263. - // Instead, the race condition will produce a blank tile. - if (!tileData) { - break; - } - - const Layer* layer = i->get(); - const std::string& bucketName = layer->baseImpl->bucketName(); - - featureIndex->addBucketLayerName(bucketName, layer->baseImpl->id); - - if (parsed.find(bucketName) != parsed.end()) { - continue; - } - - parsed.emplace(bucketName); - - auto geometryLayer = tileData->getLayer(layer->baseImpl->sourceLayer); - if (!geometryLayer) { - continue; - } - - BucketParameters parameters(id, - *geometryLayer, - obsolete, - reinterpret_cast<uintptr_t>(this), - spriteStore, - glyphAtlas, - glyphStore, - *featureIndex, - mode); - - if (layer->is<SymbolLayer>()) { - symbolLayouts.push_back(layer->as<SymbolLayer>()->impl->createLayout(parameters)); - } else { - std::unique_ptr<Bucket> bucket = layer->baseImpl->createBucket(parameters); - if (bucket->hasData()) { - buckets.emplace(layer->baseImpl->bucketName(), std::move(bucket)); - } - } - } - - return parsePendingLayers(config, std::move(buckets)); -} - -TileParseResult TileWorker::parsePendingLayers(const PlacementConfig& config) { - return parsePendingLayers(config, std::unordered_map<std::string, std::unique_ptr<Bucket>>()); -} - -TileParseResult TileWorker::parsePendingLayers(const PlacementConfig& config, - std::unordered_map<std::string, std::unique_ptr<Bucket>> buckets) { - TileParseResultData result; - - result.complete = true; - result.buckets = std::move(buckets); - - // Prepare as many SymbolLayouts as possible. - for (auto& symbolLayout : symbolLayouts) { - if (symbolLayout->state == SymbolLayout::Pending) { - if (symbolLayout->canPrepare(glyphStore, spriteStore)) { - symbolLayout->state = SymbolLayout::Prepared; - symbolLayout->prepare(reinterpret_cast<uintptr_t>(this), - glyphAtlas, - glyphStore); - } else { - result.complete = false; - } - } - } - - // If all SymbolLayouts are prepared, then perform placement. Otherwise, parsePendingLayers - // will eventually be re-run. - if (result.complete) { - TilePlacementResult placementResult = redoPlacement(config); - - featureIndex->setCollisionTile(std::move(placementResult.collisionTile)); - - for (auto& bucket : placementResult.buckets) { - result.buckets.emplace(std::move(bucket)); - } - - result.featureIndex = std::move(featureIndex); - - if (tileData) { - result.tileData = tileData->clone(); - } - } - - return std::move(result); -} - -TilePlacementResult TileWorker::redoPlacement(const PlacementConfig& config) { - TilePlacementResult result; - - result.collisionTile = std::make_unique<CollisionTile>(config); - - for (auto& symbolLayout : symbolLayouts) { - if (symbolLayout->state == SymbolLayout::Pending) { - // Can't do placement until all layouts are prepared. - return result; - } - } - - for (auto& symbolLayout : symbolLayouts) { - symbolLayout->state = SymbolLayout::Placed; - if (symbolLayout->hasSymbolInstances()) { - result.buckets.emplace(symbolLayout->bucketName, - symbolLayout->place(*result.collisionTile)); - } - } - - return result; -} - -} // namespace mbgl diff --git a/src/mbgl/tile/tile_worker.hpp b/src/mbgl/tile/tile_worker.hpp deleted file mode 100644 index e64e7dee19..0000000000 --- a/src/mbgl/tile/tile_worker.hpp +++ /dev/null @@ -1,91 +0,0 @@ -#pragma once - -#include <mbgl/map/mode.hpp> -#include <mbgl/tile/tile_id.hpp> -#include <mbgl/util/noncopyable.hpp> -#include <mbgl/util/variant.hpp> -#include <mbgl/geometry/feature_index.hpp> - -#include <atomic> -#include <string> -#include <memory> -#include <mutex> -#include <list> -#include <unordered_map> - -namespace mbgl { - -class CollisionTile; -class GeometryTileData; -class SpriteStore; -class GlyphAtlas; -class GlyphStore; -class Bucket; -class SymbolLayout; -class PlacementConfig; - -namespace style { -class Layer; -} // namespace style - -// We're using this class to shuttle the resulting buckets from the worker thread to the MapContext -// thread. This class is movable-only because the vector contains movable-only value elements. -class TileParseResultData { -public: - bool complete = false; - std::unordered_map<std::string, std::unique_ptr<Bucket>> buckets; - std::unique_ptr<FeatureIndex> featureIndex; - std::unique_ptr<const GeometryTileData> tileData; -}; - -using TileParseResult = variant< - TileParseResultData, // success - std::exception_ptr>; // error - -class TilePlacementResult { -public: - std::unordered_map<std::string, std::unique_ptr<Bucket>> buckets; - std::unique_ptr<CollisionTile> collisionTile; -}; - -class TileWorker : public util::noncopyable { -public: - TileWorker(OverscaledTileID, - SpriteStore&, - GlyphAtlas&, - GlyphStore&, - const std::atomic<bool>&, - const MapMode); - ~TileWorker(); - - TileParseResult parseAllLayers(std::vector<std::unique_ptr<style::Layer>>, - std::unique_ptr<const GeometryTileData>, - const PlacementConfig&); - - TileParseResult parsePendingLayers(const PlacementConfig&); - - TileParseResult redoLayout(std::vector<std::unique_ptr<style::Layer>>, - const PlacementConfig&); - - TilePlacementResult redoPlacement(const PlacementConfig&); - -private: - TileParseResult parsePendingLayers(const PlacementConfig&, - std::unordered_map<std::string, std::unique_ptr<Bucket>>); - - const OverscaledTileID id; - - SpriteStore& spriteStore; - GlyphAtlas& glyphAtlas; - GlyphStore& glyphStore; - const std::atomic<bool>& obsolete; - const MapMode mode; - - std::vector<std::unique_ptr<style::Layer>> layers; - std::unique_ptr<const GeometryTileData> tileData; - - std::unique_ptr<FeatureIndex> featureIndex; - std::vector<std::unique_ptr<SymbolLayout>> symbolLayouts; -}; - -} // namespace mbgl diff --git a/src/mbgl/tile/vector_tile.cpp b/src/mbgl/tile/vector_tile.cpp index bde0b4f63e..a195885415 100644 --- a/src/mbgl/tile/vector_tile.cpp +++ b/src/mbgl/tile/vector_tile.cpp @@ -1,7 +1,6 @@ #include <mbgl/tile/vector_tile.hpp> #include <mbgl/tile/tile_loader_impl.hpp> #include <mbgl/tile/geometry_tile_data.hpp> -#include <mbgl/style/update_parameters.hpp> #include <protozero/pbf_reader.hpp> @@ -75,7 +74,7 @@ VectorTile::VectorTile(const OverscaledTileID& id_, std::string sourceID_, const style::UpdateParameters& parameters, const Tileset& tileset) - : GeometryTile(id_, sourceID_, parameters.style, parameters.mode), + : GeometryTile(id_, sourceID_, parameters), loader(*this, id_, parameters, tileset) { } |