using LoopViewerApp; using Newtonsoft.Json; using Simple3.RCS; using Simple3.RCS.CarTypes; using SimpleCore; using SimpleCore.Compiler; using SimpleCore.Library; using SimpleCore.PropType; using StandardScene.CarTypes; using StandardScene.Chained.Loop; using StandardScene.Model; using StandardScene.Utils; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; namespace StandardScene.Chained { /// /// 抽象环线任务基类 /// 核心功能: /// /// 支持多种启动类型:AutoLoop(自动循环)、Api、Plc、ButtonBox(外部触发) /// 支持多种任务类别:Loop(普通循环)、BranchPoint(分流点)、JoinPoint(汇合点) /// 支持流量控制:限制目标站点的最大车辆数 /// 支持优先级调度:高优先级任务优先处理 /// 支持配置热更新:tasklist.json 文件变更自动刷新 /// 外部触发接口:子类可重写 OnApiTrigger/OnPlcTrigger/OnButtonTrigger 实现业务逻辑 /// /// /// /// 子类实现示例: /// /// public class MyLoopMission : AbstractLoopMission /// { /// protected override ExternalTriggerResult OnApiTrigger(int currentSiteId, LoopTask task, Car car) /// { /// // 返回 UseTarget(目标站点) - 使用子类指定的目标 /// // 返回 UseConfigTarget() - 使用配置文件目标 /// // 返回 Fail() - 不分配任务 /// return ExternalTriggerResult.UseConfigTarget(); /// } /// } /// /// public abstract class AbstractLoopMission : Mission, IDisposable { #region 常量定义 /// 策略同步线程轮询间隔(毫秒) private const int STRATEGY_SYNC_INTERVAL_MS = 1000; /// 业务逻辑线程轮询间隔(毫秒) private const int LOGIC_LOOP_INTERVAL_MS = 300; /// 文件变更防抖延迟(毫秒) private const int FILE_CHANGE_DEBOUNCE_MS = 50; /// 异常恢复等待时间(毫秒) private const int ERROR_RECOVERY_DELAY_MS = 3000; /// 脚本异常检测触发延迟(毫秒) private const int SCRIPT_ERROR_TRIGGER_DELAY_MS = 3000; #endregion #region 外部触发结果类 /// /// 外部触发处理结果类 /// 用于子类重写外部触发接口时返回处理结果,决定目标站点的分配方式 /// /// /// 使用示例: /// /// // 方式1:使用子类指定的目标站点 /// return ExternalTriggerResult.UseTarget(200); /// /// // 方式2:使用配置文件中的目标站点(相当于返回 true) /// return ExternalTriggerResult.UseConfigTarget(); /// /// // 方式3:业务失败,不分配任务 /// return ExternalTriggerResult.Fail(); /// /// public class ExternalTriggerResult { /// /// 是否处理成功 /// true = 成功,将根据 CustomTargetSiteId 分配目标站点 /// false = 失败或跳过,不分配任务 /// public bool Success { get; set; } /// /// 子类指定的目标站点ID /// 有值时使用此目标站点 /// null 时使用配置文件中的 TargetStationId /// public int? CustomTargetSiteId { get; set; } /// /// 创建成功结果,使用子类指定的目标站点 /// /// 目标站点ID /// 包含指定目标的成功结果 public static ExternalTriggerResult UseTarget(int targetSiteId) { return new ExternalTriggerResult { Success = true, CustomTargetSiteId = targetSiteId }; } /// /// 创建成功结果,使用配置文件中的目标站点 /// 等同于业务处理成功返回 true 的效果 /// /// 使用配置目标的成功结果 public static ExternalTriggerResult UseConfigTarget() { return new ExternalTriggerResult { Success = true, CustomTargetSiteId = null }; } /// /// 创建失败结果,不分配任务 /// 等同于业务处理失败返回 false 的效果 /// /// 失败结果 public static ExternalTriggerResult Fail() { return new ExternalTriggerResult { Success = false }; } } #endregion #region 状态与字段 /// /// 环线任务状态类 /// public class AbstractLoopMissionStatus : MissionStatus { /// 当前任务数量 public int TaskCount { get; set; } /// 最后一次错误信息 public string LastError { get; set; } = "/"; } /// 任务状态对象 /* [JsonIgnore] public override MissionStatus status { get; set; } = new LoopMissionStatus();*/ /// 策略同步线程 [JsonIgnore] private Thread _strategyThread; /// 策略同步线程运行标志 [JsonIgnore] private volatile bool _strategyRunning; /// 业务逻辑线程 [JsonIgnore] private Thread _logicThread; /// 业务逻辑线程运行标志 [JsonIgnore] private volatile bool _logicRunning; /// 任务集合同步锁 [JsonIgnore] private readonly object _tasksLock = new object(); /// 任务列表(线程安全访问需使用 _tasksLock) [JsonIgnore] protected readonly List Tasks = new List(); /// 触发器适配器列表 [JsonIgnore] private readonly List _adapters = new List(); /// 任务策略实例 private ITaskStrategy _taskStrategy; /// /// 任务策略属性 /// 支持热更新,设置时自动订阅/取消订阅变更事件 /// public ITaskStrategy TaskStrategy { get => _taskStrategy; set { // 取消旧策略的事件订阅 if (_taskStrategy is JsonFileTaskStrategy oldStrategy) { oldStrategy.Changed -= OnTaskStrategyChanged; oldStrategy.DisposeWatcher(); } // 设置新策略(null 时使用默认策略) _taskStrategy = value ?? new JsonFileTaskStrategy(); // 订阅新策略的变更事件 if (_taskStrategy is JsonFileTaskStrategy newStrategy) { newStrategy.Changed += OnTaskStrategyChanged; newStrategy.EnsureWatcher(); } } } /// 任务列表变更事件 public event EventHandler TaskListChanged; #endregion #region 构造函数 /// /// 初始化抽象环线任务基类 /// 自动使用默认的 JSON 文件策略并加载初始任务配置 /// protected AbstractLoopMission() { TaskStrategy = new JsonFileTaskStrategy(); ApplyTaskStrategy(); } #endregion #region 任务管理方法 /// /// 获取当前任务列表的只读副本(线程安全) /// /// 任务列表快照 public IReadOnlyList GetTasks() { lock (_tasksLock) { return Tasks.ToArray(); } } /// /// 添加任务到列表末尾 /// /// 要添加的任务 /// 新任务的索引 /// task 为 null 时抛出 public int EnqueueTask(LoopTask task) { if (task == null) throw new ArgumentNullException(nameof(task)); lock (_tasksLock) { Tasks.Add(task); int index = Tasks.Count - 1; // 触发任务添加事件 TaskListChanged?.Invoke(this, new TaskListChangedEventArgs(TaskChangeType.Added, index, task)); // 更新状态中的任务计数 ((AbstractLoopMissionStatus)status).TaskCount = Tasks.Count; return index; } } /// /// 更新指定索引的任务 /// /// 任务索引 /// 新的任务数据 /// 是否更新成功 public bool UpdateTask(int index, LoopTask task) { lock (_tasksLock) { if (index < 0 || index >= Tasks.Count) return false; Tasks[index] = task; // 触发任务更新事件 TaskListChanged?.Invoke(this, new TaskListChangedEventArgs(TaskChangeType.Updated, index, task)); return true; } } /// /// 移除指定索引的任务 /// /// 任务索引 /// 是否移除成功 public bool RemoveTask(int index) { lock (_tasksLock) { if (index < 0 || index >= Tasks.Count) return false; LoopTask task = Tasks[index]; Tasks.RemoveAt(index); // 触发任务移除事件 TaskListChanged?.Invoke(this, new TaskListChangedEventArgs(TaskChangeType.Removed, index, task)); // 更新状态中的任务计数 ((AbstractLoopMissionStatus)status).TaskCount = Tasks.Count; return true; } } #endregion #region 车辆查找辅助方法 /// /// 查找恰好停在指定站点的车辆 /// 条件:车辆的 holdingLocks 只包含该站点(表示车辆已到达并停稳) /// /// 站点ID /// 找到的车辆,未找到返回 null protected virtual Car FindCarArrivedAtSite(int siteId) { try { return SimpleLib.GetAllCars() .OfType() .FirstOrDefault(car => car?.status?.holdingLocks != null && car.status.holdingLocks.Length == 1 && car.status.holdingLocks[0] == siteId); } catch (Exception ex) { System.Diagnostics.Debug.WriteLine($"[AbstractLoopMission] FindCarArrivedAtSite({siteId}) error: {ex.Message}"); return null; } } /// /// 获取当前在站或正在前往指定站点的所有车辆 /// 用于流量控制统计 /// /// 站点ID /// 车辆集合 protected IEnumerable GetCarsAtOrHeadingToSite(int siteId) { try { return SimpleLib.GetAllCars() .OfType() .Where(car => { if (car?.status == null) return false; // 检查是否在站:holdingLocks 包含该站点 if (car.status.holdingLocks?.Contains(siteId) == true) return true; // 检查是否前往:pendingLocks 最后一个锁定点为该站点 if (car.status.pendingLocks.LastOrDefault()==siteId) { return true; } // 检查是否前往:goalSite 标签指向该站点 /* if (car.tags?.TryGetValue("goalSite", out var destStr) == true && int.TryParse(destStr, out int destId) && destId == siteId) return true;*/ return false; }) .ToList(); } catch (Exception ex) { System.Diagnostics.Debug.WriteLine($"[AbstractLoopMission] GetCarsAtOrHeadingToSite({siteId}) error: {ex.Message}"); return Array.Empty(); } } /// /// 统计当前在站或正在前往指定站点的车辆数量 /// /// 站点ID /// 车辆数量 protected int CountCarsAtOrHeadingToSite(int siteId) { return GetCarsAtOrHeadingToSite(siteId).Count(); } /// /// 检查车辆是否空闲(未被占用、未有目标、未执行任务) /// /// 车辆 /// 是否空闲 protected bool IsCarIdle(Car car) { if (car?.tags == null) return false; try { // 检查是否存在表示忙碌状态的标签 if (car.tags.ContainsKey("occupied")) return false; if (car.tags.ContainsKey("dest")) return false; if (car.tags.ContainsKey("deliver")) return false; return true; } catch { return false; } } #endregion #region 流量控制检查 /// /// 检查目标站点是否满足流量控制条件 /// 统计当前在站或正在前往该站点的车辆数量,与最大允许数比较 /// /// 目标站点ID /// 最大允许车辆数(0或负数表示不限制) /// true=可以发车,false=已达上限需等待 protected bool CheckTrafficControl(int targetSiteId, int maxAllowed) { // 不限制流量的情况 if (maxAllowed <= 0) return true; int currentCount = CountCarsAtOrHeadingToSite(targetSiteId); return currentCount < maxAllowed; } #endregion #region 外部触发接口(子类重写) /// /// API 触发处理接口 /// 当任务的 StartType 为 Api 时,车辆到达当前站点后会调用此方法 /// 子类重写此方法实现 API 触发的业务逻辑 /// /// 当前站点ID /// 匹配的任务配置 /// 到达的车辆 /// /// 处理结果: /// /// - 使用子类指定的目标站点 /// - 使用配置文件中的目标站点 /// - 业务失败,不分配任务 /// /// protected virtual ExternalTriggerResult OnApiTrigger(int currentSiteId, LoopTask task, Car car) { // 默认返回失败,子类需重写以启用 API 触发功能 return ExternalTriggerResult.Fail(); } /// /// PLC 信号触发处理接口 /// 当任务的 StartType 为 Plc 时,车辆到达当前站点后会调用此方法 /// 子类重写此方法实现 PLC 触发的业务逻辑 /// /// 当前站点ID /// 匹配的任务配置 /// 到达的车辆 /// 处理结果 protected virtual ExternalTriggerResult OnPlcTrigger(int currentSiteId, LoopTask task, Car car) { // 默认返回失败,子类需重写以启用 PLC 触发功能 return ExternalTriggerResult.Fail(); } /// /// 充电条件信号触发处理接口 /// 当任务的 StartType 为 Plc 时,车辆到达当前站点后会调用此方法 /// 子类重写此方法实现 PLC 触发的业务逻辑 /// /// 当前站点ID /// 匹配的任务配置 /// 到达的车辆 /// 处理结果 protected virtual ExternalTriggerResult OnChargeTrigger(int currentSiteId, LoopTask task, Car car) { // 默认返回失败,子类需重写已启用去充电触发功能 return ExternalTriggerResult.Fail(); } /// /// 按钮盒触发处理接口 /// 当任务的 StartType 为 ButtonBox 时,车辆到达当前站点后会调用此方法 /// 子类重写此方法实现按钮盒触发的业务逻辑 /// /// 当前站点ID /// 匹配的任务配置 /// 到达的车辆 /// 处理结果 protected virtual ExternalTriggerResult OnButtonTrigger(int currentSiteId, LoopTask task, Car car) { // 默认返回失败,子类需重写以启用按钮盒触发功能 return ExternalTriggerResult.Fail(); } /// /// 根据启动类型调用对应的子类触发接口 /// /// 启动类型 /// 当前站点ID /// 匹配的任务配置 /// 到达的车辆 /// 子类返回的处理结果 private ExternalTriggerResult InvokeExternalTrigger(TaskStartType startType, int currentSiteId, LoopTask task, Car car) { return startType switch { TaskStartType.Api => OnApiTrigger(currentSiteId, task, car), TaskStartType.Plc => OnPlcTrigger(currentSiteId, task, car), TaskStartType.ButtonBox => OnButtonTrigger(currentSiteId, task, car), TaskStartType.Charge => OnChargeTrigger(currentSiteId, task, car), _ => ExternalTriggerResult.Fail() }; } #endregion #region 任务策略管理 /// /// 从任务策略刷新并应用任务列表 /// 读取配置文件内容并替换当前任务列表 /// public void ApplyTaskStrategy() { try { // 刷新策略数据 TaskStrategy?.Refresh(); var sourceTasks = TaskStrategy?.GetTasks(); if (sourceTasks == null) return; lock (_tasksLock) { // 清空并重新加载任务 Tasks.Clear(); foreach (var task in sourceTasks) { Tasks.Add(task); } // 触发任务替换事件 TaskListChanged?.Invoke(this, new TaskListChangedEventArgs(TaskChangeType.Replaced, -1, null)); // 更新状态中的任务计数 ((AbstractLoopMissionStatus)status).TaskCount = Tasks.Count; } System.Diagnostics.Debug.WriteLine($"[AbstractLoopMission] ApplyTaskStrategy: 加载了 {Tasks.Count} 个任务"); } catch (Exception ex) { // 记录错误信息 ((AbstractLoopMissionStatus)status).LastError = ex.Message; System.Diagnostics.Debug.WriteLine($"[AbstractLoopMission] ApplyTaskStrategy error: {ex}"); } } /// /// 任务策略配置文件变更处理 /// private void OnTaskStrategyChanged(object sender, EventArgs e) { try { ApplyTaskStrategy(); System.Diagnostics.Debug.WriteLine("[AbstractLoopMission] OnTaskStrategyChanged: 配置文件已更新"); } catch (Exception ex) { System.Diagnostics.Debug.WriteLine($"[AbstractLoopMission] OnTaskStrategyChanged error: {ex}"); } } #endregion #region 核心业务逻辑 /// /// 业务逻辑主循环 /// 在 _logicThread 线程中执行,每 500ms 处理一次任务调度 /// protected virtual void ExecuteLogicLoop() { while (_logicRunning) { try { ProcessAutoLoopTasks(); Thread.Sleep(LOGIC_LOOP_INTERVAL_MS); } catch (ThreadAbortException) { break; } catch (Exception ex) { System.Diagnostics.Debug.WriteLine($"[AbstractLoopMission] ExecuteLogicLoop error: {ex}"); } } } /// /// 处理所有任务(主调度入口) /// 按顺序处理:自动循环任务 → 外部触发任务 → 分流点任务 → 汇合点任务 → 循环启动动作 /// protected virtual void ProcessAutoLoopTasks() { // 获取任务快照(避免长时间持锁) List snapshot; lock (_tasksLock) { snapshot = Tasks.ToList(); } // 1. 处理自动循环任务(StartType=AutoLoop, Kind=Loop) ProcessAutoLoopTypeTasks(snapshot); // 2. 处理外部触发任务(StartType=Api/Plc/ButtonBox/charge) ProcessExternalTriggerTasks(snapshot); // 3. 处理自动分流点任务(StartType=AutoLoop, Kind=BranchPoint) ProcessBranchPointTasks(snapshot); // 4. 处理自动汇合点任务(StartType=AutoLoop, Kind=JoinPoint) ProcessJoinPointTasks(snapshot); // 5. 执行循环启动动作(处理站点默认目标、执行导航、异常恢复等) LoopStartAction(); } /// /// 处理自动循环类型任务 /// 筛选 StartType=AutoLoop 且 Kind=Loop 的任务,按优先级处理 /// /// 任务快照 private void ProcessAutoLoopTypeTasks(List snapshot) { // 筛选自动循环任务并按优先级排序 var autoTasks = snapshot .Where(t => t.StartType == TaskStartType.AutoLoop && t.Kind == TaskKind.Loop) .OrderByDescending(t => t.Priority) .ToList(); foreach (var task in autoTasks) { try { // 查找到达当前站点的车辆 var car = FindCarArrivedAtSite(task.CurrentStationId); if (car == null) continue; // 检查车辆是否可用(SelectCar 返回 1 表示可用) if (Commons.SelectCar(car) != 1) continue; // 流量控制检查 if (!CheckTrafficControl(task.TargetStationId, task.TrafficControl)) { Diagnosis.Post($"[AbstractLoopMission] AutoLoop: 目标站点 {task.TargetStationId} 流量已满,车辆 {car.name} 等待", "Loop-AutoLoop", true); continue; } // 分配目标站点 AssignCarToTarget(car, task.TargetStationId); Diagnosis.Post($"[AbstractLoopMission] AutoLoop: 车辆 {car.name} 从站点 {task.CurrentStationId} 自动分配到目标站点 {task.TargetStationId}", "Loop-AutoLoop", true); } catch (Exception ex) { Diagnosis.Post($"[AbstractLoopMission] ProcessAutoLoopTypeTasks error: {ex}", "Loop-AutoLoop", true); } } } /// /// 处理外部触发类型任务(API/PLC/ButtonBox) /// 依次处理各类型的外部触发任务 /// /// 任务快照 private void ProcessExternalTriggerTasks(List snapshot) { // 依次处理 API、PLC、ButtonBox 类型任务 ProcessExternalTasksByType(snapshot, TaskStartType.Api); ProcessExternalTasksByType(snapshot, TaskStartType.Plc); ProcessExternalTasksByType(snapshot, TaskStartType.ButtonBox); ProcessExternalTasksByType(snapshot, TaskStartType.Charge); } /// /// 按启动类型处理外部触发任务 /// 处理普通循环任务、分流点任务、汇合点任务 /// /// 任务快照 /// 启动类型 private void ProcessExternalTasksByType(List snapshot, TaskStartType startType) { // 筛选指定启动类型的普通循环任务 var externalTasks = snapshot .Where(t => t.StartType == startType && t.Kind == TaskKind.Loop) .OrderByDescending(t => t.Priority) .ToList(); foreach (var task in externalTasks) { try { // 查找到达当前站点的车辆 var car = FindCarArrivedAtSite(task.CurrentStationId); if (car == null) continue; // 检查车辆是否可用 if (Commons.SelectCar(car) != 1) continue; // 调用子类接口获取处理结果 ExternalTriggerResult result = InvokeExternalTrigger(startType, task.CurrentStationId, task, car); // 子类返回失败则跳过 if (result?.Success != true) continue; // 确定目标站点:子类指定 > 配置文件 int targetSiteId = result.CustomTargetSiteId ?? task.TargetStationId; // 流量控制检查 if (!CheckTrafficControl(targetSiteId, task.TrafficControl)) { Diagnosis.Post($"[AbstractLoopMission] {startType}: 目标站点 {targetSiteId} 流量已满,车辆 {car.name} 等待", $"Loop-{startType}", true); continue; } // 分配目标站点 AssignCarToTarget(car, targetSiteId); Diagnosis.Post($"[AbstractLoopMission] {startType}: 车辆 {car.name} 从站点 {task.CurrentStationId} 分配到目标站点 {targetSiteId}", $"Loop-{startType}", true); } catch (Exception ex) { Diagnosis.Post($"[AbstractLoopMission] ProcessExternalTasksByType({startType}) error: {ex}", $"Loop-{startType}", true); } } // 处理分流点类型的外部触发任务 ProcessExternalBranchTasks(snapshot, startType); // 处理汇合点类型的外部触发任务 ProcessExternalJoinTasks(snapshot, startType); } /// /// 处理外部触发的分流点任务 /// 按当前站点分组,优先级排序,调用子类接口后分配目标 /// /// 任务快照 /// 启动类型 private void ProcessExternalBranchTasks(List snapshot, TaskStartType startType) { // 按当前站点分组 var branchGroups = snapshot .Where(t => t.Kind == TaskKind.BranchPoint && t.StartType == startType) .GroupBy(t => t.CurrentStationId) .ToList(); foreach (var group in branchGroups) { int currentSiteId = group.Key; // 查找到达当前站点的车辆 var car = FindCarArrivedAtSite(currentSiteId); if (car == null || Commons.SelectCar(car) != 1) continue; // 按优先级排序 var sortedTasks = group.OrderByDescending(t => t.Priority).ToList(); foreach (var task in sortedTasks) { // 调用子类接口 ExternalTriggerResult result = InvokeExternalTrigger(startType, currentSiteId, task, car); if (result?.Success != true) continue; // 确定目标:子类指定 > 按优先级选择流量允许的目标 int targetSiteId = result.CustomTargetSiteId ?? SelectBranchTarget(task, snapshot); if (CheckTrafficControl(targetSiteId, task.TrafficControl)) { AssignCarToTarget(car, targetSiteId); Diagnosis.Post($"[AbstractLoopMission] {startType} BranchPoint: 车辆 {car.name} 从分流点 {currentSiteId} 分配到目标 {targetSiteId}", $"BranchPoint-{startType}", true); break; // 分流点只分配一个目标 } } } } /// /// 处理外部触发的汇合点任务 /// 按目标站点分组(汇合点),每组中的策略按优先级排列,排列后每个策略根据流量判断,最后采用接口返回结果 /// /// 任务快照 /// 启动类型 private void ProcessExternalJoinTasks(List snapshot, TaskStartType startType) { // 按目标站点分组(汇合点) var joinGroups = snapshot .Where(t => t.Kind == TaskKind.JoinPoint && t.StartType == startType) .GroupBy(t => t.TargetStationId) .ToList(); foreach (var group in joinGroups) { int targetSiteId = group.Key; // 按优先级降序排列组内任务 var sortedTasks = group.OrderByDescending(t => t.Priority).ToList(); // 遍历排序后的任务,根据流量和接口返回结果处理 foreach (var task in sortedTasks) { try { // 查找到达当前站点的车辆 var car = FindCarArrivedAtSite(task.CurrentStationId); if (car == null) continue; // 检查车辆是否可用 if (Commons.SelectCar(car) != 1) continue; // 流量控制检查(优先判断,避免无效的接口调用) if (!CheckTrafficControl(targetSiteId, task.TrafficControl)) { Diagnosis.Post($"[AbstractLoopMission] {startType} JoinPoint: 目标站点 {targetSiteId} 流量已满,跳过任务 {task.Id}", $"JoinPoint-{startType}", true); continue; } // 调用子类接口获取处理结果 ExternalTriggerResult result = InvokeExternalTrigger(startType, task.CurrentStationId, task, car); // 子类返回失败则跳过当前任务 if (result?.Success != true) continue; // 确定最终目标:子类指定 > 配置文件 int finalTarget = result.CustomTargetSiteId ?? targetSiteId; // 如果子类指定了不同的目标,需要再次检查流量 if (finalTarget != targetSiteId && !CheckTrafficControl(finalTarget, task.TrafficControl)) { Diagnosis.Post($"[AbstractLoopMission] {startType} JoinPoint: 子类指定目标站点 {finalTarget} 流量已满,车辆 {car.name} 等待", $"JoinPoint-{startType}", true); continue; } // 分配目标站点 AssignCarToTarget(car, finalTarget); Diagnosis.Post($"[AbstractLoopMission] {startType} JoinPoint: 车辆 {car.name} 从站点 {task.CurrentStationId} 汇入目标 {finalTarget}(优先级 {task.Priority})", $"JoinPoint-{startType}", true); } catch (Exception ex) { Diagnosis.Post($"[AbstractLoopMission] ProcessExternalJoinTasks({startType}) task {task.Id} error: {ex.Message}", $"JoinPoint-{startType}", true); } } } } /// /// 循环启动动作 /// 处理站点配置的默认目标、执行路径规划导航、检测并处理脚本异常 /// 注意:此方法不拆分,保持原有逻辑完整性 /// private void LoopStartAction() { try { foreach (var car in SimpleLib.GetAllCars()) { // 如果车辆可用且没有目标,尝试从站点配置读取默认目标 if (Commons.SelectCar(car) == 1 && !car.tags.Contains("goalSite")) { var carSite = SimpleLib.GetSite(car.GetLastSite()); if (carSite != null && carSite.fields.TryGetValue("goSite", out var dst)) { Commons.AddOrUpdateTag(car.tags, "goalSite", dst); } if (carSite != null && !car.tags.Contains("goalSite")) { var taskLine = FindBestTaskForCar((Car)car); if (taskLine!=null && !taskLine.IsAtStartPoint && !taskLine.IsAtEndPoint) { Commons.AddOrUpdateTag(car.tags, "goalSite", taskLine.TargetSiteId.ToString()); } } } // 如果车辆可用且有目标,执行导航 if (Commons.SelectCar(car) == 1 && car.tags.Contains("goalSite")) { var goalSite = SimpleLib.GetAllSites() .FirstOrDefault(s => car.tags.IsEqual("goalSite", s.id.ToString())); var holdingSite=car.status.holdingLocks.FirstOrDefault(); if (goalSite != null && holdingSite!=goalSite.id ) { _=GoSite(car, goalSite); } else if (goalSite != null && holdingSite == goalSite.id) { // 已到达目标,清理 goalSite 标签 car.tags.Remove("goalSite"); car.tags.Remove("loopAssigned"); } } // 以下处理声明了脚本异常自恢复能力的车辆(IScriptErrorRecoverable,当前为 Kiva) if (!(car is IScriptErrorRecoverable recoverable) || !(car is Car mcar)) continue; // 检测脚本异常状态:车辆被占用但脚本状态为 Error 或 Bad LadderLogic.TriggerOnce( mcar.tags.Contains("occupied") && (mcar.status.programs.printStatus().Contains("Error") || mcar.status.programs.printStatus().Contains("Bad")), SCRIPT_ERROR_TRIGGER_DELAY_MS, () => { // 获取车辆最后持有的站点锁 var lastHoldingLocks = car.status.holdingLocks.First(); var site = SimpleLib.GetSite(lastHoldingLocks); // 标记站点不可用 site.MakeAllCarsUnavailable(); // 禁止车辆调度 mcar.NoSchedule(true); Diagnosis.Post($"errStatus ", "NoSchedule", true); // 重置车辆站点ID mcar.siteID = -1; // 更新车辆使用状态 mcar.status.usage.AddUsage("base", new CarUsage.CarUsageInfo() { scheduling = false, refreshing = false }); // 设置车辆状态为返厂检修 mcar.lstatus = "返厂检修"; // 清理车辆标签 Commons.DeleteTag(mcar.tags, "occupied"); // Commons.DeleteTag(mcar.tags, "goalSite"); // 执行车辆重置 ((Car)car).Intercept((_) => { recoverable.RecoverReset(lastHoldingLocks); }); }, mcar.id ); } } catch (Exception e) { Diagnosis.Post($"loop mission error {ExceptionFormatter.FormatEx(e)}"); } } /// /// 导航车辆到目标站点 /// 创建路径规划并执行移动任务 /// /// 车辆 /// 目标站点 /// 动作参数,默认为 "/" /// 是否倒车,默认为 false public async Task GoSite(AbstractCar car, Site targetSite, string action = "/", bool reverse = false) { try { // 创建路径规划 var plan = new SegmentPlan() { usingCar = car }; plan.fields["reverse"] = reverse.ToString(); plan.fields["action"] = action; plan.fields["allow_destination_on_route"] = "true"; // 查找从当前位置到目标站点的路径 plan.FindRoute(SimpleLib.GetSite(car.GetLastSite()), targetSite); // 编译并执行移动脚本(默认 Forecast) var program = plan.Compile("move"); // 标记车辆为占用状态 car.tags.Add("occupied", $"go{targetSite.id}"); Console.WriteLine($">>Script:{program.script}"); // 等待任务完成 var tsk = program.Queue(); await tsk; // 任务完成后清理标签 car.tags.Remove("occupied"); car.tags.Remove("goalSite"); car.tags.Remove("loopAssigned"); } catch (Exception ex) { try { car.tags.Remove("occupied"); if (car is Car trafficCar && car.GetLastSite() > 0) { var currentSite = SimpleLib.GetSite(car.GetLastSite()); if (currentSite != null) { trafficCar.TrafficReset(currentSite, true, strict: false); } } } catch { } // 记录异常并等待后重试 Diagnosis.Post( $"{car.name}循环任务报错{ExceptionFormatter.FormatEx(ex)}--去往循环点id:{targetSite.id},name:{targetSite.name}", $"重新执行循环任务", true ); Thread.Sleep(ERROR_RECOVERY_DELAY_MS); } } /// /// 处理自动分流点任务 /// 筛选 StartType=AutoLoop 且 Kind=BranchPoint 的任务 /// 按当前站点分组,优先级排序,选择流量允许的目标 /// /// 任务列表 protected virtual void ProcessBranchPointTasks(List allTasks) { // 按当前站点分组 var branchGroups = allTasks .Where(t => t.Kind == TaskKind.BranchPoint && t.StartType == TaskStartType.AutoLoop) .GroupBy(t => t.CurrentStationId) .ToList(); foreach (var group in branchGroups) { int currentSiteId = group.Key; // 查找到达当前站点的车辆 var car = FindCarArrivedAtSite(currentSiteId); if (car == null || Commons.SelectCar(car) != 1) continue; // 按优先级排序,选择流量允许的目标 var sortedTasks = group.OrderByDescending(t => t.Priority).ToList(); foreach (var task in sortedTasks) { if (CheckTrafficControl(task.TargetStationId, task.TrafficControl)) { AssignCarToTarget(car, task.TargetStationId); Diagnosis.Post($"[AbstractLoopMission] BranchPoint: 车辆 {car.name}-{car.id} 从分流点 {currentSiteId} 分配到目标 {task.TargetStationId}(优先级 {task.Priority})", $"BranchPoint-AutoLoop", true); break; // 分流点只分配一个目标 } } } } /// /// 处理自动汇合点任务 /// 筛选 StartType=AutoLoop 且 Kind=JoinPoint 的任务 /// 按目标站点分组,优先级排序,选择有车在站的最高优先级任务 /// /// 任务列表 protected virtual void ProcessJoinPointTasks(List allTasks) { // 按目标站点分组(汇合点) var joinGroups = allTasks .Where(t => t.Kind == TaskKind.JoinPoint && t.StartType == TaskStartType.AutoLoop) .GroupBy(t => t.TargetStationId) .ToList(); foreach (var group in joinGroups) { int targetSiteId = group.Key; // 检查汇合点流量控制 var firstTask = group.First(); if (!CheckTrafficControl(targetSiteId, firstTask.TrafficControl)) continue; // 按优先级排序,选择有车在站的最高优先级任务 var sortedTasks = group.OrderByDescending(t => t.Priority).ToList(); foreach (var task in sortedTasks) { var car = FindCarArrivedAtSite(task.CurrentStationId); if (car != null && Commons.SelectCar(car) == 1) { AssignCarToTarget(car, targetSiteId); Diagnosis.Post($"[AbstractLoopMission] JoinPoint: 车辆 {car.name} 从站点 {task.CurrentStationId} 汇入目标 {targetSiteId}(优先级 {task.Priority})", $"JoinPoint-AutoLoop", true); //break; // 汇合点一次只放行一辆车 } } } } /// /// 分流点目标选择 /// 按优先级选择流量允许的目标站点 /// /// 当前分流任务 /// 所有任务列表 /// 选中的目标站点ID protected virtual int SelectBranchTarget(LoopTask branchTask, List allTasks) { // 查找同一当前站点的所有分流目标 var branches = allTasks .Where(t => t.Kind == TaskKind.BranchPoint && t.CurrentStationId == branchTask.CurrentStationId) .OrderByDescending(t => t.Priority) .ToList(); // 按优先级选择流量允许的目标 foreach (var branch in branches) { if (CheckTrafficControl(branch.TargetStationId, branch.TrafficControl)) { return branch.TargetStationId; } } // 默认返回原任务目标 return branchTask.TargetStationId; } /// /// 汇合点通行判断 /// 检查当前任务是否有最高优先级通行权(是否有更高优先级的车在等待) /// /// 当前汇合任务 /// 所有任务列表 /// true=可以通行,false=需要等待更高优先级的车 protected virtual bool CanJoinPass(LoopTask joinTask, List allTasks) { // 查找同一目标站点的所有汇合来源 var joinSources = allTasks .Where(t => t.Kind == TaskKind.JoinPoint && t.TargetStationId == joinTask.TargetStationId) .OrderByDescending(t => t.Priority) .ToList(); // 检查是否有更高优先级的任务且其站点有空闲车等待 foreach (var source in joinSources) { if (source.Priority > joinTask.Priority) { var higherPriorityCar = FindCarArrivedAtSite(source.CurrentStationId); if (higherPriorityCar != null && IsCarIdle(higherPriorityCar)) { return false; // 有更高优先级的车等待,当前车需要让行 } } } return true; } /// /// 为车辆分配目标站点 /// 设置车辆的 goalSite 标签和 loopAssigned 时间戳 /// /// 车辆 /// 目标站点ID protected virtual void AssignCarToTarget(Car car, int targetSiteId) { if (car == null) return; try { string targetIdStr = targetSiteId.ToString(); // 设置或更新目标站点标签 if (car.tags.ContainsKey("goalSite")) car.tags["goalSite"] = targetIdStr; else car.tags.Add("goalSite", targetIdStr); // 记录分配时间(用于调试和监控) if (!car.tags.ContainsKey("loopAssigned")) car.tags.Add("loopAssigned", DateTime.Now.ToString("HH:mm:ss")); } catch (Exception ex) { System.Diagnostics.Debug.WriteLine($"[AbstractLoopMission] AssignCarToTarget error: {ex}"); } } #endregion #region 线程生命周期 /// /// 启动策略同步线程 /// 每秒同步一次配置文件,自动更新任务列表 /// public virtual void StartLoop() { if (_strategyRunning) return; _strategyRunning = true; _strategyThread = new Thread(() => { while (_strategyRunning) { try { ApplyTaskStrategy(); Thread.Sleep(STRATEGY_SYNC_INTERVAL_MS); } catch (ThreadAbortException) { break; } catch (Exception ex) { System.Diagnostics.Debug.WriteLine($"[AbstractLoopMission] Strategy loop error: {ex}"); } } }) { Name = "AbstractLoopMission_Strategy", IsBackground = true }; _strategyThread.Start(); status.status = "策略同步已启动"; System.Diagnostics.Debug.WriteLine("[AbstractLoopMission] StartLoop: 策略同步线程已启动"); } /// /// 启动业务逻辑线程 /// 每 500ms 处理一次任务调度 /// public virtual void StartLogicLoop() { if (_logicRunning) return; _logicRunning = true; _logicThread = new Thread(ExecuteLogicLoop) { Name = "AbstractLoopMission_Logic", IsBackground = true }; _logicThread.Start(); status.status = "业务逻辑已启动"; System.Diagnostics.Debug.WriteLine("[AbstractLoopMission] StartLogicLoop: 业务逻辑线程已启动"); } /// /// 停止策略同步线程 /// public virtual void StopLoop() { try { _strategyRunning = false; _strategyThread?.Join(2000); _strategyThread = null; System.Diagnostics.Debug.WriteLine("[AbstractLoopMission] StopLoop: 策略同步线程已停止"); } catch { } } /// /// 停止业务逻辑线程 /// public virtual void StopLogicLoop() { try { _logicRunning = false; _logicThread?.Join(2000); _logicThread = null; System.Diagnostics.Debug.WriteLine("[AbstractLoopMission] StopLogicLoop: 业务逻辑线程已停止"); } catch { } } /// /// 启动所有线程(策略同步 + 业务逻辑) /// public virtual void StartAll() { // ApplyTaskStrategy(); MarkTaskStartSitesAsTerminal(); StartLoop(); StartLogicLoop(); status.status = "已启动"; } /// /// 将任务策略中的起始站点标记为 terminal=true /// private void MarkTaskStartSitesAsTerminal() { List startSiteIds; lock (_tasksLock) { startSiteIds = Tasks .Select(t => t.CurrentStationId) .Where(id => id > 0) .Distinct() .ToList(); } foreach (var siteId in startSiteIds) { var site = SimpleLib.GetSite(siteId); if (site == null) continue; Commons.AddOrUpdateSiteField(site, "terminal", "true"); } } /// /// 停止所有线程 /// public virtual void StopAll() { StopLoop(); StopLogicLoop(); status.status = "已停止"; } /// /// 释放资源 /// 清理触发器适配器、停止所有线程、取消策略订阅 /// public void Dispose() { // 清理触发器适配器 foreach (var adapter in _adapters.ToArray()) { try { adapter?.Dispose(); } catch { } } _adapters.Clear(); // 停止所有线程 StopAll(); // 取消策略订阅 if (_taskStrategy is JsonFileTaskStrategy jsonStrategy) { jsonStrategy.Changed -= OnTaskStrategyChanged; jsonStrategy.DisposeWatcher(); } System.Diagnostics.Debug.WriteLine("[AbstractLoopMission] Dispose: 资源已释放"); } #endregion #region 辅助类型 /// /// 任务列表变更事件参数 /// public class TaskListChangedEventArgs : EventArgs { /// 变更类型 public TaskChangeType ChangeType { get; } /// 变更的任务索引(Replaced 时为 -1) public int Index { get; } /// 变更的任务(Replaced 时为 null) public LoopTask Task { get; } /// /// 创建任务列表变更事件参数 /// /// 变更类型 /// 任务索引 /// 任务对象 public TaskListChangedEventArgs(TaskChangeType changeType, int index, LoopTask task) { ChangeType = changeType; Index = index; Task = task; } } /// /// 任务变更类型枚举 /// public enum TaskChangeType { /// 添加任务 Added, /// 更新任务 Updated, /// 移除任务 Removed, /// 替换全部任务 Replaced } /// /// 循环点配置类 /// 用于定义循环点的规则和属性 /// public class LoopPoint { /// 唯一标识 public string Id { get; set; } = Guid.NewGuid().ToString("N"); /// 名称 public string Name { get; set; } = string.Empty; /// 自定义标签集合 public Dictionary Tags { get; } = new Dictionary(); /// 进入规则 public IEnterRule EnterRule { get; set; } /// 退出规则 public IExitRule ExitRule { get; set; } /// 汇合规则 public IJoinRule JoinRule { get; set; } /// 分流规则 public IBranchRule BranchRule { get; set; } } /// /// JSON 文件任务策略类 /// 从 tasklist.json 读取任务配置,支持文件变更自动刷新 /// public class JsonFileTaskStrategy : ITaskStrategy, IDisposable { /// JSON 文件路径 public string JsonPath { get; set; } /// 任务缓存列表 private List _cache = new List(); /// 文件系统监听器 private FileSystemWatcher _watcher; /// 配置变更事件 public event EventHandler Changed; /// /// 创建 JSON 文件任务策略 /// /// JSON 文件路径(null 时使用默认路径 tasklist.json) public JsonFileTaskStrategy(string jsonPath = null) { JsonPath = string.IsNullOrWhiteSpace(jsonPath) ? Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "tasklist.json") : jsonPath; EnsureWatcher(); Refresh(); } /// /// 获取任务列表 /// /// 任务列表副本 public IEnumerable GetTasks() { return _cache.ToArray(); } /// /// 刷新任务列表(从文件重新读取) /// public void Refresh() { try { if (!File.Exists(JsonPath)) { _cache = new List(); return; } string json = File.ReadAllText(JsonPath); _cache = JsonConvert.DeserializeObject>(json) ?? new List(); } catch { _cache = new List(); } } /// /// 确保文件监听器已启动 /// public void EnsureWatcher() { try { if (_watcher != null) return; string directory = Path.GetDirectoryName(JsonPath); string fileName = Path.GetFileName(JsonPath); if (string.IsNullOrEmpty(directory)) return; _watcher = new FileSystemWatcher(directory, fileName) { NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.Size | NotifyFilters.FileName }; _watcher.Changed += (s, e) => OnFileChanged(); _watcher.Renamed += (s, e) => OnFileChanged(); _watcher.EnableRaisingEvents = true; } catch { } } private void OnFileChanged() { try { Thread.Sleep(50); Refresh(); Changed?.Invoke(this, EventArgs.Empty); } catch { } } public void DisposeWatcher() { try { if (_watcher != null) { _watcher.EnableRaisingEvents = false; _watcher.Dispose(); _watcher = null; } } catch { } } public void Dispose() { DisposeWatcher(); } } #endregion #region 路径查找辅助方法 /// /// 查找从起点到终点之间的所有站点(基于轨道连接的广度优先搜索) /// /// 起点站点ID /// 终点站点ID /// 从起点到终点的站点ID列表(包含起点和终点),未找到路径返回空列表 /// /// /// // 查找站点100到站点200之间的所有站点 /// var sites = GetSitesBetween(100, 200); /// // 返回: [100, 150, 180, 200] /// /// protected List GetSitesBetween(int startSiteId, int endSiteId) { if (startSiteId == endSiteId) return new List { startSiteId }; try { // 构建站点邻接表 var adjacency = BuildSiteAdjacency(); // 广度优先搜索 var visited = new HashSet(); var queue = new Queue>(); queue.Enqueue(new List { startSiteId }); visited.Add(startSiteId); while (queue.Count > 0) { var currentPath = queue.Dequeue(); int currentSite = currentPath[currentPath.Count - 1]; // 找到终点,返回路径 if (currentSite == endSiteId) return currentPath; // 获取相邻站点 if (!adjacency.TryGetValue(currentSite, out var neighbors)) continue; foreach (var neighbor in neighbors) { if (visited.Contains(neighbor)) continue; visited.Add(neighbor); var newPath = new List(currentPath) { neighbor }; queue.Enqueue(newPath); } } // 未找到路径 return new List(); } catch (Exception ex) { System.Diagnostics.Debug.WriteLine($"[AbstractLoopMission] GetSitesBetween({startSiteId}, {endSiteId}) error: {ex.Message}"); return new List(); } } /// /// 查找从起点到终点之间的所有站点对象 /// /// 起点站点ID /// 终点站点ID /// 从起点到终点的站点对象列表 protected List GetSiteObjectsBetween(int startSiteId, int endSiteId) { var siteIds = GetSitesBetween(startSiteId, endSiteId); var result = new List(); foreach (var siteId in siteIds) { var site = SimpleLib.GetSite(siteId); if (site != null) result.Add(site); } return result; } /// /// 构建站点邻接表(基于轨道连接,考虑轨道方向) /// /// 邻接表:站点ID -> 可达的相邻站点ID列表 private Dictionary> BuildSiteAdjacency() { var adjacency = new Dictionary>(); foreach (var track in SimpleLib.GetAllTracks()) { int siteA = track.siteA; int siteB = track.siteB; // 根据轨道方向添加邻接关系 // direction: 0=双向, 1=A->B, 2=B->A switch (track.direction) { case 0: // 双向 AddAdjacency(adjacency, siteA, siteB); AddAdjacency(adjacency, siteB, siteA); break; case 1: // A -> B AddAdjacency(adjacency, siteA, siteB); break; case 2: // B -> A AddAdjacency(adjacency, siteB, siteA); break; default: AddAdjacency(adjacency, siteA, siteB); AddAdjacency(adjacency, siteB, siteA); break; } } return adjacency; } /// /// 向邻接表添加边 /// private void AddAdjacency(Dictionary> adjacency, int from, int to) { if (!adjacency.ContainsKey(from)) adjacency[from] = new HashSet(); adjacency[from].Add(to); } /// /// 检查路径上是否有其他车辆(不包含起点和终点) /// /// 起点站点ID /// 终点站点ID /// 排除的车辆(通常是当前车辆) /// true=路径上有车辆,false=路径畅通 protected bool HasCarOnPath(int startSiteId, int endSiteId, Car excludeCar = null) { var pathSites = GetSitesBetween(startSiteId, endSiteId); // 排除起点和终点 var middleSites = pathSites .Skip(1) .Take(pathSites.Count - 2) .ToList(); if (middleSites.Count == 0) return false; foreach (var car in SimpleLib.GetAllCars().OfType()) { if (excludeCar != null && car == excludeCar) continue; if (car?.status?.holdingLocks == null) continue; // 检查车辆是否在路径中间站点 foreach (var siteId in middleSites) { if (car.status.holdingLocks.Contains(siteId)) return true; } } return false; } /// /// 获取路径上的所有车辆 /// /// 起点站点ID /// 终点站点ID /// 是否包含起点和终点的车辆 /// 路径上的车辆列表 protected List GetCarsOnPath(int startSiteId, int endSiteId, bool includeEndpoints = false) { var pathSites = GetSitesBetween(startSiteId, endSiteId); if (!includeEndpoints && pathSites.Count > 2) { pathSites = pathSites .Skip(1) .Take(pathSites.Count - 2) .ToList(); } var carsOnPath = new List(); foreach (var car in SimpleLib.GetAllCars().OfType()) { if (car?.status?.holdingLocks == null) continue; foreach (var siteId in pathSites) { if (car.status.holdingLocks.Contains(siteId)) { carsOnPath.Add(car); break; } } } return carsOnPath; } /// /// 计算两站点之间的路径长度(经过的站点数) /// /// 起点站点ID /// 终点站点ID /// 路径长度(站点数),未找到路径返回 -1 protected int GetPathLength(int startSiteId, int endSiteId) { var path = GetSitesBetween(startSiteId, endSiteId); return path.Count > 0 ? path.Count : -1; } #endregion #region 任务策略匹配 /// /// 路径缓存(避免重复计算) /// [JsonIgnore] private readonly Dictionary<(int, int), List> _pathCache = new Dictionary<(int, int), List>(); /// /// 路径缓存锁 /// [JsonIgnore] private readonly object _pathCacheLock = new object(); /// /// 根据车辆当前站点匹配最优任务策略 /// 匹配逻辑: /// /// 遍历所有任务策略,获取每个策略从当前点到目标点的完整路径 /// 检查车辆当前站点是否在该路径上 /// 返回包含当前站点且优先级最高的策略 /// /// /// 车辆 /// 最优匹配结果,无匹配返回 null protected TaskPathMatchResult FindBestTaskForCar(Car car) { if (car?.status?.holdingLocks == null || car.status.holdingLocks.Length == 0) return null; int currentSiteId = car.status.holdingLocks[0]; return FindBestTaskForSite(currentSiteId); } /// /// 根据站点ID匹配最优任务策略 /// 遍历所有任务,找出包含该站点的路径,返回优先级最高的任务 /// /// 当前站点ID /// 最优匹配结果,无匹配返回 null protected TaskPathMatchResult FindBestTaskForSite(int siteId) { List snapshot; lock (_tasksLock) { snapshot = Tasks.Where(T=>T.IsViaPoint).ToList(); } var matchResults = new List(); foreach (var task in snapshot) { // 获取任务的完整路径(从当前点到目标点) var fullPath = GetSitesBetweenCached(task.CurrentStationId, task.TargetStationId); if (fullPath.Count == 0) continue; // 检查站点是否在路径上 int indexInPath = fullPath.IndexOf(siteId); if (indexInPath < 0) continue; // 创建匹配结果 var result = new TaskPathMatchResult { Task = task, CurrentSiteId = siteId, FullPath = fullPath, IndexInPath = indexInPath, RemainingPath = fullPath.Skip(indexInPath).ToList(), DistanceToTarget = fullPath.Count - indexInPath - 1, IsAtStartPoint = (indexInPath == 0), IsAtEndPoint = (indexInPath == fullPath.Count - 1), IsOnMiddlePath = (indexInPath > 0 && indexInPath < fullPath.Count - 1) }; matchResults.Add(result); } if (matchResults.Count == 0) return null; // 按匹配优先级排序: // 1. 起点匹配优先 // 2. 任务优先级高的优先 // 3. 距离目标近的优先 var bestMatch = matchResults.Where(r=>!r.IsAtEndPoint && !r.IsAtStartPoint ) .OrderByDescending(r => r.IsAtStartPoint ? 1 : 0) // 起点匹配优先 .ThenByDescending(r => r.Task.Priority) // 任务优先级高的优先 .ThenBy(r => r.DistanceToTarget) // 距离目标近的优先 .FirstOrDefault(); Diagnosis.Post($"[AbstractLoopMission] 小车获取到的任务策略 {bestMatch.ToString()} )", $"路径中间点初始化", true); return bestMatch; } /// /// 根据站点ID查找所有匹配的任务策略 /// 返回所有包含该站点的任务,按优先级排序 /// /// 站点ID /// 匹配结果列表(按优先级降序) protected List FindAllTasksForSite(int siteId) { List snapshot; lock (_tasksLock) { snapshot = Tasks.ToList(); } var matchResults = new List(); foreach (var task in snapshot) { // 获取任务的完整路径 var fullPath = GetSitesBetweenCached(task.CurrentStationId, task.TargetStationId); if (fullPath.Count == 0) continue; // 检查站点是否在路径上 int indexInPath = fullPath.IndexOf(siteId); if (indexInPath < 0) continue; var result = new TaskPathMatchResult { Task = task, CurrentSiteId = siteId, FullPath = fullPath, IndexInPath = indexInPath, RemainingPath = fullPath.Skip(indexInPath).ToList(), DistanceToTarget = fullPath.Count - indexInPath - 1, IsAtStartPoint = (indexInPath == 0), IsAtEndPoint = (indexInPath == fullPath.Count - 1), IsOnMiddlePath = (indexInPath > 0 && indexInPath < fullPath.Count - 1) }; matchResults.Add(result); } // 按优先级排序 return matchResults .OrderByDescending(r => r.IsAtStartPoint ? 1 : 0) .ThenByDescending(r => r.Task.Priority) .ThenBy(r => r.DistanceToTarget) .ToList(); } /// /// 获取车辆应该前往的下一个目标站点 /// 根据最优匹配策略返回目标站点ID /// /// 车辆 /// 目标站点ID,无匹配返回 -1 protected int GetRecommendedTargetForCar(Car car) { var match = FindBestTaskForCar(car); return match?.Task?.TargetStationId ?? -1; } /// /// 获取车辆的下一个中间站点 /// 返回当前位置的下一个路径节点 /// /// 车辆 /// 下一站点ID,无匹配返回 -1 protected int GetNextSiteForCar(Car car) { var match = FindBestTaskForCar(car); if (match?.RemainingPath == null || match.RemainingPath.Count < 2) return -1; return match.RemainingPath[1]; } /// /// 获取缓存的路径(避免重复计算) /// private List GetSitesBetweenCached(int startSiteId, int endSiteId) { var key = (startSiteId, endSiteId); lock (_pathCacheLock) { if (_pathCache.TryGetValue(key, out var cached)) return new List(cached); var path = GetSitesBetween(startSiteId, endSiteId); _pathCache[key] = path; return new List(path); } } /// /// 清除路径缓存 /// 当任务策略变更或地图变更时调用 /// protected void ClearPathCache() { lock (_pathCacheLock) { _pathCache.Clear(); } } /// /// 根据任务ID查找任务 /// /// 任务ID /// 任务对象,未找到返回 null protected LoopTask FindTaskById(int taskId) { lock (_tasksLock) { return Tasks.FirstOrDefault(t => t.Id == taskId); } } /// /// 判断车辆是否在指定任务的路径上 /// /// 车辆 /// 任务 /// 是否在路径上 protected bool IsCarOnTaskPath(Car car, LoopTask task) { if (car?.status?.holdingLocks == null || car.status.holdingLocks.Length == 0) return false; int currentSiteId = car.status.holdingLocks[0]; var path = GetSitesBetweenCached(task.CurrentStationId, task.TargetStationId); return path.Contains(currentSiteId); } #endregion #region 任务匹配结果类型 /// /// 任务路径匹配结果 /// public class TaskPathMatchResult { /// 匹配的任务 public LoopTask Task { get; set; } /// 车辆当前站点ID public int CurrentSiteId { get; set; } /// 完整路径(从任务起点到终点) public List FullPath { get; set; } = new List(); /// 当前站点在路径中的索引 public int IndexInPath { get; set; } /// 剩余路径(从当前站点到终点) public List RemainingPath { get; set; } = new List(); /// 到目标的距离(剩余站点数) public int DistanceToTarget { get; set; } /// 是否在任务起点 public bool IsAtStartPoint { get; set; } /// 是否在任务终点 public bool IsAtEndPoint { get; set; } /// 是否在路径中间 public bool IsOnMiddlePath { get; set; } /// 任务ID public int TaskId => Task?.Id ?? 0; /// 任务优先级 public int TaskPriority => Task?.Priority ?? 0; /// 目标站点ID public int TargetSiteId => Task?.TargetStationId ?? -1; /// 下一个站点ID(当前站点的下一跳) public int NextSiteId => RemainingPath.Count > 1 ? RemainingPath[1] : -1; /// 路径进度百分比 public double ProgressPercent => FullPath.Count > 1 ? (double)IndexInPath / (FullPath.Count - 1) * 100 : 0; /// /// 获取匹配信息描述 /// public override string ToString() { string position = IsAtStartPoint ? "起点" : (IsAtEndPoint ? "终点" : "中间"); return $"任务[ID={TaskId}, {Task?.CurrentStationId}->{Task?.TargetStationId}] " + $"优先级={TaskPriority}, 当前={CurrentSiteId}({position}), " + $"进度={ProgressPercent:F1}%, 剩余={DistanceToTarget}站, " + $"下一站={NextSiteId}"; } } #endregion private LoopViewer lv; [MethodMember(Name = "任务管理", Description = "任务管理")] public void Print() { lv = new LoopViewer(); lv.Show(); } [MethodMember(Name = "启动环线", Description = "启动环线")] public void Start() { StartAll(); } [MethodMember(Name = "停止环线", Description = "停止环线")] public override void Stop() { StopAll(); } [MethodMember(Name = "一键上线所有AGV", Description = "将所有已初始化的正常 AGV 标记为在线", RequiresPlatformConfirm = true, ConfirmMessage = "确认将所有已初始化的正常 AGV 一键上线?")] public void OnlineAllCars() { // 确认由 MethodMember.ConfirmMessage + ObjectEditorParity/SafeUI 居中弹出,此处勿再套 FairyUiHelper。 Task.Run(OnlineAllCarsCore); } private void OnlineAllCarsCore() { int count = 0; foreach (var car in SimpleLib.GetAllCars().OfType()) { if (car.GetLastSite() == -1 && Commons.GetVehicleStatus(car) == VehicleStatus.Normal) { Commons.AddOrUpdateTag(car.tags, "Online", "true"); car.Reset(); count++; } } status.status = $"已上线 {count} 台AGV"; Diagnosis.Post($"[AbstractLoopMission] 一键上线所有AGV:已上线 {count} 台", "Loop-OnlineAll", true); } [MethodMember(Name = "一键结束所有AGV任务", Description = "停止环线调度并清除所有 AGV 的环线任务分配", RequiresPlatformConfirm = true, ConfirmMessage = "确认停止环线调度并结束所有 AGV 的环线任务?")] public void StopAllCarTasks() { // 确认由 MethodMember.ConfirmMessage + ObjectEditorParity/SafeUI 居中弹出,此处勿再套 FairyUiHelper。 Task.Run(StopAllCarTasksCore); } private void StopAllCarTasksCore() { int count = 0; foreach (var car in SimpleLib.GetAllCars().OfType()) { Commons.DeleteTag(car.tags, "goalSite"); Commons.DeleteTag(car.tags, "loopAssigned"); Commons.DeleteTag(car.tags, "occupied"); car.ForceStop(); count++; } status.status = $"已结束 {count} 台AGV任务"; Diagnosis.Post($"[AbstractLoopMission] 一键结束所有AGV任务:已停止环线并清理 {count} 台 AGV 的任务标签", "Loop-StopAllTasks", true); } } }