diff --git a/CMakeLists.txt b/CMakeLists.txt index afdb84b16..7c0935c5f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -217,6 +217,8 @@ list(APPEND INCLUDE_FILES ${PROJECT_SOURCE_DIR}/include/mbgl/actor/established_actor.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/actor/mailbox.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/actor/message.hpp + ${PROJECT_SOURCE_DIR}/include/mbgl/actor/optional_actor.hpp + ${PROJECT_SOURCE_DIR}/include/mbgl/actor/optional_actor_ref.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/actor/scheduler.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/annotation/annotation.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/gfx/backend_scope.hpp diff --git a/bazel/core.bzl b/bazel/core.bzl index e431a9598..a95631ec6 100644 --- a/bazel/core.bzl +++ b/bazel/core.bzl @@ -618,6 +618,8 @@ MLN_CORE_HEADERS = [ "include/mbgl/actor/established_actor.hpp", "include/mbgl/actor/mailbox.hpp", "include/mbgl/actor/message.hpp", + "include/mbgl/actor/optional_actor.hpp", + "include/mbgl/actor/optional_actor_ref.hpp", "include/mbgl/actor/scheduler.hpp", "include/mbgl/annotation/annotation.hpp", "include/mbgl/gfx/backend.hpp", diff --git a/include/mbgl/actor/established_actor.hpp b/include/mbgl/actor/established_actor.hpp index 6eb0fd75e..f06776494 100644 --- a/include/mbgl/actor/established_actor.hpp +++ b/include/mbgl/actor/established_actor.hpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include @@ -31,7 +32,8 @@ public: // Construct the Object from a parameter pack `args` (i.e. `Object(args...)`) template EstablishedActor(Scheduler& scheduler, AspiringActor& parent_, Args&&... args) - requires(std::is_constructible_v || std::is_constructible_v, Args...>) + requires(std::is_constructible_v || std::is_constructible_v, Args...> || + std::is_constructible_v, Args...>) : parent(parent_) { emplaceObject(std::forward(args)...); parent.mailbox->open(scheduler); @@ -40,7 +42,8 @@ public: // Construct the Object from a parameter pack `args` (i.e. `Object(args...)`) template EstablishedActor(const TaggedScheduler& scheduler, AspiringActor& parent_, Args&&... args) - requires(std::is_constructible_v || std::is_constructible_v, Args...>) + requires(std::is_constructible_v || std::is_constructible_v, Args...> || + std::is_constructible_v, Args...>) : parent(parent_) { emplaceObject(std::forward(args)...); parent.mailbox->open(scheduler); @@ -68,6 +71,14 @@ public: } private: + // Enabled for Objects with a constructor taking OptionalActorRef as the first parameter + template + void emplaceObject(Args&&... args_) + requires(std::is_constructible_v, Args...>) + { + new (&parent.objectStorage) Object(OptionalActorRef(parent.self()), std::forward(args_)...); + } + // Enabled for Objects with a constructor taking ActorRef as the first parameter template void emplaceObject(Args&&... args_) diff --git a/include/mbgl/actor/optional_actor.hpp b/include/mbgl/actor/optional_actor.hpp new file mode 100644 index 000000000..f458fa932 --- /dev/null +++ b/include/mbgl/actor/optional_actor.hpp @@ -0,0 +1,82 @@ +#pragma once + +#include +#include + +namespace mbgl { + +class TaggedScheduler; + +template +class OptionalActor { +public: + template + OptionalActor(bool syncObject, Scheduler& scheduler, Args&&... args) { + if (syncObject) { + object = std::make_unique(std::forward(args)...); + selfRef = OptionalActorRef(object->self()); + } else { + actor = std::make_unique>(scheduler, std::forward(args)...); + selfRef = OptionalActorRef(actor->self()); + } + } + + template + OptionalActor(bool syncObject, const TaggedScheduler& scheduler, Args&&... args) { + if (syncObject) { + object = std::make_unique(std::forward(args)...); + selfRef = OptionalActorRef(object->self()); + } else { + actor = std::make_unique>(scheduler, std::forward(args)...); + selfRef = OptionalActorRef(actor->self()); + } + } + + template + OptionalActor(bool syncObject, std::shared_ptr scheduler, Args&&... args) { + if (syncObject) { + object = std::make_unique(std::forward(args)...); + selfRef = OptionalActorRef(object->self()); + } else { + actor = std::make_unique>(scheduler, std::forward(args)...); + selfRef = OptionalActorRef(actor->self()); + } + } + + OptionalActor(const OptionalActor&) = delete; + + const OptionalActorRef& self() { return selfRef; } + +private: + class SyncObject { + public: + template + SyncObject(Args&&... args) + requires(std::is_constructible_v, Args...>) + { + new (&objectStorage) Object(OptionalActorRef(self()), std::forward(args)...); + } + + template + SyncObject(Args&&... args) + requires(std::is_constructible_v) + { + new (&objectStorage) Object(std::forward(args)...); + } + + SyncObject(const SyncObject&) = delete; + + ~SyncObject() { self().~Object(); } + + Object& self() { return reinterpret_cast(objectStorage); } + + private: + std::aligned_storage_t objectStorage; + }; + + std::unique_ptr object; + std::unique_ptr> actor; + OptionalActorRef selfRef; +}; + +} // namespace mbgl diff --git a/include/mbgl/actor/optional_actor_ref.hpp b/include/mbgl/actor/optional_actor_ref.hpp new file mode 100644 index 000000000..f7133d32b --- /dev/null +++ b/include/mbgl/actor/optional_actor_ref.hpp @@ -0,0 +1,40 @@ +#pragma once + +#include + +namespace mbgl { + +class TaggedScheduler; + +template +class OptionalActorRef { +public: + OptionalActorRef() = default; + + OptionalActorRef(bool syncRef, Object& object, std::weak_ptr weakMailbox) { + if (syncRef) { + objectRef = &object; + } else { + actorRef = std::make_optional>(object, weakMailbox); + } + } + + explicit OptionalActorRef(Object& object) { objectRef = &object; } + + explicit OptionalActorRef(ActorRef actorRef_) { actorRef = actorRef_; } + + template + void invoke(Fn fn, Args&&... args) const { + if (objectRef) { + (objectRef->*fn)(std::forward(args)...); + } else if (actorRef) { + actorRef->invoke(fn, std::forward(args)...); + } + } + +private: + Object* objectRef = nullptr; + std::optional> actorRef; +}; + +} // namespace mbgl diff --git a/include/mbgl/style/sources/geojson_source.hpp b/include/mbgl/style/sources/geojson_source.hpp index 30b3344e2..f44ac231e 100644 --- a/include/mbgl/style/sources/geojson_source.hpp +++ b/include/mbgl/style/sources/geojson_source.hpp @@ -35,6 +35,9 @@ struct GeoJSONOptions { using ClusterProperties = std::map; ClusterProperties clusterProperties; + // Update options + bool synchronousUpdate = false; + static Immutable defaultOptions(); }; class GeoJSONData { @@ -46,7 +49,7 @@ public: const Immutable& = GeoJSONOptions::defaultOptions()); virtual ~GeoJSONData() = default; - virtual void getTile(const CanonicalTileID&, const std::function&) = 0; + virtual void getTile(const CanonicalTileID&, const std::function&, bool runSynchronously) = 0; // SuperclusterData virtual Features getChildren(std::uint32_t) = 0; @@ -76,6 +79,8 @@ public: mapbox::base::WeakPtr makeWeakPtr() override { return weakFactory.makeWeakPtr(); } + bool isUpdateSynchronous() const noexcept; + protected: Mutable createMutable() const noexcept final; diff --git a/platform/android/MapLibreAndroid/src/cpp/style/sources/geojson_source.cpp b/platform/android/MapLibreAndroid/src/cpp/style/sources/geojson_source.cpp index 4188db872..c151313d8 100644 --- a/platform/android/MapLibreAndroid/src/cpp/style/sources/geojson_source.cpp +++ b/platform/android/MapLibreAndroid/src/cpp/style/sources/geojson_source.cpp @@ -242,6 +242,10 @@ void GeoJSONSource::setCollectionSync(jni::JNIEnv& env, const jni::Object()->isUpdateSynchronous(); +} + void GeoJSONSource::registerNative(jni::JNIEnv& env) { // Lookup the class static auto& javaClass = jni::Class::Singleton(env); @@ -269,7 +273,8 @@ void GeoJSONSource::registerNative(jni::JNIEnv& env) { METHOD(&GeoJSONSource::querySourceFeatures, "querySourceFeatures"), METHOD(&GeoJSONSource::getClusterChildren, "nativeGetClusterChildren"), METHOD(&GeoJSONSource::getClusterLeaves, "nativeGetClusterLeaves"), - METHOD(&GeoJSONSource::getClusterExpansionZoom, "nativeGetClusterExpansionZoom")); + METHOD(&GeoJSONSource::getClusterExpansionZoom, "nativeGetClusterExpansionZoom"), + METHOD(&GeoJSONSource::isUpdateSynchronous, "nativeIsUpdateSynchronous")); } void FeatureConverter::convertJson(std::shared_ptr json, ActorRef callback) { diff --git a/platform/android/MapLibreAndroid/src/cpp/style/sources/geojson_source.hpp b/platform/android/MapLibreAndroid/src/cpp/style/sources/geojson_source.hpp index 6722cb7cb..49591e3a3 100644 --- a/platform/android/MapLibreAndroid/src/cpp/style/sources/geojson_source.hpp +++ b/platform/android/MapLibreAndroid/src/cpp/style/sources/geojson_source.hpp @@ -89,6 +89,8 @@ private: template void setCollectionSync(jni::JNIEnv&, const jni::Object&); + jboolean isUpdateSynchronous(jni::JNIEnv&); + }; // class GeoJSONSource } // namespace android diff --git a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/location/LayerSourceProvider.java b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/location/LayerSourceProvider.java index ecc0e6f40..9be0e5faf 100644 --- a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/location/LayerSourceProvider.java +++ b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/location/LayerSourceProvider.java @@ -23,10 +23,15 @@ class LayerSourceProvider { @NonNull GeoJsonSource generateSource(Feature locationFeature) { + return generateSource(locationFeature, false); + } + + @NonNull + GeoJsonSource generateSource(Feature locationFeature, Boolean synchronousUpdate) { return new GeoJsonSource( LocationComponentConstants.LOCATION_SOURCE, locationFeature, - new GeoJsonOptions().withMaxZoom(16) + new GeoJsonOptions().withMaxZoom(16).withSynchronousUpdate(synchronousUpdate) ); } diff --git a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/location/SymbolLocationLayerRenderer.java b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/location/SymbolLocationLayerRenderer.java index 55ffde470..a647b7534 100644 --- a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/location/SymbolLocationLayerRenderer.java +++ b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/location/SymbolLocationLayerRenderer.java @@ -318,7 +318,7 @@ final class SymbolLocationLayerRenderer implements LocationLayerRenderer { } private void addLocationSource() { - locationSource = layerSourceProvider.generateSource(locationFeature); + locationSource = layerSourceProvider.generateSource(locationFeature, true); style.addSource(locationSource); } diff --git a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/style/sources/GeoJsonOptions.kt b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/style/sources/GeoJsonOptions.kt index 773fb3311..c206551d0 100644 --- a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/style/sources/GeoJsonOptions.kt +++ b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/style/sources/GeoJsonOptions.kt @@ -131,4 +131,15 @@ class GeoJsonOptions : HashMap() { this["clusterProperties"] = properties return this } + + /** + * Set a flag defining whether or not the fetched tiles for the given source + * should be synchronously updated on the render thread + * + * @param value current setting for synchronous. Defaults to false. + */ + fun withSynchronousUpdate(value: Boolean): GeoJsonOptions { + this["synchronousUpdate"] = value + return this + } } diff --git a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/style/sources/GeoJsonSource.kt b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/style/sources/GeoJsonSource.kt index 380f37ff1..7919bfebf 100644 --- a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/style/sources/GeoJsonSource.kt +++ b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/style/sources/GeoJsonSource.kt @@ -228,36 +228,49 @@ class GeoJsonSource : Source { } /** - * Updates the GeoJson with a single feature. The update is performed asynchronously, - * so the data won't be immediately visible or available to query when this method returns. + * Updates the GeoJson with a single feature. + * The update is performed synchronously or asynchronously, based on the source synchronous update flag. + * In the case of asynchronous updates, the data won't be immediately visible or available to query when this method returns. * * @param feature the GeoJSON [Feature] to set */ + fun setGeoJson(feature: Feature?) { if (detached) { return } checkThread() - nativeSetFeature(feature) + if (nativeIsUpdateSynchronous()) { + nativeSetFeatureSync(feature) + } else { + nativeSetFeature(feature) + } } /** - * Updates the GeoJson with a single geometry. The update is performed asynchronously, - * so the data won't be immediately visible or available to query when this method returns. + * Updates the GeoJson with a single geometry. + * The update is performed synchronously or asynchronously, based on the source synchronous update flag. + * In the case of asynchronous updates, the data won't be immediately visible or available to query when this method returns. * * @param geometry the GeoJSON [Geometry] to set */ + fun setGeoJson(geometry: Geometry?) { if (detached) { return } checkThread() - nativeSetGeometry(geometry) + if (nativeIsUpdateSynchronous()) { + nativeSetGeometrySync(geometry) + } else { + nativeSetGeometry(geometry) + } } /** - * Updates the GeoJson. The update is performed asynchronously, - * so the data won't be immediately visible or available to query when this method returns. + * Updates the GeoJson. + * The update is performed synchronously or asynchronously, based on the source synchronous update flag. + * In the case of asynchronous updates, the data won't be immediately visible or available to query when this method returns. * * @param featureCollection the GeoJSON FeatureCollection */ @@ -266,15 +279,37 @@ class GeoJsonSource : Source { return } checkThread() + var featureCollection = featureCollection if (featureCollection != null && featureCollection.features() != null) { val features = featureCollection.features() val featuresCopy: List = ArrayList(features) - nativeSetFeatureCollection(FeatureCollection.fromFeatures(featuresCopy)) + featureCollection = FeatureCollection.fromFeatures(featuresCopy) + } + if (nativeIsUpdateSynchronous()) { + nativeSetFeatureCollectionSync(featureCollection) } else { nativeSetFeatureCollection(featureCollection) } } + /** + * Updates the GeoJson. + * The update is performed synchronously or asynchronously, based on the source synchronous update flag. + * In the case of asynchronous updates, the data won't be immediately visible or available to query when this method returns. + * + * @param json the raw GeoJson FeatureCollection string + */ + fun setGeoJson(json: String) { + if (detached) { + return + } + checkThread() + if (nativeIsUpdateSynchronous()) { + nativeSetGeoJsonStringSync(json) + } else { + nativeSetGeoJsonString(json) + } + } /** * Updates the GeoJson with a single feature. The update is performed synchronously, @@ -282,6 +317,7 @@ class GeoJsonSource : Source { * * @param feature the GeoJSON [Feature] to set */ + @Deprecated("use {@link GeoJsonOptions#withSynchronousUpdate(Boolean)} to enable synchronous updates on construction") fun setGeoJsonSync(feature: Feature?) { if (detached) { return @@ -296,6 +332,7 @@ class GeoJsonSource : Source { * * @param geometry the GeoJSON [Geometry] to set */ + @Deprecated("use {@link GeoJsonOptions#withSynchronousUpdate(Boolean)} to enable synchronous updates on construction") fun setGeoJsonSync(geometry: Geometry?) { if (detached) { return @@ -310,6 +347,7 @@ class GeoJsonSource : Source { * * @param featureCollection the GeoJSON FeatureCollection */ + @Deprecated("use {@link GeoJsonOptions#withSynchronousUpdate(Boolean)} to enable synchronous updates on construction") fun setGeoJsonSync(featureCollection: FeatureCollection?) { if (detached) { return @@ -324,26 +362,13 @@ class GeoJsonSource : Source { } } - /** - * Updates the GeoJson. The update is performed asynchronously, - * so the data won't be immediately visible or available to query when this method returns. - * - * @param json the raw GeoJson FeatureCollection string - */ - fun setGeoJson(json: String) { - if (detached) { - return - } - checkThread() - nativeSetGeoJsonString(json) - } - /** * Updates the GeoJson. The update is performed synchronously, * so the data will be immediately visible and available to query when this method returns. * * @param json the raw GeoJson FeatureCollection string */ + @Deprecated("use {@link GeoJsonOptions#withSynchronousUpdate(Boolean)} to enable synchronous updates on construction") fun setGeoJsonSync(json: String) { if (detached) { return @@ -567,6 +592,9 @@ class GeoJsonSource : Source { @Keep private external fun nativeGetClusterExpansionZoom(feature: Feature): Int + @Keep + private external fun nativeIsUpdateSynchronous(): Boolean + @Keep @Throws(Throwable::class) protected external fun finalize() diff --git a/platform/android/MapLibreAndroid/src/test/java/org/maplibre/android/location/LocationLayerControllerTest.kt b/platform/android/MapLibreAndroid/src/test/java/org/maplibre/android/location/LocationLayerControllerTest.kt index 721b3e4c1..229e1325e 100644 --- a/platform/android/MapLibreAndroid/src/test/java/org/maplibre/android/location/LocationLayerControllerTest.kt +++ b/platform/android/MapLibreAndroid/src/test/java/org/maplibre/android/location/LocationLayerControllerTest.kt @@ -42,7 +42,8 @@ class LocationLayerControllerTest : BaseTest() { sourceProvider.generateSource( ArgumentMatchers.any( Feature::class.java - ) + ), + ArgumentMatchers.eq(true) ) ).thenReturn(locationSource) val bitmapProvider = Mockito.mock( @@ -80,7 +81,8 @@ class LocationLayerControllerTest : BaseTest() { sourceProvider.generateSource( ArgumentMatchers.any( Feature::class.java - ) + ), + ArgumentMatchers.eq(true) ) ).thenReturn(locationSource) val bitmapProvider = Mockito.mock( @@ -119,7 +121,8 @@ class LocationLayerControllerTest : BaseTest() { sourceProvider.generateSource( ArgumentMatchers.any( Feature::class.java - ) + ), + ArgumentMatchers.eq(true) ) ).thenReturn(locationSource) val bitmapProvider = Mockito.mock( @@ -158,7 +161,8 @@ class LocationLayerControllerTest : BaseTest() { sourceProvider.generateSource( ArgumentMatchers.any( Feature::class.java - ) + ), + ArgumentMatchers.eq(true) ) ).thenReturn(locationSource) val bitmapProvider = Mockito.mock( @@ -197,7 +201,8 @@ class LocationLayerControllerTest : BaseTest() { sourceProvider.generateSource( ArgumentMatchers.any( Feature::class.java - ) + ), + ArgumentMatchers.eq(true) ) ).thenReturn(locationSource) val bitmapProvider = Mockito.mock( @@ -236,7 +241,8 @@ class LocationLayerControllerTest : BaseTest() { sourceProvider.generateSource( ArgumentMatchers.any( Feature::class.java - ) + ), + ArgumentMatchers.eq(true) ) ).thenReturn(locationSource) val bitmapProvider = Mockito.mock( @@ -274,7 +280,8 @@ class LocationLayerControllerTest : BaseTest() { sourceProvider.generateSource( ArgumentMatchers.any( Feature::class.java - ) + ), + ArgumentMatchers.eq(true) ) ).thenReturn(locationSource) val bitmapProvider = Mockito.mock( @@ -308,7 +315,8 @@ class LocationLayerControllerTest : BaseTest() { sourceProvider.generateSource( ArgumentMatchers.any( Feature::class.java - ) + ), + ArgumentMatchers.eq(true) ) ).thenReturn(locationSource) val layerSet: Set = HashSet() @@ -343,7 +351,8 @@ class LocationLayerControllerTest : BaseTest() { sourceProvider.generateSource( ArgumentMatchers.any( Feature::class.java - ) + ), + ArgumentMatchers.eq(true) ) ).thenReturn( Mockito.mock( @@ -390,7 +399,8 @@ class LocationLayerControllerTest : BaseTest() { sourceProvider.generateSource( ArgumentMatchers.any( Feature::class.java - ) + ), + ArgumentMatchers.eq(true) ) ).thenReturn( Mockito.mock( @@ -436,7 +446,8 @@ class LocationLayerControllerTest : BaseTest() { sourceProvider.generateSource( ArgumentMatchers.any( Feature::class.java - ) + ), + ArgumentMatchers.eq(true) ) ).thenReturn( Mockito.mock( @@ -478,7 +489,8 @@ class LocationLayerControllerTest : BaseTest() { sourceProvider.generateSource( ArgumentMatchers.any( Feature::class.java - ) + ), + ArgumentMatchers.eq(true) ) ).thenReturn( Mockito.mock( @@ -520,7 +532,8 @@ class LocationLayerControllerTest : BaseTest() { sourceProvider.generateSource( ArgumentMatchers.any( Feature::class.java - ) + ), + ArgumentMatchers.eq(true) ) ).thenReturn( Mockito.mock( @@ -562,7 +575,8 @@ class LocationLayerControllerTest : BaseTest() { sourceProvider.generateSource( ArgumentMatchers.any( Feature::class.java - ) + ), + ArgumentMatchers.eq(true) ) ).thenReturn( Mockito.mock( @@ -604,7 +618,8 @@ class LocationLayerControllerTest : BaseTest() { sourceProvider.generateSource( ArgumentMatchers.any( Feature::class.java - ) + ), + ArgumentMatchers.eq(true) ) ).thenReturn( Mockito.mock( @@ -678,7 +693,8 @@ class LocationLayerControllerTest : BaseTest() { sourceProvider.generateSource( ArgumentMatchers.any( Feature::class.java - ) + ), + ArgumentMatchers.eq(true) ) ).thenReturn( Mockito.mock( @@ -762,7 +778,8 @@ class LocationLayerControllerTest : BaseTest() { sourceProvider.generateSource( ArgumentMatchers.any( Feature::class.java - ) + ), + ArgumentMatchers.eq(true) ) ).thenReturn( Mockito.mock( @@ -824,7 +841,8 @@ class LocationLayerControllerTest : BaseTest() { sourceProvider.generateSource( ArgumentMatchers.any( Feature::class.java - ) + ), + ArgumentMatchers.eq(true) ) ).thenReturn( Mockito.mock( @@ -886,7 +904,8 @@ class LocationLayerControllerTest : BaseTest() { sourceProvider.generateSource( ArgumentMatchers.any( Feature::class.java - ) + ), + ArgumentMatchers.eq(true) ) ).thenReturn(locationSource) val bitmapProvider = Mockito.mock( @@ -928,7 +947,8 @@ class LocationLayerControllerTest : BaseTest() { sourceProvider.generateSource( ArgumentMatchers.any( Feature::class.java - ) + ), + ArgumentMatchers.eq(true) ) ).thenReturn(locationSource) val bitmapProvider = Mockito.mock( @@ -973,7 +993,8 @@ class LocationLayerControllerTest : BaseTest() { sourceProvider.generateSource( ArgumentMatchers.any( Feature::class.java - ) + ), + ArgumentMatchers.eq(true) ) ).thenReturn(locationSource) val bitmapProvider = Mockito.mock( @@ -1017,7 +1038,8 @@ class LocationLayerControllerTest : BaseTest() { sourceProvider.generateSource( ArgumentMatchers.any( Feature::class.java - ) + ), + ArgumentMatchers.eq(true) ) ).thenReturn(locationSource) val bitmapProvider = Mockito.mock( @@ -1062,7 +1084,8 @@ class LocationLayerControllerTest : BaseTest() { sourceProvider.generateSource( ArgumentMatchers.any( Feature::class.java - ) + ), + ArgumentMatchers.eq(true) ) ).thenReturn(locationSource) val bitmapProvider = Mockito.mock( @@ -1109,7 +1132,8 @@ class LocationLayerControllerTest : BaseTest() { sourceProvider.generateSource( ArgumentMatchers.any( Feature::class.java - ) + ), + ArgumentMatchers.eq(true) ) ).thenReturn(locationSource) val bitmapProvider = Mockito.mock( @@ -1155,7 +1179,8 @@ class LocationLayerControllerTest : BaseTest() { sourceProvider.generateSource( ArgumentMatchers.any( Feature::class.java - ) + ), + ArgumentMatchers.eq(true) ) ).thenReturn(locationSource) val bitmapProvider = Mockito.mock( @@ -1202,7 +1227,8 @@ class LocationLayerControllerTest : BaseTest() { sourceProvider.generateSource( ArgumentMatchers.any( Feature::class.java - ) + ), + ArgumentMatchers.eq(true) ) ).thenReturn(locationSource) val bitmapProvider = Mockito.mock( @@ -1247,7 +1273,8 @@ class LocationLayerControllerTest : BaseTest() { sourceProvider.generateSource( ArgumentMatchers.any( Feature::class.java - ) + ), + ArgumentMatchers.eq(true) ) ).thenReturn(locationSource) val bitmapProvider = Mockito.mock( @@ -1295,7 +1322,8 @@ class LocationLayerControllerTest : BaseTest() { sourceProvider.generateSource( ArgumentMatchers.any( Feature::class.java - ) + ), + ArgumentMatchers.eq(true) ) ).thenReturn(locationSource) val bitmapProvider = Mockito.mock( @@ -1332,7 +1360,8 @@ class LocationLayerControllerTest : BaseTest() { sourceProvider.generateSource( ArgumentMatchers.any( Feature::class.java - ) + ), + ArgumentMatchers.eq(true) ) ).thenReturn(locationSource) val bitmapProvider = Mockito.mock( @@ -1368,7 +1397,8 @@ class LocationLayerControllerTest : BaseTest() { sourceProvider.generateSource( ArgumentMatchers.any( Feature::class.java - ) + ), + ArgumentMatchers.eq(true) ) ).thenReturn( Mockito.mock( diff --git a/platform/android/MapLibreAndroidTestApp/src/androidTest/java/org/maplibre/android/testapp/style/GeoJsonSourceTests.java b/platform/android/MapLibreAndroidTestApp/src/androidTest/java/org/maplibre/android/testapp/style/GeoJsonSourceTests.java index 9de93454c..f5e5d247f 100644 --- a/platform/android/MapLibreAndroidTestApp/src/androidTest/java/org/maplibre/android/testapp/style/GeoJsonSourceTests.java +++ b/platform/android/MapLibreAndroidTestApp/src/androidTest/java/org/maplibre/android/testapp/style/GeoJsonSourceTests.java @@ -2,6 +2,7 @@ package org.maplibre.android.testapp.style; import org.junit.Ignore; import org.maplibre.android.style.sources.CannotAddSourceException; +import org.maplibre.android.style.sources.GeoJsonOptions; import org.maplibre.geojson.Feature; import org.maplibre.geojson.FeatureCollection; import org.maplibre.geojson.Point; @@ -95,15 +96,15 @@ public class GeoJsonSourceTests extends EspressoTest { public void testUpdateCoalescingSync() { validateTestSetup(); MapLibreMapAction.invoke(maplibreMap, (uiController, maplibreMap) -> { - GeoJsonSource source = new GeoJsonSource("source"); + GeoJsonSource source = new GeoJsonSource("source", new GeoJsonOptions().withSynchronousUpdate(true)); maplibreMap.getStyle().addSource(source); maplibreMap.getStyle().addLayer(new CircleLayer("layer", source.getId())); - source.setGeoJsonSync(Point.fromLngLat(0, 0)); - source.setGeoJsonSync(Point.fromLngLat(-25, -25)); - source.setGeoJsonSync(ResourceUtils.readRawResource(rule.getActivity(), R.raw.test_feature_properties)); + source.setGeoJson(Point.fromLngLat(0, 0)); + source.setGeoJson(Point.fromLngLat(-25, -25)); + source.setGeoJson(ResourceUtils.readRawResource(rule.getActivity(), R.raw.test_feature_properties)); - source.setGeoJsonSync(Point.fromLngLat(20, 55)); + source.setGeoJson(Point.fromLngLat(20, 55)); TestingAsyncUtils.INSTANCE.waitForLayer(uiController, mapView); assertEquals(1, maplibreMap.queryRenderedFeatures( maplibreMap.getProjection().toScreenLocation( @@ -215,12 +216,12 @@ public class GeoJsonSourceTests extends EspressoTest { protected void testFeatureFromResourceSync(final @RawRes int resource) { validateTestSetup(); MapLibreMapAction.invoke(maplibreMap, (uiController, maplibreMap) -> { - GeoJsonSource source = new GeoJsonSource("source"); + GeoJsonSource source = new GeoJsonSource("source", new GeoJsonOptions().withSynchronousUpdate(true)); maplibreMap.getStyle().addSource(source); Layer layer = new CircleLayer("layer", source.getId()); maplibreMap.getStyle().addLayer(layer); - source.setGeoJsonSync(Feature.fromJson(ResourceUtils.readRawResource(rule.getActivity(), resource))); + source.setGeoJson(Feature.fromJson(ResourceUtils.readRawResource(rule.getActivity(), resource))); maplibreMap.getStyle().removeLayer(layer); maplibreMap.getStyle().removeSource(source); @@ -262,7 +263,7 @@ public class GeoJsonSourceTests extends EspressoTest { String geoJsonString = "{\"type\":\"Feature\",\"geometry\":{\"type\":\"Point\",\"coordinates\":[0,0]}}"; for (int i = 0; i < 1000; i++) { // Create a GeoJSON source with the same ID each iteration - GeoJsonSource source = new GeoJsonSource("source"); + GeoJsonSource source = new GeoJsonSource("source", new GeoJsonOptions().withSynchronousUpdate(true)); try { maplibreMap.getStyle().addSource(source); } catch (CannotAddSourceException ex) { @@ -270,7 +271,7 @@ public class GeoJsonSourceTests extends EspressoTest { } // Schedule a sync update via setGeoJson(String) - source.setGeoJsonSync(geoJsonString); + source.setGeoJson(geoJsonString); } }); } diff --git a/platform/darwin/src/MLNShapeSource.h b/platform/darwin/src/MLNShapeSource.h index 2b6188ac8..ef7cbb162 100644 --- a/platform/darwin/src/MLNShapeSource.h +++ b/platform/darwin/src/MLNShapeSource.h @@ -163,6 +163,12 @@ FOUNDATION_EXTERN MLN_EXPORT const MLNShapeSourceOption MLNShapeSourceOptionSimp */ FOUNDATION_EXTERN MLN_EXPORT const MLNShapeSourceOption MLNShapeSourceOptionLineDistanceMetrics; +/** + An `NSNumber` object containing a Boolean flag defining whether or not the fetched tiles for the + given source should be synchronously updated on the render thread. The default value is `NO`. + */ +FOUNDATION_EXTERN MLN_EXPORT const MLNShapeSourceOption MLNShapeSourceOptionSynchronousUpdate; + /** ``MLNShapeSource`` is a map content source that supplies vector shapes to be shown on the map. The shapes may be instances of ``MLNShape`` or ``MLNShape``, diff --git a/platform/darwin/src/MLNShapeSource.mm b/platform/darwin/src/MLNShapeSource.mm index d93442013..0e3cbf225 100644 --- a/platform/darwin/src/MLNShapeSource.mm +++ b/platform/darwin/src/MLNShapeSource.mm @@ -27,6 +27,7 @@ const MLNShapeSourceOption MLNShapeSourceOptionMaximumZoomLevelForClustering = @ const MLNShapeSourceOption MLNShapeSourceOptionMinimumZoomLevel = @"MLNShapeSourceOptionMinimumZoomLevel"; const MLNShapeSourceOption MLNShapeSourceOptionSimplificationTolerance = @"MLNShapeSourceOptionSimplificationTolerance"; const MLNShapeSourceOption MLNShapeSourceOptionLineDistanceMetrics = @"MLNShapeSourceOptionLineDistanceMetrics"; +const MLNShapeSourceOption MLNShapeSourceOptionSynchronousUpdate = @"MLNShapeSourceOptionSynchronousUpdate"; mbgl::Immutable MLNGeoJSONOptionsFromDictionary(NSDictionary *options) { auto geoJSONOptions = mbgl::makeMutable(); @@ -154,6 +155,14 @@ mbgl::Immutable MLNGeoJSONOptionsFromDictionary(NSD geoJSONOptions->lineMetrics = value.boolValue; } + if (NSNumber *value = options[MLNShapeSourceOptionSynchronousUpdate]) { + if (![value isKindOfClass:[NSNumber class]]) { + [NSException raise:NSInvalidArgumentException + format:@"MLNShapeSourceOptionSynchronousUpdate must be an NSNumber."]; + } + geoJSONOptions->synchronousUpdate = value.boolValue; + } + return geoJSONOptions; } diff --git a/platform/darwin/test/MLNShapeSourceTests.mm b/platform/darwin/test/MLNShapeSourceTests.mm index 4dd18ed61..12a228e3c 100644 --- a/platform/darwin/test/MLNShapeSourceTests.mm +++ b/platform/darwin/test/MLNShapeSourceTests.mm @@ -24,7 +24,8 @@ MLNShapeSourceOptionMaximumZoomLevel: @99, MLNShapeSourceOptionBuffer: @1976, MLNShapeSourceOptionSimplificationTolerance: @0.42, - MLNShapeSourceOptionLineDistanceMetrics: @YES}; + MLNShapeSourceOptionLineDistanceMetrics: @YES, + MLNShapeSourceOptionSynchronousUpdate: @YES}; auto mbglOptions = MLNGeoJSONOptionsFromDictionary(options); XCTAssertTrue(mbglOptions->cluster); @@ -36,6 +37,7 @@ XCTAssertEqual(mbglOptions->tolerance, 0.42); XCTAssertTrue(mbglOptions->lineMetrics); XCTAssertTrue(!mbglOptions->clusterProperties.empty()); + XCTAssertTrue(mbglOptions->synchronousUpdate); options = @{MLNShapeSourceOptionClustered: @"number 1"}; XCTAssertThrows(MLNGeoJSONOptionsFromDictionary(options)); diff --git a/src/mbgl/renderer/render_orchestrator.cpp b/src/mbgl/renderer/render_orchestrator.cpp index 6859fa425..72de620d4 100644 --- a/src/mbgl/renderer/render_orchestrator.cpp +++ b/src/mbgl/renderer/render_orchestrator.cpp @@ -184,21 +184,21 @@ std::unique_ptr RenderOrchestrator::createRenderTree( PropertyEvaluationParameters evaluationParameters{zoomHistory, updateParameters->timePoint, transitionDuration}; evaluationParameters.zoomChanged = zoomChanged; - const TileParameters tileParameters{.pixelRatio = updateParameters->pixelRatio, - .debugOptions = updateParameters->debugOptions, - .transformState = updateParameters->transformState, - .fileSource = updateParameters->fileSource, - .mode = updateParameters->mode, - .annotationManager = updateParameters->annotationManager, - .imageManager = imageManager, - .glyphManager = glyphManager, - .prefetchZoomDelta = updateParameters->prefetchZoomDelta, - .threadPool = threadPool, - .tileLodMinRadius = updateParameters->tileLodMinRadius, - .tileLodScale = updateParameters->tileLodScale, - .tileLodPitchThreshold = updateParameters->tileLodPitchThreshold, - .tileLodZoomShift = updateParameters->tileLodZoomShift, - .dynamicTextureAtlas = dynamicTextureAtlas}; + TileParameters tileParameters{.pixelRatio = updateParameters->pixelRatio, + .debugOptions = updateParameters->debugOptions, + .transformState = updateParameters->transformState, + .fileSource = updateParameters->fileSource, + .mode = updateParameters->mode, + .annotationManager = updateParameters->annotationManager, + .imageManager = imageManager, + .glyphManager = glyphManager, + .prefetchZoomDelta = updateParameters->prefetchZoomDelta, + .threadPool = threadPool, + .tileLodMinRadius = updateParameters->tileLodMinRadius, + .tileLodScale = updateParameters->tileLodScale, + .tileLodPitchThreshold = updateParameters->tileLodPitchThreshold, + .tileLodZoomShift = updateParameters->tileLodZoomShift, + .dynamicTextureAtlas = dynamicTextureAtlas}; glyphManager->setURL(updateParameters->glyphURL); glyphManager->setFontFaces(updateParameters->fontFaces); @@ -410,6 +410,7 @@ std::unique_ptr RenderOrchestrator::createRenderTree( updateList[index] = true; } } + tileParameters.isUpdateSynchronous = sourceImpl->isUpdateSynchronous(); source->update(sourceImpl, filteredLayersForSource, sourceNeedsRendering, sourceNeedsRelayout, tileParameters); filteredLayersForSource.clear(); diff --git a/src/mbgl/renderer/sources/render_geojson_source.cpp b/src/mbgl/renderer/sources/render_geojson_source.cpp index 033822540..a7bbdcfc3 100644 --- a/src/mbgl/renderer/sources/render_geojson_source.cpp +++ b/src/mbgl/renderer/sources/render_geojson_source.cpp @@ -94,7 +94,8 @@ void RenderGeoJSONSource::update(Immutable baseImpl_, const uint8_t maxZ = impl().getZoomRange().max; for (const auto& pair : tilePyramid.getTiles()) { if (pair.first.canonical.z <= maxZ) { - static_cast(pair.second.get())->updateData(data_, needsRelayout); + static_cast(pair.second.get()) + ->updateData(data_, needsRelayout, parameters.isUpdateSynchronous); } } } diff --git a/src/mbgl/renderer/tile_parameters.hpp b/src/mbgl/renderer/tile_parameters.hpp index 32e3911c9..4434076b4 100644 --- a/src/mbgl/renderer/tile_parameters.hpp +++ b/src/mbgl/renderer/tile_parameters.hpp @@ -38,6 +38,7 @@ public: double tileLodPitchThreshold = (60.0 / 180.0) * std::numbers::pi; double tileLodZoomShift = 0; gfx::DynamicTextureAtlasPtr dynamicTextureAtlas; + bool isUpdateSynchronous = false; }; } // namespace mbgl diff --git a/src/mbgl/style/conversion/geojson_options.cpp b/src/mbgl/style/conversion/geojson_options.cpp index f6876143a..272137b6e 100644 --- a/src/mbgl/style/conversion/geojson_options.cpp +++ b/src/mbgl/style/conversion/geojson_options.cpp @@ -101,6 +101,16 @@ std::optional Converter::operator()(const Conver } } + const auto synchronousUpdateValue = objectMember(value, "synchronousUpdate"); + if (synchronousUpdateValue) { + if (toBool(*synchronousUpdateValue)) { + options.synchronousUpdate = *toBool(*synchronousUpdateValue); + } else { + error.message = "GeoJSON source synchronousUpdate value must be a boolean"; + return std::nullopt; + } + } + const auto clusterProperties = objectMember(value, "clusterProperties"); if (clusterProperties) { if (!isObject(*clusterProperties)) { diff --git a/src/mbgl/style/source_impl.hpp b/src/mbgl/style/source_impl.hpp index daedaa271..6359ad56d 100644 --- a/src/mbgl/style/source_impl.hpp +++ b/src/mbgl/style/source_impl.hpp @@ -31,6 +31,8 @@ public: const SourceType type; const std::string id; + virtual bool isUpdateSynchronous() const { return false; } + protected: std::optional prefetchZoomDelta; std::optional maxOverscaleFactor; diff --git a/src/mbgl/style/sources/geojson_source.cpp b/src/mbgl/style/sources/geojson_source.cpp index 29319255a..66dbf6b2f 100644 --- a/src/mbgl/style/sources/geojson_source.cpp +++ b/src/mbgl/style/sources/geojson_source.cpp @@ -123,5 +123,9 @@ Mutable GeoJSONSource::createMutable() const noexcept { return staticMutableCast(makeMutable(impl())); } +bool GeoJSONSource::isUpdateSynchronous() const noexcept { + return baseImpl->isUpdateSynchronous(); +} + } // namespace style } // namespace mbgl diff --git a/src/mbgl/style/sources/geojson_source_impl.cpp b/src/mbgl/style/sources/geojson_source_impl.cpp index db6469ee4..5f9cd72d2 100644 --- a/src/mbgl/style/sources/geojson_source_impl.cpp +++ b/src/mbgl/style/sources/geojson_source_impl.cpp @@ -24,14 +24,18 @@ namespace mbgl { namespace style { class GeoJSONVTData final : public GeoJSONData { - void getTile(const CanonicalTileID& id, const std::function& fn) final { + void getTile(const CanonicalTileID& id, const std::function& fn, bool runSynchronously) final { assert(fn); - sequencedScheduler->scheduleAndReplyValue( - util::SimpleIdentity::Empty, - [id, geoJSONVT_impl = this->impl]() -> TileFeatures { - return geoJSONVT_impl->getTile(id.z, id.x, id.y).features; - }, - fn); + if (runSynchronously) { + fn(this->impl->getTile(id.z, id.x, id.y).features); + } else { + sequencedScheduler->scheduleAndReplyValue( + util::SimpleIdentity::Empty, + [id, geoJSONVT_impl = this->impl]() -> TileFeatures { + return geoJSONVT_impl->getTile(id.z, id.x, id.y).features; + }, + fn); + } } Features getChildren(const std::uint32_t) final { return {}; } @@ -54,7 +58,7 @@ class GeoJSONVTData final : public GeoJSONData { }; class SuperclusterData final : public GeoJSONData { - void getTile(const CanonicalTileID& id, const std::function& fn) final { + void getTile(const CanonicalTileID& id, const std::function& fn, bool) final { assert(fn); fn(impl.getTile(id.z, id.x, id.y)); } @@ -156,5 +160,9 @@ std::optional GeoJSONSource::Impl::getAttribution() const { return {}; } +bool GeoJSONSource::Impl::isUpdateSynchronous() const { + return options->synchronousUpdate; +} + } // namespace style } // namespace mbgl diff --git a/src/mbgl/style/sources/geojson_source_impl.hpp b/src/mbgl/style/sources/geojson_source_impl.hpp index ca4addcdd..c2a73a7eb 100644 --- a/src/mbgl/style/sources/geojson_source_impl.hpp +++ b/src/mbgl/style/sources/geojson_source_impl.hpp @@ -23,6 +23,8 @@ public: std::optional getAttribution() const final; + bool isUpdateSynchronous() const final; + private: Immutable options; std::shared_ptr data; diff --git a/src/mbgl/tile/geojson_tile.cpp b/src/mbgl/tile/geojson_tile.cpp index 70d2f6a91..b128a57ca 100644 --- a/src/mbgl/tile/geojson_tile.cpp +++ b/src/mbgl/tile/geojson_tile.cpp @@ -16,22 +16,24 @@ GeoJSONTile::GeoJSONTile(const OverscaledTileID& overscaledTileID, std::shared_ptr data_, TileObserver* observer_) : GeometryTile(overscaledTileID, std::move(sourceID_), parameters, observer_) { - updateData(std::move(data_), false /*needsRelayout*/); + updateData(std::move(data_), false /*needsRelayout*/, parameters.isUpdateSynchronous); } -void GeoJSONTile::updateData(std::shared_ptr data_, bool needsRelayout) { +void GeoJSONTile::updateData(std::shared_ptr data_, bool needsRelayout, bool runSynchronously) { MLN_TRACE_FUNC(); assert(data_); data = std::move(data_); if (needsRelayout) reset(); - data->getTile(id.canonical, - [this, self = weakFactory.makeWeakPtr(), capturedData = data.get()](TileFeatures features) { - // If the data has changed, a new request is being processed, ignore this one - if (auto guard = self.lock(); self && data.get() == capturedData) { - setData(std::make_unique(std::move(features))); - } - }); + data->getTile( + id.canonical, + [this, self = weakFactory.makeWeakPtr(), capturedData = data.get()](TileFeatures features) { + // If the data has changed, a new request is being processed, ignore this one + if (auto guard = self.lock(); self && data.get() == capturedData) { + setData(std::make_unique(std::move(features))); + } + }, + runSynchronously); } void GeoJSONTile::querySourceFeatures(std::vector& result, const SourceQueryOptions& options) { diff --git a/src/mbgl/tile/geojson_tile.hpp b/src/mbgl/tile/geojson_tile.hpp index e2dc1ceb3..4c09a16d2 100644 --- a/src/mbgl/tile/geojson_tile.hpp +++ b/src/mbgl/tile/geojson_tile.hpp @@ -19,7 +19,7 @@ public: std::shared_ptr, TileObserver* observer = nullptr); - void updateData(std::shared_ptr data, bool needsRelayout = false); + void updateData(std::shared_ptr data, bool needsRelayout, bool runSynchronously); void querySourceFeatures(std::vector& result, const SourceQueryOptions&) override; diff --git a/src/mbgl/tile/geometry_tile.cpp b/src/mbgl/tile/geometry_tile.cpp index 0fe0cf253..ee6f1b48e 100644 --- a/src/mbgl/tile/geometry_tile.cpp +++ b/src/mbgl/tile/geometry_tile.cpp @@ -193,8 +193,9 @@ GeometryTile::GeometryTile(const OverscaledTileID& id_, ImageRequestor(parameters.imageManager), threadPool(parameters.threadPool), mailbox(std::make_shared(*Scheduler::GetCurrent())), - worker(parameters.threadPool, // Scheduler reference for the Actor retainer - ActorRef(*this, mailbox), + worker(parameters.isUpdateSynchronous, + parameters.threadPool, // Scheduler reference for the Actor retainer + OptionalActorRef(parameters.isUpdateSynchronous, *this, mailbox), parameters.threadPool, id_, sourceID, diff --git a/src/mbgl/tile/geometry_tile.hpp b/src/mbgl/tile/geometry_tile.hpp index 7f1731c98..3b7a2e003 100644 --- a/src/mbgl/tile/geometry_tile.hpp +++ b/src/mbgl/tile/geometry_tile.hpp @@ -1,6 +1,6 @@ #pragma once -#include +#include #include #include #include @@ -114,7 +114,7 @@ private: TaggedScheduler threadPool; const std::shared_ptr mailbox; - Actor worker; + OptionalActor worker; const std::shared_ptr fileSource; const std::shared_ptr glyphManager; diff --git a/src/mbgl/tile/geometry_tile_worker.cpp b/src/mbgl/tile/geometry_tile_worker.cpp index d36a18acc..bf9d816c7 100644 --- a/src/mbgl/tile/geometry_tile_worker.cpp +++ b/src/mbgl/tile/geometry_tile_worker.cpp @@ -29,8 +29,8 @@ namespace mbgl { using namespace style; -GeometryTileWorker::GeometryTileWorker(ActorRef self_, - ActorRef parent_, +GeometryTileWorker::GeometryTileWorker(OptionalActorRef self_, + OptionalActorRef parent_, const TaggedScheduler& scheduler_, OverscaledTileID id_, std::string sourceID_, diff --git a/src/mbgl/tile/geometry_tile_worker.hpp b/src/mbgl/tile/geometry_tile_worker.hpp index 362bd6a5c..53378aca1 100644 --- a/src/mbgl/tile/geometry_tile_worker.hpp +++ b/src/mbgl/tile/geometry_tile_worker.hpp @@ -5,7 +5,7 @@ #include #include #include -#include +#include #include #include #include @@ -34,8 +34,8 @@ using DynamicTextureAtlasPtr = std::shared_ptr; class GeometryTileWorker { public: - GeometryTileWorker(ActorRef self, - ActorRef parent, + GeometryTileWorker(OptionalActorRef self, + OptionalActorRef parent, const TaggedScheduler& scheduler_, OverscaledTileID, std::string, @@ -79,8 +79,8 @@ private: void checkPatternLayout(std::unique_ptr layout); - ActorRef self; - ActorRef parent; + OptionalActorRef self; + OptionalActorRef parent; TaggedScheduler scheduler; const OverscaledTileID id; diff --git a/test/tile/geojson_tile.test.cpp b/test/tile/geojson_tile.test.cpp index 33baa2414..7ff13e748 100644 --- a/test/tile/geojson_tile.test.cpp +++ b/test/tile/geojson_tile.test.cpp @@ -58,7 +58,7 @@ public: FakeGeoJSONData(TileFeatures features_) : features(std::move(features_)) {} - void getTile(const CanonicalTileID&, const std::function& fn) final { + void getTile(const CanonicalTileID&, const std::function& fn, bool) final { assert(fn); fn(features); } @@ -75,6 +75,25 @@ private: } // namespace +TEST(GeoJSONTile, SynchronousUpdate) { + GeoJSONTileTest test; + + CircleLayer layer("circle", "source"); + + mapbox::feature::feature_collection features; + features.push_back(mapbox::feature::feature{mapbox::geometry::point(0, 0)}); + auto data = std::make_shared(std::move(features)); + TileParameters tileParameters = test.tileParameters; + tileParameters.isUpdateSynchronous = true; + GeoJSONTile tile(OverscaledTileID(0, 0, 0), "source", tileParameters, data); + Immutable layerProperties = makeMutable( + staticImmutableCast(layer.baseImpl)); + std::vector> layers{layerProperties}; + tile.setLayers(layers); + ASSERT_TRUE(tile.isComplete()); + ASSERT_TRUE(tile.isRenderable()); +} + TEST(GeoJSONTile, Issue7648) { GeoJSONTileTest test; @@ -101,7 +120,7 @@ TEST(GeoJSONTile, Issue7648) { test.loop.runOnce(); } - tile.updateData(data); + tile.updateData(data, false, test.tileParameters.isUpdateSynchronous); while (!tile.isComplete()) { test.loop.runOnce(); }