Files
MapLibreParticleLab/docs/20_调查/STEP-050_正式数据链路下一阶段调查报告_v1.md
OpenAI Codex 0dacb72b57
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
docs: investigate production particle data pipeline
2026-09-04 14:29:48 +08:00

15 KiB
Raw Permalink Blame History

STEP-050 正式数据链路下一阶段调查报告

1. Git / 调查范围

  • 仓库:MapLibreParticleLab
  • 分支:particle/main
  • Starting HEAD:1572b6c3b1bcc2ab0007181bad0609e7d0134575
  • origin/particle/main:1572b6c3b1bcc2ab0007181bad0609e7d0134575
  • Gitea API 分支 HEAD:同上
  • 调查性质:代码与既有文档只读调查;未修改 Core、Renderer、Android、Tests 或 build files。

2. 结论摘要

STEP-049 的 provider-neutral contract 已经存在,但当前真正落点仍是 Core 内部的“已校验 bytes → immutable Snapshot”边界,不是生产数据源。当前实际链路为:

FileSource::request(Resource::source(...)) → RasterArraySource 收集 metadata/field bytes → OfflineFixtureParticleAdapter::load() 解析并校验 fixture-v1 → ParticleFieldDataAdapter::decode() → ParticleFieldSnapshot → RasterArraySource::Impl::field/nextField → RasterArrayTimelineRuntime / updateRuntimeFieldWindow() → Renderer。

仓库没有 Weather Service client、provider abstraction、正式天气网络 schema、GRIB/NetCDF decoder、Particle 生产 cache 或 production feed API。MapLibre 的通用 FileSource/HTTP 能力不能视为 Particle Weather Service。

推荐 STEP-051:Local Production-like Field Feed 与 Core Snapshot Submit Bridge。先用本地/离线外部 binary feed 驱动正式 neutral adapter 和既有 Runtime,建立稳定的 production-like producer/submit 边界,再在后续独立阶段讨论 provider/network decode。

3. ParticleFieldDataAdapter 现状

3.1 API 与真实文件

  • include/mbgl/particle/field/field_data_adapter.hpp
    • ParticleFieldDescriptor:fieldID、fieldType、displayDomain、validTime、尺寸、地理 bounds、noData、dataVersion、byteLength、stride、layout、encoding、可选 fieldSha256。
    • ParticleFieldSnapshot:拥有自己的 std::string 与 std::vector<float>/std::vector<uint8_t>,保存 canonical Runtime 数据和 generation。
    • ParticleFieldAdapterError / ParticleFieldAdapterResult:结构化错误与可日志化 message。
    • ParticleFieldDataAdapter::decode(const ParticleFieldDescriptor&, const std::vector<char>&, uint64_t generation):当前唯一正式 decode API。
  • src/mbgl/particle/field/field_data_adapter.cpp
    • 校验 identity/generation、dimensions/bounds/noData、interleaved-uv、float32-le、stride、byte length 与 finite data。
    • 将 payload copy 到 Snapshot;adapter 不保存外部 bytes、Android 对象、Timeline、Renderer resource 或 provider 对象。
    • 当前 renderer 语义仍限定 WIND + GLOBAL,Wave/Tide/Current 尚未实现。
  • src/mbgl/particle/field/offline_fixture_particle_adapter.cpp/.hpp
    • OfflineFixtureParticleAdapter::load() 是当前 caller 的 validation wrapper:解析 maplibre-particle-fixture/v1、校验 fixture whitelist、metadata、checksum,再构造 neutral descriptor 调正式 adapter。

3.2 Caller 与 generation/identity

当前调用者为:

  1. src/mbgl/style/sources/raster_array_source.cpp::tryLoad():初始 T0/T1 使用 nextRasterArrayFieldGeneration,再写入 Impl::field/nextField。
  2. RasterArraySource::tryLoadRuntimeField():runtime candidate bytes 完整后调用 fixture wrapper,candidate 在 adapter 前获得 Core generation。
  3. src/mbgl/renderer/layers/render_particle_validation_layer.cpp:Validation renderer 的 fixture-only fallback/validation 资源路径仍调用 fixture wrapper。
  4. test/style/source.test.cpp:直接 adapter、fixture wrapper、Runtime Window 与 Timeline 定向测试。

因此当前没有 Weather provider 或 production caller。generation 是 Core runtime revision,由 Source/Runtime 负责分配;adapter 只校验调用者传入的非零 generation。fieldID/identity 由 descriptor producer 提供,adapter 不生成业务 identity。

4. ParticleFieldSnapshot ownership 与正式输入适配性

Snapshot 已基本 provider-neutral:不含 URL、token、provider 名称、网络客户端、Android 对象或 GPU resource;vector ownership 稳定,适合作为 Runtime 输入。现存 fixture 语义不再进入 Snapshot,fixture ID 在 wrapper 中映射为 fieldID。

它已包含当前 Runtime 所需的最小 grid/field metadata,但存在两项生产化边界:

  • validTime 已存在于 neutral descriptor/Snapshot,但当前 fixture metadata 的 timestamp 未映射,Timeline Source 仍使用固定逻辑时间。
  • Snapshot 的 isValid() 目前按 Wind/Global renderer 语义判定;未来 Ocean-only field/mask 需要另行设计,不应在本阶段偷偷扩大。

5. RasterArraySource / Runtime submit 入口

5.1 当前入口

include/mbgl/style/sources/raster_array_source.hpp 提供:

  • updateRuntimeFieldWindow(ParticleFieldSnapshot newT1):已校验 Snapshot 进入唯一 authoritative T0/T1 Window 的正式入口。
  • advanceRuntimeFieldWindow() / repeatRuntimeFieldWindow():当前 Source-owned validation convenience API。
  • controlRuntimeTimeline():Android 低频命令桥,Core 持有 Timeline/Playback。

如果未来已经拥有一个合法 ParticleFieldSnapshot,最短路径是直接调用 updateRuntimeFieldWindow(std::move(snapshot));它会复用现有 OLD_T1 → NEW_T0、only NEW_T1、Duplicate NOOP 和 fail-closed 逻辑。当前该路径只适合已有 T0/T1 的 Source,尚未提供独立的 production feed/submit queue 或外部 source ownership bridge。

5.2 当前 Source 的 fixture 限制

src/mbgl/style/conversion/source.cpp::convertRasterArraySource() 要求 fixture、metadata、field,runtime candidates 要求 fixture、metadata、field。RasterArraySource::loadDescription() 通过 MapLibre FileSource 请求 bytes,但随后仍直接调用 fixture wrapper。

所以 Source 已有可复用的异步 byte transport,却不是 production-neutral source:它的 style schema、成员命名、metadata parser 和 candidate preload 仍是 fixture validation 入口。下一阶段应增加一个极薄的 neutral feed/submit bridge,或把现有 Source 输入适配到 neutral descriptor;不应复制第二套 Window。

6. Timeline snapshot 数据来源

RasterArraySource::tryInitializeTimeline() 构造 RasterArrayTimelineRuntime::Entry:

  • Impl::field 作为 index 0/T0;
  • Impl::nextField 作为 index 1/T1;
  • runtimeFields[index].snapshot 作为后续 candidates;
  • 逻辑时间当前硬编码为 0、40000、80000 + index * 40000。

RasterArrayTimelineRuntime 本身只消费 immutable Snapshot shared pointers、严格递增 timestamp 和有限 cache(capacity 8),其 play/pause/tick/seek/step/gap/invalid/recover 不认识 fixture schema。它能够消费外部 production Snapshot,但当前 Source 的 builder 只能从 fixture candidate 生成 Snapshot,且 candidate preload 仍由 fixture wrapper 完成。

结论:下一阶段原则上不改 Timeline;提供新的 Snapshot producer,并在 Source 侧把 producer 结果接到现有 entries/submit 入口即可。若生产 feed 需要真实 forecast timestamp,应在 producer/Source metadata 接线中补齐 Entry timestamp,而不是重做 Playback。

7. 仓库已有数据源能力与边界

已存在但不是 Particle 生产业务

  • MapLibre 通用 FileSource、Resource::source()、异步 request():可请求 asset/file/HTTP 资源,已有于 GeoJSONSource、ImageSource、TileSource 等通用 Source。
  • src/mbgl/util/io.cpp::read_file():通用本地文件工具。
  • platform/default/src/mbgl/storage/offline_database.cpp、offline download:MapLibre 离线资源能力,不是天气字段 decoder 或 Particle field cache policy。
  • test/fixtures/resources 和 particle-fixtures/*/field.f32:测试资源。

不存在

搜索 weather、forecast、provider、grib、netcdf、windy 等 Particle 业务能力后,没有 Weather Service client、provider registry/factory、外部天气 schema、GRIB/NetCDF decoder、production field downloader 或专用 cache。当前唯一 binary field loader 是 fixture wrapper 的 std::vector<char> 读取/异步 bytes 路径;没有 production local binary source。

因此不能因为 MapLibre 有 HTTP/FileSource 就直接引入网络。当前最小 production-like 验证应使用本地或离线 bytes,保持 provider-specific decode 在 adapter 上游,Renderer 只消费 Snapshot。

8. 候选方案比较

候选 A:Core Production Field Feed / Snapshot Submit API

  • 目标:建立接收已校验 descriptor/payload 或 Snapshot 的极薄 Core feed bridge。
  • 原因:当前 adapter 已稳定,但 Source 仍只有 fixture style schema;A 能先解耦 producer 与 fixture schema。
  • 涉及:新建小型 Core feed 类型,最小调整 RasterArraySource;复用 ParticleFieldDataAdapter 与 updateRuntimeFieldWindow()。
  • 新模块/新架构:可有一个小模块,不建立 Provider Framework。
  • network/vendor:不需要;不允许进入 Renderer。
  • Timeline/Renderer:不修改 Timeline 语义,不修改 Renderer。
  • RasterArraySource:需要最小 submit/initial-window 接线。
  • Android APK:若只做 Linux Core contract,可不需要;若增加 validation entry,则和 B 合并后一次 APK。
  • 风险:初始 T0/T1 建立、generation 分配与 timestamp ownership 需要明确,不能让 feed 管理第二套 Window。

候选 B:Local Binary / File-backed Production-like Field Source

  • 目标:用真实外部 binary + neutral metadata(不含 fixture whitelist/provider 信息)驱动 adapter、Timeline 和 Runtime,替代 synthetic fixture 验证生产链路。
  • 原因:当前缺口最具体,能验证 payload ownership、时间 metadata、preload 和 fail-closed,而不提前绑定天气供应商。
  • 涉及:local/asset feed loader、neutral metadata decode、RasterArraySource 最小 bridge、离线 fixture/test resource。
  • 新模块/新架构:一个轻量 source/loader;不需要 Provider Framework。
  • network/vendor:不需要;provider-specific decode 留在上游边界。
  • Timeline/Renderer:原则上不修改 Timeline/Renderer;复用现有 entries 与 Window。
  • RasterArraySource:需要最小修改,使 style/source 不再强制要求 fixture,或增加明确 production-like feed 构造入口。
  • Android APK:建议需要一次,用同一 APK 覆盖 local feed、Timeline、Window/Playback 回归;不单独拆 build。
  • 风险:本地 metadata schema 若设计过大,会重新形成第二套业务 schema;必须只覆盖当前 descriptor 与 forecast timestamp。

候选 C:Weather Service / Network Provider 接入骨架

  • 目标:直接建立网络 weather service/provider client。
  • 原因:最终产品需要,但当前仓库没有成熟服务边界或外部 decoder。
  • 涉及:Android/Core network、鉴权、retry、cache、provider schema 和 decoder。
  • 新模块/新架构:会引入明显新架构。
  • network/vendor:需要,且风险最高;绝不能进入 Renderer。
  • Timeline/Renderer:应不改 Timeline/Renderer,但上游数据生命周期和错误映射复杂。
  • RasterArraySource:可能需要异步 feed/preload API。
  • Android APK:需要,且会显著扩大综合验收。
  • 风险:违反第一阶段离线边界,容易把 vendor/network 业务塞进 Core/Android 或模糊 fail-closed 语义。当前不推荐。

候选 D:Timeline/Preload 通用化

  • 目标:先把 Timeline candidate/cache 从 fixture 命名和固定逻辑时间中抽象出来。
  • 原因:当前 RasterArrayTimelineRuntime 的消费接口其实已 neutral,只有 Source builder 仍 fixture-specific。
  • 涉及:RasterArraySource builder、timestamp metadata、candidate ownership。
  • 新模块/新架构:不应新增模块;属于现有 Source 小范围整理。
  • network/vendor:不需要。
  • Timeline/Renderer:尽量不修改 Timeline state machine,可能需要 Entry timestamp 接线;Renderer 不改。
  • Android APK:只有和 A/B 合并时需要一次。
  • 风险:单独做 D 会在没有真实 production producer 的情况下提前抽象,收益小于 B;不得变成第二套 cache/state machine。

9. 推荐下一实现阶段

  • STEP 编号:STEP-051
  • 中文名称:Local Production-like Field Feed 与 Core Snapshot Submit Bridge
  • 英文短名:Local Production-like Field Feed

目标范围:

  1. 定义一个最小 local/external binary feed 输入,使用 neutral descriptor + bytes,不使用 fixture whitelist。
  2. 让该 feed 通过 ParticleFieldDataAdapter 生成 immutable Snapshot,并明确 generation、identity、validTime 和 payload ownership。
  3. 在 RasterArraySource 增加最小 initial-window/next-feed bridge,继续调用既有 updateRuntimeFieldWindow() 和唯一 Timeline。
  4. 让 Timeline 使用 producer 提供的 forecast timestamps,验证 preload、PLAY、auto MIX、boundary、Gap/Invalid fail-closed。
  5. 仅使用离线 local/asset payload;增加 C++/Core 定向测试与一次 Android 综合验证入口。

明确不做:

  • 不进入 iOS/Metal;
  • 不重做 Timeline、Playback 或 Runtime Field Window;
  • 不重做 Renderer/GPU simulation/trail;
  • 不把 provider/network/vendor 逻辑放入 Renderer;
  • 不实现 Weather Service、HTTP client、鉴权、retry、cache server 或真实 provider decoder;
  • 不合并 WeatherColorLayer 与 RasterParticleLayer;
  • 不为 MIX observability 单独触发 Android build;
  • 不为 Rotation/Viewport 技术债单独触发 Android build。

10. Rotation / Viewport 与 MIX 技术债

当前 ParticleCoreLayerValidationActivity.kt 具备 onStart/onResume/onPause/onStop/onDestroy 对 MapView 的标准生命周期转发,并通过 onSaveInstanceState() 保存 Validation 控件状态;代码中没有独立的 Particle viewport/camera 恢复状态机。历史文档仍记录过 Rotation/Viewport 自动恢复延期与后续 control-mode 恢复,现状不足以把它当作生产数据链路已完全证明的 contract。

STEP-051 推荐阶段不需要修改 Android Validation Host、viewport 或 source reload,因此 Rotation/Viewport 保持独立技术债,不合并、不为其单独 build。若 STEP-051 为增加 local feed 入口必须改 UI,则只在同一 APK 中顺带回归,不扩展为独立修复。

MIX UI 文本滞后仍是 NON-BLOCKING observability;Core MIX 已通过。STEP-051 若本来增加 Android 状态入口,可顺带刷新显示;若不需要 UI 变化则继续保留,不单独触发 build。

11. Android 成本与一次验收建议

STEP-051 先完成 Linux Core/OpenGL 精确编译、adapter/source/timeline 定向测试、离线 local feed 资源与验证入口,再由用户用一次 APK 覆盖:local feed load、Timeline READY/PLAY、auto MIX、至少两个 Window boundary、OLD_T1 reuse、only NEW_T1 upload、Pause/Resume、Step/Seek、Gap/Invalid、Duplicate NOOP、Renderer fail-closed、rotation/lifecycle 回归(若入口有改动)。不执行 Jest;Codex 不执行 Android Gradle、APK、ADB 或 App 启动。

12. 是否可进入 STEP-051 实现 MD

可以。调查已确定最小实现方向、边界和验收策略:先写 STEP-051 设计/任务 MD,冻结 local feed descriptor/metadata、timestamp/generation ownership 与 Source bridge,再实现。不要在 STEP-051 直接引入 Weather Service/network/provider framework。