From bb21e0923dc06e8ecdb0100263a281195aad0a4c Mon Sep 17 00:00:00 2001 From: "yunxiao.zhu" Date: Mon, 22 Jun 2026 16:52:39 +0800 Subject: [PATCH] =?UTF-8?q?docs(planning):=20=E6=B7=BB=E5=8A=A0=20Rust=20I?= =?UTF-8?q?CSP=20=E5=8E=9F=E7=94=9F=E6=A0=B8=E5=BF=83=E8=AE=BE=E8=AE=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../2026-06-22-rust-icsp-native-design.md | 251 ++++++++++++++++++ 1 file changed, 251 insertions(+) create mode 100644 docs/superpowers/specs/2026-06-22-rust-icsp-native-design.md diff --git a/docs/superpowers/specs/2026-06-22-rust-icsp-native-design.md b/docs/superpowers/specs/2026-06-22-rust-icsp-native-design.md new file mode 100644 index 0000000..ada17ac --- /dev/null +++ b/docs/superpowers/specs/2026-06-22-rust-icsp-native-design.md @@ -0,0 +1,251 @@ +# Rust ICSP 原生规划核心设计 + +## 1. 目标 + +在不改变现有上层业务接口的前提下,使用 Rust 重新实现当前 C# 版本的 ICSP 和 clamped-zero 三次样条算法,并通过稳定的 C ABI 供 .NET 8 调用。 + +第一阶段必须实现: + +- Rust `cdylib` 中的 clamped-zero 三次样条系数求解。 +- 样条位置求值和每段一至三阶导数绝对值峰值解析计算。 +- ICSP 初始分段时长、迭代缩放、最优结果保存和最终收敛判定。 +- C# P/Invoke 适配层。 +- Rust 与现有 C# 算法的自动对拍测试。 +- Windows x64 和 Linux x64 原生库加载边界。 + +第一阶段不实现: + +- `SelfAdaptIcspPlanner` 的补中点逻辑。 +- 轨迹稠密采样、J519 发送采样和触发绑定。 +- 正运动学、MoveJoint 和 MovePose。 +- FANUC 协议、Socket 和运行时状态机。 +- 删除或替换现有 C# ICSP 实现。 + +## 2. 总体架构 + +新增原生算法模块和 C# 适配模块: + +```text +ControllerClientTrajectoryOrchestrator + | + ICSP 规划接口 + / \ +C# ICspPlanner RustIcspPlanner + | + Flyshot.Native.Interop + | + C ABI + | + flyshot_native_planning + Rust cdylib +``` + +现有 `ICspPlanner` 保持不变,继续作为行为基准和回退实现。Rust 规划器返回的数据仍由 C# 组装成现有 `PlannedTrajectory`,因此领域层、兼容层和运行时不感知原生对象。 + +## 3. Rust 模块边界 + +原生模块建议位于: + +```text +native/ + flyshot-native-planning/ + Cargo.toml + src/ + lib.rs + error.rs + ffi.rs + icsp.rs + spline.rs +``` + +各文件职责: + +- `spline.rs`:三对角方程求解、样条系数、位置求值、解析导数峰值。 +- `icsp.rs`:纯 Rust ICSP 输入、迭代规划和结果模型。 +- `ffi.rs`:C ABI 数据结构、指针校验、结果分配与释放。 +- `error.rs`:原生错误分类和 UTF-8 错误信息。 +- `lib.rs`:只导出稳定 ABI,不导出内部算法符号。 + +Rust 内部算法不依赖 .NET 类型、JSON、日志框架、网络或文件系统。 + +## 4. C ABI 设计 + +ABI 使用固定宽度整数、C 布局结构和连续 `double` 数组。路点输入采用行优先扁平布局: + +```text +[waypoint0_joint0, waypoint0_joint1, ..., + waypoint1_joint0, waypoint1_joint1, ...] +``` + +主要入口: + +```c +int32_t flyshot_icsp_plan( + const double* waypoints, + int32_t waypoint_count, + int32_t dof, + const double* velocity_limits, + const double* acceleration_limits, + const double* jerk_limits, + const FlyshotIcspOptions* options, + FlyshotIcspResult** result); + +void flyshot_icsp_result_free(FlyshotIcspResult* result); + +const char* flyshot_last_error_message(void); +``` + +`FlyshotIcspOptions` 包含: + +- `threshold` +- `max_iterations` +- `enforce_final_scale` +- `final_scale_tolerance` + +`FlyshotIcspResult` 包含: + +- `waypoint_times` 及长度 +- `segment_durations` 及长度 +- `segment_scales` 及长度 +- `iterations` +- `threshold` + +调用成功返回 `0`。输入非法、数值计算失败、未收敛和内部异常使用不同错误码。错误文本由线程局部存储保存,只保证在当前线程下一次原生调用前有效。 + +所有结果内存都由 Rust 分配,并且只能通过 `flyshot_icsp_result_free` 释放。C# 不直接释放 Rust 分配的数组。 + +所有可预期失败必须在 FFI 内转换为错误码,FFI 路径不得依赖 panic 表达业务错误。Debug 和测试构建使用 `catch_unwind` 检测遗漏的 panic;Release 使用 `panic=abort`,此时 `catch_unwind` 不能恢复 panic,因此实现必须通过边界校验避免索引越界、空指针解引用和显式 `unwrap`。任何构建模式都不得让 Rust unwind 穿过 C ABI。 + +## 5. C# 适配层 + +新增一个只负责原生互操作的 C# 项目,建议命名为: + +```text +src/Flyshot.Native.Planning/ +``` + +包含: + +- `NativeIcspMethods`:使用 `LibraryImport` 声明 C ABI。 +- `NativeIcspSafeHandle`:封装 Rust 结果指针和释放函数。 +- `RustIcspPlanner`:把 `TrajectoryRequest` 转换为连续数组,再把结果转换为 `PlannedTrajectory`。 +- `NativePlanningLibraryResolver`:按当前操作系统和体系结构解析原生库。 +- `NativePlanningException`:携带原生错误码和错误文本。 + +原生库逻辑名称固定为 `flyshot_native_planning`: + +- Windows:`flyshot_native_planning.dll` +- Linux:`libflyshot_native_planning.so` + +C# 适配层负责: + +- 检查输入自由度和数组长度。 +- 固定数组,避免调用期间被 GC 移动。 +- 将 Rust 返回数据复制到托管数组。 +- 无论成功或失败都正确释放结果句柄。 +- 将原生错误转换为包含诊断信息的 C# 异常。 + +`RustIcspPlanner` 的构造参数和 `Plan` 行为应与 `ICspPlanner` 对齐,但第一阶段不自动替换现有依赖注入注册。 + +## 6. 数值一致性 + +Rust 实现必须逐项复刻当前 C# 行为: + +- 初始段时长使用相邻路点的关节空间欧氏距离。 +- 使用 clamped-zero 边界条件求三次样条。 +- 每轮解析计算各段速度、加速度和 jerk 峰值。 +- 分段倍率分别使用一次方、平方根和立方根。 +- 收敛指标为 `sum(abs(scale - 1))`。 +- 始终保存历史最小收敛指标对应的结果。 +- 迭代范围与当前 C# 保持一致,即包含第 `max_iterations` 轮。 +- 最终按 `global_scale > 1 + tolerance` 判定是否失败。 + +第一阶段采用以下对拍容差: + +- 路点时间:绝对误差不超过 `1e-9`。 +- 段时长:绝对误差不超过 `1e-9`。 +- 段缩放:绝对误差不超过 `1e-9`。 +- 最终 threshold:绝对误差不超过 `1e-9`。 +- 迭代次数:必须完全一致。 + +如果真实样本因浮点指令或编译器优化无法满足 `1e-9`,必须先定位首个分歧计算步骤,再基于证据调整容差,不直接放宽整个测试。 + +## 7. 测试策略 + +遵循 TDD,先增加失败测试,再实现 Rust 和 C# 适配。 + +测试分四层: + +1. Rust 单元测试 + - 三次样条边界值。 + - 一至三阶导数。 + - 解析峰值。 + - ICSP 收敛与不收敛。 + - 非法指针之外的纯算法输入校验。 + +2. Rust FFI 测试 + - 正常调用和结果释放。 + - 空指针、错误维度、非有限数。 + - 错误码与错误文本。 + - 重复释放由 C# `SafeHandle` 防止,不把未定义行为包装成可接受用法。 + +3. C# 互操作测试 + - 原生库加载。 + - 托管数组到 C ABI 的转换。 + - 结果复制和释放。 + - 原生错误到 C# 异常的转换。 + +4. C#/Rust 对拍测试 + - 仓库中的稳定规划样本。 + - 当前 `PlanningCompatibilityTests` 使用的基础输入。 + - 多自由度、多路点和极限参数。 + - `maxIterations=0` 的未收敛路径。 + - 至少一个父目录真实样本固化后的 golden case。 + +对拍测试必须同时调用现有 `ICspPlanner` 和 `RustIcspPlanner`,不能把预期值只写成 Rust 自身输出。 + +## 8. 构建与发布 + +本地构建脚本负责: + +- 执行 `cargo build --release`。 +- 根据目标平台复制原生库到 C# 测试和发布输出目录。 +- Windows x64 构建 Rust `x86_64-pc-windows-msvc`。 +- Linux x64 构建 Rust `x86_64-unknown-linux-gnu`。 + +第一阶段不要求在 Windows 主机上完成 Linux 交叉编译;Linux 原生库可以在 Linux 构建环境生成。仓库必须提供对应命令和 CI/离线构建说明。 + +发布时原生库与托管程序集放在同一部署目录。现有 Obfuscar 继续处理 `Flyshot.*.dll` 托管程序集,不处理 Rust 原生库。 + +Rust Release 配置启用: + +```toml +[profile.release] +lto = true +codegen-units = 1 +strip = "symbols" +panic = "abort" +``` + +## 9. 回退与上线边界 + +第一阶段只提供显式的 Rust 规划器,不改变生产默认算法。完成全部对拍和跨平台验证后,再单独设计配置切换: + +```text +Planning:Backend = Managed | Rust +``` + +原生库缺失、架构不匹配或加载失败时,显式选择 Rust 后应返回清晰错误,不静默回退。静默回退会掩盖部署缺陷,也无法确认现场实际使用的是哪套算法。 + +保留 C# 实现至少经过一个完整现场验证周期。后续是否删除托管算法,需要独立评审,不属于本设计范围。 + +## 10. 验收标准 + +- Rust 单元测试全部通过。 +- C# 原生互操作测试全部通过。 +- 选定样本的 C#/Rust 对拍全部通过。 +- `dotnet test` 和解决方案构建通过。 +- Windows x64 实际加载 `.dll` 并完成规划。 +- Linux x64 实际加载 `.so` 并完成规划。 +- 缺少原生库时能得到明确且可诊断的错误。 +- 现有 C# `ICspPlanner` 和上层运行链路行为不变。