From 1455e5e53c1795401bec1b88272e93ce326d1753 Mon Sep 17 00:00:00 2001 From: "Thiago Marcos P. Santos" Date: Wed, 22 Apr 2015 15:05:42 +0200 Subject: [PATCH] Add partial tile parsing This change will add a new state to the tile called "partial" which is a state where the tile can be rendered but some information might be missing. For now we skip the SymbolBucket if the Sprites are not loaded (will also do the same for the glyphs in the future). When the Sprites are finally loaded, we trigger another update, this time the Tile will get completely re-rendered and change the state to "parsed". --- src/mbgl/map/source.cpp | 44 ++++++++++++++++++++++++----- src/mbgl/map/source.hpp | 1 + src/mbgl/map/tile_data.cpp | 17 +++++++++-- src/mbgl/map/tile_data.hpp | 21 +++++++++++++- src/mbgl/map/tile_parser.cpp | 22 ++++++++------- src/mbgl/map/tile_parser.hpp | 4 +++ src/mbgl/map/vector_tile_data.cpp | 47 +++++++++++++++++++++++-------- src/mbgl/map/vector_tile_data.hpp | 17 ++++++++--- 8 files changed, 137 insertions(+), 36 deletions(-) diff --git a/src/mbgl/map/source.cpp b/src/mbgl/map/source.cpp index c63cd026d..9b6631da9 100644 --- a/src/mbgl/map/source.cpp +++ b/src/mbgl/map/source.cpp @@ -216,6 +216,23 @@ TileData::State Source::hasTile(const TileID& id) { return TileData::State::invalid; } +void Source::handlePartialTile(const TileID &id, Worker &worker) { + const TileID normalized_id = id.normalized(); + + auto it = tile_data.find(normalized_id); + if (it == tile_data.end()) { + return; + } + + util::ptr data = it->second.lock(); + if (!data) { + return; + } + + auto callback = std::bind(&Source::emitTileLoaded, this); + data->reparse(worker, callback); +} + TileData::State Source::addTile(MapData& data, const TransformState& transformState, Style& style, @@ -254,7 +271,6 @@ TileData::State Source::addTile(MapData& data, } auto callback = std::bind(&Source::emitTileLoaded, this); - if (!new_tile.data) { // If we don't find working tile data, we're just going to load it. if (info.type == SourceType::Vector) { @@ -324,7 +340,7 @@ bool Source::findLoadedChildren(const TileID& id, int32_t maxCoveringZoom, std:: auto ids = id.children(z + 1); for (const auto& child_id : ids) { const TileData::State state = hasTile(child_id); - if (state == TileData::State::parsed) { + if (TileData::isReadyState(state)) { retain.emplace_front(child_id); } else { complete = false; @@ -350,7 +366,7 @@ bool Source::findLoadedParent(const TileID& id, int32_t minCoveringZoom, std::fo for (int32_t z = id.z - 1; z >= minCoveringZoom; --z) { const TileID parent_id = id.parent(z); const TileData::State state = hasTile(parent_id); - if (state == TileData::State::parsed) { + if (TileData::isReadyState(state)) { retain.emplace_front(parent_id); return true; } @@ -384,10 +400,21 @@ void Source::update(MapData& data, // Add existing child/parent tiles if the actual tile is not yet loaded for (const auto& id : required) { - const TileData::State state = addTile(data, transformState, style, glyphAtlas, glyphStore, - spriteAtlas, sprite, texturePool, id); + TileData::State state = hasTile(id); - if (state != TileData::State::parsed) { + switch (state) { + case TileData::State::partial: + handlePartialTile(id, style.workers); + break; + case TileData::State::invalid: + state = addTile(data, transformState, style, glyphAtlas, glyphStore, + spriteAtlas, sprite, texturePool, id); + break; + default: + break; + } + + if (!TileData::isReadyState(state)) { // The tile we require is not yet loaded. Try to find a parent or // child tile that we already have. @@ -422,7 +449,10 @@ void Source::update(MapData& data, bool obsolete = std::find(retain.begin(), retain.end(), tile.id) == retain.end(); if (!obsolete) { retain_data.insert(tile.data->id); - } else if (type != SourceType::Raster && tile.data->ready()) { + } else if (type != SourceType::Raster && tile.data->state == TileData::State::parsed) { + // Partially parsed tiles are never added to the cache because otherwise + // they never get updated if the go out from the viewport and the pending + // resources arrive. tileCache.add(tile.id.normalized().to_uint64(), tile.data); } return obsolete; diff --git a/src/mbgl/map/source.hpp b/src/mbgl/map/source.hpp index 255e97208..dd02cdc3b 100644 --- a/src/mbgl/map/source.hpp +++ b/src/mbgl/map/source.hpp @@ -97,6 +97,7 @@ private: void emitSourceLoaded(); void emitTileLoaded(); + void handlePartialTile(const TileID &id, Worker &worker); bool findLoadedChildren(const TileID& id, int32_t maxCoveringZoom, std::forward_list& retain); bool findLoadedParent(const TileID& id, int32_t minCoveringZoom, std::forward_list& retain); int32_t coveringZoomLevel(const TransformState&) const; diff --git a/src/mbgl/map/tile_data.cpp b/src/mbgl/map/tile_data.cpp index dd51cb841..4025bacfc 100644 --- a/src/mbgl/map/tile_data.cpp +++ b/src/mbgl/map/tile_data.cpp @@ -12,6 +12,7 @@ TileData::TileData(const TileID& id_, const SourceInfo& source_) : id(id_), name(id), state(State::initial), + parsing(ATOMIC_FLAG_INIT), source(source_), env(Environment::Get()), debugBucket(debugFontBuffer) { @@ -60,6 +61,18 @@ void TileData::cancel() { } } -void TileData::reparse(Worker& worker, std::function callback) { - workRequest = worker.send([this] { parse(); }, callback); +bool TileData::mayStartParsing() { + return !parsing.test_and_set(std::memory_order_acquire); +} + +void TileData::endParsing() { + parsing.clear(std::memory_order_release); +} + +void TileData::reparse(Worker& worker, std::function callback) { + if (!mayStartParsing()) { + return; + } + + workRequest = worker.send([this] { parse(); endParsing(); }, callback); } diff --git a/src/mbgl/map/tile_data.hpp b/src/mbgl/map/tile_data.hpp index 14e937eec..3be10dc03 100644 --- a/src/mbgl/map/tile_data.hpp +++ b/src/mbgl/map/tile_data.hpp @@ -29,10 +29,18 @@ public: initial, loading, loaded, + partial, parsed, obsolete }; + // Tile data considered "Ready" can be used for rendering. Data in + // partial state is still waiting for network resources but can also + // be rendered, although layers will be missing. + inline static bool isReadyState(const State& state) { + return state == State::partial || state == State::parsed; + } + TileData(const TileID&, const SourceInfo&); ~TileData(); @@ -42,9 +50,11 @@ public: const std::string toString() const; inline bool ready() const { - return state == State::parsed; + return isReadyState(state); } + void endParsing(); + // Override this in the child class. virtual void parse() = 0; virtual Bucket* getBucket(StyleLayer const &layer_desc) = 0; @@ -52,8 +62,17 @@ public: const TileID id; const std::string name; std::atomic state; + std::atomic_flag parsing; protected: + // Set the internal parsing state to true so we prevent + // multiple workers to parse the same tile in parallel, + // which can happen if the tile is in the "partial" state. + // It will return true if is possible to start pasing the + // tile or false if not (so some other worker is already + // parsing the tile). + bool mayStartParsing(); + const SourceInfo& source; Environment& env; diff --git a/src/mbgl/map/tile_parser.cpp b/src/mbgl/map/tile_parser.cpp index ad66aa154..18bc3b2ae 100644 --- a/src/mbgl/map/tile_parser.cpp +++ b/src/mbgl/map/tile_parser.cpp @@ -35,7 +35,8 @@ TileParser::TileParser(const GeometryTile& geometryTile_, glyphStore(glyphStore_), spriteAtlas(spriteAtlas_), sprite(sprite_), - collision(util::make_unique(tile.id.z, 4096, tile.source.tile_size, tile.depth)) { + collision(util::make_unique(tile.id.z, 4096, tile.source.tile_size, tile.depth)), + partialParse(false) { assert(sprite); assert(collision); } @@ -57,15 +58,15 @@ void TileParser::parse() { if (layer_desc->bucket) { // This is a singular layer. Check if this bucket already exists. If not, // parse this bucket. - auto bucket_it = tile.buckets.find(layer_desc->bucket->name); - if (bucket_it == tile.buckets.end()) { - // We need to create this bucket since it doesn't exist yet. - std::unique_ptr bucket = createBucket(*layer_desc->bucket); - if (bucket) { - // Bucket creation might fail because the data tile may not - // contain any data that falls into this bucket. - tile.buckets[layer_desc->bucket->name] = std::move(bucket); - } + if (tile.getBucket(*layer_desc)) { + continue; + } + + std::unique_ptr bucket = createBucket(*layer_desc->bucket); + if (bucket) { + // Bucket creation might fail because the data tile may not + // contain any data that falls into this bucket. + tile.setBucket(*layer_desc, std::move(bucket)); } } else { Log::Warning(Event::ParseTile, "layer '%s' does not have buckets", layer_desc->id.c_str()); @@ -121,6 +122,7 @@ std::unique_ptr TileParser::createBucket(const StyleBucket &bucketDesc) if (sprite->isLoaded()) { return createSymbolBucket(*layer, bucketDesc); } else { + partialParse = true; return nullptr; } } else if (bucketDesc.type == StyleLayerType::Raster) { diff --git a/src/mbgl/map/tile_parser.hpp b/src/mbgl/map/tile_parser.hpp index 2dbb8cb17..dcada6fc1 100644 --- a/src/mbgl/map/tile_parser.hpp +++ b/src/mbgl/map/tile_parser.hpp @@ -44,6 +44,9 @@ public: public: void parse(); + inline bool isPartialParse() const { + return partialParse; + } private: bool obsolete() const; @@ -67,6 +70,7 @@ private: util::ptr sprite; std::unique_ptr collision; + bool partialParse; }; } diff --git a/src/mbgl/map/vector_tile_data.cpp b/src/mbgl/map/vector_tile_data.cpp index e10e2f826..7560168ed 100644 --- a/src/mbgl/map/vector_tile_data.cpp +++ b/src/mbgl/map/vector_tile_data.cpp @@ -35,7 +35,7 @@ VectorTileData::~VectorTileData() { } void VectorTileData::parse() { - if (state != State::loaded) { + if (state != State::loaded && state != State::partial) { return; } @@ -47,24 +47,47 @@ void VectorTileData::parse() { const VectorTile* vt = &vectorTile; TileParser parser(*vt, *this, style, glyphAtlas, glyphStore, spriteAtlas, sprite); parser.parse(); + + if (state == State::obsolete) { + return; + } + + if (parser.isPartialParse()) { + state = State::partial; + } else { + state = State::parsed; + } } catch (const std::exception& ex) { Log::Error(Event::ParseTile, "Parsing [%d/%d/%d] failed: %s", id.z, id.x, id.y, ex.what()); state = State::obsolete; return; } - - if (state != State::obsolete) { - state = State::parsed; - } } Bucket* VectorTileData::getBucket(StyleLayer const& layer) { - if (state == State::parsed && layer.bucket) { - const auto it = buckets.find(layer.bucket->name); - if (it != buckets.end()) { - assert(it->second); - return it->second.get(); - } + if (!ready() || !layer.bucket) { + return nullptr; } - return nullptr; + + std::lock_guard lock(bucketsMutex); + + const auto it = buckets.find(layer.bucket->name); + if (it == buckets.end()) { + return nullptr; + } + + assert(it->second); + return it->second.get(); +} + +void VectorTileData::setBucket(StyleLayer const& layer, std::unique_ptr bucket) { + assert(layer.bucket); + + std::lock_guard lock(bucketsMutex); + + if (buckets.find(layer.bucket->name) != buckets.end()) { + return; + } + + buckets[layer.bucket->name] = std::move(bucket); } diff --git a/src/mbgl/map/vector_tile_data.hpp b/src/mbgl/map/vector_tile_data.hpp index 10eaf9c18..146bc9322 100644 --- a/src/mbgl/map/vector_tile_data.hpp +++ b/src/mbgl/map/vector_tile_data.hpp @@ -10,6 +10,7 @@ #include #include +#include #include namespace mbgl { @@ -42,6 +43,8 @@ public: void parse() override; virtual Bucket* getBucket(StyleLayer const &layer_desc) override; + void setBucket(StyleLayer const &layer_desc, std::unique_ptr bucket); + protected: // Holds the actual geometries in this tile. FillVertexBuffer fillVertexBuffer; @@ -50,16 +53,22 @@ protected: TriangleElementsBuffer triangleElementsBuffer; LineElementsBuffer lineElementsBuffer; - // Holds the buckets of this tile. - // They contain the location offsets in the buffers stored above - std::unordered_map> buckets; - GlyphAtlas& glyphAtlas; GlyphStore& glyphStore; SpriteAtlas& spriteAtlas; util::ptr sprite; Style& style; +private: + // Contains all the Bucket objects for the tile. Buckets are render + // objects and they get added to this std::map<> by the workers doing + // the actual tile parsing as they get processed. Tiles partially + // parsed can get new buckets at any moment but are also fit for + // rendering. That said, access to this list needs locking unless + // the tile is completely parsed. + std::unordered_map> buckets; + mutable std::mutex bucketsMutex; + public: const float depth; };