✨ feat(native-planning): 添加 Rust ICSP 原生规划核心及 C# 互操作集成
* 新增 Rust 原生 ICSP 规划库 (Cargo 项目, CubicSpline 算法, FFI 接口) * 新增 Flyshot.Native.Planning C# 互操作项目 (P/Invoke 封装) * 更新 ControllerClientCompat 适配新的规划接口 * 添加构建脚本 (Copy-NativePlanningArtifacts.ps1) * 新增互操作测试及一致性对比测试 * 更新解决方案文件和设计文档
This commit is contained in:
130
src/Flyshot.Native.Planning/NativeIcspMethods.cs
Normal file
130
src/Flyshot.Native.Planning/NativeIcspMethods.cs
Normal file
@@ -0,0 +1,130 @@
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Flyshot.Native.Planning;
|
||||
|
||||
/// <summary>
|
||||
/// 声明 Rust 原生规划库导出的稳定 C ABI 方法。
|
||||
/// </summary>
|
||||
internal static partial class NativeIcspMethods
|
||||
{
|
||||
/// <summary>
|
||||
/// 保护原生库句柄首次初始化,避免并发规划重复加载。
|
||||
/// </summary>
|
||||
private static readonly object LibraryLoadLock = new();
|
||||
|
||||
/// <summary>
|
||||
/// 保存已加载原生库句柄,使 P/Invoke 生命周期覆盖整个进程。
|
||||
/// </summary>
|
||||
private static nint _libraryHandle;
|
||||
|
||||
/// <summary>
|
||||
/// 保存当前进程已绑定到 P/Invoke 逻辑名称的实际库路径。
|
||||
/// </summary>
|
||||
private static string? _loadedLibraryPath;
|
||||
|
||||
/// <summary>
|
||||
/// 初始化程序集级 DllImport 解析器,把逻辑库名绑定到显式加载句柄。
|
||||
/// </summary>
|
||||
static NativeIcspMethods()
|
||||
{
|
||||
NativeLibrary.SetDllImportResolver(
|
||||
typeof(NativeIcspMethods).Assembly,
|
||||
ResolveImportedLibrary);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 使用指定解析器确保原生规划库已加载。
|
||||
/// </summary>
|
||||
/// <param name="resolver">负责生成平台路径并加载原生库的解析器。</param>
|
||||
internal static void EnsureLibraryLoaded(NativePlanningLibraryResolver resolver)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(resolver);
|
||||
string requestedPath = resolver.ResolveLibraryPath();
|
||||
|
||||
lock (LibraryLoadLock)
|
||||
{
|
||||
if (_libraryHandle != nint.Zero)
|
||||
{
|
||||
StringComparison comparison = OperatingSystem.IsWindows()
|
||||
? StringComparison.OrdinalIgnoreCase
|
||||
: StringComparison.Ordinal;
|
||||
if (!string.Equals(_loadedLibraryPath, requestedPath, comparison))
|
||||
{
|
||||
throw new NativePlanningException(
|
||||
$"Rust 原生规划库已绑定到“{_loadedLibraryPath}”,不能在同一进程中改为“{requestedPath}”。",
|
||||
errorCode: -1008);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
_libraryHandle = resolver.LoadLibrary();
|
||||
_loadedLibraryPath = requestedPath;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 调用 Rust ICSP 入口并返回错误码和结果指针。
|
||||
/// </summary>
|
||||
/// <param name="waypoints">行优先连续路点数组。</param>
|
||||
/// <param name="waypointCount">路点数量。</param>
|
||||
/// <param name="degreesOfFreedom">每个路点的关节自由度。</param>
|
||||
/// <param name="velocityLimits">逐关节速度上限。</param>
|
||||
/// <param name="accelerationLimits">逐关节加速度上限。</param>
|
||||
/// <param name="jerkLimits">逐关节 jerk 上限。</param>
|
||||
/// <param name="options">与 Rust C ABI 对齐的规划选项。</param>
|
||||
/// <param name="result">接收 Rust 分配的结果指针。</param>
|
||||
/// <returns>零表示成功,非零表示固定类别的原生错误。</returns>
|
||||
[LibraryImport(
|
||||
NativePlanningLibraryResolver.LogicalLibraryName,
|
||||
EntryPoint = "flyshot_icsp_plan")]
|
||||
internal static unsafe partial int FlyshotIcspPlan(
|
||||
double* waypoints,
|
||||
int waypointCount,
|
||||
int degreesOfFreedom,
|
||||
double* velocityLimits,
|
||||
double* accelerationLimits,
|
||||
double* jerkLimits,
|
||||
NativeIcspOptions* options,
|
||||
out nint result);
|
||||
|
||||
/// <summary>
|
||||
/// 释放由 Rust 分配的 ICSP 结果对象。
|
||||
/// </summary>
|
||||
/// <param name="result">Rust 返回的结果对象指针;允许为空指针。</param>
|
||||
[LibraryImport(
|
||||
NativePlanningLibraryResolver.LogicalLibraryName,
|
||||
EntryPoint = "flyshot_icsp_result_free")]
|
||||
internal static partial void FlyshotIcspResultFree(nint result);
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前线程最近一次 Rust 调用保存的 UTF-8 错误文本。
|
||||
/// </summary>
|
||||
/// <returns>指向线程局部 C 字符串的只读指针。</returns>
|
||||
[LibraryImport(
|
||||
NativePlanningLibraryResolver.LogicalLibraryName,
|
||||
EntryPoint = "flyshot_last_error_message")]
|
||||
internal static partial nint FlyshotLastErrorMessage();
|
||||
|
||||
/// <summary>
|
||||
/// 将固定逻辑库名解析为已经显式加载的系统句柄。
|
||||
/// </summary>
|
||||
/// <param name="libraryName">P/Invoke 请求的逻辑库名。</param>
|
||||
/// <param name="assembly">发起请求的托管程序集。</param>
|
||||
/// <param name="searchPath">调用方提供的可选搜索路径。</param>
|
||||
/// <returns>逻辑名称匹配时返回已加载句柄,否则返回零交给系统继续解析。</returns>
|
||||
private static nint ResolveImportedLibrary(
|
||||
string libraryName,
|
||||
System.Reflection.Assembly assembly,
|
||||
DllImportSearchPath? searchPath)
|
||||
{
|
||||
_ = assembly;
|
||||
_ = searchPath;
|
||||
return string.Equals(
|
||||
libraryName,
|
||||
NativePlanningLibraryResolver.LogicalLibraryName,
|
||||
StringComparison.Ordinal)
|
||||
? _libraryHandle
|
||||
: nint.Zero;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user