Asynchronous GeometryTile deletion (#2051)

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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