💥 feat(planning)!: 拆分 C# ICSP 并默认 Rust
* 新增可选 Flyshot.CSharp.Planning 项目 * 将 icsp 映射到 Rust 后端并拒绝旧 icsp_rust * 补充后端注册、文档、调试页和回归测试 BREAKING CHANGE: method=icsp 现在默认调用 Rust 后端;C# ICSP 改为 method=icsp-csharp,旧 method=icsp_rust 不再作为正式入口。
This commit is contained in:
@@ -29,9 +29,7 @@ public sealed class ControllerClientTrajectoryOrchestrator
|
||||
/// </summary>
|
||||
private const double SmoothStartStopBlend = 0.60;
|
||||
|
||||
private readonly ICspPlanner _icspPlanner;
|
||||
private readonly RustIcspPlanner _rustIcspPlanner;
|
||||
private readonly SelfAdaptIcspPlanner _selfAdaptIcspPlanner;
|
||||
private readonly IReadOnlyDictionary<string, IPlanningBackend> _planningBackends;
|
||||
private readonly ShotTimelineBuilder _shotTimelineBuilder = new(new WaypointTimestampResolver());
|
||||
private readonly Dictionary<string, PlannedExecutionBundle> _flyshotCache = new(StringComparer.Ordinal);
|
||||
private readonly ILogger<ControllerClientTrajectoryOrchestrator>? _logger;
|
||||
@@ -39,16 +37,16 @@ public sealed class ControllerClientTrajectoryOrchestrator
|
||||
/// <summary>
|
||||
/// 初始化轨迹编排器。
|
||||
/// </summary>
|
||||
/// <param name="planningBackends">显式注册的规划后端;为空时仅启用默认 Rust icsp 后端。</param>
|
||||
/// <param name="logger">日志记录器;允许 null。</param>
|
||||
/// <param name="loggerFactory">日志工厂;允许 null。</param>
|
||||
public ControllerClientTrajectoryOrchestrator(
|
||||
IEnumerable<IPlanningBackend>? planningBackends = null,
|
||||
ILogger<ControllerClientTrajectoryOrchestrator>? logger = null,
|
||||
ILoggerFactory? loggerFactory = null)
|
||||
{
|
||||
_logger = logger;
|
||||
_icspPlanner = new(logger: loggerFactory?.CreateLogger<ICspPlanner>());
|
||||
_rustIcspPlanner = new();
|
||||
_selfAdaptIcspPlanner = new(logger: loggerFactory?.CreateLogger<SelfAdaptIcspPlanner>());
|
||||
_planningBackends = BuildPlanningBackendMap(planningBackends);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -84,14 +82,15 @@ public sealed class ControllerClientTrajectoryOrchestrator
|
||||
offsetValues: Enumerable.Repeat(0, waypoints.Count),
|
||||
addressGroups: Enumerable.Range(0, waypoints.Count).Select(static _ => Array.Empty<int>()));
|
||||
|
||||
var method = ParseOrdinaryMethod(options.Method);
|
||||
var methodName = ParseOrdinaryMethod(options.Method);
|
||||
var backend = ResolvePlanningBackend(methodName);
|
||||
var request = new TrajectoryRequest(
|
||||
robot: planningRobot,
|
||||
program: program,
|
||||
method: method,
|
||||
method: backend.ResultMethod,
|
||||
saveTrajectoryArtifacts: options.SaveTrajectory);
|
||||
|
||||
var plannedTrajectory = PlanByMethod(request, method);
|
||||
var plannedTrajectory = PlanByBackend(request, backend);
|
||||
var executionTrajectory = plannedTrajectory;
|
||||
var denseJointTrajectory = CreateLimitCompliantDenseTrajectory(ref executionTrajectory, shapeTrajectoryEdges: false);
|
||||
var shotTimeline = new ShotTimeline(Array.Empty<ShotEvent>(), Array.Empty<TrajectoryDoEvent>());
|
||||
@@ -155,7 +154,8 @@ public sealed class ControllerClientTrajectoryOrchestrator
|
||||
offsetValues: uploaded.OffsetValues,
|
||||
addressGroups: uploaded.AddressGroups);
|
||||
|
||||
var method = ParseFlyshotMethod(options.Method);
|
||||
var methodName = ParseFlyshotMethod(options.Method);
|
||||
var backend = ResolvePlanningBackend(methodName);
|
||||
var cacheKey = CreateFlyshotCacheKey(planningRobot, uploaded, options, settings, effectivePlanningSpeedScale);
|
||||
if (options.UseCache && _flyshotCache.TryGetValue(cacheKey, out var cachedBundle))
|
||||
{
|
||||
@@ -168,12 +168,12 @@ public sealed class ControllerClientTrajectoryOrchestrator
|
||||
var request = new TrajectoryRequest(
|
||||
robot: planningRobot,
|
||||
program: program,
|
||||
method: method,
|
||||
method: backend.ResultMethod,
|
||||
moveToStart: options.MoveToStart,
|
||||
saveTrajectoryArtifacts: options.SaveTrajectory,
|
||||
useCache: options.UseCache);
|
||||
|
||||
var plannedTrajectory = PlanByMethod(request, method, settings);
|
||||
var plannedTrajectory = PlanByBackend(request, backend, settings);
|
||||
var smoothedExecutionTrajectory = ApplyExecutionTiming(plannedTrajectory, settings);
|
||||
var denseJointTrajectory = CreateLimitCompliantDenseTrajectory(ref smoothedExecutionTrajectory, shapeTrajectoryEdges: false);
|
||||
var shotTimeline = _shotTimelineBuilder.Build(
|
||||
@@ -206,14 +206,15 @@ public sealed class ControllerClientTrajectoryOrchestrator
|
||||
/// </summary>
|
||||
/// <param name="method">旧 SDK 传入的方法名。</param>
|
||||
/// <returns>领域层规划方法。</returns>
|
||||
private static PlanningMethod ParseOrdinaryMethod(string method)
|
||||
private static string ParseOrdinaryMethod(string method)
|
||||
{
|
||||
var normalized = NormalizeMethod(method);
|
||||
return normalized switch
|
||||
{
|
||||
"icsp" => PlanningMethod.Icsp,
|
||||
"icsp_rust" => PlanningMethod.IcspRust,
|
||||
"doubles" => PlanningMethod.Doubles,
|
||||
"icsp" => normalized,
|
||||
"icsp-csharp" => normalized,
|
||||
"icsp_rust" => throw new ArgumentException("method=\"icsp_rust\" 已改名为 method=\"icsp\"。", nameof(method)),
|
||||
"doubles" => normalized,
|
||||
_ => throw new ArgumentException($"Unsupported ExecuteTrajectory method: {method}", nameof(method))
|
||||
};
|
||||
}
|
||||
@@ -223,35 +224,80 @@ public sealed class ControllerClientTrajectoryOrchestrator
|
||||
/// </summary>
|
||||
/// <param name="method">旧 SDK 传入的方法名。</param>
|
||||
/// <returns>领域层规划方法。</returns>
|
||||
private static PlanningMethod ParseFlyshotMethod(string method)
|
||||
private static string ParseFlyshotMethod(string method)
|
||||
{
|
||||
var normalized = NormalizeMethod(method);
|
||||
return normalized switch
|
||||
{
|
||||
"icsp" => PlanningMethod.Icsp,
|
||||
"icsp_rust" => PlanningMethod.IcspRust,
|
||||
"self-adapt-icsp" => PlanningMethod.SelfAdaptIcsp,
|
||||
"doubles" => PlanningMethod.Doubles,
|
||||
"icsp" => normalized,
|
||||
"icsp-csharp" => normalized,
|
||||
"icsp_rust" => throw new ArgumentException("method=\"icsp_rust\" 已改名为 method=\"icsp\"。", nameof(method)),
|
||||
"self-adapt-icsp" => normalized,
|
||||
"doubles" => normalized,
|
||||
_ => throw new ArgumentException($"Unsupported ExecuteFlyShotTraj method: {method}", nameof(method))
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 按领域枚举分派到当前已经落地的规划器。
|
||||
/// 使用显式注册的规划后端执行规划。
|
||||
/// </summary>
|
||||
/// <param name="request">规划请求。</param>
|
||||
/// <param name="method">规划方法。</param>
|
||||
/// <param name="backend">已解析的规划后端。</param>
|
||||
/// <param name="settings">当前机器人兼容配置;为空时使用默认补点次数。</param>
|
||||
/// <returns>规划轨迹。</returns>
|
||||
private PlannedTrajectory PlanByMethod(TrajectoryRequest request, PlanningMethod method, CompatibilityRobotSettings? settings = null)
|
||||
private static PlannedTrajectory PlanByBackend(
|
||||
TrajectoryRequest request,
|
||||
IPlanningBackend backend,
|
||||
CompatibilityRobotSettings? settings = null)
|
||||
{
|
||||
return method switch
|
||||
var context = new PlanningBackendContext(settings?.AdaptIcspTryNum ?? 5);
|
||||
return backend.Plan(request, context);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从已注册后端中解析 method 对应的规划实现。
|
||||
/// </summary>
|
||||
/// <param name="methodName">已经归一化的外部 method 名称。</param>
|
||||
/// <returns>可执行该 method 的规划后端。</returns>
|
||||
private IPlanningBackend ResolvePlanningBackend(string methodName)
|
||||
{
|
||||
if (methodName == "doubles")
|
||||
{
|
||||
PlanningMethod.Icsp => _icspPlanner.Plan(request),
|
||||
PlanningMethod.IcspRust => _rustIcspPlanner.Plan(request),
|
||||
PlanningMethod.SelfAdaptIcsp => _selfAdaptIcspPlanner.Plan(request, settings?.AdaptIcspTryNum ?? 5),
|
||||
PlanningMethod.Doubles => throw new NotSupportedException("doubles 轨迹规划尚未落地。"),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(method), method, "未知轨迹规划方法。")
|
||||
};
|
||||
throw new NotSupportedException("doubles 轨迹规划尚未落地。");
|
||||
}
|
||||
|
||||
if (_planningBackends.TryGetValue(methodName, out var backend))
|
||||
{
|
||||
return backend;
|
||||
}
|
||||
|
||||
throw new NotSupportedException($"轨迹规划方法 {methodName} 未注册对应后端。");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 构造规划后端查找表,未显式注册时只启用默认 Rust icsp 后端。
|
||||
/// </summary>
|
||||
/// <param name="planningBackends">调用方显式注册的规划后端集合。</param>
|
||||
/// <returns>按 method 名称索引的后端表。</returns>
|
||||
private static IReadOnlyDictionary<string, IPlanningBackend> BuildPlanningBackendMap(IEnumerable<IPlanningBackend>? planningBackends)
|
||||
{
|
||||
var backends = planningBackends?.ToArray() ?? [];
|
||||
if (backends.Length == 0)
|
||||
{
|
||||
backends = [new RustIcspPlanningBackend()];
|
||||
}
|
||||
|
||||
var map = new Dictionary<string, IPlanningBackend>(StringComparer.Ordinal);
|
||||
foreach (var backend in backends)
|
||||
{
|
||||
var methodName = NormalizeMethod(backend.MethodName);
|
||||
if (!map.TryAdd(methodName, backend))
|
||||
{
|
||||
throw new InvalidOperationException($"规划后端 method={methodName} 被重复注册。");
|
||||
}
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
Reference in New Issue
Block a user