Files
FlyShotHost/docs/superpowers/specs/2026-06-22-rust-icsp-native-design.md
yunxiao.zhu bcbf8017ad 📝 docs(planning): 统一 Rust 规划库命名
将 Rust 原生规划库设计口径统一为 flyshot_planning。

同步更新 Windows 和 Linux 产物名以及复制命令示例。
2026-06-27 15:10:06 +08:00

9.6 KiB
Raw Permalink Blame History

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# 适配模块:

ControllerClientTrajectoryOrchestrator
            |
       ICSP 规划接口
        /         \
C# ICspPlanner   RustIcspPlanner
                     |
             Flyshot.Native.Interop
                     |
                  C ABI
                     |
          flyshot_planning
             Rust cdylib

现有 ICspPlanner 保持不变,继续作为行为基准和回退实现。Rust 规划器返回的数据仍由 C# 组装成现有 PlannedTrajectory,因此领域层、兼容层和运行时不感知原生对象。

3. Rust 模块边界

原生模块建议位于:

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 数组。路点输入采用行优先扁平布局:

[waypoint0_joint0, waypoint0_joint1, ...,
 waypoint1_joint0, waypoint1_joint1, ...]

主要入口:

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# 项目,建议命名为:

src/Flyshot.Native.Planning/

包含:

  • NativeIcspMethods:使用 LibraryImport 声明 C ABI。
  • NativeIcspSafeHandle:封装 Rust 结果指针和释放函数。
  • RustIcspPlanner:把 TrajectoryRequest 转换为连续数组,再把结果转换为 PlannedTrajectory。
  • NativePlanningLibraryResolver:按当前操作系统和体系结构解析原生库。
  • NativePlanningException:携带原生错误码和错误文本。

原生库逻辑名称固定为 flyshot_planning:

  • Windows:flyshot_planning.dll
  • Linux:libflyshot_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 配置启用:

[profile.release]
lto = true
codegen-units = 1
strip = "symbols"
panic = "abort"

9. 回退与上线边界

第一阶段只提供显式的 Rust 规划器,不改变生产默认算法。完成全部对拍和跨平台验证后,再单独设计配置切换:

Planning:Backend = Managed | Rust

原生库缺失、架构不匹配或加载失败时,显式选择 Rust 后应返回清晰错误,不静默回退。静默回退会掩盖部署缺陷,也无法确认现场实际使用的是哪套算法。

保留 C# 实现至少经过一个完整现场验证周期。后续是否删除托管算法,需要独立评审,不属于本设计范围。

10. 验收标准

  • Rust 单元测试全部通过。
  • C# 原生互操作测试全部通过。
  • 选定样本的 C#/Rust 对拍全部通过。
  • dotnet test 和解决方案构建通过。
  • Windows x64 实际加载 .dll 并完成规划。
  • Linux x64 实际加载 .so 并完成规划。
  • 缺少原生库时能得到明确且可诊断的错误。
  • 现有 C# ICspPlanner 和上层运行链路行为不变。

11. 第一阶段落地状态(2026-06-23)

当前已落地:

  • native/flyshot-native-planning Rust cdylib,包含 clamped-zero 样条、 一至三阶导数解析峰值、ICSP 迭代和稳定 C ABI。
  • src/Flyshot.Native.Planning 托管互操作项目,包含显式平台解析、 LibraryImport、SafeHandle、错误码转换和结果复制。
  • Rust 样条、ICSP、FFI 测试,以及 C#/Rust 1e-9 自动对拍测试。
  • build/Copy-NativePlanningArtifacts.ps1,用于初始化 Windows MSVC 环境、 构建原生库并复制到测试或发布目录。
  • Windows x64 已实际生成并加载 flyshot_planning.dll 完成规划。

当前上线边界保持不变:

  • 默认生产路径继续使用 C# ICspPlanner。
  • Rust 规划器只允许显式选择,库缺失、路径不一致或架构不支持时返回明确错误。
  • Linux x64 的源码、库名和构建脚本边界已落地,但 .so 的实际加载验证必须在 Linux x64 构建环境执行后才能标记完成。