Asynchronous GeometryTile deletion (#2051)

Co-authored-by: Tim Sylvester <tim@wetdogweather.com>
This commit is contained in:
mwilsnd
2024-02-28 17:18:08 -05:00
committed by GitHub
parent e3cbdb63a8
commit 4fff8a5d33
80 changed files with 1180 additions and 334 deletions

View File

@@ -1,6 +1,7 @@
#include <mbgl/actor/mailbox.hpp>
#include <mbgl/actor/message.hpp>
#include <mbgl/actor/scheduler.hpp>
#include <mbgl/util/scoped.hpp>
#include <cassert>
@@ -32,6 +33,8 @@ void Mailbox::open(Scheduler& scheduler_) {
}
void Mailbox::close() {
abandon();
// Block until neither receive() nor push() are in progress. Two mutexes are
// used because receive() must not block send(). Of the two, the receiving
// mutex must be acquired first, because that is the order that an actor
@@ -44,14 +47,37 @@ void Mailbox::close() {
closed = true;
}
void Mailbox::abandon() {
auto idleValue = State::Idle;
while (!state.compare_exchange_strong(idleValue, State::Abandoned)) {
if (state == State::Abandoned) {
break;
}
}
}
bool Mailbox::isOpen() const {
return bool(weakScheduler);
}
void Mailbox::push(std::unique_ptr<Message> message) {
auto idleState = State::Idle;
while (!state.compare_exchange_strong(idleState, State::Processing)) {
if (state == State::Abandoned) {
return;
}
}
Scoped activityFlag{[this]() {
if (state == State::Processing) {
state = State::Idle;
}
}};
std::lock_guard<std::mutex> pushingLock(pushingMutex);
if (closed) {
state = State::Abandoned;
return;
}
@@ -65,12 +91,25 @@ void Mailbox::push(std::unique_ptr<Message> message) {
}
void Mailbox::receive() {
auto idleState = State::Idle;
while (!state.compare_exchange_strong(idleState, State::Processing)) {
if (state == State::Abandoned) {
return;
}
}
Scoped activityFlag{[this]() {
if (state == State::Processing) {
state = State::Idle;
}
}};
std::lock_guard<std::recursive_mutex> receivingLock(receivingMutex);
auto guard = weakScheduler.lock();
assert(weakScheduler);
if (closed) {
state = State::Abandoned;
return;
}

View File

@@ -9,8 +9,9 @@ namespace mbgl {
using namespace style;
RenderAnnotationSource::RenderAnnotationSource(Immutable<AnnotationSource::Impl> impl_)
: RenderTileSource(std::move(impl_)) {
RenderAnnotationSource::RenderAnnotationSource(Immutable<AnnotationSource::Impl> impl_,
std::shared_ptr<Scheduler> threadPool_)
: RenderTileSource(std::move(impl_), std::move(threadPool_)) {
assert(LayerManager::annotationsEnabled);
tilePyramid.setObserver(this);
}

View File

@@ -7,7 +7,7 @@ namespace mbgl {
class RenderAnnotationSource final : public RenderTileSource {
public:
explicit RenderAnnotationSource(Immutable<AnnotationSource::Impl>);
explicit RenderAnnotationSource(Immutable<AnnotationSource::Impl>, std::shared_ptr<Scheduler>);
void update(Immutable<style::Source::Impl>,
const std::vector<Immutable<style::LayerProperties>>&,

View File

@@ -15,6 +15,7 @@
#include <mbgl/util/traits.hpp>
#include <mbgl/util/std.hpp>
#include <mbgl/util/logging.hpp>
#include <mbgl/util/thread_pool.hpp>
#if MLN_DRAWABLE_RENDERER
#include <mbgl/gl/drawable_gl.hpp>
@@ -81,6 +82,8 @@ Context::Context(RendererBackend& backend_)
Context::~Context() noexcept {
if (cleanupOnDestruction) {
Scheduler::GetBackground()->runRenderJobs();
reset();
#if !defined(NDEBUG)
Log::Debug(Event::General, "Rendering Stats:\n" + stats.toString("\n"));
@@ -90,6 +93,8 @@ Context::~Context() noexcept {
}
void Context::beginFrame() {
Scheduler::GetBackground()->runRenderJobs();
#if MLN_DRAWABLE_RENDERER
frameInFlightFence = std::make_shared<gl::Fence>();

View File

@@ -22,6 +22,7 @@
#include <mbgl/util/traits.hpp>
#include <mbgl/util/std.hpp>
#include <mbgl/util/logging.hpp>
#include <mbgl/util/thread_pool.hpp>
#include <Foundation/Foundation.hpp>
#include <Metal/Metal.hpp>
@@ -42,6 +43,7 @@ Context::Context(RendererBackend& backend_)
Context::~Context() noexcept {
if (cleanupOnDestruction) {
Scheduler::GetBackground()->runRenderJobs();
performCleanup();
emptyVertexBuffer.reset();
@@ -60,7 +62,10 @@ Context::~Context() noexcept {
}
}
void Context::beginFrame() {}
void Context::beginFrame() {
Scheduler::GetBackground()->runRenderJobs();
}
void Context::endFrame() {}
std::unique_ptr<gfx::CommandEncoder> Context::createCommandEncoder() {

View File

@@ -21,6 +21,7 @@ void ImageManager::setObserver(ImageManagerObserver* observer_) {
}
void ImageManager::setLoaded(bool loaded_) {
std::lock_guard<std::recursive_mutex> readWriteLock(rwLock);
if (loaded == loaded_) {
return;
}
@@ -31,6 +32,7 @@ void ImageManager::setLoaded(bool loaded_) {
for (const auto& entry : requestors) {
checkMissingAndNotify(*entry.first, entry.second);
}
requestors.clear();
}
}
@@ -40,16 +42,21 @@ bool ImageManager::isLoaded() const {
}
void ImageManager::addImage(Immutable<style::Image::Impl> image_) {
std::lock_guard<std::recursive_mutex> readLock(rwLock);
assert(images.find(image_->id) == images.end());
// Increase cache size if requested image was provided.
if (requestedImages.find(image_->id) != requestedImages.end()) {
requestedImagesCacheSize += image_->image.bytes();
}
availableImages.emplace(image_->id);
images.emplace(image_->id, std::move(image_));
}
bool ImageManager::updateImage(Immutable<style::Image::Impl> image_) {
std::lock_guard<std::recursive_mutex> readWriteLock(rwLock);
auto oldImage = images.find(image_->id);
assert(oldImage != images.end());
if (oldImage == images.end()) return false;
@@ -74,8 +81,10 @@ bool ImageManager::updateImage(Immutable<style::Image::Impl> image_) {
}
void ImageManager::removeImage(const std::string& id) {
std::lock_guard<std::recursive_mutex> readWriteLock(rwLock);
auto it = images.find(id);
assert(it != images.end());
// Reduce cache size for requested images.
auto requestedIt = requestedImages.find(it->second->id);
if (requestedIt != requestedImages.end()) {
@@ -83,12 +92,14 @@ void ImageManager::removeImage(const std::string& id) {
requestedImagesCacheSize -= it->second->image.bytes();
requestedImages.erase(requestedIt);
}
images.erase(it);
availableImages.erase(id);
updatedImageVersions.erase(id);
}
const style::Image::Impl* ImageManager::getImage(const std::string& id) const {
std::lock_guard<std::recursive_mutex> readWriteLock(rwLock);
if (auto* image = getSharedImage(id)) {
return image->get();
}
@@ -96,6 +107,7 @@ const style::Image::Impl* ImageManager::getImage(const std::string& id) const {
}
const Immutable<style::Image::Impl>* ImageManager::getSharedImage(const std::string& id) const {
std::lock_guard<std::recursive_mutex> readWriteLock(rwLock);
const auto it = images.find(id);
if (it != images.end()) {
return &(it->second);
@@ -107,6 +119,8 @@ void ImageManager::getImages(ImageRequestor& requestor, ImageRequestPair&& pair)
// remove previous requests from this tile
removeRequestor(requestor);
std::lock_guard<std::recursive_mutex> readWriteLock(rwLock);
// If all the icon dependencies are already present ((i.e. if they've been addeded via
// runtime styling), then notify the requestor immediately. Otherwise, if the
// sprite has not loaded, then wait for it. When the sprite has loaded check
@@ -133,6 +147,8 @@ void ImageManager::getImages(ImageRequestor& requestor, ImageRequestPair&& pair)
}
void ImageManager::removeRequestor(ImageRequestor& requestor) {
std::lock_guard<std::recursive_mutex> readWriteLock(rwLock);
requestors.erase(&requestor);
missingImageRequestors.erase(&requestor);
for (auto& requestedImage : requestedImages) {
@@ -141,6 +157,8 @@ void ImageManager::removeRequestor(ImageRequestor& requestor) {
}
void ImageManager::notifyIfMissingImageAdded() {
std::lock_guard<std::recursive_mutex> readWriteLock(rwLock);
for (auto it = missingImageRequestors.begin(); it != missingImageRequestors.end();) {
ImageRequestor& requestor = *it->first;
if (!requestor.hasPendingRequests()) {
@@ -153,6 +171,8 @@ void ImageManager::notifyIfMissingImageAdded() {
}
void ImageManager::reduceMemoryUse() {
std::lock_guard<std::recursive_mutex> readLock(rwLock);
std::vector<std::string> unusedIDs;
unusedIDs.reserve(requestedImages.size());
@@ -173,11 +193,18 @@ void ImageManager::reduceMemoryUseIfCacheSizeExceedsLimit() {
}
}
const std::set<std::string>& ImageManager::getAvailableImages() const {
return availableImages;
std::set<std::string> ImageManager::getAvailableImages() const {
std::set<std::string> copy;
{
std::lock_guard<std::recursive_mutex> readWriteLock(rwLock);
copy = availableImages;
}
return copy;
}
void ImageManager::clear() {
std::lock_guard<std::recursive_mutex> readWriteLock(rwLock);
assert(requestors.empty());
assert(missingImageRequestors.empty());
@@ -229,6 +256,7 @@ void ImageManager::checkMissingAndNotify(ImageRequestor& requestor, const ImageR
}
auto removePendingRequests = [this, missingImage] {
std::lock_guard<std::recursive_mutex> readWriteLock(rwLock);
auto existingRequest = requestedImages.find(missingImage);
if (existingRequest == requestedImages.end()) {
return;
@@ -278,11 +306,11 @@ void ImageManager::dumpDebugLogs() const {
Log::Info(Event::General, ss.str());
}
ImageRequestor::ImageRequestor(ImageManager& imageManager_)
: imageManager(imageManager_) {}
ImageRequestor::ImageRequestor(std::shared_ptr<ImageManager> imageManager_)
: imageManager(std::move(imageManager_)) {}
ImageRequestor::~ImageRequestor() {
imageManager.removeRequestor(*this);
imageManager->removeRequestor(*this);
}
} // namespace mbgl

View File

@@ -4,6 +4,7 @@
#include <mbgl/util/immutable.hpp>
#include <map>
#include <mutex>
#include <string>
namespace mbgl {
@@ -48,7 +49,7 @@ public:
void notifyIfMissingImageAdded();
void reduceMemoryUse();
void reduceMemoryUseIfCacheSizeExceedsLimit();
const std::set<std::string>& getAvailableImages() const;
std::set<std::string> getAvailableImages() const;
ImageVersionMap updatedImageVersions;
@@ -57,7 +58,6 @@ public:
private:
void checkMissingAndNotify(ImageRequestor&, const ImageRequestPair&);
void notify(ImageRequestor&, const ImageRequestPair&) const;
void removePattern(const std::string&);
bool loaded = false;
@@ -70,11 +70,13 @@ private:
std::set<std::string> availableImages;
ImageManagerObserver* observer = nullptr;
mutable std::recursive_mutex rwLock;
};
class ImageRequestor {
public:
explicit ImageRequestor(ImageManager&);
explicit ImageRequestor(std::shared_ptr<ImageManager>);
virtual ~ImageRequestor();
virtual void onImagesAvailable(ImageMap icons,
ImageMap patterns,
@@ -87,7 +89,7 @@ public:
void removePendingRequest(const std::string& imageId) { pendingRequests.erase(imageId); }
private:
ImageManager& imageManager;
std::shared_ptr<ImageManager> imageManager;
// Pending requests are image requests that are waiting to be dispatched to the client.
std::set<std::string> pendingRequests;

View File

@@ -121,7 +121,8 @@ RenderOrchestrator::RenderOrchestrator(bool backgroundLayerAsColor_, const std::
sourceImpls(makeMutable<std::vector<Immutable<style::Source::Impl>>>()),
layerImpls(makeMutable<std::vector<Immutable<style::Layer::Impl>>>()),
renderLight(makeMutable<Light::Impl>()),
backgroundLayerAsColor(backgroundLayerAsColor_) {
backgroundLayerAsColor(backgroundLayerAsColor_),
threadPool(Scheduler::GetBackground()) {
glyphManager->setObserver(this);
imageManager->setObserver(this);
}
@@ -137,6 +138,13 @@ RenderOrchestrator::~RenderOrchestrator() {
layer.markContextDestroyed();
}
}
// Wait for any deferred cleanup tasks to complete before releasing and potentially
// destroying the scheduler. Those cleanup tasks must not hold the final reference
// to the scheduler because it cannot be destroyed from one of its own pool threads.
constexpr auto deferredCleanupTimeout = Milliseconds{1000};
[[maybe_unused]] const auto remaining = threadPool->waitForEmpty(deferredCleanupTimeout);
assert(remaining == 0);
}
void RenderOrchestrator::setObserver(RendererObserver* observer_) {
@@ -180,8 +188,8 @@ std::unique_ptr<RenderTree> RenderOrchestrator::createRenderTree(
updateParameters->fileSource,
updateParameters->mode,
updateParameters->annotationManager,
*imageManager,
*glyphManager,
imageManager,
glyphManager,
updateParameters->prefetchZoomDelta};
glyphManager->setURL(updateParameters->glyphURL);
@@ -311,7 +319,7 @@ std::unique_ptr<RenderTree> RenderOrchestrator::createRenderTree(
// Create render sources for newly added sources.
for (const auto& entry : sourceDiff.added) {
std::unique_ptr<RenderSource> renderSource = RenderSource::create(entry.second);
std::unique_ptr<RenderSource> renderSource = RenderSource::create(entry.second, threadPool);
renderSource->setObserver(this);
renderSources.emplace(entry.first, std::move(renderSource));
}

View File

@@ -184,8 +184,8 @@ private:
ZoomHistory zoomHistory;
TransformState transformState;
std::unique_ptr<GlyphManager> glyphManager;
std::unique_ptr<ImageManager> imageManager;
std::shared_ptr<GlyphManager> glyphManager;
std::shared_ptr<ImageManager> imageManager;
std::unique_ptr<LineAtlas> lineAtlas;
std::unique_ptr<PatternAtlas> patternAtlas;
@@ -210,6 +210,8 @@ private:
RenderLayerReferences orderedLayers;
RenderLayerReferences layersNeedPlacement;
std::shared_ptr<Scheduler> threadPool;
#if MLN_DRAWABLE_RENDERER
std::vector<std::unique_ptr<ChangeRequest>> pendingChanges;

View File

@@ -12,28 +12,36 @@
#include <mbgl/layermanager/layer_manager.hpp>
#include <mbgl/util/constants.hpp>
#include <memory>
#include <utility>
namespace mbgl {
using namespace style;
std::unique_ptr<RenderSource> RenderSource::create(const Immutable<Source::Impl>& impl) {
std::unique_ptr<RenderSource> RenderSource::create(const Immutable<Source::Impl>& impl,
std::shared_ptr<Scheduler> threadPool_) {
switch (impl->type) {
case SourceType::Vector:
return std::make_unique<RenderVectorSource>(staticImmutableCast<VectorSource::Impl>(impl));
return std::make_unique<RenderVectorSource>(staticImmutableCast<VectorSource::Impl>(impl),
std::move(threadPool_));
case SourceType::Raster:
return std::make_unique<RenderRasterSource>(staticImmutableCast<RasterSource::Impl>(impl));
return std::make_unique<RenderRasterSource>(staticImmutableCast<RasterSource::Impl>(impl),
std::move(threadPool_));
case SourceType::RasterDEM:
return std::make_unique<RenderRasterDEMSource>(staticImmutableCast<RasterSource::Impl>(impl));
return std::make_unique<RenderRasterDEMSource>(staticImmutableCast<RasterSource::Impl>(impl),
std::move(threadPool_));
case SourceType::GeoJSON:
return std::make_unique<RenderGeoJSONSource>(staticImmutableCast<GeoJSONSource::Impl>(impl));
return std::make_unique<RenderGeoJSONSource>(staticImmutableCast<GeoJSONSource::Impl>(impl),
std::move(threadPool_));
case SourceType::Video:
assert(false);
return nullptr;
case SourceType::Annotations:
if (LayerManager::annotationsEnabled) {
return std::make_unique<RenderAnnotationSource>(staticImmutableCast<AnnotationSource::Impl>(impl));
return std::make_unique<RenderAnnotationSource>(staticImmutableCast<AnnotationSource::Impl>(impl),
std::move(threadPool_));
} else {
assert(false);
return nullptr;
@@ -41,7 +49,8 @@ std::unique_ptr<RenderSource> RenderSource::create(const Immutable<Source::Impl>
case SourceType::Image:
return std::make_unique<RenderImageSource>(staticImmutableCast<ImageSource::Impl>(impl));
case SourceType::CustomVector:
return std::make_unique<RenderCustomGeometrySource>(staticImmutableCast<CustomGeometrySource::Impl>(impl));
return std::make_unique<RenderCustomGeometrySource>(staticImmutableCast<CustomGeometrySource::Impl>(impl),
std::move(threadPool_));
}
// Not reachable, but placate GCC.

View File

@@ -17,20 +17,21 @@
namespace mbgl {
class PaintParameters;
class TransformState;
class RenderTile;
class RenderLayer;
class RenderedQueryOptions;
class SourceQueryOptions;
class Tile;
class RenderSourceObserver;
class TileParameters;
class CollisionIndex;
class TransformParameters;
class ImageManager;
class ImageSourceRenderData;
class PaintParameters;
class RenderedQueryOptions;
class RenderItem;
class RenderLayer;
class RenderSourceObserver;
class RenderTile;
class Scheduler;
class SourceQueryOptions;
class Tile;
class TileParameters;
class TransformParameters;
class TransformState;
namespace gfx {
class UploadPass;
@@ -47,7 +48,7 @@ using RenderTiles = std::shared_ptr<const std::vector<std::reference_wrapper<con
class RenderSource : protected TileObserver {
public:
static std::unique_ptr<RenderSource> create(const Immutable<style::Source::Impl>&);
static std::unique_ptr<RenderSource> create(const Immutable<style::Source::Impl>&, std::shared_ptr<Scheduler>);
~RenderSource() override;
bool isEnabled() const;

View File

@@ -7,8 +7,9 @@ namespace mbgl {
using namespace style;
RenderCustomGeometrySource::RenderCustomGeometrySource(Immutable<style::CustomGeometrySource::Impl> impl_)
: RenderTileSource(std::move(impl_)) {
RenderCustomGeometrySource::RenderCustomGeometrySource(Immutable<style::CustomGeometrySource::Impl> impl_,
std::shared_ptr<Scheduler> threadPool_)
: RenderTileSource(std::move(impl_), std::move(threadPool_)) {
tilePyramid.setObserver(this);
}

View File

@@ -7,7 +7,7 @@ namespace mbgl {
class RenderCustomGeometrySource final : public RenderTileSource {
public:
explicit RenderCustomGeometrySource(Immutable<style::CustomGeometrySource::Impl>);
explicit RenderCustomGeometrySource(Immutable<style::CustomGeometrySource::Impl>, std::shared_ptr<Scheduler>);
void update(Immutable<style::Source::Impl>,
const std::vector<Immutable<style::LayerProperties>>&,

View File

@@ -65,8 +65,9 @@ MAPBOX_ETERNAL_CONSTEXPR const auto extensionGetters =
} // namespace
RenderGeoJSONSource::RenderGeoJSONSource(Immutable<style::GeoJSONSource::Impl> impl_)
: RenderTileSource(std::move(impl_)) {}
RenderGeoJSONSource::RenderGeoJSONSource(Immutable<style::GeoJSONSource::Impl> impl_,
std::shared_ptr<Scheduler> threadPool_)
: RenderTileSource(std::move(impl_), std::move(threadPool_)) {}
RenderGeoJSONSource::~RenderGeoJSONSource() = default;

View File

@@ -11,7 +11,7 @@ class GeoJSONData;
class RenderGeoJSONSource final : public RenderTileSource {
public:
explicit RenderGeoJSONSource(Immutable<style::GeoJSONSource::Impl>);
explicit RenderGeoJSONSource(Immutable<style::GeoJSONSource::Impl>, std::shared_ptr<Scheduler>);
~RenderGeoJSONSource() override;
void update(Immutable<style::Source::Impl>,

View File

@@ -10,8 +10,9 @@ namespace mbgl {
using namespace style;
RenderRasterDEMSource::RenderRasterDEMSource(Immutable<style::RasterSource::Impl> impl_)
: RenderTileSetSource(std::move(impl_)) {}
RenderRasterDEMSource::RenderRasterDEMSource(Immutable<style::RasterSource::Impl> impl_,
std::shared_ptr<Scheduler> threadPool_)
: RenderTileSetSource(std::move(impl_), std::move(threadPool_)) {}
const style::RasterSource::Impl& RenderRasterDEMSource::impl() const {
return static_cast<const style::RasterSource::Impl&>(*baseImpl);

View File

@@ -7,7 +7,7 @@ namespace mbgl {
class RenderRasterDEMSource final : public RenderTileSetSource {
public:
explicit RenderRasterDEMSource(Immutable<style::RasterSource::Impl>);
explicit RenderRasterDEMSource(Immutable<style::RasterSource::Impl>, std::shared_ptr<Scheduler>);
std::unordered_map<std::string, std::vector<Feature>> queryRenderedFeatures(
const ScreenLineString& geometry,

View File

@@ -8,8 +8,9 @@ namespace mbgl {
using namespace style;
RenderRasterSource::RenderRasterSource(Immutable<style::RasterSource::Impl> impl_)
: RenderTileSetSource(std::move(impl_)) {}
RenderRasterSource::RenderRasterSource(Immutable<style::RasterSource::Impl> impl_,
std::shared_ptr<Scheduler> threadPool_)
: RenderTileSetSource(std::move(impl_), std::move(threadPool_)) {}
inline const style::RasterSource::Impl& RenderRasterSource::impl() const {
return static_cast<const style::RasterSource::Impl&>(*baseImpl);

View File

@@ -7,7 +7,7 @@ namespace mbgl {
class RenderRasterSource final : public RenderTileSetSource {
public:
explicit RenderRasterSource(Immutable<style::RasterSource::Impl>);
explicit RenderRasterSource(Immutable<style::RasterSource::Impl>, std::shared_ptr<Scheduler>);
private:
void prepare(const SourcePrepareParameters&) final;

View File

@@ -369,8 +369,9 @@ void TileSourceRenderItem::updateDebugDrawables(DebugLayerGroupMap& debugLayerGr
}
#endif
RenderTileSource::RenderTileSource(Immutable<style::Source::Impl> impl_)
RenderTileSource::RenderTileSource(Immutable<style::Source::Impl> impl_, std::shared_ptr<Scheduler> threadPool_)
: RenderSource(std::move(impl_)),
tilePyramid(std::move(threadPool_)),
renderTiles(makeMutable<std::vector<RenderTile>>()) {
tilePyramid.setObserver(this);
}
@@ -489,8 +490,8 @@ void RenderTileSource::dumpDebugLogs() const {
// RenderTileSetSource implementation
RenderTileSetSource::RenderTileSetSource(Immutable<style::Source::Impl> impl_)
: RenderTileSource(std::move(impl_)) {}
RenderTileSetSource::RenderTileSetSource(Immutable<style::Source::Impl> impl_, std::shared_ptr<Scheduler> threadPool_)
: RenderTileSource(std::move(impl_), std::move(threadPool_)) {}
RenderTileSetSource::~RenderTileSetSource() = default;

View File

@@ -51,7 +51,7 @@ public:
void dumpDebugLogs() const override;
protected:
RenderTileSource(Immutable<style::Source::Impl>);
RenderTileSource(Immutable<style::Source::Impl>, std::shared_ptr<Scheduler>);
TilePyramid tilePyramid;
Immutable<std::vector<RenderTile>> renderTiles;
mutable RenderTiles filteredRenderTiles;
@@ -67,7 +67,7 @@ private:
*/
class RenderTileSetSource : public RenderTileSource {
protected:
RenderTileSetSource(Immutable<style::Source::Impl>);
RenderTileSetSource(Immutable<style::Source::Impl>, std::shared_ptr<Scheduler>);
~RenderTileSetSource() override;
virtual void updateInternal(const Tileset&,

View File

@@ -8,8 +8,9 @@ namespace mbgl {
using namespace style;
RenderVectorSource::RenderVectorSource(Immutable<style::VectorSource::Impl> impl_)
: RenderTileSetSource(std::move(impl_)) {}
RenderVectorSource::RenderVectorSource(Immutable<style::VectorSource::Impl> impl_,
std::shared_ptr<Scheduler> threadPool_)
: RenderTileSetSource(std::move(impl_), std::move(threadPool_)) {}
const std::optional<Tileset>& RenderVectorSource::getTileset() const {
return static_cast<const style::VectorSource::Impl&>(*baseImpl).tileset;

View File

@@ -8,7 +8,7 @@ namespace mbgl {
class RenderVectorSource final : public RenderTileSetSource {
public:
explicit RenderVectorSource(Immutable<style::VectorSource::Impl>);
explicit RenderVectorSource(Immutable<style::VectorSource::Impl>, std::shared_ptr<Scheduler>);
private:
void updateInternal(const Tileset&,

View File

@@ -22,8 +22,8 @@ public:
std::shared_ptr<FileSource> fileSource;
const MapMode mode;
mapbox::base::WeakPtr<AnnotationManager> annotationManager;
ImageManager& imageManager;
GlyphManager& glyphManager;
std::shared_ptr<ImageManager> imageManager;
std::shared_ptr<GlyphManager> glyphManager;
const uint8_t prefetchZoomDelta;
};

View File

@@ -5,6 +5,7 @@
#include <mbgl/renderer/query.hpp>
#include <mbgl/map/transform.hpp>
#include <mbgl/math/clamp.hpp>
#include <mbgl/actor/scheduler.hpp>
#include <mbgl/util/tile_cover.hpp>
#include <mbgl/util/tile_range.hpp>
#include <mbgl/util/enum.hpp>
@@ -23,8 +24,9 @@ using namespace style;
static TileObserver nullObserver;
TilePyramid::TilePyramid()
: observer(&nullObserver) {}
TilePyramid::TilePyramid(std::shared_ptr<Scheduler> threadPool_)
: cache(std::move(threadPool_)),
observer(&nullObserver) {}
TilePyramid::~TilePyramid() = default;
@@ -65,13 +67,15 @@ void TilePyramid::update(const std::vector<Immutable<style::LayerProperties>>& l
// If we're not going to render anything, move our existing tiles into
// the cache (if they're not stale) or abandon them, and return.
if (!needsRendering) {
if (!needsRelayout) {
for (auto& entry : tiles) {
for (auto& entry : tiles) {
if (!needsRelayout) {
// These tiles are invisible, we set optional necessity
// for them and thus suppress network requests on
// tiles expiration (see `OnlineFileRequest`).
entry.second->setNecessity(TileNecessity::Optional);
cache.add(entry.first, std::move(entry.second));
} else {
cache.deferredRelease(std::move(entry.second));
}
}
@@ -227,11 +231,17 @@ void TilePyramid::update(const std::vector<Immutable<style::LayerProperties>>& l
auto retainIt = retain.begin();
while (tilesIt != tiles.end()) {
if (retainIt == retain.end() || tilesIt->first < *retainIt) {
if (!needsRelayout) {
tilesIt->second->setNecessity(TileNecessity::Optional);
cache.add(tilesIt->first, std::move(tilesIt->second));
// Remove the tile from the map.
// If it requires re-layout, discard it asynchronously, otherwise keep it in the cache
const auto key = tilesIt->first;
if (std::unique_ptr<Tile> tile = std::move(tiles.extract(tilesIt++).mapped())) {
if (needsRelayout) {
cache.deferredRelease(std::move(tile));
} else {
tile->setNecessity(TileNecessity::Optional);
cache.add(key, std::move(tile));
}
}
tiles.erase(tilesIt++);
} else {
if (!(*retainIt < tilesIt->first)) {
++tilesIt;

View File

@@ -29,7 +29,7 @@ class SourcePrepareParameters;
class TilePyramid {
public:
TilePyramid();
TilePyramid(std::shared_ptr<Scheduler> threadPool_);
~TilePyramid();
bool isLoaded() const;

View File

@@ -21,33 +21,36 @@ GlyphManager::~GlyphManager() = default;
void GlyphManager::getGlyphs(GlyphRequestor& requestor, GlyphDependencies glyphDependencies, FileSource& fileSource) {
auto dependencies = std::make_shared<GlyphDependencies>(std::move(glyphDependencies));
// Figure out which glyph ranges need to be fetched. For each range that
// does need to be fetched, record an entry mapping the requestor to a
// shared pointer containing the dependencies. When the shared pointer
// becomes unique, we know that all the dependencies for that requestor have
// been fetched, and can notify it of completion.
for (const auto& dependency : *dependencies) {
const FontStack& fontStack = dependency.first;
Entry& entry = entries[fontStack];
{
std::lock_guard<std::recursive_mutex> readWriteLock(rwLock);
// Figure out which glyph ranges need to be fetched. For each range that
// does need to be fetched, record an entry mapping the requestor to a
// shared pointer containing the dependencies. When the shared pointer
// becomes unique, we know that all the dependencies for that requestor have
// been fetched, and can notify it of completion.
for (const auto& dependency : *dependencies) {
const FontStack& fontStack = dependency.first;
Entry& entry = entries[fontStack];
const GlyphIDs& glyphIDs = dependency.second;
std::unordered_set<GlyphRange> ranges;
for (const auto& glyphID : glyphIDs) {
if (localGlyphRasterizer->canRasterizeGlyph(fontStack, glyphID)) {
if (entry.glyphs.find(glyphID) == entry.glyphs.end()) {
entry.glyphs.emplace(glyphID, makeMutable<Glyph>(generateLocalSDF(fontStack, glyphID)));
const GlyphIDs& glyphIDs = dependency.second;
std::unordered_set<GlyphRange> ranges;
for (const auto& glyphID : glyphIDs) {
if (localGlyphRasterizer->canRasterizeGlyph(fontStack, glyphID)) {
if (entry.glyphs.find(glyphID) == entry.glyphs.end()) {
entry.glyphs.emplace(glyphID, makeMutable<Glyph>(generateLocalSDF(fontStack, glyphID)));
}
} else {
ranges.insert(getGlyphRange(glyphID));
}
} else {
ranges.insert(getGlyphRange(glyphID));
}
}
for (const auto& range : ranges) {
auto it = entry.ranges.find(range);
if (it == entry.ranges.end() || !it->second.parsed) {
GlyphRequest& request = entry.ranges[range];
request.requestors[&requestor] = dependencies;
requestRange(request, fontStack, range, fileSource);
for (const auto& range : ranges) {
auto it = entry.ranges.find(range);
if (it == entry.ranges.end() || !it->second.parsed) {
GlyphRequest& request = entry.ranges[range];
request.requestors[&requestor] = dependencies;
requestRange(request, fontStack, range, fileSource);
}
}
}
}
@@ -88,39 +91,43 @@ void GlyphManager::processResponse(const Response& res, const FontStack& fontSta
return;
}
Entry& entry = entries[fontStack];
GlyphRequest& request = entry.ranges[range];
{
std::lock_guard<std::recursive_mutex> readWriteLock(rwLock);
if (!res.noContent) {
std::vector<Glyph> glyphs;
Entry& entry = entries[fontStack];
GlyphRequest& request = entry.ranges[range];
try {
glyphs = parseGlyphPBF(range, *res.data);
} catch (...) {
observer->onGlyphsError(fontStack, range, std::current_exception());
return;
}
if (!res.noContent) {
std::vector<Glyph> glyphs;
for (auto& glyph : glyphs) {
auto id = glyph.id;
if (!localGlyphRasterizer->canRasterizeGlyph(fontStack, id)) {
entry.glyphs.erase(id);
entry.glyphs.emplace(id, makeMutable<Glyph>(std::move(glyph)));
try {
glyphs = parseGlyphPBF(range, *res.data);
} catch (...) {
observer->onGlyphsError(fontStack, range, std::current_exception());
return;
}
for (auto& glyph : glyphs) {
auto id = glyph.id;
if (!localGlyphRasterizer->canRasterizeGlyph(fontStack, id)) {
entry.glyphs.erase(id);
entry.glyphs.emplace(id, makeMutable<Glyph>(std::move(glyph)));
}
}
}
}
request.parsed = true;
request.parsed = true;
for (auto& pair : request.requestors) {
GlyphRequestor& requestor = *pair.first;
const std::shared_ptr<GlyphDependencies>& dependencies = pair.second;
if (dependencies.unique()) {
notify(requestor, *dependencies);
for (auto& pair : request.requestors) {
GlyphRequestor& requestor = *pair.first;
const std::shared_ptr<GlyphDependencies>& dependencies = pair.second;
if (dependencies.unique()) {
notify(requestor, *dependencies);
}
}
}
request.requestors.clear();
request.requestors.clear();
}
observer->onGlyphsLoaded(fontStack, range);
}
@@ -153,6 +160,7 @@ void GlyphManager::notify(GlyphRequestor& requestor, const GlyphDependencies& gl
}
void GlyphManager::removeRequestor(GlyphRequestor& requestor) {
std::lock_guard<std::recursive_mutex> readWriteLock(rwLock);
for (auto& entry : entries) {
for (auto& range : entry.second.ranges) {
range.second.requestors.erase(&requestor);
@@ -161,6 +169,7 @@ void GlyphManager::removeRequestor(GlyphRequestor& requestor) {
}
void GlyphManager::evict(const std::set<FontStack>& keep) {
std::lock_guard<std::recursive_mutex> readWriteLock(rwLock);
util::erase_if(entries, [&](const auto& entry) { return keep.count(entry.first) == 0; });
}

View File

@@ -7,6 +7,7 @@
#include <mbgl/util/font_stack.hpp>
#include <mbgl/util/immutable.hpp>
#include <mutex>
#include <string>
#include <unordered_map>
@@ -71,6 +72,8 @@ private:
GlyphManagerObserver* observer = nullptr;
std::unique_ptr<LocalGlyphRasterizer> localGlyphRasterizer;
std::recursive_mutex rwLock;
};
} // namespace mbgl

View File

@@ -20,6 +20,7 @@
#include <mbgl/tile/geometry_tile_worker.hpp>
#include <mbgl/tile/tile_observer.hpp>
#include <mbgl/util/logging.hpp>
#include <mbgl/util/thread_pool.hpp>
#include <mbgl/gfx/upload_pass.hpp>
#include <utility>
@@ -159,8 +160,9 @@ GeometryTile::GeometryTile(const OverscaledTileID& id_, std::string sourceID_, c
: Tile(Kind::Geometry, id_),
ImageRequestor(parameters.imageManager),
sourceID(std::move(sourceID_)),
threadPool(Scheduler::GetBackground()),
mailbox(std::make_shared<Mailbox>(*Scheduler::GetCurrent())),
worker(Scheduler::GetBackground(),
worker(threadPool,
ActorRef<GeometryTile>(*this, mailbox),
id_,
sourceID,
@@ -175,8 +177,15 @@ GeometryTile::GeometryTile(const OverscaledTileID& id_, std::string sourceID_, c
showCollisionBoxes(parameters.debugOptions & MapDebugOptions::Collision) {}
GeometryTile::~GeometryTile() {
glyphManager.removeRequestor(*this);
markObsolete();
glyphManager->removeRequestor(*this);
imageManager->removeRequestor(*this);
if (layoutResult) {
threadPool->runOnRenderThread(
[layoutResult_{std::move(layoutResult)}, atlasTextures_{std::move(atlasTextures)}]() {});
}
}
void GeometryTile::cancel() {
@@ -185,6 +194,7 @@ void GeometryTile::cancel() {
void GeometryTile::markObsolete() {
obsolete = true;
mailbox->abandon();
}
void GeometryTile::setError(std::exception_ptr err) {
@@ -193,13 +203,17 @@ void GeometryTile::setError(std::exception_ptr err) {
}
void GeometryTile::setData(std::unique_ptr<const GeometryTileData> data_) {
if (obsolete) {
return;
}
// Mark the tile as pending again if it was complete before to prevent
// signaling a complete state despite pending parse operations.
pending = true;
++correlationID;
worker.self().invoke(
&GeometryTileWorker::setData, std::move(data_), imageManager.getAvailableImages(), correlationID);
&GeometryTileWorker::setData, std::move(data_), imageManager->getAvailableImages(), correlationID);
}
void GeometryTile::reset() {
@@ -238,7 +252,7 @@ void GeometryTile::setLayers(const std::vector<Immutable<LayerProperties>>& laye
++correlationID;
worker.self().invoke(
&GeometryTileWorker::setLayers, std::move(impls), imageManager.getAvailableImages(), correlationID);
&GeometryTileWorker::setLayers, std::move(impls), imageManager->getAvailableImages(), correlationID);
}
void GeometryTile::setShowCollisionBoxes(const bool showCollisionBoxes_) {
@@ -278,7 +292,7 @@ void GeometryTile::onGlyphsAvailable(GlyphMap glyphs) {
void GeometryTile::getGlyphs(GlyphDependencies glyphDependencies) {
if (fileSource) {
glyphManager.getGlyphs(*this, std::move(glyphDependencies), *fileSource);
glyphManager->getGlyphs(*this, std::move(glyphDependencies), *fileSource);
}
}
@@ -294,7 +308,7 @@ void GeometryTile::onImagesAvailable(ImageMap images,
}
void GeometryTile::getImages(ImageRequestPair pair) {
imageManager.getImages(*this, std::move(pair));
imageManager->getImages(*this, std::move(pair));
}
std::shared_ptr<FeatureIndex> GeometryTile::getFeatureIndex() const {

View File

@@ -106,12 +106,14 @@ private:
// Used to signal the worker that it should abandon parsing this tile as soon as possible.
std::atomic<bool> obsolete{false};
std::shared_ptr<Mailbox> mailbox;
const std::shared_ptr<Scheduler> threadPool;
const std::shared_ptr<Mailbox> mailbox;
Actor<GeometryTileWorker> worker;
std::shared_ptr<FileSource> fileSource;
GlyphManager& glyphManager;
ImageManager& imageManager;
const std::shared_ptr<FileSource> fileSource;
const std::shared_ptr<GlyphManager> glyphManager;
const std::shared_ptr<ImageManager> imageManager;
uint64_t correlationID = 0;

View File

@@ -19,6 +19,7 @@
#include <mbgl/util/string.hpp>
#include <mbgl/util/exception.hpp>
#include <mbgl/util/stopwatch.hpp>
#include <mbgl/util/thread_pool.hpp>
#include <unordered_set>
#include <utility>
@@ -44,7 +45,9 @@ GeometryTileWorker::GeometryTileWorker(ActorRef<GeometryTileWorker> self_,
pixelRatio(pixelRatio_),
showCollisionBoxes(showCollisionBoxes_) {}
GeometryTileWorker::~GeometryTileWorker() = default;
GeometryTileWorker::~GeometryTileWorker() {
Scheduler::GetBackground()->runOnRenderThread([renderData_{std::move(renderData)}]() {});
}
/*
GeometryTileWorker is a state machine. This is its transition diagram.

View File

@@ -76,7 +76,7 @@ public:
// render data with the given properties.
//
// Returns `true` if the corresponding render layer data is present in this
// tile (and i.e. it was succesfully updated); returns `false` otherwise.
// tile (and i.e. it was successfully updated); returns `false` otherwise.
virtual bool layerPropertiesUpdated(const Immutable<style::LayerProperties>& layerProperties) = 0;
virtual void setShowCollisionBoxes(const bool) {}
virtual void setLayers(const std::vector<Immutable<style::LayerProperties>>&) {}

View File

@@ -1,4 +1,5 @@
#include <mbgl/tile/tile_cache.hpp>
#include <mbgl/actor/scheduler.hpp>
#include <cassert>
namespace mbgl {
@@ -7,23 +8,66 @@ void TileCache::setSize(size_t size_) {
size = size_;
while (orderedKeys.size() > size) {
auto key = orderedKeys.front();
const auto key = orderedKeys.front();
orderedKeys.remove(key);
tiles.erase(key);
auto hit = tiles.find(key);
if (hit != tiles.end()) {
auto tile = std::move(hit->second);
tiles.erase(hit);
deferredRelease(std::move(tile));
}
}
assert(orderedKeys.size() <= size);
}
void TileCache::add(const OverscaledTileID& key, std::unique_ptr<Tile> tile) {
namespace {
/// This exists solely to prevent a problem where temporary lambda captures
/// are retained for the duration of the scope instead of being destroyed immediately.
template <typename T>
struct CaptureWrapper {
CaptureWrapper(std::unique_ptr<T>&& item_)
: item(std::move(item_)) {}
CaptureWrapper(const CaptureWrapper& other)
: item(other.item) {}
std::shared_ptr<T> item;
};
} // namespace
void TileCache::deferredRelease(std::unique_ptr<Tile>&& tile) {
tile->cancel();
// The `std::function` must be created in a separate statement from the `schedule` call.
// Creating a `std::function` from a lambda involves a copy, which is why we must use
// `shared_ptr` rather than `unique_ptr` for the capture. As a result, a temporary holds
// a reference until the construction is complete and the lambda is destroyed.
// If this temporary outlives the `schedule` call, and the function is executed immediately
// by a waiting thread and is already complete, that temporary reference ends up being the
// last one and the destruction actually occurs here on this thread.
std::function<void()> func{[tile_{CaptureWrapper<Tile>{std::move(tile)}}]() {
}};
threadPool->schedule(std::move(func));
}
void TileCache::add(const OverscaledTileID& key, std::unique_ptr<Tile>&& tile) {
if (!tile->isRenderable() || !size) {
deferredRelease(std::move(tile));
return;
}
// insert new or query existing tile
if (!tiles.emplace(key, std::move(tile)).second) {
// remove existing tile key
const auto result = tiles.insert(std::make_pair(key, std::unique_ptr<Tile>{}));
if (result.second) {
// inserted
result.first->second = std::move(tile);
} else {
// already present
// remove existing tile key to move it to the end
orderedKeys.remove(key);
// release the newly-provided item
deferredRelease(std::move(tile));
}
// (re-)insert tile key as newest
@@ -31,7 +75,7 @@ void TileCache::add(const OverscaledTileID& key, std::unique_ptr<Tile> tile) {
// purge oldest key/tile if necessary
if (orderedKeys.size() > size) {
pop(orderedKeys.front());
deferredRelease(pop(orderedKeys.front()));
}
assert(orderedKeys.size() <= size);
@@ -49,10 +93,9 @@ Tile* TileCache::get(const OverscaledTileID& key) {
std::unique_ptr<Tile> TileCache::pop(const OverscaledTileID& key) {
std::unique_ptr<Tile> tile;
auto it = tiles.find(key);
const auto it = tiles.find(key);
if (it != tiles.end()) {
tile = std::move(it->second);
tiles.erase(it);
tile = std::move(tiles.extract(it).mapped());
orderedKeys.remove(key);
assert(tile->isRenderable());
}
@@ -65,6 +108,9 @@ bool TileCache::has(const OverscaledTileID& key) {
}
void TileCache::clear() {
for (auto& item : tiles) {
deferredRelease(std::move(item.second));
}
orderedKeys.clear();
tiles.clear();
}

View File

@@ -9,22 +9,36 @@
namespace mbgl {
class Scheduler;
class TileCache {
public:
TileCache(size_t size_ = 0)
: size(size_) {}
TileCache(std::shared_ptr<Scheduler> threadPool_, size_t size_ = 0)
: threadPool(std::move(threadPool_)),
size(size_) {}
/// Change the maximum size of the cache.
void setSize(size_t);
size_t getSize() const { return size; };
void add(const OverscaledTileID& key, std::unique_ptr<Tile> tile);
/// Get the maximum size
size_t getMaxSize() const { return size; }
/// Add a new tile with the given ID.
/// If a tile with the same ID is already present, it will be retained and the new one will be discarded.
void add(const OverscaledTileID& key, std::unique_ptr<Tile>&& tile);
std::unique_ptr<Tile> pop(const OverscaledTileID& key);
Tile* get(const OverscaledTileID& key);
bool has(const OverscaledTileID& key);
void clear();
/// Destroy a tile without blocking
void deferredRelease(std::unique_ptr<Tile>&&);
private:
std::map<OverscaledTileID, std::unique_ptr<Tile>> tiles;
std::list<OverscaledTileID> orderedKeys;
std::shared_ptr<Scheduler> threadPool;
size_t size;
};

View File

@@ -3,6 +3,10 @@
#include <mbgl/storage/resource.hpp>
#include <mbgl/tile/tile.hpp>
#include <atomic>
#include <mutex>
#include <shared_mutex>
namespace mbgl {
class FileSource;
@@ -48,6 +52,19 @@ private:
std::shared_ptr<FileSource> fileSource;
std::unique_ptr<AsyncRequest> request;
TileUpdateParameters updateParameters{Duration::zero(), false};
/// @brief It's possible for async requests in flight to mess with the request
/// object at the same time as the loader's destructor. This construct is shared
/// with the request lambdas to ensure more tightly controlled synchronization
/// to prevent this from happening.
struct Shared {
std::shared_mutex requestLock;
std::atomic_bool aborted{false};
};
// Allocated as a share_ptr so either the loader or request can outlive the
// other and still see this.
std::shared_ptr<Shared> shared;
};
} // namespace mbgl

View File

@@ -30,6 +30,9 @@ TileLoader<T>::TileLoader(T& tile_,
Resource::LoadingMethod::CacheOnly)),
fileSource(parameters.fileSource) {
assert(!request);
shared = std::make_shared<Shared>();
if (!fileSource) {
tile.setError(getCantLoadTileError());
return;
@@ -55,7 +58,12 @@ TileLoader<T>::TileLoader(T& tile_,
}
template <typename T>
TileLoader<T>::~TileLoader() = default;
TileLoader<T>::~TileLoader() {
std::unique_lock<std::shared_mutex> lock(shared->requestLock);
shared->aborted = true;
tile.cancel();
request.reset();
};
template <typename T>
void TileLoader<T>::setNecessity(TileNecessity newNecessity) {
@@ -90,29 +98,36 @@ void TileLoader<T>::loadFromCache() {
}
resource.loadingMethod = Resource::LoadingMethod::CacheOnly;
request = fileSource->request(resource, [this](const Response& res) {
request.reset();
request = fileSource->request(resource, [this, shared_{shared}](const Response& res) {
do {
if (shared_->requestLock.try_lock_shared()) {
std::shared_lock<std::shared_mutex> lock(shared_->requestLock, std::adopt_lock);
if (shared_->aborted) return;
tile.setTriedCache();
request.reset();
tile.setTriedCache();
if (res.error && res.error->reason == Response::Error::Reason::NotFound) {
// When the cache-only request could not be satisfied, don't treat
// it as an error. A cache lookup could still return data, _and_ an
// error, in particular when we were able to find the data, but it
// is expired and the Cache-Control headers indicated that we aren't
// allowed to use expired responses. In this case, we still get the
// data which we can use in our conditional network request.
resource.priorModified = res.modified;
resource.priorExpires = res.expires;
resource.priorEtag = res.etag;
resource.priorData = res.data;
} else {
loadedData(res);
}
if (res.error && res.error->reason == Response::Error::Reason::NotFound) {
// When the cache-only request could not be satisfied, don't treat
// it as an error. A cache lookup could still return data, _and_ an
// error, in particular when we were able to find the data, but it
// is expired and the Cache-Control headers indicated that we aren't
// allowed to use expired responses. In this case, we still get the
// data which we can use in our conditional network request.
resource.priorModified = res.modified;
resource.priorExpires = res.expires;
resource.priorEtag = res.etag;
resource.priorData = res.data;
} else {
loadedData(res);
}
if (necessity == TileNecessity::Required) {
loadFromNetwork();
}
if (necessity == TileNecessity::Required) {
loadFromNetwork();
}
break;
}
} while (!shared_->aborted);
});
}
@@ -164,7 +179,19 @@ void TileLoader<T>::loadFromNetwork() {
resource.minimumUpdateInterval = updateParameters.minimumUpdateInterval;
resource.storagePolicy = updateParameters.isVolatile ? Resource::StoragePolicy::Volatile
: Resource::StoragePolicy::Permanent;
request = fileSource->request(resource, [this](const Response& res) { loadedData(res); });
request = fileSource->request(resource, [this, shared_{shared}](const Response& res) {
do {
if (shared_->requestLock.try_lock_shared()) {
std::shared_lock<std::shared_mutex> lock(shared_->requestLock, std::adopt_lock);
if (shared_->aborted) return;
request.reset();
loadedData(res);
break;
}
} while (!shared_->aborted);
});
}
} // namespace mbgl

View File

@@ -11,7 +11,7 @@ protected:
ThreadLocalBase();
~ThreadLocalBase();
void* get();
void* get() const;
void set(void*);
private:
@@ -27,7 +27,7 @@ public:
ThreadLocal(T* val) { set(val); }
T* get() { return reinterpret_cast<T*>(impl::ThreadLocalBase::get()); }
T* get() const { return reinterpret_cast<T*>(impl::ThreadLocalBase::get()); }
void set(T* ptr) { impl::ThreadLocalBase::set(ptr); }
};

View File

@@ -2,6 +2,7 @@
#include <mbgl/platform/settings.hpp>
#include <mbgl/platform/thread.hpp>
#include <mbgl/util/monotonic_timer.hpp>
#include <mbgl/util/platform.hpp>
#include <mbgl/util/string.hpp>
@@ -10,11 +11,16 @@ namespace mbgl {
ThreadedSchedulerBase::~ThreadedSchedulerBase() = default;
void ThreadedSchedulerBase::terminate() {
// Run any leftover render jobs
runRenderJobs();
{
std::lock_guard<std::mutex> lock(mutex);
terminated = true;
}
cv.notify_all();
// Wake up all threads so that they shut down
cvAvailable.notify_all();
}
std::thread ThreadedSchedulerBase::makeSchedulerThread(size_t index) {
@@ -25,13 +31,18 @@ std::thread ThreadedSchedulerBase::makeSchedulerThread(size_t index) {
platform::setCurrentThreadPriority(*priority);
}
platform::setCurrentThreadName(std::string{"Worker "} + util::toString(index + 1));
platform::setCurrentThreadName("Worker " + util::toString(index + 1));
platform::attachThread();
owningThreadPool.set(this);
while (true) {
std::unique_lock<std::mutex> lock(mutex);
if (queue.empty() && !pendingItems) {
cvEmpty.notify_all();
}
cv.wait(lock, [this] { return !queue.empty() || terminated; });
cvAvailable.wait(lock, [this] { return !queue.empty() || terminated; });
if (terminated) {
platform::detachThread();
@@ -40,20 +51,83 @@ std::thread ThreadedSchedulerBase::makeSchedulerThread(size_t index) {
auto function = std::move(queue.front());
queue.pop();
if (function) {
pendingItems++;
}
lock.unlock();
if (function) function();
if (function) {
const auto cleanup = [&] {
// destroy the function and release its captures before unblocking `waitForEmpty`
function = {};
pendingItems--;
if (queue.empty() && !pendingItems) {
cvEmpty.notify_all();
}
};
try {
function();
cleanup();
} catch (...) {
lock.lock();
if (handler) {
handler(std::current_exception());
}
cleanup();
if (handler) {
continue;
}
throw;
}
}
}
});
}
void ThreadedSchedulerBase::schedule(std::function<void()> fn) {
void ThreadedSchedulerBase::schedule(std::function<void()>&& fn) {
assert(fn);
{
std::lock_guard<std::mutex> lock(mutex);
queue.push(std::move(fn));
if (fn) {
{
// We need to block if adding adding a new task from a thread not controlled by this
// pool. Tasks are added by other tasks, so we must not block a thread we do control
// or `waitForEmpty` will deadlock.
std::unique_lock<std::mutex> addLock(addMutex, std::defer_lock);
if (!thisThreadIsOwned()) {
addLock.lock();
}
std::lock_guard<std::mutex> lock(mutex);
queue.push(std::move(fn));
}
cvAvailable.notify_one();
}
}
cv.notify_one();
std::size_t ThreadedSchedulerBase::waitForEmpty(Milliseconds timeout) {
// Must not be called from a thread in our pool, or we would deadlock
assert(!thisThreadIsOwned());
if (!thisThreadIsOwned()) {
const auto startTime = util::MonotonicTimer::now();
const auto isDone = [&] {
return queue.empty() && pendingItems == 0;
};
// Block any other threads from adding new items
std::scoped_lock<std::mutex> addLock(addMutex);
std::unique_lock<std::mutex> lock(mutex);
while (!isDone()) {
if (timeout > Milliseconds::zero()) {
const auto elapsed = util::MonotonicTimer::now() - startTime;
if (timeout <= elapsed || !cvEmpty.wait_for(lock, timeout - elapsed, isDone)) {
break;
}
} else {
cvEmpty.wait(lock, isDone);
}
}
return queue.size() + pendingItems;
}
return 0;
}
} // namespace mbgl

View File

@@ -2,18 +2,20 @@
#include <mbgl/actor/mailbox.hpp>
#include <mbgl/actor/scheduler.hpp>
#include <mbgl/util/thread_local.hpp>
#include <array>
#include <algorithm>
#include <condition_variable>
#include <mutex>
#include <queue>
#include <thread>
#include <vector>
namespace mbgl {
class ThreadedSchedulerBase : public Scheduler {
public:
void schedule(std::function<void()>) override;
void schedule(std::function<void()>&&) override;
protected:
ThreadedSchedulerBase() = default;
@@ -22,9 +24,27 @@ protected:
void terminate();
std::thread makeSchedulerThread(size_t index);
/// Wait until there's nothing pending or in process
/// Must not be called from a task provided to this scheduler.
/// @param timeout Time to wait, or zero to wait forever.
std::size_t waitForEmpty(Milliseconds timeout) override;
/// Returns true if called from a thread managed by the scheduler
bool thisThreadIsOwned() const { return owningThreadPool.get() == this; }
std::queue<std::function<void()>> queue;
// protects `queue`
std::mutex mutex;
std::condition_variable cv;
// Used to block addition of new items while waiting
std::mutex addMutex;
// Signal when an item is added to the queue
std::condition_variable cvAvailable;
// Signal when the queue becomes empty
std::condition_variable cvEmpty;
// Count of functions removed from the queue but still executing
std::atomic<std::size_t> pendingItems{0};
// Points to the owning pool in owned threads
util::ThreadLocal<ThreadedSchedulerBase> owningThreadPool;
bool terminated{false};
};
@@ -36,16 +56,17 @@ protected:
* Note: If N == 1 all scheduled tasks are guaranteed to execute consequently;
* otherwise, some of the scheduled tasks might be executed in parallel.
*/
template <std::size_t N>
class ThreadedScheduler : public ThreadedSchedulerBase {
public:
ThreadedScheduler() {
for (std::size_t i = 0u; i < N; ++i) {
ThreadedScheduler(std::size_t n)
: threads(n) {
for (std::size_t i = 0u; i < threads.size(); ++i) {
threads[i] = makeSchedulerThread(i);
}
}
~ThreadedScheduler() override {
assert(!thisThreadIsOwned());
terminate();
for (auto& thread : threads) {
assert(std::this_thread::get_id() != thread.get_id());
@@ -53,19 +74,48 @@ public:
}
}
void runOnRenderThread(std::function<void()>&& fn) override {
std::lock_guard<std::mutex> lock(renderMutex);
renderThreadQueue.push(std::move(fn));
}
void runRenderJobs() override {
std::lock_guard<std::mutex> lock(renderMutex);
while (renderThreadQueue.size()) {
auto fn = std::move(renderThreadQueue.front());
renderThreadQueue.pop();
if (fn) {
fn();
}
}
}
mapbox::base::WeakPtr<Scheduler> makeWeakPtr() override { return weakFactory.makeWeakPtr(); }
private:
std::array<std::thread, N> threads;
std::vector<std::thread> threads;
mapbox::base::WeakPtrFactory<Scheduler> weakFactory{this};
static_assert(N > 0, "Thread count must be more than zero.");
std::queue<std::function<void()>> renderThreadQueue;
std::mutex renderMutex;
};
class SequencedScheduler : public ThreadedScheduler<1> {};
class SequencedScheduler : public ThreadedScheduler {
public:
SequencedScheduler()
: ThreadedScheduler(1) {}
};
template <std::size_t extra>
using ParallelScheduler = ThreadedScheduler<1 + extra>;
class ParallelScheduler : public ThreadedScheduler {
public:
ParallelScheduler(std::size_t extra)
: ThreadedScheduler(1 + extra) {}
};
class ThreadPool : public ParallelScheduler<3> {};
class ThreadPool : public ParallelScheduler {
public:
ThreadPool()
: ParallelScheduler(3) {}
};
} // namespace mbgl