* 新增 Rust 原生 ICSP 规划库 (Cargo 项目, CubicSpline 算法, FFI 接口) * 新增 Flyshot.Native.Planning C# 互操作项目 (P/Invoke 封装) * 更新 ControllerClientCompat 适配新的规划接口 * 添加构建脚本 (Copy-NativePlanningArtifacts.ps1) * 新增互操作测试及一致性对比测试 * 更新解决方案文件和设计文档
331 lines
12 KiB
Markdown
331 lines
12 KiB
Markdown
# 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`
|
||
|
||
- [x] **Step 1: 先写托管侧失败测试,锁定缺少原生库时的报错边界**
|
||
|
||
```csharp
|
||
[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);
|
||
}
|
||
```
|
||
|
||
- [x] **Step 2: 运行单测确认当前失败**
|
||
|
||
Run: `dotnet test tests/Flyshot.Core.Tests/Flyshot.Core.Tests.csproj --filter RustIcspPlanner_ThrowsClearError_WhenNativeLibraryIsMissing -v minimal`
|
||
Expected: FAIL,提示 `RustIcspPlanner` 或互操作类型不存在
|
||
|
||
- [x] **Step 3: 创建 Rust `cdylib` 和托管互操作项目骨架**
|
||
|
||
```toml
|
||
[package]
|
||
name = "flyshot-native-planning"
|
||
version = "0.1.0"
|
||
edition = "2021"
|
||
|
||
[lib]
|
||
crate-type = ["cdylib"]
|
||
|
||
[profile.release]
|
||
lto = true
|
||
codegen-units = 1
|
||
strip = "symbols"
|
||
panic = "abort"
|
||
```
|
||
|
||
```xml
|
||
<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>
|
||
```
|
||
|
||
- [x] **Step 4: 添加最小库解析与异常包装实现**
|
||
|
||
```csharp
|
||
public sealed class NativePlanningException : Exception
|
||
{
|
||
public NativePlanningException(string message, int errorCode, Exception? innerException = null)
|
||
: base(message, innerException)
|
||
{
|
||
ErrorCodeValue = errorCode;
|
||
}
|
||
|
||
public int ErrorCodeValue { get; }
|
||
}
|
||
```
|
||
|
||
```csharp
|
||
public sealed class NativePlanningLibraryResolver
|
||
{
|
||
private readonly Func<RuntimeInformation, string> _resolveLibraryPath;
|
||
}
|
||
```
|
||
|
||
- [x] **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`
|
||
|
||
- [x] **Step 1: 先写 Rust 失败测试,锁定 clamped-zero 样条边界和值/导数接口**
|
||
|
||
```rust
|
||
#[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(×, &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);
|
||
}
|
||
```
|
||
|
||
- [x] **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,提示样条类型或构造函数不存在
|
||
|
||
- [x] **Step 3: 实现三对角求解、样条系数和每段导数峰值接口**
|
||
|
||
```rust
|
||
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] { /* ... */ }
|
||
}
|
||
```
|
||
|
||
- [x] **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`
|
||
|
||
- [x] **Step 1: 先写 Rust 失败测试,锁定初始段时长、scale 计算和未收敛路径**
|
||
|
||
```rust
|
||
#[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);
|
||
}
|
||
```
|
||
|
||
- [x] **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` 或结果结构不存在
|
||
|
||
- [x] **Step 3: 实现最小 ICSP 纯算法结构,逐项复刻 C# 收敛语义**
|
||
|
||
```rust
|
||
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> { /* ... */ }
|
||
```
|
||
|
||
- [x] **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`
|
||
|
||
- [x] **Step 1: 先写托管失败测试,锁定数组布局、结果复制和释放路径**
|
||
|
||
```csharp
|
||
[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);
|
||
}
|
||
```
|
||
|
||
- [x] **Step 2: 运行单测确认失败**
|
||
|
||
Run: `dotnet test tests/Flyshot.Core.Tests/Flyshot.Core.Tests.csproj --filter RustIcspPlanner_ReturnsManagedPlannedTrajectory_FromNativeResult -v minimal`
|
||
Expected: FAIL,提示 P/Invoke 签名、结果结构或释放逻辑未实现
|
||
|
||
- [x] **Step 3: 实现 C ABI 入口、线程局部错误文本和 `SafeHandle` 释放**
|
||
|
||
```rust
|
||
#[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 { /* ... */ }
|
||
```
|
||
|
||
```csharp
|
||
[LibraryImport(NativeLibraryName, EntryPoint = "flyshot_icsp_plan")]
|
||
internal static unsafe partial int FlyshotIcspPlan(/* ... */);
|
||
```
|
||
|
||
- [x] **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`
|
||
|
||
- [x] **Step 1: 先写失败测试,锁定 C# 与 Rust 在基础样本上的逐项一致**
|
||
|
||
```csharp
|
||
[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);
|
||
}
|
||
```
|
||
|
||
- [x] **Step 2: 运行对拍测试确认失败**
|
||
|
||
Run: `dotnet test tests/Flyshot.Core.Tests/Flyshot.Core.Tests.csproj --filter RustIcspPlanner_MatchesManagedIcspPlanner_ForCompatibilityFixture -v minimal`
|
||
Expected: FAIL,提示数值未对齐或原生库复制失败
|
||
|
||
- [x] **Step 3: 补齐原生库复制脚本、测试输出复制规则和对拍辅助断言**
|
||
|
||
```powershell
|
||
param(
|
||
[string]$Configuration = "Release"
|
||
)
|
||
|
||
cargo build --manifest-path native/flyshot-native-planning/Cargo.toml --release
|
||
Copy-Item native/flyshot-native-planning/target/release/flyshot_native_planning.dll tests/Flyshot.Core.Tests/bin/$Configuration/net8.0/ -Force
|
||
```
|
||
|
||
- [x] **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`
|
||
|
||
- [x] **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 失败,本计划未修改该行为。
|
||
|
||
- [x] **Step 3: 运行解决方案构建**
|
||
|
||
Run: `dotnet build FlyshotReplacement.sln -v minimal`
|
||
Expected: PASS
|
||
|
||
- [x] **Step 4: 更新文档,记录本地构建、库复制和当前上线边界**
|
||
|
||
```markdown
|
||
- Rust 规划器当前仅显式使用,不替换默认 `ICspPlanner`
|
||
- Windows 需要 `flyshot_native_planning.dll`
|
||
- Linux 需要 `libflyshot_native_planning.so`
|
||
```
|