From f37f6eea1f7720c64b2cc949938f29539422b480 Mon Sep 17 00:00:00 2001 From: OpenAI Codex Date: Sat, 5 Sep 2026 19:01:59 +0800 Subject: [PATCH] docs: investigate React Native weather rendering bridge --- .../STEP-056_RN天气渲染桥接现状调查_v1.md | 147 ++++++++++++++++++ .../STEP-056_RN天气渲染桥接现状调查_v1.md | 17 ++ 粒子开发记录.md | 6 + 3 files changed, 170 insertions(+) create mode 100644 docs/20_调查/STEP-056_RN天气渲染桥接现状调查_v1.md create mode 100644 docs/50_Step记录/STEP-056_RN天气渲染桥接现状调查_v1.md diff --git a/docs/20_调查/STEP-056_RN天气渲染桥接现状调查_v1.md b/docs/20_调查/STEP-056_RN天气渲染桥接现状调查_v1.md new file mode 100644 index 000000000..9c007b0e2 --- /dev/null +++ b/docs/20_调查/STEP-056_RN天气渲染桥接现状调查_v1.md @@ -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`,再调用 `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`、`vector → 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 逻辑。 diff --git a/docs/50_Step记录/STEP-056_RN天气渲染桥接现状调查_v1.md b/docs/50_Step记录/STEP-056_RN天气渲染桥接现状调查_v1.md new file mode 100644 index 000000000..085f75391 --- /dev/null +++ b/docs/50_Step记录/STEP-056_RN天气渲染桥接现状调查_v1.md @@ -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`。 diff --git a/粒子开发记录.md b/粒子开发记录.md index 8f65307d7..d28e56313 100644 --- a/粒子开发记录.md +++ b/粒子开发记录.md @@ -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()` 冒充。