Compare commits
10 Commits
maplibre-p
...
particle/m
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a3c3b4e540 | ||
|
|
e48258434e | ||
|
|
4d9cb0ef69 | ||
|
|
1aba9875f0 | ||
|
|
9d6c9e9684 | ||
|
|
3c3dcb9d18 | ||
|
|
7f92f71416 | ||
|
|
970e3ad606 | ||
|
|
a9cea8b581 | ||
|
|
b0f34cc9a2 |
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 目标仍开放。
|
||||
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 或原业务总目标最终完成。
|
||||
13
docs/50_Step记录/STEP-121_V28统一正式发行_v1.md
Normal file
13
docs/50_Step记录/STEP-121_V28统一正式发行_v1.md
Normal file
@@ -0,0 +1,13 @@
|
||||
# STEP-121 V28 统一正式发行记录 v1
|
||||
|
||||
日期:2026-10-21 JST;项目 MapLibreParticleLab(NavSea 多源联调专业支持方)。
|
||||
|
||||
- 前置 STEP-120 iOS Native Filter 无损或拒绝接口通过 RDC Mac Metal Bazel opt 编译;新增两项通用 ObjC API,不更改既有 NSPredicate 行为。原正式 v27 不覆盖。
|
||||
- GSRL 源码提交 1aba9875f07d8e609eb5bf42810a4d8f40d15d3f;统一冻结发行源码 4d9cb0ef69dc4bb946c48377f4e1a5b2ba2bbbe8。
|
||||
- tools/particle_release_pipeline/release.sh 28 prepare/build/publish/finalize 全部退出 0,完整日志位于 /private/tmp/JOINT-PBF-001-V28-GSRL-{prepare,build,publish,finalize}.log。
|
||||
- Android OpenGL 四 ABI(armeabi-v7a/arm64-v8a/x86/x86_64)、Maven Local AAR/POM/module、RN tgz、iOS Metal ios-arm64 XCFramework 均通过统一构建。
|
||||
- NAS 原子发布,六个文件公网逐字节回读,SwiftPM 6.29.0-particle.28 Tag 验证 PASS。
|
||||
- 版本级 release/maplibre-particle/13.6.1-particle.28/README.md 与 SOURCE_LOCK.json 包含六项文件的 SHA-256 和构建来源。
|
||||
- 正式发行最终提交 e48258434e5b9ee40ab1d414c223a4ed809be879;正式 Tag maplibre-particle/rn/13.6.1-particle.28;origin/particle/main 与 Tag push PASS。
|
||||
- 状态 CLOSED / GSRL RELEASED。没有在最终发行 AAR/XCFramework 的真实设备上执行原 Filter 查询/应用/恢复,也没有验证 NavSea MLRN iOS Adapter 与完整四档业务。
|
||||
- 下一唯一主责 NavSea:确认 SwiftPM .28 / RN .28 / Android Maven .28 接线,完成 iOS MLRN 11.5.0 patch Bridge,进行 Mac iOS 与 Android 回归和 Host E2E。不能把发行成功直接升格为 Provider Mapping 受信证明。
|
||||
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。
|
||||
33
docs/报告/STEP-121_V28统一正式发行_RDC完成报告_v1.md
Normal file
33
docs/报告/STEP-121_V28统一正式发行_RDC完成报告_v1.md
Normal file
@@ -0,0 +1,33 @@
|
||||
# STEP-121 V28 统一正式发行 RDC 完成报告 v1
|
||||
|
||||
日期:2026-10-21 JST。执行者 GPT/RDC,Native 实现由 MapLibreParticleLab 自己的 GPT-6.1 Sol High Codex 完成。唯一联调 Lead=NavSea,本报告只证明 MapLibreParticleLab 专业支持方的正式发行。
|
||||
|
||||
## 结果
|
||||
**CLOSED / GSRL RELEASE_PASS**。
|
||||
|
||||
| 必做项 | 结果 |
|
||||
| --- | --- |
|
||||
| STEP-120 两个 SDK 文件与中文报告的 scoped Git 提交 | 已完成,1aba9875f07d8e609eb5bf42810a4d8f40d15d3f |
|
||||
| GSRL prepare 版本冻结与源码提交 | 已完成,4d9cb0ef69dc4bb946c48377f4e1a5b2ba2bbbe8 |
|
||||
| Android 四 ABI Release 与 Maven Local | 已完成,四 ABI 构建均 PASS |
|
||||
| RN tgz 与 iOS Metal Bazel opt XCFramework | 已完成,Build PASS |
|
||||
| Publish NAS 原子发布及六项公网回读 | 已完成,PASS |
|
||||
| SwiftPM Exact 6.29.0-particle.28 | 已完成,公网 Tag PASS |
|
||||
| README/SOURCE_LOCK 与最终 GSRL Manifest | 已完成,发行 Git Tag 所在提交包含 |
|
||||
| Finalize Git Tag/Push | 已完成,e48258434e5b9ee40ab1d414c223a4ed809be879 |
|
||||
| Android/iOS 真机原 Filter 回归 | 无法验证,未运行 App |
|
||||
| NavSea 正式 Host 多源 E2E 与 CM95 Mapping | 未完成,属于 NavSea 下一阶段 |
|
||||
|
||||
## 版本与锁
|
||||
- Android Maven:cc.navsea.maplibre:android-sdk-particle-opengl:13.6.1-particle.28
|
||||
- RN:@navsea/maplibre-particle-rn@13.6.1-particle.28
|
||||
- iOS SwiftPM:https://pbf.jpcn.ccwu.cc/swift/maplibre-particle-spm.git Exact 6.29.0-particle.28 / MapLibre
|
||||
- 正式 Tag:maplibre-particle/rn/13.6.1-particle.28
|
||||
- 源码 Commit:4d9cb0ef69dc4bb946c48377f4e1a5b2ba2bbbe8;Final Commit:e48258434e5b9ee40ab1d414c223a4ed809be879
|
||||
- 六项 SHA-256、文件大小、正式下载 URL 参见 release/maplibre-particle/13.6.1-particle.28/SOURCE_LOCK.json。
|
||||
- GSRL 日志:/private/tmp/JOINT-PBF-001-V28-GSRL-prepare.log、build.log、publish.log、finalize.log。
|
||||
|
||||
## 限制
|
||||
Release 成功只证明一套冻结源码构建、来源与公网文件可信,不证明 iOS Filter 的 Runtime 与复杂表达式在设备上能安全切换。新增 Native API 对不支持的 Filter 会拒绝;Host 必须保持 Layer Style 实例、revision、身份和安全审查。未用旧 .27 的截图代替新 .28 设备证据。
|
||||
|
||||
下一步由 NavSea Lead 接入与编译验收;本仓不允许追加 CM95 专属业务代码。
|
||||
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.26")\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,12 @@
|
||||
|
||||
## 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。
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# @navsea/maplibre-particle-rn
|
||||
|
||||
当前版本:`13.6.1-particle.26`。Android Native 必须配套 `cc.navsea.maplibre:android-sdk-particle-opengl:13.6.1-particle.26`。本包是 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.26.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.26
|
||||
cc.navsea.maplibre:android-sdk-particle-opengl:13.6.1-particle.28
|
||||
```
|
||||
|
||||
Expo plugin 与 `android/build.gradle` 必须指向同一 `.26`。正常 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.26"
|
||||
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.26")\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.26",
|
||||
"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.26
|
||||
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,11 +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)
|
||||
|
||||
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.26/MapLibre.dynamic.xcframework.zip",
|
||||
checksum: "64b51c2b9eaec4adefcfb2dfc1c6338943f11db4d6c4ce2a4e1c2a39bb84bddb"
|
||||
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.26
|
||||
当前正式发行:6.29.0-particle.28
|
||||
|
||||
- Renderer:Metal
|
||||
- Product / Module:MapLibre
|
||||
- Binary:https://pbf.jpcn.ccwu.cc/swift/maplibre-particle/6.29.0-particle.26/MapLibre.dynamic.xcframework.zip
|
||||
- SHA-256:64b51c2b9eaec4adefcfb2dfc1c6338943f11db4d6c4ce2a4e1c2a39bb84bddb
|
||||
- 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.26
|
||||
- Source Commit:fce326a9009d3fc21eb91c5df362f0769163d07e
|
||||
- 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.26",
|
||||
"distributionVersion": "6.29.0-particle.28",
|
||||
"moduleName": "MapLibre",
|
||||
"compilationMode": "opt",
|
||||
"artifact": {
|
||||
"fileName": "MapLibre.dynamic.xcframework.zip",
|
||||
"sizeBytes": 4181892,
|
||||
"sha256": "64b51c2b9eaec4adefcfb2dfc1c6338943f11db4d6c4ce2a4e1c2a39bb84bddb",
|
||||
"url": "https://pbf.jpcn.ccwu.cc/swift/maplibre-particle/6.29.0-particle.26/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": "fce326a9009d3fc21eb91c5df362f0769163d07e",
|
||||
"sourceCommit": "4d9cb0ef69dc4bb946c48377f4e1a5b2ba2bbbe8",
|
||||
"swiftPackage": {
|
||||
"url": "https://pbf.jpcn.ccwu.cc/swift/maplibre-particle-spm.git",
|
||||
"requirement": "exactVersion",
|
||||
"version": "6.29.0-particle.26",
|
||||
"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": 26,
|
||||
"sourceCommit": "fce326a9009d3fc21eb91c5df362f0769163d07e",
|
||||
"generatedAtUtc": "2026-10-12T22:32:46.580161+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.26",
|
||||
"version": "13.6.1-particle.28",
|
||||
"artifact": {
|
||||
"fileName": "navsea-maplibre-particle-rn-13.6.1-particle.26.tgz",
|
||||
"sizeBytes": 29038,
|
||||
"sha256": "3136316bb6508e774748b21c2095ab61b522d2cf4c3ff2e8c67774dde8549b93",
|
||||
"url": "https://pbf.jpcn.ccwu.cc/npm/navsea-maplibre-particle-rn-13.6.1-particle.26.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.26",
|
||||
"coordinate": "cc.navsea.maplibre:android-sdk-particle-opengl:13.6.1-particle.26",
|
||||
"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.26.aar",
|
||||
"sizeBytes": 16640537,
|
||||
"sha256": "46c1cf7ff8ebc3bedc25aa38d7d445078a2301e6ca223c0307b93e830991c2b9",
|
||||
"url": "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"
|
||||
"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.26",
|
||||
"distributionVersion": "6.29.0-particle.28",
|
||||
"renderer": "metal",
|
||||
"compilationMode": "opt",
|
||||
"moduleName": "MapLibre",
|
||||
"artifact": {
|
||||
"fileName": "MapLibre.dynamic.xcframework.zip",
|
||||
"sizeBytes": 4181892,
|
||||
"sha256": "64b51c2b9eaec4adefcfb2dfc1c6338943f11db4d6c4ce2a4e1c2a39bb84bddb",
|
||||
"url": "https://pbf.jpcn.ccwu.cc/swift/maplibre-particle/6.29.0-particle.26/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.26",
|
||||
"version": "6.29.0-particle.28",
|
||||
"productName": "MapLibre"
|
||||
},
|
||||
"xcode": "Xcode 26.3 Build version 17C529 ",
|
||||
|
||||
@@ -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},
|
||||
|
||||
24
粒子开发记录.md
24
粒子开发记录.md
@@ -1217,3 +1217,27 @@ Android V3-08 单次综合验收已完成并关闭。Native `13.4.1-particle.2`
|
||||
- 续作补 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 原业务总目标仍开放。
|
||||
|
||||
## 2026-10-21 — STEP-121 V28 统一 GSRL 正式发行 CLOSED / PASS
|
||||
|
||||
- STEP-120 iOS 原 Style Filter 无损或拒绝 Native API 代码已由本仓 Codex GPT-6.1 Sol High 完成并通过 Mac RDC iOS Metal opt 编译,源码提交 1aba9875f07d8e609eb5bf42810a4d8f40d15d3f。
|
||||
- GSRL Prepare 冻结源码 4d9cb0ef69dc4bb946c48377f4e1a5b2ba2bbbe8;Build Android OpenGL 四 ABI/Maven Local、RN tgz 和 iOS Metal XCFramework PASS;Publish NAS 原子发布和六项公网文件逐字节 SHA256 回读 PASS;SwiftPM .28 Tag PASS;Finalize PASS。
|
||||
- 版本 Android/RN 13.6.1-particle.28,iOS 6.29.0-particle.28,正式发行 Tag maplibre-particle/rn/13.6.1-particle.28,最终提交 e48258434e5b9ee40ab1d414c223a4ed809be879,origin 推送完成。README/SOURCE_LOCK 和 Release Manifest 已锁定,旧 .27 不覆盖。
|
||||
- 中文 Step docs/50_Step记录/STEP-121_V28统一正式发行_v1.md、RDC 报告 docs/报告/STEP-121_V28统一正式发行_RDC完成报告_v1.md。真机与 NavSea Host/Mapping 四档运行验收尚未完成;下一唯一 Owner 是 NavSea,负责 .28 SDK 接收、MLRN iOS Bridge 与双平台 App 构建。
|
||||
|
||||
Reference in New Issue
Block a user