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

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

12 KiB
Raw Blame History

Rust ICSP Native Implementation Plan

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: 为现有 ICspPlanner 增加一套基于 Rust cdylib 的原生 ICSP 规划实现,并通过 C ABI + C# P/Invoke 完成可验证的对拍接入,同时不改变当前默认生产路径。

Architecture: 新增 native/flyshot-native-planning Rust 原生库承载 clamped-zero 三次样条与 ICSP 迭代核心;新增 src/Flyshot.Native.Planning 托管互操作项目负责库加载、数组编排、错误转换和结果复制。现有 Flyshot.Core.Planning.ICspPlanner 保持不变,RustIcspPlanner 仅作为显式新入口并在测试中与托管实现逐项对拍。

Tech Stack: Rust cdylib、C ABI、.NET 8、LibraryImport、SafeHandle、xUnit、PowerShell、Cargo


Task 1: 建立原生与托管骨架

Files:

  • Create: native/flyshot-native-planning/Cargo.toml

  • Create: native/flyshot-native-planning/src/lib.rs

  • Create: native/flyshot-native-planning/src/error.rs

  • Create: native/flyshot-native-planning/src/ffi.rs

  • Create: native/flyshot-native-planning/src/icsp.rs

  • Create: native/flyshot-native-planning/src/spline.rs

  • Create: src/Flyshot.Native.Planning/Flyshot.Native.Planning.csproj

  • Create: src/Flyshot.Native.Planning/NativeIcspMethods.cs

  • Create: src/Flyshot.Native.Planning/NativeIcspSafeHandle.cs

  • Create: src/Flyshot.Native.Planning/NativePlanningException.cs

  • Create: src/Flyshot.Native.Planning/NativePlanningLibraryResolver.cs

  • Modify: FlyshotReplacement.sln

  • Modify: tests/Flyshot.Core.Tests/Flyshot.Core.Tests.csproj

  • Step 1: 先写托管侧失败测试,锁定缺少原生库时的报错边界

[Fact]
public void RustIcspPlanner_ThrowsClearError_WhenNativeLibraryIsMissing()
{
    var planner = new RustIcspPlanner(
        options: NativeIcspPlannerOptions.Default,
        libraryResolver: new NativePlanningLibraryResolver(_ => "missing-library"));

    var exception = Assert.Throws<NativePlanningException>(() => planner.Plan(CreateSimpleRequest()));
    Assert.Contains("missing-library", exception.Message);
}
  • Step 2: 运行单测确认当前失败

Run: dotnet test tests/Flyshot.Core.Tests/Flyshot.Core.Tests.csproj --filter RustIcspPlanner_ThrowsClearError_WhenNativeLibraryIsMissing -v minimal Expected: FAIL,提示 RustIcspPlanner 或互操作类型不存在

  • Step 3: 创建 Rust cdylib 和托管互操作项目骨架
[package]
name = "flyshot_planning"
version = "0.1.0"
edition = "2021"

[lib]
crate-type = ["cdylib"]

[profile.release]
lto = true
codegen-units = 1
strip = "symbols"
panic = "abort"
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net8.0</TargetFramework>
    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
  </PropertyGroup>
  <ItemGroup>
    <ProjectReference Include="..\Flyshot.Core.Domain\Flyshot.Core.Domain.csproj" />
    <ProjectReference Include="..\Flyshot.Core.Planning\Flyshot.Core.Planning.csproj" />
  </ItemGroup>
</Project>
  • Step 4: 添加最小库解析与异常包装实现
public sealed class NativePlanningException : Exception
{
    public NativePlanningException(string message, int errorCode, Exception? innerException = null)
        : base(message, innerException)
    {
        ErrorCodeValue = errorCode;
    }

    public int ErrorCodeValue { get; }
}
public sealed class NativePlanningLibraryResolver
{
    private readonly Func<RuntimeInformation, string> _resolveLibraryPath;
}
  • Step 5: 重新运行单测,确认错误已变为清晰的原生库缺失异常

Run: dotnet test tests/Flyshot.Core.Tests/Flyshot.Core.Tests.csproj --filter RustIcspPlanner_ThrowsClearError_WhenNativeLibraryIsMissing -v minimal Expected: PASS

Task 2: 先做样条基础能力

Files:

  • Modify: native/flyshot-native-planning/src/spline.rs

  • Modify: native/flyshot-native-planning/src/lib.rs

  • Create: native/flyshot-native-planning/tests/spline_tests.rs

  • Step 1: 先写 Rust 失败测试,锁定 clamped-zero 样条边界和值/导数接口

#[test]
fn clamped_zero_spline_hits_waypoints_and_zero_boundary_velocity() {
    let times = [0.0, 1.0, 2.0, 3.0];
    let values = [0.0, 1.0, 0.5, 2.0];
    let spline = CubicSpline1D::new_clamped_zero(&times, &values).unwrap();

    assert_abs_diff_eq!(spline.position(0.0), 0.0, epsilon = 1e-12);
    assert_abs_diff_eq!(spline.position(3.0), 2.0, epsilon = 1e-12);
    assert_abs_diff_eq!(spline.velocity(0.0), 0.0, epsilon = 1e-12);
    assert_abs_diff_eq!(spline.velocity(3.0), 0.0, epsilon = 1e-12);
}
  • Step 2: 运行 Rust 测试确认失败

Run: cargo test --manifest-path native/flyshot-native-planning/Cargo.toml spline_tests::clamped_zero_spline_hits_waypoints_and_zero_boundary_velocity -- --exact Expected: FAIL,提示样条类型或构造函数不存在

  • Step 3: 实现三对角求解、样条系数和每段导数峰值接口
pub struct CubicSpline1D {
    knots: Vec<f64>,
    coefficients: Vec<[f64; 4]>,
}

impl CubicSpline1D {
    pub fn new_clamped_zero(times: &[f64], values: &[f64]) -> Result<Self, NativePlanningError> { /* ... */ }
    pub fn position(&self, time: f64) -> f64 { /* ... */ }
    pub fn velocity(&self, time: f64) -> f64 { /* ... */ }
    pub fn acceleration(&self, time: f64) -> f64 { /* ... */ }
    pub fn jerk(&self, time: f64) -> f64 { /* ... */ }
    pub fn segment_peak_derivatives(&self, segment_index: usize) -> [f64; 3] { /* ... */ }
}
  • Step 4: 重新运行 Rust 样条测试

Run: cargo test --manifest-path native/flyshot-native-planning/Cargo.toml spline_tests::clamped_zero_spline_hits_waypoints_and_zero_boundary_velocity -- --exact Expected: PASS

Task 3: 实现纯 Rust ICSP 核心

Files:

  • Modify: native/flyshot-native-planning/src/icsp.rs

  • Create: native/flyshot-native-planning/tests/icsp_tests.rs

  • Step 1: 先写 Rust 失败测试,锁定初始段时长、scale 计算和未收敛路径

#[test]
fn icsp_returns_same_iteration_count_as_managed_baseline_fixture() {
    let input = test_fixture_three_segment_case();
    let result = plan_icsp(&input).unwrap();

    assert_eq!(result.iterations, 2);
    assert_abs_diff_eq!(result.threshold, 0.0, epsilon = 1e-12);
}
  • Step 2: 运行 Rust ICSP 测试确认失败

Run: cargo test --manifest-path native/flyshot-native-planning/Cargo.toml icsp_tests::icsp_returns_same_iteration_count_as_managed_baseline_fixture -- --exact Expected: FAIL,提示 plan_icsp 或结果结构不存在

  • Step 3: 实现最小 ICSP 纯算法结构,逐项复刻 C# 收敛语义
pub struct IcspInput<'a> {
    pub waypoints: &'a [f64],
    pub waypoint_count: usize,
    pub dof: usize,
    pub velocity_limits: &'a [f64],
    pub acceleration_limits: &'a [f64],
    pub jerk_limits: &'a [f64],
    pub options: IcspOptions,
}

pub fn plan_icsp(input: &IcspInput<'_>) -> Result<IcspPlanResult, NativePlanningError> { /* ... */ }
  • Step 4: 重新运行 Rust ICSP 测试

Run: cargo test --manifest-path native/flyshot-native-planning/Cargo.toml icsp_tests::icsp_returns_same_iteration_count_as_managed_baseline_fixture -- --exact Expected: PASS

Task 4: 接通 C ABI 和托管结果复制

Files:

  • Modify: native/flyshot-native-planning/src/ffi.rs

  • Modify: native/flyshot-native-planning/src/error.rs

  • Modify: src/Flyshot.Native.Planning/NativeIcspMethods.cs

  • Modify: src/Flyshot.Native.Planning/NativeIcspSafeHandle.cs

  • Create: src/Flyshot.Native.Planning/NativeIcspInteropModels.cs

  • Create: src/Flyshot.Native.Planning/RustIcspPlanner.cs

  • Create: tests/Flyshot.Core.Tests/RustIcspPlannerInteropTests.cs

  • Step 1: 先写托管失败测试,锁定数组布局、结果复制和释放路径

[Fact]
public void RustIcspPlanner_ReturnsManagedPlannedTrajectory_FromNativeResult()
{
    var planner = CreateRustPlannerAgainstBuiltNativeLibrary();
    var result = planner.Plan(CreateSimpleRequest());

    Assert.Equal(PlanningMethod.Icsp, result.Method);
    Assert.Equal(result.PlannedWaypointCount, result.WaypointTimes.Count);
}
  • Step 2: 运行单测确认失败

Run: dotnet test tests/Flyshot.Core.Tests/Flyshot.Core.Tests.csproj --filter RustIcspPlanner_ReturnsManagedPlannedTrajectory_FromNativeResult -v minimal Expected: FAIL,提示 P/Invoke 签名、结果结构或释放逻辑未实现

  • Step 3: 实现 C ABI 入口、线程局部错误文本和 SafeHandle 释放
#[no_mangle]
pub extern "C" fn flyshot_icsp_plan(/* ... */) -> i32 { /* ... */ }

#[no_mangle]
pub extern "C" fn flyshot_icsp_result_free(result: *mut FlyshotIcspResult) { /* ... */ }

#[no_mangle]
pub extern "C" fn flyshot_last_error_message() -> *const c_char { /* ... */ }
[LibraryImport(NativeLibraryName, EntryPoint = "flyshot_icsp_plan")]
internal static unsafe partial int FlyshotIcspPlan(/* ... */);
  • Step 4: 重新运行托管互操作测试

Run: dotnet test tests/Flyshot.Core.Tests/Flyshot.Core.Tests.csproj --filter RustIcspPlanner_ReturnsManagedPlannedTrajectory_FromNativeResult -v minimal Expected: PASS

Task 5: 增加 C#/Rust 对拍测试和构建复制脚本

Files:

  • Modify: tests/Flyshot.Core.Tests/PlanningCompatibilityTests.cs

  • Create: tests/Flyshot.Core.Tests/RustIcspPlannerParityTests.cs

  • Create: build/Copy-NativePlanningArtifacts.ps1

  • Modify: README.md

  • Step 1: 先写失败测试,锁定 C# 与 Rust 在基础样本上的逐项一致

[Fact]
public void RustIcspPlanner_MatchesManagedIcspPlanner_ForCompatibilityFixture()
{
    var request = CreateCompatibilityRequest();
    var managed = new ICspPlanner().Plan(request);
    var native = CreateRustPlannerAgainstBuiltNativeLibrary().Plan(request);

    AssertTrajectoryMatches(managed, native, tolerance: 1e-9);
}
  • Step 2: 运行对拍测试确认失败

Run: dotnet test tests/Flyshot.Core.Tests/Flyshot.Core.Tests.csproj --filter RustIcspPlanner_MatchesManagedIcspPlanner_ForCompatibilityFixture -v minimal Expected: FAIL,提示数值未对齐或原生库复制失败

  • Step 3: 补齐原生库复制脚本、测试输出复制规则和对拍辅助断言
param(
    [string]$Configuration = "Release"
)

cargo build --manifest-path native/flyshot-native-planning/Cargo.toml --release
Copy-Item native/flyshot-native-planning/target/release/flyshot_planning.dll tests/Flyshot.Core.Tests/bin/$Configuration/net8.0/ -Force
  • Step 4: 重新运行对拍测试

Run: dotnet test tests/Flyshot.Core.Tests/Flyshot.Core.Tests.csproj --filter RustIcspPlanner_MatchesManagedIcspPlanner_ForCompatibilityFixture -v minimal Expected: PASS

Task 6: 解决方案级验证

Files:

  • Modify: README.md

  • Modify: docs/superpowers/specs/2026-06-22-rust-icsp-native-design.md

  • Step 1: 运行 Rust 全量测试

Run: cargo test --manifest-path native/flyshot-native-planning/Cargo.toml Expected: PASS

  • Step 2: 运行 Core 测试

Run: dotnet test tests/Flyshot.Core.Tests/Flyshot.Core.Tests.csproj -v minimal Expected: PASS

当前验证:新增 Rust 相关测试全部通过;Core 全套仍保留实施前已存在的两项 FlyshotExecutionSendSequenceBuilder speed-ratio 失败,本计划未修改该行为。

  • Step 3: 运行解决方案构建

Run: dotnet build FlyshotReplacement.sln -v minimal Expected: PASS

  • Step 4: 更新文档,记录本地构建、库复制和当前上线边界
- Rust 规划器当前仅显式使用,不替换默认 `ICspPlanner`
- Windows 需要 `flyshot_planning.dll`
- Linux 需要 `libflyshot_planning.so`