using System; using System.Diagnostics; using System.Threading; namespace MultiWheelC.TrajectoryPlanning.Utils; /// 一次规划操作的中立停止原因;Map 与 CoarsePath 分别映射为自己的结果状态。 internal enum PlanningOperationStopReason { /// 预算仍可继续执行。 None, /// 调用方取消了本次操作。 Cancelled, /// 从预算创建起已经达到总超时。 TimedOut, } /// /// 以单调时钟统一控制一次规划操作的取消和总超时。 /// 本类不依赖 Map 或 CoarsePath,供两层在长循环检查同一个停止条件。 /// internal sealed class PlanningOperationBudget { private const int CheckIntervalWorkItems = 256; private readonly CancellationToken _cancellationToken; private readonly Stopwatch _stopwatch; private readonly TimeSpan _timeout; private readonly bool _hasTimeout; /// /// 创建具有总超时的操作预算。 /// 参数:cancellationToken 为调用方停止请求;timeout 必须非负,且从构造时开始计时。 /// internal PlanningOperationBudget(CancellationToken cancellationToken, TimeSpan timeout) { if (timeout < TimeSpan.Zero) throw new ArgumentOutOfRangeException(nameof(timeout)); _cancellationToken = cancellationToken; _timeout = timeout; _hasTimeout = true; _stopwatch = Stopwatch.StartNew(); } private PlanningOperationBudget(CancellationToken cancellationToken) { _cancellationToken = cancellationToken; _timeout = TimeSpan.Zero; _hasTimeout = false; _stopwatch = Stopwatch.StartNew(); } /// 创建只响应取消、不限制总耗时的兼容预算。 internal static PlanningOperationBudget Unlimited(CancellationToken cancellationToken) { return new PlanningOperationBudget(cancellationToken); } /// 从预算创建到当前的单调经过时间。 internal TimeSpan Elapsed { get { return _stopwatch.Elapsed; } } /// 立即查询当前停止原因;取消优先于超时。 internal PlanningOperationStopReason GetStopReason() { if (_cancellationToken.IsCancellationRequested) return PlanningOperationStopReason.Cancelled; return _hasTimeout && _stopwatch.Elapsed >= _timeout ? PlanningOperationStopReason.TimedOut : PlanningOperationStopReason.None; } /// /// 在循环工作单元中按固定上限检查预算。 /// 参数:workItemCount 为当前循环共享的已处理单元计数;首次及每 256 个单元检查一次。 /// internal PlanningOperationStopReason CheckEvery(ref int workItemCount) { workItemCount++; return workItemCount == 1 || workItemCount % CheckIntervalWorkItems == 0 ? GetStopReason() : PlanningOperationStopReason.None; } }