From 45502a53f4fc486bd24cb0008d1ab0ff221e7e63 Mon Sep 17 00:00:00 2001 From: OpenAI Codex Date: Thu, 3 Sep 2026 18:26:13 +0800 Subject: [PATCH] feat: add runtime particle field updates --- .../style/sources/raster_array_source.hpp | 46 ++++++- .../src/cpp/native_map_view.cpp | 20 +++ .../src/cpp/native_map_view.hpp | 1 + .../maplibre/android/maps/MapLibreMap.java | 5 + .../org/maplibre/android/maps/NativeMap.java | 2 + .../maplibre/android/maps/NativeMapView.java | 9 ++ .../ParticleCoreLayerValidationActivity.kt | 36 ++++- .../src/main/res/values/strings.xml | 2 +- src/mbgl/style/conversion/source.cpp | 40 +++++- .../style/sources/raster_array_source.cpp | 125 +++++++++++++++++- .../sources/raster_array_source_impl.hpp | 8 +- test/style/source.test.cpp | 67 ++++++++++ 12 files changed, 346 insertions(+), 15 deletions(-) diff --git a/include/mbgl/style/sources/raster_array_source.hpp b/include/mbgl/style/sources/raster_array_source.hpp index e02d6c9cc..6da98098c 100644 --- a/include/mbgl/style/sources/raster_array_source.hpp +++ b/include/mbgl/style/sources/raster_array_source.hpp @@ -1,14 +1,35 @@ #pragma once +#include #include #include +#include namespace mbgl { class AsyncRequest; namespace style { +// A Source-owned, preloaded validation candidate. Production runtimes can use +// updateRuntimeFieldWindow() directly with an already decoded snapshot instead. +struct RasterArrayRuntimeFieldDescriptor { + std::string fixtureID; + std::string metadataURL; + std::string fieldURL; +}; + +enum class RasterArrayRuntimeFieldUpdateResult : uint8_t { + Applied, + Noop, + RejectedNotLoaded, + RejectedInvalidSnapshot, + RejectedIncompatibleSnapshot, + RejectedNonMonotonicGeneration, + RejectedCandidateNotReady, + RejectedNoCandidate, +}; + class RasterArraySource final : public Source { public: RasterArraySource(std::string id, @@ -17,12 +38,20 @@ public: std::string fieldURL, std::string nextFixtureID = {}, std::string nextMetadataURL = {}, - std::string nextFieldURL = {}); + std::string nextFieldURL = {}, + std::vector runtimeFields = {}); ~RasterArraySource() override; class Impl; const Impl& impl() const; void loadDescription(FileSource&) override; + // Advances the authoritative Source time window from T0/T1 to T1/newT1. + // The caller transfers an immutable, already decoded Field snapshot; the + // renderer observes the resulting Source change and owns GPU reuse/upload. + RasterArrayRuntimeFieldUpdateResult updateRuntimeFieldWindow(particle::ParticleFieldSnapshot newT1); + // Validation convenience: advance using the next Source-owned preloaded + // candidate. Android supplies no T0/T1 state and no GPU resource here. + RasterArrayRuntimeFieldUpdateResult advanceRuntimeFieldWindow(); bool supportsLayerType(const LayerTypeInfo*) const override; mapbox::base::WeakPtr makeWeakPtr() override { return weakFactory.makeWeakPtr(); } @@ -30,7 +59,19 @@ protected: Mutable createMutable() const noexcept override; private: + struct RuntimeFieldCandidate { + RasterArrayRuntimeFieldDescriptor descriptor; + std::unique_ptr metadataRequest; + std::unique_ptr fieldRequest; + std::optional metadata; + std::optional> fieldBytes; + std::optional snapshot; + }; + void tryLoad(); + void tryLoadRuntimeField(size_t); + RasterArrayRuntimeFieldUpdateResult rejectRuntimeFieldUpdate(RasterArrayRuntimeFieldUpdateResult, + const std::string& reason) const; std::unique_ptr metadataRequest; std::unique_ptr fieldRequest; @@ -40,6 +81,9 @@ private: std::optional> fieldBytes; std::optional nextMetadata; std::optional> nextFieldBytes; + std::vector runtimeFields; + size_t nextRuntimeField = 0; + uint64_t runtimeUpdateSequence = 0; mapbox::base::WeakPtrFactory weakFactory{this}; }; diff --git a/platform/android/MapLibreAndroid/src/cpp/native_map_view.cpp b/platform/android/MapLibreAndroid/src/cpp/native_map_view.cpp index 3470bf9e4..7d152626f 100644 --- a/platform/android/MapLibreAndroid/src/cpp/native_map_view.cpp +++ b/platform/android/MapLibreAndroid/src/cpp/native_map_view.cpp @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -1145,6 +1146,24 @@ jni::jboolean NativeMapView::setStyleLayerFloatProperty(JNIEnv& env, return true; } +jni::jboolean NativeMapView::advanceRasterArraySourceFieldWindow(JNIEnv& env, const jni::String& sourceId) { + const auto id = jni::Make(env, sourceId); + auto* source = map->getStyle().getSource(id); + auto* rasterArraySource = dynamic_cast(source); + if (!rasterArraySource) { + mbgl::Log::Error(mbgl::Event::JNI, + "ParticleField: event=FIELD_WINDOW_UPDATE_REJECTED reason=SOURCE_NOT_FOUND sourceId=" + id); + return false; + } + const auto result = rasterArraySource->advanceRuntimeFieldWindow(); + const bool success = result == mbgl::style::RasterArrayRuntimeFieldUpdateResult::Applied || + result == mbgl::style::RasterArrayRuntimeFieldUpdateResult::Noop; + mbgl::Log::Info(mbgl::Event::JNI, + "ParticleField: event=FIELD_WINDOW_UPDATE_BRIDGE sourceId=" + id + + " result=" + (success ? "SUCCESS" : "REJECTED")); + return success; +} + void NativeMapView::addLayer(JNIEnv& env, jlong nativeLayerPtr, const jni::String& before) { assert(nativeLayerPtr != 0); @@ -1529,6 +1548,7 @@ void NativeMapView::registerNative(jni::JNIEnv& env) { METHOD(&NativeMapView::getLayers, "nativeGetLayers"), METHOD(&NativeMapView::getLayer, "nativeGetLayer"), METHOD(&NativeMapView::setStyleLayerFloatProperty, "nativeSetStyleLayerFloatProperty"), + METHOD(&NativeMapView::advanceRasterArraySourceFieldWindow, "nativeAdvanceRasterArraySourceFieldWindow"), METHOD(&NativeMapView::addLayer, "nativeAddLayer"), METHOD(&NativeMapView::addLayerAbove, "nativeAddLayerAbove"), METHOD(&NativeMapView::addLayerAt, "nativeAddLayerAt"), diff --git a/platform/android/MapLibreAndroid/src/cpp/native_map_view.hpp b/platform/android/MapLibreAndroid/src/cpp/native_map_view.hpp index be355b89b..e6ee42e54 100644 --- a/platform/android/MapLibreAndroid/src/cpp/native_map_view.hpp +++ b/platform/android/MapLibreAndroid/src/cpp/native_map_view.hpp @@ -282,6 +282,7 @@ public: jni::Local> getLayer(JNIEnv&, const jni::String&); jni::jboolean setStyleLayerFloatProperty(JNIEnv&, const jni::String&, const jni::String&, jni::jfloat); + jni::jboolean advanceRasterArraySourceFieldWindow(JNIEnv&, const jni::String&); void addLayer(JNIEnv&, jlong, const jni::String&); diff --git a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/MapLibreMap.java b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/MapLibreMap.java index c3d815441..80fac2f18 100644 --- a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/MapLibreMap.java +++ b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/MapLibreMap.java @@ -291,6 +291,11 @@ public final class MapLibreMap { return nativeMapView.setStyleLayerFloatProperty(layerId, propertyName, value); } + /** Advances a Source-owned raster-array runtime Field window without reloading the Style. */ + public boolean advanceRasterArraySourceFieldWindow(String sourceId) { + return nativeMapView.advanceRasterArraySourceFieldWindow(sourceId); + } + // Style /** diff --git a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/NativeMap.java b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/NativeMap.java index 314d1b379..d41eaf11f 100644 --- a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/NativeMap.java +++ b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/NativeMap.java @@ -141,6 +141,8 @@ interface NativeMap { boolean setStyleLayerFloatProperty(String layerId, String propertyName, float value); + boolean advanceRasterArraySourceFieldWindow(String sourceId); + boolean removeLayer(@NonNull String layerId); boolean removeLayer(@NonNull Layer layer); diff --git a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/NativeMapView.java b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/NativeMapView.java index 5fe789d95..87eba3f46 100755 --- a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/NativeMapView.java +++ b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/NativeMapView.java @@ -956,6 +956,12 @@ final class NativeMapView implements NativeMap { && nativeSetStyleLayerFloatProperty(layerId, propertyName, value); } + @Override + public boolean advanceRasterArraySourceFieldWindow(String sourceId) { + return !checkState("advanceRasterArraySourceFieldWindow") + && nativeAdvanceRasterArraySourceFieldWindow(sourceId); + } + @Override public void addLayer(@NonNull Layer layer) { if (checkState("addLayer")) { @@ -1690,6 +1696,9 @@ final class NativeMapView implements NativeMap { @Keep private native boolean nativeSetStyleLayerFloatProperty(String layerId, String propertyName, float value); + @Keep + private native boolean nativeAdvanceRasterArraySourceFieldWindow(String sourceId); + @Keep private native void nativeAddLayer(long layerPtr, String before) throws CannotAddLayerException; diff --git a/platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/particle/ParticleCoreLayerValidationActivity.kt b/platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/particle/ParticleCoreLayerValidationActivity.kt index 8da7e2d5e..2b830f0d1 100644 --- a/platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/particle/ParticleCoreLayerValidationActivity.kt +++ b/platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/particle/ParticleCoreLayerValidationActivity.kt @@ -105,10 +105,7 @@ class ParticleCoreLayerValidationActivity : AppCompatActivity() { findViewById