Asynchronous GeometryTile deletion (#2051)
Co-authored-by: Tim Sylvester <tim@wetdogweather.com>
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
21
include/mbgl/util/scoped.hpp
Normal file
21
include/mbgl/util/scoped.hpp
Normal 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
|
||||
@@ -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>
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -14,4 +14,9 @@ public interface MapRendererScheduler {
|
||||
@Keep
|
||||
void queueEvent(Runnable runnable);
|
||||
|
||||
@Keep
|
||||
void waitForEmpty();
|
||||
|
||||
@Keep
|
||||
long waitForEmpty(long timeoutMillis);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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}
|
||||
*/
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -442,5 +442,10 @@ class NativeMapViewTest : AppCenter() {
|
||||
override fun queueEvent(runnable: Runnable?) {
|
||||
// no-op
|
||||
}
|
||||
|
||||
override fun waitForEmpty(timeoutMillis: Long): Long {
|
||||
// no-op
|
||||
return 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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};
|
||||
};
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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>>&,
|
||||
|
||||
@@ -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>();
|
||||
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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>>&,
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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>,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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&,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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&,
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -29,7 +29,7 @@ class SourcePrepareParameters;
|
||||
|
||||
class TilePyramid {
|
||||
public:
|
||||
TilePyramid();
|
||||
TilePyramid(std::shared_ptr<Scheduler> threadPool_);
|
||||
~TilePyramid();
|
||||
|
||||
bool isLoaded() const;
|
||||
|
||||
@@ -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; });
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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>>&) {}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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); }
|
||||
};
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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);
|
||||
|
||||
49
test/include/mbgl/test/vector_tile_test.hpp
Normal file
49
test/include/mbgl/test/vector_tile_test.hpp
Normal 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
|
||||
@@ -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) {
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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());
|
||||
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user