diff --git a/docs/40_任务/STEP-060E_CoreOnlyLayer存在性检查修复_Codex任务_v1.md b/docs/40_任务/STEP-060E_CoreOnlyLayer存在性检查修复_Codex任务_v1.md new file mode 100644 index 000000000..1a158eac0 --- /dev/null +++ b/docs/40_任务/STEP-060E_CoreOnlyLayer存在性检查修复_Codex任务_v1.md @@ -0,0 +1,255 @@ +# STEP-060E CoreOnly Layer 存在性检查修复 — Codex任务 v1 + +## 1. 任务定位 + +当前仍为 **STEP-060**,不得进入 STEP-061。 + +本轮修复 STEP-060D 代码审查确认的 Android 根因: + +- `raster-particle` 与 `weather-color` layer 在 Android `LayerManagerAndroid` 中通过 `addLayerTypeCoreOnly(...)` 注册; +- Core 可以正常解析、持有和渲染这些 layer; +- 但 CoreOnly layer 没有 Java `Layer` peer; +- `Style.getLayer(id)` 最终依赖 `LayerManagerAndroid::createJavaLayerPeer(...)`; +- 对 CoreOnly layer,该函数返回 null; +- 因此 Bridge 用 `Style.getLayer()` 判断 layer 是否存在,必然产生假阴性。 + +这与当前现象一致:原生 Particle validation 成功,但 RN `WeatherRuntimeBridge.attach()` 报 weather layer 缺失。 + +本任务只修复这一 Android Java/JNI/Core 存在性检查链,并保持现有 CoreOnly 设计。 +## 2. 开工基线 + +正式仓库: + +`/mnt/sda1/MapLibreParticleLab` + +分支:`particle/main` + +本任务开工目标基线: + +`2d2375cd532468821c6e3c158ee1a275f4522044` + +该提交已包含 Win11 已验证的 RN Test Host 修复: + +- React `19.2.3` +- RN 0.85.2 codegen 配置 +- `NativeWeatherRuntimeBridge.ts` 同文件 type aliases +- 保留 Expo/RN repositories 的 Maven Local plugin 逻辑 +- STEP-060D `13.4.1-step060d` 版本接线 + +开工前执行 `git pull --ff-only origin particle/main`,确认基线没有落后。 +不得回退上述 Win11 已验证修复。 +## 3. 已确认源码证据 + +以下证据已经由 ChatGPT 直接审查当前源码确认,不要用 Codex 完成报告替代: + +1. `platform/android/MapLibreAndroid/src/cpp/style/layers/layer_manager.cpp` + - `RasterParticleLayerFactory` 使用 `addLayerTypeCoreOnly(...)` + - `WeatherColorLayerFactory` 使用 `addLayerTypeCoreOnly(...)` + - `createJavaLayerPeer(...)` 找不到 Java peer factory 时返回 null +2. `platform/android/MapLibreAndroid/src/cpp/native_map_view.cpp` + - `NativeMapView::getLayer(...)` 取得 Core layer 后调用 `createJavaLayerPeer(...)` + - 因而 CoreOnly layer 即使真实存在,Java `Style.getLayer()` 仍可得到 null +3. 当前 Bridge `WeatherRuntimeBridgeModule.attach()` 使用 `style.getLayer(...)` 做存在性判断,因此判断机制本身错误。 +4. STEP-060D 新增 `style.getLayers().map { it.id }` 也不安全:CoreOnly layer 没有 Java peer,不能用 Java Layer peer 枚举作为 Core layer 真值来源。 +5. `MapLibreMap.getStyle()` 只在 Style fully loaded 时返回非 null,所以本轮不是普通 style-ready 时机修复。 + +不得重新把问题归因于 React、MLRN mapStyle JSON 传递、AAR 混用或简单 attach 重试时机。 +## 4. 必做实现:Core 直接存在性查询 + +新增通用、窄范围的 Android MapLibre API,用来查询 **Core Style 真值**,不得依赖 Java Source/Layer peer。 + +公开语义固定为: + +- `MapLibreMap.hasStyleSource(String sourceId): boolean` +- `MapLibreMap.hasStyleLayer(String layerId): boolean` + +具体落点至少覆盖: + +- `NativeMap.java` +- `NativeMapView.java` +- `MapLibreMap.java` +- `native_map_view.hpp` +- `native_map_view.cpp` +- JNI method registration + +C++ 最终判断必须直接基于: + +- `map->getStyle().getSource(id) != nullptr` +- `map->getStyle().getLayer(id) != nullptr` + +不得通过 `createJavaLayerPeer()`、`Style.getLayer()` 或其他 Java peer 路径间接判断。 +## 5. Bridge attach 修正 + +修改正式: + +`integration/react-native/MapLibreParticleRNTestHost/modules/weather-runtime-bridge/android/src/main/java/org/maplibre/particle/rntesthost/weather/WeatherRuntimeBridgeModule.kt` + +attach 在取得 fully-loaded `style` 后,改为: + +- source:使用 `map.hasStyleSource(WeatherIds.SOURCE)` +- weather color layer:使用 `map.hasStyleLayer(WeatherIds.COLOR_LAYER)` +- particle layer:使用 `map.hasStyleLayer(WeatherIds.PARTICLE_LAYER)` + +三个结果继续独立输出。 + +必须删除/停止使用以下方式作为存在性真值: + +- `style.getLayer(...)` +- `style.getLayers().map { it.id }` +- Java Layer peer 数量作为 Core layer 数量 + +source 也统一改用 Core 直接查询,不再让 Source/Layer 使用两套存在性语义。 +## 6. STEP-060D 诊断修正 + +保留稳定错误码: + +`WEATHER_OBJECTS_NOT_READY` + +失败信息至少包含: + +- `reactTag` +- `mapView=present|missing` +- `mapLibreMap=present|missing` +- `style=present|missing` +- `source[rn-weather-fields]=present|missing` +- `layer[rn-weather-color]=present|missing` +- `layer[rn-weather-particles]=present|missing` + +另外使用公开 `Style.getUri()` / Kotlin 对应属性输出 `styleUri`;JSON style URI 为空时明确写 `` 或等价稳定表示。 + +不要再调用 `style.getLayers()` 构造诊断列表。 +如果确实需要 ID 列表,只允许使用直接 Core ID 查询;本任务默认不要求新增列表 API,避免扩大修改面。 + +成功路径仍必须保持现有 `replaceWeatherRuntime(...)`、snapshot、listener 和 Phase A/B 控制语义不变。 +## 7. 明确禁止 + +本任务不得: + +- 将 `RasterParticleLayerFactory` 或 `WeatherColorLayerFactory` 从 `addLayerTypeCoreOnly(...)` 改成普通 Java layer registration +- 为了让 `Style.getLayer()` 非 null 而新建 WeatherColor/RasterParticle Java peer 类 +- 改变 CoreOnly layer 的产品边界 +- 用解析完整 Style JSON 作为正式存在性判断 +- 增加 attach 定时重试或无限重试 +- 修改 MLRN mapStyle 传递链来掩盖问题 +- 修改 WeatherRuntime 控制状态机 +- 实现 binary U/V field transport +- 进入 STEP-061 +- 在 Win11 临时修改 Native 正式源码 +- 执行 Win11 APK build/install、ADB 或设备测试 + +本轮目标是让 Java/RN Host 能以一个窄 API 查询 Core Style 真值,而不是把 CoreOnly 类型暴露成完整 Android Java Layer API。 +## 8. Maven / AAR 版本 + +本轮必须使用新的临时版本,禁止复用 STEP-060D 内容不同但版本相同的 artifact: + +`org.maplibre.gl:android-sdk-opengl:13.4.1-step060e` + +同步更新至少包括: + +- `platform/android/VERSION` +- RN Test Host `modules/weather-runtime-bridge/android/build.gradle` +- `plugins/withParticleNativeAndroid.js` + +Codex **不得构建 AAR,也不得执行 Android/Gradle AAR 打包或 Maven Local 发布**。 + +Codex 本轮只负责把源码与版本接线准备到可由用户手工构建的状态,并在完成 Linux 静态检查/精确编译后 commit + push。 + +用户在 Codex 停止后手工执行 OpenGL Release custom AAR 构建与 Maven Local 发布。用户构建完成后再记录: + +- Maven coordinate +- AAR build output 路径 +- Maven Local AAR 路径 +- AAR SHA256 +- 四 ABI 是否存在 + +Codex 完成报告中必须明确写:`AAR_BUILD_NOT_EXECUTED_BY_CODEX`,不得把“源码已准备”表述成“AAR 已生成”。 + +不得建立新的正式 Maven pipeline;本轮仍为 STEP-060 临时可追溯版本。 +## 9. Linux 验证要求 + +Codex 只执行与本轮源码相关的轻量、精确验证,不执行 AAR 打包。 + +至少完成: + +- Android JNI/C++ 相关源码的精确编译或现有等价最小验证; +- Android Java/Kotlin 受影响源码的可行精确编译;若因当前生成工程/codegen 缺失无法完成,必须明确记录限制,不得通过扩大修改范围绕过; +- 检查新增 JNI 方法声明、实现、注册签名一致; +- 检查 `MapLibreMap.hasStyleSource/hasStyleLayer` 最终确实直达 Core Style; +- 检查 Bridge 已完全停止用 Java `Style.getLayer()` / `Style.getLayers()` 判断 CoreOnly layer 存在性; +- `git diff --check`; +- 检查最终 diff,不得混入无关文件。 + +编译输出遵循全局 `$build-output-throttle`。 + +本任务不执行 Jest,也不得新增 Jest 要求。 +## 10. 文档要求 + +完成后更新 STEP-060E 对应中文 Step 记录与中文 Codex 完成报告。 + +同主题已有文档时优先更新或使用递增版本号,不得无意义创建多个不同文件名。 + +文档必须明确: + +- 根因为 CoreOnly layer 无 Java peer,导致 Java `Style.getLayer()` 假阴性; +- 本轮采用 Core 直接存在性查询,不改变 CoreOnly 架构; +- Win11 已验证 RN Test Host 基线来自 `2d2375cd532468821c6e3c158ee1a275f4522044`; +- Maven 临时版本已切到 `13.4.1-step060e`; +- `AAR_BUILD_NOT_EXECUTED_BY_CODEX`; +- Android APK/ADB/设备验证尚未执行; +- STEP-060 尚未 CLOSED,未进入 STEP-061。 +## 11. Git 收尾 + +完成规定源码、验证和文档后,只提交本任务相关变更到当前主线 `particle/main`。 + +必须: + +- commit 本任务相关源码、版本接线、任务文件及文档; +- push 到 `origin/particle/main`; +- push 失败时不得声称任务完成; +- 不得提交与本任务无关的本地改动。 + +完成报告必须记录: + +- source commit SHA; +- final HEAD; +- commit message; +- push 结果; +- remote branch; +- 预期 Maven coordinate:`org.maplibre.gl:android-sdk-opengl:13.4.1-step060e`; +- `AAR_BUILD_NOT_EXECUTED_BY_CODEX`。 +## 12. 完成报告格式 + +结束时按以下必做项逐项报告:`已完成 / 未完成 / 无法验证`,并说明未完成或无法验证原因: + +- Core `hasStyleSource` 查询链; +- Core `hasStyleLayer` 查询链; +- Bridge attach 改用 Core 真值; +- 移除危险的 `style.getLayers()` 诊断枚举; +- 增加 `styleUri` 诊断; +- 成功路径与 WeatherRuntime 控制语义保持不变; +- `13.4.1-step060e` 版本接线; +- Linux 精确验证; +- 文档更新; +- Git commit/push; +- AAR 未由 Codex 构建; +- 未进入 STEP-061。 + +不得用“基本完成”“应该可以”等模糊表述替代状态。 +## 13. 停止条件与后续人工链路 + +Codex 成功 push 后立即停止,不得自行构建 AAR,不得进入 Win11 或 Android 运行验证。 + +后续固定人工/ChatGPT 链路: + +1. ChatGPT 审核远端实际代码; +2. 用户在 Linux 手工构建 `13.4.1-step060e` OpenGL Release AAR并发布 Maven Local; +3. 记录 Linux AAR SHA256; +4. Win11 从 Git pull 最新 RN Test Host 源码; +5. Win11 通过 SCP 从 `root@192.168.200.184` 拉取 AAR; +6. Win11 计算 SHA256,必须与 Linux 完全一致; +7. 更新 Win11 Maven Local; +8. 执行 Bridge 与 MLRN 两条 `dependencyInsight`,必须共同解析到 `13.4.1-step060e`; +9. 用户手工构建 APK; +10. 再进行 Android attach/runtime 验证。 + +在上述 Android 结果出来前,不得宣布 STEP-060 CLOSED,也不得进入 STEP-061。 diff --git a/docs/50_Step记录/STEP-060E_CoreOnlyLayer存在性检查修复_v1.md b/docs/50_Step记录/STEP-060E_CoreOnlyLayer存在性检查修复_v1.md new file mode 100644 index 000000000..ac31ce280 --- /dev/null +++ b/docs/50_Step记录/STEP-060E_CoreOnlyLayer存在性检查修复_v1.md @@ -0,0 +1,37 @@ +# STEP-060E CoreOnly Layer 存在性检查修复 + +状态:`IMPLEMENTED / WAITING_CODE_REVIEW` + +## 目标与基线 + +在 `particle/main` 的 Win11 已验证 RN Test Host 基线 +NaN2d2375cd532468821c6e3c158ee1a275f4522044` 上,修正 STEP-060D 的 attach +存在性检查假阴性。本轮仍属于 STEP-060,未进入 STEP-061。 + +## 根因与修正 + +- `raster-particle` 与 `weather-color` 在 Android `LayerManagerAndroid` 中以 + `addLayerTypeCoreOnly(...)` 注册。Core 可以解析、持有和渲染它们,但不创建 Java + `Layer` peer。 +- 因此 `Style.getLayer(id)` 经 `createJavaLayerPeer(...)` 后会对真实存在的 CoreOnly + layer 返回 null;`Style.getLayers()` 也不能作为 Core layer 真值。 +- 新增窄范围公开 API `MapLibreMap.hasStyleSource(String)` 与 + `MapLibreMap.hasStyleLayer(String)`,经 `NativeMap`、`NativeMapView`、JNI 到 + `NativeMapView`,分别直接调用 Core `Style::getSource/getLayer`。 +- RN `WeatherRuntimeBridge.attach()` 保持 fully-loaded `Style` 前置条件,但三项存在性 + 判断统一改走上述 Core 真值;成功时原有 `replaceWeatherRuntime()`、snapshot 与 detach + listener 不变。 +- `WEATHER_OBJECTS_NOT_READY` 仍给出三个固定对象的独立状态,诊断改为输出 + `Style.getUri()`(JSON style 为 ``),不再枚举 Java source/layer peer。 + +## 版本与验证边界 + +- Maven 临时坐标已同步为 + `org.maplibre.gl:android-sdk-opengl:13.4.1-step060e`。 +- `AAR_BUILD_NOT_EXECUTED_BY_CODEX`:本轮不构建 AAR、不发布 Maven Local。 +- Android APK、ADB、设备/模拟器验证尚未执行;必须在人工构建并发布该新版本 AAR 后进行。 + +## 结果 + +CoreOnly 架构未改变、未新增 Java weather layer peer、未引入 attach retry,且没有修改 +WeatherRuntime 状态机、binary field transport 或 STEP-061 范围。 diff --git a/docs/报告/STEP-060E_CoreOnlyLayer存在性检查修复_Codex完成报告_v1.md b/docs/报告/STEP-060E_CoreOnlyLayer存在性检查修复_Codex完成报告_v1.md new file mode 100644 index 000000000..543fc803a --- /dev/null +++ b/docs/报告/STEP-060E_CoreOnlyLayer存在性检查修复_Codex完成报告_v1.md @@ -0,0 +1,44 @@ +# STEP-060E CoreOnly Layer 存在性检查修复 — Codex 完成报告 + +## 结论 + +状态:IMPLEMENTED / WAITING_CODE_REVIEW。本轮完成 Android Host 到 JNI/Core 的 Style 存在性窄查询,修复 RN bridge 对 CoreOnly weather layer 的假阴性;STEP-060 未 CLOSED,未进入 STEP-061。 + +## 实际完成内容 + +- 根因:RasterParticleLayer 与 WeatherColorLayer 是 CoreOnly layer,Core 可正常渲染,但 Style.getLayer() 需要 Java peer,故会错误返回 null。 +- 新增公开 MapLibreMap.hasStyleSource(String)、hasStyleLayer(String);实现穿过 NativeMap/NativeMapView 和 JNI,C++ 直接以 map->getStyle().getSource(id) 与 getLayer(id) 判断。 +- WeatherRuntimeBridge.attach() 在 style fully loaded 后,使用三个独立 Core 查询检查 rn-weather-fields、rn-weather-color、rn-weather-particles;没有改变 attach 成功路径、replaceWeatherRuntime()、snapshot 或 View detach listener。 +- WEATHER_OBJECTS_NOT_READY 保留固定错误码和三个独立 present/missing 字段,新增 styleUri(JSON style 为空时 ),移除 style.getLayers()/getSources() 的 Java peer 枚举。 +- Maven 临时版本同步为 13.4.1-step060e。 + +## 修改文件 + +- platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/{NativeMap.java,NativeMapView.java,MapLibreMap.java} +- platform/android/MapLibreAndroid/src/cpp/{native_map_view.hpp,native_map_view.cpp} +- integration/react-native/MapLibreParticleRNTestHost/modules/weather-runtime-bridge/android/src/main/java/org/maplibre/particle/rntesthost/weather/WeatherRuntimeBridgeModule.kt +- platform/android/VERSION、bridge android/build.gradle、plugins/withParticleNativeAndroid.js、Test Host README、本 Step 记录、本文档及 粒子开发记录.md。 + +## 验证 + +- ./gradlew :MapLibreAndroid:tasks --all --console=plain:PASS。 +- ./gradlew :MapLibreAndroid:compileOpenglReleaseJavaWithJavac --console=plain:PASS。 +- ./gradlew :MapLibreAndroid:externalNativeBuildOpenglRelease --console=plain:PASS(x86_64 等价精确 CMake/Ninja target);该任务仅编译 JNI/OpenGL 原生目标,不打包 AAR。 +- 静态检查:JNI Java 声明、C++ 声明/实现/注册名称一致;Bridge 不再调用 Style.getLayer()、Style.getLayers()、Style.getSource() 或 Style.getSources() 作为 weather object 真值。 +- git diff --check:PASS。 + +所有可能产生大量输出的 Gradle 编译均通过 build-output-throttle 执行;未执行 Jest。 + +## 未执行与风险 + +- AAR_BUILD_NOT_EXECUTED_BY_CODEX。 +- 未执行 Maven Local publication、Android APK build/install、ADB、设备或模拟器验证。 +- 本地 Expo 生成工程先前缺少 NativeWeatherRuntimeBridgeSpec;未为本轮新增大型 codegen 或 mocking 方案。后续人工构建新 AAR 并在 Win11 更新 Maven Local 后,仍需执行两条 dependencyInsight、APK build 与 attach/runtime 人工验收。 +- 运行期新 JNI 方法的真值行为需要上述 Android 人工验收;本轮未改变 CoreOnly 注册或 WeatherRuntime 控制状态机。 + +## Git + +- source baseline:2d2375cd532468821c6e3c158ee1a275f4522044 +- 功能 commit / Final HEAD / push 结果:将在本次受限验证、提交并推送后回填。 +- remote branch:origin/particle/main +- 预期 Maven 坐标:org.maplibre.gl:android-sdk-opengl:13.4.1-step060e diff --git a/integration/react-native/MapLibreParticleRNTestHost/README.md b/integration/react-native/MapLibreParticleRNTestHost/README.md index 434c70e6a..af20f90d8 100644 --- a/integration/react-native/MapLibreParticleRNTestHost/README.md +++ b/integration/react-native/MapLibreParticleRNTestHost/README.md @@ -7,7 +7,7 @@ - package manager 为 npm(`packageManager: npm@10.9.4`),锁文件为 `package-lock.json`;当前环境的 `npm ci --dry-run --ignore-scripts` 无须 `--legacy-peer-deps` 即可解析。 - package 明确固定 React Native `0.85.2`、React `19.2.3`、Expo `56.0.4`、TypeScript `5.9.3`、MLRN `11.3.6`;`app.json` 明确请求 New Architecture。Hermes 的最终启用状态必须在首次 Android prebuild 后检查生成工程。 - AGP `8.5.0`、Gradle `8.14.3`、Kotlin `2.1.20`、NDK `27.1.12297006`、JDK 17、minSdk 24、compile/targetSdk 36 是本 Test Host 的**人工验收配置目标**,不是尚未生成 Android 工程的已验证结果。首次 prebuild 后必须以实际 Gradle wrapper、AGP/Kotlin plugin、SDK/NDK 与 Hermes/New Architecture 配置为准核对。 -- MLRN 的 root Gradle extras 固定 `org.maplibre.reactnative.nativeVersion=13.4.1-step060d` 与 `nativeVariant=opengl`;这是 STEP-060D attach 诊断 AAR 的唯一临时版本,避免与先前 `13.4.1` Maven Local artifact 缓存混淆。 +- MLRN 的 root Gradle extras 固定 `org.maplibre.reactnative.nativeVersion=13.4.1-step060e` 与 `nativeVariant=opengl`;这是 STEP-060E CoreOnly layer 存在性检查修复 的唯一临时版本,避免与先前 `13.4.1` Maven Local artifact 缓存混淆。 ## Custom SDK 来源(手工执行) diff --git a/integration/react-native/MapLibreParticleRNTestHost/modules/weather-runtime-bridge/android/build.gradle b/integration/react-native/MapLibreParticleRNTestHost/modules/weather-runtime-bridge/android/build.gradle index 315355175..42cfc147b 100644 --- a/integration/react-native/MapLibreParticleRNTestHost/modules/weather-runtime-bridge/android/build.gradle +++ b/integration/react-native/MapLibreParticleRNTestHost/modules/weather-runtime-bridge/android/build.gradle @@ -14,7 +14,7 @@ android { dependencies { implementation "com.facebook.react:react-android" implementation "org.jetbrains.kotlin:kotlin-stdlib:2.1.20" - implementation "org.maplibre.gl:android-sdk-opengl:13.4.1-step060d" + implementation "org.maplibre.gl:android-sdk-opengl:13.4.1-step060e" compileOnly project(":maplibre_maplibre-react-native") } diff --git a/integration/react-native/MapLibreParticleRNTestHost/modules/weather-runtime-bridge/android/src/main/java/org/maplibre/particle/rntesthost/weather/WeatherRuntimeBridgeModule.kt b/integration/react-native/MapLibreParticleRNTestHost/modules/weather-runtime-bridge/android/src/main/java/org/maplibre/particle/rntesthost/weather/WeatherRuntimeBridgeModule.kt index 8c502a816..b2b5377a8 100644 --- a/integration/react-native/MapLibreParticleRNTestHost/modules/weather-runtime-bridge/android/src/main/java/org/maplibre/particle/rntesthost/weather/WeatherRuntimeBridgeModule.kt +++ b/integration/react-native/MapLibreParticleRNTestHost/modules/weather-runtime-bridge/android/src/main/java/org/maplibre/particle/rntesthost/weather/WeatherRuntimeBridgeModule.kt @@ -25,9 +25,9 @@ class WeatherRuntimeBridgeModule(private val context: ReactApplicationContext) : @ReactMethod override fun attach(reactTag: Double, promise: Promise) = withMapView(reactTag, promise) { view, map -> val style = map.style ?: return@withMapView reject(promise, "STYLE_NOT_READY", "The MapLibre style is not loaded") - val sourceExists = style.getSource(WeatherIds.SOURCE) != null - val weatherColorLayerExists = style.getLayer(WeatherIds.COLOR_LAYER) != null - val particleLayerExists = style.getLayer(WeatherIds.PARTICLE_LAYER) != null + val sourceExists = map.hasStyleSource(WeatherIds.SOURCE) + val weatherColorLayerExists = map.hasStyleLayer(WeatherIds.COLOR_LAYER) + val particleLayerExists = map.hasStyleLayer(WeatherIds.PARTICLE_LAYER) if (!sourceExists || !weatherColorLayerExists || !particleLayerExists) { return@withMapView reject( promise, @@ -142,9 +142,9 @@ class WeatherRuntimeBridgeModule(private val context: ReactApplicationContext) : } /** - * Keep the attach rejection searchable and bounded: MLRN lifecycle investigation needs the - * exact absent weather object plus enough final-style context, but never an unbounded dump. - * This executes on the UI queue together with the Style lookup above. + * Core-only layer types do not have a Java Layer peer. Presence is checked directly in Core + * before this diagnostic is built; the style URI distinguishes a JSON style (empty URI) from + * a URI-backed style without materialising all sources or layers. */ private fun weatherObjectsNotReadyDiagnostic( reactTag: Int, @@ -153,47 +153,28 @@ class WeatherRuntimeBridgeModule(private val context: ReactApplicationContext) : weatherColorLayerExists: Boolean, particleLayerExists: Boolean, ): String { - val sourceIds = style.getSources().map { it.id } - val layerIds = style.getLayers().map { it.id } return buildString { append("WEATHER_OBJECTS_NOT_READY reactTag=").append(reactTag) append(" mapView=present mapLibreMap=present style=present") + append(" styleUri=").append(style.getUri().ifEmpty { "" }) append(" source[").append(WeatherIds.SOURCE).append("]=") .append(presence(sourceExists)) append(" layer[").append(WeatherIds.COLOR_LAYER).append("]=") .append(presence(weatherColorLayerExists)) append(" layer[").append(WeatherIds.PARTICLE_LAYER).append("]=") .append(presence(particleLayerExists)) - append(" styleSourceCount=").append(sourceIds.size) - append(" styleLayerCount=").append(layerIds.size) - append(" styleSourceIds=").append(boundedIds(sourceIds)) - append(" styleLayerIds=").append(boundedIds(layerIds)) } } private fun presence(exists: Boolean) = if (exists) "present" else "missing" - private fun boundedIds(ids: List): String { - val weatherIds = ids.filter { it in WeatherIds.ALL }.distinct() - val otherIds = ids.filterNot { it in WeatherIds.ALL }.take(MAX_STYLE_IDS - weatherIds.size) - val shown = weatherIds + otherIds - val omitted = ids.size - shown.size - return buildString { - append('[').append(shown.joinToString(",")) - if (omitted > 0) append(",…+").append(omitted) - append(']') - } - } - private fun reject(promise: Promise, code: String, message: String) = promise.reject(code, message) private object WeatherIds { const val SOURCE = "rn-weather-fields" const val COLOR_LAYER = "rn-weather-color" const val PARTICLE_LAYER = "rn-weather-particles" - val ALL = setOf(SOURCE, COLOR_LAYER, PARTICLE_LAYER) } companion object { const val NAME = "WeatherRuntimeBridge" - const val MAX_STYLE_IDS = 24 } } diff --git a/integration/react-native/MapLibreParticleRNTestHost/plugins/withParticleNativeAndroid.js b/integration/react-native/MapLibreParticleRNTestHost/plugins/withParticleNativeAndroid.js index e15d1d52c..6a4ff2ad7 100644 --- a/integration/react-native/MapLibreParticleRNTestHost/plugins/withParticleNativeAndroid.js +++ b/integration/react-native/MapLibreParticleRNTestHost/plugins/withParticleNativeAndroid.js @@ -10,7 +10,7 @@ const marker = "// MapLibreParticleRNTestHost custom OpenGL SDK"; module.exports = function withParticleNativeAndroid(config) { return withProjectBuildGradle(config, (project) => { if (project.modResults.contents.includes(marker)) return project; - project.modResults.contents += `\n${marker}\ngradle.projectsEvaluated {\n allprojects { p ->\n p.repositories.mavenLocal()\n p.repositories.withType(org.gradle.api.artifacts.repositories.MavenArtifactRepository).configureEach { repo ->\n def repoUrl = repo.url.toString()\n if (repoUrl.contains(\"repo.maven.apache.org/maven2\") || repoUrl.contains(\"repo1.maven.org/maven2\")) {\n repo.content { excludeModule(\"org.maplibre.gl\", \"android-sdk-opengl\") }\n }\n }\n }\n}\nrootProject.ext.set(\"org.maplibre.reactnative.nativeVersion\", \"13.4.1-step060d\")\nrootProject.ext.set(\"org.maplibre.reactnative.nativeVariant\", \"opengl\")\n`; + project.modResults.contents += `\n${marker}\ngradle.projectsEvaluated {\n allprojects { p ->\n p.repositories.mavenLocal()\n p.repositories.withType(org.gradle.api.artifacts.repositories.MavenArtifactRepository).configureEach { repo ->\n def repoUrl = repo.url.toString()\n if (repoUrl.contains(\"repo.maven.apache.org/maven2\") || repoUrl.contains(\"repo1.maven.org/maven2\")) {\n repo.content { excludeModule(\"org.maplibre.gl\", \"android-sdk-opengl\") }\n }\n }\n }\n}\nrootProject.ext.set(\"org.maplibre.reactnative.nativeVersion\", \"13.4.1-step060e\")\nrootProject.ext.set(\"org.maplibre.reactnative.nativeVariant\", \"opengl\")\n`; return project; }); }; diff --git a/platform/android/MapLibreAndroid/src/cpp/native_map_view.cpp b/platform/android/MapLibreAndroid/src/cpp/native_map_view.cpp index d32d385bb..d76bb4083 100644 --- a/platform/android/MapLibreAndroid/src/cpp/native_map_view.cpp +++ b/platform/android/MapLibreAndroid/src/cpp/native_map_view.cpp @@ -1113,6 +1113,10 @@ jni::Local> NativeMapView::getLayer(JNIEnv& env, const jni::S return LayerManagerAndroid::get()->createJavaLayerPeer(env, *coreLayer); } +jni::jboolean NativeMapView::hasStyleLayer(JNIEnv& env, const jni::String& layerId) { + return map->getStyle().getLayer(jni::Make(env, layerId)) != nullptr; +} + jni::jboolean NativeMapView::setStyleLayerFloatProperty(JNIEnv& env, const jni::String& layerId, const jni::String& propertyName, @@ -1496,6 +1500,10 @@ jni::Local> NativeMapView::getSource(JNIEnv& env, const jni: return jni::NewLocal(env, Source::peerForCoreSource(env, *coreSource, *rendererFrontend, *map)); } +jni::jboolean NativeMapView::hasStyleSource(JNIEnv& env, const jni::String& sourceId) { + return map->getStyle().getSource(jni::Make(env, sourceId)) != nullptr; +} + void NativeMapView::addSource(JNIEnv& env, const jni::Object& obj, jlong sourcePtr) { assert(sourcePtr != 0); @@ -1728,6 +1736,7 @@ void NativeMapView::registerNative(jni::JNIEnv& env) { METHOD(&NativeMapView::getLight, "nativeGetLight"), METHOD(&NativeMapView::getLayers, "nativeGetLayers"), METHOD(&NativeMapView::getLayer, "nativeGetLayer"), + METHOD(&NativeMapView::hasStyleLayer, "nativeHasStyleLayer"), METHOD(&NativeMapView::setStyleLayerFloatProperty, "nativeSetStyleLayerFloatProperty"), METHOD(&NativeMapView::setStyleLayerVisibility, "nativeSetStyleLayerVisibility"), METHOD(&NativeMapView::setRasterParticleColors, "nativeSetRasterParticleColors"), @@ -1750,6 +1759,7 @@ void NativeMapView::registerNative(jni::JNIEnv& env) { METHOD(&NativeMapView::removeLayer, "nativeRemoveLayer"), METHOD(&NativeMapView::getSources, "nativeGetSources"), METHOD(&NativeMapView::getSource, "nativeGetSource"), + METHOD(&NativeMapView::hasStyleSource, "nativeHasStyleSource"), METHOD(&NativeMapView::addSource, "nativeAddSource"), METHOD(&NativeMapView::removeSource, "nativeRemoveSource"), METHOD(&NativeMapView::addImage, "nativeAddImage"), diff --git a/platform/android/MapLibreAndroid/src/cpp/native_map_view.hpp b/platform/android/MapLibreAndroid/src/cpp/native_map_view.hpp index 57886a699..057984632 100644 --- a/platform/android/MapLibreAndroid/src/cpp/native_map_view.hpp +++ b/platform/android/MapLibreAndroid/src/cpp/native_map_view.hpp @@ -281,6 +281,7 @@ public: jni::Local>> getLayers(JNIEnv&); jni::Local> getLayer(JNIEnv&, const jni::String&); + jni::jboolean hasStyleLayer(JNIEnv&, const jni::String&); jni::jboolean setStyleLayerFloatProperty(JNIEnv&, const jni::String&, const jni::String&, jni::jfloat); jni::jboolean setStyleLayerVisibility(JNIEnv&, const jni::String&, jni::jboolean); jni::jboolean setRasterParticleColors(JNIEnv&, const jni::String&, const jni::Array&, const jni::Array&); @@ -313,6 +314,7 @@ public: jni::Local>> getSources(JNIEnv&); jni::Local> getSource(JNIEnv&, const jni::String&); + jni::jboolean hasStyleSource(JNIEnv&, const jni::String&); void addSource(JNIEnv&, const jni::Object&, jlong nativePtr); 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 575b97a75..28f629457 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 @@ -179,6 +179,24 @@ public final class MapLibreMap { } } + /** + * Returns whether the fully loaded Core style contains {@code layerId}. + * + * This intentionally queries Core by id instead of materialising a Java {@link + * org.maplibre.android.style.layers.Layer} peer. It therefore also supports layer types that + * are registered Core-only. + */ + public boolean hasStyleLayer(@NonNull String layerId) { + return nativeMapView.hasStyleLayer(layerId); + } + + /** + * Returns whether the fully loaded Core style contains {@code sourceId}. + */ + public boolean hasStyleSource(@NonNull String sourceId) { + return nativeMapView.hasStyleSource(sourceId); + } + /** * Replaces this map's style-scoped weather facade. The caller must have already verified the * configured source and layers in the currently loaded 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 db988665d..d4163f24a 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 @@ -139,6 +139,8 @@ interface NativeMap { Layer getLayer(String layerId); + boolean hasStyleLayer(@NonNull String layerId); + boolean setStyleLayerFloatProperty(String layerId, String propertyName, float value); boolean setStyleLayerVisibility(String layerId, boolean visible); @@ -182,6 +184,8 @@ interface NativeMap { Source getSource(@NonNull String sourceId); + boolean hasStyleSource(@NonNull String sourceId); + boolean removeSource(@NonNull String sourceId); boolean removeSource(@NonNull Source source); 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 e1db92001..790bed333 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 @@ -950,6 +950,11 @@ final class NativeMapView implements NativeMap { return nativeGetLayer(layerId); } + @Override + public boolean hasStyleLayer(@NonNull String layerId) { + return !checkState("hasStyleLayer") && nativeHasStyleLayer(layerId); + } + @Override public boolean setStyleLayerFloatProperty(String layerId, String propertyName, float value) { return !checkState("setStyleLayerFloatProperty") @@ -1116,6 +1121,11 @@ final class NativeMapView implements NativeMap { return nativeGetSource(sourceId); } + @Override + public boolean hasStyleSource(@NonNull String sourceId) { + return !checkState("hasStyleSource") && nativeHasStyleSource(sourceId); + } + @Override public void addSource(@NonNull Source source) { if (checkState("addSource")) { @@ -1758,6 +1768,9 @@ final class NativeMapView implements NativeMap { @Keep private native Layer nativeGetLayer(String layerId); + @Keep + private native boolean nativeHasStyleLayer(String layerId); + @Keep private native boolean nativeSetStyleLayerFloatProperty(String layerId, String propertyName, float value); @@ -1811,6 +1824,9 @@ final class NativeMapView implements NativeMap { @Keep private native Source nativeGetSource(String sourceId); + @Keep + private native boolean nativeHasStyleSource(String sourceId); + @Keep private native void nativeAddSource(Source source, long sourcePtr) throws CannotAddSourceException; diff --git a/platform/android/VERSION b/platform/android/VERSION index 8af61e2f9..6437a5894 100644 --- a/platform/android/VERSION +++ b/platform/android/VERSION @@ -1 +1 @@ -13.4.1-step060d +13.4.1-step060e diff --git a/粒子开发记录.md b/粒子开发记录.md index 5d3c9c680..e9d26cdae 100644 --- a/粒子开发记录.md +++ b/粒子开发记录.md @@ -532,3 +532,10 @@ WeatherColorLayer 的 `ParticleGpuValidationUBO` designated initializer 补齐 ` - attach 现独立报告 `rn-weather-fields`、`rn-weather-color`、`rn-weather-particles` 状态;`WEATHER_OBJECTS_NOT_READY` 包含 reactTag、MapView/MapLibreMap/Style 状态、三个 ID 的 present/missing、Style source/layer count 和 weather-first 的有界 ID 摘要。 - Linux 生成并发布唯一诊断 AAR:`org.maplibre.gl:android-sdk-opengl:13.4.1-step060d`,build output 与 Maven Local AAR SHA-256 均为 `7391440f02e976f295caa265bc15e895a7c0183883a0e2cb08deef4cf42b0096`,含四 ABI。 - 当前 Expo 生成工程缺失 `NativeWeatherRuntimeBridgeSpec`,标准 codegen task 为 SKIPPED,故 bridge Kotlin compile 不能独立验证;未改生成工程绕过。后续由 CARBON7i 用此唯一 AAR 收集 Android attach 诊断,仍不进入 STEP-061。 + +# 2026-09-08 — STEP-060E CoreOnly Layer 存在性检查修复 + +- 根因确认:WeatherColorLayer 与 RasterParticleLayer 属于 CoreOnly layer,Core 能渲染但没有 Java Layer peer;故 STEP-060D bridge 用 Style.getLayer() 会对真实 layer 产生假阴性。 +- 新增 MapLibreMap.hasStyleSource/hasStyleLayer,经 Android JNI 直接查询 Core Style;bridge attach 三项 weather object 判断统一使用该真值,保留既有成功路径与 WeatherRuntime 生命周期,不增 Java peer、重试或私有 registry。 +- WEATHER_OBJECTS_NOT_READY 改用 styleUri(JSON style 为 )并移除不可靠的 Java layer/source 枚举;临时 Maven version 升为 13.4.1-step060e。 +- 本轮不构建 AAR、不发布 Maven Local,APK/ADB/设备验收待人工构建该版本后进行;仍属于 STEP-060,未进入 STEP-061。