Compare commits
14 Commits
maplibre-p
...
e48258434e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e48258434e | ||
|
|
4d9cb0ef69 | ||
|
|
1aba9875f0 | ||
|
|
9d6c9e9684 | ||
|
|
3c3dcb9d18 | ||
|
|
7f92f71416 | ||
|
|
970e3ad606 | ||
|
|
a9cea8b581 | ||
|
|
b0f34cc9a2 | ||
|
|
e3cd9eba3c | ||
|
|
21c4450e01 | ||
|
|
fce326a900 | ||
|
|
343ca2fa68 | ||
|
|
badb515d59 |
161
docs/40_任务/STEP-110_R0_Placement跨段归因只读调查_Codex任务_v1.md
Normal file
161
docs/40_任务/STEP-110_R0_Placement跨段归因只读调查_Codex任务_v1.md
Normal file
@@ -0,0 +1,161 @@
|
||||
# STEP-110 R0 Placement 跨段归因与缓存安全边界只读调查 — Codex 任务 v1
|
||||
|
||||
## 一、任务性质与核心问题
|
||||
|
||||
**性质:Particle Native / Shared Core 专项,只读调查;不实现、不修复、不编译、不上机。**
|
||||
|
||||
用户要求粒子项目独立回答:
|
||||
|
||||
> Native 能否区分“R0 自己的 Placement 变化”和“其他 RenderSegment 引起的 Placement 变化”?如果能区分,是否足以在**不破坏 Symbol 碰撞、文字/图标位置及图层顺序**的条件下让未变化的 R0 复用缓存?
|
||||
|
||||
必须把以下不同概念分开审查:
|
||||
1. **事件来源**:哪个 source/layer/segment 引发了 placement/repaint/revision;
|
||||
2. **实际影响范围**:全局碰撞计算是否使 R0 的 Symbol 位置、可见性、fade 或绘制结果改变;
|
||||
3. **缓存安全判据**:能否证明 R0 内容/Placement 仍与已缓存画面等价;
|
||||
4. **当前实现**:上述信息目前是否已有、是否按 Segment 使用、还是仅采用全局保守 Gate。
|
||||
|
||||
禁止预设“全局 Placement 变化一定是 Bug”,也禁止以“R0 自己没有提交数据”直接推导“R0 视觉必然没有变化”。
|
||||
|
||||
## 二、仓库、版本与事实优先级
|
||||
|
||||
- 项目:`/Users/tei/dev/MapLibreParticleLab`,正式分支 `particle/main`、远端 `origin/particle/main`。
|
||||
- 创建任务时只读确认:`particle/main` HEAD `2df504cdc0403bdad85d3f31f16fd54a8744abac`、干净工作树。执行时必须**重新读取 Git 实际状态**,不得把此 HEAD 当作永恒基线。
|
||||
- NavSea 产品实机所用 APK 内 `assets/component-manifest.json` 记录:RN / Android Native AAR `13.6.1-particle.23`。V23 正式来源对应 `refs/tags/maplibre-particle/rn/13.6.1-particle.23`、GSRL Git commit `87f3d1c59a52581fb8339f0ea7d106b9f4b94fef`。
|
||||
- **必须分别标明 V23 发布源码与当前 `particle/main` 的事实**。如当前 main 有未发行后续改动,不能以 main 代替 V23,不能以 V23 APK 推断未发行修复已经生效。
|
||||
- 跨项目事实遵循 `D:\React\navsea-particle-integration\instructions\NAVSEA_PROJECT_INSTRUCTIONS.md` 及最新 COORD / RFC / ADR / Contract / Status;当前既有外部契约 C-001。此任务不新增/更改公共 API 或契约。
|
||||
- 粒子项目根 `AGENTS.md`、`PARTICLE_BASELINE.md`、`docs/00_项目规范/`、`docs/00_项目总则/README.md` 与四份有效总则必须先读,按当前项目事实处理过时的历史环境/发布坐标。
|
||||
- 必读既有证据:STEP-088、089、090、092、094、099、100、102、106、108、109;重点对比 STEP-090 早期缓存/快路成功条件、STEP-102 placement-free 分段隔离范围、STEP-106 T1/T2 Symbol 与 Annotation 边界的真实验收。
|
||||
|
||||
## 三、来自 NavSea 的问题证据(仅作为输入,不由本任务修改 NavSea)
|
||||
|
||||
FZG1-2 / Android 13 / V23 当前 APK,NavSea 采用**两段 R-C**:
|
||||
|
||||
- R0:RETAINED,31 层,`background` 至 `place-labels-land-ja`,包含有 Placement 能力的 Symbol;
|
||||
- C1:CONTINUOUS,82 层,`rn-weather-color` 至 `chartDangerScanCenterLineLayer`,包含天气/粒子和其他业务层。
|
||||
|
||||
固定同一风场、同一 map 生命周期,连续约 15.24 秒,`mapReactTag=864`、`generation=4`、`configRevision=1`、`topologyRevision=8`、113 层均不变:
|
||||
- 风场期间进程 CPU 样本均值约 98.6% / 99.1%,关闭天气后约 4%~8%;
|
||||
- `fastPathHit Δ0`、`fastPathMiss Δ452`、`fullCpuFrame Δ453`;
|
||||
- R0 `build Δ0`、`reuse Δ0`、`invalidation Δ453`、`fallbackCount Δ453`、texture/composite create `Δ0`;
|
||||
- 两端 `valid=true`、`fallback=false`、`sourceReady=true`、`sourceStable=true`、`placementChanged=true`、`placementGatePassed=false`、`cache.supported=false`、`cacheState=fallback`、`lastInvalidationReason=static-placement`;
|
||||
- `mainBufferExecutionMode=mixed`、`globalLiveFallbackReason=none`、`fastPathFallbackReason=cache-invalid`。
|
||||
|
||||
**这些是症状,不代表已证明 C1 导致 R0 Placement 改变。** 此外,历史较早的 V23 短时快照曾观察到 `placementChanged=false` 但 R0 记录仍为 `static-placement`,需要区分“当前门槛值”和“上次失效原因/诊断时序”,避免误读。
|
||||
|
||||
Mac 粒子团队如无法访问 Windows 原始快照,可依据上述冻结事实做 Native 只读审计;缺失证据须列为“需要后续验证”,不得自行编造 NavSea 图层或运行结果。
|
||||
|
||||
## 四、必须回答的调查问题
|
||||
|
||||
### Q1:Placement 的产生与传播
|
||||
|
||||
从真实 V23 Native 代码绘制完整的因果链:
|
||||
- 触发 RenderTree / Placement 计算的入口与时机是什么?
|
||||
- `placementChanged`、`symbolFadeChange`、`placementTransitionActive`、`crossSourceCollisions` 分别表示**当前帧/上帧/全局/局部**的什么事实?何时清零或稳定?
|
||||
- Source/Layer 的 layout、tile、visibility、camera、animation 等变化,分别可能影响哪些 Placement 状态?
|
||||
- R0 以外的 C1 Symbol/Annotation/业务层变化,**在哪些条件下**可能影响 R0 的符号碰撞与最终视觉?
|
||||
- 如果 RenderTree 每次都重新算 Placement 但画面没有变化,如何区分“执行过计算”和“R0 最终结果发生变化”?
|
||||
|
||||
### Q2:现有 R0 Gate 的作用域
|
||||
|
||||
调查 V23 实际调用链及数据结构,至少解释:
|
||||
- R0 结构性 `placementCapable` 如何判定,与动态 `needsPlacement()` 有何区别;
|
||||
- R0 `placementGatePassed` 如何使用全局 `placementChanged`、fade、transition;
|
||||
- R0 `cache.supported`、`static-placement`、`actualExecution=live` 的完整因果关系;
|
||||
- R0 的 source-local mutation、segment fingerprint、retained revision 与 placement gate 是否真正各自独立;
|
||||
- STEP-102 对 **placement-free** R 的隔离已解决什么、**placement-bearing** R 仍未解决什么;
|
||||
- `lastInvalidationReason` 与当前失败条件是否可能属于不同采样/缓存生命周期。
|
||||
|
||||
### Q3:能否证明“R0 不受 C1 变化影响”
|
||||
|
||||
针对以下反事实场景逐项作出有证据的判断:
|
||||
1. R0 含静态地名,C1 只有普通非 Symbol 动画/颜色/粒子更新;
|
||||
2. R0 含静态地名,C1 的 Symbol 内容变化,但不同 Source 且跨源碰撞关闭;
|
||||
3. R0 含静态地名,C1 的 Symbol 内容变化且跨源碰撞开启;
|
||||
4. R0 Symbol 自身 layout/tile/visibility/zoom 发生变化;
|
||||
5. Camera/pan/zoom/rotation/pitch 变化;
|
||||
6. Symbol Fade/Placement Transition 活跃或刚结束;
|
||||
7. C1 有内部自动插入的 Annotation Symbol(参照 STEP-106)。
|
||||
|
||||
每行必须分别给出:
|
||||
- “谁触发 Placement”能否识别;
|
||||
- “R0 最终 Placement/可见性是否改变”能否独立证明;
|
||||
- **允许 R0 Reuse 的必要安全条件**;
|
||||
- 当前 V23 是否具备这种判定能力及证据来源;
|
||||
- 无法证明时为什么必须继续 fail-closed。
|
||||
|
||||
严禁把“不同 Segment”简单等同于“碰撞和 Placement 相互独立”。
|
||||
|
||||
### Q4:对 Fast Path 的影响(仅限前置依赖)
|
||||
|
||||
解释“R0 缓存有效”是否为 Fast Path 的必要条件但**非充分条件**。只读标出 R0 Gate 与单 C / unsupported continuous layer / revision / pending changes 等后续 Gate 的先后关系。不要扩大成 C1 全图层性能调查,不实施多 C 优化。
|
||||
|
||||
## 五、只读检查范围与方法
|
||||
|
||||
允许读取:
|
||||
- 当前 Particle Core 的 Renderer、RenderOrchestrator、PlacementController、RenderTree、Symbol/Collision、Retained Cache、RenderSegment Plan、Diagnostics 与 Fast Path 相关源码;
|
||||
- V23 tag 与当前 `particle/main` 的只读 `git show`、`git diff`、`git log`;
|
||||
- STEP-090/102/106 等历史设计、报告、设备验证结果;
|
||||
- 现有 C++ 定向测试源码与 Validation 场景源码(只读,不执行、不修改)。
|
||||
|
||||
建议从当前实际路径 `src/mln/renderer/` 与相关 Shared Core 源码定位,**不得沿用旧 STEP 中过时的路径假设**。
|
||||
|
||||
每一条重要结论必须给出:**文件路径 + 类/函数或结构名称 + 适当行范围或 commit + 相关调用顺序**。核心论断不得只引用历史文档或本任务描述。
|
||||
|
||||
允许纯只读 Git/文本搜索,不要求也不允许额外建立实验分支、修改 Native/JS/Kotlin/测试代码、插桩、添加日志、运行 Jest、运行测试、Gradle、CMake/Ninja/Bazel、ADB 或重新安装 App。不得改 NavSea、Integration 或正式 release/tag。
|
||||
|
||||
## 六、最终必须提供的结论矩阵
|
||||
|
||||
**每个问题必须选择明确判定**,而不是只罗列建议:
|
||||
|
||||
| 维度 | 可选结论 |
|
||||
|---|---|
|
||||
| 能否定位 Placement 事件来源 | 现有数据可区分 / 现有数据不足 / 条件性可区分 |
|
||||
| 能否证明 R0 的实际视觉结果未变化 | 已能证明 / 需扩展内部安全证据 / 不能按段安全证明 |
|
||||
| 现有 Gate 是什么性质 | 正确的设计保守限制 / 明确可证的过度阻塞 Bug / 混合 / 证据不足 |
|
||||
| 缓存失效的主要责任边界 | Native Placement 数据模型 / Native Gate 判定 / 当前跨段碰撞语义 / Host Style 组合 / 多因素 / 未确定 |
|
||||
| 可选后续方向 | 无需代码调整 / Native 内部诊断补强 / Native 内部安全判据设计 / 宿主层序方案需协调 / 需真机对照实验 |
|
||||
|
||||
如果判断是 Bug,必须指出**违背了哪项既有语义/可复现的状态不变量**;如果判断是安全限制,必须指出**缺少何种证明才不能放宽**。如有多个阻塞项,按必要性和因果顺序排序,避免“修第一个之后才继续猜下一个”。
|
||||
|
||||
## 七、报告与交接产物
|
||||
|
||||
只允许新建/更新以下本 Step 文档(不改现有源码):
|
||||
|
||||
1. `docs/20_调查/R0_Placement跨段归因与缓存安全边界调查_v1.md`(主报告);
|
||||
2. `docs/50_Step记录/STEP-110_R0_Placement跨段归因只读调查_v1.md`(状态及完成项);
|
||||
3. `docs/报告/STEP-110_R0_Placement跨段归因只读调查_Codex完成报告_v1.md`(执行报告);
|
||||
4. `粒子开发记录.md`(简短追加式记录)。
|
||||
|
||||
调查主报告必备章节:
|
||||
- V23 tag / 当前 main 基线对照,说明是否有相关行为变化;
|
||||
- Q1~Q4 的逐条答案及代码证据;
|
||||
- **数据流/状态流**:Placement 事件 → 全局/局部状态 → R0 安全判定 → Cache Build/Reuse/Fallback → Fast Path;
|
||||
- 七类反事实场景矩阵,明确跨源碰撞、Fade、Annotation 的影响;
|
||||
- 已确定事实、假设、证据不足之处三栏;
|
||||
- 结论矩阵、根因优先级、最小下一步验证建议;
|
||||
- 若需调整公共 Contract / API,则仅提出 Integration 阻塞,不自行实施。
|
||||
|
||||
完成报告必须逐项按“**已完成 / 未完成 / 无法验证**”回答任务项并说明原因;记录实际检查范围、当前 branch/HEAD、Git 提交及推送信息。不得用“源码已读”代替代码证据。
|
||||
|
||||
**状态判定**:只读因果审计证据完整才可标为 `调查 PASS(不等于修复 PASS)`;否则 `PARTIAL/BLOCKED` 并列出缺失项。严禁声称“Native 已修复”、“R0 已缓存”或“CPU 已下降”。
|
||||
|
||||
## 八、Git 收尾与执行界限
|
||||
|
||||
- 本任务**只负责调查**。Codex/粒子项目未经用户后续明确授权,不得实施任何缓存、Placement、Fast Path 逻辑修改。
|
||||
- 完成相关文档后,仅提交本 Step 新增报告、Step 记录及追加的开发记录;保留所有无关 dirty/untracked 文件,不得强制清理。
|
||||
- 将本任务相关文档 scoped commit 到正式主线 `particle/main` 并 push 到公开远端 `origin/particle/main`。禁止 force push、改写历史、推送 upstream 或增加无关文件。
|
||||
- 完成报告必须给出**实际 commit hash、commit message、push 结果、远端分支**;push 失败不得宣称完成,应明确报告阻塞。
|
||||
- 本 MD 是交给粒子项目执行的**待办合同**。写入任务书本身不代表调查已执行、已验证或已得出结论。
|
||||
- 本任务不包含 Jest,不涉及原生编译、测试执行或 Android 设备操作。
|
||||
|
||||
## 九、停止条件
|
||||
|
||||
遇到以下情形须停留在调查层,提交 PARTIAL/BLOCKED 事实,不得扩大实现:
|
||||
1. V23 tag 与当前代码差异导致无法确认实际执行版本;
|
||||
2. Placement 影响关系必须依赖运行时数据,静态源码无法证明;
|
||||
3. 证明缓存安全需新增 per-segment collision/placement fingerprint 或改写 Core 算法;
|
||||
4. 需要改变对外 Native/RN 公共接口或跨项目责任;
|
||||
5. 需要 NavSea 配合修改 Style、增加调试或重新编译/测试;
|
||||
6. 所需证据缺失或与 STEP-090/102/106 的历史结论相冲突。
|
||||
|
||||
出现以上情况,在报告中给出**最小下一步任务建议**,等待用户/Integration 决定;不得在本任务中直接实施。
|
||||
@@ -0,0 +1,41 @@
|
||||
# STEP-111 R 段 Placement 结果等价与独立刷新可行性只读调查 — Codex 任务 v1
|
||||
|
||||
## 一、用户目标
|
||||
用户希望 Native 对 R-C-R-C-R-C(简称 RCRCRC)中的每一个 RETAINED R 段单独决定是否更新。若该段本身最终视觉输出未变化,不应仅因另一段 Symbol/Placement/Fade 全局状态更新而被动重建;确实受跨段碰撞影响的 R 段必须正确更新,不能破坏文字、图标或层序。此任务是下一步架构/可实现性调查,不是授权实施优化。
|
||||
|
||||
**核心区别**:A. 哪个 Layer/Source/Segment 触发重新计算;B. 全图 Placement 后每一个 R 段的最终 Symbol 视觉结果是否改变;C. Retained R 段缓存能否安全复用;D. 是否可进一步省略 R 段 prepare/placement 计算。A≠B、B≠C、C≠D。不要把各 R 分段直接当作 Collision Domain,也不要承诺每段无需参与全图 Placement 计算。
|
||||
|
||||
## 二、正式边界与必读
|
||||
- Mac /Users/tei/dev/MapLibreParticleLab,particle/main,origin/particle/main;执行时重新记录 HEAD、branch、dirty、远端基线;保留未跟踪 STEP-110 任务书。
|
||||
- 先读仓库 AGENTS.md、PARTICLE_BASELINE.md、docs/00_项目规范/、docs/00_项目总则/README.md 和全部现行总则;读 STEP-110 主报告和 Codex 完成报告,以及 STEP-102、106、109、Fast Path 相关历史证据;以当前源码事实为准。
|
||||
- 先检查 /Users/tei/dev/navsea-particle-integration 的 main 与 origin/main 同步状态,读取其 README、WORKFLOW、TEAM_MODEL、contracts、rfc、adr、status、compatibility;已有跨项目结论优先。本 Step 只讨论 Shared Core Native,不直接修改 Integration/NavSea/Android/iOS。
|
||||
- STEP-110 已证明:PlacementController 仍是整图语义;R 的 placementCapable=true 时 Gate 消费 global placementChanged、symbolFadeChange、placementTransitionActive;跨 Source Collision 可能影响 R0,source≠segment。STEP-102/106 只验收过 placement-free R 的安全复用。本 Step 不能简单重复这些事实,重点给出**新增的判等/判无变化能力**具体可行性。
|
||||
|
||||
## 三、只读调查工作项
|
||||
1. **从源码定位可实现性**:追踪 CrossTileSymbolIndex、Placement::placeLayers/commit、CollisionIndex/CollisionGroups、symbol buckets、placed icon/text、opacity/fade、RenderTree、RenderLayer、RenderSegment/Retained Cache 的真实控制与数据所有权。指出计算结束何处可以读取每一个 R 的最终 Placement 输出,是否已有稳定 ID、可见性/位置/opacity;写明路径、函数、行号和生命周期。辨别层间/Source 间/段间影响。
|
||||
2. **定义每段结果等价契约候选**:提出具体 per-R PlacementResultKey/Signature/Revision 候选字段(Symbol 实例稳定 ID、tile/source/bucket generation、placement result、collision visible/hidden、text/icon anchor/offset、paint opacity/fade/transition、zoom/camera、glyph/image atlas、style/layer order、render target boundary 等)。逐项解释哪些是 Placement 专属,哪些已有其他 cache gate 覆盖,哪些必须动态比较,哪些不需要加入,避免对每个 Symbol 每帧做过度 O(N) 或漏比较;比较发生于何时、如何处理首次帧、transition、reset、missing data。
|
||||
3. **RCRCRC 示例与判定表**:至少 R0/C0/R1/C1/R2/C2,其中 R0/R1/R2 都有 Symbol。分别分析:
|
||||
- C1 仅粒子/颜色动画,R 均无可见变化;
|
||||
- C1 Symbol 变动且只影响 R2;
|
||||
- C1 Symbol 变动,碰撞也影响 R0/R1;
|
||||
- R1 自己改变但 R0/R2 不变;
|
||||
- 同一 Source 跨段、不同 Source 且 crossSourceCollisions=true/false;
|
||||
- Annotation Symbol 自动插入,layer 顺序、camera/zoom/pitch、tile/glyph fade/opacity transition;
|
||||
- 不能证明者 fail-closed。
|
||||
结论表需区分“依然执行全局 Placement”、“该段仅避免 cache rebuild/composite 是否可复用”、“能否避免 render/prepare”、“Fast Path 仍被哪些其它 Gate 阻断”。
|
||||
4. **方案对比与难度**:对 A 只加 tracing/per-R 指标(不改变判定)、B 新增 per-R result fingerprint 但 shadow compare(仅记录真假)、C 利用已证明等价替换 global-only Symbol Gate 允许单段 Reuse、D 深改 Placement/Collision 成分段增量计算,逐一评估前提、复杂度(1-5)、改动范围、CPU/GPU/内存开销、风险、能否只改 Shared Core、对 Android OpenGL/iOS Metal 的影响。不能因为理论上可 hash 就声称 hash 一定安全。
|
||||
5. **执行与验收计划**:给出最小分阶段实现建议和真实可验证指标,包括 per-R signature stable/change、false invalidation、false reuse(严格 0)、build/reuse/fallback、视觉 A/B、Fade 长尾、CPU/frame、same frame draw ordering;指出先做诊断或原型是否需要新 Native 内部接口,哪些行为必须同时覆盖 Android/iOS。分别评估改善 retained cache 命中与改善整帧 CPU/Fast Path 的边界。
|
||||
6. **明确推荐结论**:回答每个 R 段可否“自身未变就不刷新”、可做到的最小粒度、不能保证的情形、最佳第一阶段(原型)与中长期方案,给出具体源码位置和工程量区间;区分源码已证实 vs 推断 vs 必须运行时验证。
|
||||
|
||||
## 四、禁止项
|
||||
调查只允许读取 Native/历史 Git/本机历史实验文件以及新增本 Step 文档。禁止修改任何 C++/JS/Kotlin/Swift/编译脚本/测试代码;禁止执行编译、链接、测试、Jest、Gradle、ADB、Bazel、CMake、设备操作;禁止更改 NavSea、Integration、公用 Contract/API、GSRL、release/Source Lock、发行 Tag;禁止放宽当前 fail-closed Gate。禁止重置/清除现有 dirty、禁止 force push。除非明确用户授权,不执行后续实施。
|
||||
|
||||
## 五、必做输出(实际生成)
|
||||
1. docs/20_调查/STEP-111_R段Placement结果等价与独立刷新可行性调查_v1.md — 主报告:代码路径/行号、RCRCRC 场景矩阵、Signature 草案字段逐项依据与生命周期、可行方案 A-D 对比、复杂度与工程量估计、风险、可执行的后续 Step 拆分、源码已证/待验证标识。
|
||||
2. docs/50_Step记录/STEP-111_R段Placement结果等价调查_v1.md — 简短中文 Step 记录,状态 PASS(调查完整)/PARTIAL/BLOCKED,记录未验证项与下一步。
|
||||
3. docs/报告/STEP-111_R段Placement结果等价调查_Codex完成报告_v1.md — 完成报告:实际模型与 reasoning effort(仅凭运行会话证据)、读取资料、逐任务结果、调查起/终 HEAD、branch、dirty、文件修改、未运行构建和测试、检查命令、限制、Git/Push 状态如实记录。
|
||||
4. 粒子开发记录.md — 本阶段简短追加(不要重写历史)。
|
||||
Codex 仅负责内容调查和上述报告落盘,Git scoped commit/push/远端验证由 GPT 指挥 RDC 独立负责,完成报告不得预报未执行 Git 结果。原有未跟踪 STEP-110 任务文件不纳入提交。本任务书自身保持未跟踪,避免误提交。
|
||||
|
||||
## 六、验收与停止
|
||||
只在调查结果有对应源码位置/函数/调用顺序,明确安全条件、未证实风险、工程阶段与复杂度对比时,记“调查 PASS”;如生产实际视觉等价无法仅通过源码证明,可记“调查 PASS / 运行验证未执行”,不得声称优化已通过。必要的实机状态/插桩超出只读范围时明确给出下一阶段最小需求,不得扩大当前范围。不得把历史 STEP-110 的 PARTIAL 当成本次已经实现结果。任务退出前向 GPT 汇报生成文件、最后状态和未完事项。
|
||||
33
docs/40_任务/STEP-120_iOS原始Filter无损Native接口_Codex任务_v1.md
Normal file
33
docs/40_任务/STEP-120_iOS原始Filter无损Native接口_Codex任务_v1.md
Normal file
@@ -0,0 +1,33 @@
|
||||
# STEP-120 — iOS 原始 Style Layer Filter 无损 Native 公共接口(JOINT-PBF-001 D4-13 支持方)
|
||||
|
||||
## 1. 主责和环境
|
||||
|
||||
- 联调唯一业务主责:NavSea;本任务专业支持方:MapLibreParticleLab,只增加 iOS Native SDK 的可复用无损 Filter 公共接口,不能把 NavSea Provider/CM95 逻辑迁到 Native。
|
||||
- Mac 原仓库 `/Users/tei/dev/MapLibreParticleLab`,原分支 `particle/main`,当前 HEAD `9d6c9e9684e4c71cc2fb3f08bfb27e1638be4188`,开工必须重新检查 Git status/worktree/submodule,预期干净。**禁止新建/切换 branch/worktree**、reset/clean、stage/commit/push/tag、升级依赖或发行,保留其它项目。
|
||||
- Codex **GPT-6.1 Sol High**,由外部 CLI 明确配置;只实现/自检,不执行 Bazel/Xcode/Android Gradle 构建或发布。后续 RDC 负责独立编译和版本发行管理。
|
||||
- 开始前必须完整读取本仓 `AGENTS.md`、`PARTICLE_BASELINE.md`、`docs/00_项目规范/`、`docs/00_项目总则/README.md` 及 README 中现行全部规范、`docs/00_项目总则/当前基线与开发路线_v2.md`、本任务和最新 iOS 正式发行文档 `release/maplibre-particle/13.6.1-particle.27/README.md`、当前 C++/Darwin 实际源码。本仓早期 Linux 说明与当前 Mac 实际基线冲突时,以已确认 Mac 主机事实为准;报告真实差异,不自行编辑项目总则。
|
||||
|
||||
## 2. 真实问题与任务目标
|
||||
|
||||
NavSea MLRN11.5.0 D4-11 Android 已支持 existing Style Layer 三个 Filter 接口且 Mac Kotlin/Java/AAR 编译通过;D4-12 iOS 支持被明确阻断:`MLNVectorStyleLayer.predicate` 的 C++ `Filter` → NSPredicate 转换对某些复杂 `all` / 带 `locale` 条件会有损,加载时 `styleJSON` 不是当前 Layer 状态,无法保证外部修改检测和原图层 Filter 精确恢复。不得使用 `NSPredicate` getter 或 Style 初始化 JSON 伪造安全快照。
|
||||
|
||||
本 Step 仅在 MapLibre iOS Native SDK 的 `MLNVectorStyleLayer`(或同一现有 Style Layer 类层次)提供 **C++ `style::Filter` → 原始 JSON 对象无损读取 + JSON 对象 → C++ `style::Filter` 直接设置** 的最小公开 ObjC API,让 MLRN 按已冻结单一 Context 语义完成操作。直接使用 `self.rawLayer->getFilter().serialize()` 和真实 `MLNJSONObjectFromMBGLValue`,设置通过 `mln::style::conversion::convert<mln::style::Filter>(mln::style::conversion::makeConvertible(json),error)` 解析并且只在成功后 `self.rawLayer->setFilter()`,不经过 NSPredicate 的有损中转。具体以真实可编译 SDK API 为准。
|
||||
|
||||
## 3. 严格修改范围及安全条件
|
||||
|
||||
允许修改:`platform/darwin/src/MLNVectorStyleLayer.h`、`platform/darwin/src/MLNVectorStyleLayer.mm`,如果必须引用已有内用辅助头可包含,不修改具体 Style Layer 生成器,不增加 NavSea 专用 API、不修改 C++ 算法/滤镜语义、不修改 Android/Particle/RenderCore。若真实源码要求触及其他文件,先报阻断,不自动扩大。
|
||||
- 暴露稳定、简洁、明确的 ObjC 方法,例如 `- (nullable id)rawStyleFilterJSONObject`、`- (BOOL)setRawStyleFilterJSONObject:(id)json error:(NSError **)error`;最终名称与语义请依据当前代码风格确定,务必公开到随 XCFramework 构建的 Headers。
|
||||
- **未修改的原始 Filter** 必须从当前 rawLayer 的 Filter 精确读取,不能触发模型折叠/locale/条件丢失;序列化只允许无损 `mln::Value` 到 Foundation JSON(含 NSNull)。过深/不合法输入应拒绝且不修改原层。源 Layer 已 invalid/detach、非 Vector Layer、解析错误必须拒绝或抛出明确错误。Style generation/Layer ID/revision 上下文属于 MLRN Host,SDK 不自行创建另一套状态管理。
|
||||
- 新接口只改 Filter,绝不可删除 Layer、改变 Paint/Layout/Source/Z-order、重载 Style 或触碰 Particle 渲染。保持既有 `predicate` 原功能/签名及其旧行为不变。
|
||||
- 提供至少三组源码级安全样例(含 `all` 第二个属性与 `locale` 参数、一般 `==`、原为空)作为受限测试/报告中的验证条件;可以用现存 Core/Style 单测框架若实际可用,但本任务不涉及 Jest。无可运行单测时记录待 RDC Test Gate,不编造 PASS。
|
||||
|
||||
## 4. Codex 验收输出
|
||||
|
||||
- 静态检查(无构建时可做 C++/ObjC 语法和源码审查,绝不可冒称 iOS 编译);`git diff --check`。
|
||||
- 所有已完成/未完成/无法验证项和当前 ABI 兼容/新 API 约束写入中文报告:
|
||||
`docs/报告/STEP-120_iOS原始Filter无损Native接口_Codex完成报告_v1.md`,如果同题已存在须递增版本,禁止覆盖。
|
||||
- Step 文件 `docs/50_Step记录/STEP-120_iOS原始Filter无损Native接口_v1.md`,以及 `粒子开发记录.md` 仅追加。不能编辑现有旧报告、Release README/SOURCE_LOCK 或产物记录。
|
||||
- 结论 `IMPLEMENTED_WAITING_RDC_IOS_BUILD` 或明确 BLOCKED;不标记 iOS 可打包、发版、真机 PASS;不创建/切换 Git 分支/worktree、不执行 Commit/Push/Tag。
|
||||
- GPT/RDC 将独立复核并执行实际 Mac Bazel iOS SDK 精确编译,复测无损接口可用性;如通过,另起正式发行/NavSea Host 集成步骤,遵守 GSRL 与跨项目发布边界。主责仍 NavSea。
|
||||
|
||||
目标不漂移:本 Step 为双平台原始 Style Layer Filter 功能提供底层无损基础,不是原始多源 Provider 项目的最终完成。
|
||||
20
docs/40_任务/STEP-120_iOS原始Filter无损Native接口_Codex任务_v2.md
Normal file
20
docs/40_任务/STEP-120_iOS原始Filter无损Native接口_Codex任务_v2.md
Normal file
@@ -0,0 +1,20 @@
|
||||
# STEP-120 v2 — iOS Style Filter 无损或拒绝的最小 Native 接口(续行)
|
||||
唯一业务主责 NavSea;专业支持仓 Mac `/Users/tei/dev/MapLibreParticleLab`,原 `particle/main@9d6c9e9684e4c71cc2fb3f08bfb27e1638be4188`,禁止创建/切换分支/worktree、reset/clean、Git stage/commit/push/tag/发布。Codex 必须 **GPT-6.1 Sol High**。用户授权 Mac 自动完成双平台以便之后自行打包。本续行任务针对 v1 的真实阻断**收敛准入范围**,不是降低安全条件。
|
||||
|
||||
## 必读和原始目标
|
||||
阅读仓库 AGENTS.md、PARTICLE_BASELINE.md、docs/00_项目规范、docs/00_项目总则 README 及全部现行总则、当前路线、v1任务 `docs/40_任务/STEP-120_iOS原始Filter无损Native接口_Codex任务_v1.md`、v1报告 `docs/报告/STEP-120_iOS原始Filter无损Native接口_Codex完成报告_v1.md`、当前 native iOS Filter源码及 Git status。原目标仍是 NavSea 多 PBF Provider/唯一 Mapping/原始 Style/四档/Pickup/Danger/Planning;本 Step 只解 iOS Filter 快照/修改底层能力,不碰业务内容。
|
||||
|
||||
## v1 阻断的确认与限定设计裁决
|
||||
|
||||
1. 当前 `MLNJSONObjectFromMBGLValue` 使用 c_str 导致 Foundation 对合法内嵌 NUL 字符串/键可能截断(另有数值和 legacy Filter 折叠风险)。**无需为了支持所有 JSON 扩大修改到全局转换器**。只对本次 API 的数据执行 preflight 与后验验证:支持被证明转换无损的 Filter;任何包含 U+0000、超出大小/深度/节点限制、数字不能无损转换、Core 重新转换/serialize 与原始 C++ `Filter.serialize()` 不等价、或无法证明互逆的输入必须 **E_FILTER_UNSUPPORTED / NSError**,不能返回截断后的成功对象。用 C++ `mln::Value` 结构/语义等价检查和反向 Core `conversion::convert<Filter>` 验证;仅对可重放的子集返回结果。
|
||||
2. Layer 在正常 `removeLayer:` 后 `rawLayer` 可能仍合法但已不属于 Style;SDK 只有通用“rawLayer 是否有效”检验,**Style 成员关系必须由 MLRN Host 调用侧校验**(当前 Style 根据 Layer ID 返回的对象身份、Token 等)。SDK API 只操作**当前有效 rawLayer**,不能伪称它能证明当前 MapStyle Membership。严格记录此边界到 API Doc 和报告,不因单独 Native SDK 无 MapView 访问而阻断可行实现。已销毁的 rawLayer 要 reject。
|
||||
3. 保留 `NSPredicate` getter/setter 现有行为不变。提供新的独立 ObjC API,在现有 `platform/darwin/src/MLNVectorStyleLayer.h`/.mm 内使用真实 C++ Filter 与现有转换器,无 NSExpression/NSPredicate 读写中转。空 filter 可返回 NSNull,恢复 set Filter(),上游 MLRN 如遵循 Android 约束可选择 fail-closed 不下发 null 修改。
|
||||
4. 新 Native Setter 必须先确保输入 JSON 是支持类型(数组/NSNull 等),在调用 `rawLayer->setFilter` 之前转换、完整验签;绝对不能先写再发现数据损失。若 Core setter 后与预期不符应作可验证原状态回滚/抛错误,必要时由 Host 毒化 context。方法命名稳定、NSError 代码明确,公开头将由当前已确认 Bazel 目标收录。
|
||||
5. 修改范围仍仅 `platform/darwin/src/MLNVectorStyleLayer.h` 与 `.mm`,另只新增中文 Step、报告、追加开发记录;不允许修改全局 `MLNStyleValue.mm` 等辅助文件、不改 C++ Filter、生成器、iOS Build、Android、Particle。若确实无法在两文件实现“无损或拒绝”,再次明确阻断,不制造占位。
|
||||
|
||||
## 实施与校验要求
|
||||
- 实现并精确记录拒绝域:包含 NUL 的字符串/键、特殊数值、大/深对象、legacy filter、SDK不支持的复杂表达式;尽可能不阻断常见 `all / any / ! / get / has / == / literal`,但宁可拒绝也不得删语义。
|
||||
- 静态证明输入与原始 Core `Filter.serialize()` 一致性,至少采集简单等值、范围两个属性、locale,以及 NUL 拒绝例;如果不能在 Codex 环境运行 Native 单测,标记 RDC 待测,不冒称通过。
|
||||
- Codex 不运行 Bazel/Xcode/Gradle 长构建或 Jest;只做 `git diff --check` 等精确源码检查,RDC 后续负责真实 iOS Metal 编译和产物。
|
||||
- 必须输出中文 `docs/报告/STEP-120_iOS原始Filter无损Native接口_Codex完成报告_v2.md`(若已存在则新版本),`docs/50_Step记录/STEP-120_iOS原始Filter无损Native接口_v2.md`,追加 `粒子开发记录.md` 而不覆盖 v1;报告逐项完成/未完成/无法验证,Git/源码/编译/设备证据边界明确。
|
||||
- 禁止任何 Git 分支切换、stage/commit/push/tag、正式发行、破坏其它工作区修改;Codex 只实现;GPT/RDC 完成后严格独立验收。原始多源 Provider 目标仍开放。
|
||||
41
docs/50_Step记录/STEP-119_SymbolRetained生命周期修复_v1.md
Normal file
41
docs/50_Step记录/STEP-119_SymbolRetained生命周期修复_v1.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# STEP-119 SymbolRetained 生命周期修复 v1
|
||||
|
||||
日期:2026-10-12(JST)。Step 状态:进行中。代码交付:**IMPLEMENTED / WAITING_RDC_VALIDATION**。这是此前中断任务的续作;不关闭整体 STEP-119,不构成编译、GTest、GPU 或设备验收 PASS。
|
||||
|
||||
## 目标与基线
|
||||
|
||||
按照 `/tmp/fzg1_astra_guidance_20261012.md` 修复内部 Symbol Drawable 的 RenderTile 同源绑定、旧 Drawable 更新及 Bucket/Atlas 更换生命周期;保留原缓存、证书和 Full → Build → 成功 Full Reuse → Fast 顺序。
|
||||
|
||||
- 机器:Mac.lan / Darwin 24.6.0 / x86_64;仓库:`/Users/tei/dev/MapLibreParticleLab`。
|
||||
- 开工及交付分支:`particle/main`;HEAD:`badb515d590fbdf221751b6677de3da0a5650d7e`。未切换分支/worktree,未提交或操作远端。
|
||||
- 接续已有四个源码修改;起点另有受保护 STEP-110/111 两份未跟踪任务书。
|
||||
- 用户本轮明确指定模型 `GPT-6.1 Sol High`(`gpt-6.1-sol / high`)。当前工具未提供执行器模型元数据,且本机当日 sessions 目录不存在,无法独立核验实际运行参数;不宣称已从日志核实,没有主动切换模型或启动子代理。
|
||||
- 当前发行锁为 Android/RN `13.6.1-particle.25`、iOS `6.29.0-particle.25`,构建冻结源 `f32f6f45fd359007e8b3c77911b66d9e7fc93848`;本修复不改 Source Lock/发行物,不声称已进入 v25 制品。
|
||||
|
||||
## 必做项
|
||||
|
||||
- [x] 读取 Astra 指导、AGENTS、PARTICLE_BASELINE、规范、四份当前总则、当前 STEP-119 任务/实现/测试资料,核对现有四文件 diff。
|
||||
- [x] 同次 source prepare 获取 raw owner 和 Y 排序引用列表;仅比较地址完成同源校验,再复制当前 ID/atlas。
|
||||
- [x] 同 Bucket/Atlas 的完整 tile/pass 组先检查,再全部重新绑定当前 owner/RenderTile;不逐帧重建 Drawable/tweaker。
|
||||
- [x] Bucket/Atlas/组内成员失配或缺 atlas 时移除整组,走原创建路径;Text/IconRGBA/IconSDF/Halo 使用同一 atlas 与实际 tweaker 配对。
|
||||
- [x] Retained 使用内部强引用 atlas 和当前地址映射,不沿旧 Drawable 裸指针读取 atlas/Tile;缺证立即 complete=false。
|
||||
- [x] 保活 RenderTile → renderData → LayoutResult 的证据资源,不把每次 prepare 的 owner 地址写入比较字节。
|
||||
- [x] 编写并静态核对 13 项 SymbolRetainedLifetime GTest 源码,修正 atlas-only TileRenderData 查询默认 assert 的测试替身缺口。
|
||||
- [x] 中文 Step、完成报告及开发记录末尾追加;四源码路径 scoped diff 空白检查 exit0。
|
||||
- [ ] RDC 实际编译/链接并发现、运行生命周期与 STEP-119 定向 GTest。
|
||||
- [ ] Android OpenGL、Metal 实际路径验证与 Vulkan 编译兼容性证据。
|
||||
- [ ] 新源码锁/APK/SO 身份闭环、FZ-G1 动态生命周期、原24Gate、Full/Fast 视觉及释放验收。
|
||||
|
||||
## 修改范围与当前结果
|
||||
|
||||
生产修改限 `src/mln/gfx/symbol_drawable_data.hpp`、`src/mln/renderer/layers/render_symbol_layer.cpp`、`src/mln/renderer/layers/render_symbol_layer.hpp`;测试限 `test/renderer/retained_underlay_cache.test.cpp`。沿用此前修改,续作只补未知 Symbol 类型拒绝、测试替身空查询覆盖、Text/Icon/SDF/Halo 连续 prepare 稳定性与证书资源释放检查。
|
||||
|
||||
未改 RN/JNI 公共 API、原24Gate/证书判据、Placement、缓存开关、发行物、Integration、旧报告或两份保护任务书。未运行 CMake、编译/链接/语法编译、GTest、Gradle/APK、ADB/安装、Jest;没有新制品或设备数据。
|
||||
|
||||
## 阻塞与下一步
|
||||
|
||||
实现已落盘,可编译性与运行正确性仍待 RDC。现 verifier 会拒绝 tracked dirty,后续 Git 来源锁须在独立审计和另行授权后处理;本轮禁止 Git 收尾,不能绕过 verifier 或借旧 APK。历史 Metal/libuv 缺口未重新验证,不安装升级依赖。
|
||||
|
||||
RenderTile 容器不拥有底层 `Tile&`;强引用 atlas/texture 也不能证明原地像素或槽位不变,继续依赖既有 content epoch 和真实绑定复核。原始寄存器 tombstone 缺失,不能断言 `0x8cdc7c` 唯一根因为本缺陷;保留原 SO Build ID `485459ba44f806ef0a0c08633c717bfd5d82ad8f` 与匹配符号交 RDC。
|
||||
|
||||
准确命令、测试覆盖限制、停止门槛和交接证据见 [本轮完成报告](../报告/STEP-119_SymbolRetained生命周期修复_Codex完成报告_v1.md)。Codex 到此交接,RDC 独立审计后逐条验证;失败保留首错与现场,不放宽条件。
|
||||
43
docs/50_Step记录/STEP-120_iOS原始Filter无损Native接口_v1.md
Normal file
43
docs/50_Step记录/STEP-120_iOS原始Filter无损Native接口_v1.md
Normal file
@@ -0,0 +1,43 @@
|
||||
# STEP-120 iOS 原始 Filter 无损 Native 接口 v1
|
||||
|
||||
日期:2026-10-21(JST)。状态:**阻塞(BLOCKED)**。
|
||||
|
||||
## 目标与基线
|
||||
|
||||
为 NavSea JOINT-PBF-001 D4-13 提供通用 iOS Native 原始 Style Layer Filter 读取/设置接口,保留全部条件与 locale,由 NavSea 负责业务及单一 Host Context。
|
||||
|
||||
Mac.lan / macOS 15.7.7 / x86_64,原仓库 `/Users/tei/dev/MapLibreParticleLab`;`particle/main@9d6c9e9684e4c71cc2fb3f08bfb27e1638be4188`。原 worktree 唯一,子模块提交正常。预存未跟踪 STEP-120 任务书,tracked 与暂存区开工无差异。既有 iOS 发行版本 `6.29.0-particle.27`,其构建源 `970e3ad606e59873a32e9389e65ef257e1c259bb`,不包含本 Step 接口。
|
||||
|
||||
用户指定 `GPT-6.1 Sol High`;执行器实际配置未取得可独立核实元数据,没有主动切换模型或启动子代理。
|
||||
|
||||
## 范围与禁止事项
|
||||
|
||||
源码只允许 `platform/darwin/src/MLNVectorStyleLayer.h/.mm`。允许新增本 Step、递增完成报告及末尾追加开发记录。禁止修改辅助实现、Layer 生成器、C++ Core、Android、Particle、RenderCore、发行记录及跨项目代码;禁止分支/worktree 变更、stage/commit/push/tag、reset/clean、升级依赖、Bazel/Xcode/Gradle 构建或发布。
|
||||
|
||||
## 必做项
|
||||
|
||||
- [x] 完整读取仓库规则、现行总则、任务书、V27 README 与相关实际 Darwin/Core 源码。
|
||||
- [x] 重新检查主机、branch、HEAD、status、worktree、submodule;保留预存任务书。
|
||||
- [x] 静态确认现有 Core Filter 与 Foundation 转换接口及公开 Headers 构建链。
|
||||
- [x] 报告严格无损与 detach 条件的源码级阻塞,不自行扩大范围。
|
||||
- [x] 在完成报告提供复杂 all/第二属性/locale、一般等号、原为空三组条件及安全反例。
|
||||
- [ ] 实现无损 getter / setter 及公开声明。
|
||||
- [ ] 完成深度/输入/失效/detach/非 Vector 防护。
|
||||
- [ ] RDC 独立语法编译、iOS SDK 构建及样例实际验证。
|
||||
- [x] 新增中文报告并仅追加开发记录;落盘后静态检查结果记录在完成报告末尾。
|
||||
|
||||
## 当前结果与阻塞
|
||||
|
||||
未修改 SDK 源码,公共接口未实现,现有 `predicate` 与 ABI 保持原状。
|
||||
|
||||
1. 指定 `MLNJSONObjectFromMBGLValue()` 对字符串与键使用 `c_str()`,当前反向 Foundation 转换同样不传字符串长度;合法 JSON 的嵌入 NUL 会被截断,不能直接包装后承诺完整无损。修复既有辅助实现超出本轮源码范围;另写独立转换则需明确调整指定复用路径。整数转 double 的精度也需纳入后续门槛。
|
||||
2. 正常 `removeLayer:` 后 `_pendingLayer` 仍拥有有效 rawLayer;现有失效宏只检查 rawLayer 非空,没有权威 Style 成员关系查询。不能将其写成已拒绝 detach。已发出语义澄清,未答复不视为授权。
|
||||
3. 模型/推理参数只有用户指定值,无独立运行元数据证据;如需严格核实,由外部执行器补齐。
|
||||
|
||||
## 检查、输出与下一步
|
||||
|
||||
本轮仅做只读调查和静态自检,未执行任何编译、链接、codegen、单测、设备或发布操作。不存在 iOS 构建、运行或打包 PASS。
|
||||
|
||||
完成报告:`docs/报告/STEP-120_iOS原始Filter无损Native接口_Codex完成报告_v1.md`,含具体源码位置、三组样例、空 Filter 特殊处理、ABI 状态、准确 RDC Bazel 单项命令和独立证据清单。
|
||||
|
||||
先冻结 B1/B2 的契约与实现范围,再由 Codex 实现、RDC 独立验证;如通过,另起发行/NavSea Host 集成步骤。本 Step 不进入后续发布阶段,联调唯一业务主责仍 NavSea。
|
||||
43
docs/50_Step记录/STEP-120_iOS原始Filter无损Native接口_v2.md
Normal file
43
docs/50_Step记录/STEP-120_iOS原始Filter无损Native接口_v2.md
Normal file
@@ -0,0 +1,43 @@
|
||||
# STEP-120 iOS 原始 Filter 无损 Native 接口 v2
|
||||
|
||||
日期:2026-10-21(JST)。Step 状态:**进行中**;Codex 实现交付:**IMPLEMENTED_WAITING_RDC_IOS_BUILD**。
|
||||
|
||||
## 目标、基线与授权裁决
|
||||
|
||||
NavSea 是 JOINT-PBF-001 D4-13 唯一业务主责。本轮只实现通用 iOS Native Filter 的安全快照/修改能力,原始多源 Provider、唯一 Mapping、原始 Style、四档及 Pickup/Danger/Planning 总目标仍开放。
|
||||
|
||||
实际 Mac.lan / macOS 15.7.7 / x86_64;原仓 `/Users/tei/dev/MapLibreParticleLab`,`particle/main@9d6c9e9684e4c71cc2fb3f08bfb27e1638be4188`。原 worktree 唯一,子模块正常;开工已有 v1 开发记录追加及 v1/v2 未跟踪任务书、v1 Step/报告,完整保留。正式 iOS 仍为 `6.29.0-particle.27`,不含新接口。
|
||||
|
||||
用户指定外部执行器 `GPT-6.1 Sol High`,当前工具无独立模型/档位元数据,不宣称已核实;没有切模型或启动子代理。
|
||||
|
||||
v2 明确:仅支持可证明无损重放的子集,其余 E_FILTER_UNSUPPORTED;Style 成员关系由 Host 校验;Native 只检查有效 rawLayer,正常 pending/detach 可用;空值通过 NSNull 精确恢复 Filter()。
|
||||
|
||||
## 范围与禁止事项
|
||||
|
||||
源码仅 `platform/darwin/src/MLNVectorStyleLayer.h/.mm`;文档只新增本 v2 Step/报告、末尾追加开发记录。禁止改共享转换器、C++ Core、生成器、Build 配置、Android、Particle、业务与发行记录;禁止 Git stage/commit/push/tag、分支/worktree 变更、reset/clean、依赖升级或发布。Codex 不执行长构建、Native 单测、Jest 或设备操作。
|
||||
|
||||
## 必做项与当前结果
|
||||
|
||||
- [x] 重新读取规范、总则、路线、v1/v2 任务和 v1 报告;核对实际源码及 Git/主机基线。
|
||||
- [x] 在公共头声明 `rawStyleFilterJSONObjectWithError:` 与 `setRawStyleFilterJSONObject:error:`,并提供稳定 NSError 域和五个代码。
|
||||
- [x] 在指定 `.mm` 局部实现 Foundation 独立快照、C++ Value 归一/比较、真实桥接及 Core 反向解析;不经 NSPredicate/NSExpression。
|
||||
- [x] 预检 NUL、UTF-8、特殊/超范围数值及大小限制;深度 64、节点 4096、UTF-8 字符串/键合计 256 KiB。
|
||||
- [x] 明确支持的操作符候选、受限 legacy 域及结构变化拒绝;同时核对实际 Core 表达式,不依赖 Filter 指针相等。
|
||||
- [x] 先验证输入及原状态再写入;写后核对,异常/不符时恢复原 Filter 并独立核实,无法确认则要求 Host 废弃 Context。
|
||||
- [x] 文档明确主线程、有效 rawLayer 与 Host Style 成员关系的边界;没有另建 Context/generation/revision。
|
||||
- [x] 原 predicate/description 实现保持;不修改其他 Layer 属性或移除重建 Layer。
|
||||
- [x] 报告交付简单等值、两个属性范围、locale、NUL 拒绝、空 Filter 及 common 组合样例与源码依据。
|
||||
- [x] 新增中文 v2 报告并末尾追加开发记录;静态空白、范围和保护检查。
|
||||
- [ ] RDC 独立 ObjC++ 编译、iOS Metal SDK 构建与公开 Headers/调用链接验证。
|
||||
- [ ] RDC 实际运行样例、输入边界、回滚注入、线程/生命周期及 Host 成员关系联调。
|
||||
- [ ] 后续另行授权发行与 NavSea Host 集成。
|
||||
|
||||
## 检查、阻塞与下一步
|
||||
|
||||
只执行精确源码/文档/Git 静态检查,没有编译、codegen、测试或设备结果。两份 v1 文档、两份任务书、共享转换器及 V27 发行文件保持;开发记录保留开工前全部字节,仅追加。具体检查与保护证据见 v2 完成报告末尾。
|
||||
|
||||
v1 的完整 JSON 与 Native detach 契约阻塞已按用户裁决收敛;当前没有已知必须扩围的源码阻塞,但实现尚未编译,不能确认可构建。存在保守拒绝域、Core 初次序列化开销、回滚通知无法撤销与旧 SDK 不含新符号等限制;这些均已在 API 文档及报告说明。
|
||||
|
||||
下一步切换 RDC 独立审计,并在同工作区运行 `bazel build -c opt --//:renderer=metal //platform/ios:MapLibre.dynamic`;真实失败返回 Codex 修复。成功后另起发行/Host 集成,不在本轮自动推进。
|
||||
|
||||
完成报告:`docs/报告/STEP-120_iOS原始Filter无损Native接口_Codex完成报告_v2.md`。状态维持等待 RDC 验证,不宣布本 Step 或原业务总目标最终完成。
|
||||
128
docs/报告/STEP-119_SymbolRetained生命周期修复_Codex完成报告_v1.md
Normal file
128
docs/报告/STEP-119_SymbolRetained生命周期修复_Codex完成报告_v1.md
Normal file
@@ -0,0 +1,128 @@
|
||||
# STEP-119 SymbolRetained 生命周期修复 Codex 完成报告 v1
|
||||
|
||||
日期:2026-10-12(JST)。状态:**IMPLEMENTED / WAITING_RDC_VALIDATION**。接续此前因用量限制中断的同一任务,四个既有修改经静态复审及明确缺口修正后交付。没有运行编译或测试,整体 STEP-119 仍未验收,不报告整体 PASS。
|
||||
|
||||
## 1. 模型、环境与 Git 基线
|
||||
|
||||
- 用户本轮明确指定 `GPT-6.1 Sol High`,即 `gpt-6.1-sol / high`。当前工具没有执行器模型/推理参数元数据;轻量查找 `/Users/tei/.codex/sessions/2026/10/12` 返回目录不存在,实际运行参数无法独立核验。记录用户指定值,不把它写成已从执行器日志核实。未主动升级模型、启动第二实例或子代理。
|
||||
- `Mac.lan`,Darwin 24.6.0,x86_64;目录 `/Users/tei/dev/MapLibreParticleLab`。
|
||||
- 开工/交付 branch=`particle/main`,HEAD=`badb515d590fbdf221751b6677de3da0a5650d7e`。基线为四个已有 tracked 修改及 STEP-110/111 两份 untracked 任务书;不从头复写,不改分支/worktree/HEAD。
|
||||
- `git submodule status` 已只读检查,没有变更前缀;无子模块修改。未 commit/push/tag/fetch/pull/rebase/reset/clean,未回读远端 HEAD(本轮禁止远端操作)。
|
||||
- 只读当前 v25 Source Lock:Android/RN `13.6.1-particle.25`,iOS `6.29.0-particle.25`,冻结构建源 `f32f6f45fd359007e8b3c77911b66d9e7fc93848`。发行记录与制品未修改,本轮未生成制品,不能以现有 v25 包验证本工作区修复。
|
||||
- 首先读取 Astra 原文 `/tmp/fzg1_astra_guidance_20261012.md`,再读取仓库规则、当前资料和现有 diff。当前用户续作授权优先于旧任务中的历史基线/跨项目调查和 Git 收尾安排。
|
||||
|
||||
## 2. 实际修改文件及生命周期不变量
|
||||
|
||||
| 文件 | 实际变更与不变量 |
|
||||
|---|---|
|
||||
| `src/mln/gfx/symbol_drawable_data.hpp` | 内部数据保存强类型 atlas 与实际原生 atlas tweaker;创建时两者来自同一份 atlas,增加内部 Drawable 类型标记。它们不拥有 RenderTile 或底层 Tile。 |
|
||||
| `src/mln/renderer/layers/render_symbol_layer.hpp` | 私有同源/绑定/整组复用/采集辅助方法及当前地址→ID/atlas 映射;仅保存当前代次,不建立历史容器列表。friend 测试入口不修改公共 API。 |
|
||||
| `src/mln/renderer/layers/render_symbol_layer.cpp` | prepare 从同次 source 获取 raw owner 与原 Y 排序列表,先校验成员地址再读 ID/atlas,保留 fading tile。成功复用先检查 tile/pass 整组,再全部刷新当前 RenderTile/owner;Bucket、atlas、layer tweaker、数据/类型或成员失配整组移除重建,atlas 缺失移除旧绘制。Retained 从强引用 atlas 取证,旧指针只作当前地址映射查找,使用 Drawable 自己的 ID;类型未知/绑定不符/未上传立即拒绝。owner 只放入 resources,不序列化 owner 地址。 |
|
||||
| `test/renderer/retained_underlay_cache.test.cpp` | 13 项 `SymbolRetainedLifetime` 源码调用生产 prepare/bind/rebind/retained 辅助路径与原生 atlas tweaker;真实类接口/签名和友元访问逐项静态核对。atlas-only TileRenderData 显式覆盖空 Bucket/LayerRenderData 查询,避免基类 assert;补组内类型失配、Text/IconRGBA/IconSDF/Halo 稳定身份、未知类型及证书释放检查。 |
|
||||
| `docs/50_Step记录/STEP-119_SymbolRetained生命周期修复_v1.md`(新增) | 本阶段目标、基线、完成/待验清单及交接。 |
|
||||
| `docs/报告/STEP-119_SymbolRetained生命周期修复_Codex完成报告_v1.md`(新增) | 本报告,记录事实、准确命令、风险和停止条件。 |
|
||||
| `粒子开发记录.md` | 仅末尾追加本次实现与未验证事实,保留历史内容。 |
|
||||
|
||||
两个新文档路径创建前已确认不存在,同主题无旧版本,选择 v1;使用独占创建,未覆盖旧报告。前三个生产文件及测试均沿用开工时修改;本续作生产补丁仅增加 Retained 对未知 SymbolType 的拒绝,测试补丁只处理上述明确缺口及覆盖不足。
|
||||
|
||||
### 所有权与稳定性
|
||||
|
||||
1. Drawable 只由通过同源校验的 owner/元素绑定。复用检查不从旧 RenderTile 读取 ID/Bucket/atlas;任何一个 Text/Icon/Halo 成员不兼容时整组移除,避免 `updateTile()` 的“任一成功”留下半新半旧组。
|
||||
2. 同 Bucket/atlas 保持 Drawable 和 tweaker 身份,仅刷新 owner/RenderTile 与既有动态属性;source 每次产生新容器不应自行造成每帧重建或证书字节变化。
|
||||
3. 当前容器拥有 RenderTile;RenderTile 独占 renderData;真实 GeometryTileRenderData 强持有 LayoutResult/atlas,LayoutResult 负责槽位释放。证据 resources 保活容器,但不调用 `result.retain(renderTilesOwner)`,因此 owner 地址不进入比较字节。证书 candidate/completed 替换或 invalidate 后旧证据可退出,未新增无界容器。
|
||||
4. Retained 先验证当前地址/保存的 ID、内部数据类型、atlas 来源、实际 tweaker 和 shader,再检查真实 glyph/icon 纹理绑定及上传状态。任何缺证立即返回 incomplete,不沿失效裸指针追读 renderData/atlas。
|
||||
5. RenderTile 内 `Tile&` 的寿命独立;保活容器不保活 Tile,不允许把旧 Tile 上的 getOverscaledTileID/holdForFade 当成安全操作。测试分别计数 Tile 与 TileRenderData,旧 Tile 销毁后仅比较旧地址及读取 atlas 强引用。
|
||||
6. 同 atlas 的 texture 替换经原生 tweaker 更新实际绑定,前后证据不同;原地像素更新即使上传后指针不变,仍依赖真实 content epoch。既有 Build 前/后及提交后复核、证书版本/R cache/prepared Fast 保护保留,未改变准入顺序或放宽任何判据。
|
||||
|
||||
## 3. 静态审查与测试源码范围
|
||||
|
||||
13 项生命周期源码覆盖:连续 prepare 后刷新/释放旧 renderData、owner/元素错配、atlas 替换整组清理与新纹理绑定、组内单成员失配、atlas 缺失、同 atlas texture 更换及重新 Build/Reuse 状态、未上传原地更新拒绝、owner 身份不进入完整空组结果字节、Tile 与 renderData 寿命分离、Layer 移除及证书失效释放 atlas、同名 Source 替换、tile 退出清理、Text/Icon/SDF/Halo 连续三代保持身份、未知 Symbol 类型拒绝(其中相关情形合并在同一用例)。
|
||||
|
||||
测试使用真实 RenderSource/Tile/TileRenderData/RenderTile、SymbolBucket、Drawable/TileLayerGroup 接口和生产辅助方法;GPU Texture/Drawable/Shader 使用 CPU 替身。真实 API 的纯虚函数、构造参数、Mutable→Immutable 转换、SymbolLayer.baseImpl 可见性及派生类 friend 访问继承 protected 成员已静态核对,没有用悬空指针解引用构造普通反例。
|
||||
|
||||
**覆盖限制**:atlas-only fixture 不生成真实 Placement/顶点/GPU 命令;atlasResult 是生产采集的局部证据,不能代表完整渲染证书。整组失配测试走生产移除入口,但重新创建由 fixture 添加 Drawable,仍需 RDC 实际 update/builder/GPU 链验证。tile 退出测试直接调用正常 update 使用的清理入口,没有伪造 Context 或声称执行完整 update。owner 稳定证书测试走完整生产空组采集;非空 Text/Icon/Halo 稳定性只检查绑定输入和身份。原地 image patch 上传后的 epoch、Style/context 清理引用既有 `Step119SymbolResults.RealTileGlyphImageStyleContextCallbacksRejectBuiltCertificate` 等回调测试源码,不等同真实槽位释放、Source/Style/context 完整运行验收。
|
||||
|
||||
## 4. 已做及未做检查
|
||||
|
||||
已做:必要文件阅读、开工 Git/机器/版本及 diff 审查;真实 C++ 接口/生命周期调用静态核对;确认测试文件在 `test/CMakeLists.txt` 已登记;源码静态计数 `SymbolRetainedLifetime` 为13项;新增文档版本/路径检查;STEP-110/111 SHA-256 保护检查;开发记录追加与文件范围检查。
|
||||
|
||||
已执行四源码路径 scoped `git diff --check -- src/mln/gfx/symbol_drawable_data.hpp src/mln/renderer/layers/render_symbol_layer.cpp src/mln/renderer/layers/render_symbol_layer.hpp test/renderer/retained_underlay_cache.test.cpp`:**exit=0,stdout/stderr 空**。最终全工作区及七任务路径空白检查由本轮落盘后再次执行,结果见下方交付记录。空白检查不证明 C++ 编译或测试通过。
|
||||
|
||||
未执行:CMake 配置、Native/NDK 编译、`-fsyntax-only`、链接、GTest、Metal/iOS/GPU运行、Gradle/APK、ADB/安装、Jest、Bazel/codegen、发行、Git写操作及远端读取。没有新编译失败日志、设备日志、截图、APK/SO SHA 或 Build/Reuse/Fast 数据;既有 libuv 环境失败只作为历史限制,未重试也未修改环境。
|
||||
|
||||
保护的未跟踪任务书 SHA-256:
|
||||
|
||||
- STEP-110:`33037df71b4b30aa7956d59f4b99f77af3085405b24111bc0fe11ec857a93db0`。
|
||||
- STEP-111:`f3a5806f25384bf46055db2edfea0d21bfbc4124184bc813b46e38b8079c1ec4`。
|
||||
|
||||
## 5. RDC 准确编译与定向测试命令(Codex 未执行)
|
||||
|
||||
独立审查本报告和实际 diff 后,在同一工作区逐条执行;每条保存 command/exit/耗时/完整日志路径,失败立即停止下一步,回传首个真实 error、target 与必要上下文。PATH 找不到 cmake 时可使用既有 `/usr/local/opt/cmake/bin/cmake`;不得安装升级依赖或关闭宏绕过 libuv/工具链错误。
|
||||
|
||||
```bash
|
||||
cd /Users/tei/dev/MapLibreParticleLab
|
||||
cmake --preset macos-metal
|
||||
cmake --build build-macos-metal --target mbgl-test-runner --parallel 3
|
||||
build-macos-metal/mbgl-test-runner --gtest_filter='SymbolRetainedLifetime.*:Step119SymbolResults.*' --gtest_list_tests
|
||||
build-macos-metal/mbgl-test-runner --gtest_filter='SymbolRetainedLifetime.*:Step119SymbolResults.*:RetainedUnderlayCache.*:RenderOrchestratorError.*:RenderOrchestratorTeardown.*:CrossTileSymbolLayerIndex.*' --gtest_color=no
|
||||
```
|
||||
|
||||
必须发现本轮13项 `SymbolRetainedLifetime` 与既有13项 `Step119SymbolResults`(后者分布两个已登记文件),真实执行数量非零。零匹配、旧 runner、仅语法检查不能写 GTest PASS。Metal 与 Android OpenGL 分别保留编译及运行事实;Android 实际 compile_commands 的类型核对须保持原 `-Werror`,不加 `-Wno-error`。本轮未生成额外编译脚本。
|
||||
|
||||
Vulkan 至少取得生产修改的兼容性编译证据;已有 macOS Vulkan 环境可用以下真实 preset/target,环境缺失则记未验证/阻塞,不升级环境:
|
||||
|
||||
```bash
|
||||
cmake --preset macos-vulkan
|
||||
cmake --build build-macos-vulkan --target mbgl-core --parallel 3
|
||||
```
|
||||
|
||||
Vulkan Symbol provenance 既有保守边界保持,编译成功不等于其 Symbol retained 运行通过。可在既有 sanitizer 配置下补查 UAF,不改造构建体系;Host Metal/GTest 也不替代 iOS 真机。
|
||||
|
||||
## 6. 后续 FZ-G1 验收命令与证据(Codex 未执行)
|
||||
|
||||
**前置门槛**:独立审计/Native验证完成、后续 Git 来源锁和 Android/设备操作获得授权。当前 tracked dirty,verifier 的 `SOURCE_NOT_LOCKED` 会拒绝本工作区;本轮禁止提交,故这是后续验收前置条件,不能改 verifier、伪造 build-info 或用旧 APK。本报告仅交命令,不代执行 Android/ADB,也不代授权 Git。
|
||||
|
||||
用户/RDC 按其阶段授权完成一次标准新 APK 构建与安装,固定 FZG1-2、serial=`192.168.200.139:5555`、OpenGL Debug/x86_64:
|
||||
|
||||
```bash
|
||||
cd /Users/tei/dev/MapLibreParticleLab
|
||||
./tools/particle_android_validation/build_validation_apk.sh
|
||||
adb -s 192.168.200.139:5555 install -r artifacts/maplibre-particle/android-validation/MapLibreAndroidTestApp-validation-opengl-debug-x86_64.apk
|
||||
python3 -B tools/particle_android_validation/verify_step113_fast_path.py --serial 192.168.200.139:5555 --preflight-only
|
||||
python3 -B tools/particle_android_validation/verify_step113_fast_path.py --serial 192.168.200.139:5555 --scenario STATIC_RC_NO_TIDE --seconds 90
|
||||
python3 -B tools/particle_android_validation/verify_step113_fast_path.py --serial 192.168.200.139:5555 --scenario RCRR_C_NARROW_NO_TIDE --seconds 90
|
||||
python3 -B tools/particle_android_validation/verify_step113_fast_path.py --serial 192.168.200.139:5555 --scenario STATIC_RC --seconds 90
|
||||
```
|
||||
|
||||
正向缓存保持开启,每次稳定后须真实 Build>0、Reuse>0、FastDelta>0,完整120帧24Gate mismatch=0;原63层 STATIC_RC 缺 tide 继续作为安全拒绝负对照,验证器可能返回 NO_FAST_OBSERVED/exit2,不能当正向 PASS,也不能添加假 tide。正向静止90秒不是生命周期验收。
|
||||
|
||||
RDC/用户还需反复 pan/zoom/rotate、跨 tile/world wrap、Source同名替换、Style清理/重载、glyph/icon/atlas更新、后台恢复/context reset;每次变化保存失效 → Full → 新 Build → 成功 Full Reuse → Fast 的顺序证据,Text/Icon/SDF/Halo 不消失、残影或错位。相同相机的 Full/Fast 图像对照、资源随循环最终释放均须实测。保持正式 Style/Source 和公共契约,不以禁用 Retained、每帧重建或放宽24Gate替代验证。
|
||||
|
||||
必须保存:新锁定源码 HEAD/diff、build-info/完整构建日志、本地及安装 APK SHA-256、实际 libmaplibre.so Build ID与匹配符号、测试发现/执行数、Build/Reuse/Full/Fast/Invalidate 区间增量、完整24Gate快照、操作前后截图及视觉判断、资源数量/释放证据和全部未验证项。证据写新目录,不覆盖 ignored 历史证据。
|
||||
|
||||
异常先保存 crash 日志/bugreport,不先清日志;由获授权执行者执行:
|
||||
|
||||
```bash
|
||||
STEP119_EVIDENCE_DIR=$(mktemp -d /tmp/step119-symbol-lifetime-XXXXXX)
|
||||
adb -s 192.168.200.139:5555 logcat -b crash -d > "$STEP119_EVIDENCE_DIR/crash-logcat.txt"
|
||||
adb -s 192.168.200.139:5555 bugreport "$STEP119_EVIDENCE_DIR/bugreport.zip"
|
||||
```
|
||||
|
||||
## 7. 风险、证据缺口与停止门槛
|
||||
|
||||
- 缺原始寄存器 tombstone;原 Build ID `485459ba44f806ef0a0c08633c717bfd5d82ad8f` 和匹配符号应保留,不能断言 `0x8cdc7c` 唯一原因是本 UAF 链。
|
||||
- 当前模型运行参数没有可独立回读的执行器元数据,RDC报告须补该身份事实;不伪造“已核实”。
|
||||
- 真实 C++ 编译、GTest、Metal/OpenGL/Vulkan、底层 Tile 寿命及真实 atlas 槽位/像素变化、异步线程边界、释放/内存开销均未验收。静态检查只能排除部分明显问题。
|
||||
- 若必须改变公共契约、重构全部 Tile 所有权、永久禁用缓存、放宽测试/证书或修改发行物才能继续,停止扩大修改并标 BLOCKED。
|
||||
- 编译/链接/GTest失败、SIGSEGV、24Gate mismatch、Fast永久归零、资源循环单调增长、Symbol视觉异常均保留现场,不能通过降门槛、旧APK或隐藏失败制造 PASS。
|
||||
|
||||
本轮实现交接到此,后续由 RDC 独立验证;失败按 Codex 修复 → RDC 验证闭环返回,不继续后续开发阶段。
|
||||
|
||||
## 8. 最终交付静态检查记录
|
||||
|
||||
- 完整命令 `git diff --check`:exit=0,stdout/stderr 空。
|
||||
- 七个任务文件显式路径的 `git diff --check -- ...`:exit=0,stdout/stderr 空。新建 Step/报告尚未跟踪,Git diff 不包含其内容;另以轻量 Python 文件检查确认两份 UTF-8 文档状态、末尾换行及无行尾空白,不借此声称 Git 已检查 untracked 内容。
|
||||
- `git status --short`:仅四个接续源码、开发记录、两份新增本任务文档及两份原有保护任务书;没有其它修改。branch/HEAD 与开工一致。
|
||||
- 最终 STEP-110/111 SHA-256 与开工值一致;开发记录内容以 HEAD 原文的完整字节为前缀,只有末尾追加。
|
||||
- 静态计数:生命周期13项,既有 Step119SymbolResults 为9+4=13项。没有发现/执行 GTest,不能声称实际26项通过。
|
||||
- 没有新的编译/测试/设备验收结论;状态维持 IMPLEMENTED / WAITING_RDC_VALIDATION。
|
||||
147
docs/报告/STEP-120_iOS原始Filter无损Native接口_Codex完成报告_v1.md
Normal file
147
docs/报告/STEP-120_iOS原始Filter无损Native接口_Codex完成报告_v1.md
Normal file
@@ -0,0 +1,147 @@
|
||||
# STEP-120 iOS 原始 Filter 无损 Native 接口 Codex 完成报告 v1
|
||||
|
||||
日期:2026-10-21(JST)。结论:**BLOCKED(未实现公共接口)**。
|
||||
|
||||
本轮完成前置阅读、实际源码调查与交接条件整理。当前“仅改两个文件、直接复用指定转换器、所有原始 Filter 无损、Native 拒绝 detach Layer”四项要求存在契约阻塞;没有修改 SDK,也没有将调查或空白检查写成接口实现、编译或运行 PASS。NavSea 仍为联调唯一业务主责,D4-13 未解除阻塞。
|
||||
|
||||
## 1. 环境、模型与 Git 基线
|
||||
|
||||
- 实际主机:`Mac.lan`,Darwin `24.6.0`,macOS `15.7.7 (24G720)`,`x86_64`。
|
||||
- 仓库:`/Users/tei/dev/MapLibreParticleLab`;分支:`particle/main`;开工 HEAD:`9d6c9e9684e4c71cc2fb3f08bfb27e1638be4188`。
|
||||
- `git worktree list --porcelain` 仅列出原仓库;`git submodule status` 全部为正常空格前缀,没有未初始化、偏离提交或冲突标记。本轮没有修改子模块。
|
||||
- 开工 tracked 工作区和暂存区无差异,但不是完全干净:预先存在未跟踪任务书 `docs/40_任务/STEP-120_iOS原始Filter无损Native接口_Codex任务_v1.md`。保留该文件,未 stage。
|
||||
- 用户指定外部 CLI 配置为 `GPT-6.1 Sol High`(`gpt-6.1-sol / high`)。当前工具未提供执行器模型/推理参数元数据,实际配置无法独立核验;不把用户指定值写成已核实的运行身份。没有切换模型、启动其他 Codex 实例或子代理。
|
||||
- 只读发行文档:Android/RN `13.6.1-particle.27`;iOS Metal/SwiftPM `6.29.0-particle.27`;发行构建源码 `970e3ad606e59873a32e9389e65ef257e1c259bb`;正式 Tag `maplibre-particle/rn/13.6.1-particle.27`。这些是既有发行记录,不包含本 Step 的新接口。
|
||||
- 已完整读取 `AGENTS.md`、`PARTICLE_BASELINE.md`、`docs/00_项目规范/` 全部文档、项目总则 README 指定的四份现行总则、当前任务书、V27 README,并回读相关 Darwin/C++ 实现与上一 Step 生命周期修复报告。
|
||||
- 历史 Linux 路径、Ubuntu 环境及旧版本坐标与当前 Mac/V27 事实不同;遵循当前任务,不修改总则、不创建分支。当前任务指定的报告目录与 Git 禁止项优先于历史流程。
|
||||
|
||||
## 2. 阻塞证据与边界
|
||||
|
||||
### B1:指定复用的 JSON 桥接并非对所有合法 JSON 字符串无损
|
||||
|
||||
实际源码:
|
||||
|
||||
- `platform/darwin/src/MLNStyleValue.mm:16`:字符串使用 `@(value.c_str())`;第 34 行字典键同样使用 `c_str()`。两处都采用以 NUL 结尾的字符串语义。
|
||||
- `platform/darwin/src/MLNConversion.h:105` 起:`toString()` 使用 `std::string([value UTF8String])`,没有传入 UTF-8 字节长度;字典成员枚举的键也使用无长度构造。
|
||||
- `src/mln/style/rapidjson_conversion.hpp` 的字符串及键转换使用 `GetStringLength()`,因此从 Style JSON 进入 Core 的字符串可以保留嵌入 NUL。
|
||||
|
||||
可复核的源码级反例:合法 JSON Filter `["==","name","a\u0000b"]` 可在 Core 保留三字节字符串。`Filter::serialize()` 保留其值,但指定 getter 桥接会将该字符串变成 `@"a"`;setter 直接使用当前 Foundation 转换器也会截断同类输入。这是源码推导,**没有执行 iOS 实验**。
|
||||
|
||||
只增加两个直接包装方法会公开有损接口。预检后拒绝嵌入 NUL 可以避免静默损坏,却仍无法提供任务要求的所有未修改原始 Filter 的精确快照,属于缩小契约,不能自行采用。
|
||||
|
||||
若继续坚持通用无损约束,需确认以下实现路径之一:授权修复既有辅助文件 `MLNStyleValue.mm`、`MLNConversion.h`;或明确允许在新接口内实现独立的长度感知转换,调整“直接使用真实既有辅助函数”的要求。前者超出当前两个源码文件范围,并需要验证共享转换器对旧接口的影响;本轮两者均未实施。
|
||||
|
||||
此外,Foundation 转换器的数值 `toValue()` 使用 `doubleValue`,而 Core 的 legacy Filter 序列化可保留 `uint64_t/int64_t`。例如 legacy 数值 `9007199254740993` 存在 setter 精度丢失风险。后续必须验证并明确精确支持或失败拒绝,不能将 Foundation 输出持有整数等同于双向无损。
|
||||
|
||||
### B2:rawLayer 存活不等于仍属于当前 Style
|
||||
|
||||
- `platform/darwin/src/MLNStyleLayer.mm:15` 起仅保存私有 `_pendingLayer` 与 `_weakLayer`;`rawLayer` 返回 `_weakLayer.get()`。
|
||||
- 同文件 `removeFromStyle:` 在成功移除时把 Core Layer 所有权转回 `_pendingLayer`;正常移除后 `rawLayer` 仍有效,可重新加入 Style。
|
||||
- `MLNStyleLayer_Private.h` 的 `MLNAssertStyleLayerIsValid()` 只拒绝空 `rawLayer`,未提供 Style 成员关系检查。公共及私有现有 Layer 接口也没有 owning Style / attached 查询。
|
||||
|
||||
因此,这个宏可以处理因销毁而失效的 Layer,却不能满足“Native 拒绝所有正常 detach Layer”的严格解释。仅在两个文件内添加新的生命周期标记不能替代权威 Style 查询;本轮不建立另一套 Host Context、generation、revision 或生命周期跟踪状态。
|
||||
|
||||
已通过异步澄清说明两种语义:沿用 SDK 有效 Layer 语义并由 Host 校验成员关系;或坚持 Native 拒绝 detach、在当前范围下报 BLOCKED。落盘时尚无答复,不把未答复当作放宽安全条件的授权。即使选择前者,B1 仍需解决。
|
||||
|
||||
### 已确认可以复用的事实
|
||||
|
||||
- `mln::style::Layer` 已有 `getFilter()` / `setFilter()`;`mln::style::Filter` 已有 `serialize()`。
|
||||
- `MLNStyleValue_Private.h` 声明真实 `MLNJSONObjectFromMBGLValue()`;`MLNConversion.h` 提供 `makeConvertible(id)`;`mln/style/conversion/filter.hpp` 提供 `Converter<Filter>`。现有 `NSPredicate+MLNAdditions.mm` 是直接转换调用的实际参考。
|
||||
- `Filter{}` 序列化为 `NullValue`,既有 Foundation 桥接将其变成 `NSNull`。但 Core 当前转换 `null` 的 legacy 分支生成恒真表达式,不能直接把这一步当作原空 Filter 的精确恢复;新 setter 应对空值明确设置 `Filter{}`。
|
||||
- `platform/darwin/bazel/files.bzl` 已将 `MLNVectorStyleLayer.h` 列入 `MLN_DARWIN_OBJC_HEADERS`,`.mm` 已登记源码;`platform/darwin/BUILD.bazel` 的 `darwin_objc_hdrs` 被 `platform/ios/BUILD.bazel` 的 `public_hdrs` 引用并传入 `MapLibre.dynamic`。无需为公开声明修改生成器或构建配置。
|
||||
|
||||
## 3. 实际修改与 ABI 状态
|
||||
|
||||
仅有以下项目文档变更:
|
||||
|
||||
| 文件 | 处理 |
|
||||
| --- | --- |
|
||||
| `docs/报告/STEP-120_iOS原始Filter无损Native接口_Codex完成报告_v1.md` | 新增本报告;创建前不存在同题版本。 |
|
||||
| `docs/50_Step记录/STEP-120_iOS原始Filter无损Native接口_v1.md` | 新增阻塞状态与交接清单;创建前不存在同题版本。 |
|
||||
| `粒子开发记录.md` | 仅末尾追加本 Step 事实。 |
|
||||
|
||||
`MLNVectorStyleLayer.h/.mm`、全部辅助源码、C++ Core、具体 Layer 生成器、Android、Particle、RenderCore、发行文档及 SOURCE_LOCK 均未修改。没有新增 ObjC selector、导出符号、实例变量或 C++ ABI 变更;当前 SDK **没有** `rawStyleFilterJSONObject` / `setRawStyleFilterJSONObject:error:`。不应让 Host 直接调用尚不存在的方法。
|
||||
|
||||
阻塞解除后的接口草案仍为以下两个 selector,**不是本轮已交付 API**:
|
||||
|
||||
```objc
|
||||
- (nullable id)rawStyleFilterJSONObject;
|
||||
- (BOOL)setRawStyleFilterJSONObject:(nullable id)json
|
||||
error:(NSError * _Nullable * _Nullable)error;
|
||||
```
|
||||
|
||||
新接口应说明线程与现有 Style 修改入口一致;getter 从当前 Core Filter 取值,不经 NSPredicate 或初始化 styleJSON;setter 先检查 JSON 类型、有限数值、深度及循环容器,再解析临时 Filter,只有成功后才能调用 `setFilter()`;失败不修改原层。`nil` / `NSNull` 的清空语义及错误域/异常应在实现时冻结。不得更改原 `predicate` 的签名或旧行为,也不得通过移除重建 Layer 实现恢复。
|
||||
|
||||
## 4. 至少三组安全样例与 RDC Test Gate
|
||||
|
||||
以下是交接的源码级断言条件,**均未编译、未运行**,没有新增或修改测试文件。可在授权后使用现有 Darwin `MLNStyleLayerTests` / `MLNPredicateTests` 框架组织测试;当前任务禁止扩大测试文件范围,本轮将样例保存在报告中。
|
||||
|
||||
### 样例 A:两个不同属性的 all,加 locale
|
||||
|
||||
```json
|
||||
["all",
|
||||
["<=", ["string", ["get", "name"]], "m",
|
||||
["collator", {"case-sensitive": false, "diacritic-sensitive": true, "locale": "de"}]],
|
||||
[">=", ["string", ["get", "category"]], "a",
|
||||
["collator", {"case-sensitive": false, "diacritic-sensitive": true, "locale": "de"}]]]
|
||||
```
|
||||
|
||||
从当前运行时 Layer 读取 `before`,确认含第二个属性 `category`、两个条件及两个 `locale=de`。设置独立覆盖 Filter 后恢复 `before`,断言读取对象与 `before` 结构相等,Core `serialize()` 的对应树一致。不要只断言 NSPredicate 相等。另以数值 `all` 的 `minDepth <= 10` 与 `maxDepth >= 2` 验证不同属性不会折叠成同一属性的 BETWEEN。
|
||||
|
||||
### 样例 B:一般等号
|
||||
|
||||
```json
|
||||
["==", "kind", "buoy"]
|
||||
```
|
||||
|
||||
对 legacy 形式及表达式形式 `["==",["get","kind"],"buoy"]` 分别执行读取、覆盖、恢复;以当前 Core 序列化结果为比较基准。外部通过其他有效 SDK 入口修改 Filter 后,新 getter 必须反映当前状态,不能回读初始化 JSON。全过程记录 Layer 身份、ID、Source、Paint/Layout 与层序,确认仅 Filter 变化。
|
||||
|
||||
### 样例 C:原为空
|
||||
|
||||
构造当前 Core `Filter{}`,getter 应返回 `NSNull`;覆盖后以该快照恢复,断言 Core `bool(getFilter()) == false`,`serialize()` 仍为 NullValue,而非恒真表达式。若接口最终允许 `nil`,同样验证其清空行为。`NSNull` 也应在合法嵌套 JSON 位置保留,不得吞掉数组成员。
|
||||
|
||||
### 必需反例与扩展条件
|
||||
|
||||
| 条件 | 必须观测的结果 |
|
||||
| --- | --- |
|
||||
| 当前 Filter 含 `a\u0000b`;对象键含嵌入 NUL | 阻塞解除后应精确保留字符及键长度;不能静默变成 `a`。 |
|
||||
| legacy 整数 `9007199254740993` | 精确往返,或在变更前明确拒绝不支持的输入;不能静默取整后返回成功。 |
|
||||
| 超过冻结深度上限、循环数组/字典、非字符串键、NSDate/NSData、NaN/Infinity、Core 解析错误 | 明确失败,设置前后 Core Filter 和其余 Layer 状态完全一致。深度上限须在实现时明确,当前未冻结数值。 |
|
||||
| 销毁 Style 后 rawLayer 失效、正常移除仍存活、同名 Layer 替换 | 分别按最终冻结的失效/成员关系契约拒绝;不得用旧指针或只比较 Layer ID 伪造有效性。 |
|
||||
| Raster/Background Layer 或错误的内部 raw 类型 | 不得走 Vector Filter 设置;明确拒绝/异常。 |
|
||||
| Core 序列化得到布尔常量 | 验证 getter 快照能否被 setter 精确恢复;现有 Core 顶层 primitive 转换行为需要专门处理,不能假定直接转换总是对称。 |
|
||||
|
||||
## 5. 已执行与未执行检查
|
||||
|
||||
已执行:仓库规范和任务阅读、实际主机检查、Git branch/HEAD/status/worktree/submodule 只读检查、实际调用签名与公开 Headers 构建链静态审查、三个必需样例的源码推导、保护文件 SHA-256 记录、创建前版本检查。写文档前 `git diff --check` 返回 exit=0;文档落盘后的检查结果见末尾交付记录。
|
||||
|
||||
没有执行:ObjC/C++ `-fsyntax-only`、Bazel/Xcode/CMake/Ninja/Gradle 编译、链接、codegen、XCTest/GTest/Jest、打包、安装、ADB、模拟器/真机运行、网络发行核验。没有编译/测试失败日志,因为没有启动这些命令。若干只读探查最初使用旧 `mbgl` 路径或不存在的候选辅助头,随后按真实 `mln` 路径核对;这不是构建结果。
|
||||
|
||||
没有执行任何 stage/commit/push/tag、新建/切换 branch/worktree、reset/clean、依赖升级或发布。没有新增制品,因此没有本 Step 的 XCFramework/APK/AAR 大小、SHA-256 或设备验收证据。没有远端写入,也未查询远端 HEAD。
|
||||
|
||||
保护的预存任务书 SHA-256:`8231df05be824e414e186471e7ca37139545dcb1390658ae2a2734fb2182c547`。保护文件的落盘前哈希保存在本机会话临时记录 `/tmp/step120_readonly_baseline_hashes.json`,该文件不是仓库交付物;最终检查还应直接与 Git HEAD 对比,以免依赖临时文件存续。
|
||||
|
||||
## 6. 独立验收命令与后续证据
|
||||
|
||||
先解决 B1/B2、重新明确允许范围,再实现接口并交 RDC 独立审计。当前阻塞阶段不应仅运行旧 SDK 构建后宣称新接口通过。以下是从当前正式 Pipeline 读取的准确 **iOS 单项构建命令,Codex 未执行**:
|
||||
|
||||
```bash
|
||||
cd /Users/tei/dev/MapLibreParticleLab
|
||||
bazel build -c opt --//:renderer=metal //platform/ios:MapLibre.dynamic
|
||||
```
|
||||
|
||||
不要为此调用 `release.sh ... build`:该脚本还会执行 Android 构建、Maven Local 发布等本任务未授权步骤。不得以现有 V27 产物替代新实现的编译证据。
|
||||
|
||||
RDC 构建后应独立读取当次生成的 `bazel-out/darwin_x86_64-opt/bin/platform/ios/MapLibre.dynamic.xcframework.zip`,确认 `ios-arm64/MapLibre.framework/Headers/MLNVectorStyleLayer.h` 中两个公开 selector 均存在。后续提供实际 ObjC 调用编译/链接证据及本报告各样例的 XCTest/运行结果;单纯 ZIP 内出现头文件不足以证明实现可调用。
|
||||
|
||||
独立验收需保留:执行器实际模型/档位证据;实现后的 HEAD 与精确 diff;完整命令、exit code、耗时、日志路径;当次 XCFramework SHA-256;导出头文件及可调用性;样例的 Core 序列化前后树、Foundation 值/类型与字符串长度;失败输入的原层未变证据;失效/detach/同名替换行为;Paint/Layout/Source/Z-order 未变证据。新发行与 NavSea Host 集成另起授权步骤,遵守 GSRL 与跨项目边界。
|
||||
|
||||
当前未完成:公共 API 实现、输入防护、源码语法编译、iOS SDK 精确构建、实际无损与生命周期验证。当前 ABI 保持原状不等于新 API ABI 验证通过。结论维持 **BLOCKED**,不标记 iOS 可打包、发版、真机 PASS,也不代表原始多源 Provider 项目完成。
|
||||
|
||||
## 7. 落盘后的静态交付记录
|
||||
|
||||
- `git diff --check`:exit=0,无输出;`git diff --cached --exit-code`:exit=0,暂存区未变。Git diff 不包含未跟踪文档内容,另以 Python 检查两份新文档和开发记录的 UTF-8、末尾换行及行尾空白,全部通过。
|
||||
- 当前 tracked 差异只有开发记录,增加 7 行、1513 字节;逐字节确认以 Git HEAD 中的完整旧开发记录为前缀,属于末尾追加。
|
||||
- 两个指定 SDK 文件、`MLNStyleValue.mm`、`MLNConversion.h` 与 Git HEAD 字节完全一致;这些源码、V27 README/SOURCE_LOCK 及预存 STEP-120 任务书的 SHA-256 均与落盘前记录一致。
|
||||
- `git status --short` 仅列开发记录、两份本轮新增文档及原有未跟踪任务书;没有其他项目修改。branch/HEAD 保持 `particle/main@9d6c9e9684e4c71cc2fb3f08bfb27e1638be4188`。
|
||||
- 本节是静态文档/范围检查记录,不是语法编译、iOS 构建或测试 PASS。没有解除 B1/B2,也没有新增接口。
|
||||
219
docs/报告/STEP-120_iOS原始Filter无损Native接口_Codex完成报告_v2.md
Normal file
219
docs/报告/STEP-120_iOS原始Filter无损Native接口_Codex完成报告_v2.md
Normal file
@@ -0,0 +1,219 @@
|
||||
# STEP-120 iOS 原始 Filter 无损 Native 接口 Codex 完成报告 v2
|
||||
|
||||
日期:2026-10-21(JST)。实现交付状态:**IMPLEMENTED_WAITING_RDC_IOS_BUILD**。
|
||||
|
||||
本轮按 v2 裁决,在限定两个 SDK 文件中增加“无损或拒绝”的通用 ObjC 公共接口。完成的是源码实现和静态审查,**没有执行 ObjC++ 语法编译、iOS 构建或 Native 单测,尚不能确认编译、链接及运行通过**。v1 报告保留原文;其两个契约阻塞由本轮用户裁决收敛,不据此声称完整 JSON 支持。
|
||||
|
||||
NavSea 仍为唯一业务主责。本轮没有实现或迁入 Provider、CM95、Mapping、四档、Pickup、Danger、Planning 逻辑;原始多源 Provider 总目标仍开放。
|
||||
|
||||
## 1. 环境、模型与 Git 基线
|
||||
|
||||
- 实际主机:`Mac.lan`,Darwin `24.6.0`,macOS `15.7.7 (24G720)`,`x86_64`;仓库 `/Users/tei/dev/MapLibreParticleLab`。
|
||||
- 开工分支/HEAD:`particle/main@9d6c9e9684e4c71cc2fb3f08bfb27e1638be4188`。`git worktree list --porcelain` 只有原工作区;子模块状态均为空格前缀,无未初始化、偏离提交或冲突标记。
|
||||
- 开工不是干净工作区:开发记录已有 v1 的末尾追加;未跟踪文件包括 v1/v2 任务书、v1 Step 和 v1 报告。它们均保留;本轮不覆盖旧文档、不暂存。
|
||||
- 用户指定外部执行器必须为 `GPT-6.1 Sol High`(`gpt-6.1-sol / high`);工具没有可独立核实的模型/推理参数元数据。因此仅记录用户指定配置,实际执行器配置仍待外部证据,不宣称已核实。未自行切换模型、启动另一实例或子代理。
|
||||
- 已重新完整读取 AGENTS、PARTICLE_BASELINE、项目规范全部文档、总则 README 及四份现行总则、当前路线、v1/v2 任务书、v1 报告、V27 README 和相关 Darwin/Core 源码。早期 Linux 路径/系统/版本与实际 Mac/V27 不同,按本轮任务执行,未编辑总则。
|
||||
- 现有正式坐标仍为 Android/RN `13.6.1-particle.27`、iOS `6.29.0-particle.27`;发行构建源码为 `970e3ad606e59873a32e9389e65ef257e1c259bb`。本轮新接口没有进入该既有发行包。
|
||||
|
||||
## 2. 实际修改文件与接口
|
||||
|
||||
| 文件 | 实际修改 |
|
||||
| --- | --- |
|
||||
| `platform/darwin/src/MLNVectorStyleLayer.h` | 增加两个公共 selector、错误域/枚举及中文契约说明。 |
|
||||
| `platform/darwin/src/MLNVectorStyleLayer.mm` | 增加局部预检、独立快照、Core 解析、结构/表达式核对与 Filter 写入后回滚验证。 |
|
||||
| `docs/50_Step记录/STEP-120_iOS原始Filter无损Native接口_v2.md` | 新增本次实现/待验收清单。 |
|
||||
| `docs/报告/STEP-120_iOS原始Filter无损Native接口_Codex完成报告_v2.md` | 新增本报告,不覆盖 v1。 |
|
||||
| `粒子开发记录.md` | 在开工时已有 v1 内容之后仅末尾追加。 |
|
||||
|
||||
公开接口:
|
||||
|
||||
```objc
|
||||
- (nullable id)rawStyleFilterJSONObjectWithError:(NSError * _Nullable * _Nullable)error;
|
||||
- (BOOL)setRawStyleFilterJSONObject:(id)json error:(NSError * _Nullable * _Nullable)error;
|
||||
```
|
||||
|
||||
getter 成功返回数组或 NSNull;失败返回 nil。setter 接收数组或 NSNull;nil 不是清空指令。两者成功都会将提供的 error 指针置 nil,可以不传 error 指针。采用带 error 的 getter,使不支持的快照和真正的空 Filter 可明确区分。
|
||||
|
||||
错误域为 `MLNStyleFilterErrorDomain`;NSError.code 使用以下公开枚举,localizedDescription 以对应标识开头,Host 应以域/数值分支而非解析描述文字。
|
||||
|
||||
| code | 公开枚举 | 标识及意义 |
|
||||
| --- | --- | --- |
|
||||
| 1 | `MLNStyleFilterErrorUnsupported` | `E_FILTER_UNSUPPORTED`:不支持、超限、不可逆或写入前验证异常;没有调用目标写入。 |
|
||||
| 2 | `MLNStyleFilterErrorInvalidLayer` | `E_FILTER_INVALID_LAYER`:rawLayer 不存在、类型不符或写入前已失效/被替换。 |
|
||||
| 3 | `MLNStyleFilterErrorWrongThread` | `E_FILTER_WRONG_THREAD`:未在主线程调用。 |
|
||||
| 4 | `MLNStyleFilterErrorMutationFailed` | `E_FILTER_MUTATION_FAILED`:写入或后验核对失败,原 Filter 已恢复且核实;Host 应重新获取状态。 |
|
||||
| 5 | `MLNStyleFilterErrorRollbackFailed` | `E_FILTER_ROLLBACK_FAILED`:无法确认恢复,Host 必须废弃当前操作 Context。 |
|
||||
|
||||
## 3. 源码安全不变量与静态证明
|
||||
|
||||
### 3.1 预检与表示等价
|
||||
|
||||
`NormalizeFilterValue` 直接遍历 C++ `mln::Value`,只接受 JSON 值。`SnapshotFilterJSON` 独立读取 Foundation JSON 为 C++ 值,使用带长度 UTF-8 字节,不调用全局转换器来决定输入是否已经有损。对象键和值、数组成员和顺序、布尔与数值类型均保留;对象键顺序不参与 JSON 等价比较。
|
||||
|
||||
有限、范围内的整数和浮点数归一为 double 后进行 C++ 树比较,避免整数/浮点存储类型不同导致误拒绝。整数界限为 `±9007199254740991`,整数转 double 在此范围精确;更大的整数/浮点数即使恰好可表示也保守拒绝。NSNumber 仅接受标准整数/float/double/布尔编码,CFBoolean 与普通数值区分,NSDecimalNumber 全拒绝;不做十进制舍入或字符串数值解析。
|
||||
|
||||
字符串、键在调用真实 `MLNJSONObjectFromMBGLValue` 之前,必须证明 UTF-8 有效且不含 NUL。桥接后再次经独立读取器生成 C++ 树并比较,防止转换产生截断、类型改变或键合并。**未修改** `MLNStyleValue.mm`、`MLNConversion.h` 等全局转换器。
|
||||
|
||||
### 3.2 getter 的重放证明
|
||||
|
||||
1. 在主线程检查当前 rawLayer 非空且 Core LayerTypeInfo 为 Geometry/Source::Required,复制当前 Filter;不读取 NSPredicate 或初始化 styleJSON。
|
||||
2. 先对表达式树做深度/节点预检,再从当前 Filter 的 `serialize()` 得到值树并检查资源/类型限制。
|
||||
3. 只将受支持的数值存储类型精确归一,调用真实 Foundation 桥接,再独立读取并比较值树。
|
||||
4. 使用真实 `conversion::convert<Filter>(makeConvertible(json), error)` 反向解析;要求重放后的 `Filter.serialize()` 与原值树相等。
|
||||
5. 同时检查表达式存在性、表达式序列化树和 Core `Expression::operator==`。不以 `Filter::operator==` 的 shared_ptr 身份代替语义检查。
|
||||
6. 全部成立才返回快照;不支持或异常返回 NSError,getter 没有任何 Filter 写入。
|
||||
|
||||
这同时检查 legacy 保留的 JSON 和实际执行的表达式树;仅仅“legacy 原文还在”不足以通过。
|
||||
|
||||
### 3.3 setter 的写入前准入与后验处理
|
||||
|
||||
1. 验证 rawLayer/线程,保存原 Filter 副本及原 Layer 弱指针;独立读取调用方 JSON。调用方不得并发修改容器或 Style。
|
||||
2. 从受检 C++ 值建立桥接快照,Core 只解析这份独立对象;要求输入树与解析后 `serialize()` 相等。
|
||||
3. 对新 Filter 运行 getter 的整条重放证明,再对原 Filter 做相同证明。**原状态不支持快照时也拒绝覆盖**,不把危险旧状态先覆盖再考虑恢复。
|
||||
4. 写入前重新核对弱指针/当前 rawLayer 身份及原 Filter;验证期间改变则拒绝。只有这些检查结束后才调用 `setFilter(next)`。
|
||||
5. 写入后核对实际 Filter 的值树及表达式;成功才返回 YES。发生异常或不等价则调用 `setFilter(previous)` 恢复保存的真实 Filter,随后独立核对恢复结果。
|
||||
6. observer 可在写入/恢复后抛异常,所以恢复后的核对独立执行。弱指针失效时不继续解引用旧地址,返回无法确认恢复的错误。
|
||||
|
||||
`setFilter` 只有目标写入和回滚两处;没有移除/重建 Layer、改变 Paint/Layout/Source/层序或重载 Style。写入尝试可能触发既有 Core observer/渲染失效;回滚不撤销已产生的通知,Host 不能把错误当作无事件发生,也不能在无法恢复时继续复用原 Context。
|
||||
|
||||
空 Filter 特例在解析前直接构造 `Filter()`,不把 NSNull 经 legacy converter 变成恒真表达式。Root NSNull 可成功读写;合法嵌套 NSNull 保持原成员位置。
|
||||
|
||||
## 4. 精确准入与拒绝域
|
||||
|
||||
| 项目 | 本次约束 |
|
||||
| --- | --- |
|
||||
| JSON 深度 | 根为 1,最大 64;对象键也按其子节点深度计数。循环容器会触及深度/节点限制而拒绝。 |
|
||||
| 节点数 | 单次值树最多 4096,包含容器、值和对象键;表达式树另受 64 层/4096 节点限制。 |
|
||||
| 字符串大小 | 一棵值树的字符串及键 UTF-8 字节总量最多 262144;不是 HTTP 大小或进程内存限额。 |
|
||||
| 字符 | 字符串/键任意位置含 U+0000、非法 UTF-8/无法无损编码均拒绝,包含 literal 数据。 |
|
||||
| 数值 | 拒绝 NaN、Infinity、负零、绝对值大于 2^53-1、NSDecimalNumber、非标准 NSNumber 编码。布尔和数字不合并。 |
|
||||
| Foundation 类型 | 支持 NSArray、NSString、NSNumber、NSNull、仅字符串键的 NSDictionary;其他类型拒绝。顶层只准数组或 NSNull。 |
|
||||
| 操作符候选 | `all/any/none/!`,`==/!=/</<=/>/>=`,`get/has/!has/in/!in/literal`,`string/number/boolean` 断言和 `collator`。候选仍必须过 Core 解析及后验比较。 |
|
||||
| legacy 子集 | 仅明确形状的纯 legacy 组合:all/any/none 的子项仍为 legacy;比较为属性字符串加单个标量;in/!in 为属性字符串加标量列表;has/!has 恰有属性参数。混合表达式、数组/对象操作数或额外参数拒绝。 |
|
||||
| Core 变化 | setter 输入增加隐式断言、literal 标量展开、常量折叠、缺省 collator 参数补全、丢失未知键或其他结构变化均拒绝;不靠猜测证明等价。 |
|
||||
| 其他复杂表达式 | match/case/let/within/distance 等不在本次候选集,即使 SDK 其他入口支持也拒绝。未来扩展另行授权。 |
|
||||
| 常量 Filter | 顶层布尔快照当前拒绝;NSNull 不与恒真布尔合并。 |
|
||||
| 当前状态 | 原 Filter 不能通过 getter 全路径证明时,setter 同样拒绝覆盖,包括清空操作。 |
|
||||
|
||||
Core 自己的 `serialize()` 必须先产生实际值树,之后才能检查该树的大小;本次限制约束准入值和局部遍历,不承诺 Core 内部首次序列化的绝对内存/耗时上界。对已存在表达式先做遍历上限检查。未增加缓存、并发保障或任意自定义 Foundation 子类的副作用隔离;调用方应传普通 JSON 容器并在主线程串行操作。
|
||||
|
||||
当前运行时 getter 获得的快照已经过往返验证,可直接作为恢复候选。Host 新造的 setter 输入应使用 Core 可往返形式,例如数值范围显式使用 `number` 断言、collator 显式带两个布尔项;不要把“合法 Style JSON”理解为本次 API 必须接受的全集。
|
||||
|
||||
## 5. Style 成员关系与 ABI 边界
|
||||
|
||||
SDK **只操作当前有效 rawLayer**。正常 `removeLayer:` 后仍由 ObjC pendingLayer 拥有的对象、尚未加入 Style 的对象,可以使用本次接口;销毁后的空 rawLayer 拒绝。Host 必须用当前 Style 的 Layer ID 查询结果核对对象身份,并检查自身 Token/代次/Context 与外部修改。Native 没有创建另一套 Context、generation 或 revision;弱指针仅用于本次调用后验核对和回滚。
|
||||
|
||||
Raster/Background ObjC 对象不继承本接口,Host 应先做 MLNVectorStyleLayer 类型检查。内部错误地给 Vector peer 绑定非 Geometry 或不需 Source 的 rawLayer 时返回 InvalidLayer,不对其写 Filter。
|
||||
|
||||
本轮新增两个 ObjC selector、一个导出 NSErrorDomain 和枚举,没有新增实例变量、修改 C++ ABI、改变既有方法签名或实现。旧 predicate/description 方法从 `setPredicate:` 起的完整实现后缀与 HEAD 逐字节相同。**二进制兼容性仅有静态依据,尚未编译/链接验证**;旧 V27 二进制不具备新接口,不能使用新头文件链接旧包并宣称可用。
|
||||
|
||||
`MLNVectorStyleLayer.h/.mm` 已在 `platform/darwin/bazel/files.bzl` 登记;公共头经 Darwin `darwin_objc_hdrs` 传入 iOS `public_hdrs` 与 `MapLibre.dynamic`;iOS umbrella `Mapbox.h` 已 import 此头。未修改生成器、Build 配置或发行记录。
|
||||
|
||||
## 6. 安全样例及静态采集
|
||||
|
||||
以下 JSON/断言条件随本报告交付;只核对了生产源码路径,**未编译、未运行 Native 样例,不记测试 PASS**。不在允许范围外新增测试源码,也没有 Jest。
|
||||
|
||||
### A:简单等值(表达式与受限 legacy)
|
||||
|
||||
```json
|
||||
["==", ["get", "kind"], "buoy"]
|
||||
```
|
||||
|
||||
另测 legacy `["==","kind","buoy"]`。两者分别从当前 Core Filter 取快照、设置其他受支持 Filter、恢复原快照;记录 C++ `Filter.serialize()`、表达式树及 Foundation 值。要求恢复前后的树相等,不以两种 Filter 互相相等替代各自恢复。
|
||||
|
||||
静态依据:Core comparison 对 `==` 保留 get 与字符串操作数;legacy 原文和值树由 Core 保留,反向生成的实际 legacy 表达式也接受独立相等检查。
|
||||
|
||||
### B:范围 all 使用两个不同属性
|
||||
|
||||
```json
|
||||
["all",
|
||||
["<=", ["number", ["get", "minDepth"]], 10],
|
||||
[">=", ["number", ["get", "maxDepth"]], 2]]
|
||||
```
|
||||
|
||||
静态依据:显式类型断言避免 setter 再由 Core 增加 number 包装;两个条件和 `maxDepth` 均在 Value 比较中逐项保留。Core 重放及表达式相等也必须通过,SDK 不走原 NSPredicate 的 BETWEEN 折叠路径。RDC 应另外给原始无显式断言形式做 setter 负例,确认结构变化时返回 Unsupported 且无写入;运行时 getter 对已由 Core 生成断言的 Filter 则从当前序列化读取。
|
||||
|
||||
### C:两个属性及 locale 参数
|
||||
|
||||
```json
|
||||
["all",
|
||||
["<=", ["string", ["get", "name"]], "m",
|
||||
["collator", {"case-sensitive": false, "diacritic-sensitive": true, "locale": "de"}]],
|
||||
[">=", ["string", ["get", "category"]], "a",
|
||||
["collator", {"case-sensitive": false, "diacritic-sensitive": true, "locale": "de"}]]]
|
||||
```
|
||||
|
||||
静态依据:Core `CollatorExpression::serialize()` 明确保留两个布尔项及 locale;本次独立 JSON 读取保留所有对象成员;getter/setter 需要两棵条件树及实际表达式同时相等。RDC 必须断言 `category` 和两个 `locale=de` 均保留,并验证省略布尔项的 setter 输入被保守拒绝。
|
||||
|
||||
### D:NUL 明确拒绝
|
||||
|
||||
```json
|
||||
["==", "name", "a\u0000b"]
|
||||
```
|
||||
|
||||
另测 literal 对象键 `a\u0000b`。getter fixture 应从 Core 构造含完整三字节字符串的 Filter,避免经有损辅助函数先构造错误测试基线。setter 通过 NSJSONSerialization 解析上述 JSON 得到含 NUL 的 NSString。两条路径都应返回 Unsupported;setter 比较完整原 Filter/原 Impl 与 observer 计数,确认预检拒绝、没有 setFilter 调用。静态依据为 `FilterBudget::string()` 在桥接之前的 `find('\0')` 检查。
|
||||
|
||||
### E:空 Filter 与 common 组合
|
||||
|
||||
Core 原 `Filter()` 的 getter 应返回 NSNull,设置有效 Filter 后用 NSNull 恢复,要求 `bool(getFilter()) == false`,serialize 为 NullValue。另测 `any`、`!`、`has` 与 `["in",["get","kind"],["literal",["buoy","beacon"]]]`,确认其可往返形式可用,literal 内 NSNull 不丢位置。
|
||||
|
||||
RDC 可在现有 Darwin XCTest 框架加入下列调用形状,完整 fixture 与断言仍需由独立验收实现,当前没有已登记的新 Native 测试:
|
||||
|
||||
```objc
|
||||
NSError *error = nil;
|
||||
id saved = [layer rawStyleFilterJSONObjectWithError:&error];
|
||||
XCTAssertNotNil(saved);
|
||||
XCTAssertNil(error);
|
||||
XCTAssertTrue([layer setRawStyleFilterJSONObject:overrideJSON error:&error]);
|
||||
XCTAssertNil(error);
|
||||
XCTAssertTrue([layer setRawStyleFilterJSONObject:saved error:&error]);
|
||||
XCTAssertNil(error);
|
||||
XCTAssertEqualObjects(saved, [layer rawStyleFilterJSONObjectWithError:&error]);
|
||||
```
|
||||
|
||||
还需独立验证:边界深度/节点/字节上限及超限;循环容器、NaN/Infinity/-0、大整数/NSDecimalNumber、非字符串键、NSDate/NSData;未知操作符、短 `["in"]`、混合 legacy/表达式、Core 解析错误;失效 Layer、非 Vector、后台线程;正常 detach 仍有效且由 Host 阻止错误成员操作;外部 Filter 更新被 getter 读取;返回对象修改不影响 rawLayer;Paint/Layout/Source/层序/Layer 身份不变。
|
||||
|
||||
回滚测试必须通过测试 observer 或等效受控钩子使 `onLayerChanged` 一次性改写 Filter/抛异常,验证 code=4 的完整恢复;再使恢复无法核实或 Layer 失效,验证 code=5。测试应设置递归保护,不让注入本身无限递归;保存实际前后树及回调计数。当前这些运行证据均缺失。
|
||||
|
||||
## 7. 已执行检查、未执行项与差异
|
||||
|
||||
已执行的轻量检查:
|
||||
|
||||
- 机器/版本及 Git branch、HEAD、status、worktree、submodule 只读检查;当前源码/规则重读。
|
||||
- 静态核对真实 Value、Filter、Expression、Layer/WeakPtr、Foundation 转换签名;Bazel C++20/异常支持与公开头路径只读审查。
|
||||
- `git diff --check` 以及显式两个 SDK 路径和开发记录的 scoped 空白检查:均 exit=0、无输出;不等同编译。
|
||||
- Python 源文件核对:旧 `setPredicate:` 至文件末尾字节完全保持;只含两处 `->setFilter(`;保护任务书、v1 文档、共享转换器及 V27 记录哈希均未变化。这些是源码/保护检查,不是 Native 行为测试。
|
||||
|
||||
没有执行 ObjC++/C++ `-fsyntax-only`、Bazel/Xcode/CMake/Ninja/Gradle 构建、链接、codegen、XCTest/GTest/Jest、打包、安装、ADB、模拟器/真机或性能测试。没有新构建/测试失败日志;不能用静态检查声称它们通过。只读查找中的不存在候选路径已按实际 `mln`/`src` 路径核正,不是编译错误。
|
||||
|
||||
未 stage/commit/push/tag;未新建/切换分支或 worktree;未 reset/clean、升级依赖、修改其他仓库或发行。没有执行远端操作,故无远端 HEAD 核验。本轮没有产物大小、SHA-256、设备日志或实际运行证据。
|
||||
|
||||
与 v2 授权范围差异:无源码扩围;getter 命名采用 `...WithError:` 以返回 NSError,符合本轮无损或拒绝语义。Android 未修改/构建;任务开头双平台授权没有覆盖同一任务中明确的两文件范围与禁止长构建规定。
|
||||
|
||||
## 8. RDC 精确命令与独立证据
|
||||
|
||||
独立审计本报告与当前 diff 后,在同一实际工作区执行以下 **iOS 单项构建,Codex 未执行**:
|
||||
|
||||
```bash
|
||||
cd /Users/tei/dev/MapLibreParticleLab
|
||||
git diff --check
|
||||
bazel build -c opt --//:renderer=metal //platform/ios:MapLibre.dynamic
|
||||
```
|
||||
|
||||
这是现有正式 Pipeline 的真实 iOS target;不要为单项验证调用整套 release.sh,以免执行 Android、Maven Local 或发布步骤。编译失败保留现场,回传首个真实 error、target、必要上下文及完整日志路径,不改宏或放宽 `-Werror` 制造通过。
|
||||
|
||||
构建后检查当次 `bazel-out/darwin_x86_64-opt/bin/platform/ios/MapLibre.dynamic.xcframework.zip`:在 `ios-arm64/MapLibre.framework/Headers/MLNVectorStyleLayer.h` 中确认两个 selector、错误域和枚举;再进行实际 ObjC 调用的编译/链接与上述 XCTest。当前正式动态 target 为真机 arm64,不把旧包或其他平台产物当作新接口证据。
|
||||
|
||||
需采集:外部执行器实际模型/档位;不变 HEAD 与本轮精确 diff;真实编译 command/exit/耗时/完整日志;新产物 SHA-256 和 Headers;实际调用编译/链接;各样例的 C++ 原树/表达式与重放结果、Foundation 类型/UTF-8 长度、error domain/code;每种预检拒绝前后状态未变;真实回滚两类错误;Style/Layer/Host 身份与失效边界证据;非 Filter 属性及层序不变。
|
||||
|
||||
未完成/无法验证项:真实 ObjC++ 编译、链接、iOS Metal SDK 构建、所有 Native 样例执行、回滚异常注入、线程/生命周期和 ABI 消费者验证、真机/Host 联调。若以上成功,另起正式发行与 NavSea Host 集成步骤,遵守 GSRL。本轮不标记 iOS 可打包、发版或真机 PASS。
|
||||
|
||||
## 9. 最终静态交付记录
|
||||
|
||||
- 文档落盘后 `git diff --check`、`git diff --cached --exit-code` 均 exit=0、无输出;前者不覆盖 untracked 文档,因此另逐文件检查本轮五个文件的 UTF-8、末尾换行及无行尾空白,全部通过。
|
||||
- tracked diff 只有两个 SDK 文件及开发记录:头文件新增 54 行、实现新增 374 行;开发记录相对 HEAD 包含既有 v1 和本轮追加,共新增 15 行。本轮相对续行开工时的完整记录仅末尾增加 2037 字节,已验证原字节全量保留。
|
||||
- SHA-256 保护检查确认两份任务书、v1 Step/报告、两个全局转换器及 V27 README/SOURCE_LOCK 与本轮开工时一致。临时保护记录位于 `/tmp/step120_v2_baseline.json`,原开发记录快照位于 `/tmp/step120_v2_development_log_before`;它们是本地自检材料,不是构建产物或仓库交付文件。
|
||||
- Python 仅解析报告中四组 JSON 样例,确认 JSON 语法、第二属性、locale 和实际解码的 NUL;不代表 Core 接受或 Native 测试通过。
|
||||
- 源码交付 SHA-256:`MLNVectorStyleLayer.h` 为 `7c3e09d3582db6bd170bc091d05fe99a02ebc7f400eff60451758245134d9d6f`;`.mm` 为 `0fb201c86c058381007a405c9933406e25f3fa8d15204b22f46d57bda479326d`。
|
||||
- 最终 branch/HEAD 保持 `particle/main@9d6c9e9684e4c71cc2fb3f08bfb27e1638be4188`;暂存区无差异,没有其他文件修改。子模块开工状态正常,本轮未修改子模块或工作区结构。
|
||||
- 结论仍为 **IMPLEMENTED_WAITING_RDC_IOS_BUILD**;本节所有通过项都仅是静态源码、文档及范围检查。
|
||||
23
docs/报告/STEP-120_iOS原始Filter无损接口_RDC编译验收报告_v1.md
Normal file
23
docs/报告/STEP-120_iOS原始Filter无损接口_RDC编译验收报告_v1.md
Normal file
@@ -0,0 +1,23 @@
|
||||
# STEP-120 iOS 原始 Filter 无损接口 RDC 独立编译验收报告 v1
|
||||
|
||||
日期:2026-10-21 JST。联调业务唯一主责 NavSea;专业支持仓 MapLibreParticleLab。本报告只是 Native 编译证据,不是 .28 正式发行、Runtime 或设备验收。
|
||||
|
||||
## 实际基线
|
||||
- Mac /Users/tei/dev/MapLibreParticleLab,分支 particle/main,初始 HEAD 9d6c9e9684e4c71cc2fb3f08bfb27e1638be4188;未创建/切换分支、Worktree,未触碰无关项目。
|
||||
- Codex GPT-6.1 Sol High 已按 STEP-120 v2 修改 platform/darwin/src/MLNVectorStyleLayer.h 与 .mm;中文报告 docs/报告/STEP-120_iOS原始Filter无损Native接口_Codex完成报告_v2.md。
|
||||
- 当前新增两个 ObjC API:rawStyleFilterJSONObjectWithError:、setRawStyleFilterJSONObject:error:。保留旧 predicate 逻辑、原 Style Layer 的业务语义和原 .27 正式发行文件。
|
||||
|
||||
## RDC 真实编译与校验
|
||||
- 命令:bazel build -c opt --//:renderer=metal //platform/ios:MapLibre.dynamic。
|
||||
- 日志:/private/tmp/JOINT-PBF-001-STEP120-RDC-ios-metal-build-v1.log。
|
||||
- 结果:INFO: Build completed successfully, 708 total actions;1176.689s;只有 /opt/homebrew/lib 搜索路径 warning,不影响链接。
|
||||
- 产物:bazel-out/darwin_x86_64-opt/bin/platform/ios/MapLibre.dynamic.xcframework.zip。
|
||||
- 大小 4,187,287 bytes,SHA256 577d0784f1e436c04b3cf0bd0213ce38fdf14612898367ec6e7c73156b82cd74。
|
||||
- unzip -tq PASS;产物仅 ios-arm64 真机 Slice;公开头 MLNVectorStyleLayer.h 包含上述两个 API。
|
||||
- git diff --check PASS;尚未执行设备调用或 Native Filter 回滚的独立运行测试。
|
||||
|
||||
## 后续及边界
|
||||
- “无损或拒绝”不等于所有 CM95 Filter 自动支持;Host 必须核对 Layer 实例、Style 代次、revision、外部修改。
|
||||
- .28 GSRL 必须基于干净的冻结源码提交,随后由现有 release.sh 流水线完成 Android/RN/iOS 统一构建、原子发布、公网回读、Source Lock/Manifest、Tag/Push;不得覆盖旧 .27。
|
||||
- NavSea 必须等待正式 SwiftPM 6.29.0-particle.28,然后再完成 MLRN iOS Bridge 的查询、应用、恢复三 API 及编译。Android 现有 .27 Kotlin Bridge 已构建通过但 .28 必须回归。
|
||||
- 当前状态:STEP120_RDC_IOS_METAL_OPT_BUILD_PASS / XCFRAMEWORK_HEADER_EXPORT_PASS / DEVICE_RUNTIME_PENDING / V28_RELEASE_PENDING。
|
||||
36
docs/报告/V25_统一正式发行_GSRL_RDC完成报告_v1.md
Normal file
36
docs/报告/V25_统一正式发行_GSRL_RDC完成报告_v1.md
Normal file
@@ -0,0 +1,36 @@
|
||||
# V25 统一正式发行 GSRL RDC 完成报告 v1
|
||||
|
||||
日期:2026-10-12(JST)
|
||||
结论:**GSRL RELEASED / PASS**。跳过 v24,v23 历史 Tag/制品未覆盖。设备运行时与 Host 联调独立待验收。
|
||||
|
||||
## 版本及身份
|
||||
|
||||
- Android OpenGL AAR / React Native:`13.6.1-particle.25`;iOS Metal / SwiftPM:`6.29.0-particle.25`。
|
||||
- 仓库:`https://git.eyaku.jp/tei/MapLibreParticleLab`;发行分支:`particle/main`。
|
||||
- 实际统一 Build Source Commit:`f32f6f45fd359007e8b3c77911b66d9e7fc93848`,已 Push 并与远端一致。
|
||||
- GSRL Source Lock 文档提交:`4b2f3c8d3874161c9cba832a4ba28d5182b05ca6`,Commit Message:`release: record v25 GSRL source lock and receipt instructions`。
|
||||
- Pipeline Finalize Commit / 完成 Tag 时的 Final HEAD:`3774d0a8418daa36d7a988b2ce9650a6a1178fc4`,Commit Message:`release: finalize unified particle.25`。
|
||||
- 不可变正式 Tag:`maplibre-particle/rn/13.6.1-particle.25`,远端 annotated tag peeled Commit = `3774d0a8418daa36d7a988b2ce9650a6a1178fc4`;SwiftPM 远端 `6.29.0-particle.25` 存在。
|
||||
- 发布入口:`release/maplibre-particle/13.6.1-particle.25/README.md`、`SOURCE_LOCK.json`;六文件 SHA/大小/地址以 Source Lock 为权威。
|
||||
|
||||
## 严格串行执行及验收
|
||||
|
||||
1. Integration main 同步并核对跨项目发行约束;版本占用核对,v25 未占用;Prepare 提交 `d5841e6471058614e9a06bf5e27dba0df38e8a97`,远端核验 PASS。
|
||||
2. 首次 Build 的 Android 四 ABI AAR、Maven Local PASS;iOS Bazel `actool` 因无 Simulator Runtime 失败。**v25 不增加 Simulator 架构**。使用未变化的 v23 `Images.xcassets` 对应、Xcode 26.3 编译的 `Assets.car`(SHA256 `997014687d17e93b9a979b4a47229d082e04a03587b01b4718ac7cb20f1a196b`),并补齐三处 Bazel Core 头文件声明,详见 `V25_iOS实体机Bazel构建兼容性修复与资源溯源_v1.md`;源修复提交 `f32f6f45fd359007e8b3c77911b66d9e7fc93848`。
|
||||
3. 正式 `release.sh 25 build`:**exit 0**,Android 四 ABI 完整,Maven Local、iOS Bazel opt Metal 真机 ios-arm64 ZIP(无 Simulator slice)、RN npm pack PASS。构建日志:`/tmp/maplibre_v25_unified_release_build_final_20261011.log`。
|
||||
4. `release.sh 25 publish`:**exit 0**,NAS 原子发布、六文件公网逐字节读回 PASS、SwiftPM Tag 已发布。日志:`/tmp/maplibre_v25_unified_release_publish_20261011.log`。
|
||||
5. 生成 GSRL `gsrl/source-lock/v1` Source Lock;六项文件 SHA-256 与本机正式构建产物逐一比对 PASS;版本、依赖、来源、使用说明齐全,Git scoped Commit。
|
||||
6. `release.sh 25 finalize`:**exit 0**,正式 Finalize Commit、发行 Tag Push 成功;再次 Fetch 确认 `origin/particle/main` 与正式 Tag 的 peeled Commit 均为 `3774d0a8418daa36d7a988b2ce9650a6a1178fc4`。日志:`/tmp/maplibre_v25_unified_release_finalize_20261012.log`。
|
||||
7. 2026-10-12 独立公网二次回读:AAR、POM、Module、RN tgz、iOS Metal ZIP、ZIP.sha256 **6/6 PASS**,各 URL 下载退出码 0、size 与 SHA 均和 Source Lock 相同;SwiftPM 远端 Tag 再次核验存在。
|
||||
|
||||
## 三个主体制品 SHA-256
|
||||
|
||||
- Android AAR:`629d5d437d884ec2017f3fde007aaadfd2bc787983457f75bcaf4d4a13184a1e`
|
||||
- RN tgz:`e5ed678c90bad6082dda30fb9937894cabe28732160391816b785d3c88c208e9`
|
||||
- iOS Metal XCFramework ZIP:`91808cfb5559dfa050fe4d466d2c9fa2dc492021bd1a203c778450dc0dae20af`
|
||||
|
||||
## 未覆盖事项
|
||||
|
||||
- 本次 **未执行 v25 发行 AAR/RN/iOS XCFramework 的 Android/iPad 真机运行与 NavSea Host 整体验收**。早前 STEP-119 Android CPU FastPath 基准来自其他源码版本和 Validation APK,不能冒充 v25 正式制品的设备 PASS;动态 Camera、Style、Source/Tile/Annotation 更新与像素等价仍为后续专项验收。
|
||||
- GSRL RELEASED/PASS 仅证明来源、版本、正式制品及公网验收链闭合,不等于功能或性能全场景认证。
|
||||
- 本报告为 Tag 后的审计文档增补,**不得移动或覆盖既有发行 Tag**;后续分支文档 Commit 不更改正式 Tag 指向。
|
||||
73
docs/报告/V26_Metal混合Segment视觉错误_Retained保留修复调查报告_v2.md
Normal file
73
docs/报告/V26_Metal混合Segment视觉错误_Retained保留修复调查报告_v2.md
Normal file
@@ -0,0 +1,73 @@
|
||||
# V26 Metal 混合 Segment 视觉错误——保留 Retained 的修复调查报告 v2
|
||||
|
||||
日期:2026-10-13
|
||||
状态:**iPad F 组真机 FAIL(2026-10-13 18:51 JST);CommandBuffer 修正候选不能作为修复完成证据。Native 编译/安装/启动通过,但 Retained 视觉验收不通过。禁止发布 V27。**
|
||||
前一版:`V26_Metal混合Segment视觉错误_安全降级调查报告_v1.md` **REJECTED**,禁止发布其中关闭 Metal Retained 的方案。
|
||||
|
||||
## 1. 用户强制约束
|
||||
|
||||
- 不允许关闭整个 Retained、不允许把有效 Retained Plan 隐式改成全量 Live。
|
||||
- Retained 必须保持 GPU 离屏绘制、缓存 Build/Reuse、Cache Texture 合成;Continuous 执行频率保持独立。
|
||||
- Android/iOS 使用同一套 JS Plan 和用户步骤,不以 `Platform.OS` 分配不同实验语义。
|
||||
- 以 Style Z-order 为唯一图层顺序,不改本船、AIS 或天气业务数据。
|
||||
- 不新增私有 RN/Native API,不改 Particle Contract。
|
||||
- 实际执行:2026-10-13 经用户授权,ChatGPT 通过 RDC 在 Mac 远程执行 iOS Metal 构建、签名、覆盖安装与启动;Android 原生编译和安装未执行。iPad 视觉验收由用户人工确认。
|
||||
|
||||
## 2. 已取得的实际复现
|
||||
|
||||
来源:Pleiades iPad mini 5,已安装 V26;Windows NavSea Metro `192.168.200.100:8081`。
|
||||
|
||||
- E:全 115 层 CONTINUOUS,Retained 总开关开启,0 Retained Segment,iPad 无闪烁,图标/本船航行线/AIS 航线/色场/粒子正确。快照 `/tmp/navsea-v26-ipad-render-20261013/E-0746/start.json`。
|
||||
- F:R0 仅 `background`,C1 其余 114 层,Native `valid=true`, `fallback=false`,`actualExecution=[composite,live]`,R0 build 96/reuse 2778。冷启动即图标过大、本船/AIS 航线消失;天气仍可见且不闪。快照 `/tmp/navsea-v26-ipad-render-20261013/F-0740/start.json`。
|
||||
- 原始 RC:R0 层 0..30,C1 层 31..114;天气 ON 后闪烁,OFF 后持续。旧 RCR 预检的末端 Layer ID 硬编码与实际 Style 不符,现场最终跑 RC。
|
||||
- A:关闭 Retained 不闪;B:Native 天气隐藏不闪;C 和 D 分别只显示色场或粒子都闪。C/D 使用相同 Native Runtime 数据提交,不代表单独 shader 根因。
|
||||
|
||||
F 在仅最底层缓存时也失败,说明“缩小缓存至简单图层”不能作为修复。
|
||||
|
||||
### 2026-10-13 新构建真机复测(候选 FAIL)
|
||||
|
||||
- RDC Mac Bazel `bazel build -c opt --//:renderer=metal //platform/ios:MapLibre.dynamic`:943/943,exit 0。新候选 ZIP SHA-256:`b89bc06bf03eba20a5790da21215fe7e167da20b3fdfa6a34782420bd503a354`。
|
||||
- 新 XCFramework 从本地 ZIP 替换至暂存 NavSea.app,codesign 重新签名和验证 PASS,Pleiades 覆盖安装及启动 PASS;没有发布/替换正式 SwiftPM V26。
|
||||
- 用户报告新候选 E:地图、天气 ON/OFF 都正常(E 不执行 Retained Cache)。
|
||||
- 用户报告新候选 F:**FAIL**,冷启动光弧等图形巨大、不闪、航向线消失、天气开关无可见效果。不能将无闪烁当作正确。
|
||||
- 新 F Native 快照:`/tmp/navsea-v27-retained-ipad-stage/F-1851/start.json` 和 `end.json`,时间 `2026-10-13T09:51:04Z`、generation 3、mapReactTag 802、118 Style Layers;R0 `[0,1)` `background`、`actualExecution=composite`、`build=13`、`reuse=1069`、`invalidation=13`;C1 `[1,118)` `actualExecution=live`;`valid=true/fallback=false`。证明缓存确实工作,但画面仍错。
|
||||
- 结论:CommandBuffer 原实现存在执行序不明确的风险,然而此候选未修复视觉缺陷,**无法将它认定为根因或 V27 修复**。后续重点分析 Metal Composite 的绘制状态、RenderPass、Stencil/Depth 和 Live 绘制的相互作用。
|
||||
- 新 F 完成后 Windows Metro 同一 Android/iOS Debug 开关已改回 E,iOS Bundle HTTP 200;**用户于 2026-10-13 20:43 JST 确认 iPad 重新运行 E 后显示 OK**。由此 E 稳定恢复;F 明确失败。候选仍留在 iPad,仅切回 JS E,不代表回装了旧 V26。
|
||||
- 后续本轮只读源代码调查:`renderer_impl.cpp` 的 F/E 共同走标准 Live `drawableOpaquePass/drawableTranslucentPass`,区别是 F 额外执行了 `RetainedUnderlayCache::beginBuild/finishBuild/composite`;`mtl::RenderPass` 和 `PaintParameters::beginRenderTarget()` 会重建 Pass 状态及清理 tile clipping mask ID。需重点验证 Cache Composite 的真实 Metal state、Stencil/Depth、顶点/纹理坐标以及 Live drawable 的状态恢复;仅从快照计数不能推断根因。
|
||||
|
||||
## 3. 已从实际代码确认的候选根因
|
||||
|
||||
V26 `src/mln/mtl/offscreen_texture.cpp` 的 `OffscreenTextureResource::bind` 以 `context.getBackend().getCommandQueue()->commandBuffer()` 单独取得离屏 CommandBuffer;`swap()` 对它独立 `commit + waitUntilCompleted`。
|
||||
|
||||
iOS `platform/ios/src/MLNMapView+Metal.mm` 的默认画面 `MLNMapViewMetalRenderableResource` 自建 `commandQueue` / `commandBuffer`,直到帧末 `swap` 才提交。
|
||||
|
||||
`renderer_impl.cpp` 先用默认画面 CommandBuffer 组织上传,再绘制 Retained 离屏缓存、调用其 `present`,之后在默认画面绘制缓存纹理及 Continuous。**这两条独立命令队列并不建立“主帧上传 → 离屏构建 → 主帧合成”的 GPU 顺序保证。** 这是确定存在的同步风险;是否是图标放大/航线丢失的唯一原因仍需真机验证。
|
||||
|
||||
## 4. 第二版实现(保留 Retained)
|
||||
|
||||
- `include/mln/mtl/context.hpp` 和 `src/mln/mtl/context.cpp`:提供帧内借用的主 CommandBuffer 指针,在 beginFrame/endFrame 清理,不持有跨帧引用。
|
||||
- `src/mln/mtl/command_encoder.cpp`:默认画面 UploadPass 创建时,将它的 CommandBuffer 登记为当前帧 CommandBuffer。
|
||||
- `include/mln/mtl/offscreen_texture.hpp`、`src/mln/mtl/offscreen_texture.cpp`:增加仅供 Retained Cache 显式启用的帧内排序开关。启用后其离屏 RenderPass **复用当前帧主 CommandBuffer**;离屏 `swap` 在共享模式只清理引用,**不提前提交主帧**,由默认画面帧末统一 commit。未启用此开关的离屏纹理(包括 Snapshot/其它 RenderTarget),即使同帧存在主 CommandBuffer,也维持原先的独立 commit/wait。
|
||||
- `src/mln/renderer/retained_underlay_cache.cpp`:仅在 Metal Retained Cache 分配或使用 offscreenTexture 时显式启用上述开关;OpenGL 路径不变。
|
||||
- `test/renderer/mtl_retained_attachments.test.cpp`:新增共享 CommandBuffer、离屏 swap 后仍允许添加同帧 RenderPass 的针对性 Metal 测试。
|
||||
|
||||
预期顺序:一个 Metal CommandBuffer 内 `Upload → Retained offscreen render → main composite → live Continuous → final present`。各 Pass 的独立 Color/Depth/Stencil attachment 继续保留。
|
||||
**没有修改 `RetainedUnderlayCache` 的 Build/Reuse/Composite 语义,也没有跳过任意合法 Retained Segment。**
|
||||
|
||||
## 5. 验收和停止条件
|
||||
|
||||
1. 检查改动的 C++ 格式与差异;本轮不自动运行 Native 编译或设备操作。
|
||||
2. 用户手工构建 Metal Candidate XCFramework(使用现有已验证命令 `bazel build -c opt --//:renderer=metal //platform/ios:MapLibre.dynamic`),在独立测试版中接入。**不能覆盖正式 V26、不能用旧 SwiftPM binary 冒充新补丁。**
|
||||
3. 先运行 MetalRetainedAttachments 的针对性单测,确认共享 CommandBuffer 不提前 commit,独立离屏路径不退化。
|
||||
4. iPad 首先复测 F:保留 `[R(background),C(rest)]` 的 Native Plan,不允许 Native 临时回退全部 Continuous;要求 `build>0`、`reuse>0`、`actualExecution=[composite,live]`,并且图标大小和所有航线正常。
|
||||
5. F 通过后再测 RC:天气 OFF/ON/OFF,色场、粒子、本船/AIS/海图视觉完整,无异常闪烁;对照 Android OpenGL 同一 JS Plan。
|
||||
6. 检查帧率、CPU/GPU 时间、缓存重建/复用、内存;保证 Retained 的性能价值。
|
||||
7. F 如果仍错误,**立即停止**,采集 Metal GPU Capture 对比两个 RenderPass 的资源绑定与混合/Depth/Stencil,不能宣布 bug 已修好,也不得重新关闭 Retained。
|
||||
|
||||
## 6. 当前工程和发行状态
|
||||
|
||||
Mac 仓库 `/Users/tei/dev/MapLibreParticleLab` branch `particle/main`、当前起点 HEAD `b0f34cc9a299a75af1f7e708c63d75eaa42e9ca2`。源码改动尚未 commit/push/tag/publish;正式 V26 SOURCE_LOCK 指向 `fce326a9009d3fc21eb91c5df362f0769163d07e`,它是旧发行身份,不代表此次候选已发布。
|
||||
|
||||
Windows NavSea 当前 JS 已取消 iOS 独享 E:Debug Android/iOS 均选 E,Release BASELINE。正式修复后需在新 Native 实际安装成功、用户批准测试窗口后,把共用 JS Test Mode 切到 F/RC 进行对照;不能在旧 V26 上提前切换造成已知异常。
|
||||
|
||||
当前明确结论:**这是保留 Retained 的候选修复,尚未通过 Native 编译与设备画面确认,不能写成 CLOSED/PASS。**
|
||||
45
docs/报告/V26_Metal混合Segment视觉错误_Retained保留修复调查报告_v3.md
Normal file
45
docs/报告/V26_Metal混合Segment视觉错误_Retained保留修复调查报告_v3.md
Normal file
@@ -0,0 +1,45 @@
|
||||
# V26 Metal 混合 Segment 视觉缺陷——保留 Retained 修复调查报告 v3
|
||||
|
||||
日期:2026-10-13
|
||||
状态:**第二版 Stencil 状态修正 F 真机 FAIL / ABORTED;图标仍异常放大,且用户报告地图无法移动。已保存证据、恢复共用 JS E,并归档/撤销失败的源码补丁。不可发布 V27。**
|
||||
正式发行:**NOT RELEASED,不得标记 V27 PASS;保留 Retained 构建/复用/合成**
|
||||
上版:`V26_Metal混合Segment视觉错误_Retained保留修复调查报告_v2.md`(CommandBuffer 候选 F 真机失败)
|
||||
|
||||
## 1. 已证实事实
|
||||
|
||||
- Pleiades iPad mini 5;Native 旧 V26 F,及第一版 CommandBuffer 候选 F,均真机 FAIL:背景 Retained 真实 `Build/Reuse/Composite`,但光弧或图标被放大、航向线消失、天气开关异常。旧版本 E(全 Continuous)多次由用户确认正常。
|
||||
- 第一次候选 Native F 快照保留于 `/tmp/navsea-v27-retained-ipad-stage/F-1851/start.json` 与 `end.json`,其中 Retained R0 `[0,1)`、C1 `[1,118)`、`build=13`、`reuse=1069`、`actualExecution=composite`、`valid=true`、`fallback=false`。
|
||||
- 不能据此认定 CommandBuffer 是根因。原候选 `MapLibre.dynamic.xcframework.zip` SHA-256 为 `b89bc06bf03eba20a5790da21215fe7e167da20b3fdfa6a34782420bd503a354`;旧候选补丁备份在 `/tmp/navsea-v27-retained-ipad-stage/patches/V27_candidate1_commandbuffer_plus_current_context.patch`。
|
||||
|
||||
## 2. 本次单变量改动
|
||||
|
||||
恢复前一版 CommandBuffer 改动的七个源码/测试文件,原 Candidate 1 不再参与当前源码编译。仅两个文件相对 `particle/main` HEAD `b0f34cc9a2` 发生功能代码修改:
|
||||
|
||||
- `src/mln/mtl/context.cpp`:`Context::renderTileClippingMasks` 的 Metal Stencil ID 设置改走 `mtlRenderPass.setStencilReference(...)`,不再绕过 RenderPass 缓存直接调用 `encoder->setStencilReferenceValue`。
|
||||
- `src/mln/mtl/tile_layer_group.cpp`:3D Stencil Reference 也走 RenderPass 缓存,删除不再使用的局部 encoder 引用。
|
||||
|
||||
代码已确认存在 Metal 原先绕过 RenderPass 状态缓存的路径;其与 F 画面错误的因果关系**尚待真机结果**。没有变更 Retained Cache 或共享 RN API,未禁用 Retained,未引入 JS Android/iOS 平台分流。
|
||||
|
||||
## 3. 新 Native 制品与安装
|
||||
|
||||
- Mac `/Users/tei/dev/MapLibreParticleLab`,`bazel build -c opt --//:renderer=metal //platform/ios:MapLibre.dynamic`,exit 0,约 49 秒;`git diff --check` PASS。
|
||||
- 新 Metal ZIP SHA-256:`5a8bef4e8419ae2b8c5bf22253361d40a4ed7ffd7c156ea73d5df8eae72b3393`。
|
||||
- 独立保留于 `/tmp/navsea-v27-retained-ipad-stage/candidate2_stencil/MapLibre.dynamic.xcframework.zip`;最小源码 patch 同目录 `candidate2_stencil_only.patch`。
|
||||
- RDC Mac 使用现有 `scripts/ios-install-test-device.sh` 的 `scripts/pass.txt` 所对应 Keychain 与签名身份,在暂存 App 替换完整 MapLibre.framework,重新签名并严格验证;覆盖安装 Pleiades `com.navsea.app` 成功,`devicectl process launch` 成功。
|
||||
- 无卸载、无清数据、无 SwiftPM V26 正式仓库/Package 更改、无发布、无 Git Commit/Tag。
|
||||
- 安装后 E Native 快照:`/tmp/navsea-v27-retained-ipad-stage/candidate2_stencil/E-preF/start.json`,时间 `2026-10-13T12:00:50Z`,`valid=true`,0 Retained / 1 Continuous、`[0,118)` Live。仅验证了运行时模式,并非用户视觉结果。
|
||||
|
||||
## 4. 第二版 F 真机结果(失败,已停止测试)
|
||||
|
||||
- 用户于 2026-10-13 21:09~21:10 JST 确认 **F 仍有大图标,没有改进,并补充报告“也不移动了”**。目前“无法移动”是手势没有作用,还是相机已变但视觉没有更新,仍需通过输入事件/相机指标判定,不得臆测。
|
||||
- 从 Pleiades 实际取得 `Library/Caches/navsea-render-segment-start.json` 与 `end.json`,已保存在 `/tmp/navsea-v27-retained-ipad-stage/candidate2_stencil/F-failed-20261013-2110/`。其中 capturedAt `2026-10-13T12:10:12Z`、`generation=4`、`mapReactTag=804`、`styleLayerCount=118`、`valid=true/fallback=false`;R0 `[0,1)` `background`,`actualExecution=composite`、`build=14`、`reuse=6076`、`invalidation=14`;C1 `[1,118)` `actualExecution=live`;`fullCpuFrame=6231`。帧计数不等于相机更新证明。
|
||||
- 用户报告画面失败,Native 配置/执行链未发生全 Live 回退,因此第二版 Stencil 修正 **FAIL**。没有进入 RC,没有发布 V27。
|
||||
- Windows Metro 当前统一 Debug 开关已经恢复为 `E_CONTINUOUS_ONLY`,iOS Bundle HTTP 200,Android 同一 JS 源配置;用户需冷启动 iPad 才能加载 E。**用户 2026-10-13 21:18 JST 已再次确认本次切回 E 后正常,包括地图移动;因此 E 恢复 PASS。**
|
||||
- 失败的第二版源码 diff 另存 `/tmp/navsea-v27-retained-ipad-stage/candidate2_stencil/candidate2_failed_stencil_source.patch`。已只针对两个改动的 Metal 源文件 `git restore` 回 `particle/main` HEAD;无其他功能文件变更,报告保留,iPad 上仍是已安装的 Candidate 2 二进制(仅 JS 回 E),不等于已更换 Native。
|
||||
- 下一轮调查把相机/手势事件、Native 相机矩阵变化、Retained 的 `camera-or-boundary` invalidation、主画面真实绘制与 GPU 合成可视化分开验证,再制定单一变量修复;禁止无验证继续叠加补丁。
|
||||
|
||||
## 5. 范围与发布约束
|
||||
|
||||
- 仅本地候选,不代表 V27 已发行。
|
||||
- 不接受关闭 Retained、全 Continuous 降级或 JS 平台分流作为最终修复。
|
||||
- 若 F 依旧失败,下一轮应进一步检查 Retained Composite 顶点/纹理与 Metal Encoder 状态、Stencil/Depth、主画面 RenderPass,并用证据区分构建与合成副作用。
|
||||
58
docs/报告/V26_Metal混合Segment视觉错误_安全降级调查报告_v1.md
Normal file
58
docs/报告/V26_Metal混合Segment视觉错误_安全降级调查报告_v1.md
Normal file
@@ -0,0 +1,58 @@
|
||||
# V26 Metal 混合 Segment 视觉错误与第一阶段安全降级调查报告 v1
|
||||
|
||||
**已撤回:2026-10-13 用户明确要求必须保留 Retained 实际构建/复用/合成。本文安全降级代码已在 Particle 工作区撤销,禁止按本文编译或发布。请以同主题 v2 修复调查报告为准。**
|
||||
|
||||
日期:2026-10-13
|
||||
状态:**已撤回(REJECTED)——原代码级安全降级方案不可用;保留本文件仅作历史记录**
|
||||
目标仓库:`MapLibreParticleLab`
|
||||
冻结 V26 源码:`fce326a9009d3fc21eb91c5df362f0769163d07e`(SOURCE_LOCK),当前工作分支 `particle/main`,修改前 HEAD `b0f34cc9a299a75af1f7e708c63d75eaa42e9ca2`。
|
||||
|
||||
## 一、iPad 实测事实
|
||||
|
||||
iPad:Pleiades(iPad mini 5);iOS 已安装 V26 Native。实验使用 Windows CARBON7i `D:\React\NavSea` 的 Metro `192.168.200.100:8081`,而非 Mac 本地 Metro。
|
||||
|
||||
- 原始 RC:天气 ON 后本船/AIS/海图闪烁,天气 OFF 后仍异常。
|
||||
- A(关闭 Retained,完整天气):不闪烁。
|
||||
- B(保留 Retained,隐藏 Native 天气):不闪烁;色场与粒子均无显示。
|
||||
- C(只有色场,保留 Retained):色场出现,闪烁。
|
||||
- D(只有粒子,保留 Retained):闪烁。
|
||||
- E(Retained 总开关开、实际 0 Retained + 1 Continuous):不闪、天气色场和粒子完整,本船/AIS 图标尺寸与航线正常。
|
||||
- F(最底层 background 单层 Retained + 其余 114 层 Continuous):冷启动即图标异常放大、本船航行线与 AIS 航线消失;不闪;天气色场与粒子正常。**F 是失败,不得据其不闪而判 PASS**。
|
||||
|
||||
iPad Native 快照原始保存于 Mac:
|
||||
`/tmp/navsea-v26-ipad-render-20261013/E-0746/start.json`、
|
||||
`/tmp/navsea-v26-ipad-render-20261013/F-0740/start.json`;
|
||||
F:`enabled=true`, `valid=true`, `fallback=false`; R0 `[0,1)` 实际 `composite`,C1 `[1,115)` 实际 `live`;
|
||||
E:`enabled=true`, `valid=true`, `fallback=false`; 单 Continuous `[0,115)` 实际 `live`。
|
||||
旧 RCR 实验曾因 JS 预检将最后 Layer 硬编码为 `chartDangerScanCenterLineLayer`,但实际最后 Layer 是 `route-history-dotted-line`,回退到 RC。不要把当时现场当成 RCR Native 执行证明。
|
||||
|
||||
## 二、已核对的 Native 代码路径
|
||||
|
||||
`src/mln/renderer/renderer_impl.cpp` 中,实际含 Retained Segment 的绘制在独立 RenderPass 中构建离屏缓存,调用 `RetainedUnderlayCache::composite`,然后绘制 Continuous 图层。
|
||||
`src/mln/renderer/retained_underlay_cache.cpp` 的缓存创建与合成对应 `prepareResources`、`beginBuild`、`finishBuild`、`composite`。
|
||||
iOS Metal `src/mln/mtl/render_pass.cpp` 已有 Color/Depth/Stencil Clear 逻辑,尚不能证明清屏本身出错。
|
||||
现有证据将异常定位在 **实际执行 Retained 离屏构建/合成和连续图层共存时**,但尚未证明具体 GPU 状态或 shader 错误。
|
||||
|
||||
## 三、第一阶段保护性补丁(尚未发布)
|
||||
|
||||
1. `include/mln/renderer/render_segment.hpp`:新增 `requiresMetalRetainedCompositeSafetyFallback`,针对合法 Plan 内任何 RETAINED Segment + Metal 后端启用安全回退;全 Continuous 和非 Metal 保留原逻辑。
|
||||
2. `src/mln/renderer/renderer_impl.cpp`:Native 检测到上述情形时,禁止 Retained 离屏资源构建、Reuse、合成;释放历史缓存;完整 Style 范围做常规 Live Opaque+Translucent 绘制。阻止 Particle FastFrame 在该组合误准入。Native 诊断 `globalLiveFallbackReason` 记录 `metal-retained-composite-safety`,`actualExecution` 应全部为 `live`。
|
||||
3. `test/renderer/retained_underlay_cache.test.cpp`:加入 OpenGL 不启用、Metal R-C 启用、Metal全Continuous不启用、Metal全Retained启用、无效Plan不启用的安全策略单测。
|
||||
|
||||
**此补丁属于 correctness-first 的暂时降级方案,不是 Retained 合成根因修复,也不提供 Retained GPU 加速。** 禁止在未做真实 Metal 回归前声称问题已关闭。Native 公共 JS API 与输入 Contract 不变;Renderer 层仍以 Style Z-order 为唯一图层顺序。
|
||||
|
||||
## 四、待用户手工验收
|
||||
|
||||
1. 保留冻结 V26 artifact,不覆盖正式发行版本;从当前修改源码建立新的测试版 iOS Metal XCFramework / Test Host,不把未验证产物标成 V26 正式版本。用户手工完成 iOS 构建、安装、启动。
|
||||
2. 用统一 JS 测试配置加载含 Retained Segment 的 RC/F 方案,确认 Native 诊断显示 `global-live`、`metal-retained-composite-safety`,且无 Retained texture build/composite;不能只凭 JS mode badge 判断。
|
||||
3. 冷启动关天气、ON、OFF,检查本船/AIS 图标尺寸、航行线、海图层、天气色场、粒子、图层 Z-order、视觉稳定。
|
||||
4. Android OpenGL 使用完全相同的 JS Plan 对照,确认没有触发 Metal 安全回退;记录 native 版本差异,版本未对齐时不得直接宣称平台差异。
|
||||
5. 记录帧率、CPU、GPU、内存和耗电;补丁性能可能低于 Retained 缓存方案。
|
||||
6. 后续通过 Metal GPU Capture/离屏纹理、Blend、Depth/Stencil 和 Drawables 生命周期调查根因,修复后再移除安全降级,并对 R-C/R-C-R、多 Retained 段复测。
|
||||
|
||||
## 五、执行边界与状态
|
||||
|
||||
- 本轮没有执行 iOS/Android 原生编译、APK/XCFramework 安装、ADB、真机自动操作,也没有发布 tag/制品。
|
||||
- 粒子仓库工作区变更先保持未提交,避免污染冻结 V26。
|
||||
- Windows NavSea Debug JS 的平台分流已移除:Android/iOS 共用 `E_CONTINUOUS_ONLY`,Release 仍为 `BASELINE`。这项改动仍需 Android 用户手工真机核验。
|
||||
- 全部修复最终验收需要跨项目 Integration 按既定 Contract/Status 更新,不由 NavSea 独自宣告 Particle 发布完成。
|
||||
54
docs/报告/V26_统一正式发行_GSRL_RDC完成报告_v1.md
Normal file
54
docs/报告/V26_统一正式发行_GSRL_RDC完成报告_v1.md
Normal file
@@ -0,0 +1,54 @@
|
||||
# V26 统一正式发行 GSRL RDC 完成报告 v1
|
||||
|
||||
日期:2026-10-13 07:38:02 +0900(JST)
|
||||
结论:**GSRL RELEASED / PASS**。仅指来源锁、发行包、公网回读与 Git 发行链闭合;NavSea 产品集成及真机运行不属于本结论。
|
||||
|
||||
## 版本及源码身份
|
||||
|
||||
- Android OpenGL AAR、React Native:`13.6.1-particle.26`;iOS Metal/SwiftPM:`6.29.0-particle.26`。
|
||||
- Git Remote:`https://git.eyaku.jp/tei/MapLibreParticleLab`;正式源码分支 `particle/main`。
|
||||
- **冻结实际 Build Source Commit**:`fce326a9009d3fc21eb91c5df362f0769163d07e`(Android 4 ABI、iOS Metal、RN 同源),已 Push 并验证远端。
|
||||
- 相关修复与文档提交:`343ca2fa68f807a2120948006dc12d052aa96059`,`fix: secure Symbol Retained drawable lifetime and record investigations`,包含用户要求保留的 STEP-110/111 两份任务书。
|
||||
- Prepare Commit:`fce326a9009d3fc21eb91c5df362f0769163d07e`,`release: prepare unified particle.26`。
|
||||
- GSRL Source Lock Commit:`21c4450e01c3d5b8be3743867a26374dce3c8cf3`,`release: record v26 GSRL source lock and receipt instructions`,Push PASS。
|
||||
- **正式 Finalize / Tag Commit**:`e3cd9eba3cd02149d431e0061b8d32192d876f78`,`release: finalize unified particle.26`。
|
||||
- Tag:`maplibre-particle/rn/13.6.1-particle.26`,已 Push,远端 annotated tag peeled SHA = `e3cd9eba3cd02149d431e0061b8d32192d876f78`;Final HEAD/远端 `particle/main` 在 Finalize 完成时均 = `e3cd9eba3cd02149d431e0061b8d32192d876f78`。
|
||||
- SwiftPM Tag:`6.29.0-particle.26`,已通过远端 `ls-remote` 验证。
|
||||
- 本次独立验收结束时 Git 工作区干净,未创建 Worktree,未强制覆盖/移动任何正式 Tag 或历史版本。
|
||||
|
||||
## 严格串行执行、日志及验收
|
||||
|
||||
1. Integration `main` = `80667d6c343411d03a760425438e3d292f452118`,与远端 `main` 一致;V26 公网发行位点原本未占用。
|
||||
2. 将 4 个 Symbol Retained 修改源码/测试、`粒子开发记录.md`、STEP-119 两份报告与 STEP-110/111 两份历史任务书 **9 个文件全部 Commit/Push**,Git 工作区恢复干净。冻结源码 V26 版本号并 Push。
|
||||
3. `release.sh 26 build` 返回 **exit 0**;Android OpenGL 四 ABI 分别完成 Release 编译,Maven Local AAR/POM/Module PASS;iOS Bazel opt Metal arm64 真机 XCFramework PASS(无 Simulator);RN npm pack PASS。
|
||||
- 构建日志:`/tmp/maplibre_v26_unified_build_20261012.log`。
|
||||
4. `release.sh 26 publish` 返回 **exit 0**;NAS SSH 临时路径上传、远端 SHA-256 核对、原子升级、六文件公网逐字节回读、SwiftPM 版本 Tag 全部 PASS。
|
||||
- 日志:`/tmp/maplibre_v26_publish_20261013.log`。
|
||||
5. 按 GSRL v1 生成独立 `release/maplibre-particle/13.6.1-particle.26/SOURCE_LOCK.json`、中文接收说明和 `release/README.md` 当前推荐入口;GSRL Commit/Push 与远端 HEAD 验证通过。
|
||||
6. `release.sh 26 finalize` 返回 **exit 0**:4 项 Manifest/iOS 发布元信息 Commit、Git Tag Push PASS。日志:`/tmp/maplibre_v26_finalize_20261013.log`。
|
||||
7. **独立二次验收**:公网 6 个文件单独重新下载,逐项 SHA-256/大小与 Source Lock 和本地构建物一致,结果 **6/6 PASS**;Android AAR 确认 4 个 `jni/<ABI>/libmaplibre.so`,iOS ZIP 仅 `ios-arm64`,RN tgz version/peer 正确,Git Remote HEAD/Tag/source 关系一致,工作区干净。
|
||||
- 机器可读独立核验文件:`/tmp/maplibre_v26_independent_receipt_20261013.json`。
|
||||
|
||||
## 正式文件验收表(重新计算的公网 SHA-256)
|
||||
|
||||
| 交付文件 | 字节数 | SHA-256 | 公网回读 |
|
||||
| --- | ---: | --- | --- |
|
||||
| android-sdk-particle-opengl-13.6.1-particle.26.aar | 16640537 | `46c1cf7ff8ebc3bedc25aa38d7d445078a2301e6ca223c0307b93e830991c2b9` | PASS |
|
||||
| android-sdk-particle-opengl-13.6.1-particle.26.pom | 5687 | `b1e34295c58238a45a8b14d33c801028c3cabc0f40aae6116d007b352fa25520` | PASS |
|
||||
| android-sdk-particle-opengl-13.6.1-particle.26.module | 6999 | `405b3bb3732ef29a0b54e6b24b192839d07d4c28e959276d6bd92051637011c4` | PASS |
|
||||
| navsea-maplibre-particle-rn-13.6.1-particle.26.tgz | 29038 | `3136316bb6508e774748b21c2095ab61b522d2cf4c3ff2e8c67774dde8549b93` | PASS |
|
||||
| MapLibre.dynamic.xcframework.zip | 4181892 | `64b51c2b9eaec4adefcfb2dfc1c6338943f11db4d6c4ce2a4e1c2a39bb84bddb` | PASS |
|
||||
| MapLibre.dynamic.xcframework.zip.sha256 | 99 | `ae79556c2b288a3970d6e4eba0c30a68023faa79ff55de291c9e2f30f16fd9ae` | PASS |
|
||||
|
||||
下载 URL、依赖和安装说明以 `release/maplibre-particle/13.6.1-particle.26/README.md` 和 `SOURCE_LOCK.json` 为准。正式版本不在 Git 存放大二进制,以 NAS PBF/Maven/npm/SwiftPM 公开发行仓库为获取入口。
|
||||
|
||||
## 明确未验收项
|
||||
|
||||
- 本次**没有把 V26 正式 Android AAR 安装到 NavSea/FZ-G1 进行产品集成验收**,也没有对 V26 正式 iOS Metal XCFramework 执行 iPhone/iPad 实机运行测试;两者均为 **NOT_TESTED**。
|
||||
- STEP-119 原 FZ-G1 MapLibre Test App 开发态四轮稳定和 CPU/FastPath 测试,仅说明独立引擎测试表现,**不是 V26 正式二进制/NavSea 运行验收**。
|
||||
- 13 项新增 C++ 生命周期 GTest 未运行,Mac CMake 环境缺 `libuv`,状态 **BLOCKED / NOT_TESTED**;不得将静态检查或 Android/Metal 正式编译视作 GTest 通过。
|
||||
- Context 重建、完整 Atlas/Style/Source 变更压力、像素完全等价、跨平台真机及 RN Host 行为,不在本次 GSRL 身份/制品闭环内,不声明通过。
|
||||
|
||||
## 收尾注意
|
||||
|
||||
该报告属于 **Tag 完成后的审计记录追加**。记录进入 `particle/main` 的后续文档 Commit,不移动或覆盖已经完成的 V26 正式 Tag `maplibre-particle/rn/13.6.1-particle.26`;Tag 仍固定指向 `e3cd9eba3cd02149d431e0061b8d32192d876f78`。发行 Source Lock 永久记录实际构建源码 `fce326a9009d3fc21eb91c5df362f0769163d07e`,不能用之后的分支 HEAD 替换。
|
||||
72
docs/报告/V27_统一正式发行_GSRL_RDC完成报告_v1.md
Normal file
72
docs/报告/V27_统一正式发行_GSRL_RDC完成报告_v1.md
Normal file
@@ -0,0 +1,72 @@
|
||||
# V27 统一正式发行 GSRL RDC 完成报告 v1
|
||||
|
||||
日期:2026-10-14(JST)
|
||||
结论:**GSRL RELEASED / PASS**。该结论限于 Git Source Lock、正式发行文件、依赖与公网取回一致性,不等同于 NavSea 真机产品验收。
|
||||
|
||||
## 一、发行范围与版本
|
||||
|
||||
- 项目:MapLibreParticleLab;Git Remote:https://git.eyaku.jp/tei/MapLibreParticleLab
|
||||
- 当前仓库:Mac.lan /Users/tei/dev/MapLibreParticleLab,分支 particle/main。
|
||||
- Android OpenGL AAR / React Native:13.6.1-particle.27。
|
||||
- iOS Metal / SwiftPM:6.29.0-particle.27,仅 iOS arm64 真机,不含 Simulator。
|
||||
- 冻结的实际共用构建源码 Commit:970e3ad606e59873a32e9389e65ef257e1c259bb。
|
||||
- 正式 Git Tag:maplibre-particle/rn/13.6.1-particle.27。
|
||||
- Tag 直接目标(peeled commit):3c3dcb9d1856c41e9e50734aa12bda0b6f71e294。
|
||||
- Annotated Tag 对象:ee09f254fd3c42c8be999ce1bd39aed6477d08a2。
|
||||
- 公网 Maven:https://pbf.jpcn.ccwu.cc/maven
|
||||
- 公网 RN:https://pbf.jpcn.ccwu.cc/npm
|
||||
- 公网 iOS:https://pbf.jpcn.ccwu.cc/swift/maplibre-particle
|
||||
- SwiftPM 仓库:https://pbf.jpcn.ccwu.cc/swift/maplibre-particle-spm.git
|
||||
|
||||
V27 源码变化重点为 Metal Retained Underlay Composite 顶点缓冲绑定槽位修正为 maxUBOCountPerShader + 0。三份 V26 失败候选调查报告作为历史证据保留;此前失败候选不能被认作本次正式修复。NavSea 当前采用 RCR 运行基线,仍保留 Retained;不将 E 全 Continuous 降级作为 V27 最终方案。
|
||||
|
||||
## 二、环境与发行前保护
|
||||
|
||||
- Integration 仓库 /Users/tei/dev/navsea-particle-integration 已 fetch 核验:main 与 origin/main 均为 80667d6c343411d03a760425438e3d292f452118,差异 0/0。
|
||||
- GSRL 正式规范采用 v1.0.0;Win11 D:\React\GSRL 核验 HEAD 8a34221da415b3a880969e22e3b768d705a28944。
|
||||
- 开始时 Particle particle/main 为 b0f34cc9a299a75af1f7e708c63d75eaa42e9ca2,与 origin/particle/main 一致。存在一项未提交的 Metal Shader 变更和三份未跟踪历史报告。
|
||||
- 只将本次 Metal 修复和三份历史报告纳入源代码提交;未删除无关修改,未新建或切换 Git 分支、Worktree,未 Force Push。
|
||||
- 发行前 V27 Git Tag 不存在,三个主要公网地址均为 HTTP 404;没有覆盖既有 V26 文件或 Tag。
|
||||
- 本次全部实际设备操作由 RDC 直连 Mac 执行,没有调用 Codex。
|
||||
|
||||
## 三、严格串行执行结果
|
||||
|
||||
1. 前置 Git 修复提交:a9cea8b581f71bf8aab8b85a73a258017d69a33e,fix: retain correct Metal composite vertex buffer slot;包括 Shader 与三份历史报告,Commit/Push/Remote Receipt PASS。
|
||||
2. V27 Prepare:release.sh 27 prepare,exit 0;生成版本配置并提交 970e3ad606e59873a32e9389e65ef257e1c259bb,release: prepare unified particle.27。该提交就是后续发行文件统一使用的实际构建源码,已 Push 并核对远端。
|
||||
3. V27 Build:release.sh 27 build,exit 0;Android OpenGL 四 ABI Gradle Release 与 Maven Local AAR/POM/Module PASS,Bazel 8.6.0 opt Metal XCFramework PASS,RN npm pack PASS。完整日志:/tmp/maplibre_v27_unified_build_20261014.log。Bazel 报告 INFO Build completed successfully,ZIP 为 ios-arm64 真机版;Android 编译日志确认四种 libmaplibre.so。
|
||||
4. V27 Publish:release.sh 27 publish,exit 0;NAS SSH 暂存上传、哈希对照及路径原子晋升,AAR/POM/Module/RN tgz/iOS ZIP/iOS ZIP.sha256 六文件公网逐字节回读 PASS;SwiftPM 6.29.0-particle.27 Tag PASS。日志:/tmp/maplibre_v27_publish_20261014.log。
|
||||
5. GSRL Source Lock:新建 release/maplibre-particle/13.6.1-particle.27/SOURCE_LOCK.json 及 README.md,并在 release/README.md 登记当前推荐版。Source Lock Commit:7f92f71416fd57dcd55c5c836365385049a6450a,release: record v27 GSRL source lock and receipt instructions;Commit/Push/Remote Receipt PASS。
|
||||
6. Finalize:release.sh 27 finalize,exit 0。四份通用/iOS Manifest 与 Package.swift 模板 Commit:3c3dcb9d1856c41e9e50734aa12bda0b6f71e294,release: finalize unified particle.27。推送 particle/main 及新的 annotated Git Tag,远端 peeled Tag 与 Final Commit 一致。日志:/tmp/maplibre_v27_finalize_20261014.log。
|
||||
7. 独立二次核验:不是依靠发布脚本内部的回读结论,而是重新下载六项公网文件,按 Source Lock 单独计算 SHA-256 和文件大小,6/6 PASS;AAR 四 ABI、iOS ZIP 无 Simulator、RN package version/peer、SwiftPM Tag、源码 Commit 祖先关系、远端 Tag/HEAD 及 Git clean 均 PASS。机器可读回执:/tmp/maplibre_v27_independent_receipt_20261014.json。
|
||||
8. 本报告属于发行 Tag 完成后的追加审计记录。后续提交本报告不移动既有 V27 Tag,也不更改 Source Lock 指向的实际构建源码 Commit。
|
||||
|
||||
## 四、六项正式文件独立校验
|
||||
|
||||
| 文件 | 大小(字节) | SHA-256 | 公网独立下载 |
|
||||
| --- | ---: | --- | --- |
|
||||
| android-sdk-particle-opengl-13.6.1-particle.27.aar | 16640539 | 2ff3ead8e5d7cb8802a4c05ba20cd7f8f0a7029f72619f9a2ec176c942ae8de4 | PASS |
|
||||
| android-sdk-particle-opengl-13.6.1-particle.27.pom | 5687 | 324d14d689ba7c9206298734d7f9636b95e947c7241d94ec60f8f1d57bd1b701 | PASS |
|
||||
| android-sdk-particle-opengl-13.6.1-particle.27.module | 6999 | 403804395f065cef75f1254403edbda67c3a885769acbf6d25e091d37c325910 | PASS |
|
||||
| navsea-maplibre-particle-rn-13.6.1-particle.27.tgz | 29040 | fdd67210debd8363d4bf72fcc0120ef747dc3037d55b8c0d83330dd98bb0bc5d | PASS |
|
||||
| MapLibre.dynamic.xcframework.zip | 4181894 | 5dbde28600df35484a7f31e7c7c4780f27b919fc6ec59f6aa88d65d81342448c | PASS |
|
||||
| MapLibre.dynamic.xcframework.zip.sha256 | 99 | 1dba0b0a2831beeadcc022801622829d630ef0838babdfef4edc4b8bec34e08d | PASS |
|
||||
|
||||
六项文件的完整实际下载 URL、依赖版本和使用方法由本版 SOURCE_LOCK.json / README.md 权威记录。
|
||||
|
||||
## 五、未测试事项、风险及下一步
|
||||
|
||||
- 本次未在 Android/FZ-G1 安装运行 V27 正式发行 AAR,也未通过正式 NavSea Host 真机验收;先前开发态 Test App 验收不能代替正式发行包验收。
|
||||
- 本次未在 iPad/iPhone 安装运行 V27 正式发行 XCFramework;用户此前确认的是 Metal slot-9 预发行调试候选画面,不应虚报正式制品真机 PASS。
|
||||
- 未执行新增或历史 C++ 生命周期 GTest;不以构建 PASS 冒充 GTest PASS。
|
||||
- RCR 是当前 Host 基线;RCRR 仍须按其单独测试范围区分实验能力和产品正式验收。
|
||||
- GSRL PASS 表明源码可恢复、产物可取得且 SHA-256 相符,不能推断与所有 NavSea Host 场景兼容或端到端运行稳定。
|
||||
- 后续正式接入应从 release/README.md 选择 V27,接收方复核 SOURCE_LOCK.json 的六项哈希,并依据各平台操作边界完成真机验收。
|
||||
|
||||
## 六、Git 与 Source Lock 最终原则
|
||||
|
||||
冻结 Build Source Commit:970e3ad606e59873a32e9389e65ef257e1c259bb。
|
||||
Source Lock Commit:7f92f71416fd57dcd55c5c836365385049a6450a。
|
||||
正式 Finalize / Tag Commit:3c3dcb9d1856c41e9e50734aa12bda0b6f71e294。
|
||||
正式 Tag:maplibre-particle/rn/13.6.1-particle.27。
|
||||
|
||||
上述源码、发行文件和 Tag 通过远端与公网独立核验。发行 Tag 不会因本报告后续追加提交而移动。
|
||||
@@ -6,7 +6,7 @@ const marker = "// MapLibreParticleRNTestHost NavSea Particle release 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.maven { repository -> repository.url = p.uri("https://pbf.jpcn.ccwu.cc/maven") }\n p.configurations.configureEach { configuration ->\n configuration.resolutionStrategy.dependencySubstitution {\n substitute module("org.maplibre.gl:android-sdk-opengl") using module("cc.navsea.maplibre:android-sdk-particle-opengl:13.6.1-particle.25")\n }\n }\n }\n}\nrootProject.ext.set("org.maplibre.reactnative.nativeVersion", "13.6.1")\nrootProject.ext.set("org.maplibre.reactnative.nativeVariant", "opengl")\n`;
|
||||
project.modResults.contents += `\n${marker}\ngradle.projectsEvaluated {\n allprojects { p ->\n p.repositories.maven { repository -> repository.url = p.uri("https://pbf.jpcn.ccwu.cc/maven") }\n p.configurations.configureEach { configuration ->\n configuration.resolutionStrategy.dependencySubstitution {\n substitute module("org.maplibre.gl:android-sdk-opengl") using module("cc.navsea.maplibre:android-sdk-particle-opengl:13.6.1-particle.28")\n }\n }\n }\n}\nrootProject.ext.set("org.maplibre.reactnative.nativeVersion", "13.6.1")\nrootProject.ext.set("org.maplibre.reactnative.nativeVariant", "opengl")\n`;
|
||||
return project;
|
||||
});
|
||||
};
|
||||
|
||||
@@ -2,6 +2,15 @@
|
||||
|
||||
## Unreleased
|
||||
|
||||
## 13.6.1-particle.28(Android / iOS 统一发行)
|
||||
|
||||
- Mac unified release pipeline 统一生成 Android Native、RN package 与 iOS SwiftPM/XCFramework;共享 Particle revision 28。
|
||||
## 13.6.1-particle.27(Android / iOS 统一发行)
|
||||
|
||||
- Mac unified release pipeline 统一生成 Android Native、RN package 与 iOS SwiftPM/XCFramework;共享 Particle revision 27。
|
||||
## 13.6.1-particle.26(Android / iOS 统一发行)
|
||||
|
||||
- Mac unified release pipeline 统一生成 Android Native、RN package 与 iOS SwiftPM/XCFramework;共享 Particle revision 26。
|
||||
## 13.6.1-particle.25(Android / iOS 统一发行)
|
||||
|
||||
- Mac unified release pipeline 统一生成 Android Native、RN package 与 iOS SwiftPM/XCFramework;共享 Particle revision 25。
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# @navsea/maplibre-particle-rn
|
||||
|
||||
当前版本:`13.6.1-particle.25`。Android Native 必须配套 `cc.navsea.maplibre:android-sdk-particle-opengl:13.6.1-particle.25`。本包是 React Native 的薄控制面,不负责 Map 组件、provider、HTTP、manifest、下载、缓存或 NavSea 业务策略。
|
||||
当前版本:`13.6.1-particle.28`。Android Native 必须配套 `cc.navsea.maplibre:android-sdk-particle-opengl:13.6.1-particle.28`。本包是 React Native 的薄控制面,不负责 Map 组件、provider、HTTP、manifest、下载、缓存或 NavSea 业务策略。
|
||||
|
||||
## 当前推荐主链:QBIN V4
|
||||
|
||||
@@ -37,7 +37,7 @@ Podfile 的 `post_install do |installer|` 必须同时调用 `$MLRN.post_install
|
||||
RN tgz:
|
||||
|
||||
```text
|
||||
https://pbf.jpcn.ccwu.cc/npm/navsea-maplibre-particle-rn-13.6.1-particle.25.tgz
|
||||
https://pbf.jpcn.ccwu.cc/npm/navsea-maplibre-particle-rn-13.6.1-particle.28.tgz
|
||||
```
|
||||
|
||||
Native Maven repository:
|
||||
@@ -49,7 +49,7 @@ https://pbf.jpcn.ccwu.cc/maven
|
||||
Native coordinate:
|
||||
|
||||
```text
|
||||
cc.navsea.maplibre:android-sdk-particle-opengl:13.6.1-particle.25
|
||||
cc.navsea.maplibre:android-sdk-particle-opengl:13.6.1-particle.28
|
||||
```
|
||||
|
||||
Expo plugin 与 `android/build.gradle` 必须指向同一 `.25`。正常 Consumer 不复制 AAR、不依赖 MapLibreParticleLab 源码路径,也不以 Maven Local 作为公网验证主路径。完整 Linux 发布与 Win11 验证流程见 `docs/50_Step记录/STEP-080_Android_AAR与RNTestHost标准构建流水线_v2.md`。
|
||||
Expo plugin 与 `android/build.gradle` 必须指向同一 `.28`。正常 Consumer 不复制 AAR、不依赖 MapLibreParticleLab 源码路径,也不以 Maven Local 作为公网验证主路径。完整 Linux 发布与 Win11 验证流程见 `docs/50_Step记录/STEP-080_Android_AAR与RNTestHost标准构建流水线_v2.md`。
|
||||
|
||||
@@ -14,7 +14,7 @@ android {
|
||||
dependencies {
|
||||
implementation "com.facebook.react:react-android"
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib:2.1.20"
|
||||
implementation "cc.navsea.maplibre:android-sdk-particle-opengl:13.6.1-particle.25"
|
||||
implementation "cc.navsea.maplibre:android-sdk-particle-opengl:13.6.1-particle.28"
|
||||
compileOnly project(":maplibre_maplibre-react-native")
|
||||
testImplementation "junit:junit:4.13.2"
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ const iosTag = "navsea-maplibre-particle-rn-maplibre-spm";
|
||||
module.exports = function withNavSeaMapLibreParticle(config) {
|
||||
config = withProjectBuildGradle(config, (project) => {
|
||||
if (project.modResults.contents.includes(androidMarker)) return project;
|
||||
project.modResults.contents += `\n${androidMarker}\ngradle.projectsEvaluated {\n allprojects { p ->\n p.repositories.maven { repository -> repository.url = p.uri("https://pbf.jpcn.ccwu.cc/maven") }\n p.configurations.configureEach { configuration ->\n configuration.resolutionStrategy.dependencySubstitution {\n substitute module("org.maplibre.gl:android-sdk-opengl") using module("cc.navsea.maplibre:android-sdk-particle-opengl:13.6.1-particle.25")\n }\n }\n }\n}\nrootProject.ext.set("org.maplibre.reactnative.nativeVersion", "13.6.1")\nrootProject.ext.set("org.maplibre.reactnative.nativeVariant", "opengl")\n`;
|
||||
project.modResults.contents += `\n${androidMarker}\ngradle.projectsEvaluated {\n allprojects { p ->\n p.repositories.maven { repository -> repository.url = p.uri("https://pbf.jpcn.ccwu.cc/maven") }\n p.configurations.configureEach { configuration ->\n configuration.resolutionStrategy.dependencySubstitution {\n substitute module("org.maplibre.gl:android-sdk-opengl") using module("cc.navsea.maplibre:android-sdk-particle-opengl:13.6.1-particle.28")\n }\n }\n }\n}\nrootProject.ext.set("org.maplibre.reactnative.nativeVersion", "13.6.1")\nrootProject.ext.set("org.maplibre.reactnative.nativeVariant", "opengl")\n`;
|
||||
return project;
|
||||
});
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@navsea/maplibre-particle-rn",
|
||||
"version": "13.6.1-particle.25",
|
||||
"version": "13.6.1-particle.28",
|
||||
"description": "NavSea MapLibre Particle React Native pure Field Renderer API.",
|
||||
"license": "BSD-2-Clause",
|
||||
"main": "src/index.ts",
|
||||
|
||||
@@ -1 +1 @@
|
||||
13.6.1-particle.25
|
||||
13.6.1-particle.28
|
||||
|
||||
@@ -5,6 +5,23 @@
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/** 原始 Filter 接口的错误域。 */
|
||||
FOUNDATION_EXTERN MLN_EXPORT NSErrorDomain const MLNStyleFilterErrorDomain;
|
||||
|
||||
/** 原始 Filter 接口的稳定错误代码;不影响既有 predicate 接口。 */
|
||||
typedef NS_ENUM(NSInteger, MLNStyleFilterErrorCode) {
|
||||
/** E_FILTER_UNSUPPORTED:输入超限、不支持或无法证明无损重放;未写入。 */
|
||||
MLNStyleFilterErrorUnsupported = 1,
|
||||
/** E_FILTER_INVALID_LAYER:rawLayer 已失效或不是支持的 Vector Layer。 */
|
||||
MLNStyleFilterErrorInvalidLayer = 2,
|
||||
/** E_FILTER_WRONG_THREAD:必须在主线程调用。 */
|
||||
MLNStyleFilterErrorWrongThread = 3,
|
||||
/** E_FILTER_MUTATION_FAILED:写入/后验检查失败,已验证原 Filter 恢复。 */
|
||||
MLNStyleFilterErrorMutationFailed = 4,
|
||||
/** E_FILTER_ROLLBACK_FAILED:无法确认恢复;Host 必须废弃当前操作 Context。 */
|
||||
MLNStyleFilterErrorRollbackFailed = 5,
|
||||
};
|
||||
|
||||
/**
|
||||
``MLNVectorStyleLayer`` is an abstract superclass for style layers whose content
|
||||
is defined by an ``MLNShapeSource`` or ``MLNShapeSource`` object.
|
||||
@@ -53,6 +70,43 @@ MLN_EXPORT
|
||||
*/
|
||||
@property (nonatomic, nullable) NSPredicate *predicate;
|
||||
|
||||
/**
|
||||
读取当前有效 rawLayer 的 Filter JSON 快照;不经过 NSPredicate 或初始化 Style JSON。
|
||||
空 Filter 返回 NSNull;失败返回 nil,并通过 error 报告原因。
|
||||
|
||||
仅返回经现有 Foundation/Core 转换器往返验证的子集:JSON 最大深度 64(根为 1),
|
||||
最多 4096 个节点(含对象键),字符串和键的 UTF-8 总量最多 256 KiB。
|
||||
字符串/键含 U+0000 或非法 UTF-8、非有限数值、负零、绝对值大于 2^53-1 的数值、
|
||||
非 JSON Core 值以及不可重放的 Filter 均报 MLNStyleFilterErrorUnsupported。
|
||||
结构比较保留数组顺序、对象键和值、布尔/数值区别;仅精确可表示的数值存储类型可归一。
|
||||
|
||||
常用 all/any/!/比较/get/has/in/literal、类型断言和 collator 可以进入验证;
|
||||
其他操作符、混合 legacy/表达式 Filter 或发生折叠/默认值补全而无法结构往返者可能拒绝。
|
||||
返回对象与 Layer 状态分离,调用方不得在调用期间并发修改输入或 Style。
|
||||
|
||||
必须在主线程调用。本接口不证明 Layer 属于当前 Style:未加入或正常移除后仍存活的
|
||||
Layer 可以使用。Host 必须另行核验当前 Style 按 ID 查得的对象身份、Token/代次等;
|
||||
rawLayer 已销毁则失败。该接口不管理 Host Context 或 revision。
|
||||
*/
|
||||
- (nullable id)rawStyleFilterJSONObjectWithError:(NSError * _Nullable * _Nullable)error;
|
||||
|
||||
/**
|
||||
只设置当前 rawLayer 的 Filter。输入为 Filter 数组或 NSNull(精确恢复空 Filter);
|
||||
nil 不作为清空指令。NSDecimalNumber 及不支持的 Foundation 类型也会被拒绝。
|
||||
|
||||
在写入前对输入建立独立快照、验证 Core 解析/序列化不丢失结构,并证明新旧 Filter
|
||||
可以经上述 getter 的同一路径重放。任何准入失败都返回 NO,原 Layer 不变。
|
||||
输入必须已是 Core 可往返的形式;显式 literal 标量、常量折叠或省略 collator 默认项
|
||||
等即使语义可能等价,也可被保守拒绝。具体限制与 rawStyleFilterJSONObjectWithError: 一致。
|
||||
|
||||
写入后重新检查实际 Filter;若不符或写入抛出异常则尝试恢复原 Filter。
|
||||
MLNStyleFilterErrorMutationFailed 表示恢复已核实,但宿主仍应重新获取状态;
|
||||
MLNStyleFilterErrorRollbackFailed 表示状态无法确认,宿主必须废弃当前操作 Context。
|
||||
成功仅代表本次 Filter 操作,不能替代 Host 的 Style 成员关系/外部修改检测。
|
||||
不修改 Paint/Layout/Source/层序,也不移除 Layer 或重载 Style。
|
||||
*/
|
||||
- (BOOL)setRawStyleFilterJSONObject:(id)json error:(NSError * _Nullable * _Nullable)error;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -1,8 +1,382 @@
|
||||
#import "MLNVectorStyleLayer.h"
|
||||
#import "MLNStyleLayer_Private.h"
|
||||
|
||||
#import <CoreFoundation/CoreFoundation.h>
|
||||
|
||||
#include <mln/style/conversion/filter.hpp>
|
||||
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
|
||||
NSErrorDomain const MLNStyleFilterErrorDomain = @"MLNStyleFilterErrorDomain";
|
||||
|
||||
namespace {
|
||||
using Filter = mln::style::Filter;
|
||||
using Value = mln::Value;
|
||||
using Array = std::vector<Value>;
|
||||
using Object = std::unordered_map<std::string, Value>;
|
||||
|
||||
constexpr std::size_t MLNFilterMaxDepth = 64;
|
||||
constexpr std::size_t MLNFilterMaxNodes = 4096;
|
||||
constexpr std::size_t MLNFilterMaxStringBytes = 256 * 1024;
|
||||
constexpr int64_t MLNFilterMaxInteger = 9007199254740991LL;
|
||||
|
||||
// 对象键也占一个节点;循环 Foundation 容器在触及深度或节点上限时拒绝。
|
||||
struct FilterBudget {
|
||||
std::size_t nodes = 0;
|
||||
std::size_t stringBytes = 0;
|
||||
|
||||
bool enter(std::size_t depth) {
|
||||
return depth <= MLNFilterMaxDepth && ++nodes <= MLNFilterMaxNodes;
|
||||
}
|
||||
bool string(const std::string &text) {
|
||||
if (text.size() > MLNFilterMaxStringBytes - stringBytes ||
|
||||
text.find('\0') != std::string::npos) return false;
|
||||
if (![[NSString alloc] initWithBytes:text.data()
|
||||
length:text.size()
|
||||
encoding:NSUTF8StringEncoding]) return false;
|
||||
stringBytes += text.size();
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
std::optional<Value> FilterNumber(double number) {
|
||||
if (!std::isfinite(number) || std::abs(number) > MLNFilterMaxInteger ||
|
||||
(number == 0 && std::signbit(number))) return std::nullopt;
|
||||
return Value(number);
|
||||
}
|
||||
|
||||
// 只归一精确可表示的数值;不使用 JSON 文本比较、NSPredicate 或 Filter 的指针相等。
|
||||
std::optional<Value> NormalizeFilterValue(const Value &value, FilterBudget &budget,
|
||||
std::size_t depth = 1) {
|
||||
if (!budget.enter(depth)) return std::nullopt;
|
||||
return value.match(
|
||||
[](mln::NullValue) -> std::optional<Value> { return Value(mln::NullValue()); },
|
||||
[](bool v) -> std::optional<Value> { return Value(v); },
|
||||
[](int64_t v) -> std::optional<Value> {
|
||||
if (v < -MLNFilterMaxInteger || v > MLNFilterMaxInteger) return std::nullopt;
|
||||
return FilterNumber(static_cast<double>(v));
|
||||
},
|
||||
[](uint64_t v) -> std::optional<Value> {
|
||||
if (v > static_cast<uint64_t>(MLNFilterMaxInteger)) return std::nullopt;
|
||||
return FilterNumber(static_cast<double>(v));
|
||||
},
|
||||
[](float v) { return FilterNumber(v); }, [](double v) { return FilterNumber(v); },
|
||||
[&](const std::string &v) -> std::optional<Value> {
|
||||
return budget.string(v) ? std::optional<Value>(Value(v)) : std::nullopt;
|
||||
},
|
||||
[&](const Array &items) -> std::optional<Value> {
|
||||
if (items.size() > MLNFilterMaxNodes - budget.nodes) return std::nullopt;
|
||||
Array result;
|
||||
for (const auto &item : items) {
|
||||
auto child = NormalizeFilterValue(item, budget, depth + 1);
|
||||
if (!child) return std::nullopt;
|
||||
result.push_back(std::move(*child));
|
||||
}
|
||||
return Value(std::move(result));
|
||||
},
|
||||
[&](const Object &items) -> std::optional<Value> {
|
||||
if (items.size() > (MLNFilterMaxNodes - budget.nodes) / 2) return std::nullopt;
|
||||
Object result;
|
||||
for (const auto &item : items) {
|
||||
if (!budget.enter(depth + 1) || !budget.string(item.first)) return std::nullopt;
|
||||
auto child = NormalizeFilterValue(item.second, budget, depth + 1);
|
||||
if (!child) return std::nullopt;
|
||||
result.emplace(item.first, std::move(*child));
|
||||
}
|
||||
return Value(std::move(result));
|
||||
},
|
||||
[](const auto &) -> std::optional<Value> { return std::nullopt; });
|
||||
}
|
||||
|
||||
std::optional<Value> NormalizeFilterValue(const Value &value) {
|
||||
FilterBudget budget;
|
||||
return NormalizeFilterValue(value, budget);
|
||||
}
|
||||
|
||||
std::optional<Value> SnapshotFilterJSON(id json, FilterBudget &budget, std::size_t depth = 1) {
|
||||
if (!json || !budget.enter(depth)) return std::nullopt;
|
||||
if (json == [NSNull null]) return Value(mln::NullValue());
|
||||
if ([json isKindOfClass:[NSString class]]) {
|
||||
NSString *text = json;
|
||||
if (text.length > MLNFilterMaxStringBytes - budget.stringBytes) return std::nullopt;
|
||||
NSData *data = [text dataUsingEncoding:NSUTF8StringEncoding allowLossyConversion:NO];
|
||||
if (!data || data.length > MLNFilterMaxStringBytes - budget.stringBytes) return std::nullopt;
|
||||
std::string value(data.length ? static_cast<const char *>(data.bytes) : "", data.length);
|
||||
return budget.string(value) ? std::optional<Value>(Value(std::move(value))) : std::nullopt;
|
||||
}
|
||||
if ([json isKindOfClass:[NSNumber class]]) {
|
||||
if ([json isKindOfClass:[NSDecimalNumber class]]) return std::nullopt;
|
||||
if (CFGetTypeID((__bridge CFTypeRef)json) == CFBooleanGetTypeID())
|
||||
return Value([json boolValue] != NO);
|
||||
const char *type = [json objCType];
|
||||
if (!type || !type[0] || type[1] || !std::strchr("cCsSiIlLqQfdB", type[0]))
|
||||
return std::nullopt;
|
||||
return FilterNumber([json doubleValue]);
|
||||
}
|
||||
if ([json isKindOfClass:[NSArray class]]) {
|
||||
NSArray *items = json;
|
||||
if (items.count > MLNFilterMaxNodes - budget.nodes) return std::nullopt;
|
||||
Array result;
|
||||
for (id item in items) {
|
||||
auto child = SnapshotFilterJSON(item, budget, depth + 1);
|
||||
if (!child) return std::nullopt;
|
||||
result.push_back(std::move(*child));
|
||||
}
|
||||
return Value(std::move(result));
|
||||
}
|
||||
if ([json isKindOfClass:[NSDictionary class]]) {
|
||||
NSDictionary *items = json;
|
||||
if (items.count > (MLNFilterMaxNodes - budget.nodes) / 2) return std::nullopt;
|
||||
Object result;
|
||||
for (id key in items) {
|
||||
if (![key isKindOfClass:[NSString class]]) return std::nullopt;
|
||||
auto name = SnapshotFilterJSON(key, budget, depth + 1);
|
||||
auto child = name ? SnapshotFilterJSON(items[key], budget, depth + 1) : std::nullopt;
|
||||
if (!name || !child) return std::nullopt;
|
||||
if (!result.emplace(name->get<std::string>(), std::move(*child)).second) return std::nullopt;
|
||||
}
|
||||
return Value(std::move(result));
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<Value> SnapshotFilterJSON(id json) {
|
||||
FilterBudget budget;
|
||||
return SnapshotFilterJSON(json, budget);
|
||||
}
|
||||
|
||||
// 有限操作符准入;literal 的载荷是 JSON 数据,不按表达式解释。
|
||||
// 特别在 Core 访问 ["in"] 的缺失参数前拒绝非法形状。
|
||||
bool FilterSyntax(const Value &value) {
|
||||
if (!value.is<Array>()) return !value.is<Object>();
|
||||
const auto &items = value.get<Array>();
|
||||
if (items.empty() || !items[0].is<std::string>()) return false;
|
||||
const auto &op = items[0].get<std::string>();
|
||||
const auto count = items.size();
|
||||
if (op == "literal") return count == 2;
|
||||
if (op == "collator") {
|
||||
if (count != 2 || !items[1].is<Object>()) return false;
|
||||
for (const auto &item : items[1].get<Object>()) if (!FilterSyntax(item.second)) return false;
|
||||
return true;
|
||||
}
|
||||
bool valid = (op == "all" || op == "any" || op == "none") ? count >= 1
|
||||
: op == "!" ? count == 2
|
||||
: (op == "==" || op == "!=" || op == "<" || op == "<=" || op == ">" || op == ">=")
|
||||
? (count == 3 || count == 4)
|
||||
: (op == "get" || op == "has") ? (count == 2 || count == 3)
|
||||
: op == "!has" ? count == 2
|
||||
: (op == "in" || op == "!in") ? count >= 3
|
||||
: (op == "string" || op == "number" || op == "boolean") ? count >= 2
|
||||
: false;
|
||||
if (!valid) return false;
|
||||
for (std::size_t i = 1; i < count; ++i) if (!FilterSyntax(items[i])) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
// 仅接受具有明确参数形状的 legacy Filter,拒绝 Core 宽松分支可能忽略的混合表达式。
|
||||
bool LegacyFilterSyntax(const Value &value) {
|
||||
if (!value.is<Array>()) return false;
|
||||
const auto &items = value.get<Array>();
|
||||
if (items.empty() || !items[0].is<std::string>()) return false;
|
||||
const auto &op = items[0].get<std::string>();
|
||||
if (op == "all" || op == "any" || op == "none") {
|
||||
if (items.size() < 2) return false;
|
||||
for (std::size_t i = 1; i < items.size(); ++i) if (!LegacyFilterSyntax(items[i])) return false;
|
||||
return true;
|
||||
}
|
||||
if (items.size() < 2 || !items[1].is<std::string>()) return false;
|
||||
if (op == "has" || op == "!has") return items.size() == 2;
|
||||
bool valid = (op == "in" || op == "!in") ? items.size() >= 3
|
||||
: (op == "==" || op == "!=" || op == "<" || op == "<=" || op == ">" || op == ">=")
|
||||
&& items.size() == 3;
|
||||
if (!valid) return false;
|
||||
for (std::size_t i = 2; i < items.size(); ++i)
|
||||
if (items[i].is<Array>() || items[i].is<Object>()) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FilterExpressionFits(const Filter &filter) {
|
||||
if (!filter.expression) return true;
|
||||
if (!*filter.expression) return false;
|
||||
std::size_t nodes = 0;
|
||||
const auto visit = [&](const auto &self, const mln::style::expression::Expression &expression,
|
||||
std::size_t depth) -> bool {
|
||||
if (depth > MLNFilterMaxDepth || ++nodes > MLNFilterMaxNodes) return false;
|
||||
bool valid = true;
|
||||
expression.eachChild([&](const auto &child) { if (valid) valid = self(self, child, depth + 1); });
|
||||
return valid;
|
||||
};
|
||||
return visit(visit, **filter.expression, 1);
|
||||
}
|
||||
|
||||
bool SameFilter(const Filter &actual, const Filter &expected, const Value &expectedJSON) {
|
||||
if (!FilterExpressionFits(actual) || !FilterExpressionFits(expected)) return false;
|
||||
auto serialized = NormalizeFilterValue(actual.serialize());
|
||||
if (!serialized || *serialized != expectedJSON ||
|
||||
actual.expression.has_value() != expected.expression.has_value()) return false;
|
||||
if (!actual.expression) return true;
|
||||
auto actualExpression = NormalizeFilterValue((**actual.expression).serialize());
|
||||
auto expectedExpression = NormalizeFilterValue((**expected.expression).serialize());
|
||||
return actualExpression && expectedExpression && *actualExpression == *expectedExpression &&
|
||||
**actual.expression == **expected.expression;
|
||||
}
|
||||
|
||||
std::optional<Filter> ParseFilterJSON(const Value &expected, id json) {
|
||||
if (expected.is<mln::NullValue>()) return Filter();
|
||||
if (!expected.is<Array>() || !FilterSyntax(expected)) return std::nullopt;
|
||||
mln::style::conversion::Error error;
|
||||
auto parsed = mln::style::conversion::convert<Filter>(
|
||||
mln::style::conversion::makeConvertible(json), error);
|
||||
if (!parsed || !FilterExpressionFits(*parsed)) return std::nullopt;
|
||||
auto serialized = NormalizeFilterValue(parsed->serialize());
|
||||
if (!serialized || *serialized != expected || !parsed->expression) return std::nullopt;
|
||||
auto expression = NormalizeFilterValue((**parsed->expression).serialize());
|
||||
if (!expression || (*expression != expected && !LegacyFilterSyntax(expected))) return std::nullopt;
|
||||
return parsed;
|
||||
}
|
||||
|
||||
// 输出真实桥接对象前,先检查 C++ 树,再核对桥接值和 Core 重放的结构与表达式语义。
|
||||
id CheckedFilterJSON(const Filter &filter, Value &expected) {
|
||||
if (!FilterExpressionFits(filter)) return nil;
|
||||
auto normalized = NormalizeFilterValue(filter.serialize());
|
||||
if (!normalized) return nil;
|
||||
id json = MLNJSONObjectFromMBGLValue(*normalized);
|
||||
auto bridged = SnapshotFilterJSON(json);
|
||||
if (!bridged || *bridged != *normalized) return nil;
|
||||
auto replayed = ParseFilterJSON(*normalized, json);
|
||||
if (!replayed || !SameFilter(filter, *replayed, *normalized)) return nil;
|
||||
expected = std::move(*normalized);
|
||||
return json;
|
||||
}
|
||||
|
||||
NSError *FilterError(MLNStyleFilterErrorCode code, NSString *detail) {
|
||||
NSString *identifier = @"E_FILTER_UNSUPPORTED";
|
||||
switch (code) {
|
||||
case MLNStyleFilterErrorUnsupported: identifier = @"E_FILTER_UNSUPPORTED"; break;
|
||||
case MLNStyleFilterErrorInvalidLayer: identifier = @"E_FILTER_INVALID_LAYER"; break;
|
||||
case MLNStyleFilterErrorWrongThread: identifier = @"E_FILTER_WRONG_THREAD"; break;
|
||||
case MLNStyleFilterErrorMutationFailed: identifier = @"E_FILTER_MUTATION_FAILED"; break;
|
||||
case MLNStyleFilterErrorRollbackFailed: identifier = @"E_FILTER_ROLLBACK_FAILED"; break;
|
||||
}
|
||||
return [NSError errorWithDomain:MLNStyleFilterErrorDomain code:code
|
||||
userInfo:@{NSLocalizedDescriptionKey : [NSString stringWithFormat:@"%@: %@", identifier, detail]}];
|
||||
}
|
||||
|
||||
mln::style::Layer *FilterLayer(MLNVectorStyleLayer *receiver, NSError **error) {
|
||||
if (![NSThread isMainThread]) {
|
||||
if (error) *error = FilterError(MLNStyleFilterErrorWrongThread, @"原始 Filter 接口必须在主线程调用。");
|
||||
return nullptr;
|
||||
}
|
||||
auto *layer = receiver.rawLayer;
|
||||
if (!layer || layer->getTypeInfo()->tileKind != mln::style::LayerTypeInfo::TileKind::Geometry ||
|
||||
layer->getTypeInfo()->source != mln::style::LayerTypeInfo::Source::Required) {
|
||||
if (error) *error = FilterError(MLNStyleFilterErrorInvalidLayer, @"rawLayer 已失效或不是 Vector Layer。");
|
||||
return nullptr;
|
||||
}
|
||||
return layer;
|
||||
}
|
||||
|
||||
// 弱指针仅保护后验检查/回滚,不表示 Style 成员关系,也不管理 Host Context。
|
||||
NSError *ApplyFilter(MLNVectorStyleLayer *receiver, const Filter &next, const Value &nextJSON,
|
||||
const Filter &previous, const Value &previousJSON,
|
||||
const mapbox::base::WeakPtr<mln::style::Layer> &weakLayer) {
|
||||
const auto current = [&]() {
|
||||
auto *layer = weakLayer.get();
|
||||
return layer && receiver.rawLayer == layer ? layer : nullptr;
|
||||
};
|
||||
bool attempted = false;
|
||||
try {
|
||||
@try {
|
||||
auto *layer = current();
|
||||
if (!layer) return FilterError(MLNStyleFilterErrorInvalidLayer, @"写入前 rawLayer 已失效或被替换;未写入。");
|
||||
if (!SameFilter(layer->getFilter(), previous, previousJSON))
|
||||
return FilterError(MLNStyleFilterErrorUnsupported, @"验证期间原 Filter 已改变;未写入。");
|
||||
attempted = true;
|
||||
layer->setFilter(next);
|
||||
if (current() && SameFilter(current()->getFilter(), next, nextJSON)) return nil;
|
||||
} @catch (NSException *exception) {
|
||||
(void)exception;
|
||||
// 写入或 observer 抛出异常后,仍进入下面的原状态恢复和独立核对。
|
||||
}
|
||||
} catch (...) {
|
||||
}
|
||||
if (!attempted)
|
||||
return FilterError(MLNStyleFilterErrorUnsupported, @"写入前验证异常;未写入。");
|
||||
bool restored = false;
|
||||
try {
|
||||
@try {
|
||||
if (auto *layer = current()) layer->setFilter(previous);
|
||||
} @catch (NSException *exception) {
|
||||
(void)exception;
|
||||
}
|
||||
} catch (...) {
|
||||
}
|
||||
// observer 可以在原 Filter 已恢复后抛出异常,恢复核对必须独立执行。
|
||||
try {
|
||||
@try {
|
||||
restored = current() && SameFilter(current()->getFilter(), previous, previousJSON);
|
||||
} @catch (NSException *exception) {
|
||||
(void)exception;
|
||||
}
|
||||
} catch (...) {
|
||||
}
|
||||
return restored ? FilterError(MLNStyleFilterErrorMutationFailed, @"写入或后验检查失败;原 Filter 已恢复并核实。")
|
||||
: FilterError(MLNStyleFilterErrorRollbackFailed, @"无法确认原 Filter 恢复;Host 必须废弃当前 Context。");
|
||||
}
|
||||
} // namespace
|
||||
|
||||
@implementation MLNVectorStyleLayer
|
||||
|
||||
- (id)rawStyleFilterJSONObjectWithError:(NSError **)error {
|
||||
if (error) *error = nil;
|
||||
auto *layer = FilterLayer(self, error);
|
||||
if (!layer) return nil;
|
||||
try {
|
||||
@try {
|
||||
Value expected;
|
||||
const Filter filter = layer->getFilter();
|
||||
if (id json = CheckedFilterJSON(filter, expected)) return json;
|
||||
} @catch (NSException *exception) {
|
||||
(void)exception;
|
||||
}
|
||||
} catch (...) {
|
||||
}
|
||||
if (error) *error = FilterError(MLNStyleFilterErrorUnsupported, @"当前 Filter 超限、不受支持或无法证明无损重放。");
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (BOOL)setRawStyleFilterJSONObject:(id)json error:(NSError **)error {
|
||||
if (error) *error = nil;
|
||||
auto *layer = FilterLayer(self, error);
|
||||
if (!layer) return NO;
|
||||
auto weakLayer = layer->makeWeakPtr();
|
||||
try {
|
||||
@try {
|
||||
const Filter previous = layer->getFilter();
|
||||
auto input = SnapshotFilterJSON(json);
|
||||
if (input) {
|
||||
// 解析独立的桥接快照,避免转换器读取调用方的可变容器。
|
||||
id snapshot = MLNJSONObjectFromMBGLValue(*input);
|
||||
auto bridged = SnapshotFilterJSON(snapshot);
|
||||
auto next = bridged && *bridged == *input ? ParseFilterJSON(*input, snapshot) : std::nullopt;
|
||||
Value nextJSON, previousJSON;
|
||||
if (next && CheckedFilterJSON(*next, nextJSON) && nextJSON == *input &&
|
||||
CheckedFilterJSON(previous, previousJSON)) {
|
||||
NSError *failure = ApplyFilter(self, *next, nextJSON, previous, previousJSON, weakLayer);
|
||||
if (error) *error = failure;
|
||||
return failure == nil;
|
||||
}
|
||||
}
|
||||
} @catch (NSException *exception) {
|
||||
(void)exception;
|
||||
}
|
||||
} catch (...) {
|
||||
}
|
||||
if (error) *error = FilterError(MLNStyleFilterErrorUnsupported, @"输入或原 Filter 超限、不受支持或无法证明无损重放;未写入。");
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (void)setPredicate:(NSPredicate *)predicate {
|
||||
[NSException raise:MLNAbstractClassException format:@"MLNVectorStyleLayer is an abstract class"];
|
||||
}
|
||||
|
||||
@@ -6,10 +6,14 @@
|
||||
|
||||
| 发行代号 | Android Native / RN | iOS Metal | 状态 | 入口 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| v27 | `13.6.1-particle.27` | `6.29.0-particle.27` | GSRL Source Lock 正式发行 | [v27 版本说明](maplibre-particle/13.6.1-particle.27/README.md) |
|
||||
| v26 | `13.6.1-particle.26` | `6.29.0-particle.26` | GSRL Source Lock 正式发行 | [v26 版本说明](maplibre-particle/13.6.1-particle.26/README.md) |
|
||||
| v25 | `13.6.1-particle.25` | `6.29.0-particle.25` | GSRL Source Lock 正式发行 | [v25 版本说明](maplibre-particle/13.6.1-particle.25/README.md) |
|
||||
| v23 | `13.6.1-particle.23` | `6.29.0-particle.23` | GSRL Source Lock 正式发行 | [v23 版本说明](maplibre-particle/13.6.1-particle.23/README.md) |
|
||||
| v22 | `13.6.1-particle.22` | `6.29.0-particle.22` | GSRL Source Lock 正式发行 | [v22 版本说明](maplibre-particle/13.6.1-particle.22/README.md) |
|
||||
|
||||
- v27 Source Lock:[SOURCE_LOCK.json](maplibre-particle/13.6.1-particle.27/SOURCE_LOCK.json)
|
||||
- v26 Source Lock:[SOURCE_LOCK.json](maplibre-particle/13.6.1-particle.26/SOURCE_LOCK.json)
|
||||
- v25 Source Lock:[SOURCE_LOCK.json](maplibre-particle/13.6.1-particle.25/SOURCE_LOCK.json)
|
||||
- v23 Source Lock:[SOURCE_LOCK.json](maplibre-particle/13.6.1-particle.23/SOURCE_LOCK.json)
|
||||
- v22 Source Lock:[SOURCE_LOCK.json](maplibre-particle/13.6.1-particle.22/SOURCE_LOCK.json)
|
||||
|
||||
32
release/maplibre-particle/13.6.1-particle.26/README.md
Normal file
32
release/maplibre-particle/13.6.1-particle.26/README.md
Normal file
@@ -0,0 +1,32 @@
|
||||
# MapLibre Particle V26 正式发行
|
||||
|
||||
Android/RN:`13.6.1-particle.26`;iOS Metal/SwiftPM:`6.29.0-particle.26`。
|
||||
|
||||
统一真实构建源码 Commit:`fce326a9009d3fc21eb91c5df362f0769163d07e`;正式 Git Tag:`maplibre-particle/rn/13.6.1-particle.26`。
|
||||
正式身份、依赖与六项文件 SHA-256 以本目录 [SOURCE_LOCK.json](SOURCE_LOCK.json) 为准。
|
||||
|
||||
## 公网发行文件
|
||||
|
||||
| 组件 / 文件 | 获取 URL |
|
||||
| --- | --- |
|
||||
| android / android-sdk-particle-opengl-13.6.1-particle.26.aar | https://pbf.jpcn.ccwu.cc/maven/cc/navsea/maplibre/android-sdk-particle-opengl/13.6.1-particle.26/android-sdk-particle-opengl-13.6.1-particle.26.aar |
|
||||
| android / android-sdk-particle-opengl-13.6.1-particle.26.pom | https://pbf.jpcn.ccwu.cc/maven/cc/navsea/maplibre/android-sdk-particle-opengl/13.6.1-particle.26/android-sdk-particle-opengl-13.6.1-particle.26.pom |
|
||||
| android / android-sdk-particle-opengl-13.6.1-particle.26.module | https://pbf.jpcn.ccwu.cc/maven/cc/navsea/maplibre/android-sdk-particle-opengl/13.6.1-particle.26/android-sdk-particle-opengl-13.6.1-particle.26.module |
|
||||
| reactNative / navsea-maplibre-particle-rn-13.6.1-particle.26.tgz | https://pbf.jpcn.ccwu.cc/npm/navsea-maplibre-particle-rn-13.6.1-particle.26.tgz |
|
||||
| ios / MapLibre.dynamic.xcframework.zip | https://pbf.jpcn.ccwu.cc/swift/maplibre-particle/6.29.0-particle.26/MapLibre.dynamic.xcframework.zip |
|
||||
| ios / MapLibre.dynamic.xcframework.zip.sha256 | https://pbf.jpcn.ccwu.cc/swift/maplibre-particle/6.29.0-particle.26/MapLibre.dynamic.xcframework.zip.sha256 |
|
||||
|
||||
## 安装和使用
|
||||
|
||||
- Android Maven:配置仓库 `https://pbf.jpcn.ccwu.cc/maven`,依赖 `cc.navsea.maplibre:android-sdk-particle-opengl:13.6.1-particle.26`;OpenGL AAR 含 `armeabi-v7a`、`arm64-v8a`、`x86`、`x86_64`。
|
||||
- RN:使用上表 tgz 安装 `@navsea/maplibre-particle-rn@13.6.1-particle.26`;peer 依赖 `@maplibre/maplibre-react-native@11.5.0`,React Native `>=0.80.0`,可选 Expo `>=54.0.0`。
|
||||
- iOS:SwiftPM 仓库 `https://pbf.jpcn.ccwu.cc/swift/maplibre-particle-spm.git`,精确版本 `6.29.0-particle.26`,product `MapLibre`;Metal、iOS arm64 真机设备专用,不含 Simulator。
|
||||
|
||||
## GSRL 接收要求和测试边界
|
||||
|
||||
按此目录 SOURCE_LOCK 核对项目、版本、Git Commit/Tag、全部交付文件的 SHA-256、依赖和安装说明;不匹配不得接收。
|
||||
`release.sh 26 build` 从上述冻结源码正式构建;`release.sh 26 publish` 执行 NAS 原子发布、六项文件公网下载逐字节对比、SwiftPM Tag 核对均通过。
|
||||
|
||||
V26 正式 AAR/Metal ZIP 的真机验收及 NavSea Host 整体接入 **NOT_TESTED**;原 STEP-119 FZ-G1 四轮 Test App 开发态检查不是此次正式 AAR 的运行验收。新增 C++ 生命周期 GTest 因 Mac 缺少 libuv 前置依赖未执行;这些事项不虚报 PASS。
|
||||
|
||||
总发行 Manifest:[release-manifest.json](../release-manifest.json)。
|
||||
104
release/maplibre-particle/13.6.1-particle.26/SOURCE_LOCK.json
Normal file
104
release/maplibre-particle/13.6.1-particle.26/SOURCE_LOCK.json
Normal file
@@ -0,0 +1,104 @@
|
||||
{
|
||||
"schemaVersion": "gsrl/source-lock/v1",
|
||||
"project": "MapLibreParticleLab",
|
||||
"releaseUnit": "Particle unified Android OpenGL / React Native / iOS Metal",
|
||||
"version": "13.6.1-particle.26",
|
||||
"versions": {
|
||||
"android": "13.6.1-particle.26",
|
||||
"reactNative": "13.6.1-particle.26",
|
||||
"ios": "6.29.0-particle.26"
|
||||
},
|
||||
"source": {
|
||||
"gitRemote": "https://git.eyaku.jp/tei/MapLibreParticleLab",
|
||||
"gitCommit": "fce326a9009d3fc21eb91c5df362f0769163d07e",
|
||||
"gitRef": "refs/tags/maplibre-particle/rn/13.6.1-particle.26",
|
||||
"note": "gitCommit是本次Android/RN/iOS共用的实际冻结构建源码;发行Tag指向含SOURCE_LOCK与发行Manifest的后续Final Commit。"
|
||||
},
|
||||
"files": [
|
||||
{
|
||||
"releaseUnit": "android",
|
||||
"name": "android-sdk-particle-opengl-13.6.1-particle.26.aar",
|
||||
"location": "https://pbf.jpcn.ccwu.cc/maven/cc/navsea/maplibre/android-sdk-particle-opengl/13.6.1-particle.26/android-sdk-particle-opengl-13.6.1-particle.26.aar",
|
||||
"sha256": "46c1cf7ff8ebc3bedc25aa38d7d445078a2301e6ca223c0307b93e830991c2b9",
|
||||
"sizeBytes": 16640537
|
||||
},
|
||||
{
|
||||
"releaseUnit": "android",
|
||||
"name": "android-sdk-particle-opengl-13.6.1-particle.26.pom",
|
||||
"location": "https://pbf.jpcn.ccwu.cc/maven/cc/navsea/maplibre/android-sdk-particle-opengl/13.6.1-particle.26/android-sdk-particle-opengl-13.6.1-particle.26.pom",
|
||||
"sha256": "b1e34295c58238a45a8b14d33c801028c3cabc0f40aae6116d007b352fa25520",
|
||||
"sizeBytes": 5687
|
||||
},
|
||||
{
|
||||
"releaseUnit": "android",
|
||||
"name": "android-sdk-particle-opengl-13.6.1-particle.26.module",
|
||||
"location": "https://pbf.jpcn.ccwu.cc/maven/cc/navsea/maplibre/android-sdk-particle-opengl/13.6.1-particle.26/android-sdk-particle-opengl-13.6.1-particle.26.module",
|
||||
"sha256": "405b3bb3732ef29a0b54e6b24b192839d07d4c28e959276d6bd92051637011c4",
|
||||
"sizeBytes": 6999
|
||||
},
|
||||
{
|
||||
"releaseUnit": "reactNative",
|
||||
"name": "navsea-maplibre-particle-rn-13.6.1-particle.26.tgz",
|
||||
"location": "https://pbf.jpcn.ccwu.cc/npm/navsea-maplibre-particle-rn-13.6.1-particle.26.tgz",
|
||||
"sha256": "3136316bb6508e774748b21c2095ab61b522d2cf4c3ff2e8c67774dde8549b93",
|
||||
"sizeBytes": 29038
|
||||
},
|
||||
{
|
||||
"releaseUnit": "ios",
|
||||
"name": "MapLibre.dynamic.xcframework.zip",
|
||||
"location": "https://pbf.jpcn.ccwu.cc/swift/maplibre-particle/6.29.0-particle.26/MapLibre.dynamic.xcframework.zip",
|
||||
"sha256": "64b51c2b9eaec4adefcfb2dfc1c6338943f11db4d6c4ce2a4e1c2a39bb84bddb",
|
||||
"sizeBytes": 4181892
|
||||
},
|
||||
{
|
||||
"releaseUnit": "ios",
|
||||
"name": "MapLibre.dynamic.xcframework.zip.sha256",
|
||||
"location": "https://pbf.jpcn.ccwu.cc/swift/maplibre-particle/6.29.0-particle.26/MapLibre.dynamic.xcframework.zip.sha256",
|
||||
"sha256": "ae79556c2b288a3970d6e4eba0c30a68023faa79ff55de291c9e2f30f16fd9ae",
|
||||
"sizeBytes": 99
|
||||
}
|
||||
],
|
||||
"dependencies": [
|
||||
{
|
||||
"name": "@maplibre/maplibre-react-native",
|
||||
"version": "11.5.0",
|
||||
"role": "React Native host peer, iOS MapLibreReactNative Pod"
|
||||
},
|
||||
{
|
||||
"name": "React Native",
|
||||
"version": ">=0.80.0",
|
||||
"role": "RN package peer requirement"
|
||||
},
|
||||
{
|
||||
"name": "Expo",
|
||||
"version": ">=54.0.0 (optional)",
|
||||
"role": "RN optional peer"
|
||||
},
|
||||
{
|
||||
"name": "Android Native SDK",
|
||||
"version": "13.6.1-particle.26",
|
||||
"coordinate": "cc.navsea.maplibre:android-sdk-particle-opengl:13.6.1-particle.26",
|
||||
"role": "OpenGL AAR, arm64-v8a/armeabi-v7a/x86/x86_64"
|
||||
},
|
||||
{
|
||||
"name": "iOS SwiftPM MapLibre",
|
||||
"version": "6.29.0-particle.26",
|
||||
"url": "https://pbf.jpcn.ccwu.cc/swift/maplibre-particle-spm.git",
|
||||
"role": "Exact version, product MapLibre, Metal ios-arm64"
|
||||
}
|
||||
],
|
||||
"usage": "release/maplibre-particle/13.6.1-particle.26/README.md",
|
||||
"releaseManifest": "release/maplibre-particle/release-manifest.json",
|
||||
"status": "RELEASED",
|
||||
"validation": {
|
||||
"publicReadback": "PASS - release.sh 26 publish exit 0; AAR, POM, Module, RN tgz, Metal ZIP, ZIP.sha256 six public files byte-compared to release artifacts",
|
||||
"androidBuild": "PASS - Android OpenGL Release four ABI from frozen source fce326a9009d3fc21eb91c5df362f0769163d07e",
|
||||
"iosMetalBuild": "PASS - Bazel opt Metal ios-arm64 XCFramework, no simulator slices",
|
||||
"reactNativePack": "PASS - npm pack @navsea/maplibre-particle-rn 13.6.1-particle.26; peer MapLibre RN 11.5.0",
|
||||
"androidDevice": "NOT_TESTED - V26 final release AAR itself; STEP-119 earlier development Test App validated separately",
|
||||
"reactNative115HostIntegration": "NOT_TESTED - V26 final release binary in NavSea",
|
||||
"iosMetalDevice": "NOT_TESTED - V26 final release XCFramework on iPhone/iPad",
|
||||
"step119CppGTest": "BLOCKED - Mac CMake prerequisite libuv unavailable, 13 lifecycle GTest sources not executed",
|
||||
"note": "GSRL RELEASED verifies frozen source/files/dependency/public retrieval; it does not certify NavSea production runtime, Android/iOS device tests, or unexecuted C++ GTest."
|
||||
}
|
||||
}
|
||||
35
release/maplibre-particle/13.6.1-particle.27/README.md
Normal file
35
release/maplibre-particle/13.6.1-particle.27/README.md
Normal file
@@ -0,0 +1,35 @@
|
||||
# MapLibre Particle V27 正式发行
|
||||
|
||||
Android/RN:13.6.1-particle.27;iOS Metal/SwiftPM:6.29.0-particle.27。
|
||||
|
||||
统一实际构建源码 Commit:970e3ad606e59873a32e9389e65ef257e1c259bb;正式 Git Tag:maplibre-particle/rn/13.6.1-particle.27。
|
||||
正式来源、依赖和六项文件 SHA-256 以本目录 [SOURCE_LOCK.json](SOURCE_LOCK.json) 为准。
|
||||
|
||||
## 内容及公开下载
|
||||
|
||||
本次新增 Metal Retained Underlay Composite 顶点缓冲槽修复(maxUBOCountPerShader + 0),保留 V26 失败候选的历史调查记录。RCR 为当前 NavSea Host 基线选择;不以 E 全 Continuous 降级冒充本次修复。
|
||||
|
||||
| 组件 / 文件 | 公网 URL |
|
||||
| --- | --- |
|
||||
| android / android-sdk-particle-opengl-13.6.1-particle.27.aar | https://pbf.jpcn.ccwu.cc/maven/cc/navsea/maplibre/android-sdk-particle-opengl/13.6.1-particle.27/android-sdk-particle-opengl-13.6.1-particle.27.aar |
|
||||
| android / android-sdk-particle-opengl-13.6.1-particle.27.pom | https://pbf.jpcn.ccwu.cc/maven/cc/navsea/maplibre/android-sdk-particle-opengl/13.6.1-particle.27/android-sdk-particle-opengl-13.6.1-particle.27.pom |
|
||||
| android / android-sdk-particle-opengl-13.6.1-particle.27.module | https://pbf.jpcn.ccwu.cc/maven/cc/navsea/maplibre/android-sdk-particle-opengl/13.6.1-particle.27/android-sdk-particle-opengl-13.6.1-particle.27.module |
|
||||
| reactNative / navsea-maplibre-particle-rn-13.6.1-particle.27.tgz | https://pbf.jpcn.ccwu.cc/npm/navsea-maplibre-particle-rn-13.6.1-particle.27.tgz |
|
||||
| ios / MapLibre.dynamic.xcframework.zip | https://pbf.jpcn.ccwu.cc/swift/maplibre-particle/6.29.0-particle.27/MapLibre.dynamic.xcframework.zip |
|
||||
| ios / MapLibre.dynamic.xcframework.zip.sha256 | https://pbf.jpcn.ccwu.cc/swift/maplibre-particle/6.29.0-particle.27/MapLibre.dynamic.xcframework.zip.sha256 |
|
||||
|
||||
## 安装、依赖与兼容性
|
||||
|
||||
- Android:Maven https://pbf.jpcn.ccwu.cc/maven ,坐标 cc.navsea.maplibre:android-sdk-particle-opengl:13.6.1-particle.27;OpenGL AAR 包含 armeabi-v7a、arm64-v8a、x86、x86_64。
|
||||
- React Native:安装上表 tgz,对应 @navsea/maplibre-particle-rn@13.6.1-particle.27;MapLibre React Native 11.5.0、React Native >=0.80.0,Expo >=54.0.0 可选。
|
||||
- iOS:SwiftPM https://pbf.jpcn.ccwu.cc/swift/maplibre-particle-spm.git ,Exact Version 6.29.0-particle.27,Product MapLibre;Metal iOS arm64 真机专用,无 Simulator slice。
|
||||
|
||||
## GSRL 核验与运行测试边界
|
||||
|
||||
release.sh 27 build:Android 四 ABI、Maven Local、iOS Metal Bazel opt、RN pack PASS。
|
||||
release.sh 27 publish:NAS 原子发布、六项公网文件逐字节回读、SwiftPM 版本 Tag 核验 PASS。
|
||||
|
||||
此次尚未执行 V27 正式二进制的 Android/iOS 真机安装运行、NavSea Host 整体接入及 C++ 生命周期 GTest。此前 slot-9 测试候选获用户视觉确认,不等于本次发行包通过设备验收。
|
||||
|
||||
接收方必须独立核对 Git Commit/Tag、全部文件 SHA-256、依赖与安装说明;校验未通过不得正式接入。
|
||||
统一 [发行 Manifest](../release-manifest.json)。
|
||||
104
release/maplibre-particle/13.6.1-particle.27/SOURCE_LOCK.json
Normal file
104
release/maplibre-particle/13.6.1-particle.27/SOURCE_LOCK.json
Normal file
@@ -0,0 +1,104 @@
|
||||
{
|
||||
"schemaVersion": "gsrl/source-lock/v1",
|
||||
"project": "MapLibreParticleLab",
|
||||
"releaseUnit": "Particle unified Android OpenGL / React Native / iOS Metal",
|
||||
"version": "13.6.1-particle.27",
|
||||
"versions": {
|
||||
"android": "13.6.1-particle.27",
|
||||
"reactNative": "13.6.1-particle.27",
|
||||
"ios": "6.29.0-particle.27"
|
||||
},
|
||||
"source": {
|
||||
"gitRemote": "https://git.eyaku.jp/tei/MapLibreParticleLab",
|
||||
"gitCommit": "970e3ad606e59873a32e9389e65ef257e1c259bb",
|
||||
"gitRef": "refs/tags/maplibre-particle/rn/13.6.1-particle.27",
|
||||
"note": "gitCommit是本次Android/RN/iOS共用的实际冻结构建源码;发行Tag指向含SOURCE_LOCK与发行Manifest的后续Final Commit。"
|
||||
},
|
||||
"files": [
|
||||
{
|
||||
"releaseUnit": "android",
|
||||
"name": "android-sdk-particle-opengl-13.6.1-particle.27.aar",
|
||||
"location": "https://pbf.jpcn.ccwu.cc/maven/cc/navsea/maplibre/android-sdk-particle-opengl/13.6.1-particle.27/android-sdk-particle-opengl-13.6.1-particle.27.aar",
|
||||
"sha256": "2ff3ead8e5d7cb8802a4c05ba20cd7f8f0a7029f72619f9a2ec176c942ae8de4",
|
||||
"sizeBytes": 16640539
|
||||
},
|
||||
{
|
||||
"releaseUnit": "android",
|
||||
"name": "android-sdk-particle-opengl-13.6.1-particle.27.pom",
|
||||
"location": "https://pbf.jpcn.ccwu.cc/maven/cc/navsea/maplibre/android-sdk-particle-opengl/13.6.1-particle.27/android-sdk-particle-opengl-13.6.1-particle.27.pom",
|
||||
"sha256": "324d14d689ba7c9206298734d7f9636b95e947c7241d94ec60f8f1d57bd1b701",
|
||||
"sizeBytes": 5687
|
||||
},
|
||||
{
|
||||
"releaseUnit": "android",
|
||||
"name": "android-sdk-particle-opengl-13.6.1-particle.27.module",
|
||||
"location": "https://pbf.jpcn.ccwu.cc/maven/cc/navsea/maplibre/android-sdk-particle-opengl/13.6.1-particle.27/android-sdk-particle-opengl-13.6.1-particle.27.module",
|
||||
"sha256": "403804395f065cef75f1254403edbda67c3a885769acbf6d25e091d37c325910",
|
||||
"sizeBytes": 6999
|
||||
},
|
||||
{
|
||||
"releaseUnit": "reactNative",
|
||||
"name": "navsea-maplibre-particle-rn-13.6.1-particle.27.tgz",
|
||||
"location": "https://pbf.jpcn.ccwu.cc/npm/navsea-maplibre-particle-rn-13.6.1-particle.27.tgz",
|
||||
"sha256": "fdd67210debd8363d4bf72fcc0120ef747dc3037d55b8c0d83330dd98bb0bc5d",
|
||||
"sizeBytes": 29040
|
||||
},
|
||||
{
|
||||
"releaseUnit": "ios",
|
||||
"name": "MapLibre.dynamic.xcframework.zip",
|
||||
"location": "https://pbf.jpcn.ccwu.cc/swift/maplibre-particle/6.29.0-particle.27/MapLibre.dynamic.xcframework.zip",
|
||||
"sha256": "5dbde28600df35484a7f31e7c7c4780f27b919fc6ec59f6aa88d65d81342448c",
|
||||
"sizeBytes": 4181894
|
||||
},
|
||||
{
|
||||
"releaseUnit": "ios",
|
||||
"name": "MapLibre.dynamic.xcframework.zip.sha256",
|
||||
"location": "https://pbf.jpcn.ccwu.cc/swift/maplibre-particle/6.29.0-particle.27/MapLibre.dynamic.xcframework.zip.sha256",
|
||||
"sha256": "1dba0b0a2831beeadcc022801622829d630ef0838babdfef4edc4b8bec34e08d",
|
||||
"sizeBytes": 99
|
||||
}
|
||||
],
|
||||
"dependencies": [
|
||||
{
|
||||
"name": "@maplibre/maplibre-react-native",
|
||||
"version": "11.5.0",
|
||||
"role": "React Native host peer, iOS MapLibreReactNative Pod"
|
||||
},
|
||||
{
|
||||
"name": "React Native",
|
||||
"version": ">=0.80.0",
|
||||
"role": "RN package peer requirement"
|
||||
},
|
||||
{
|
||||
"name": "Expo",
|
||||
"version": ">=54.0.0 (optional)",
|
||||
"role": "RN optional peer"
|
||||
},
|
||||
{
|
||||
"name": "Android Native SDK",
|
||||
"version": "13.6.1-particle.27",
|
||||
"coordinate": "cc.navsea.maplibre:android-sdk-particle-opengl:13.6.1-particle.27",
|
||||
"role": "OpenGL AAR, arm64-v8a/armeabi-v7a/x86/x86_64"
|
||||
},
|
||||
{
|
||||
"name": "iOS SwiftPM MapLibre",
|
||||
"version": "6.29.0-particle.27",
|
||||
"url": "https://pbf.jpcn.ccwu.cc/swift/maplibre-particle-spm.git",
|
||||
"role": "Exact version, product MapLibre, Metal ios-arm64"
|
||||
}
|
||||
],
|
||||
"usage": "release/maplibre-particle/13.6.1-particle.27/README.md",
|
||||
"releaseManifest": "release/maplibre-particle/release-manifest.json",
|
||||
"status": "RELEASED",
|
||||
"validation": {
|
||||
"publicReadback": "PASS - release.sh 27 publish exit 0; six public files byte-compared, SwiftPM tag checked",
|
||||
"androidBuild": "PASS - OpenGL Release four ABI from frozen source 970e3ad606e59873a32e9389e65ef257e1c259bb",
|
||||
"iosMetalBuild": "PASS - Bazel opt Metal ios-arm64 XCFramework, no simulator slice",
|
||||
"reactNativePack": "PASS - npm pack 13.6.1-particle.27; MapLibre RN 11.5.0 peer",
|
||||
"androidDevice": "NOT_TESTED - V27 final release AAR itself on device",
|
||||
"reactNative115HostIntegration": "NOT_TESTED - V27 final release package in NavSea Host",
|
||||
"iosMetalDevice": "NOT_TESTED - V27 final XCFramework; prior slot-9 candidate visual acceptance is not a release binary acceptance",
|
||||
"step119CppGTest": "NOT_TESTED - not run in V27 GSRL release",
|
||||
"note": "GSRL RELEASED certifies source, files, dependencies and public readback, not production runtime or device tests."
|
||||
}
|
||||
}
|
||||
37
release/maplibre-particle/13.6.1-particle.28/README.md
Normal file
37
release/maplibre-particle/13.6.1-particle.28/README.md
Normal file
@@ -0,0 +1,37 @@
|
||||
# MapLibre Particle V28 正式发行
|
||||
|
||||
Android/RN:13.6.1-particle.28;iOS Metal/SwiftPM:6.29.0-particle.28。
|
||||
|
||||
统一冻结构建源码 Commit:4d9cb0ef69dc4bb946c48377f4e1a5b2ba2bbbe8;正式 Git Tag:maplibre-particle/rn/13.6.1-particle.28。
|
||||
正式来源、依赖及全部六项文件 SHA-256 见本目录 [SOURCE_LOCK.json](SOURCE_LOCK.json)。
|
||||
|
||||
## 本次变更
|
||||
|
||||
iOS MapLibre Native 在 MLNVectorStyleLayer 新增原 Style Layer Filter 的受控原始 JSON 读取与设置 API。实现采用“无损或拒绝”:无法证明与 Core Filter 往返等价时 fail-closed,不经过有损 NSPredicate 读回。原有 predicate、粒子渲染及 Android Native 功能保持。
|
||||
|
||||
Style 成员关系、代次和 revision 由 MLRN Host 核验,Native 不内置 CM95 语义。本功能是双端方案 B 的上游前提,不代表 NavSea Host 或设备已通过。
|
||||
|
||||
## 公网产物
|
||||
|
||||
| 组件 | 文件 | SHA-256 |
|
||||
| --- | --- | --- |
|
||||
| android | [android-sdk-particle-opengl-13.6.1-particle.28.aar](https://pbf.jpcn.ccwu.cc/maven/cc/navsea/maplibre/android-sdk-particle-opengl/13.6.1-particle.28/android-sdk-particle-opengl-13.6.1-particle.28.aar) | a60818db5987f5305d7f94f7eb64d4f3c757131a432cadc9f60c8c3467303f32 |
|
||||
| android | [android-sdk-particle-opengl-13.6.1-particle.28.pom](https://pbf.jpcn.ccwu.cc/maven/cc/navsea/maplibre/android-sdk-particle-opengl/13.6.1-particle.28/android-sdk-particle-opengl-13.6.1-particle.28.pom) | 20286294b9b210b225c370c7160ca35a023759d4dae58e091df2657d8d540554 |
|
||||
| android | [android-sdk-particle-opengl-13.6.1-particle.28.module](https://pbf.jpcn.ccwu.cc/maven/cc/navsea/maplibre/android-sdk-particle-opengl/13.6.1-particle.28/android-sdk-particle-opengl-13.6.1-particle.28.module) | 883c81928d232160f1c80442c8d831fc362807c2ea5a68bded9b599eb844505c |
|
||||
| reactNative | [navsea-maplibre-particle-rn-13.6.1-particle.28.tgz](https://pbf.jpcn.ccwu.cc/npm/navsea-maplibre-particle-rn-13.6.1-particle.28.tgz) | 16726d076ad1117afd2134d578b24a7466c44e68173fb95fa962b5592bd2accd |
|
||||
| ios | [MapLibre.dynamic.xcframework.zip](https://pbf.jpcn.ccwu.cc/swift/maplibre-particle/6.29.0-particle.28/MapLibre.dynamic.xcframework.zip) | 577d0784f1e436c04b3cf0bd0213ce38fdf14612898367ec6e7c73156b82cd74 |
|
||||
| ios | [MapLibre.dynamic.xcframework.zip.sha256](https://pbf.jpcn.ccwu.cc/swift/maplibre-particle/6.29.0-particle.28/MapLibre.dynamic.xcframework.zip.sha256) | fa52c0e75b04a9e81d4e21f3be4bcfe0586fccfee3dcc242784cbdb85397a91a |
|
||||
|
||||
## 使用与安装
|
||||
|
||||
- Android Maven:https://pbf.jpcn.ccwu.cc/maven;坐标 cc.navsea.maplibre:android-sdk-particle-opengl:13.6.1-particle.28,OpenGL AAR 含 armeabi-v7a、arm64-v8a、x86、x86_64。
|
||||
- React Native:@navsea/maplibre-particle-rn@13.6.1-particle.28;MapLibre RN 11.5.0,React Native >=0.80.0,Expo >=54.0.0 可选。
|
||||
- iOS SwiftPM:https://pbf.jpcn.ccwu.cc/swift/maplibre-particle-spm.git,Exact Version 6.29.0-particle.28,Product MapLibre,Metal iOS arm64 真机专用,无 Simulator slice。
|
||||
|
||||
## GSRL 核验与尚未验证
|
||||
|
||||
release.sh 28 prepare/build/publish:PASS;冻结源码提交、Android 四 ABI/Maven Local、RN pack、iOS Metal Bazel opt、NAS 原子发布、六项公网文件逐字节回读及 SwiftPM Tag 均通过。
|
||||
|
||||
尚未执行 V28 发行 AAR/XCFramework 的真机安装运行,以及 NavSea Host 的 CM95 Filter 查询/应用/恢复、iOS MLRN Bridge 和正式四档测试。构建/发行通过不等于安全语义或设备测试通过。
|
||||
|
||||
接收方需核验 Git Tag、SOURCE_LOCK、全部 SHA-256、依赖及 Host Filter 可信审核门槛。
|
||||
104
release/maplibre-particle/13.6.1-particle.28/SOURCE_LOCK.json
Normal file
104
release/maplibre-particle/13.6.1-particle.28/SOURCE_LOCK.json
Normal file
@@ -0,0 +1,104 @@
|
||||
{
|
||||
"schemaVersion": "gsrl/source-lock/v1",
|
||||
"project": "MapLibreParticleLab",
|
||||
"releaseUnit": "Particle unified Android OpenGL / React Native / iOS Metal",
|
||||
"version": "13.6.1-particle.28",
|
||||
"versions": {
|
||||
"android": "13.6.1-particle.28",
|
||||
"reactNative": "13.6.1-particle.28",
|
||||
"ios": "6.29.0-particle.28"
|
||||
},
|
||||
"source": {
|
||||
"gitRemote": "https://git.eyaku.jp/tei/MapLibreParticleLab",
|
||||
"gitCommit": "4d9cb0ef69dc4bb946c48377f4e1a5b2ba2bbbe8",
|
||||
"gitRef": "refs/tags/maplibre-particle/rn/13.6.1-particle.28",
|
||||
"note": "gitCommit是Android/RN/iOS共用的实际冻结构建源码;发行Tag指向含SOURCE_LOCK与Manifest的后续Final Commit。"
|
||||
},
|
||||
"files": [
|
||||
{
|
||||
"releaseUnit": "android",
|
||||
"name": "android-sdk-particle-opengl-13.6.1-particle.28.aar",
|
||||
"location": "https://pbf.jpcn.ccwu.cc/maven/cc/navsea/maplibre/android-sdk-particle-opengl/13.6.1-particle.28/android-sdk-particle-opengl-13.6.1-particle.28.aar",
|
||||
"sha256": "a60818db5987f5305d7f94f7eb64d4f3c757131a432cadc9f60c8c3467303f32",
|
||||
"sizeBytes": 16640539
|
||||
},
|
||||
{
|
||||
"releaseUnit": "android",
|
||||
"name": "android-sdk-particle-opengl-13.6.1-particle.28.pom",
|
||||
"location": "https://pbf.jpcn.ccwu.cc/maven/cc/navsea/maplibre/android-sdk-particle-opengl/13.6.1-particle.28/android-sdk-particle-opengl-13.6.1-particle.28.pom",
|
||||
"sha256": "20286294b9b210b225c370c7160ca35a023759d4dae58e091df2657d8d540554",
|
||||
"sizeBytes": 5687
|
||||
},
|
||||
{
|
||||
"releaseUnit": "android",
|
||||
"name": "android-sdk-particle-opengl-13.6.1-particle.28.module",
|
||||
"location": "https://pbf.jpcn.ccwu.cc/maven/cc/navsea/maplibre/android-sdk-particle-opengl/13.6.1-particle.28/android-sdk-particle-opengl-13.6.1-particle.28.module",
|
||||
"sha256": "883c81928d232160f1c80442c8d831fc362807c2ea5a68bded9b599eb844505c",
|
||||
"sizeBytes": 6999
|
||||
},
|
||||
{
|
||||
"releaseUnit": "reactNative",
|
||||
"name": "navsea-maplibre-particle-rn-13.6.1-particle.28.tgz",
|
||||
"location": "https://pbf.jpcn.ccwu.cc/npm/navsea-maplibre-particle-rn-13.6.1-particle.28.tgz",
|
||||
"sha256": "16726d076ad1117afd2134d578b24a7466c44e68173fb95fa962b5592bd2accd",
|
||||
"sizeBytes": 29063
|
||||
},
|
||||
{
|
||||
"releaseUnit": "ios",
|
||||
"name": "MapLibre.dynamic.xcframework.zip",
|
||||
"location": "https://pbf.jpcn.ccwu.cc/swift/maplibre-particle/6.29.0-particle.28/MapLibre.dynamic.xcframework.zip",
|
||||
"sha256": "577d0784f1e436c04b3cf0bd0213ce38fdf14612898367ec6e7c73156b82cd74",
|
||||
"sizeBytes": 4187287
|
||||
},
|
||||
{
|
||||
"releaseUnit": "ios",
|
||||
"name": "MapLibre.dynamic.xcframework.zip.sha256",
|
||||
"location": "https://pbf.jpcn.ccwu.cc/swift/maplibre-particle/6.29.0-particle.28/MapLibre.dynamic.xcframework.zip.sha256",
|
||||
"sha256": "fa52c0e75b04a9e81d4e21f3be4bcfe0586fccfee3dcc242784cbdb85397a91a",
|
||||
"sizeBytes": 99
|
||||
}
|
||||
],
|
||||
"dependencies": [
|
||||
{
|
||||
"name": "@maplibre/maplibre-react-native",
|
||||
"version": "11.5.0",
|
||||
"role": "React Native host peer, iOS MapLibreReactNative Pod"
|
||||
},
|
||||
{
|
||||
"name": "React Native",
|
||||
"version": ">=0.80.0",
|
||||
"role": "RN package peer requirement"
|
||||
},
|
||||
{
|
||||
"name": "Expo",
|
||||
"version": ">=54.0.0 (optional)",
|
||||
"role": "RN optional peer"
|
||||
},
|
||||
{
|
||||
"name": "Android Native SDK",
|
||||
"version": "13.6.1-particle.28",
|
||||
"coordinate": "cc.navsea.maplibre:android-sdk-particle-opengl:13.6.1-particle.28",
|
||||
"role": "OpenGL AAR, arm64-v8a/armeabi-v7a/x86/x86_64"
|
||||
},
|
||||
{
|
||||
"name": "iOS SwiftPM MapLibre",
|
||||
"version": "6.29.0-particle.28",
|
||||
"url": "https://pbf.jpcn.ccwu.cc/swift/maplibre-particle-spm.git",
|
||||
"role": "Exact version, product MapLibre, Metal ios-arm64"
|
||||
}
|
||||
],
|
||||
"usage": "release/maplibre-particle/13.6.1-particle.28/README.md",
|
||||
"releaseManifest": "release/maplibre-particle/release-manifest.json",
|
||||
"status": "RELEASED",
|
||||
"validation": {
|
||||
"publicReadback": "PASS - release.sh 28 publish exit 0; six public files byte-compared; SwiftPM tag checked",
|
||||
"androidBuild": "PASS - OpenGL Release four ABI from frozen source 4d9cb0ef69dc4bb946c48377f4e1a5b2ba2bbbe8",
|
||||
"iosMetalBuild": "PASS - Bazel opt Metal ios-arm64 XCFramework, no simulator slice",
|
||||
"reactNativePack": "PASS - npm pack 13.6.1-particle.28; MapLibre RN 11.5.0 peer",
|
||||
"newNativeIosAPI": "COMPILED - rawStyleFilterJSONObjectWithError and setRawStyleFilterJSONObject:error exported in iOS framework headers; no independent device invocation",
|
||||
"androidDevice": "NOT_TESTED - V28 final AAR on physical device",
|
||||
"iosMetalDevice": "NOT_TESTED - V28 final XCFramework on physical device",
|
||||
"navseaHostIntegration": "NOT_TESTED - NavSea MLRN iOS bridge and native app build remain separate",
|
||||
"note": "GSRL RELEASED certifies source, files, dependencies and public readback, not full CM95 runtime or device tests."
|
||||
}
|
||||
}
|
||||
@@ -9,8 +9,8 @@ let package = Package(
|
||||
targets: [
|
||||
.binaryTarget(
|
||||
name: "MapLibre",
|
||||
url: "https://pbf.jpcn.ccwu.cc/swift/maplibre-particle/6.29.0-particle.25/MapLibre.dynamic.xcframework.zip",
|
||||
checksum: "91808cfb5559dfa050fe4d466d2c9fa2dc492021bd1a203c778450dc0dae20af"
|
||||
url: "https://pbf.jpcn.ccwu.cc/swift/maplibre-particle/6.29.0-particle.28/MapLibre.dynamic.xcframework.zip",
|
||||
checksum: "577d0784f1e436c04b3cf0bd0213ce38fdf14612898367ec6e7c73156b82cd74"
|
||||
),
|
||||
]
|
||||
)
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
# MapLibre Particle iOS 发行
|
||||
|
||||
当前正式发行:6.29.0-particle.25
|
||||
当前正式发行:6.29.0-particle.28
|
||||
|
||||
- Renderer:Metal
|
||||
- Product / Module:MapLibre
|
||||
- Binary:https://pbf.jpcn.ccwu.cc/swift/maplibre-particle/6.29.0-particle.25/MapLibre.dynamic.xcframework.zip
|
||||
- SHA-256:91808cfb5559dfa050fe4d466d2c9fa2dc492021bd1a203c778450dc0dae20af
|
||||
- Binary:https://pbf.jpcn.ccwu.cc/swift/maplibre-particle/6.29.0-particle.28/MapLibre.dynamic.xcframework.zip
|
||||
- SHA-256:577d0784f1e436c04b3cf0bd0213ce38fdf14612898367ec6e7c73156b82cd74
|
||||
- SwiftPM:https://pbf.jpcn.ccwu.cc/swift/maplibre-particle-spm.git
|
||||
- Exact Version:6.29.0-particle.25
|
||||
- Source Commit:f32f6f45fd359007e8b3c77911b66d9e7fc93848
|
||||
- Exact Version:6.29.0-particle.28
|
||||
- Source Commit:4d9cb0ef69dc4bb946c48377f4e1a5b2ba2bbbe8
|
||||
|
||||
构建命令固定为:
|
||||
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
{
|
||||
"schemaVersion": 2,
|
||||
"status": "PUBLISHED_PUBLIC_READBACK_PASS",
|
||||
"distributionVersion": "6.29.0-particle.25",
|
||||
"distributionVersion": "6.29.0-particle.28",
|
||||
"moduleName": "MapLibre",
|
||||
"compilationMode": "opt",
|
||||
"artifact": {
|
||||
"fileName": "MapLibre.dynamic.xcframework.zip",
|
||||
"sizeBytes": 4176630,
|
||||
"sha256": "91808cfb5559dfa050fe4d466d2c9fa2dc492021bd1a203c778450dc0dae20af",
|
||||
"url": "https://pbf.jpcn.ccwu.cc/swift/maplibre-particle/6.29.0-particle.25/MapLibre.dynamic.xcframework.zip"
|
||||
"sizeBytes": 4187287,
|
||||
"sha256": "577d0784f1e436c04b3cf0bd0213ce38fdf14612898367ec6e7c73156b82cd74",
|
||||
"url": "https://pbf.jpcn.ccwu.cc/swift/maplibre-particle/6.29.0-particle.28/MapLibre.dynamic.xcframework.zip"
|
||||
},
|
||||
"sourceCommit": "f32f6f45fd359007e8b3c77911b66d9e7fc93848",
|
||||
"sourceCommit": "4d9cb0ef69dc4bb946c48377f4e1a5b2ba2bbbe8",
|
||||
"swiftPackage": {
|
||||
"url": "https://pbf.jpcn.ccwu.cc/swift/maplibre-particle-spm.git",
|
||||
"requirement": "exactVersion",
|
||||
"version": "6.29.0-particle.25",
|
||||
"version": "6.29.0-particle.28",
|
||||
"productName": "MapLibre"
|
||||
},
|
||||
"notes": "Metal renderer optimized release built with bazel build -c opt --//:renderer=metal //platform/ios:MapLibre.dynamic"
|
||||
|
||||
@@ -1,49 +1,49 @@
|
||||
{
|
||||
"schemaVersion": 2,
|
||||
"releaseState": "PUBLISHED_PUBLIC_READBACK_PASS",
|
||||
"particleRevision": 25,
|
||||
"sourceCommit": "f32f6f45fd359007e8b3c77911b66d9e7fc93848",
|
||||
"generatedAtUtc": "2026-10-11T13:58:48.659461+00:00",
|
||||
"particleRevision": 28,
|
||||
"sourceCommit": "4d9cb0ef69dc4bb946c48377f4e1a5b2ba2bbbe8",
|
||||
"generatedAtUtc": "2026-10-21T07:17:43.623656+00:00",
|
||||
"reactNative": {
|
||||
"name": "@navsea/maplibre-particle-rn",
|
||||
"version": "13.6.1-particle.25",
|
||||
"version": "13.6.1-particle.28",
|
||||
"artifact": {
|
||||
"fileName": "navsea-maplibre-particle-rn-13.6.1-particle.25.tgz",
|
||||
"sizeBytes": 29029,
|
||||
"sha256": "e5ed678c90bad6082dda30fb9937894cabe28732160391816b785d3c88c208e9",
|
||||
"url": "https://pbf.jpcn.ccwu.cc/npm/navsea-maplibre-particle-rn-13.6.1-particle.25.tgz"
|
||||
"fileName": "navsea-maplibre-particle-rn-13.6.1-particle.28.tgz",
|
||||
"sizeBytes": 29063,
|
||||
"sha256": "16726d076ad1117afd2134d578b24a7466c44e68173fb95fa962b5592bd2accd",
|
||||
"url": "https://pbf.jpcn.ccwu.cc/npm/navsea-maplibre-particle-rn-13.6.1-particle.28.tgz"
|
||||
},
|
||||
"mapLibreReactNative": "11.5.0"
|
||||
},
|
||||
"android": {
|
||||
"version": "13.6.1-particle.25",
|
||||
"coordinate": "cc.navsea.maplibre:android-sdk-particle-opengl:13.6.1-particle.25",
|
||||
"version": "13.6.1-particle.28",
|
||||
"coordinate": "cc.navsea.maplibre:android-sdk-particle-opengl:13.6.1-particle.28",
|
||||
"renderer": "opengl",
|
||||
"artifact": {
|
||||
"fileName": "android-sdk-particle-opengl-13.6.1-particle.25.aar",
|
||||
"sizeBytes": 16625344,
|
||||
"sha256": "629d5d437d884ec2017f3fde007aaadfd2bc787983457f75bcaf4d4a13184a1e",
|
||||
"url": "https://pbf.jpcn.ccwu.cc/maven/cc/navsea/maplibre/android-sdk-particle-opengl/13.6.1-particle.25/android-sdk-particle-opengl-13.6.1-particle.25.aar"
|
||||
"fileName": "android-sdk-particle-opengl-13.6.1-particle.28.aar",
|
||||
"sizeBytes": 16640539,
|
||||
"sha256": "a60818db5987f5305d7f94f7eb64d4f3c757131a432cadc9f60c8c3467303f32",
|
||||
"url": "https://pbf.jpcn.ccwu.cc/maven/cc/navsea/maplibre/android-sdk-particle-opengl/13.6.1-particle.28/android-sdk-particle-opengl-13.6.1-particle.28.aar"
|
||||
},
|
||||
"ndk": "28.2.13676358",
|
||||
"gradle": "9.5.1",
|
||||
"jdk": "openjdk version \"17.0.20.1\" 2026-08-18"
|
||||
},
|
||||
"ios": {
|
||||
"distributionVersion": "6.29.0-particle.25",
|
||||
"distributionVersion": "6.29.0-particle.28",
|
||||
"renderer": "metal",
|
||||
"compilationMode": "opt",
|
||||
"moduleName": "MapLibre",
|
||||
"artifact": {
|
||||
"fileName": "MapLibre.dynamic.xcframework.zip",
|
||||
"sizeBytes": 4176630,
|
||||
"sha256": "91808cfb5559dfa050fe4d466d2c9fa2dc492021bd1a203c778450dc0dae20af",
|
||||
"url": "https://pbf.jpcn.ccwu.cc/swift/maplibre-particle/6.29.0-particle.25/MapLibre.dynamic.xcframework.zip"
|
||||
"sizeBytes": 4187287,
|
||||
"sha256": "577d0784f1e436c04b3cf0bd0213ce38fdf14612898367ec6e7c73156b82cd74",
|
||||
"url": "https://pbf.jpcn.ccwu.cc/swift/maplibre-particle/6.29.0-particle.28/MapLibre.dynamic.xcframework.zip"
|
||||
},
|
||||
"swiftPackage": {
|
||||
"url": "https://pbf.jpcn.ccwu.cc/swift/maplibre-particle-spm.git",
|
||||
"requirement": "exactVersion",
|
||||
"version": "6.29.0-particle.25",
|
||||
"version": "6.29.0-particle.28",
|
||||
"productName": "MapLibre"
|
||||
},
|
||||
"xcode": "Xcode 26.3 Build version 17C529 ",
|
||||
|
||||
@@ -5,14 +5,19 @@
|
||||
#include <mln/util/tiny_unordered_map.hpp>
|
||||
|
||||
#include <memory>
|
||||
#include <cstddef>
|
||||
#include <utility>
|
||||
|
||||
namespace mln {
|
||||
class TileAtlasTextures;
|
||||
namespace style {
|
||||
enum class SymbolType : uint8_t;
|
||||
} // namespace style
|
||||
namespace gfx {
|
||||
class DrawableAtlasesTweaker;
|
||||
|
||||
struct SymbolDrawableData : public DrawableData {
|
||||
static constexpr std::size_t drawableType = 1;
|
||||
SymbolDrawableData(const bool isHalo_,
|
||||
const bool bucketVariablePlacement_,
|
||||
const style::SymbolType symbolType_,
|
||||
@@ -20,7 +25,9 @@ struct SymbolDrawableData : public DrawableData {
|
||||
const style::AlignmentType rotationAlignment_,
|
||||
const style::SymbolPlacementType placement_,
|
||||
const style::IconTextFitType textFit_,
|
||||
const bool isOffset_)
|
||||
const bool isOffset_,
|
||||
std::shared_ptr<TileAtlasTextures> atlases_,
|
||||
std::shared_ptr<DrawableAtlasesTweaker> atlasTweaker_)
|
||||
: isHalo(isHalo_),
|
||||
bucketVariablePlacement(bucketVariablePlacement_),
|
||||
symbolType(symbolType_),
|
||||
@@ -28,7 +35,9 @@ struct SymbolDrawableData : public DrawableData {
|
||||
rotationAlignment(rotationAlignment_),
|
||||
placement(placement_),
|
||||
textFit(textFit_),
|
||||
isOffset(isOffset_) {}
|
||||
isOffset(isOffset_),
|
||||
atlases(std::move(atlases_)),
|
||||
atlasTweaker(std::move(atlasTweaker_)) {}
|
||||
~SymbolDrawableData() override = default;
|
||||
|
||||
const bool isHalo;
|
||||
@@ -39,6 +48,10 @@ struct SymbolDrawableData : public DrawableData {
|
||||
const style::SymbolPlacementType placement;
|
||||
const style::IconTextFitType textFit;
|
||||
const bool isOffset;
|
||||
// Paired at creation with the actual native atlas tweaker. Neither
|
||||
// resource owns RenderTile; its current container is bound separately.
|
||||
const std::shared_ptr<TileAtlasTextures> atlases;
|
||||
const std::shared_ptr<DrawableAtlasesTweaker> atlasTweaker;
|
||||
};
|
||||
|
||||
using UniqueSymbolDrawableData = std::unique_ptr<SymbolDrawableData>;
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
#include <mln/renderer/layers/collision_layer_tweaker.hpp>
|
||||
|
||||
#include <set>
|
||||
#include <unordered_set>
|
||||
|
||||
namespace mln {
|
||||
|
||||
@@ -195,14 +196,96 @@ bool RenderSymbolLayer::hasCrossfade() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
void RenderSymbolLayer::setRenderTiles(Immutable<std::vector<RenderTile>> owner, RenderTiles sorted) {
|
||||
renderTilesOwner = std::move(owner);
|
||||
renderTiles = std::move(sorted);
|
||||
currentTileBindings.clear();
|
||||
renderTilesSameSource = false;
|
||||
if (!renderTilesOwner.get() || !renderTiles) return;
|
||||
|
||||
std::unordered_set<const RenderTile*> addresses;
|
||||
addresses.reserve(renderTilesOwner->size());
|
||||
for (const auto& tile : *renderTilesOwner) addresses.insert(&tile);
|
||||
for (const auto& ref : *renderTiles) {
|
||||
if (!addresses.contains(&ref.get())) {
|
||||
// Do not dereference a reference from another prepare generation.
|
||||
renderTiles.reset();
|
||||
return;
|
||||
}
|
||||
}
|
||||
for (const auto& ref : *renderTiles) {
|
||||
const auto& tile = ref.get();
|
||||
currentTileBindings.emplace(&tile, TileBinding{tile.getOverscaledTileID(), tile.getAtlasTextures()});
|
||||
}
|
||||
renderTilesSameSource = true;
|
||||
}
|
||||
|
||||
bool RenderSymbolLayer::bindRenderTile(gfx::Drawable& drawable, const RenderTile& tile) {
|
||||
const auto it = currentTileBindings.find(&tile);
|
||||
if (!renderTilesSameSource || it == currentTileBindings.end() ||
|
||||
!drawable.getTileID() || *drawable.getTileID() != it->second.id) return false;
|
||||
drawable.setRenderTile(renderTilesOwner, &tile);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RenderSymbolLayer::rebindTileDrawables(const RenderTile& tile,
|
||||
const std::shared_ptr<Bucket>& bucket,
|
||||
const TileAtlasTexturesPtr& atlases) {
|
||||
const auto binding = currentTileBindings.find(&tile);
|
||||
if (!renderTilesSameSource || binding == currentTileBindings.end()) return false;
|
||||
const auto& tileID = binding->second.id;
|
||||
auto* group = static_cast<TileLayerGroup*>(layerGroup.get());
|
||||
if (!group) return false;
|
||||
const auto count = group->getDrawableCount(passes, tileID);
|
||||
bool compatible = bucket && layerTweaker && atlases && binding->second.atlases == atlases;
|
||||
group->visitDrawables(passes, tileID, [&](gfx::Drawable& drawable) {
|
||||
if (drawable.getLayerTweaker() != layerTweaker || drawable.getBucket() != bucket ||
|
||||
drawable.getType() != gfx::SymbolDrawableData::drawableType || !drawable.getData() ||
|
||||
!drawable.getTileID() || *drawable.getTileID() != tileID ||
|
||||
drawable.getRenderPass() != passes) {
|
||||
compatible = false;
|
||||
return;
|
||||
}
|
||||
const auto& data = static_cast<const gfx::SymbolDrawableData&>(*drawable.getData());
|
||||
if (!data.atlasTweaker || data.atlases != atlases || drawable.getTweakers().size() != 1 ||
|
||||
drawable.getTweakers().front() != data.atlasTweaker) compatible = false;
|
||||
if (!atlases || (data.symbolType == SymbolType::Text ? !atlases->glyph : !atlases->icon) ||
|
||||
(data.symbolType != SymbolType::Text && data.symbolType != SymbolType::IconRGBA &&
|
||||
data.symbolType != SymbolType::IconSDF)) compatible = false;
|
||||
});
|
||||
if (!compatible || group->getDrawableCount(passes, tileID) != count) {
|
||||
// updateTile() means ANY updated. Preflight the entire text/icon/halo
|
||||
// group before changing any member, so partial reuse cannot survive.
|
||||
removeTile(passes, tileID);
|
||||
return false;
|
||||
}
|
||||
if (!count) return false;
|
||||
group->visitDrawables(passes, tileID, [&](gfx::Drawable& drawable) {
|
||||
if (!bindRenderTile(drawable, tile)) compatible = false;
|
||||
});
|
||||
if (!compatible || group->getDrawableCount(passes, tileID) != count) {
|
||||
removeTile(passes, tileID);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void RenderSymbolLayer::prepare(const LayerPrepareParameters& params) {
|
||||
renderTiles = params.source->getRenderTilesSortedByYPosition();
|
||||
// Keep the Y order and fading tiles of the Symbol source path, while
|
||||
// taking both views from the same completed source prepare.
|
||||
auto owner = params.source->getRawRenderTiles();
|
||||
auto sorted = params.source->getRenderTilesSortedByYPosition();
|
||||
setRenderTiles(std::move(owner), std::move(sorted));
|
||||
placementData.clear();
|
||||
if (!renderTilesSameSource) {
|
||||
renderTileIDs.clear();
|
||||
removeAllDrawables();
|
||||
return;
|
||||
}
|
||||
|
||||
updateRenderTileIDs();
|
||||
addRenderPassesFromTiles();
|
||||
|
||||
placementData.clear();
|
||||
|
||||
for (const RenderTile& renderTile : *renderTiles) {
|
||||
auto* bucket = static_cast<SymbolBucket*>(renderTile.getBucket(*baseImpl));
|
||||
if (bucket && bucket->bucketLeaderID == getID() && static_cast<Bucket*>(bucket)->check(SYM_GUARD_LOC)) {
|
||||
@@ -475,6 +558,53 @@ std::size_t RenderSymbolLayer::removeAllDrawables() {
|
||||
return stats.drawablesRemoved - oldValue;
|
||||
}
|
||||
|
||||
bool RenderSymbolLayer::appendRetainedTileResult(SymbolRenderResult& result, const gfx::Drawable& drawable) const {
|
||||
// Only compare a possibly old pointer as an address. IDs were copied while
|
||||
// source prepare owned a live Tile; never call through the old RenderTile.
|
||||
const auto it = currentTileBindings.find(drawable.getRenderTile());
|
||||
if (!renderTilesSameSource || it == currentTileBindings.end() || !drawable.getTileID() ||
|
||||
*drawable.getTileID() != it->second.id ||
|
||||
drawable.getType() != gfx::SymbolDrawableData::drawableType || !drawable.getData()) {
|
||||
result.complete = false;
|
||||
return false;
|
||||
}
|
||||
const auto& data = static_cast<const gfx::SymbolDrawableData&>(*drawable.getData());
|
||||
const auto& atlases = data.atlases;
|
||||
if ((data.symbolType != SymbolType::Text && data.symbolType != SymbolType::IconRGBA &&
|
||||
data.symbolType != SymbolType::IconSDF) ||
|
||||
!atlases || atlases != it->second.atlases || !data.atlasTweaker ||
|
||||
drawable.getTweakers().size() != 1 || drawable.getTweakers().front() != data.atlasTweaker ||
|
||||
!drawable.getShader()) {
|
||||
result.complete = false;
|
||||
return false;
|
||||
}
|
||||
const auto& expected = data.symbolType == SymbolType::Text ? atlases->glyph : atlases->icon;
|
||||
if (!expected || expected->needsUpload() ||
|
||||
drawable.getTexture(shaders::idSymbolImageTexture) != expected ||
|
||||
(drawable.getShader()->getSamplerLocation(shaders::idSymbolImageIconTexture) &&
|
||||
(!atlases->icon || atlases->icon->needsUpload() ||
|
||||
drawable.getTexture(shaders::idSymbolImageIconTexture) != atlases->icon))) {
|
||||
result.complete = false;
|
||||
return false;
|
||||
}
|
||||
result.retain(atlases);
|
||||
result.retain(atlases->glyph);
|
||||
result.retain(atlases->icon);
|
||||
for (std::size_t id = 0; id < shaders::maxTextureCountPerShader; ++id) {
|
||||
const auto& texture = drawable.getTexture(id);
|
||||
if (texture && texture->needsUpload()) {
|
||||
result.complete = false;
|
||||
return false;
|
||||
}
|
||||
result.retain(texture);
|
||||
if (texture) {
|
||||
result.append(texture->getSize());
|
||||
result.append(texture->getFormat());
|
||||
}
|
||||
}
|
||||
return result.complete;
|
||||
}
|
||||
|
||||
SymbolRenderResult RenderSymbolLayer::retainedRenderResult(float fadeChange, uint64_t frame) {
|
||||
SymbolRenderResult result;
|
||||
result.append(layerIndex);
|
||||
@@ -483,7 +613,13 @@ SymbolRenderResult RenderSymbolLayer::retainedRenderResult(float fadeChange, uin
|
||||
// A hidden layer is admitted only after processChanges has removed both
|
||||
// groups (the orchestrator verifies registry membership separately).
|
||||
if (!isLayerRenderable()) return result;
|
||||
if (collisionTileLayerGroup && !collisionTileLayerGroup->empty()) result.complete = false;
|
||||
if (!renderTilesSameSource || (collisionTileLayerGroup && !collisionTileLayerGroup->empty())) {
|
||||
result.complete = false;
|
||||
return result;
|
||||
}
|
||||
// Keep TileRenderData/LayoutResult (including atlas slots) alive across
|
||||
// completed Full evidence. The per-prepare owner address is NOT an input.
|
||||
result.resources.emplace_back(std::make_shared<Immutable<std::vector<RenderTile>>>(renderTilesOwner));
|
||||
if (!layerGroup || layerGroup->empty()) {
|
||||
// Empty is provable only if there are no renderable Symbol buckets.
|
||||
if (renderTiles) for (const auto& tileRef : *renderTiles) {
|
||||
@@ -501,7 +637,10 @@ SymbolRenderResult RenderSymbolLayer::retainedRenderResult(float fadeChange, uin
|
||||
// RenderSymbolLayer is final and creates only this tweaker and the native
|
||||
// atlas tweaker. Unknown placement-capable layers never call this method.
|
||||
const auto& tweaker = static_cast<const SymbolLayerTweaker&>(*layerTweaker);
|
||||
if (tweaker.getRenderResultFrame() != frame) result.complete = false;
|
||||
if (tweaker.getRenderResultFrame() != frame) {
|
||||
result.complete = false;
|
||||
return result;
|
||||
}
|
||||
result.appendResult(tweaker.getRenderResult());
|
||||
result.retain(layerGroup);
|
||||
result.append(layerGroup->getEnabled());
|
||||
@@ -509,12 +648,14 @@ SymbolRenderResult RenderSymbolLayer::retainedRenderResult(float fadeChange, uin
|
||||
std::size_t visited = 0;
|
||||
visitLayerGroupDrawables(*layerGroup, [&](gfx::Drawable& drawable) {
|
||||
++visited;
|
||||
if (!result.complete) return;
|
||||
if (drawable.getIsCustom() || drawable.getLayerTweaker() != layerTweaker ||
|
||||
!drawable.getBucket() || !drawable.getTileID() || !drawable.getData() ||
|
||||
!drawable.getShader() || drawable.getTweakers().size() != 1) {
|
||||
result.complete = false;
|
||||
return;
|
||||
}
|
||||
if (!appendRetainedTileResult(result, drawable)) return;
|
||||
result.append(drawable.getID());
|
||||
if (!SymbolShaderProvenance::get().supports(*drawable.getShader())) result.complete = false;
|
||||
result.retain(drawable.getTweakers().front()); // immutable native atlas/sampler policy
|
||||
@@ -588,29 +729,6 @@ SymbolRenderResult RenderSymbolLayer::retainedRenderResult(float fadeChange, uin
|
||||
if (indices->empty()) result.complete = false;
|
||||
}
|
||||
}
|
||||
const auto* tile = drawable.getRenderTile();
|
||||
const auto& atlases = tile ? tile->getAtlasTextures() : TileAtlasTexturesPtr{};
|
||||
if (!atlases) {
|
||||
result.complete = false;
|
||||
return;
|
||||
}
|
||||
result.retain(atlases);
|
||||
result.retain(atlases->glyph);
|
||||
result.retain(atlases->icon);
|
||||
for (std::size_t id = 0; id < shaders::maxTextureCountPerShader; ++id) {
|
||||
const auto& texture = drawable.getTexture(id);
|
||||
result.retain(texture);
|
||||
if (texture) {
|
||||
result.append(texture->getSize());
|
||||
result.append(texture->getFormat());
|
||||
if (texture->needsUpload()) result.complete = false;
|
||||
}
|
||||
}
|
||||
const auto& expected = data.symbolType == SymbolType::Text ? atlases->glyph : atlases->icon;
|
||||
if (!expected || expected->needsUpload() ||
|
||||
drawable.getTexture(shaders::idSymbolImageTexture) != expected) result.complete = false;
|
||||
if (drawable.getShader()->getSamplerLocation(shaders::idSymbolImageIconTexture) &&
|
||||
drawable.getTexture(shaders::idSymbolImageIconTexture) != atlases->icon) result.complete = false;
|
||||
});
|
||||
if (visited != layerGroup->getDrawableCount()) result.complete = false;
|
||||
return result;
|
||||
@@ -623,7 +741,7 @@ void RenderSymbolLayer::update(gfx::ShaderRegistry& shaders,
|
||||
const PaintParameters&,
|
||||
const RenderTree&,
|
||||
UniqueChangeRequestVec& changes) {
|
||||
if (!renderTiles || renderTiles->empty() || passes == RenderPass::None) {
|
||||
if (!renderTilesSameSource || !renderTiles || renderTiles->empty() || passes == RenderPass::None) {
|
||||
removeAllDrawables();
|
||||
return;
|
||||
}
|
||||
@@ -732,6 +850,12 @@ void RenderSymbolLayer::update(gfx::ShaderRegistry& shaders,
|
||||
const auto& renderData = *optRenderData;
|
||||
const auto& bucket = static_cast<const SymbolBucket&>(*renderData.bucket);
|
||||
const auto& evaluated = getEvaluated<SymbolLayerProperties>(renderData.layerProperties);
|
||||
const auto& atlases = tile.getAtlasTextures();
|
||||
if (!atlases || (bucket.hasTextData() && !atlases->glyph) ||
|
||||
((bucket.hasIconData() || bucket.hasSdfIconData() || bucket.iconsInText) && !atlases->icon)) {
|
||||
removeTile(passes, tileID);
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto prevBucketID = getRenderTileBucketID(tileID);
|
||||
if (prevBucketID != util::SimpleIdentity::Empty && prevBucketID != bucket.getID()) {
|
||||
@@ -807,18 +931,14 @@ void RenderSymbolLayer::update(gfx::ShaderRegistry& shaders,
|
||||
// If we already have drawables for this tile, update them.
|
||||
// Just update the drawables we already created
|
||||
auto updateExisting = [&](gfx::Drawable& drawable) {
|
||||
if (drawable.getLayerTweaker() != layerTweaker) {
|
||||
// This drawable was produced on a previous style/bucket, and should not be updated.
|
||||
return false;
|
||||
}
|
||||
|
||||
propertiesAsUniforms.first.clear();
|
||||
propertiesAsUniforms.second.clear();
|
||||
|
||||
updateTileDrawable(drawable, bucket, bucketPaintProperties, evaluated);
|
||||
return true;
|
||||
};
|
||||
if (updateTile(passes, tileID, std::move(updateExisting))) {
|
||||
if (rebindTileDrawables(tile, renderData.bucket, atlases) &&
|
||||
updateTile(passes, tileID, std::move(updateExisting))) {
|
||||
// re-create collision drawables
|
||||
if (collisionTileLayerGroup) {
|
||||
collisionTileLayerGroup->removeDrawables(passes, tileID);
|
||||
@@ -855,11 +975,6 @@ void RenderSymbolLayer::update(gfx::ShaderRegistry& shaders,
|
||||
}
|
||||
};
|
||||
|
||||
const auto& atlases = tile.getAtlasTextures();
|
||||
if (!atlases) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (bucket.hasIconData() && atlases->icon) {
|
||||
addRenderables(bucket.icon, SymbolType::IconRGBA);
|
||||
}
|
||||
@@ -879,7 +994,7 @@ void RenderSymbolLayer::update(gfx::ShaderRegistry& shaders,
|
||||
using RawVertexVec = std::vector<std::uint8_t>; // raw buffer for vertexes of <int16_t, 4>
|
||||
struct TileInfo {
|
||||
RawVertexVec textVertices, iconVertices;
|
||||
gfx::DrawableTweakerPtr textTweaker, iconTweaker;
|
||||
std::shared_ptr<gfx::DrawableAtlasesTweaker> textTweaker, iconTweaker;
|
||||
};
|
||||
|
||||
mln::unordered_map<UnwrappedTileID, TileInfo> tileCache;
|
||||
@@ -1031,13 +1146,14 @@ void RenderSymbolLayer::update(gfx::ShaderRegistry& shaders,
|
||||
for (auto& drawable : builder->clearDrawables()) {
|
||||
drawable->setTileID(tileID);
|
||||
drawable->setLayerTweaker(layerTweaker);
|
||||
drawable->setRenderTile(renderTilesOwner, &tile);
|
||||
if (!bindRenderTile(*drawable, tile)) continue;
|
||||
|
||||
PaintPropertyBindersBase& textBinders = bucketPaintProperties.textBinders;
|
||||
PaintPropertyBindersBase& iconBinders = bucketPaintProperties.iconBinders;
|
||||
drawable->setBinders(renderable.renderData.bucket, isText ? &textBinders : &iconBinders);
|
||||
|
||||
const bool hasVariablePlacement = bucket.hasVariablePlacement;
|
||||
drawable->setType(gfx::SymbolDrawableData::drawableType);
|
||||
drawable->setData(std::make_unique<gfx::SymbolDrawableData>(
|
||||
/*.isHalo=*/isHalo,
|
||||
/*.bucketVaraiblePlacement=*/hasVariablePlacement,
|
||||
@@ -1046,7 +1162,9 @@ void RenderSymbolLayer::update(gfx::ShaderRegistry& shaders,
|
||||
/*.rotationAlignment=*/values.rotationAlignment,
|
||||
/*.placement=*/bucketLayout.get<SymbolPlacement>(),
|
||||
/*.textFit=*/bucketLayout.get<IconTextFit>(),
|
||||
/*.isOffset=*/isOffset));
|
||||
/*.isOffset=*/isOffset,
|
||||
atlases,
|
||||
isText ? tileInfo.textTweaker : tileInfo.iconTweaker));
|
||||
|
||||
tileLayerGroup->addDrawable(passes, tileID, std::move(drawable));
|
||||
++stats.drawablesAdded;
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include <unordered_map>
|
||||
|
||||
namespace mln {
|
||||
class TileAtlasTextures;
|
||||
|
||||
namespace style {
|
||||
|
||||
@@ -82,6 +83,20 @@ protected:
|
||||
std::size_t removeTile(RenderPass, const OverscaledTileID &) override;
|
||||
|
||||
private:
|
||||
friend struct SymbolRetainedLifetimeTestAccess;
|
||||
// Validate addresses before reading any reference-based tile input.
|
||||
void setRenderTiles(Immutable<std::vector<RenderTile>>, RenderTiles);
|
||||
bool bindRenderTile(gfx::Drawable&, const RenderTile&);
|
||||
bool appendRetainedTileResult(SymbolRenderResult&, const gfx::Drawable&) const;
|
||||
bool rebindTileDrawables(const RenderTile&, const std::shared_ptr<Bucket>&,
|
||||
const std::shared_ptr<TileAtlasTextures>&);
|
||||
struct TileBinding {
|
||||
OverscaledTileID id;
|
||||
std::shared_ptr<TileAtlasTextures> atlases;
|
||||
};
|
||||
std::unordered_map<const RenderTile*, TileBinding> currentTileBindings;
|
||||
bool renderTilesSameSource = false;
|
||||
|
||||
void transition(const TransitionParameters &) override;
|
||||
void evaluate(const PropertyEvaluationParameters &) override;
|
||||
bool hasTransition() const override;
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
namespace mln::shaders {
|
||||
using Source = ShaderSource<BuiltIn::RetainedUnderlayCompositeShader, gfx::Backend::Type::Metal>;
|
||||
const std::array<AttributeInfo, 1> Source::attributes = {
|
||||
AttributeInfo{0, gfx::AttributeDataType::Short2, 0, idRetainedUnderlayCompositePosVertexAttribute},
|
||||
AttributeInfo{0, gfx::AttributeDataType::Short2, maxUBOCountPerShader + 0, idRetainedUnderlayCompositePosVertexAttribute},
|
||||
};
|
||||
const std::array<TextureInfo, 1> Source::textures = {
|
||||
TextureInfo{0, idRetainedUnderlayCompositeTexture},
|
||||
|
||||
@@ -10,6 +10,22 @@
|
||||
#include <mln/renderer/buckets/symbol_bucket.hpp>
|
||||
#include <mln/renderer/symbol_shader_provenance.hpp>
|
||||
#include <mln/shaders/symbol_layer_ubo.hpp>
|
||||
#include <mln/gfx/drawable_atlases_tweaker.hpp>
|
||||
#include <mln/gfx/drawable_impl.hpp>
|
||||
#include <mln/gfx/symbol_drawable_data.hpp>
|
||||
#include <mln/geometry/line_atlas.hpp>
|
||||
#include <mln/renderer/image_manager.hpp>
|
||||
#include <mln/renderer/layers/render_symbol_layer.hpp>
|
||||
#include <mln/renderer/layers/symbol_layer_tweaker.hpp>
|
||||
#include <mln/renderer/paint_parameters.hpp>
|
||||
#include <mln/renderer/pattern_atlas.hpp>
|
||||
#include <mln/renderer/render_source.hpp>
|
||||
#include <mln/renderer/render_tile.hpp>
|
||||
#include <mln/renderer/render_tree.hpp>
|
||||
#include <mln/renderer/tile_render_data.hpp>
|
||||
#include <mln/style/layers/symbol_layer.hpp>
|
||||
#include <mln/style/sources/geojson_source_impl.hpp>
|
||||
#include <mln/tile/tile.hpp>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
@@ -22,6 +38,40 @@
|
||||
|
||||
using namespace mln;
|
||||
|
||||
namespace mln {
|
||||
// Exercise the same internal methods used by prepare/update/result collection,
|
||||
// without a GPU Context or a second copy of their compatibility predicates.
|
||||
struct SymbolRetainedLifetimeTestAccess {
|
||||
static void prepare(RenderSymbolLayer& layer, const LayerPrepareParameters& params) { layer.prepare(params); }
|
||||
static void setTiles(RenderSymbolLayer& layer, Immutable<std::vector<RenderTile>> owner, RenderTiles refs) {
|
||||
layer.setRenderTiles(std::move(owner), std::move(refs));
|
||||
}
|
||||
static void setup(RenderSymbolLayer& layer, std::shared_ptr<TileLayerGroup> group) {
|
||||
layer.layerGroup = std::move(group);
|
||||
layer.passes = RenderPass::Translucent;
|
||||
layer.layerTweaker = std::make_shared<SymbolLayerTweaker>(layer.getID(), layer.evaluatedProperties);
|
||||
layer.isRenderable = true;
|
||||
}
|
||||
static const LayerTweakerPtr& tweaker(const RenderSymbolLayer& layer) { return layer.layerTweaker; }
|
||||
static bool bind(RenderSymbolLayer& layer, gfx::Drawable& drawable, const RenderTile& tile) {
|
||||
return layer.bindRenderTile(drawable, tile);
|
||||
}
|
||||
static bool reuse(RenderSymbolLayer& layer, const RenderTile& tile, const std::shared_ptr<Bucket>& bucket,
|
||||
const std::shared_ptr<TileAtlasTextures>& atlases) {
|
||||
return layer.rebindTileDrawables(tile, bucket, atlases);
|
||||
}
|
||||
static SymbolRenderResult atlasResult(const RenderSymbolLayer& layer, const gfx::Drawable& drawable) {
|
||||
SymbolRenderResult result;
|
||||
layer.appendRetainedTileResult(result, drawable);
|
||||
return result;
|
||||
}
|
||||
static void removeLayer(RenderSymbolLayer& layer) {
|
||||
UniqueChangeRequestVec changes;
|
||||
layer.layerRemoved(changes);
|
||||
}
|
||||
};
|
||||
} // namespace mln
|
||||
|
||||
namespace {
|
||||
RetainedUnderlayKey key(Size size = {640, 480}, int32_t boundary = 4) {
|
||||
RetainedUnderlayKey result{size, 0, boundary, {}};
|
||||
@@ -2109,3 +2159,425 @@ TEST(FastGateTrace, RetainedSamplesHaveDistinctPhasesAndExplicitLimit) {
|
||||
EXPECT_EQ(frame.post.retained[0].cache.build, 1u);
|
||||
EXPECT_EQ(frame.post.retained[0].cache.sourceRevision, 7u);
|
||||
}
|
||||
|
||||
namespace {
|
||||
class LifetimeUniformBuffers final : public gfx::UniformBufferArray {
|
||||
public:
|
||||
void bind(gfx::RenderPass&) override {}
|
||||
protected:
|
||||
std::unique_ptr<gfx::UniformBuffer> copy(const gfx::UniformBuffer&) override { return nullptr; }
|
||||
};
|
||||
|
||||
class LifetimeDrawable final : public gfx::Drawable {
|
||||
public:
|
||||
LifetimeDrawable() : Drawable("symbol-lifetime") {}
|
||||
void draw(PaintParameters&) const override {}
|
||||
void updateVertexAttributes(gfx::VertexAttributeArrayPtr, std::size_t, gfx::DrawMode,
|
||||
gfx::IndexVectorBasePtr, const SegmentBase*, std::size_t) override {}
|
||||
void setVertices(std::vector<uint8_t>&&, std::size_t, gfx::AttributeDataType) override {}
|
||||
void setIndexData(gfx::IndexVectorBasePtr, std::vector<UniqueDrawSegment>) override {}
|
||||
const gfx::UniformBufferArray& getUniformBuffers() const override { return buffers; }
|
||||
gfx::UniformBufferArray& mutableUniformBuffers() override { return buffers; }
|
||||
private:
|
||||
LifetimeUniformBuffers buffers;
|
||||
};
|
||||
|
||||
class LifetimeGroup final : public TileLayerGroup {
|
||||
public:
|
||||
LifetimeGroup() : TileLayerGroup(0, 4, "symbol-lifetime") {}
|
||||
const gfx::UniformBufferArray& getUniformBuffers() const override { return buffers; }
|
||||
gfx::UniformBufferArray& mutableUniformBuffers() override { return buffers; }
|
||||
private:
|
||||
LifetimeUniformBuffers buffers;
|
||||
};
|
||||
|
||||
class LifetimeTexture final : public gfx::Texture2D {
|
||||
public:
|
||||
Texture2D& setSamplerConfiguration(const SamplerState&) noexcept override { return *this; }
|
||||
Texture2D& setFormat(gfx::TexturePixelType, gfx::TextureChannelDataType) noexcept override { return *this; }
|
||||
Texture2D& setSize(Size value) noexcept override { size = value; return *this; }
|
||||
Texture2D& setImage(std::shared_ptr<PremultipliedImage>) noexcept override { dirty = true; return *this; }
|
||||
gfx::TexturePixelType getFormat() const noexcept override { return gfx::TexturePixelType::RGBA; }
|
||||
Size getSize() const noexcept override { return size; }
|
||||
size_t getDataSize() const noexcept override { return size.width * size.height * 4; }
|
||||
size_t getPixelStride() const noexcept override { return 4; }
|
||||
size_t numChannels() const noexcept override { return 4; }
|
||||
void create() override {}
|
||||
void upload(const void*, const Size&) override { dirty = false; }
|
||||
void uploadSubRegion(const void*, const Size&, uint16_t, uint16_t) override { dirty = true; }
|
||||
void upload() override { dirty = false; }
|
||||
bool needsUpload() const noexcept override { return dirty; }
|
||||
bool dirty = false;
|
||||
private:
|
||||
Size size{8, 8};
|
||||
};
|
||||
|
||||
class LifetimeTileData final : public TileRenderData {
|
||||
public:
|
||||
LifetimeTileData(std::shared_ptr<TileAtlasTextures> atlases, std::shared_ptr<int> live_)
|
||||
: TileRenderData(std::move(atlases)), live(std::move(live_)) { ++*live; }
|
||||
~LifetimeTileData() override { --*live; }
|
||||
private:
|
||||
// This fixture supplies atlas-only render data. The base implementations
|
||||
// assert: prepare() and the empty-group collector must dispatch here.
|
||||
const LayerRenderData* getLayerRenderData(const style::Layer::Impl&) const override { return nullptr; }
|
||||
Bucket* getBucket(const style::Layer::Impl&) const override { return nullptr; }
|
||||
std::shared_ptr<int> live;
|
||||
};
|
||||
|
||||
class LifetimeTile final : public Tile {
|
||||
public:
|
||||
LifetimeTile(std::shared_ptr<int> liveData_, std::shared_ptr<int> liveTiles_)
|
||||
: Tile(Kind::Geometry, OverscaledTileID{2, 0, {2, 1, 1}}, "symbol-lifetime-source"),
|
||||
liveData(std::move(liveData_)), liveTiles(std::move(liveTiles_)) { ++*liveTiles; }
|
||||
~LifetimeTile() override { --*liveTiles; }
|
||||
std::unique_ptr<TileRenderData> createRenderData() override {
|
||||
return std::make_unique<LifetimeTileData>(atlases, liveData);
|
||||
}
|
||||
void cancel() override {}
|
||||
bool layerPropertiesUpdated(const Immutable<style::LayerProperties>&) override { return true; }
|
||||
bool holdForFade() const override { return true; }
|
||||
std::shared_ptr<TileAtlasTextures> atlases = std::make_shared<TileAtlasTextures>();
|
||||
private:
|
||||
std::shared_ptr<int> liveData, liveTiles;
|
||||
};
|
||||
|
||||
class LifetimeSource final : public RenderSource {
|
||||
public:
|
||||
LifetimeSource(std::shared_ptr<int> liveData, std::shared_ptr<int> liveTiles)
|
||||
: RenderSource(makeMutable<style::GeoJSONSource::Impl>(
|
||||
"symbol-lifetime-source", style::GeoJSONOptions::defaultOptions())),
|
||||
tile(std::make_unique<LifetimeTile>(std::move(liveData), std::move(liveTiles))) {}
|
||||
bool isLoaded() const override { return true; }
|
||||
void update(Immutable<style::Source::Impl>, const std::vector<Immutable<style::LayerProperties>>&,
|
||||
bool, bool, const TileParameters&) override {}
|
||||
std::unique_ptr<RenderItem> createRenderItem() override { return nullptr; }
|
||||
void prepare(const SourcePrepareParameters& params) override {
|
||||
auto tiles = makeMutable<std::vector<RenderTile>>();
|
||||
tiles->emplace_back(tile->id.toUnwrapped(), *tile);
|
||||
tiles->back().prepare(params);
|
||||
owner = std::move(tiles);
|
||||
sorted = std::make_shared<std::vector<std::reference_wrapper<const RenderTile>>>(
|
||||
std::initializer_list<std::reference_wrapper<const RenderTile>>{owner->front()});
|
||||
}
|
||||
Immutable<std::vector<RenderTile>> getRawRenderTiles() const override { return owner; }
|
||||
RenderTiles getRenderTilesSortedByYPosition() const override { return sorted; }
|
||||
// Deliberately excludes the fading tile, unlike the Symbol path.
|
||||
RenderTiles getRenderTiles() const override {
|
||||
return std::make_shared<std::vector<std::reference_wrapper<const RenderTile>>>();
|
||||
}
|
||||
void updateFadingTiles() override {}
|
||||
bool hasFadingTiles() const override { return true; }
|
||||
std::unordered_map<std::string, std::vector<Feature>> queryRenderedFeatures(
|
||||
const ScreenLineString&, const TransformState&, const std::unordered_map<std::string, const RenderLayer*>&,
|
||||
const RenderedQueryOptions&, const mat4&) const override { return {}; }
|
||||
std::vector<Feature> querySourceFeatures(const SourceQueryOptions&) const override { return {}; }
|
||||
void reduceMemoryUse() override {}
|
||||
void dumpDebugLogs() const override {}
|
||||
std::unique_ptr<LifetimeTile> tile;
|
||||
Immutable<std::vector<RenderTile>> owner = makeMutable<std::vector<RenderTile>>();
|
||||
RenderTiles sorted;
|
||||
};
|
||||
|
||||
class SymbolRetainedLifetime : public ::testing::Test {
|
||||
protected:
|
||||
SymbolRetainedLifetime()
|
||||
: styleLayer("symbol-lifetime", "symbol-lifetime-source"),
|
||||
layer(staticImmutableCast<style::SymbolLayer::Impl>(styleLayer.baseImpl)) {
|
||||
state.setSize({640, 480});
|
||||
source = std::make_unique<LifetimeSource>(liveData, liveTiles);
|
||||
source->tile->atlases->glyph = std::make_shared<LifetimeTexture>();
|
||||
source->tile->atlases->icon = std::make_shared<LifetimeTexture>();
|
||||
SymbolRetainedLifetimeTestAccess::setup(layer, group);
|
||||
prepare();
|
||||
}
|
||||
void prepare() {
|
||||
TransformParameters transform(state);
|
||||
const auto debug = MapDebugOptions::NoDebug;
|
||||
source->prepare({transform, debug, *images, "symbol-lifetime-source"});
|
||||
SymbolRetainedLifetimeTestAccess::prepare(layer, {source.get(), *images, patterns, lines, state});
|
||||
}
|
||||
std::shared_ptr<SymbolBucket> makeBucket() {
|
||||
return std::make_shared<SymbolBucket>(makeMutable<style::SymbolLayoutProperties::PossiblyEvaluated>(),
|
||||
std::map<std::string, Immutable<style::LayerProperties>>{}, 16.0f, 1.0f, 0.0f, false, false,
|
||||
"symbol-lifetime", std::vector<SymbolInstance>{}, std::vector<SortKeyRange>{}, 1.0f, false,
|
||||
std::vector<style::TextWritingModeType>{}, false);
|
||||
}
|
||||
LifetimeDrawable& add(const std::shared_ptr<Bucket>& bucket, style::SymbolType type = style::SymbolType::Text,
|
||||
bool halo = false) {
|
||||
auto drawable = std::make_unique<LifetimeDrawable>();
|
||||
auto& ref = *drawable;
|
||||
ref.setTileID(source->tile->id);
|
||||
ref.setRenderPass(RenderPass::Translucent);
|
||||
ref.setLayerTweaker(SymbolRetainedLifetimeTestAccess::tweaker(layer));
|
||||
ref.setBinders(bucket, nullptr); // The production rebind path never reads binders.
|
||||
ref.setShader(std::make_shared<Step119UnknownSymbolProgram>());
|
||||
const auto atlases = source->tile->atlases;
|
||||
auto tweaker = std::make_shared<gfx::DrawableAtlasesTweaker>(atlases,
|
||||
shaders::idSymbolImageIconTexture, shaders::idSymbolImageTexture, type == style::SymbolType::Text,
|
||||
type == style::SymbolType::IconSDF, style::AlignmentType::Viewport, false, true);
|
||||
ref.addTweaker(tweaker);
|
||||
ref.setType(gfx::SymbolDrawableData::drawableType);
|
||||
ref.setData(std::make_unique<gfx::SymbolDrawableData>(halo, false, type, style::AlignmentType::Viewport,
|
||||
style::AlignmentType::Viewport, style::SymbolPlacementType::Point, style::IconTextFitType::None,
|
||||
false, atlases, tweaker));
|
||||
EXPECT_TRUE(SymbolRetainedLifetimeTestAccess::bind(layer, ref, source->owner->front()));
|
||||
tweaker->init(ref); // Actual production texture binding, CPU texture backend only.
|
||||
group->addDrawable(RenderPass::Translucent, source->tile->id, std::move(drawable));
|
||||
return ref;
|
||||
}
|
||||
bool reuse(const std::shared_ptr<Bucket>& bucket) {
|
||||
return SymbolRetainedLifetimeTestAccess::reuse(layer, source->owner->front(), bucket, source->tile->atlases);
|
||||
}
|
||||
void releaseTiles() {
|
||||
SymbolRetainedLifetimeTestAccess::setTiles(layer, makeMutable<std::vector<RenderTile>>(),
|
||||
std::make_shared<std::vector<std::reference_wrapper<const RenderTile>>>());
|
||||
source.reset();
|
||||
}
|
||||
std::shared_ptr<int> liveData = std::make_shared<int>(0), liveTiles = std::make_shared<int>(0);
|
||||
style::SymbolLayer styleLayer;
|
||||
RenderSymbolLayer layer;
|
||||
std::shared_ptr<LifetimeGroup> group = std::make_shared<LifetimeGroup>();
|
||||
TransformState state;
|
||||
std::shared_ptr<ImageManager> images = ImageManager::create();
|
||||
PatternAtlas patterns;
|
||||
LineAtlas lines;
|
||||
std::unique_ptr<LifetimeSource> source;
|
||||
};
|
||||
} // namespace
|
||||
|
||||
TEST_F(SymbolRetainedLifetime, ConsecutiveSourcePrepareRebindsAndReleasesOldRenderData) {
|
||||
const auto bucket = makeBucket();
|
||||
auto& drawable = add(bucket);
|
||||
const auto* oldTile = drawable.getRenderTile();
|
||||
const auto drawableID = drawable.getID();
|
||||
const auto atlasTweaker = drawable.getTweakers().front();
|
||||
prepare();
|
||||
EXPECT_NE(oldTile, &source->owner->front()); // Old owner is still held by drawable.
|
||||
EXPECT_EQ(*liveData, 2);
|
||||
EXPECT_TRUE(reuse(bucket));
|
||||
EXPECT_EQ(drawable.getRenderTile(), &source->owner->front());
|
||||
EXPECT_EQ(*liveData, 1); // No old tree/owner remains after rebind.
|
||||
EXPECT_EQ(drawable.getID(), drawableID);
|
||||
EXPECT_EQ(drawable.getTweakers().front(), atlasTweaker);
|
||||
EXPECT_TRUE(SymbolRetainedLifetimeTestAccess::atlasResult(layer, drawable).complete);
|
||||
EXPECT_TRUE(drawable.getRenderTile()->getAtlasTextures()); // Valid new renderData.
|
||||
}
|
||||
|
||||
TEST_F(SymbolRetainedLifetime, MismatchedOwnerAndElementAreRejectedBeforeReading) {
|
||||
const auto bucket = makeBucket();
|
||||
auto& drawable = add(bucket);
|
||||
const auto validOldOwner = source->owner;
|
||||
const auto oldRefs = source->sorted;
|
||||
prepare();
|
||||
EXPECT_FALSE(SymbolRetainedLifetimeTestAccess::bind(layer, drawable, validOldOwner->front()));
|
||||
EXPECT_FALSE(SymbolRetainedLifetimeTestAccess::atlasResult(layer, drawable).complete);
|
||||
SymbolRetainedLifetimeTestAccess::setTiles(layer, source->owner, oldRefs);
|
||||
EXPECT_FALSE(SymbolRetainedLifetimeTestAccess::bind(layer, drawable, source->owner->front()));
|
||||
EXPECT_FALSE(layer.retainedRenderResult(1, 0).complete);
|
||||
// Keep the counterexample owner live; no dangling pointer is dereferenced.
|
||||
}
|
||||
|
||||
TEST_F(SymbolRetainedLifetime, AtlasReplacementDropsWholeTextIconHaloGroupAndBindsNewTextures) {
|
||||
const auto bucket = makeBucket();
|
||||
add(bucket);
|
||||
add(bucket, style::SymbolType::Text, true);
|
||||
add(bucket, style::SymbolType::IconRGBA);
|
||||
source->tile->atlases = std::make_shared<TileAtlasTextures>();
|
||||
source->tile->atlases->glyph = std::make_shared<LifetimeTexture>();
|
||||
source->tile->atlases->icon = std::make_shared<LifetimeTexture>();
|
||||
prepare();
|
||||
EXPECT_FALSE(reuse(bucket));
|
||||
EXPECT_TRUE(group->empty());
|
||||
auto& rebuilt = add(bucket);
|
||||
EXPECT_EQ(rebuilt.getTexture(shaders::idSymbolImageTexture), source->tile->atlases->glyph);
|
||||
EXPECT_TRUE(SymbolRetainedLifetimeTestAccess::atlasResult(layer, rebuilt).complete);
|
||||
}
|
||||
|
||||
TEST_F(SymbolRetainedLifetime, OneIncompatibleMemberDropsWholeGroup) {
|
||||
for (unsigned mismatch = 0; mismatch < 5; ++mismatch) {
|
||||
const auto bucket = makeBucket();
|
||||
add(bucket);
|
||||
auto& halo = add(bucket, style::SymbolType::Text, true);
|
||||
add(bucket, style::SymbolType::IconRGBA);
|
||||
if (mismatch == 0) halo.setBinders(makeBucket(), nullptr);
|
||||
if (mismatch == 1) halo.setLayerTweaker(nullptr);
|
||||
if (mismatch == 2) halo.clearTweakers();
|
||||
if (mismatch == 3) halo.setData(nullptr);
|
||||
if (mismatch == 4) halo.setType(0);
|
||||
EXPECT_FALSE(reuse(bucket));
|
||||
EXPECT_TRUE(group->empty());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(SymbolRetainedLifetime, MissingAtlasDropsOldDrawables) {
|
||||
const auto bucket = makeBucket();
|
||||
add(bucket);
|
||||
source->tile->atlases.reset();
|
||||
prepare();
|
||||
EXPECT_FALSE(reuse(bucket));
|
||||
EXPECT_TRUE(group->empty());
|
||||
}
|
||||
|
||||
TEST_F(SymbolRetainedLifetime, SameAtlasTextureReplacementRejectsOldBindingAndRequiresNewBuild) {
|
||||
const auto bucket = makeBucket();
|
||||
auto& drawable = add(bucket);
|
||||
auto original = SymbolRetainedLifetimeTestAccess::atlasResult(layer, drawable);
|
||||
ASSERT_TRUE(original.complete);
|
||||
SymbolResultCertificate certificate;
|
||||
const auto binding = step119Binding();
|
||||
step119Settle(certificate, original, binding);
|
||||
const auto atlasTweaker = drawable.getTweakers().front();
|
||||
source->tile->atlases->glyph = std::make_shared<LifetimeTexture>();
|
||||
EXPECT_TRUE(reuse(bucket)); // Same atlas policy is retained and will run normally.
|
||||
auto unbound = SymbolRetainedLifetimeTestAccess::atlasResult(layer, drawable);
|
||||
EXPECT_FALSE(unbound.complete);
|
||||
EXPECT_FALSE(certificate.matchesCandidate(unbound));
|
||||
atlasTweaker->init(drawable);
|
||||
auto rebound = SymbolRetainedLifetimeTestAccess::atlasResult(layer, drawable);
|
||||
EXPECT_TRUE(rebound.complete);
|
||||
EXPECT_NE(rebound.bytes, original.bytes);
|
||||
EXPECT_FALSE(certificate.observeFull(rebound, binding, true));
|
||||
certificate.completeFull(binding, true, false);
|
||||
EXPECT_TRUE(certificate.observeFull(rebound, binding, true));
|
||||
EXPECT_FALSE(certificate.candidateHasBuild(binding));
|
||||
certificate.completeFull(binding, true, true);
|
||||
EXPECT_TRUE(certificate.observeFull(rebound, binding, true));
|
||||
certificate.completeFull(binding, true, false, true);
|
||||
EXPECT_TRUE(certificate.canFast(binding));
|
||||
EXPECT_EQ(drawable.getTweakers().front(), atlasTweaker);
|
||||
}
|
||||
|
||||
TEST_F(SymbolRetainedLifetime, InPlaceTextureUploadRejectsEvidenceUntilUploaded) {
|
||||
const auto bucket = makeBucket();
|
||||
auto& drawable = add(bucket);
|
||||
const auto texture = std::static_pointer_cast<LifetimeTexture>(source->tile->atlases->glyph);
|
||||
texture->uploadSubRegion(nullptr, {1, 1}, 0, 0);
|
||||
EXPECT_FALSE(SymbolRetainedLifetimeTestAccess::atlasResult(layer, drawable).complete);
|
||||
texture->upload();
|
||||
EXPECT_TRUE(SymbolRetainedLifetimeTestAccess::atlasResult(layer, drawable).complete);
|
||||
// Pixel updates AFTER upload additionally require the real content epoch;
|
||||
// Step119SymbolResults.RealTileGlyphImageStyleContextCallbacksRejectBuiltCertificate covers it.
|
||||
}
|
||||
|
||||
TEST_F(SymbolRetainedLifetime, OwnerIdentityDoesNotEnterCertificateAndEvidenceKeepsRenderData) {
|
||||
// Empty native Symbol group still exercises the complete production collector.
|
||||
auto first = layer.retainedRenderResult(1, 0);
|
||||
ASSERT_TRUE(first.complete);
|
||||
prepare();
|
||||
auto second = layer.retainedRenderResult(1, 0);
|
||||
ASSERT_TRUE(second.complete);
|
||||
EXPECT_EQ(first.bytes, second.bytes);
|
||||
EXPECT_EQ(*liveData, 2);
|
||||
SymbolResultCertificate certificate;
|
||||
const auto binding = step119Binding();
|
||||
EXPECT_FALSE(certificate.observeFull(first, binding, true));
|
||||
certificate.completeFull(binding, true, false);
|
||||
EXPECT_TRUE(certificate.observeFull(second, binding, true));
|
||||
certificate.completeFull(binding, true, true);
|
||||
EXPECT_TRUE(certificate.observeFull(second, binding, true));
|
||||
certificate.completeFull(binding, true, false, true);
|
||||
EXPECT_TRUE(certificate.canFast(binding));
|
||||
first = {}; // Old renderData can now leave, despite the old ref-list having existed.
|
||||
EXPECT_EQ(*liveData, 1);
|
||||
second = {};
|
||||
releaseTiles();
|
||||
EXPECT_EQ(*liveTiles, 0); // Container evidence does not own Tile&.
|
||||
EXPECT_EQ(*liveData, 1); // But it owns RenderTile -> TileRenderData.
|
||||
certificate.invalidate();
|
||||
EXPECT_EQ(*liveData, 0);
|
||||
}
|
||||
|
||||
TEST_F(SymbolRetainedLifetime, LayerRemovalAndCertificateInvalidationReleaseResources) {
|
||||
const auto bucket = makeBucket();
|
||||
auto& drawable = add(bucket);
|
||||
std::weak_ptr<TileAtlasTextures> atlas = source->tile->atlases;
|
||||
SymbolResultCertificate certificate;
|
||||
step119Settle(certificate, SymbolRetainedLifetimeTestAccess::atlasResult(layer, drawable), step119Binding());
|
||||
SymbolRetainedLifetimeTestAccess::removeLayer(layer);
|
||||
EXPECT_TRUE(group->empty());
|
||||
releaseTiles();
|
||||
EXPECT_EQ(*liveTiles, 0);
|
||||
EXPECT_EQ(*liveData, 0);
|
||||
EXPECT_FALSE(atlas.expired()); // Retained atlas evidence outlives the removed group.
|
||||
certificate.invalidate();
|
||||
EXPECT_TRUE(atlas.expired());
|
||||
}
|
||||
|
||||
TEST_F(SymbolRetainedLifetime, SameSourceIDReplacementRejectsOldAtlasAndReleasesOldContainer) {
|
||||
const auto bucket = makeBucket();
|
||||
add(bucket);
|
||||
std::weak_ptr<TileAtlasTextures> oldAtlas = source->tile->atlases;
|
||||
source = std::make_unique<LifetimeSource>(liveData, liveTiles); // Same source/tile ID, new Tile and atlas.
|
||||
source->tile->atlases->glyph = std::make_shared<LifetimeTexture>();
|
||||
source->tile->atlases->icon = std::make_shared<LifetimeTexture>();
|
||||
prepare(); // Never invoke a method on the old Tile&, which has already gone.
|
||||
EXPECT_FALSE(reuse(bucket));
|
||||
EXPECT_TRUE(group->empty());
|
||||
EXPECT_TRUE(oldAtlas.expired());
|
||||
EXPECT_EQ(*liveData, 1);
|
||||
auto& rebuilt = add(bucket);
|
||||
EXPECT_TRUE(SymbolRetainedLifetimeTestAccess::atlasResult(layer, rebuilt).complete);
|
||||
}
|
||||
|
||||
TEST_F(SymbolRetainedLifetime, TileLeavesCoverCleanupRemovesDrawable) {
|
||||
const auto bucket = makeBucket();
|
||||
add(bucket);
|
||||
releaseTiles();
|
||||
// The early update branch has no Context dependencies. Use removeAllDrawables,
|
||||
// the production branch's cleanup entry, instead of fabricating a Context reference.
|
||||
EXPECT_EQ(layer.removeAllDrawables(), 1u);
|
||||
EXPECT_TRUE(group->empty());
|
||||
EXPECT_EQ(*liveData, 0);
|
||||
EXPECT_EQ(*liveTiles, 0);
|
||||
}
|
||||
|
||||
TEST_F(SymbolRetainedLifetime, TextIconSdfAndHalosKeepIdentityAcrossRepeatedPrepare) {
|
||||
const auto bucket = makeBucket();
|
||||
add(bucket);
|
||||
add(bucket, style::SymbolType::Text, true);
|
||||
add(bucket, style::SymbolType::IconRGBA);
|
||||
add(bucket, style::SymbolType::IconSDF);
|
||||
add(bucket, style::SymbolType::IconSDF, true);
|
||||
std::vector<util::SimpleIdentity> identities;
|
||||
std::vector<gfx::DrawableTweakerPtr> tweakers;
|
||||
std::vector<std::vector<uint8_t>> inputs;
|
||||
group->visitDrawables([&](gfx::Drawable& drawable) {
|
||||
identities.push_back(drawable.getID());
|
||||
tweakers.push_back(drawable.getTweakers().front());
|
||||
const auto result = SymbolRetainedLifetimeTestAccess::atlasResult(layer, drawable);
|
||||
EXPECT_TRUE(result.complete);
|
||||
inputs.push_back(result.bytes);
|
||||
});
|
||||
for (unsigned generation = 0; generation < 3; ++generation) {
|
||||
prepare();
|
||||
ASSERT_TRUE(reuse(bucket));
|
||||
ASSERT_EQ(group->getDrawableCount(), identities.size());
|
||||
std::size_t index = 0;
|
||||
group->visitDrawables([&](gfx::Drawable& drawable) {
|
||||
EXPECT_EQ(drawable.getID(), identities[index]);
|
||||
EXPECT_EQ(drawable.getTweakers().front(), tweakers[index]);
|
||||
EXPECT_EQ(drawable.getRenderTile(), &source->owner->front());
|
||||
const auto result = SymbolRetainedLifetimeTestAccess::atlasResult(layer, drawable);
|
||||
EXPECT_TRUE(result.complete);
|
||||
EXPECT_EQ(result.bytes, inputs[index]);
|
||||
++index;
|
||||
});
|
||||
EXPECT_EQ(*liveData, 1);
|
||||
}
|
||||
// This checks production binding evidence, not complete GPU scene equivalence.
|
||||
}
|
||||
|
||||
TEST_F(SymbolRetainedLifetime, UnknownSymbolTypeRejectsEvidenceAndDropsWholeGroup) {
|
||||
const auto bucket = makeBucket();
|
||||
add(bucket);
|
||||
auto& drawable = add(bucket, style::SymbolType::Text, true);
|
||||
const auto atlases = source->tile->atlases;
|
||||
const auto tweaker = std::static_pointer_cast<gfx::DrawableAtlasesTweaker>(drawable.getTweakers().front());
|
||||
drawable.setData(std::make_unique<gfx::SymbolDrawableData>(true, false, static_cast<style::SymbolType>(255),
|
||||
style::AlignmentType::Viewport, style::AlignmentType::Viewport, style::SymbolPlacementType::Point,
|
||||
style::IconTextFitType::None, false, atlases, tweaker));
|
||||
EXPECT_FALSE(SymbolRetainedLifetimeTestAccess::atlasResult(layer, drawable).complete);
|
||||
EXPECT_FALSE(reuse(bucket));
|
||||
EXPECT_TRUE(group->empty());
|
||||
}
|
||||
|
||||
28
粒子开发记录.md
28
粒子开发记录.md
@@ -1206,3 +1206,31 @@ Android V3-08 单次综合验收已完成并关闭。Native `13.4.1-particle.2`
|
||||
- 2026-10-11 STEP-119 RDC Android CPU真机续验:用户明确授权 Mac 编译,新 x86_64 APK commit=32ac795fc4abd04006673bf644f87a711329e78f SHA256=2bbd1cb4dc9e03ec56d8404363970e56f76b03819f6f2b50c8aee6dbb6fa43d2,Gradle成功、设备gzip+HTTP传输校验及pm安装成功、preflight SHA/ABI匹配。9次CPU基准中8次有效:Full中位60.67ms/帧、RCR两R中位3.50ms/帧、RCRR三R 7.04ms/帧、RCRRR四R 7.82ms/帧;正向全 Fast、Native末120帧119 Fast/0 Full、24Gate完整失配0。Full-Fast-Full控制漂移2.7%,RCRR相对Full估算CPU/帧降低88.4%。错误宽C Fast0/Full+339、Particle误入R Fast0/Full+322,皆安全拒绝。原采样器shell参数和日志环绕错误经RDC复盘纠正;仅证实固定相机CPU稳态,动态天气/Camera/Source/Tile/Annotation/Context刷新、图像同等、GPU能耗、GTest/Metal/iOS仍未通过。STEP119仍PARTIAL,正式报告docs/报告/STEP-119_RCRR粒子独占C_CPU真机验证_阶段报告_v2.md。
|
||||
|
||||
- 2026-10-12 v25 GSRL 发行记录准备:跳过 v24,Android/RN 13.6.1-particle.25、iOS 6.29.0-particle.25,同源构建提交 f32f6f45fd359007e8b3c77911b66d9e7fc93848。统一构建 exit0、六项制品 NAS 公网回读逐字节 PASS、SwiftPM 发布 PASS;新增 Source Lock 与使用说明,最终 Tag/Push/remote verify 须以后续 Pipeline 结果为准。iOS arm64 预编译未变化资源 Assets.car 的来源/Hash 已另文记录;v25 真机运行 NOT_TESTED,STEP-119 CPU 结果不冒充 v25 运行时验证。
|
||||
|
||||
- 2026-10-12 v25 正式发行 CLOSED / GSRL PASS:Build Source=f32f6f45fd359007e8b3c77911b66d9e7fc93848;统一 Build/Publish/Finalize 均 exit0,六项公网制品重复回读 SHA/size PASS,SwiftPM 6.29.0-particle.25 已发布;正式 Tag maplibre-particle/rn/13.6.1-particle.25 指向 3774d0a8418daa36d7a988b2ce9650a6a1178fc4,远端 particle/main 与 Tag 在 Finalize 后相同。Source Lock 路径 release/maplibre-particle/13.6.1-particle.25/SOURCE_LOCK.json。v25 正式包真机运行、RN Host 联调 NOT_TESTED;不借 STEP119 旧 APK 的 CPU 结果替代。
|
||||
|
||||
|
||||
### 2026-10-12 STEP-119 SymbolRetained 生命周期修复续作
|
||||
|
||||
- 状态 IMPLEMENTED / WAITING_RDC_VALIDATION,接续此前中断四文件修改;Mac.lan particle/main@badb515d590fbdf221751b6677de3da0a5650d7e 保持不变。用户指定 GPT-6.1 Sol High(gpt-6.1-sol/high);本轮工具无执行器元数据、当日 sessions 路径不存在,实际参数未能独立核验,不宣称日志核实,无模型升级/子代理。
|
||||
- 读取 Astra 指导及当前规则/STEP119资料、现有 diff;沿用同源 owner/RenderTile 校验、整组 Bucket/Atlas 失配重建、稳定组刷新绑定与 Retained 强引用取证。证据保活 RenderTile/renderData/LayoutResult,不把 owner 地址写入稳定字节;底层 Tile& 寿命仍独立,原24Gate/证书/缓存与原地内容 epoch 保持。
|
||||
- 续作补 Retained 未知 Symbol 类型 fail-closed;修测试替身未覆盖 TileRenderData 查询导致基类 assert 的缺口,强化 Text/IconRGBA/IconSDF/Halo 连续 prepare 身份、组内类型失配及证书失效释放。SymbolRetainedLifetime 共13项测试源码,真实 GPU/Placement/完整 update 与资源释放仍待 RDC,不能声称实际测试 PASS。
|
||||
- 四源码路径 git diff --check exit0;只做轻量静态检查,未 CMake/编译/语法编译/链接/GTest/Gradle/APK/ADB/安装/Jest,无 commit/push/远端操作。保护 STEP110/111 两份未跟踪任务书及 v25 Source Lock/制品/API/Integration。
|
||||
- 新增 docs/50_Step记录/STEP-119_SymbolRetained生命周期修复_v1.md 与 docs/报告/STEP-119_SymbolRetained生命周期修复_Codex完成报告_v1.md(未覆盖旧版),报告含准确 RDC 命令、源码锁前置条件、FZ-G1 正负及动态生命周期/视觉/释放证据、风险与停止门槛;本记录仅末尾追加。立即交 RDC 独立验证,整体 STEP119 未关闭。
|
||||
|
||||
- 2026-10-13 v26 正式发行 CLOSED / GSRL PASS:Android/RN 13.6.1-particle.26、iOS Metal 6.29.0-particle.26,冻结实际构建源码 fce326a9009d3fc21eb91c5df362f0769163d07e;统一 Android 四 ABI/iOS arm64 真机 Metal/RN 包 Build exit0、Publish exit0、Finalize exit0。六项正式制品 NAS 原子发布及公网二次下载 SHA/size 6/6 PASS;Source Lock release/maplibre-particle/13.6.1-particle.26/SOURCE_LOCK.json,Tag maplibre-particle/rn/13.6.1-particle.26 指向 e3cd9eba3cd02149d431e0061b8d32192d876f78,远端 particle/main Finalize 后同 HEAD。V26 发行二进制 NavSea/FZ-G1 及 iOS 实机未测;STEP119 13 项 C++ GTest 因 Mac libuv 前置依赖未执行,未虚报功能/性能正式全通过。报告 docs/报告/V26_统一正式发行_GSRL_RDC完成报告_v1.md,独立验收 JSON=/tmp/maplibre_v26_independent_receipt_20261013.json。
|
||||
|
||||
### 2026-10-21 STEP-120 iOS 原始 Filter 无损接口调查(BLOCKED)
|
||||
|
||||
- Mac.lan / macOS 15.7.7 / x86_64,particle/main@9d6c9e9684e4c71cc2fb3f08bfb27e1638be4188,原 worktree 与子模块正常;预存未跟踪 STEP-120 任务书保留。用户指定 GPT-6.1 Sol High,当前未取得执行器实际模型/档位元数据,不宣称独立核实;未切模型或启动子代理。
|
||||
- 完整读取当前规则、任务、V27 iOS 6.29.0-particle.27 发行记录及 Darwin/Core 源码。指定既有 JSON 桥接对字符串/键用无长度转换,会截断合法嵌入 NUL;反向整数转 double 也有精度风险。严格无损不能仅靠两个直接包装方法保证,未擅改辅助文件或缩小契约。
|
||||
- 正常 removeLayer 后私有 pendingLayer 仍拥有有效 rawLayer,现有失效宏不能证明已拒绝 detach;已发出 Host 成员关系/Native 拒绝语义澄清,未答复不当授权。SDK 未建立另一套 Context/生命周期状态,也未修改任何源码;公共 API 尚未实现,D4-13 仍 BLOCKED。
|
||||
- 新增 docs/50_Step记录/STEP-120_iOS原始Filter无损Native接口_v1.md 与 docs/报告/STEP-120_iOS原始Filter无损Native接口_Codex完成报告_v1.md,含 all/第二属性/locale、一般等号、原为空安全样例及准确 RDC 单项构建命令。仅静态审查及空白/范围/保护检查,未编译、codegen、单测、设备、发布或 Git 写操作。先明确契约及范围再实现,由 RDC 独立验收;NavSea 为业务唯一主责。
|
||||
|
||||
### 2026-10-21 STEP-120 v2 iOS Filter 无损或拒绝接口实现
|
||||
|
||||
- 状态 IMPLEMENTED_WAITING_RDC_IOS_BUILD;Mac.lan / macOS 15.7.7 / x86_64,particle/main@9d6c9e9684e4c71cc2fb3f08bfb27e1638be4188 保持。用户指定 GPT-6.1 Sol High,工具无执行器实际参数独立证据;未切模型、启动子代理或其他实例。
|
||||
- 按 v2 授权收敛 v1 阻塞,仅改 MLNVectorStyleLayer.h/.mm:公开带 NSError 的 rawStyleFilterJSONObjectWithError: 与 setRawStyleFilterJSONObject:error:,五类错误区分 Unsupported、InvalidLayer、WrongThread、已核实恢复和无法核实恢复。主线程只操作有效 rawLayer,正常 pending/detach 成员关系交 Host 校验,不建另一套 Context。
|
||||
- 局部预检和独立 JSON 快照限制深度64、节点4096、字符串/键 UTF-8 合计256KiB,拒绝NUL、特殊数值/NSDecimalNumber、超出±(2^53-1)、未知/不可重放结构;只归一精确数值,保持布尔、数组、对象键和值。经真实桥接及 Core convert<Filter>/serialize、实际 Expression 比较后才能读出或写入,不走 NSPredicate/NSExpression、不改共享转换器。
|
||||
- setter 先证明新旧 Filter 可重放并复核 Layer 身份,再写入;后验不符/异常恢复原 Filter 并独立核实,无法核实要求 Host 废弃 Context。NSNull 精确还原 Filter();不改变 Paint/Layout/Source/层序,原 predicate/description 字节保持。受限 legacy、隐式断言/常量折叠/省略collator默认项等拒绝边界已写 API Doc 和报告。
|
||||
- 新增 v2 Step 和 docs/报告/STEP-120_iOS原始Filter无损Native接口_Codex完成报告_v2.md,保留 v1 与任务书,提供等值、两属性范围、locale、NUL拒绝和空Filter样例、真实 RDC 单项 Bazel 命令。仅静态源码、空白及保护检查,未语法编译、Bazel/Xcode/Gradle/codegen/Native单测/Jest、设备或发行;没有 stage/commit/push/tag 或分支/worktree 改动。RDC 编译/链接/运行及 ABI/Host 验收待执行,NavSea 原业务总目标仍开放。
|
||||
|
||||
Reference in New Issue
Block a user