docs: investigate React Native weather rendering bridge
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled

This commit is contained in:
OpenAI Codex
2026-09-05 19:01:59 +08:00
parent 44a50e6e4a
commit f37f6eea1f
3 changed files with 170 additions and 0 deletions

View File

@@ -0,0 +1,147 @@
# STEP-056 React Native 天气渲染桥接现状调查
状态:调查完成(只读);STEP-055 仍为 `IMPLEMENTED / WAITING_ANDROID_VALIDATION`。
Starting HEAD:`44a50e6e4a4e79fae7ddf36af007b5e35e969a92`(`particle/main`,`origin/particle/main` 同步确认)。本报告没有修改功能代码、没有执行 Android Gradle/APK/ADB/设备测试或 Jest。
## A. 当前真实调用架构
```mermaid
flowchart LR
A[ParticleCoreLayerValidationActivity.kt\nsubmitRealWeatherDataset] --> B[MapLibreMap.submitRasterArraySourceHostField]
B --> C[NativeMapView.java\nnativeSubmitRasterArraySourceHostField]
C --> D[native_map_view.cpp\nNativeMapView::submitRasterArraySourceHostField]
D --> E[RasterArraySource::submitHostField]
E --> F[LocalParticleFieldFeed::load]
F --> G[ParticleFieldDataAdapter::decode]
G --> H[immutable ParticleFieldSnapshot]
H --> I[RasterArrayTimelineRuntime\nT0/T1 + cursor/mix]
I --> J[RenderRasterParticleLayer]
I --> K[RenderWeatherColorLayer]
J --> L[State/Trail/particle GPU passes]
K --> M[WeatherColor GPU pass]
```
当前真实 WeatherV2 Test App 资源位于 `platform/android/MapLibreAndroidTestApp/src/main/assets/integration/particle-weatherv2/ecmwf-hres-wind-20260807/`;`ParticleCoreLayerValidationActivity.kt::submitRealWeatherDataset()` 读取每个 `metadata.json` 与 `field.f32`,依次提交 `2026080700/06/12/18`。
仓库没有被跟踪的 React Native package、TurboModule、Fabric、JSI 或 RN NativeModule。现有可复用入口是 Android Test App 的 Java/JNI/C++ Host Field Submission,不是 RN binding。
## B. 文件与职责清单
| 文件 | 类/函数 | 当前职责 | RN v1 可复用 | 备注 |
|---|---|---|---|---|
| `include/mbgl/particle/field/field_data_adapter.hpp` | `ParticleFieldDescriptor` / `ParticleFieldSnapshot` / `ParticleFieldDataAdapter` | canonical descriptor、immutable snapshot、校验/解码 contract | 是 | U=eastward、V=northward、float32-le、interleaved U/V;不含 provider/URL |
| `src/mbgl/particle/field/field_data_adapter.cpp` | `decode()` | 校验 identity/generation/time/dimension/bytes,复制并解码 payload | 是 | Adapter 不补 generation,也不理解 JS/provider |
| `include/mbgl/style/sources/raster_array_source.hpp` | `RasterArraySource::submitHostField` | Host ingress、submit result、Timeline holder | 是 | 当前缺少 `clearWindFields()` |
| `src/mbgl/style/sources/raster_array_source.cpp` | `submitHostField` / `RasterArrayTimelineRuntime` | T0/T1 创建、后续 append、Timeline/Window state | 是 | 有 bounded timeline(8 entries)及 log,不向 Host 发事件 |
| `platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/MapLibreMap.java` | `submitRasterArraySourceHostField` / `controlRasterArraySourceTimeline` | Android public facade | 是 | RN Android module 可调用或包裹它 |
| `platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/NativeMapView.java` | native declarations | Java 到 JNI | 是 | payload 目前仅 `byte[]` |
| `platform/android/MapLibreAndroid/src/cpp/native_map_view.cpp` | `NativeMapView::submitRasterArraySourceHostField` | `byte[]` 拷贝进 C++、调用 Source | 是 | 返回 `jint` result;没有 callback |
| `platform/android/MapLibreAndroidTestApp/.../ParticleCoreLayerValidationActivity.kt` | `submitRealWeatherDataset` / `controlTimeline` | Test App asset 模拟 Host | 仅作参考 | 不是正式 RN 入口 |
| `src/mbgl/style/layers/raster_particle_layer.cpp` | RasterParticle properties | particle style property parser/model | 部分 | 无 arbitrary particle/trail color |
| `src/mbgl/renderer/layers/render_particle_validation_layer.cpp` | `RenderRasterParticleLayer` | field upload、Timeline tick、GPU simulation/trail | 不直接 | 需保持 C++ ownership |
| `src/mbgl/style/layers/weather_color_layer.cpp` | WeatherColor property parser | 仅 opacity | 部分 | 无 dynamic speed stops |
| `src/mbgl/renderer/layers/render_weather_color_layer.cpp` | `RenderWeatherColorLayer` | 同一 Timeline frame 的色场绘制 | 不直接 | 重用 T0/T1 GPU content |
| `shaders/particle_gpu_validation.fragment.glsl` | `weatherSpeedColor` / `particleSpeedColor` | 当前固定色表/粒子 speed-color | 将来需扩展 | 色带硬编码在 shader |
## C. 当前 Host Field Submission 路径
`NativeMapView::submitRasterArraySourceHostField(JNIEnv&, String, String, byte[])` 用 `jni::GetArrayRegion` 把 Java `byte[]` 复制到 `std::vector<char>`,再调用 `RasterArraySource::submitHostField(metadataText, payload)`。
1. `LocalParticleFieldFeed::load()` 解析 provider-neutral metadata,并调用 `ParticleFieldDataAdapter::decode()`。
2. Adapter 严格检查非空 fieldId、合法 generation/validTime、width/height、`float32-le` + `interleaved-uv`、精确 payload bytes;成功后生成拥有 `velocity` 与 `valid` 的 immutable Snapshot。
3. 第一个合法 Field 成为 pending T0,结果为 `AcceptedPendingInitialWindow`。
4. 第二个严格较新的 compatible Field 通过既有 `submitInitialRuntimeFieldWindow(T0,T1)`,建立 `RasterArrayTimelineRuntime`,结果为 `InitialWindowReady`。
5. 后续 Field 按 producer `validTime` 追加同一个 bounded Timeline,结果为 `AcceptedNextSnapshot`。
6. 相同 GPU content 返回 `Duplicate`;非法、非单调、capacity 等返回结构化负 result,最后有效 Field 保留(fail-closed)。
当前 513×769 单帧为 `513 × 769 × 2 × 4 = 3,155,976` bytes,约 3.01 MiB。Android Test App 路径至少有:asset 到 Java `ByteArray`、`ByteArray → vector<char>`、`vector<char> → Snapshot::velocity` 三段 materialization/copy;随后 renderer 还有 GPU image upload。它不适合转换成巨大 JSON 数组。
## D. Timeline 与时间插值
`RasterArrayTimelineRuntime`(`raster_array_source.hpp/.cpp`)持有按 timestamp 排序的 Snapshot entries、T0 index、mix、speed 与 state。`tick()` 使用 `steady_clock` wall elapsed 推进 mix;相邻有效 entries 决定 T0/T1,跨界时 `moveToLocked()` 递增 window revision 并记录 `OLD_T1_TO_NEW_T0`。
`RenderRasterParticleLayer::prepare/update` 读取 `timeline->frame()`,在 revision 变化时让 `RasterArraySource` 的 authoritative window 前进;renderer 用同一 GPU content 判定复用,因而正常 A/B→B/C 只上传 new T1,不 rebuild simulation/clear trail。`RenderWeatherColorLayer` 读取同一 frame/mix,在 shader 内先插值 U/V 再计算 speed。
Android 当前低频命令路径是:`ParticleCoreLayerValidationActivity::controlTimeline` → `MapLibreMap::controlRasterArraySourceTimeline` → `NativeMapView::nativeControlRasterArraySourceTimeline` → `RasterArraySource::controlRuntimeTimeline`。现有命令为 play/pause、0.5/1/2 speed、step、按 **index** seek、Gap/Invalid/Recover;没有 `setForecastTime(timestamp)` 的公开 Host API,也没有向 JS 返回 MIX/T0/T1 的 callback。
## E. WeatherColorLayer 当前颜色实现
`WeatherColorLayer` 当前唯一公开 paint property 是 `weather-color-opacity`(`weather_color_layer.cpp`)。`RenderWeatherColorLayer` 以 UBO mode 5 运行 `particle_gpu_validation.fragment.glsl::weatherSpeedColor(speed)`;该函数把 0–30 m/s 映射到硬编码蓝/青/红色,并在 shader 用 `smoothstep/mix` 插值。
结论:当前没有 dynamic stops/ramp 数据结构,也没有 Android/JNI setter。React dynamic color stops 最小新增点应是 WeatherColorLayer 的 style property/model(有序 `speedMps,color` contract)及紧凑 shader-visible ramp(uniform/texture),由 Native 插值、绘制;React 只提交 stops。不能把 provider 色表放入 Renderer,也不能继续把固定 preset 作为唯一 API。
## F. ParticleStyle 当前实现
已有 RasterParticleLayer paint entry:`raster-particle-active`、`mask`、`time-mix`、`particle-count`、`line-width`、`particle-length`、`particle-opacity`、`trail-opacity`、`trail-fade`、`speed-color-enabled`。Android 低频 `NativeMapView::setStyleLayerFloatProperty` 已转发上述大部分 float/bool property。
| 目标项 | 当前入口/行为 | 副作用 |
|---|---|---|
| `particleCount` | `raster-particle-count` | State/draw resources 必要重建;不应 field resubmit |
| `lineWidth`、`particleLength`、`particleOpacity`、`trailOpacity`、`trailFade` | RasterParticle paint property | visual/uniform 更新;当前路径不应 style reload、field upload 或 Timeline reset |
| `speedColor` | `raster-particle-speed-color-enabled` | shader 在固定 speed palette 与固定蓝色之间切换 |
| `particleColor` | 不存在 | 需新增明确 style property |
| `trailColor` | 不存在 | 当前 trail RGB 与 particle segment 路径耦合,需独立 style/shader contract |
因此未来应将 `speedColor` 保留为可选模式;当它为 false 时,用 React 的 `particleColor`;`trailColor` 必须独立提交,不能让 WeatherColorLayer speed ramp 隐式控制粒子颜色。
## G. Runtime Enable / Disable / Release
当前 generic Layer visibility 可以隐藏 layer;`RenderRasterParticleLayer::needsRepaint()` 仅在 `active && visibility != None` 时为真,故 particle hidden 时停止该层持续 repaint。它不等价于 release:layer/source 未 remove 时 State/Trail/field GPU resources 与 Snapshot/Timeline 仍被持有。Weather color 的 `needsRepaint()` 返回 `isLayerRenderable()`,visibility none 是否不再 render/repaint 由通用 Layer renderability 决定;本调查未用设备测试验证。
真实释放的现有路径是 layer removal:`RenderRasterParticleLayer::layerRemoved()` 移除 State/Trail targets 与 draw buffers;`RenderWeatherColorLayer::layerRemoved()` reset resources。source destruction 释放 `RasterArraySource` 的 timeline/pending Snapshot 共享引用;Activity 当前 `enabled=false` 是重载 validation style 并省略 layers,不是对 RN 暴露的一次性 runtime unload API。
`setWeatherEnabled(false)` 可实现真正释放,但当前没有一条 API 完整完成它。最小高层语义应在 map/style 安全线程中:停止低频 playback → remove Particle/WeatherColor layers(GPU release)→ clear timeline/pending host fields 或 remove RasterArraySource(Snapshot release)。需要新增 `clearWindFields()`/runtime lifecycle bridge;不能以 visibility 伪装 unload,也不应向 React 暴露 State/Trail/T0/T1。
## H. RN Binary Bridge 建议
当前没有 RN binary bridge。建议 v1 使用 `ArrayBuffer`/`Uint8Array` 在 RN Android module 侧稳定 materialize 为 `byte[]`,复用现有 `MapLibreMap.submitRasterArraySourceHostField(..., byte[])` 与 JNI;metadata 继续是小型 String/JSON。约 3.01 MiB/帧和四帧队列的峰值复制需要纳入内存预算,但在先验证正确性阶段不必承诺 zero-copy。
后续 profiling 若证明 JS→Java→JNI copies 是瓶颈,再增加 **新的** direct `ByteBuffer` JNI overload(或宿主 JSI ArrayBuffer native ownership seam)。不能假称现有 `byte[]` 通路可 zero-copy;也不应把文件/mmap 作为 RN v1 的必需前提。
## I. RN API 与 Native 能力映射
| RN 目标功能 | 当前 Native 能力 | 已有入口 | 缺口 / 建议接入点 |
|---|---|---|---|
| `setWeatherEnabled` | 层 removal/资源释放存在 | generic Style remove;layerRemoved | 新增高层 lifecycle/unload + clear fields,不用 visibility 代替 |
| `setWeatherColorVisible` | generic visibility | Style Layer visibility | 可包装;隐藏不释放 |
| `setParticleVisible` | generic visibility;active 控制 simulation | Style property | 可包装;明确 hide/suspend/unload 三语义 |
| `submitWindField` | 完整 Host canonical ingress | `submitRasterArraySourceHostField` | RN module/facade 与 binary adapter 缺失 |
| `clearWindFields` | 无 | 无 | Source clear/lifecycle seam |
| `setForecastTime` | 仅 index seek | timeline command 5 | 增 timestamp→entry selection API,JS 不设 T0/T1/MIX |
| `setPlaybackState` | play/pause | timeline commands 0/1 | 可包装 |
| `setPlaybackSpeed` | 0.5/1/2 | command 2 | 枚举/范围需正式 Host contract |
| `setWindColorStyle` | opacity only | WeatherColor paint property | 增 dynamic stops style model + shader ramp |
| `setParticleStyle` | 多数 float/bool 属性 | `setStyleLayerFloatProperty` | 增 particle/trail color typed properties;不要靠 speedColor |
| `setParticlePerformance` | 无 weather-local FPS gate | global `MapView#setMaximumFps` | 独立 render scheduling 阶段 |
| runtime/error callbacks | submit `jint` + logcat | JNI result | 增低频 structured event/callback/state query;不回调 per-frame MIX |
## J. targetFps 调查
当前持续帧由 `RenderRasterParticleLayer::needsRepaint()`(active/visible)及 `RenderWeatherColorLayer::needsRepaint()`(renderable)驱动。`Map::Impl::onDidFinishRenderingFrame()` 在 `needsRepaint` 时再次 `onUpdate()`;Android frontend 最终调用 `MapRenderer::requestRender()`。因此接近 60 FPS 是 MapLibre continuous render request 链的结果,不是 Test App Timer。
现有 `MapView#setMaximumFps()` / `MapRenderer#setMaximumFps()` 是整张地图的 global cap,不应直接实现天气专属 targetFps。最安全的天气 30 FPS 节流点是 future `RenderRasterParticleLayer`/WeatherColor coordination 的 render scheduling gate:只在 deadline 到达时执行 State/Trail 与 dependent color redraw,使用累计 monotonic wall delta 驱动 lifecycle、trail fade 和 Timeline tick。这样不能把固定 1/30 秒塞给 shader;需保留真实 elapsed,并审查 simulation dt clamp/substep。WeatherColor 也必须参与 gate,否则它单独 `needsRepaint()` 仍保持全图约 60 FPS。建议该项独立于 RN Bridge v1,以免把 render scheduling 风险混入接口接线。
## K. 建议实施拆分与结论
**推荐下一实现阶段:STEP-057 Android Host Weather Runtime Bridge Foundation。**
目标是完成可被未来 RN module 包装的 Android/Native typed facade,而不是在本仓库凭空建立 RN package(当前没有 RN dependency、package 或 host app topology)。建议一个较大 Batch:
1. 低频 runtime lifecycle(enable/suspend/hide/unload)与 `clearWindFields`;安全 layer/source teardown。
2. canonical `submitWindField(metadata, binary)`、timestamp seek、play/pause/speed 的 typed Android/JNI facade,复用 Source/Timeline。
3. 可见性、现有 particle visual properties、WeatherColor opacity 的 typed API;增加 structured submission/runtime/error event,避免 per-frame callback。
4. 定义并接入 dynamic WeatherColor stops 与独立 particle/trail colors 的 style property contract;所有 provider/network/forecast decision 仍留 JS。
5. 以 Test App/Native tests 走完整 facade,最后一次 Android build/install 验收;真正 RN module 留给具备 RN host/build dependency 的应用仓库或后续明确任务。
**后续 Batch(STEP-058,建议独立):** weather-local `targetFps`、repaint gate、validation logging 降频与性能测量。不要把 `MapView#setMaximumFps()` 当作默认实现。
直接进入“RN package 实现”目前的阻塞不是 Core 数据 contract,而是仓库不存在 RN binding 与 RN Android build ownership。可立即开发的是上述 Host-native facade;等宿主提供 RN 依赖和 module placement 后,RN 层可薄包裹它。
最大三项风险:
1. 3.01 MiB 级 payload 在 JS/Java/JNI/Snapshot 的多次 copy 造成峰值内存、GC 与提交抖动。
2. visibility、suspend 与 unload 被混同,导致 Native/GPU/Snapshot 资源未真正释放,或在 style/render thread 上错误 teardown。
3. Timeline tick、particle State pass 与持续 repaint 当前耦合;错误 targetFps gate 会改变 motion、wall-time lifecycle 或 Timeline 行为。
冻结边界继续有效:Descriptor/Snapshot、T0/T1 window/reuse、Core-owned Timeline/Playback、canonical m/s U/V、WeatherColor 与 RasterParticle 分离、Renderer 不含 provider/network/vendor 逻辑。

View File

@@ -0,0 +1,17 @@
# STEP-056 React Native 天气渲染桥接现状调查
状态:`INVESTIGATED / READY_FOR_HOST_BRIDGE_FOUNDATION`。
Starting HEAD:`44a50e6e4a4e79fae7ddf36af007b5e35e969a92`。
本阶段为只读调查,未修改 C++、Kotlin/Java、JNI、shader 或 Android UI;未执行 Android Gradle、APK、ADB、设备测试或 Jest。
结论:仓库没有现成 React Native/TurboModule/Fabric/JSI binding。可复用的稳定 seam 是 Android `MapLibreMap.submitRasterArraySourceHostField()` → JNI → `RasterArraySource::submitHostField()`;它已严格复用 canonical Descriptor、Adapter、Snapshot、Core Timeline 与 Runtime Window。
当前 `byte[]` Host path 适合 RN v1 先以 ArrayBuffer/Uint8Array materialize 为 byte[] 后接入;单帧 513×769 U/V 为 3,155,976 bytes,存在 Java/C++ Snapshot 多次 copy,direct ByteBuffer/zero-copy 仅在 profiling 后另行优化。
下一实现建议为 STEP-057 Android Host Weather Runtime Bridge Foundation:补齐 typed lifecycle/clear、timestamp time control、visibility/style、structured low-frequency result/event,以及 dynamic WeatherColor stops 和独立 particle/trail color contract。RN package 本身因本仓库没有 RN host/dependency 不应凭空创建;未来 React 层薄包裹该 facade。
`targetFps` 应另列性能批次:现有约 60 FPS 来自两层 `needsRepaint()` → Map update/requestRender;`MapView#setMaximumFps()` 是全图上限,不是天气专属节流点。STEP-057 不应改变既有 Renderer scheduling。
调查报告:`docs/20_调查/STEP-056_RN天气渲染桥接现状调查_v1.md`。

View File

@@ -443,3 +443,9 @@ configured particleCount 保持不变;稳定相机后,State respawn 域改
## STEP-055 v9 Android C++ 编译遗漏修复
WeatherColorLayer 的 `ParticleGpuValidationUBO` designated initializer 补齐 `respawnBounds`,并使用与 `fieldBounds` 相同的中性范围;仅修复 Android `-Wmissing-designated-field-initializers` 编译失败。已搜索确认两个 C++ initializer 均完整,未改粒子 respawn、色场行为或冻结数据/时间线边界,仍待 Android 验收。
## 2026-09-05 — STEP-056 React Native 天气渲染桥接现状调查
- 仓库当前没有 React Native/TurboModule/Fabric/JSI binding;可复用正式入口是 Android `byte[] → JNI → RasterArraySource::submitHostField()`,它已接入 canonical Adapter/Snapshot、Core Timeline/Window 与两个 GPU layer。
- 推荐先建立 Android Host Weather Runtime typed facade:runtime unload/clear、canonical field submit、timestamp playback、visibility/style 和低频 result/event;RN module 应在具备 RN host/build dependency 的后续任务中薄包裹该 facade。
- 单帧 WeatherV2 payload 为 3,155,976 bytes;RN v1 应先稳定复用 byte[] path,zero-copy/direct ByteBuffer 仅在 profiling 后处理。动态 WeatherColor stops、独立 particle/trail color 和 weather-local targetFps 均是当前缺口;后者应独立性能批次,不能使用全图 `MapView#setMaximumFps()` 冒充。