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 | |
parent | 0bd66d40ddf9e75f860fe18e7c80de9c840f48ac (diff) | |
download | qtlocation-mapboxgl-41bbd4e4f7d66465433e370ca024ab0239fcace3.tar.gz |
[core] Use an actor model for tile worker concurrency
41 files changed, 1191 insertions, 818 deletions
diff --git a/cmake/core-files.cmake b/cmake/core-files.cmake index 1a7353e979..11300e444d 100644 --- a/cmake/core-files.cmake +++ b/cmake/core-files.cmake @@ -1,6 +1,16 @@ # Do not edit. Regenerate this with ./scripts/generate-core-files.sh set(MBGL_CORE_FILES + # actor + src/mbgl/actor/actor.hpp + src/mbgl/actor/actor_ref.hpp + src/mbgl/actor/mailbox.cpp + src/mbgl/actor/mailbox.hpp + src/mbgl/actor/message.hpp + src/mbgl/actor/scheduler.hpp + src/mbgl/actor/thread_pool.cpp + src/mbgl/actor/thread_pool.hpp + # algorithm src/mbgl/algorithm/covered_by_children.hpp src/mbgl/algorithm/generate_clip_ids.cpp @@ -365,8 +375,12 @@ set(MBGL_CORE_FILES src/mbgl/tile/geometry_tile.hpp src/mbgl/tile/geometry_tile_data.cpp src/mbgl/tile/geometry_tile_data.hpp + src/mbgl/tile/geometry_tile_worker.cpp + src/mbgl/tile/geometry_tile_worker.hpp src/mbgl/tile/raster_tile.cpp src/mbgl/tile/raster_tile.hpp + src/mbgl/tile/raster_tile_worker.cpp + src/mbgl/tile/raster_tile_worker.hpp src/mbgl/tile/tile.cpp src/mbgl/tile/tile.hpp src/mbgl/tile/tile_cache.cpp @@ -376,8 +390,6 @@ set(MBGL_CORE_FILES src/mbgl/tile/tile_loader.hpp src/mbgl/tile/tile_loader_impl.hpp src/mbgl/tile/tile_observer.hpp - src/mbgl/tile/tile_worker.cpp - src/mbgl/tile/tile_worker.hpp src/mbgl/tile/vector_tile.cpp src/mbgl/tile/vector_tile.hpp @@ -472,6 +484,4 @@ set(MBGL_CORE_FILES src/mbgl/util/work_queue.cpp src/mbgl/util/work_queue.hpp src/mbgl/util/work_request.cpp - src/mbgl/util/worker.cpp - src/mbgl/util/worker.hpp ) diff --git a/cmake/test-files.cmake b/cmake/test-files.cmake index 0a4a6e3c41..84f9bcca63 100644 --- a/cmake/test-files.cmake +++ b/cmake/test-files.cmake @@ -1,6 +1,10 @@ # Do not edit. Regenerate this with ./scripts/generate-test-files.sh set(MBGL_TEST_FILES + # actor + test/actor/actor.cpp + test/actor/actor_ref.cpp + # algorithm test/algorithm/covered_by_children.cpp test/algorithm/generate_clip_ids.cpp @@ -43,10 +47,12 @@ set(MBGL_TEST_FILES # src/mbgl/test test/src/mbgl/test/conversion_stubs.hpp + test/src/mbgl/test/fake_file_source.hpp test/src/mbgl/test/fixture_log_observer.cpp test/src/mbgl/test/fixture_log_observer.hpp test/src/mbgl/test/stub_file_source.cpp test/src/mbgl/test/stub_file_source.hpp + test/src/mbgl/test/stub_layer_observer.hpp test/src/mbgl/test/stub_style_observer.hpp test/src/mbgl/test/test.cpp test/src/mbgl/test/util.cpp @@ -63,6 +69,9 @@ set(MBGL_TEST_FILES test/storage/online_file_source.cpp test/storage/resource.cpp + # style/conversion + test/style/conversion/geojson_options.cpp + # style test/style/filter.cpp test/style/functions.cpp @@ -73,9 +82,6 @@ set(MBGL_TEST_FILES test/style/style_parser.cpp test/style/tile_source.cpp test/style/variant.cpp - - # style conversion - test/style/conversion/geojson_options.cpp # text test/text/quads.cpp diff --git a/include/mbgl/util/run_loop.hpp b/include/mbgl/util/run_loop.hpp index 6559b72ef8..939531d6c3 100644 --- a/include/mbgl/util/run_loop.hpp +++ b/include/mbgl/util/run_loop.hpp @@ -1,5 +1,7 @@ #pragma once +#include <mbgl/actor/scheduler.hpp> +#include <mbgl/actor/mailbox.hpp> #include <mbgl/util/noncopyable.hpp> #include <mbgl/util/util.hpp> #include <mbgl/util/work_task.hpp> @@ -16,7 +18,8 @@ namespace util { typedef void * LOOP_HANDLE; -class RunLoop : private util::noncopyable { +class RunLoop : public Scheduler, + private util::noncopyable { public: enum class Type : uint8_t { Default, @@ -31,7 +34,7 @@ public: }; RunLoop(Type type = Type::Default); - ~RunLoop(); + ~RunLoop() override; static RunLoop* Get(); static LOOP_HANDLE getLoopHandle(); @@ -78,6 +81,14 @@ private: void push(std::shared_ptr<WorkTask>); + void schedule(std::weak_ptr<Mailbox> mailbox) override { + invoke([mailbox] () { + if (auto locked = mailbox.lock()) { + locked->receive(); + } + }); + } + void withMutex(std::function<void()>&& fn) { std::lock_guard<std::mutex> lock(mutex); fn(); diff --git a/src/mbgl/actor/actor.hpp b/src/mbgl/actor/actor.hpp new file mode 100644 index 0000000000..281bbdaed1 --- /dev/null +++ b/src/mbgl/actor/actor.hpp @@ -0,0 +1,76 @@ +#pragma once + +#include <mbgl/actor/mailbox.hpp> +#include <mbgl/actor/message.hpp> +#include <mbgl/actor/actor_ref.hpp> +#include <mbgl/util/noncopyable.hpp> + +#include <memory> + +namespace mbgl { + +/* + An `Actor<O>` is an owning reference to an asynchronous object of type `O`: an "actor". + Communication with an actor happens via message passing: you send a message to the object + (using `invoke`), passing a pointer to the member function to call and arguments which + are then forwarded to the actor. + + The actor receives messages sent to it asynchronously, in a manner defined its `Scheduler`. + To store incoming messages before their receipt, each actor has a `Mailbox`, which acts as + a FIFO queue. Messages sent from actor S to actor R are guaranteed to be processed in the + order sent. However, relative order of messages sent by two *different* actors S1 and S2 + to R is *not* guaranteed (and can't be: S1 and S2 may be acting asynchronously with respect + to each other). + + Construction and destruction of an actor is currently synchronous: the corresponding `O` + object is constructed synchronously by the `Actor` constructor, and destructed synchronously + by the `~Actor` destructor, after ensuring that the `O` is not currently receiving an + asynchronous message. (Construction and destruction may change to be asynchronous in the + future.) + + An `Actor<O>` can be converted to an `ActorRef<O>`, a non-owning value object representing + a (weak) reference to the actor. Messages can be sent via the `Ref` as well. + + It's safe -- and encouraged -- to pass `Ref`s between actors via messages. This is how two-way + communication and other forms of collaboration between multiple actors is accomplished. + + It's safe for a `Ref` to outlive its `Actor` -- the reference is "weak", and does not extend + the lifetime of the owning Actor, and sending a message to a `Ref` whose `Actor` has died is + a no-op. (In the future, a dead-letters queue or log may be implemented.) + + Please don't send messages that contain shared pointers or references. That subverts the + purpose of the actor model: prohibiting direct concurrent access to shared state. +*/ + +template <class Object> +class Actor : public util::noncopyable { +public: + template <class... Args> + Actor(Scheduler& scheduler, Args&&... args_) + : mailbox(std::make_shared<Mailbox>(scheduler)), + object(self(), std::forward<Args>(args_)...) { + } + + ~Actor() { + mailbox->close(); + } + + template <typename Fn, class... Args> + void invoke(Fn fn, Args&&... args) { + mailbox->push(actor::makeMessage(object, fn, std::forward<Args>(args)...)); + } + + ActorRef<std::decay_t<Object>> self() { + return ActorRef<std::decay_t<Object>>(object, mailbox); + } + + operator ActorRef<std::decay_t<Object>>() { + return self(); + } + +private: + std::shared_ptr<Mailbox> mailbox; + Object object; +}; + +} // namespace mbgl diff --git a/src/mbgl/actor/actor_ref.hpp b/src/mbgl/actor/actor_ref.hpp new file mode 100644 index 0000000000..9d858d823f --- /dev/null +++ b/src/mbgl/actor/actor_ref.hpp @@ -0,0 +1,43 @@ +#pragma once + +#include <mbgl/actor/mailbox.hpp> +#include <mbgl/actor/message.hpp> + +#include <memory> + +namespace mbgl { + +/* + An `ActorRef<O>` is a *non*-owning, weak reference to an actor of type `O`. You can send it + messages just like an `Actor<O>`. It's a value object: safe to copy and pass between actors + via messages. + + An `ActorRef<O>` does not extend the lifetime of the corresponding `Actor<O>`. That's determined + entirely by whichever object owns the `Actor<O>` -- the actor's "supervisor". + + It's safe for a `Ref` to outlive its `Actor` -- the reference is "weak", and does not extend + the lifetime of the owning Actor, and sending a message to a `Ref` whose `Actor` has died is + a no-op. (In the future, a dead-letters queue or log may be implemented.) +*/ + +template <class Object> +class ActorRef { +public: + ActorRef(Object& object_, std::weak_ptr<Mailbox> weakMailbox_) + : object(object_), + weakMailbox(std::move(weakMailbox_)) { + } + + template <typename Fn, class... Args> + void invoke(Fn fn, Args&&... args) { + if (auto mailbox = weakMailbox.lock()) { + mailbox->push(actor::makeMessage(object, fn, std::forward<Args>(args)...)); + } + } + +private: + Object& object; + std::weak_ptr<Mailbox> weakMailbox; +}; + +} // namespace mbgl diff --git a/src/mbgl/actor/mailbox.cpp b/src/mbgl/actor/mailbox.cpp new file mode 100644 index 0000000000..ae3c0967af --- /dev/null +++ b/src/mbgl/actor/mailbox.cpp @@ -0,0 +1,55 @@ +#include <mbgl/actor/mailbox.hpp> +#include <mbgl/actor/message.hpp> +#include <mbgl/actor/scheduler.hpp> + +#include <cassert> + +namespace mbgl { + +Mailbox::Mailbox(Scheduler& scheduler_) + : scheduler(scheduler_) { +} + +void Mailbox::push(std::unique_ptr<Message> message) { + assert(!closing); + + std::lock_guard<std::mutex> queueLock(queueMutex); + bool wasEmpty = queue.empty(); + queue.push(std::move(message)); + if (wasEmpty) { + scheduler.schedule(shared_from_this()); + } +} + +void Mailbox::close() { + // Block until the scheduler is guaranteed not to be executing receive(). + std::lock_guard<std::mutex> closingLock(closingMutex); + closing = true; +} + +void Mailbox::receive() { + std::lock_guard<std::mutex> closingLock(closingMutex); + + if (closing) { + return; + } + + std::unique_ptr<Message> message; + bool wasEmpty; + + { + std::lock_guard<std::mutex> queueLock(queueMutex); + assert(!queue.empty()); + message = std::move(queue.front()); + queue.pop(); + wasEmpty = queue.empty(); + } + + (*message)(); + + if (!wasEmpty) { + scheduler.schedule(shared_from_this()); + } +} + +} // namespace mbgl diff --git a/src/mbgl/actor/mailbox.hpp b/src/mbgl/actor/mailbox.hpp new file mode 100644 index 0000000000..5d5e8cb924 --- /dev/null +++ b/src/mbgl/actor/mailbox.hpp @@ -0,0 +1,31 @@ +#pragma once + +#include <memory> +#include <mutex> +#include <queue> + +namespace mbgl { + +class Scheduler; +class Message; + +class Mailbox : public std::enable_shared_from_this<Mailbox> { +public: + Mailbox(Scheduler&); + + void push(std::unique_ptr<Message>); + + void close(); + void receive(); + +private: + Scheduler& scheduler; + + std::mutex closingMutex; + bool closing { false }; + + std::mutex queueMutex; + std::queue<std::unique_ptr<Message>> queue; +}; + +} // namespace mbgl diff --git a/src/mbgl/actor/message.hpp b/src/mbgl/actor/message.hpp new file mode 100644 index 0000000000..cf071d4933 --- /dev/null +++ b/src/mbgl/actor/message.hpp @@ -0,0 +1,48 @@ +#pragma once + +#include <utility> + +namespace mbgl { + +// A movable type-erasing function wrapper. This allows to store arbitrary invokable +// things (like std::function<>, or the result of a movable-only std::bind()) in the queue. +// Source: http://stackoverflow.com/a/29642072/331379 +class Message { +public: + virtual ~Message() = default; + virtual void operator()() = 0; +}; + +template <class Object, class MemberFn, class ArgsTuple> +class MessageImpl : public Message { +public: + MessageImpl(Object& object_, MemberFn memberFn_, ArgsTuple argsTuple_) + : object(object_), + memberFn(memberFn_), + argsTuple(std::move(argsTuple_)) { + } + + void operator()() override { + invoke(std::make_index_sequence<std::tuple_size<ArgsTuple>::value>()); + } + + template <std::size_t... I> + void invoke(std::index_sequence<I...>) { + (object.*memberFn)(std::move(std::get<I>(argsTuple))...); + } + + Object& object; + MemberFn memberFn; + ArgsTuple argsTuple; +}; + +namespace actor { + +template <class Object, class MemberFn, class... Args> +std::unique_ptr<Message> makeMessage(Object& object, MemberFn memberFn, Args&&... args) { + auto tuple = std::make_tuple(std::forward<Args>(args)...); + return std::make_unique<MessageImpl<Object, MemberFn, decltype(tuple)>>(object, memberFn, std::move(tuple)); +} + +} // namespace actor +} // namespace mbgl diff --git a/src/mbgl/actor/scheduler.hpp b/src/mbgl/actor/scheduler.hpp new file mode 100644 index 0000000000..83689c3348 --- /dev/null +++ b/src/mbgl/actor/scheduler.hpp @@ -0,0 +1,38 @@ +#pragma once + +#include <memory> + +namespace mbgl { + +class Mailbox; + +/* + A `Scheduler` is responsible for coordinating the processing of messages by + one or more actors via their mailboxes. It's an abstract interface. Currently, + the following concrete implementations exist: + + * `ThreadPool` can coordinate an unlimited number of actors over any number of + threads via a pool, preserving the following behaviors: + + - Messages from each individual mailbox are processed in order + - Only a single message from a mailbox is processed at a time; there is no + concurrency within a mailbox + + Subject to these constraints, processing can happen on whatever thread in the + pool is available. + + * `RunLoop` is a `Scheduler` that is typically used to create a mailbox and + `ActorRef` for an object that lives on the main thread and is not itself wrapped + as an `Actor`: + + auto mailbox = std::make_shared<Mailbox>(*util::RunLoop::Get()); + Actor<Worker> worker(threadPool, ActorRef<Foo>(*this, mailbox)); +*/ + +class Scheduler { +public: + virtual ~Scheduler() = default; + virtual void schedule(std::weak_ptr<Mailbox>) = 0; +}; + +} // namespace mbgl diff --git a/src/mbgl/actor/thread_pool.cpp b/src/mbgl/actor/thread_pool.cpp new file mode 100644 index 0000000000..89ca1b72f0 --- /dev/null +++ b/src/mbgl/actor/thread_pool.cpp @@ -0,0 +1,48 @@ +#include <mbgl/actor/thread_pool.hpp> +#include <mbgl/actor/mailbox.hpp> + +namespace mbgl { + +ThreadPool::ThreadPool(std::size_t count) { + threads.reserve(count); + for (std::size_t i = 0; i < count; ++i) { + threads.emplace_back([this] () { + while (true) { + std::unique_lock<std::mutex> lock(mutex); + + cv.wait(lock, [this] { + return !queue.empty() || terminate.load(); + }); + + if (terminate.load()) { + return; + } + + auto mailbox = queue.front(); + queue.pop(); + lock.unlock(); + + if (auto locked = mailbox.lock()) { + locked->receive(); + } + } + }); + } +} + +ThreadPool::~ThreadPool() { + terminate.store(true); + cv.notify_all(); + + for (auto& thread : threads) { + thread.join(); + } +} + +void ThreadPool::schedule(std::weak_ptr<Mailbox> mailbox) { + std::lock_guard<std::mutex> lock(mutex); + queue.push(mailbox); + cv.notify_one(); +} + +} // namespace mbgl diff --git a/src/mbgl/actor/thread_pool.hpp b/src/mbgl/actor/thread_pool.hpp new file mode 100644 index 0000000000..7e0b40f4e3 --- /dev/null +++ b/src/mbgl/actor/thread_pool.hpp @@ -0,0 +1,28 @@ +#pragma once + +#include <mbgl/actor/scheduler.hpp> + +#include <atomic> +#include <condition_variable> +#include <mutex> +#include <queue> +#include <thread> + +namespace mbgl { + +class ThreadPool : public Scheduler { +public: + ThreadPool(std::size_t count); + ~ThreadPool() override; + + void schedule(std::weak_ptr<Mailbox>) override; + +private: + std::vector<std::thread> threads; + std::queue<std::weak_ptr<Mailbox>> queue; + std::mutex mutex; + std::condition_variable cv; + std::atomic_bool terminate { false }; +}; + +} // namespace mbgl diff --git a/src/mbgl/annotation/annotation_tile.cpp b/src/mbgl/annotation/annotation_tile.cpp index c9ad9b5214..9d28f67785 100644 --- a/src/mbgl/annotation/annotation_tile.cpp +++ b/src/mbgl/annotation/annotation_tile.cpp @@ -10,7 +10,7 @@ namespace mbgl { AnnotationTile::AnnotationTile(const OverscaledTileID& overscaledTileID, const style::UpdateParameters& parameters) - : GeometryTile(overscaledTileID, AnnotationManager::SourceID, parameters.style, parameters.mode), + : GeometryTile(overscaledTileID, AnnotationManager::SourceID, parameters), annotationManager(parameters.annotationManager) { annotationManager.addTile(*this); } diff --git a/src/mbgl/map/map.cpp b/src/mbgl/map/map.cpp index 56be99d8fc..ad77ac19fe 100644 --- a/src/mbgl/map/map.cpp +++ b/src/mbgl/map/map.cpp @@ -22,6 +22,7 @@ #include <mbgl/util/async_task.hpp> #include <mbgl/util/mapbox.hpp> #include <mbgl/util/tile_coordinate.hpp> +#include <mbgl/actor/thread_pool.hpp> namespace mbgl { @@ -61,6 +62,7 @@ public: gl::ObjectStore store; Update updateFlags = Update::Nothing; util::AsyncTask asyncUpdate; + ThreadPool workerThreadPool; std::unique_ptr<AnnotationManager> annotationManager; std::unique_ptr<Painter> painter; @@ -99,6 +101,7 @@ Map::Impl::Impl(View& view_, contextMode(contextMode_), pixelRatio(view.getPixelRatio()), asyncUpdate([this] { update(); }), + workerThreadPool(4), annotationManager(std::make_unique<AnnotationManager>(pixelRatio)) { } @@ -227,9 +230,8 @@ void Map::Impl::update() { style::UpdateParameters parameters(pixelRatio, debugOptions, transform.getState(), - style->workers, + workerThreadPool, fileSource, - style->shouldReparsePartialTiles, mode, *annotationManager, *style); diff --git a/src/mbgl/style/source_impl.cpp b/src/mbgl/style/source_impl.cpp index e124cde583..58ae3d9f91 100644 --- a/src/mbgl/style/source_impl.cpp +++ b/src/mbgl/style/source_impl.cpp @@ -5,6 +5,7 @@ #include <mbgl/renderer/painter.hpp> #include <mbgl/style/update_parameters.hpp> #include <mbgl/style/query_parameters.hpp> +#include <mbgl/text/placement_config.hpp> #include <mbgl/platform/log.hpp> #include <mbgl/math/clamp.hpp> #include <mbgl/util/tile_cover.hpp> @@ -77,7 +78,7 @@ const std::map<UnwrappedTileID, RenderTile>& Source::Impl::getRenderTiles() cons return renderTiles; } -void Source::Impl::loadTiles(const UpdateParameters& parameters) { +void Source::Impl::updateTiles(const UpdateParameters& parameters) { if (!loaded) { return; } @@ -161,28 +162,17 @@ void Source::Impl::loadTiles(const UpdateParameters& parameters) { ++retainIt; } } -} -bool Source::Impl::parseTiles(const UpdateParameters& parameters) { - bool allTilesUpdated = true; - const PlacementConfig newConfig{ parameters.transformState.getAngle(), - parameters.transformState.getPitch(), - parameters.debugOptions & MapDebugOptions::Collision }; + const PlacementConfig config { parameters.transformState.getAngle(), + parameters.transformState.getPitch(), + parameters.debugOptions & MapDebugOptions::Collision }; + for (auto& pair : tiles) { - auto tile = pair.second.get(); - if (parameters.shouldReparsePartialTiles && tile->isIncomplete()) { - if (!tile->parsePending()) { - allTilesUpdated = false; - } - } else { - tile->redoPlacement(newConfig); - } + pair.second->setPlacementConfig(config); } - - return allTilesUpdated; } -void Source::Impl::reload() { +void Source::Impl::reloadTiles() { cache.clear(); for (auto& pair : tiles) { @@ -258,18 +248,14 @@ void Source::Impl::setObserver(SourceObserver* observer_) { observer = observer_; } -void Source::Impl::onTileLoaded(Tile& tile, TileLoadState loadState) { - observer->onTileLoaded(base, tile.id, loadState); +void Source::Impl::onTileChanged(Tile& tile) { + observer->onTileChanged(base, tile.id); } void Source::Impl::onTileError(Tile& tile, std::exception_ptr error) { observer->onTileError(base, tile.id, error); } -void Source::Impl::onTileUpdated(Tile& tile) { - observer->onTileUpdated(base, tile.id); -} - void Source::Impl::dumpDebugLogs() const { Log::Info(Event::General, "Source::id: %s", base.getID().c_str()); Log::Info(Event::General, "Source::loaded: %d", loaded); diff --git a/src/mbgl/style/source_impl.hpp b/src/mbgl/style/source_impl.hpp index 5a54f4781a..0de3760fc3 100644 --- a/src/mbgl/style/source_impl.hpp +++ b/src/mbgl/style/source_impl.hpp @@ -43,16 +43,14 @@ public: virtual void loadDescription(FileSource&) = 0; bool isLoaded() const; - // Request or parse all the tiles relevant for the "TransformState". This method - // will return true if all the tiles were scheduled for updating of false if - // they were not. shouldReparsePartialTiles must be set to "true" if there is - // new data available that a tile in the "partial" state might be interested at. - void loadTiles(const UpdateParameters&); - bool parseTiles(const UpdateParameters&); + // Called when the camera has changed or icons or glyphs are loaded. May load new + // tiles, unload obsolete tiles, and trigger further parsing of incomplete tiles or + // re-placement of existing complete tiles. + void updateTiles(const UpdateParameters&); // Request that all loaded tiles re-run the layout operation on the existing source // data with fresh style information. - void reload(); + void reloadTiles(); void startRender(algorithm::ClipIDGenerator&, const mat4& projMatrix, @@ -88,9 +86,8 @@ protected: private: // TileObserver implementation. - void onTileLoaded(Tile&, TileLoadState) override; + void onTileChanged(Tile&) override; void onTileError(Tile&, std::exception_ptr) override; - void onTileUpdated(Tile&) override; virtual uint16_t getTileSize() const = 0; virtual Range<uint8_t> getZoomRange() = 0; diff --git a/src/mbgl/style/source_observer.hpp b/src/mbgl/style/source_observer.hpp index 6e0b226c3b..26b25b78da 100644 --- a/src/mbgl/style/source_observer.hpp +++ b/src/mbgl/style/source_observer.hpp @@ -19,9 +19,8 @@ public: virtual void onSourceLoaded(Source&) {} virtual void onSourceError(Source&, std::exception_ptr) {} - virtual void onTileLoaded(Source&, const OverscaledTileID&, TileLoadState) {} + virtual void onTileChanged(Source&, const OverscaledTileID&) {} virtual void onTileError(Source&, const OverscaledTileID&, std::exception_ptr) {} - virtual void onTileUpdated(Source&, const OverscaledTileID&) {} }; } // namespace style diff --git a/src/mbgl/style/style.cpp b/src/mbgl/style/style.cpp index 34790683bc..a00c5e4efc 100644 --- a/src/mbgl/style/style.cpp +++ b/src/mbgl/style/style.cpp @@ -43,8 +43,7 @@ Style::Style(FileSource& fileSource_, float pixelRatio) spriteStore(std::make_unique<SpriteStore>(pixelRatio)), spriteAtlas(std::make_unique<SpriteAtlas>(1024, 1024, pixelRatio, *spriteStore)), lineAtlas(std::make_unique<LineAtlas>(256, 512)), - observer(&nullObserver), - workers(4) { + observer(&nullObserver) { glyphStore->setObserver(this); spriteStore->setObserver(this); } @@ -215,19 +214,8 @@ double Style::getDefaultPitch() const { } void Style::updateTiles(const UpdateParameters& parameters) { - bool allTilesUpdated = true; - for (const auto& source : sources) { - source->baseImpl->loadTiles(parameters); - if (!source->baseImpl->parseTiles(parameters)) { - allTilesUpdated = false; - } - } - - // We can only stop updating "partial" tiles when all of them - // were notified of the arrival of the new resources. - if (allTilesUpdated) { - shouldReparsePartialTiles = false; + source->baseImpl->updateTiles(parameters); } } @@ -235,7 +223,7 @@ void Style::relayout() { for (const auto& sourceID : updateBatch.sourceIDs) { Source* source = getSource(sourceID); if (!source) continue; - source->baseImpl->reload(); + source->baseImpl->reloadTiles(); } updateBatch.sourceIDs.clear(); } @@ -447,7 +435,6 @@ void Style::setObserver(style::Observer* observer_) { } void Style::onGlyphsLoaded(const FontStack& fontStack, const GlyphRange& glyphRange) { - shouldReparsePartialTiles = true; observer->onGlyphsLoaded(fontStack, glyphRange); observer->onUpdate(Update::Repaint); } @@ -473,12 +460,8 @@ void Style::onSourceError(Source& source, std::exception_ptr error) { observer->onResourceError(error); } -void Style::onTileLoaded(Source& source, const OverscaledTileID& tileID, TileLoadState loadState) { - if (loadState == TileLoadState::First) { - shouldReparsePartialTiles = true; - } - - observer->onTileLoaded(source, tileID, loadState); +void Style::onTileChanged(Source& source, const OverscaledTileID& tileID) { + observer->onTileChanged(source, tileID); observer->onUpdate(Update::Repaint); } @@ -490,12 +473,7 @@ void Style::onTileError(Source& source, const OverscaledTileID& tileID, std::exc observer->onResourceError(error); } -void Style::onTileUpdated(Source&, const OverscaledTileID&) { - observer->onUpdate(Update::Repaint); -} - void Style::onSpriteLoaded() { - shouldReparsePartialTiles = true; observer->onSpriteLoaded(); observer->onUpdate(Update::Repaint); } diff --git a/src/mbgl/style/style.hpp b/src/mbgl/style/style.hpp index 8027313ab9..3e678ec060 100644 --- a/src/mbgl/style/style.hpp +++ b/src/mbgl/style/style.hpp @@ -12,25 +12,28 @@ #include <mbgl/util/noncopyable.hpp> #include <mbgl/util/chrono.hpp> -#include <mbgl/util/worker.hpp> #include <mbgl/util/optional.hpp> #include <mbgl/util/feature.hpp> #include <mbgl/util/geo.hpp> #include <cstdint> +#include <memory> #include <string> #include <vector> namespace mbgl { class FileSource; +class GlyphStore; class GlyphAtlas; +class SpriteStore; class SpriteAtlas; class LineAtlas; class RenderData; namespace style { +class Layer; class UpdateParameters; class QueryParameters; @@ -134,9 +137,8 @@ private: // SourceObserver implementation. void onSourceLoaded(Source&) override; void onSourceError(Source&, std::exception_ptr) override; - void onTileLoaded(Source&, const OverscaledTileID&, TileLoadState) override; + void onTileChanged(Source&, const OverscaledTileID&) override; void onTileError(Source&, const OverscaledTileID&, std::exception_ptr) override; - void onTileUpdated(Source&, const OverscaledTileID&) override; // LayerObserver implementation. void onLayerFilterChanged(Layer&) override; @@ -154,9 +156,7 @@ private: bool hasPendingTransitions = false; public: - bool shouldReparsePartialTiles = false; bool loaded = false; - Worker workers; }; } // namespace style diff --git a/src/mbgl/style/update_parameters.hpp b/src/mbgl/style/update_parameters.hpp index 306cddf706..900f4b5183 100644 --- a/src/mbgl/style/update_parameters.hpp +++ b/src/mbgl/style/update_parameters.hpp @@ -5,7 +5,7 @@ namespace mbgl { class TransformState; -class Worker; +class Scheduler; class FileSource; class AnnotationManager; @@ -18,18 +18,16 @@ public: UpdateParameters(float pixelRatio_, MapDebugOptions debugOptions_, const TransformState& transformState_, - Worker& worker_, + Scheduler& workerScheduler_, FileSource& fileSource_, - bool shouldReparsePartialTiles_, const MapMode mode_, AnnotationManager& annotationManager_, Style& style_) : pixelRatio(pixelRatio_), debugOptions(debugOptions_), transformState(transformState_), - worker(worker_), + workerScheduler(workerScheduler_), fileSource(fileSource_), - shouldReparsePartialTiles(shouldReparsePartialTiles_), mode(mode_), annotationManager(annotationManager_), style(style_) {} @@ -37,9 +35,8 @@ public: float pixelRatio; MapDebugOptions debugOptions; const TransformState& transformState; - Worker& worker; + Scheduler& workerScheduler; FileSource& fileSource; - bool shouldReparsePartialTiles; const MapMode mode; AnnotationManager& annotationManager; 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) { } diff --git a/src/mbgl/util/worker.cpp b/src/mbgl/util/worker.cpp deleted file mode 100644 index 8245628e84..0000000000 --- a/src/mbgl/util/worker.cpp +++ /dev/null @@ -1,129 +0,0 @@ -#include <mbgl/util/worker.hpp> -#include <mbgl/util/work_task.hpp> -#include <mbgl/util/work_request.hpp> -#include <mbgl/platform/platform.hpp> -#include <mbgl/renderer/raster_bucket.hpp> -#include <mbgl/tile/geometry_tile_data.hpp> -#include <mbgl/style/layer.hpp> -#include <mbgl/text/collision_tile.hpp> - -#include <cassert> -#include <future> - -namespace mbgl { - -class Worker::Impl { -public: - Impl() = default; - - void parseRasterTile(std::unique_ptr<RasterBucket> bucket, - std::shared_ptr<const std::string> data, - std::function<void(RasterTileParseResult)> callback) { - try { - bucket->setImage(decodeImage(*data)); - // Destruct the shared pointer before calling the callback. - data.reset(); - callback(RasterTileParseResult(std::move(bucket))); - } catch (...) { - callback(std::current_exception()); - } - } - - void parseGeometryTile(TileWorker* worker, - std::vector<std::unique_ptr<style::Layer>> layers, - std::unique_ptr<const GeometryTileData> tileData, - PlacementConfig config, - std::function<void(TileParseResult)> callback) { - try { - callback(worker->parseAllLayers(std::move(layers), std::move(tileData), config)); - } catch (...) { - callback(std::current_exception()); - } - } - - void parsePendingGeometryTileLayers(TileWorker* worker, - PlacementConfig config, - std::function<void(TileParseResult)> callback) { - try { - callback(worker->parsePendingLayers(config)); - } catch (...) { - callback(std::current_exception()); - } - } - - void redoLayout(TileWorker* worker, - std::vector<std::unique_ptr<style::Layer>> layers, - PlacementConfig config, - std::function<void(TileParseResult)> callback) { - try { - callback(worker->redoLayout(std::move(layers), config)); - } catch (...) { - callback(std::current_exception()); - } - } - - void redoPlacement(TileWorker* worker, - PlacementConfig config, - std::function<void(TilePlacementResult)> callback) { - callback(worker->redoPlacement(config)); - } -}; - -Worker::Worker(std::size_t count) { - util::ThreadContext context = { "Worker", util::ThreadPriority::Low }; - for (std::size_t i = 0; i < count; i++) { - threads.emplace_back(std::make_unique<util::Thread<Impl>>(context)); - } -} - -Worker::~Worker() = default; - -std::unique_ptr<AsyncRequest> -Worker::parseRasterTile(std::unique_ptr<RasterBucket> bucket, - const std::shared_ptr<const std::string> data, - std::function<void(RasterTileParseResult)> callback) { - current = (current + 1) % threads.size(); - return threads[current]->invokeWithCallback(&Worker::Impl::parseRasterTile, std::move(bucket), - data, callback); -} - -std::unique_ptr<AsyncRequest> -Worker::parseGeometryTile(TileWorker& worker, - std::vector<std::unique_ptr<style::Layer>> layers, - std::unique_ptr<const GeometryTileData> tileData, - PlacementConfig config, - std::function<void(TileParseResult)> callback) { - current = (current + 1) % threads.size(); - return threads[current]->invokeWithCallback(&Worker::Impl::parseGeometryTile, &worker, - std::move(layers), std::move(tileData), config, callback); -} - -std::unique_ptr<AsyncRequest> -Worker::parsePendingGeometryTileLayers(TileWorker& worker, - PlacementConfig config, - std::function<void(TileParseResult)> callback) { - current = (current + 1) % threads.size(); - return threads[current]->invokeWithCallback(&Worker::Impl::parsePendingGeometryTileLayers, - &worker, config, callback); -} - -std::unique_ptr<AsyncRequest> -Worker::redoLayout(TileWorker& worker, - std::vector<std::unique_ptr<style::Layer>> layers, - PlacementConfig config, - std::function<void(TileParseResult)> callback) { - current = (current + 1) % threads.size(); - return threads[current]->invokeWithCallback(&Worker::Impl::redoLayout, &worker, - std::move(layers), config, callback); -} - -std::unique_ptr<AsyncRequest> -Worker::redoPlacement(TileWorker& worker, - PlacementConfig config, - std::function<void(TilePlacementResult)> callback) { - current = (current + 1) % threads.size(); - return threads[current]->invokeWithCallback(&Worker::Impl::redoPlacement, &worker, - config, callback); -} - -} // end namespace mbgl diff --git a/src/mbgl/util/worker.hpp b/src/mbgl/util/worker.hpp deleted file mode 100644 index 5b2ea06525..0000000000 --- a/src/mbgl/util/worker.hpp +++ /dev/null @@ -1,66 +0,0 @@ -#pragma once - -#include <mbgl/util/noncopyable.hpp> -#include <mbgl/util/thread.hpp> -#include <mbgl/tile/tile_worker.hpp> - -#include <functional> -#include <memory> - -namespace mbgl { - -class AsyncRequest; -class RasterBucket; -class GeometryTileLoader; -class CollisionTile; - -using RasterTileParseResult = variant< - std::unique_ptr<Bucket>, // success - std::exception_ptr>; // error - -class Worker : public mbgl::util::noncopyable { -public: - explicit Worker(std::size_t count); - ~Worker(); - - // Request work be done on a thread pool. Callbacks are executed on the invoking - // thread, which must have a run loop, after the work is complete. - // - // The return value represents the request to perform the work asynchronously. - // Its destructor guarantees that the work function has finished executing, and - // that the after function has either finished executing or will not execute. - // Together, this means that an object may make a work request with lambdas which - // bind references to itself, and if and when those lambdas execute, the references - // will still be valid. - - using Request = std::unique_ptr<AsyncRequest>; - - Request parseRasterTile(std::unique_ptr<RasterBucket> bucket, - std::shared_ptr<const std::string> data, - std::function<void(RasterTileParseResult)> callback); - - Request parseGeometryTile(TileWorker&, - std::vector<std::unique_ptr<style::Layer>>, - std::unique_ptr<const GeometryTileData>, - PlacementConfig, - std::function<void(TileParseResult)> callback); - - Request parsePendingGeometryTileLayers(TileWorker&, - PlacementConfig config, - std::function<void(TileParseResult)> callback); - - Request redoLayout(TileWorker&, - std::vector<std::unique_ptr<style::Layer>>, - PlacementConfig, - std::function<void(TileParseResult)> callback); - - Request redoPlacement(TileWorker&, - PlacementConfig config, - std::function<void(TilePlacementResult)> callback); - -private: - class Impl; - std::vector<std::unique_ptr<util::Thread<Impl>>> threads; - std::size_t current = 0; -}; -} // namespace mbgl diff --git a/test/actor/actor.cpp b/test/actor/actor.cpp new file mode 100644 index 0000000000..7bb76784d6 --- /dev/null +++ b/test/actor/actor.cpp @@ -0,0 +1,139 @@ +#include <mbgl/actor/actor.hpp> +#include <mbgl/actor/thread_pool.hpp> + +#include <mbgl/test/util.hpp> + +#include <chrono> +#include <functional> +#include <future> + +using namespace mbgl; +using namespace std::chrono_literals; + +TEST(Actor, Construction) { + // Construction is currently synchronous. It may become asynchronous in the future. + + struct Test { + Test(ActorRef<Test>, bool& constructed) { + constructed = true; + }; + }; + + ThreadPool pool { 1 }; + bool constructed = false; + Actor<Test> test(pool, std::ref(constructed)); + + EXPECT_TRUE(constructed); +} + +TEST(Actor, DestructionClosesMailbox) { + // Destruction blocks until the actor is not receiving. + + struct Test { + std::promise<void> promise; + std::future<void> future; + std::atomic<bool> waited; + + Test(ActorRef<Test>, std::promise<void> promise_, std::future<void> future_) + : promise(std::move(promise_)), + future(std::move(future_)), + waited(false) { + } + + ~Test() { + EXPECT_TRUE(waited.load()); + } + + void wait() { + promise.set_value(); + future.wait(); + std::this_thread::sleep_for(1ms); + waited = true; + } + }; + + ThreadPool pool { 1 }; + + std::promise<void> enteredPromise; + std::future<void> enteredFuture = enteredPromise.get_future(); + + std::promise<void> exitingPromise; + std::future<void> exitingFuture = exitingPromise.get_future(); + + Actor<Test> test(pool, std::move(enteredPromise), std::move(exitingFuture)); + + test.invoke(&Test::wait); + enteredFuture.wait(); + exitingPromise.set_value(); +} + +TEST(Actor, OrderedMailbox) { + // Messages are processed in order. + + struct Test { + int last = 0; + std::promise<void> promise; + + Test(ActorRef<Test>, std::promise<void> promise_) + : promise(std::move(promise_)) { + } + + void receive(int i) { + EXPECT_EQ(i, last + 1); + last = i; + } + + void end() { + promise.set_value(); + } + }; + + ThreadPool pool { 1 }; + + std::promise<void> endedPromise; + std::future<void> endedFuture = endedPromise.get_future(); + Actor<Test> test(pool, std::move(endedPromise)); + + for (auto i = 1; i <= 10; ++i) { + test.invoke(&Test::receive, i); + } + + test.invoke(&Test::end); + endedFuture.wait(); +} + +TEST(Actor, NonConcurrentMailbox) { + // An individual actor is never itself concurrent. + + struct Test { + int last = 0; + std::promise<void> promise; + + Test(ActorRef<Test>, std::promise<void> promise_) + : promise(std::move(promise_)) { + } + + void receive(int i) { + EXPECT_EQ(i, last + 1); + last = i; + std::this_thread::sleep_for(1ms); + } + + void end() { + promise.set_value(); + } + }; + + ThreadPool pool { 10 }; + + std::promise<void> endedPromise; + std::future<void> endedFuture = endedPromise.get_future(); + Actor<Test> test(pool, std::move(endedPromise)); + + for (auto i = 1; i <= 10; ++i) { + test.invoke(&Test::receive, i); + } + + test.invoke(&Test::end); + endedFuture.wait(); +} diff --git a/test/actor/actor_ref.cpp b/test/actor/actor_ref.cpp new file mode 100644 index 0000000000..655529035f --- /dev/null +++ b/test/actor/actor_ref.cpp @@ -0,0 +1,42 @@ +#include <mbgl/actor/actor.hpp> +#include <mbgl/actor/thread_pool.hpp> + +#include <mbgl/test/util.hpp> + +#include <chrono> +#include <functional> +#include <future> + +using namespace mbgl; +using namespace std::chrono_literals; + +TEST(ActorRef, CanOutliveActor) { + // An ActorRef can outlive its actor. Doing does not extend the actor's lifetime. + // Sending a message to an ActorRef whose actor has died is a no-op. + + struct Test { + bool& died; + + Test(ActorRef<Test>, bool& died_) + : died(died_) { + } + + ~Test() { + died = true; + } + + void receive() { + FAIL(); + } + }; + + ThreadPool pool { 1 }; + bool died = false; + + ActorRef<Test> test = [&] () { + return Actor<Test>(pool, std::ref(died)).self(); + }(); + + EXPECT_TRUE(died); + test.invoke(&Test::receive); +} diff --git a/test/src/mbgl/test/stub_style_observer.hpp b/test/src/mbgl/test/stub_style_observer.hpp index 4de3430fcc..aa780121f5 100644 --- a/test/src/mbgl/test/stub_style_observer.hpp +++ b/test/src/mbgl/test/stub_style_observer.hpp @@ -34,19 +34,15 @@ public: if (sourceError) sourceError(source, error); } - void onTileLoaded(Source& source, const OverscaledTileID& tileID, TileLoadState loadState) override { - if (tileLoaded) tileLoaded(source, tileID, loadState); - } + void onTileChanged(Source& source, const OverscaledTileID& tileID) override { + if (tileChanged) tileChanged(source, tileID); + }; void onTileError(Source& source, const OverscaledTileID& tileID, std::exception_ptr error) override { if (tileError) tileError(source, tileID, error); } - void onTileUpdated(Source& source, const OverscaledTileID& tileID) override { - if (tileUpdated) tileUpdated(source, tileID); - }; - void onResourceError(std::exception_ptr error) override { if (resourceError) resourceError(error); }; @@ -57,8 +53,7 @@ public: std::function<void (std::exception_ptr)> spriteError; std::function<void (Source&)> sourceLoaded; std::function<void (Source&, std::exception_ptr)> sourceError; - std::function<void (Source&, const OverscaledTileID&, TileLoadState)> tileLoaded; + std::function<void (Source&, const OverscaledTileID&)> tileChanged; std::function<void (Source&, const OverscaledTileID&, std::exception_ptr)> tileError; - std::function<void (Source&, const OverscaledTileID&)> tileUpdated; std::function<void (std::exception_ptr)> resourceError; }; diff --git a/test/style/source.cpp b/test/style/source.cpp index a33467dedb..519ca9288e 100644 --- a/test/style/source.cpp +++ b/test/style/source.cpp @@ -11,10 +11,10 @@ #include <mbgl/util/string.hpp> #include <mbgl/util/io.hpp> #include <mbgl/util/tileset.hpp> +#include <mbgl/actor/thread_pool.hpp> #include <mbgl/platform/log.hpp> #include <mbgl/map/transform.hpp> -#include <mbgl/util/worker.hpp> #include <mbgl/style/style.hpp> #include <mbgl/style/update_parameters.hpp> #include <mbgl/style/layers/line_layer.hpp> @@ -31,7 +31,7 @@ public: StubStyleObserver observer; Transform transform; TransformState transformState; - Worker worker { 1 }; + ThreadPool threadPool { 1 }; AnnotationManager annotationManager { 1.0 }; style::Style style { fileSource, 1.0 }; @@ -39,9 +39,8 @@ public: 1.0, MapDebugOptions(), transformState, - worker, + threadPool, fileSource, - true, MapMode::Continuous, annotationManager, style @@ -123,7 +122,7 @@ TEST(Source, RasterTileEmpty) { return response; }; - test.observer.tileLoaded = [&] (Source& source, const OverscaledTileID&, TileLoadState) { + test.observer.tileChanged = [&] (Source& source, const OverscaledTileID&) { EXPECT_EQ("source", source.getID()); test.end(); }; @@ -138,7 +137,7 @@ TEST(Source, RasterTileEmpty) { RasterSource source("source", tileset, 512); source.baseImpl->setObserver(&test.observer); source.baseImpl->loadDescription(test.fileSource); - source.baseImpl->loadTiles(test.updateParameters); + source.baseImpl->updateTiles(test.updateParameters); test.run(); } @@ -152,7 +151,7 @@ TEST(Source, VectorTileEmpty) { return response; }; - test.observer.tileLoaded = [&] (Source& source, const OverscaledTileID&, TileLoadState) { + test.observer.tileChanged = [&] (Source& source, const OverscaledTileID&) { EXPECT_EQ("source", source.getID()); test.end(); }; @@ -167,7 +166,7 @@ TEST(Source, VectorTileEmpty) { VectorSource source("source", tileset); source.baseImpl->setObserver(&test.observer); source.baseImpl->loadDescription(test.fileSource); - source.baseImpl->loadTiles(test.updateParameters); + source.baseImpl->updateTiles(test.updateParameters); test.run(); } @@ -196,7 +195,7 @@ TEST(Source, RasterTileFail) { RasterSource source("source", tileset, 512); source.baseImpl->setObserver(&test.observer); source.baseImpl->loadDescription(test.fileSource); - source.baseImpl->loadTiles(test.updateParameters); + source.baseImpl->updateTiles(test.updateParameters); test.run(); } @@ -225,7 +224,7 @@ TEST(Source, VectorTileFail) { VectorSource source("source", tileset); source.baseImpl->setObserver(&test.observer); source.baseImpl->loadDescription(test.fileSource); - source.baseImpl->loadTiles(test.updateParameters); + source.baseImpl->updateTiles(test.updateParameters); test.run(); } @@ -253,7 +252,7 @@ TEST(Source, RasterTileCorrupt) { RasterSource source("source", tileset, 512); source.baseImpl->setObserver(&test.observer); source.baseImpl->loadDescription(test.fileSource); - source.baseImpl->loadTiles(test.updateParameters); + source.baseImpl->updateTiles(test.updateParameters); test.run(); } @@ -285,7 +284,7 @@ TEST(Source, VectorTileCorrupt) { VectorSource source("source", tileset); source.baseImpl->setObserver(&test.observer); source.baseImpl->loadDescription(test.fileSource); - source.baseImpl->loadTiles(test.updateParameters); + source.baseImpl->updateTiles(test.updateParameters); test.run(); } @@ -298,7 +297,7 @@ TEST(Source, RasterTileCancel) { return optional<Response>(); }; - test.observer.tileLoaded = [&] (Source&, const OverscaledTileID&, TileLoadState) { + test.observer.tileChanged = [&] (Source&, const OverscaledTileID&) { FAIL() << "Should never be called"; }; @@ -312,7 +311,7 @@ TEST(Source, RasterTileCancel) { RasterSource source("source", tileset, 512); source.baseImpl->setObserver(&test.observer); source.baseImpl->loadDescription(test.fileSource); - source.baseImpl->loadTiles(test.updateParameters); + source.baseImpl->updateTiles(test.updateParameters); test.run(); } @@ -325,7 +324,7 @@ TEST(Source, VectorTileCancel) { return optional<Response>(); }; - test.observer.tileLoaded = [&] (Source&, const OverscaledTileID&, TileLoadState) { + test.observer.tileChanged = [&] (Source&, const OverscaledTileID&) { FAIL() << "Should never be called"; }; @@ -339,7 +338,7 @@ TEST(Source, VectorTileCancel) { VectorSource source("source", tileset); source.baseImpl->setObserver(&test.observer); source.baseImpl->loadDescription(test.fileSource); - source.baseImpl->loadTiles(test.updateParameters); + source.baseImpl->updateTiles(test.updateParameters); test.run(); } diff --git a/test/style/style.cpp b/test/style/style.cpp index 0d04fa5834..ff03e3c2cc 100644 --- a/test/style/style.cpp +++ b/test/style/style.cpp @@ -4,6 +4,7 @@ #include <mbgl/style/style.hpp> #include <mbgl/style/source_impl.hpp> #include <mbgl/util/io.hpp> +#include <mbgl/util/run_loop.hpp> using namespace mbgl; using namespace mbgl::style; |