Asynchronous GeometryTile deletion (#2051)

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

View File

@@ -158,7 +158,6 @@ if(MLN_DRAWABLE_RENDERER)
${PROJECT_SOURCE_DIR}/include/mbgl/renderer/layer_tweaker.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/renderer/render_target.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/shader_program_base.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/util/identity.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/util/suppress_copies.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/gl/shader_info.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/gl/shader_program_gl.hpp
@@ -216,7 +215,6 @@ if(MLN_DRAWABLE_RENDERER)
${PROJECT_SOURCE_DIR}/src/mbgl/renderer/layers/collision_layer_tweaker.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/renderer/layers/collision_layer_tweaker.hpp
${PROJECT_SOURCE_DIR}/src/mbgl/shaders/shader_program_base.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/util/identity.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/shaders/gl/shader_program_gl.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/gl/buffer_allocator.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/gl/drawable_gl.cpp
@@ -403,6 +401,7 @@ list(APPEND INCLUDE_FILES
${PROJECT_SOURCE_DIR}/include/mbgl/util/geo.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/util/geojson.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/util/geometry.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/util/identity.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/util/ignore.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/util/image.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/util/immutable.hpp
@@ -415,6 +414,7 @@ list(APPEND INCLUDE_FILES
${PROJECT_SOURCE_DIR}/include/mbgl/util/projection.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/util/range.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/util/run_loop.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/util/scoped.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/util/size.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/util/string.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/util/string_indexer.hpp
@@ -939,6 +939,7 @@ list(APPEND SRC_FILES
${PROJECT_SOURCE_DIR}/src/mbgl/util/http_timeout.hpp
${PROJECT_SOURCE_DIR}/src/mbgl/util/i18n.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/util/i18n.hpp
${PROJECT_SOURCE_DIR}/src/mbgl/util/identity.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/util/interpolate.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/util/intersection_tests.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/util/intersection_tests.hpp
@@ -1444,4 +1445,4 @@ endif()
add_subdirectory(${PROJECT_SOURCE_DIR}/test)
add_subdirectory(${PROJECT_SOURCE_DIR}/benchmark)
add_subdirectory(${PROJECT_SOURCE_DIR}/render-test)
add_subdirectory(${PROJECT_SOURCE_DIR}/render-test)

View File

@@ -141,7 +141,6 @@ MLN_CORE_SOURCE = [
"src/mbgl/geometry/line_atlas.hpp",
"src/mbgl/gfx/attribute.cpp",
"src/mbgl/gfx/attribute.hpp",
"include/mbgl/gfx/backend.hpp",
"src/mbgl/gfx/color_mode.hpp",
"src/mbgl/gfx/command_encoder.hpp",
"src/mbgl/gfx/cull_face_mode.hpp",
@@ -149,9 +148,11 @@ MLN_CORE_SOURCE = [
"src/mbgl/gfx/depth_mode.hpp",
"src/mbgl/gfx/draw_mode.hpp",
"src/mbgl/gfx/draw_scope.hpp",
"src/mbgl/gfx/fill_generator.cpp",
"src/mbgl/gfx/index_buffer.hpp",
"src/mbgl/gfx/index_vector.hpp",
"src/mbgl/gfx/offscreen_texture.hpp",
"src/mbgl/gfx/polyline_generator.cpp",
"src/mbgl/gfx/program.hpp",
"src/mbgl/gfx/render_pass.hpp",
"src/mbgl/gfx/renderbuffer.hpp",
@@ -598,6 +599,7 @@ MLN_CORE_SOURCE = [
"src/mbgl/util/http_timeout.hpp",
"src/mbgl/util/i18n.cpp",
"src/mbgl/util/i18n.hpp",
"src/mbgl/util/identity.cpp",
"src/mbgl/util/interpolate.cpp",
"src/mbgl/util/intersection_tests.cpp",
"src/mbgl/util/intersection_tests.hpp",
@@ -618,8 +620,6 @@ MLN_CORE_SOURCE = [
"src/mbgl/util/premultiply.cpp",
"src/mbgl/util/quaternion.cpp",
"src/mbgl/util/quaternion.hpp",
"src/mbgl/gfx/polyline_generator.cpp",
"src/mbgl/gfx/fill_generator.cpp",
"src/mbgl/util/rapidjson.cpp",
"src/mbgl/util/rapidjson.hpp",
"src/mbgl/util/rect.hpp",
@@ -661,8 +661,11 @@ MLN_CORE_HEADERS = [
"include/mbgl/actor/message.hpp",
"include/mbgl/actor/scheduler.hpp",
"include/mbgl/annotation/annotation.hpp",
"include/mbgl/gfx/backend.hpp",
"include/mbgl/gfx/backend_scope.hpp",
"include/mbgl/gfx/fill_generator.hpp",
"include/mbgl/gfx/gfx_types.hpp",
"include/mbgl/gfx/polyline_generator.hpp",
"include/mbgl/gfx/renderable.hpp",
"include/mbgl/gfx/renderer_backend.hpp",
"include/mbgl/gfx/rendering_stats.hpp",
@@ -820,6 +823,7 @@ MLN_CORE_HEADERS = [
"include/mbgl/util/geo.hpp",
"include/mbgl/util/geojson.hpp",
"include/mbgl/util/geometry.hpp",
"include/mbgl/util/identity.hpp",
"include/mbgl/util/ignore.hpp",
"include/mbgl/util/image.hpp",
"include/mbgl/util/immutable.hpp",
@@ -829,12 +833,11 @@ MLN_CORE_HEADERS = [
"include/mbgl/util/monotonic_timer.hpp",
"include/mbgl/util/noncopyable.hpp",
"include/mbgl/util/platform.hpp",
"include/mbgl/gfx/polyline_generator.hpp",
"include/mbgl/gfx/fill_generator.hpp",
"include/mbgl/util/premultiply.hpp",
"include/mbgl/util/projection.hpp",
"include/mbgl/util/range.hpp",
"include/mbgl/util/run_loop.hpp",
"include/mbgl/util/scoped.hpp",
"include/mbgl/util/size.hpp",
"include/mbgl/util/string.hpp",
"include/mbgl/util/string_indexer.hpp",
@@ -905,7 +908,6 @@ MLN_OPENGL_SOURCE = [
]
MLN_OPENGL_HEADERS = [
"include/mbgl/gfx/backend.hpp",
"include/mbgl/gl/renderable_resource.hpp",
"include/mbgl/gl/renderer_backend.hpp",
"include/mbgl/layermanager/location_indicator_layer_factory.hpp",
@@ -957,7 +959,6 @@ MLN_DRAWABLES_SOURCE = [
"src/mbgl/renderer/layers/collision_layer_tweaker.cpp",
"src/mbgl/renderer/layers/collision_layer_tweaker.hpp",
"src/mbgl/shaders/shader_program_base.cpp",
"src/mbgl/util/identity.cpp",
"src/mbgl/style/layers/custom_drawable_layer.cpp",
"src/mbgl/layermanager/custom_drawable_layer_factory.cpp",
"src/mbgl/style/layers/custom_drawable_layer_impl.cpp",
@@ -999,7 +1000,6 @@ MLN_DRAWABLES_HEADERS = [
"include/mbgl/shaders/shader_defines.hpp",
"include/mbgl/shaders/shader_program_base.hpp",
"include/mbgl/shaders/symbol_layer_ubo.hpp",
"include/mbgl/util/identity.hpp",
"include/mbgl/util/suppress_copies.hpp",
"include/mbgl/style/layers/custom_drawable_layer.hpp",
"include/mbgl/layermanager/custom_drawable_layer_factory.hpp",

View File

@@ -71,7 +71,7 @@ public:
ActorRef<std::decay_t<Object>> self() { return parent.self(); }
private:
std::shared_ptr<Scheduler> retainer;
const std::shared_ptr<Scheduler> retainer;
AspiringActor<Object> parent;
EstablishedActor<Object> target;
};

View File

@@ -1,5 +1,5 @@
#pragma once
#include <atomic>
#include <functional>
#include <memory>
#include <mutex>
@@ -28,6 +28,9 @@ public:
void open(Scheduler& scheduler_);
void close();
// Indicate this mailbox will no longer be checked for messages
void abandon();
bool isOpen() const;
void push(std::unique_ptr<Message>);
@@ -37,11 +40,18 @@ public:
static std::function<void()> makeClosure(std::weak_ptr<Mailbox>);
private:
enum class State : uint32_t {
Idle = 0,
Processing,
Abandoned
};
mapbox::base::WeakPtr<Scheduler> weakScheduler;
std::recursive_mutex receivingMutex;
std::mutex pushingMutex;
std::atomic<State> state{State::Idle};
bool closed{false};
std::mutex queueMutex;

View File

@@ -1,5 +1,7 @@
#pragma once
#include <mbgl/util/chrono.hpp>
#include <mapbox/std/weak.hpp>
#include <functional>
@@ -36,9 +38,12 @@ public:
virtual ~Scheduler() = default;
/// Enqueues a function for execution.
virtual void schedule(std::function<void()>) = 0;
virtual void schedule(std::function<void()>&&) = 0;
/// Makes a weak pointer to this Scheduler.
virtual mapbox::base::WeakPtr<Scheduler> makeWeakPtr() = 0;
/// Enqueues a function for execution on the render thread.
virtual void runOnRenderThread(std::function<void()>&&){};
virtual void runRenderJobs() {}
/// Returns a closure wrapping the given one.
///
@@ -62,6 +67,11 @@ public:
scheduleAndReplyValue(task, reply, GetCurrent()->makeWeakPtr());
}
/// Wait until there's nothing pending or in process
/// Must not be called from a task provided to this scheduler.
/// @param timeout Time to wait, or zero to wait forever.
virtual std::size_t waitForEmpty(Milliseconds timeout = Milliseconds{0}) = 0;
/// Set/Get the current Scheduler for this thread
static Scheduler* GetCurrent();
static void SetCurrent(Scheduler*);
@@ -84,6 +94,9 @@ public:
/// on the same thread-unsafe object.
[[nodiscard]] static std::shared_ptr<Scheduler> GetSequenced();
/// Set a function to be called when an exception occurs on a thread controlled by the scheduler
void setExceptionHandler(std::function<void(const std::exception_ptr)> handler_) { handler = std::move(handler_); }
protected:
template <typename TaskFn, typename ReplyFn>
void scheduleAndReplyValue(const TaskFn& task,
@@ -97,9 +110,10 @@ protected:
};
replyScheduler->schedule(std::move(scheduledReply));
};
schedule(std::move(scheduled));
}
std::function<void(const std::exception_ptr)> handler;
};
} // namespace mbgl

View File

@@ -1,5 +1,6 @@
#pragma once
#include <mbgl/actor/scheduler.hpp>
#include <mbgl/gfx/backend.hpp>
#include <mbgl/gfx/command_encoder.hpp>
#include <mbgl/gfx/draw_scope.hpp>
@@ -53,7 +54,8 @@ using VertexAttributeArrayPtr = std::shared_ptr<VertexAttributeArray>;
class Context {
protected:
Context(uint32_t maximumVertexBindingCount_)
: maximumVertexBindingCount(maximumVertexBindingCount_) {}
: maximumVertexBindingCount(maximumVertexBindingCount_),
backgroundScheduler(Scheduler::GetBackground()) {}
public:
static constexpr const uint32_t minimumRequiredVertexBindingCount = 8;
@@ -161,6 +163,8 @@ protected:
virtual std::unique_ptr<DrawScopeResource> createDrawScopeResource() = 0;
gfx::RenderingStats stats;
std::shared_ptr<Scheduler> backgroundScheduler;
};
} // namespace gfx

View File

@@ -78,9 +78,11 @@ public:
return std::make_unique<WorkRequest>(task);
}
void schedule(std::function<void()> fn) override { invoke(std::move(fn)); }
void schedule(std::function<void()>&& fn) override { invoke(std::move(fn)); }
::mapbox::base::WeakPtr<Scheduler> makeWeakPtr() override { return weakFactory.makeWeakPtr(); }
std::size_t waitForEmpty(Milliseconds timeout) override;
class Impl;
private:

View File

@@ -0,0 +1,21 @@
#pragma once
namespace mbgl {
/// Run a lambda on scope exit
template <typename Func>
struct Scoped {
Scoped(Func&& fn)
: cb(std::move(fn)){};
~Scoped() { cb(); }
Scoped(const Scoped&) = delete;
Scoped(Scoped&&) noexcept = delete;
Scoped& operator=(const Scoped&) = delete;
Scoped& operator=(Scoped&&) noexcept = delete;
private:
Func cb;
};
} // namespace mbgl

View File

@@ -1,10 +1,12 @@
#pragma once
#include <string>
#include <cstdint>
#include <cstdlib>
#include <type_traits>
#include <exception>
#include <sstream>
#include <string>
#include <thread>
#include <type_traits>
// Polyfill needed by Qt when building for Android with GCC
#if defined(__ANDROID__) && defined(__GLIBCXX__)
@@ -76,6 +78,10 @@ inline std::string toString(long double t, bool decimal = false) {
return toString(static_cast<double>(t), decimal);
}
inline std::string toString(std::thread::id threadId) {
return (std::ostringstream() << threadId).str();
}
std::string toString(const std::exception_ptr &);
template <class T>

View File

@@ -60,7 +60,7 @@ ActorRef<Renderer> MapRenderer::actor() const {
return *rendererRef;
}
void MapRenderer::schedule(std::function<void()> scheduled) {
void MapRenderer::schedule(std::function<void()>&& scheduled) {
try {
// Create a runnable
android::UniqueEnv _env = android::AttachEnv();
@@ -84,6 +84,23 @@ void MapRenderer::schedule(std::function<void()> scheduled) {
}
}
std::size_t MapRenderer::waitForEmpty(Milliseconds timeout) {
try {
android::UniqueEnv _env = android::AttachEnv();
static auto& javaClass = jni::Class<MapRenderer>::Singleton(*_env);
static auto waitForEmpty = javaClass.GetMethod<jni::jint(jni::jlong)>(*_env, "waitForEmpty");
if (auto weakReference = javaPeer.get(*_env)) {
return weakReference.Call(*_env, waitForEmpty, static_cast<int64_t>(timeout.count()));
}
// If the peer is already cleaned up, there's nothing to wait for
return 0;
} catch (...) {
Log::Error(Event::Android, "MapRenderer::waitForEmpty failed");
jni::ThrowJavaError(*android::AttachEnv(), std::current_exception());
return 0;
}
}
void MapRenderer::requestRender() {
try {
android::UniqueEnv _env = android::AttachEnv();

View File

@@ -66,9 +66,13 @@ public:
// From Scheduler. Schedules by using callbacks to the
// JVM to process the mailbox on the right thread.
void schedule(std::function<void()> scheduled) override;
void schedule(std::function<void()>&& scheduled) override;
mapbox::base::WeakPtr<Scheduler> makeWeakPtr() override { return weakFactory.makeWeakPtr(); }
// Wait for the queue to be empty
// A timeout of zero results in an unbounded wait
std::size_t waitForEmpty(Milliseconds timeout) override;
void requestRender();
// Snapshot - requires a RunLoop on the calling thread

View File

@@ -120,6 +120,11 @@ public abstract class MapRenderer implements MapRendererScheduler {
this.queueEvent((Runnable) runnable);
}
/// Wait indefinitely for the queue to become empty
public void waitForEmpty() {
waitForEmpty(0);
}
private native void nativeInitialize(MapRenderer self,
float pixelRatio,
String localIdeographFontFamily);

View File

@@ -14,4 +14,9 @@ public interface MapRendererScheduler {
@Keep
void queueEvent(Runnable runnable);
@Keep
void waitForEmpty();
@Keep
long waitForEmpty(long timeoutMillis);
}

View File

@@ -116,4 +116,11 @@ public class GLSurfaceViewMapRenderer extends MapRenderer implements GLSurfaceVi
glSurfaceView.queueEvent(runnable);
}
/**
* {@inheritDoc}
*/
@Override
public long waitForEmpty(long timeoutMillis) {
return glSurfaceView.waitForEmpty(timeoutMillis);
}
}

View File

@@ -317,6 +317,16 @@ public class MapLibreGLSurfaceView extends SurfaceView implements SurfaceHolder.
glThread.queueEvent(r);
}
/**
* Wait for the queue to become empty
* @param timeoutMillis Timeout in milliseconds
* @return Number of queue items remaining
*/
public long waitForEmpty(long timeoutMillis) {
return glThread.waitForEmpty(timeoutMillis);
}
/**
* This method is used as part of the View class and is not normally
* called or subclassed by clients of GLSurfaceView.
@@ -1023,6 +1033,39 @@ public class MapLibreGLSurfaceView extends SurfaceView implements SurfaceHolder.
}
}
/**
* Wait for the queue to become empty
* @param timeoutMillis Timeout in milliseconds, zero for indefinite wait
* @return Number of queue items remaining
*/
public int waitForEmpty(long timeoutMillis) {
final long startTime = System.nanoTime();
synchronized (glThreadManager) {
// Wait for the queue to be empty
while (!this.eventQueue.isEmpty()) {
if (timeoutMillis > 0) {
final long elapsedMillis = (System.nanoTime() - startTime) / 1000 / 1000;
if (elapsedMillis < timeoutMillis) {
try {
glThreadManager.wait(timeoutMillis - elapsedMillis);
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
} else {
break;
}
} else {
try {
glThreadManager.wait();
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
}
}
return this.eventQueue.size();
}
}
// Once the thread is started, all accesses to the following member
// variables are protected by the sGLThreadManager monitor
private boolean shouldExit;

View File

@@ -35,6 +35,7 @@ public class TextureViewMapRenderer extends MapRenderer {
super(context, localIdeographFontFamily);
this.translucentSurface = translucentSurface;
renderThread = new TextureViewRenderThread(textureView, this);
renderThread.setName("TextureViewRenderer");
renderThread.start();
}
@@ -86,6 +87,14 @@ public class TextureViewMapRenderer extends MapRenderer {
renderThread.queueEvent(runnable);
}
/**
* {@inheritDoc}
*/
@Override
public long waitForEmpty(long timeoutMillis) {
return renderThread.waitForEmpty(timeoutMillis);
}
/**
* {@inheritDoc}
*/

View File

@@ -135,6 +135,39 @@ class TextureViewRenderThread extends Thread implements TextureView.SurfaceTextu
}
}
/**
* Wait for the queue to be empty.
* @param timeoutMillis Maximum time to wait, in milliseconds
* @return The number of items remaining in the queue
*/
@UiThread
int waitForEmpty(long timeoutMillis) {
final long startTime = System.nanoTime();
synchronized (lock) {
// Wait for the queue to be empty
while (!this.eventQueue.isEmpty()) {
if (timeoutMillis > 0) {
final long elapsedMillis = (System.nanoTime() - startTime) / 1000 / 1000;
if (elapsedMillis < timeoutMillis) {
try {
lock.wait(timeoutMillis - elapsedMillis);
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
} else {
break;
}
} else {
try {
lock.wait(0);
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
}
}
return this.eventQueue.size();
}
}
@UiThread
void onPause() {

View File

@@ -442,5 +442,10 @@ class NativeMapViewTest : AppCenter() {
override fun queueEvent(runnable: Runnable?) {
// no-op
}
override fun waitForEmpty(timeoutMillis: Long): Long {
// no-op
return 0
}
}
}

View File

@@ -1,11 +1,13 @@
#include "run_loop_impl.hpp"
#include <mbgl/util/platform.hpp>
#include <mbgl/util/thread_local.hpp>
#include <mbgl/util/thread.hpp>
#include <mbgl/util/timer.hpp>
#include <mbgl/actor/scheduler.hpp>
#include <mbgl/util/event.hpp>
#include <mbgl/util/logging.hpp>
#include <mbgl/util/monotonic_timer.hpp>
#include <mbgl/util/platform.hpp>
#include <mbgl/util/thread.hpp>
#include <mbgl/util/thread_local.hpp>
#include <mbgl/util/timer.hpp>
#include <android/looper.h>
@@ -18,7 +20,6 @@
#include <fcntl.h>
#include <unistd.h>
#include <mbgl/util/logging.hpp>
#define PIPE_OUT 0
#define PIPE_IN 1
@@ -167,6 +168,9 @@ void RunLoop::Impl::addRunnable(Runnable* runnable) {
void RunLoop::Impl::removeRunnable(Runnable* runnable) {
std::lock_guard<std::mutex> lock(mutex);
runnables.remove(runnable);
if (runnables.empty()) {
cvEmpty.notify_all();
}
}
Milliseconds RunLoop::Impl::processRunnables() {
@@ -196,6 +200,10 @@ Milliseconds RunLoop::Impl::processRunnables() {
runnable->runTask();
}
if (runnables.empty()) {
cvEmpty.notify_all();
}
if (runnables.empty() || nextDue == TimePoint::max()) {
return Milliseconds(-1);
}
@@ -208,6 +216,25 @@ Milliseconds RunLoop::Impl::processRunnables() {
return timeout;
}
std::size_t RunLoop::Impl::waitForEmpty(Milliseconds timeout) {
const auto startTime = mbgl::util::MonotonicTimer::now();
while (true) {
std::size_t remaining;
{
std::lock_guard<std::mutex> lock(mutex);
remaining = runnables.size();
}
const auto elapsed = mbgl::util::MonotonicTimer::now() - startTime;
const auto elapsedMillis = std::chrono::duration_cast<Milliseconds>(elapsed);
if (remaining == 0 || (Milliseconds::zero() < timeout && timeout <= elapsedMillis)) {
return remaining;
}
runLoop->runOnce();
}
}
RunLoop* RunLoop::Get() {
assert(static_cast<RunLoop*>(Scheduler::GetCurrent()));
return static_cast<RunLoop*>(Scheduler::GetCurrent());
@@ -230,6 +257,10 @@ void RunLoop::wake() {
impl->wake();
}
std::size_t RunLoop::waitForEmpty(std::chrono::milliseconds timeout) {
return impl->waitForEmpty(timeout);
}
void RunLoop::run() {
MBGL_VERIFY_THREAD(tid);

View File

@@ -42,6 +42,8 @@ public:
Milliseconds processRunnables();
std::size_t waitForEmpty(Milliseconds timeout);
ALooper* loop = nullptr;
RunLoop* runLoop = nullptr;
std::atomic<bool> running;
@@ -57,6 +59,7 @@ private:
std::unique_ptr<Thread<Alarm>> alarm;
std::mutex mutex;
std::condition_variable cvEmpty;
std::list<Runnable*> runnables;
};

View File

@@ -1,6 +1,8 @@
#include <mbgl/util/run_loop.hpp>
#include <mbgl/util/async_task.hpp>
#include <mbgl/actor/scheduler.hpp>
#include <mbgl/util/async_task.hpp>
#include <mbgl/util/monotonic_timer.hpp>
#include <mbgl/util/run_loop.hpp>
#include <mbgl/util/string.hpp>
#include <CoreFoundation/CoreFoundation.h>
@@ -45,5 +47,24 @@ void RunLoop::stop() {
invoke([&] { CFRunLoopStop(CFRunLoopGetCurrent()); });
}
std::size_t RunLoop::waitForEmpty(Milliseconds timeout) {
const auto startTime = mbgl::util::MonotonicTimer::now();
while (true) {
std::size_t remaining;
{
std::lock_guard<std::mutex> lock(mutex);
remaining = defaultQueue.size() + highPriorityQueue.size();
}
const auto elapsed = mbgl::util::MonotonicTimer::now() - startTime;
const auto elapsedMillis = std::chrono::duration_cast<std::chrono::milliseconds>(elapsed);
if (remaining == 0 || (Milliseconds::zero() < timeout && timeout <= elapsedMillis)) {
return remaining;
}
runOnce();
}
}
} // namespace util
} // namespace mbgl

View File

@@ -1,7 +1,8 @@
#include <mbgl/util/run_loop.hpp>
#include <mbgl/util/async_task.hpp>
#include <mbgl/util/thread_local.hpp>
#include <mbgl/actor/scheduler.hpp>
#include <mbgl/util/async_task.hpp>
#include <mbgl/util/monotonic_timer.hpp>
#include <mbgl/util/run_loop.hpp>
#include <mbgl/util/thread_local.hpp>
#include <uv.h>
@@ -148,6 +149,25 @@ void RunLoop::stop() {
invoke([&] { uv_unref(impl->holderHandle()); });
}
std::size_t RunLoop::waitForEmpty(Milliseconds timeout) {
const auto startTime = mbgl::util::MonotonicTimer::now();
while (true) {
std::size_t remaining;
{
std::lock_guard<std::mutex> lock(mutex);
remaining = defaultQueue.size() + highPriorityQueue.size();
}
const auto elapsed = mbgl::util::MonotonicTimer::now() - startTime;
const auto elapsedMillis = std::chrono::duration_cast<Milliseconds>(elapsed);
if (remaining == 0 || (Milliseconds::zero() < timeout && timeout <= elapsedMillis)) {
return remaining;
}
runOnce();
}
}
void RunLoop::addWatch(int fd, Event event, std::function<void(int, Event)>&& callback) {
MBGL_VERIFY_THREAD(tid);

View File

@@ -30,8 +30,8 @@ ThreadLocalBase::~ThreadLocalBase() {
}
}
void* ThreadLocalBase::get() {
return pthread_getspecific(reinterpret_cast<pthread_key_t&>(storage));
void* ThreadLocalBase::get() const {
return pthread_getspecific(reinterpret_cast<const pthread_key_t&>(storage));
}
void ThreadLocalBase::set(void* ptr) {

View File

@@ -1,6 +1,7 @@
#include "run_loop_impl.hpp"
#include <mbgl/actor/scheduler.hpp>
#include <mbgl/util/monotonic_timer.hpp>
#include <QCoreApplication>
@@ -90,6 +91,25 @@ void RunLoop::runOnce() {
}
}
std::size_t RunLoop::waitForEmpty(std::chrono::milliseconds timeout) {
const auto startTime = mbgl::util::MonotonicTimer::now();
while (true) {
std::size_t remaining;
{
std::lock_guard<std::mutex> lock(mutex);
remaining = defaultQueue.size() + highPriorityQueue.size();
}
const auto elapsed = mbgl::util::MonotonicTimer::now() - startTime;
const auto elapsedMillis = std::chrono::duration_cast<std::chrono::milliseconds>(elapsed);
if (remaining == 0 || timeout <= elapsedMillis) {
return remaining;
}
runOnce();
}
}
void RunLoop::addWatch(int fd, Event event, std::function<void(int, Event)>&& cb) {
MBGL_VERIFY_THREAD(tid);

View File

@@ -27,8 +27,8 @@ ThreadLocalBase::~ThreadLocalBase() {
reinterpret_cast<StorageType&>(storage).~QThreadStorage();
}
void* ThreadLocalBase::get() {
return reinterpret_cast<StorageType&>(storage).localData()[0];
void* ThreadLocalBase::get() const {
return reinterpret_cast<const StorageType&>(storage).localData()[0];
}
void ThreadLocalBase::set(void* ptr) {

View File

@@ -1,5 +1,6 @@
#include "scheduler.hpp"
#include <mbgl/util/monotonic_timer.hpp>
#include <mbgl/util/util.hpp>
#include <cassert>
@@ -12,7 +13,7 @@ Scheduler::~Scheduler() {
MBGL_VERIFY_THREAD(tid);
}
void Scheduler::schedule(std::function<void()> function) {
void Scheduler::schedule(std::function<void()>&& function) {
const std::lock_guard<std::mutex> lock(m_taskQueueMutex);
m_taskQueue.push(std::move(function));
@@ -26,6 +27,7 @@ void Scheduler::processEvents() {
{
const std::unique_lock<std::mutex> lock(m_taskQueueMutex);
std::swap(taskQueue, m_taskQueue);
pendingItems += taskQueue.size();
}
while (!taskQueue.empty()) {
@@ -34,7 +36,29 @@ void Scheduler::processEvents() {
function();
}
taskQueue.pop();
pendingItems--;
}
cvEmpty.notify_all();
}
std::size_t Scheduler::waitForEmpty(std::chrono::milliseconds timeout) {
MBGL_VERIFY_THREAD(tid);
const auto startTime = mbgl::util::MonotonicTimer::now();
std::unique_lock<std::mutex> lock(m_taskQueueMutex);
const auto isDone = [&] {
return m_taskQueue.empty() && pendingItems == 0;
};
while (!isDone()) {
const auto elapsed = mbgl::util::MonotonicTimer::now() - startTime;
if (timeout <= elapsed || !cvEmpty.wait_for(lock, timeout - elapsed, isDone)) {
assert(isDone());
break;
}
}
return m_taskQueue.size() + pendingItems;
}
} // namespace QMapLibre

View File

@@ -5,6 +5,7 @@
#include <QObject>
#include <condition_variable>
#include <memory>
#include <mutex>
#include <queue>
@@ -19,7 +20,10 @@ public:
~Scheduler() override;
// mbgl::Scheduler implementation.
void schedule(std::function<void()> function) final;
void schedule(std::function<void()>&& function) final;
std::size_t waitForEmpty(std::chrono::milliseconds timeout) override;
mapbox::base::WeakPtr<mbgl::Scheduler> makeWeakPtr() override { return weakFactory.makeWeakPtr(); }
void processEvents();
@@ -31,6 +35,8 @@ private:
MBGL_STORE_THREAD(tid);
std::mutex m_taskQueueMutex;
std::condition_variable cvEmpty;
std::atomic<std::size_t> pendingItems;
std::queue<std::function<void()>> m_taskQueue;
mapbox::base::WeakPtrFactory<Scheduler> weakFactory{this};
};

View File

@@ -10,6 +10,7 @@
#include "thread.h"
#define StorageToThreadInfo reinterpret_cast<THREAD_INFO*&>(storage)
#define StorageToConstThreadInfo reinterpret_cast<THREAD_INFO* const&>(storage)
namespace mbgl {
namespace util {
@@ -47,8 +48,8 @@ ThreadLocalBase::~ThreadLocalBase() {
}
}
void* ThreadLocalBase::get() {
return TlsGetValue(StorageToThreadInfo->key);
void* ThreadLocalBase::get() const {
return TlsGetValue(StorageToConstThreadInfo->key);
}
void ThreadLocalBase::set(void* ptr) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -2,9 +2,7 @@ load("//bazel:flags.bzl", "CPP_FLAGS", "MAPLIBRE_FLAGS")
cc_library(
name = "testutils",
hdrs = [
"include/mbgl/test/util.hpp",
],
hdrs = glob(["include/mbgl/test/*.hpp"]),
strip_include_prefix = "include",
deps = [
"//vendor/googletest:gtest",

View File

@@ -91,7 +91,12 @@ TEST(Actor, DestructionBlocksOnSend) {
~TestScheduler() override { EXPECT_TRUE(waited.load()); }
void schedule(std::function<void()>) final {
std::size_t waitForEmpty(Milliseconds) override {
assert(false);
return 0;
}
void schedule(std::function<void()>&&) final {
promise.set_value();
future.wait();
std::this_thread::sleep_for(1ms);

View File

@@ -0,0 +1,49 @@
#include <mbgl/map/transform.hpp>
#include <mbgl/renderer/tile_parameters.hpp>
#include <mbgl/renderer/image_manager.hpp>
#include <mbgl/style/style.hpp>
#include <mbgl/test/fake_file_source.hpp>
#include <mbgl/text/glyph_manager.hpp>
#include <mbgl/util/run_loop.hpp>
#include <memory>
#include <mbgl/test/util.hpp>
#include <mbgl/util/logging.hpp>
#include <mbgl/util/string.hpp>
namespace mbgl {
class VectorTileTest {
public:
std::shared_ptr<FileSource> fileSource = std::make_shared<FakeFileSource>();
TransformState transformState;
util::RunLoop loop;
style::Style style{fileSource, 1};
AnnotationManager annotationManager{style};
const std::shared_ptr<ImageManager> imageManager = std::make_shared<ImageManager>();
const std::shared_ptr<GlyphManager> glyphManager = std::make_shared<GlyphManager>();
Tileset tileset{{"https://example.com"}, {0, 22}, "none"};
const std::shared_ptr<Scheduler> threadPool = Scheduler::GetBackground();
TileParameters tileParameters{1.0,
MapDebugOptions(),
transformState,
fileSource,
MapMode::Continuous,
annotationManager.makeWeakPtr(),
imageManager,
glyphManager,
0};
~VectorTileTest() {
// Ensure that deferred releases are complete before cleaning up
EXPECT_EQ(0, loop.waitForEmpty(Milliseconds::zero()));
EXPECT_EQ(0, threadPool->waitForEmpty());
}
};
} // namespace mbgl

View File

@@ -1451,11 +1451,14 @@ TEST(Map, KeepRenderData) {
test.map.getStyle().loadURL("maptiler://maps/streets");
const int iterations = 3;
const int resourcesCount = 4 /*tiles*/;
requestsCount = 0;
// Keep render data.
for (int i = 1; i <= iterations; ++i) {
test.frontend.render(test.map);
EXPECT_EQ(resourcesCount, requestsCount);
}
requestsCount = 0;
// Clear render data.
for (int i = 1; i <= iterations; ++i) {

View File

@@ -33,6 +33,8 @@ TEST(ImageManager, Basic) {
ASSERT_TRUE(stored);
EXPECT_EQ(image->image.size, stored->image.size);
}
imageManager.dumpDebugLogs();
}
TEST(ImageManager, AddRemove) {
@@ -79,7 +81,7 @@ TEST(ImageManager, RemoveReleasesBinPackRect) {
class StubImageRequestor : public ImageRequestor {
public:
StubImageRequestor(ImageManager& imageManager_)
StubImageRequestor(std::shared_ptr<ImageManager> imageManager_)
: ImageRequestor(imageManager_) {}
void onImagesAvailable(ImageMap icons,
@@ -95,8 +97,9 @@ public:
TEST(ImageManager, NotifiesRequestorWhenSpriteIsLoaded) {
util::RunLoop runLoop;
ImageManager imageManager;
StubImageRequestor requestor(imageManager);
auto imageManagerPtr = std::make_shared<ImageManager>();
auto& imageManager = *imageManagerPtr;
StubImageRequestor requestor(imageManagerPtr);
bool notified = false;
ImageManagerObserver observer;
@@ -123,8 +126,9 @@ TEST(ImageManager, NotifiesRequestorWhenSpriteIsLoaded) {
}
TEST(ImageManager, NotifiesRequestorImmediatelyIfDependenciesAreSatisfied) {
ImageManager imageManager;
StubImageRequestor requestor(imageManager);
auto imageManagerPtr = std::make_shared<ImageManager>();
auto& imageManager = *imageManagerPtr;
StubImageRequestor requestor(imageManagerPtr);
bool notified = false;
requestor.imagesAvailable = [&](ImageMap, ImageMap, std::unordered_map<std::string, uint32_t>) {
@@ -160,8 +164,9 @@ public:
TEST(ImageManager, OnStyleImageMissingBeforeSpriteLoaded) {
util::RunLoop runLoop;
ImageManager imageManager;
StubImageRequestor requestor(imageManager);
auto imageManagerPtr = std::make_shared<ImageManager>();
auto& imageManager = *imageManagerPtr;
StubImageRequestor requestor(imageManagerPtr);
StubImageManagerObserver observer;
imageManager.setObserver(&observer);
@@ -203,7 +208,7 @@ TEST(ImageManager, OnStyleImageMissingBeforeSpriteLoaded) {
ASSERT_FALSE(requestor.hasPendingRequests());
// Another requestor shall not have pending requests for already obtained images.
StubImageRequestor anotherRequestor(imageManager);
StubImageRequestor anotherRequestor(imageManagerPtr);
imageManager.getImages(anotherRequestor, std::make_pair(dependencies, ++imageCorrelationID));
ASSERT_FALSE(anotherRequestor.hasPendingRequests());
@@ -215,8 +220,9 @@ TEST(ImageManager, OnStyleImageMissingBeforeSpriteLoaded) {
TEST(ImageManager, OnStyleImageMissingAfterSpriteLoaded) {
util::RunLoop runLoop;
ImageManager imageManager;
StubImageRequestor requestor(imageManager);
auto imageManagerPtr = std::make_shared<ImageManager>();
auto& imageManager = *imageManagerPtr;
StubImageRequestor requestor(imageManagerPtr);
StubImageManagerObserver observer;
imageManager.setObserver(&observer);
@@ -251,7 +257,8 @@ TEST(ImageManager, OnStyleImageMissingAfterSpriteLoaded) {
TEST(ImageManager, RemoveUnusedStyleImages) {
util::RunLoop runLoop;
ImageManager imageManager;
auto imageManagerPtr = std::make_shared<ImageManager>();
auto& imageManager = *imageManagerPtr;
StubImageManagerObserver observer;
imageManager.setObserver(&observer);
imageManager.setLoaded(true);
@@ -276,7 +283,7 @@ TEST(ImageManager, RemoveUnusedStyleImages) {
// Single requestor
{
std::unique_ptr<StubImageRequestor> requestor = std::make_unique<StubImageRequestor>(imageManager);
std::unique_ptr<StubImageRequestor> requestor = std::make_unique<StubImageRequestor>(imageManagerPtr);
imageManager.getImages(*requestor, std::make_pair(ImageDependencies{{"missing", ImageType::Icon}}, 0ull));
runLoop.runOnce();
EXPECT_EQ(observer.count, 1);
@@ -296,7 +303,7 @@ TEST(ImageManager, RemoveUnusedStyleImages) {
// Single requestor, exceed cache size limit.
{
std::unique_ptr<StubImageRequestor> requestor = std::make_unique<StubImageRequestor>(imageManager);
std::unique_ptr<StubImageRequestor> requestor = std::make_unique<StubImageRequestor>(imageManagerPtr);
imageManager.getImages(*requestor, std::make_pair(ImageDependencies{{"1024px", ImageType::Icon}}, 0ull));
runLoop.runOnce();
EXPECT_EQ(observer.count, 2);
@@ -311,8 +318,8 @@ TEST(ImageManager, RemoveUnusedStyleImages) {
// Multiple requestors
{
std::unique_ptr<StubImageRequestor> requestor1 = std::make_unique<StubImageRequestor>(imageManager);
std::unique_ptr<StubImageRequestor> requestor2 = std::make_unique<StubImageRequestor>(imageManager);
std::unique_ptr<StubImageRequestor> requestor1 = std::make_unique<StubImageRequestor>(imageManagerPtr);
std::unique_ptr<StubImageRequestor> requestor2 = std::make_unique<StubImageRequestor>(imageManagerPtr);
imageManager.getImages(*requestor1, std::make_pair(ImageDependencies{{"missing", ImageType::Icon}}, 0ull));
imageManager.getImages(*requestor2, std::make_pair(ImageDependencies{{"missing", ImageType::Icon}}, 1ull));
runLoop.runOnce();
@@ -330,9 +337,9 @@ TEST(ImageManager, RemoveUnusedStyleImages) {
// Multiple requestors, check that image resource is not destroyed if there
// is at least 1 requestor that uses it.
std::unique_ptr<StubImageRequestor> requestor = std::make_unique<StubImageRequestor>(imageManager);
std::unique_ptr<StubImageRequestor> requestor = std::make_unique<StubImageRequestor>(imageManagerPtr);
{
std::unique_ptr<StubImageRequestor> requestor1 = std::make_unique<StubImageRequestor>(imageManager);
std::unique_ptr<StubImageRequestor> requestor1 = std::make_unique<StubImageRequestor>(imageManagerPtr);
imageManager.getImages(
*requestor,
std::make_pair(ImageDependencies{{"missing", ImageType::Icon}, {"1024px", ImageType::Icon}}, 0ull));

View File

@@ -62,8 +62,9 @@ public:
TransformState transformState;
Style style{fileSource, 1};
AnnotationManager annotationManager{style};
ImageManager imageManager;
GlyphManager glyphManager;
std::shared_ptr<ImageManager> imageManager = std::make_shared<ImageManager>();
std::shared_ptr<GlyphManager> glyphManager = std::make_shared<GlyphManager>();
std::shared_ptr<Scheduler> threadPool = Scheduler::GetBackground();
TileParameters tileParameters(MapMode mapMode = MapMode::Continuous) {
return {1.0,
@@ -87,6 +88,8 @@ public:
transformState = transform.getState();
}
~SourceTest() { threadPool->waitForEmpty(); }
void run() { loop.run(); }
void end() { loop.stop(); }
@@ -167,7 +170,7 @@ TEST(Source, RasterTileEmpty) {
FAIL() << "Should never be called";
};
auto renderSource = RenderSource::create(source.baseImpl);
auto renderSource = RenderSource::create(source.baseImpl, test.threadPool);
renderSource->setObserver(&test.renderSourceObserver);
renderSource->update(source.baseImpl, layers, true, true, test.tileParameters());
@@ -203,7 +206,7 @@ TEST(Source, RasterDEMTileEmpty) {
FAIL() << "Should never be called";
};
auto renderSource = RenderSource::create(source.baseImpl);
auto renderSource = RenderSource::create(source.baseImpl, test.threadPool);
renderSource->setObserver(&test.renderSourceObserver);
renderSource->update(source.baseImpl, layers, true, true, test.tileParameters());
@@ -241,7 +244,7 @@ TEST(Source, VectorTileEmpty) {
FAIL() << "Should never be called";
};
auto renderSource = RenderSource::create(source.baseImpl);
auto renderSource = RenderSource::create(source.baseImpl, test.threadPool);
renderSource->setObserver(&test.renderSourceObserver);
renderSource->update(source.baseImpl, layers, true, true, test.tileParameters());
@@ -276,7 +279,7 @@ TEST(Source, RasterTileFail) {
test.end();
};
auto renderSource = RenderSource::create(source.baseImpl);
auto renderSource = RenderSource::create(source.baseImpl, test.threadPool);
renderSource->setObserver(&test.renderSourceObserver);
renderSource->update(source.baseImpl, layers, true, true, test.tileParameters());
@@ -311,7 +314,7 @@ TEST(Source, RasterDEMTileFail) {
test.end();
};
auto renderSource = RenderSource::create(source.baseImpl);
auto renderSource = RenderSource::create(source.baseImpl, test.threadPool);
renderSource->setObserver(&test.renderSourceObserver);
renderSource->update(source.baseImpl, layers, true, true, test.tileParameters());
@@ -348,7 +351,7 @@ TEST(Source, VectorTileFail) {
test.end();
};
auto renderSource = RenderSource::create(source.baseImpl);
auto renderSource = RenderSource::create(source.baseImpl, test.threadPool);
renderSource->setObserver(&test.renderSourceObserver);
renderSource->update(source.baseImpl, layers, true, true, test.tileParameters());
@@ -384,7 +387,7 @@ TEST(Source, RasterTileCorrupt) {
test.end();
};
auto renderSource = RenderSource::create(source.baseImpl);
auto renderSource = RenderSource::create(source.baseImpl, test.threadPool);
renderSource->setObserver(&test.renderSourceObserver);
renderSource->update(source.baseImpl, layers, true, true, test.tileParameters());
@@ -421,7 +424,7 @@ TEST(Source, RasterDEMTileCorrupt) {
test.end();
};
auto renderSource = RenderSource::create(source.baseImpl);
auto renderSource = RenderSource::create(source.baseImpl, test.threadPool);
renderSource->setObserver(&test.renderSourceObserver);
renderSource->update(source.baseImpl, layers, true, true, test.tileParameters());
@@ -458,7 +461,7 @@ TEST(Source, VectorTileCorrupt) {
test.end();
};
auto renderSource = RenderSource::create(source.baseImpl);
auto renderSource = RenderSource::create(source.baseImpl, test.threadPool);
renderSource->setObserver(&test.renderSourceObserver);
renderSource->update(source.baseImpl, layers, true, true, test.tileParameters());
@@ -492,7 +495,7 @@ TEST(Source, RasterTileCancel) {
FAIL() << "Should never be called";
};
auto renderSource = RenderSource::create(source.baseImpl);
auto renderSource = RenderSource::create(source.baseImpl, test.threadPool);
renderSource->setObserver(&test.renderSourceObserver);
renderSource->update(source.baseImpl, layers, true, true, test.tileParameters());
@@ -526,7 +529,7 @@ TEST(Source, RasterDEMTileCancel) {
FAIL() << "Should never be called";
};
auto renderSource = RenderSource::create(source.baseImpl);
auto renderSource = RenderSource::create(source.baseImpl, test.threadPool);
renderSource->setObserver(&test.renderSourceObserver);
renderSource->update(source.baseImpl, layers, true, true, test.tileParameters());
@@ -562,7 +565,7 @@ TEST(Source, VectorTileCancel) {
FAIL() << "Should never be called";
};
auto renderSource = RenderSource::create(source.baseImpl);
auto renderSource = RenderSource::create(source.baseImpl, test.threadPool);
renderSource->setObserver(&test.renderSourceObserver);
renderSource->update(source.baseImpl, layers, true, true, test.tileParameters());
@@ -607,7 +610,7 @@ TEST(Source, RasterTileAttribution) {
source.setObserver(&test.styleObserver);
source.loadDescription(*test.fileSource);
auto renderSource = RenderSource::create(source.baseImpl);
auto renderSource = RenderSource::create(source.baseImpl, test.threadPool);
renderSource->update(source.baseImpl, layers, true, true, test.tileParameters());
test.run();
@@ -648,7 +651,7 @@ TEST(Source, RasterDEMTileAttribution) {
source.setObserver(&test.styleObserver);
source.loadDescription(*test.fileSource);
auto renderSource = RenderSource::create(source.baseImpl);
auto renderSource = RenderSource::create(source.baseImpl, test.threadPool);
renderSource->update(source.baseImpl, layers, true, true, test.tileParameters());
test.run();
@@ -741,7 +744,7 @@ TEST(Source, CustomGeometrySourceSetTileData) {
FAIL() << "Should never be called";
};
auto renderSource = RenderSource::create(source.baseImpl);
auto renderSource = RenderSource::create(source.baseImpl, test.threadPool);
renderSource->setObserver(&test.renderSourceObserver);
renderSource->update(source.baseImpl, layers, true, true, test.tileParameters());
@@ -778,8 +781,8 @@ public:
MOCK_METHOD1(tileSetNecessity, void(TileNecessity));
MOCK_METHOD1(tileSetMinimumUpdateInterval, void(Duration));
explicit FakeTileSource(Immutable<style::Source::Impl> impl_)
: RenderTileSetSource(std::move(impl_)) {}
explicit FakeTileSource(Immutable<style::Source::Impl> impl_, std::shared_ptr<Scheduler> threadPool_)
: RenderTileSetSource(std::move(impl_), std::move(threadPool_)) {}
void updateInternal(const Tileset& tileset,
const std::vector<Immutable<style::LayerProperties>>& layers,
const bool needsRendering,
@@ -816,7 +819,7 @@ TEST(Source, InvisibleSourcesTileNecessity) {
VectorSource initialized("source", Tileset{{"tiles"}});
initialized.loadDescription(*test.fileSource);
FakeTileSource renderTilesetSource{initialized.baseImpl};
FakeTileSource renderTilesetSource{initialized.baseImpl, test.threadPool};
RenderSource* renderSource = &renderTilesetSource;
LineLayer layer("id", "source");
Immutable<LayerProperties> layerProperties = makeMutable<LineLayerProperties>(
@@ -839,7 +842,7 @@ TEST(Source, SourceMinimumUpdateInterval) {
VectorSource initialized("source", Tileset{{"tiles"}});
initialized.loadDescription(*test.fileSource);
FakeTileSource renderTilesetSource{initialized.baseImpl};
FakeTileSource renderTilesetSource{initialized.baseImpl, test.threadPool};
RenderSource* renderSource = &renderTilesetSource;
LineLayer layer("id", "source");
Immutable<LayerProperties> layerProperties = makeMutable<LineLayerProperties>(
@@ -882,8 +885,8 @@ TEST(Source, RenderTileSetSourceUpdate) {
class FakeRenderTileSetSource : public RenderTileSetSource {
public:
explicit FakeRenderTileSetSource(Immutable<style::Source::Impl> impl_)
: RenderTileSetSource(std::move(impl_)) {}
explicit FakeRenderTileSetSource(Immutable<style::Source::Impl> impl_, std::shared_ptr<Scheduler> threadPool_)
: RenderTileSetSource(std::move(impl_), std::move(threadPool_)) {}
MOCK_METHOD0(mockedUpdateInternal, void());
@@ -903,7 +906,7 @@ TEST(Source, RenderTileSetSourceUpdate) {
VectorSource initialized("source", Tileset{{"tiles"}});
initialized.loadDescription(*test.fileSource);
FakeRenderTileSetSource renderTilesetSource{initialized.baseImpl};
FakeRenderTileSetSource renderTilesetSource{initialized.baseImpl, test.threadPool};
LineLayer layer("id", "source");
Immutable<LayerProperties> layerProperties = makeMutable<LineLayerProperties>(
@@ -969,7 +972,7 @@ TEST(Source, GeoJSONSourceTilesAfterDataReset) {
auto geoJSONData = GeoJSONData::create(mapbox::geojson::parse(
R"({"geometry": {"type": "Point", "coordinates": [1.1, 1.1]}, "type": "Feature", "properties": {}})"));
source.setGeoJSONData(geoJSONData);
RenderGeoJSONSource renderSource{staticImmutableCast<GeoJSONSource::Impl>(source.baseImpl)};
RenderGeoJSONSource renderSource{staticImmutableCast<GeoJSONSource::Impl>(source.baseImpl), test.threadPool};
CircleLayer layer("id", "source");
Immutable<LayerProperties> layerProperties = makeMutable<CircleLayerProperties>(
@@ -1026,7 +1029,7 @@ TEST(Source, SetMaxParentOverscaleFactor) {
ASSERT_EQ(3, *source.getMaxOverscaleFactorForParentTiles());
source.loadDescription(*test.fileSource);
auto renderSource = RenderSource::create(source.baseImpl);
auto renderSource = RenderSource::create(source.baseImpl, test.threadPool);
renderSource->setObserver(&test.renderSourceObserver);
renderSource->update(source.baseImpl, layers, true, true, test.tileParameters());

View File

@@ -27,8 +27,8 @@ public:
util::RunLoop loop;
style::Style style{fileSource, 1};
AnnotationManager annotationManager{style};
ImageManager imageManager;
GlyphManager glyphManager;
std::shared_ptr<ImageManager> imageManager = std::make_shared<ImageManager>();
std::shared_ptr<GlyphManager> glyphManager = std::make_shared<GlyphManager>();
TileParameters tileParameters{1.0,
MapDebugOptions(),

View File

@@ -27,8 +27,8 @@ public:
util::RunLoop loop;
style::Style style{fileSource, 1};
AnnotationManager annotationManager{style};
ImageManager imageManager;
GlyphManager glyphManager;
std::shared_ptr<ImageManager> imageManager = std::make_shared<ImageManager>();
std::shared_ptr<GlyphManager> glyphManager = std::make_shared<GlyphManager>();
Tileset tileset{{"https://example.com"}, {0, 22}, "none"};
TileParameters tileParameters{1.0,

View File

@@ -21,8 +21,8 @@ public:
util::RunLoop loop;
style::Style style{fileSource, 1};
AnnotationManager annotationManager{style};
ImageManager imageManager;
GlyphManager glyphManager;
std::shared_ptr<ImageManager> imageManager = std::make_shared<ImageManager>();
std::shared_ptr<GlyphManager> glyphManager = std::make_shared<GlyphManager>();
Tileset tileset{{"https://example.com"}, {0, 22}, "none"};
TileParameters tileParameters{1.0,

View File

@@ -21,8 +21,8 @@ public:
util::RunLoop loop;
style::Style style{fileSource, 1};
AnnotationManager annotationManager{style};
ImageManager imageManager;
GlyphManager glyphManager;
std::shared_ptr<ImageManager> imageManager = std::make_shared<ImageManager>();
std::shared_ptr<GlyphManager> glyphManager = std::make_shared<GlyphManager>();
Tileset tileset{{"https://example.com"}, {0, 22}, "none"};
TileParameters tileParameters{1.0,

View File

@@ -7,6 +7,7 @@
#include <mbgl/tile/vector_tile.hpp>
#include <mbgl/tile/vector_tile_data.hpp>
#include <mbgl/actor/scheduler.hpp>
#include <mbgl/annotation/annotation_manager.hpp>
#include <mbgl/geometry/feature_index.hpp>
#include <mbgl/map/transform.hpp>
@@ -16,34 +17,16 @@
#include <mbgl/renderer/tile_parameters.hpp>
#include <mbgl/style/layers/symbol_layer.hpp>
#include <mbgl/style/style.hpp>
#include <mbgl/test/vector_tile_test.hpp>
#include <mbgl/text/glyph_manager.hpp>
#include <mbgl/util/run_loop.hpp>
#include <mbgl/util/identity.hpp>
#include <memory>
using namespace mbgl;
class VectorTileTest {
public:
std::shared_ptr<FileSource> fileSource = std::make_shared<FakeFileSource>();
TransformState transformState;
util::RunLoop loop;
style::Style style{fileSource, 1};
AnnotationManager annotationManager{style};
ImageManager imageManager;
GlyphManager glyphManager;
Tileset tileset{{"https://example.com"}, {0, 22}, "none"};
TileParameters tileParameters{1.0,
MapDebugOptions(),
transformState,
fileSource,
MapMode::Continuous,
annotationManager.makeWeakPtr(),
imageManager,
glyphManager,
0};
};
namespace {
class VectorTileMock : public VectorTile {
public:
@@ -51,16 +34,20 @@ public:
std::string sourceID_,
const TileParameters& parameters,
const Tileset& tileset)
: VectorTile(id_, sourceID_, parameters, tileset) {
: VectorTile(id_, std::move(sourceID_), parameters, tileset) {
renderable = true;
}
util::SimpleIdentity uniqueId;
};
} // namespace
TEST(TileCache, Smoke) {
VectorTileTest test;
TileCache cache(1);
OverscaledTileID id(0, 0, 0);
std::unique_ptr<Tile> tile = std::make_unique<VectorTileMock>(id, "source", test.tileParameters, test.tileset);
TileCache cache(Scheduler::GetBackground(), 1);
const OverscaledTileID id(0, 0, 0);
auto tile = std::make_unique<VectorTileMock>(id, "source", test.tileParameters, test.tileset);
cache.add(id, std::move(tile));
EXPECT_TRUE(cache.has(id));
@@ -70,18 +57,36 @@ TEST(TileCache, Smoke) {
TEST(TileCache, Issue15926) {
VectorTileTest test;
TileCache cache(2);
OverscaledTileID id0(0, 0, 0);
OverscaledTileID id1(1, 0, 0);
std::unique_ptr<Tile> tile1 = std::make_unique<VectorTileMock>(id0, "source", test.tileParameters, test.tileset);
std::unique_ptr<Tile> tile2 = std::make_unique<VectorTileMock>(id0, "source", test.tileParameters, test.tileset);
std::unique_ptr<Tile> tile3 = std::make_unique<VectorTileMock>(id1, "source", test.tileParameters, test.tileset);
TileCache cache(test.threadPool, 2);
const OverscaledTileID id0(0, 0, 0);
const OverscaledTileID id1(1, 0, 0);
auto tile1 = std::make_unique<VectorTileMock>(id0, "source", test.tileParameters, test.tileset);
auto tile2 = std::make_unique<VectorTileMock>(id0, "source", test.tileParameters, test.tileset);
auto tile3 = std::make_unique<VectorTileMock>(id1, "source", test.tileParameters, test.tileset);
auto tile4 = std::make_unique<VectorTileMock>(id0, "source", test.tileParameters, test.tileset);
const auto tile1Id = tile1->uniqueId;
// add
cache.add(id0, std::move(tile1));
EXPECT_TRUE(cache.has(id0));
// adding a key already present doesn't replace the existing item
cache.add(id0, std::move(tile2));
EXPECT_EQ(tile1Id, static_cast<VectorTileMock*>(cache.get(id0))->uniqueId);
// Evict on add
cache.setSize(1);
cache.add(id1, std::move(tile3));
EXPECT_FALSE(cache.has(id0));
EXPECT_TRUE(cache.has(id1));
// Evict due to size limit change
cache.setSize(2);
cache.add(id0, std::move(tile4));
EXPECT_TRUE(cache.has(id0));
EXPECT_TRUE(cache.has(id1));
cache.setSize(1);
// older item should be evicted
EXPECT_TRUE(cache.has(id0));
EXPECT_FALSE(cache.has(id1));
}

View File

@@ -15,35 +15,13 @@
#include <mbgl/geometry/feature_index.hpp>
#include <mbgl/annotation/annotation_manager.hpp>
#include <mbgl/renderer/image_manager.hpp>
#include <mbgl/test/vector_tile_test.hpp>
#include <mbgl/text/glyph_manager.hpp>
#include <memory>
using namespace mbgl;
class VectorTileTest {
public:
std::shared_ptr<FileSource> fileSource = std::make_shared<FakeFileSource>(ResourceOptions::Default(),
ClientOptions());
TransformState transformState;
util::RunLoop loop;
style::Style style{fileSource, 1};
AnnotationManager annotationManager{style};
ImageManager imageManager;
GlyphManager glyphManager;
Tileset tileset{{"https://example.com"}, {0, 22}, "none"};
TileParameters tileParameters{1.0,
MapDebugOptions(),
transformState,
fileSource,
MapMode::Continuous,
annotationManager.makeWeakPtr(),
imageManager,
glyphManager,
0};
};
TEST(VectorTile, setError) {
VectorTileTest test;
VectorTile tile(OverscaledTileID(0, 0, 0), "source", test.tileParameters, test.tileset);

View File

@@ -2,6 +2,7 @@
#include <mbgl/actor/actor_ref.hpp>
#include <mbgl/actor/scheduler.hpp>
#include <mbgl/platform/settings.hpp>
#include <mbgl/test/util.hpp>
#include <mbgl/util/run_loop.hpp>
#include <mbgl/util/timer.hpp>
@@ -134,6 +135,11 @@ TEST(Thread, Concurrency) {
unsigned numMessages = 100000;
std::atomic_uint completed(numMessages);
auto& settings = platform::Settings::getInstance();
if (!settings.get(platform::EXPERIMENTAL_THREAD_PRIORITY_WORKER).getDouble()) {
settings.set(platform::EXPERIMENTAL_THREAD_PRIORITY_WORKER, 0.5);
}
Actor<TestWorker> poolWorker(Scheduler::GetBackground());
auto poolWorkerRef = poolWorker.self();
@@ -328,3 +334,108 @@ TEST(Thread, DeleteBeforeChildStarts) {
// Should process the queue before destruction.
ASSERT_TRUE(flag);
}
TEST(Thread, PoolWait) {
auto pool = Scheduler::GetBackground();
constexpr int threadCount = 10;
for (int i = 0; i < threadCount; ++i) {
pool->schedule([&] { std::this_thread::sleep_for(Milliseconds(100)); });
}
EXPECT_EQ(0, pool->waitForEmpty());
}
TEST(Thread, PoolWaitRecursiveAdd) {
auto pool = Scheduler::GetBackground();
pool->schedule([&] {
// Scheduled tasks can add more tasks
pool->schedule([&] {
std::this_thread::sleep_for(Milliseconds(10));
pool->schedule([&] { std::this_thread::sleep_for(Milliseconds(10)); });
});
std::this_thread::sleep_for(Milliseconds(10));
});
EXPECT_EQ(0, pool->waitForEmpty());
}
TEST(Thread, PoolWaitAdd) {
auto pool = Scheduler::GetBackground();
auto seq = Scheduler::GetSequenced();
// add new tasks every few milliseconds
std::atomic<bool> addActive{true};
std::atomic<int> added{0};
std::atomic<int> executed{0};
seq->schedule([&] {
while (addActive) {
pool->schedule([&] { executed++; });
added++;
}
});
// Wait be sure some are added
while (added < 1) {
std::this_thread::sleep_for(Milliseconds(10));
}
// Add an item that should take long enough to be confident that
// more items would be added by the sequential task if not blocked
pool->schedule([&] { std::this_thread::sleep_for(Milliseconds(100)); });
EXPECT_EQ(0, pool->waitForEmpty());
addActive = false;
EXPECT_EQ(0, pool->waitForEmpty());
}
TEST(Thread, PoolWaitTimeout) {
auto pool = Scheduler::GetBackground();
std::mutex mutex;
{
std::lock_guard<std::mutex> outerLock(mutex);
pool->schedule([&] { std::lock_guard<std::mutex> innerLock(mutex); });
// should always time out
EXPECT_EQ(1, pool->waitForEmpty(Milliseconds(100)));
}
EXPECT_EQ(0, pool->waitForEmpty());
}
TEST(Thread, PoolWaitException) {
auto pool = Scheduler::GetBackground();
std::atomic<int> caught{0};
pool->setExceptionHandler([&](const auto) { caught++; });
constexpr int threadCount = 3;
for (int i = 0; i < threadCount; ++i) {
pool->schedule([=] {
std::this_thread::sleep_for(Milliseconds(i));
if (i & 1) {
throw std::runtime_error("test");
} else {
throw 1;
}
});
}
// Exceptions shouldn't cause deadlocks by, e.g., abandoning locks.
EXPECT_EQ(0, pool->waitForEmpty());
EXPECT_EQ(threadCount, caught);
}
#if defined(NDEBUG)
TEST(Thread, WrongThread) {
auto pool = Scheduler::GetBackground();
// Asserts in debug builds, silently ignored in release.
pool->schedule([&] { EXPECT_EQ(0, pool->waitForEmpty()); });
EXPECT_EQ(0, pool->waitForEmpty());
}
#endif