From 4e31163516ad77473df672e162f554223cd2f09e Mon Sep 17 00:00:00 2001 From: OpenAI Codex Date: Thu, 3 Sep 2026 19:02:21 +0800 Subject: [PATCH] test: add runtime field duplicate noop control --- .../STEP-045_Runtime_Field动态更新接口_v1.md | 7 ++++ ...UNTIME-05_Duplicate_NOOP_Codex完成报告_v1.md | 36 +++++++++++++++++++ .../style/sources/raster_array_source.hpp | 3 ++ .../src/cpp/native_map_view.cpp | 18 ++++++++++ .../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 | 22 ++++++++++++ ...ctivity_particle_core_layer_validation.xml | 1 + .../src/main/res/values/strings.xml | 1 + .../style/sources/raster_array_source.cpp | 7 ++++ test/style/source.test.cpp | 5 +++ 粒子开发记录.md | 6 ++++ 14 files changed, 123 insertions(+) create mode 100644 docs/报告/40-04_T-RUNTIME-05_Duplicate_NOOP_Codex完成报告_v1.md diff --git a/docs/50_Step记录/STEP-045_Runtime_Field动态更新接口_v1.md b/docs/50_Step记录/STEP-045_Runtime_Field动态更新接口_v1.md index 69eb41f22..de358865c 100644 --- a/docs/50_Step记录/STEP-045_Runtime_Field动态更新接口_v1.md +++ b/docs/50_Step记录/STEP-045_Runtime_Field动态更新接口_v1.md @@ -33,3 +33,10 @@ - 新增 Source 定向测试对象覆盖 A/B→B/C→C/D、重复 Snapshot NOOP、无效 Snapshot fail-closed,并已精确编译;当前 build tree 缺少大量无关测试依赖,未把完整 `mbgl-test` 二进制执行误报为通过。 - 未运行 Android Gradle、ADB、APK 安装、设备测试或 Jest。 - 实现提交:`45502a53f4fc486bd24cb0008d1ab0ff221e7e63`(`feat: add runtime particle field updates`);文档收尾另行提交。 + +## T-RUNTIME-05 Duplicate NOOP 补验收入口 + +- 用户已完成 T-RUNTIME-01~04 与 T-RUNTIME-06 Android 手工验证;本轮只补 T-RUNTIME-05 的真实触发入口,状态仍为 `IMPLEMENTED / WAITING FOR ANDROID VALIDATION`。 +- `DUPLICATE T1` 按钮经 `MapLibreMap -> NativeMapView -> JNI -> RasterArraySource::repeatRuntimeFieldWindow()` 重提 Source 当前 T1;它不调用 `setStyle()`、不推进 candidate queue,也不改变 Kotlin 的 MIX/窗口状态。 +- `repeatRuntimeFieldWindow()` 只把已存在的 `Impl::nextField` 交给既有 `updateRuntimeFieldWindow()`。在 `T0=vortex/T1=nodata`,content identity 命中既有 T1,正式 API 返回 `Noop`,不发 Source change,因而不触发 Field upload、GPU slot 交换、simulation reset 或 trail clear。 +- 新增 Source test assertion 在 C/D 后调用该入口并验证返回 `Noop`、T0/T1 未变、`sourceChangedCount` 未增加。Linux/OpenGL 的 `raster_array_source.cpp.o` 与 `source.test.cpp.o` 以 `-Werror` 精确编译;Android Gradle、ADB、设备和 Jest 未执行。 diff --git a/docs/报告/40-04_T-RUNTIME-05_Duplicate_NOOP_Codex完成报告_v1.md b/docs/报告/40-04_T-RUNTIME-05_Duplicate_NOOP_Codex完成报告_v1.md new file mode 100644 index 000000000..581a319f3 --- /dev/null +++ b/docs/报告/40-04_T-RUNTIME-05_Duplicate_NOOP_Codex完成报告_v1.md @@ -0,0 +1,36 @@ +# 40-04 T-RUNTIME-05 Duplicate NOOP 补验收 Codex 完成报告 + +## 状态 + +`40-04:IMPLEMENTED / WAITING FOR ANDROID VALIDATION` + +用户已手工通过 T-RUNTIME-01~04、T-RUNTIME-06;本轮只补 T-RUNTIME-05 的 Android 验证入口,未执行 Android 构建或设备操作。 + +## 实现 + +- 新增 `RasterArraySource::repeatRuntimeFieldWindow()`。它读取 Source 权威 `Impl::nextField` 并调用既有正式 `updateRuntimeFieldWindow()`,不直接操作 renderer 或 GPU。 +- 当当前窗口为 `T0=vortex / T1=nodata`,这次重提交命中 `sameFieldGpuContent(currentT1, newT1)`,返回 `Noop`;不调用 `onSourceChanged()`,所以没有 Field upload、slot swap、simulation reset、trail clear、Style reload 或 Layer/Source recreate。 +- Android 增加 `DUPLICATE T1`。调用链为 `MapLibreMap -> NativeMapView -> JNI -> Style::getSource() -> RasterArraySource`;Kotlin/JNI 不持有 T0/T1,也不消费预加载候选。 +- JNI 只在返回 `Noop` 时报告 duplicate bridge 成功。日志应出现 `FIELD_WINDOW_UPDATE_DUPLICATE_REQUEST`、Core 的 `FIELD_WINDOW_UPDATE_APPLIED result=NOOP` 与 bridge `result=NOOP`。 + +## 本地检查 + +- Linux/OpenGL:`raster_array_source.cpp.o`、`source.test.cpp.o` 精确 `-Werror` 编译通过。 +- Source test 补充 C/D 后 duplicate assertion:结果 `Noop`、T0/T1 保持 vortex/nodata、Source changed count 不增加。 +- `clang-format --dry-run --Werror`、`git diff --check` 通过。 +- Android Gradle、ADB、APK、实体机和 Jest:均未执行。 + +## 用户手工验收 + +在已通过 A/B→B/C→C/D 后,保持 `T0=vortex / T1=nodata`,点击 `DUPLICATE T1` 一次。必须确认: + +```text +FIELD_WINDOW_UPDATE_APPLIED result=NOOP +FIELD_UPLOAD delta=0 +SIMULATION_RESET delta=0 +trail clear delta=0 +Style reload delta=0 +当前 T0/T1、MIX、frame 持续不变/继续运行 +``` + +随后仍可点击普通 `ADVANCE WINDOW`,它应继续保留既有 T-RUNTIME-06 的 `NO_PRELOADED_FIELD` fail-closed 语义。 diff --git a/include/mbgl/style/sources/raster_array_source.hpp b/include/mbgl/style/sources/raster_array_source.hpp index 6da98098c..adf21a732 100644 --- a/include/mbgl/style/sources/raster_array_source.hpp +++ b/include/mbgl/style/sources/raster_array_source.hpp @@ -52,6 +52,9 @@ public: // Validation convenience: advance using the next Source-owned preloaded // candidate. Android supplies no T0/T1 state and no GPU resource here. RasterArrayRuntimeFieldUpdateResult advanceRuntimeFieldWindow(); + // Validation convenience: resubmit the current T1 through the formal + // update API. This must return Noop without changing the Source window. + RasterArrayRuntimeFieldUpdateResult repeatRuntimeFieldWindow(); bool supportsLayerType(const LayerTypeInfo*) const override; mapbox::base::WeakPtr makeWeakPtr() override { return weakFactory.makeWeakPtr(); } diff --git a/platform/android/MapLibreAndroid/src/cpp/native_map_view.cpp b/platform/android/MapLibreAndroid/src/cpp/native_map_view.cpp index 7d152626f..87597d760 100644 --- a/platform/android/MapLibreAndroid/src/cpp/native_map_view.cpp +++ b/platform/android/MapLibreAndroid/src/cpp/native_map_view.cpp @@ -1164,6 +1164,23 @@ jni::jboolean NativeMapView::advanceRasterArraySourceFieldWindow(JNIEnv& env, co return success; } +jni::jboolean NativeMapView::repeatRasterArraySourceFieldWindow(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->repeatRuntimeFieldWindow(); + const bool success = result == mbgl::style::RasterArrayRuntimeFieldUpdateResult::Noop; + mbgl::Log::Info(mbgl::Event::JNI, + "ParticleField: event=FIELD_WINDOW_UPDATE_DUPLICATE_BRIDGE sourceId=" + id + + " result=" + (success ? "NOOP" : "REJECTED")); + return success; +} + void NativeMapView::addLayer(JNIEnv& env, jlong nativeLayerPtr, const jni::String& before) { assert(nativeLayerPtr != 0); @@ -1549,6 +1566,7 @@ void NativeMapView::registerNative(jni::JNIEnv& env) { METHOD(&NativeMapView::getLayer, "nativeGetLayer"), METHOD(&NativeMapView::setStyleLayerFloatProperty, "nativeSetStyleLayerFloatProperty"), METHOD(&NativeMapView::advanceRasterArraySourceFieldWindow, "nativeAdvanceRasterArraySourceFieldWindow"), + METHOD(&NativeMapView::repeatRasterArraySourceFieldWindow, "nativeRepeatRasterArraySourceFieldWindow"), 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 e6ee42e54..547986ca1 100644 --- a/platform/android/MapLibreAndroid/src/cpp/native_map_view.hpp +++ b/platform/android/MapLibreAndroid/src/cpp/native_map_view.hpp @@ -283,6 +283,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&); + jni::jboolean repeatRasterArraySourceFieldWindow(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 80fac2f18..f20aa342b 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 @@ -296,6 +296,11 @@ public final class MapLibreMap { return nativeMapView.advanceRasterArraySourceFieldWindow(sourceId); } + /** Resubmits the Source-owned current T1 to validate the Runtime Field NOOP path. */ + public boolean repeatRasterArraySourceFieldWindow(String sourceId) { + return nativeMapView.repeatRasterArraySourceFieldWindow(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 d41eaf11f..6b97fcde8 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 @@ -143,6 +143,8 @@ interface NativeMap { boolean advanceRasterArraySourceFieldWindow(String sourceId); + boolean repeatRasterArraySourceFieldWindow(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 87eba3f46..cd7c9a856 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 @@ -962,6 +962,12 @@ final class NativeMapView implements NativeMap { && nativeAdvanceRasterArraySourceFieldWindow(sourceId); } + @Override + public boolean repeatRasterArraySourceFieldWindow(String sourceId) { + return !checkState("repeatRasterArraySourceFieldWindow") + && nativeRepeatRasterArraySourceFieldWindow(sourceId); + } + @Override public void addLayer(@NonNull Layer layer) { if (checkState("addLayer")) { @@ -1699,6 +1705,9 @@ final class NativeMapView implements NativeMap { @Keep private native boolean nativeAdvanceRasterArraySourceFieldWindow(String sourceId); + @Keep + private native boolean nativeRepeatRasterArraySourceFieldWindow(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 2b830f0d1..34fb8ade1 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 @@ -107,6 +107,9 @@ class ParticleCoreLayerValidationActivity : AppCompatActivity() { findViewById