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