16 KiB
STEP-056 React Native 天气渲染桥接现状调查
状态:调查完成(只读);STEP-055 仍为 IMPLEMENTED / WAITING_ANDROID_VALIDATION。
Starting HEAD:44a50e6e4a4e79fae7ddf36af007b5e35e969a92(particle/main,origin/particle/main 同步确认)。本报告没有修改功能代码、没有执行 Android Gradle/APK/ADB/设备测试或 Jest。
A. 当前真实调用架构
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)。
LocalParticleFieldFeed::load()解析 provider-neutral metadata,并调用ParticleFieldDataAdapter::decode()。- Adapter 严格检查非空 fieldId、合法 generation/validTime、width/height、
float32-le+interleaved-uv、精确 payload bytes;成功后生成拥有velocity与valid的 immutable Snapshot。 - 第一个合法 Field 成为 pending T0,结果为
AcceptedPendingInitialWindow。 - 第二个严格较新的 compatible Field 通过既有
submitInitialRuntimeFieldWindow(T0,T1),建立RasterArrayTimelineRuntime,结果为InitialWindowReady。 - 后续 Field 按 producer
validTime追加同一个 bounded Timeline,结果为AcceptedNextSnapshot。 - 相同 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:
- 低频 runtime lifecycle(enable/suspend/hide/unload)与
clearWindFields;安全 layer/source teardown。 - canonical
submitWindField(metadata, binary)、timestamp seek、play/pause/speed 的 typed Android/JNI facade,复用 Source/Timeline。 - 可见性、现有 particle visual properties、WeatherColor opacity 的 typed API;增加 structured submission/runtime/error event,避免 per-frame callback。
- 定义并接入 dynamic WeatherColor stops 与独立 particle/trail colors 的 style property contract;所有 provider/network/forecast decision 仍留 JS。
- 以 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 层可薄包裹它。
最大三项风险:
- 3.01 MiB 级 payload 在 JS/Java/JNI/Snapshot 的多次 copy 造成峰值内存、GC 与提交抖动。
- visibility、suspend 与 unload 被混同,导致 Native/GPU/Snapshot 资源未真正释放,或在 style/render thread 上错误 teardown。
- 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 逻辑。