[core] Use an actor model for tile worker concurrency

This commit is contained in:
John Firebaugh
2016-09-06 15:01:34 -07:00
parent 0bd66d40dd
commit 41bbd4e4f7
41 changed files with 1198 additions and 825 deletions

View File

@@ -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
)

View File

@@ -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

View File

@@ -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();

76
src/mbgl/actor/actor.hpp Normal file
View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

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

View File

@@ -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);

View File

@@ -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);

View File

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

View File

@@ -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

View File

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

View File

@@ -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

View File

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

View File

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

View File

@@ -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,30 +19,63 @@ 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::cancel() {
obsolete = true;
}
void GeometryTile::setError(std::exception_ptr err) {
availableData = DataAvailability::All;
observer->onTileError(*this, err);
}
std::vector<std::unique_ptr<Layer>> GeometryTile::cloneStyleLayers() const {
std::vector<std::unique_ptr<Layer>> result;
void GeometryTile::setData(std::unique_ptr<const GeometryTileData> data_) {
// 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;
}
++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() {
// 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;
}
std::vector<std::unique_ptr<Layer>> copy;
for (const Layer* layer : style.getLayers()) {
// Avoid cloning and including irrelevant layers.
@@ -54,121 +88,31 @@ std::vector<std::unique_ptr<Layer>> GeometryTile::cloneStyleLayers() const {
continue;
}
result.push_back(layer->baseImpl->clone());
copy.push_back(layer->baseImpl->clone());
}
return result;
++correlationID;
worker.invoke(&GeometryTileWorker::setLayers, std::move(copy), correlationID);
}
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();
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);
}
void GeometryTile::onPlacement(PlacementResult result) {
if (result.correlationID == correlationID) {
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;
for (auto& bucket : result.buckets) {
buckets[bucket.first] = std::move(bucket.second);
}
// 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);
});
}
void GeometryTile::redoLayout() {
// 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;
}
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();
if (result.is<TileParseResultData>()) {
auto& resultBuckets = result.get<TileParseResultData>();
availableData = resultBuckets.complete ? DataAvailability::All : DataAvailability::Some;
// 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;
// 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);
if (isComplete()) {
featureIndex = std::move(resultBuckets.featureIndex);
data = std::move(resultBuckets.tileData);
}
redoPlacement();
observer->onTileLoaded(*this, TileLoadState::First);
} else {
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;
}
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

View File

@@ -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;
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:
std::vector<std::unique_ptr<style::Layer>> cloneStyleLayers() const;
void redoPlacement();
void tileLoaded(TileParseResult, PlacementConfig);
const std::string sourceID;
style::Style& style;
Worker& worker;
TileWorker tileWorker;
std::unique_ptr<AsyncRequest> workRequest;
// 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::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 };
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;
};
} // namespace mbgl

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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.

View File

@@ -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

View File

@@ -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

View File

@@ -18,7 +18,7 @@ void Tile::setObserver(TileObserver* observer_) {
void Tile::setTriedOptional() {
triedOptional = true;
observer->onTileUpdated(*this);
observer->onTileChanged(*this);
}
void Tile::dumpDebugLogs() const {

View File

@@ -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,

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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) {
}

View File

@@ -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

View File

@@ -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

139
test/actor/actor.cpp Normal file
View File

@@ -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();
}

42
test/actor/actor_ref.cpp Normal file
View File

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

View File

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

View File

@@ -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();
}

View File

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