Files
StandardSence/StandardScene.Core/Chained/AbstractLoopMission.cs
T

2228 lines
83 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using LoopViewerApp;
using Newtonsoft.Json;
using SimpleLite.RCS;
using SimpleLite.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
{
/// <summary>
/// 抽象环线任务基类
/// <para>核心功能:</para>
/// <list type="bullet">
/// <item><description>支持多种启动类型:AutoLoop(自动循环)、Api、Plc、ButtonBox(外部触发)</description></item>
/// <item><description>支持多种任务类别:Loop(普通循环)、BranchPoint(分流点)、JoinPoint(汇合点)</description></item>
/// <item><description>支持流量控制:限制目标站点的最大车辆数</description></item>
/// <item><description>支持优先级调度:高优先级任务优先处理</description></item>
/// <item><description>支持配置热更新:tasklist.json 文件变更自动刷新</description></item>
/// <item><description>外部触发接口:子类可重写 OnApiTrigger/OnPlcTrigger/OnButtonTrigger 实现业务逻辑</description></item>
/// </list>
/// </summary>
/// <remarks>
/// <para>子类实现示例:</para>
/// <code>
/// public class MyLoopMission : AbstractLoopMission
/// {
/// protected override ExternalTriggerResult OnApiTrigger(int currentSiteId, LoopTask task, Car car)
/// {
/// // 返回 UseTarget(目标站点) - 使用子类指定的目标
/// // 返回 UseConfigTarget() - 使用配置文件目标
/// // 返回 Fail() - 不分配任务
/// return ExternalTriggerResult.UseConfigTarget();
/// }
/// }
/// </code>
/// </remarks>
public abstract class AbstractLoopMission : Mission, IDisposable
{
#region 常量定义
/// <summary>策略同步线程轮询间隔(毫秒)</summary>
private const int STRATEGY_SYNC_INTERVAL_MS = 1000;
/// <summary>业务逻辑线程轮询间隔(毫秒)</summary>
private const int LOGIC_LOOP_INTERVAL_MS = 300;
/// <summary>文件变更防抖延迟(毫秒)</summary>
private const int FILE_CHANGE_DEBOUNCE_MS = 50;
/// <summary>异常恢复等待时间(毫秒)</summary>
private const int ERROR_RECOVERY_DELAY_MS = 3000;
/// <summary>脚本异常检测触发延迟(毫秒)</summary>
private const int SCRIPT_ERROR_TRIGGER_DELAY_MS = 3000;
#endregion
#region 外部触发结果类
/// <summary>
/// 外部触发处理结果类
/// <para>用于子类重写外部触发接口时返回处理结果,决定目标站点的分配方式</para>
/// </summary>
/// <example>
/// 使用示例:
/// <code>
/// // 方式1:使用子类指定的目标站点
/// return ExternalTriggerResult.UseTarget(200);
///
/// // 方式2:使用配置文件中的目标站点(相当于返回 true)
/// return ExternalTriggerResult.UseConfigTarget();
///
/// // 方式3:业务失败,不分配任务
/// return ExternalTriggerResult.Fail();
/// </code>
/// </example>
public class ExternalTriggerResult
{
/// <summary>
/// 是否处理成功
/// <para>true = 成功,将根据 CustomTargetSiteId 分配目标站点</para>
/// <para>false = 失败或跳过,不分配任务</para>
/// </summary>
public bool Success { get; set; }
/// <summary>
/// 子类指定的目标站点ID
/// <para>有值时使用此目标站点</para>
/// <para>null 时使用配置文件中的 TargetStationId</para>
/// </summary>
public int? CustomTargetSiteId { get; set; }
/// <summary>
/// 创建成功结果,使用子类指定的目标站点
/// </summary>
/// <param name="targetSiteId">目标站点ID</param>
/// <returns>包含指定目标的成功结果</returns>
public static ExternalTriggerResult UseTarget(int targetSiteId)
{
return new ExternalTriggerResult
{
Success = true,
CustomTargetSiteId = targetSiteId
};
}
/// <summary>
/// 创建成功结果,使用配置文件中的目标站点
/// <para>等同于业务处理成功返回 true 的效果</para>
/// </summary>
/// <returns>使用配置目标的成功结果</returns>
public static ExternalTriggerResult UseConfigTarget()
{
return new ExternalTriggerResult
{
Success = true,
CustomTargetSiteId = null
};
}
/// <summary>
/// 创建失败结果,不分配任务
/// <para>等同于业务处理失败返回 false 的效果</para>
/// </summary>
/// <returns>失败结果</returns>
public static ExternalTriggerResult Fail()
{
return new ExternalTriggerResult { Success = false };
}
}
#endregion
#region 状态与字段
/// <summary>
/// 环线任务状态类
/// </summary>
public class AbstractLoopMissionStatus : MissionStatus
{
/// <summary>当前任务数量</summary>
public int TaskCount { get; set; }
/// <summary>最后一次错误信息</summary>
public string LastError { get; set; } = "/";
}
/// <summary>任务状态对象</summary>
/* [JsonIgnore]
public override MissionStatus status { get; set; } = new LoopMissionStatus();*/
/// <summary>策略同步线程</summary>
[JsonIgnore]
private Thread _strategyThread;
/// <summary>策略同步线程运行标志</summary>
[JsonIgnore]
private volatile bool _strategyRunning;
/// <summary>业务逻辑线程</summary>
[JsonIgnore]
private Thread _logicThread;
/// <summary>业务逻辑线程运行标志</summary>
[JsonIgnore]
private volatile bool _logicRunning;
/// <summary>任务集合同步锁</summary>
[JsonIgnore]
private readonly object _tasksLock = new object();
/// <summary>任务列表(线程安全访问需使用 _tasksLock</summary>
[JsonIgnore]
protected readonly List<LoopTask> Tasks = new List<LoopTask>();
/// <summary>触发器适配器列表</summary>
[JsonIgnore]
private readonly List<ITriggerAdapter> _adapters = new List<ITriggerAdapter>();
/// <summary>任务策略实例</summary>
private ITaskStrategy _taskStrategy;
/// <summary>
/// 任务策略属性
/// <para>支持热更新,设置时自动订阅/取消订阅变更事件</para>
/// </summary>
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();
}
}
}
/// <summary>任务列表变更事件</summary>
public event EventHandler<TaskListChangedEventArgs> TaskListChanged;
#endregion
#region 构造函数
/// <summary>
/// 初始化抽象环线任务基类
/// <para>自动使用默认的 JSON 文件策略并加载初始任务配置</para>
/// </summary>
protected AbstractLoopMission()
{
TaskStrategy = new JsonFileTaskStrategy();
ApplyTaskStrategy();
}
#endregion
#region 任务管理方法
/// <summary>
/// 获取当前任务列表的只读副本(线程安全)
/// </summary>
/// <returns>任务列表快照</returns>
public IReadOnlyList<LoopTask> GetTasks()
{
lock (_tasksLock)
{
return Tasks.ToArray();
}
}
/// <summary>
/// 添加任务到列表末尾
/// </summary>
/// <param name="task">要添加的任务</param>
/// <returns>新任务的索引</returns>
/// <exception cref="ArgumentNullException">task 为 null 时抛出</exception>
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;
}
}
/// <summary>
/// 更新指定索引的任务
/// </summary>
/// <param name="index">任务索引</param>
/// <param name="task">新的任务数据</param>
/// <returns>是否更新成功</returns>
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;
}
}
/// <summary>
/// 移除指定索引的任务
/// </summary>
/// <param name="index">任务索引</param>
/// <returns>是否移除成功</returns>
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 车辆查找辅助方法
/// <summary>
/// 查找恰好停在指定站点的车辆
/// <para>条件:车辆的 holdingLocks 只包含该站点(表示车辆已到达并停稳)</para>
/// </summary>
/// <param name="siteId">站点ID</param>
/// <returns>找到的车辆,未找到返回 null</returns>
protected virtual Car FindCarArrivedAtSite(int siteId)
{
try
{
return SimpleLib.GetAllCars()
.OfType<Car>()
.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;
}
}
/// <summary>
/// 获取当前在站或正在前往指定站点的所有车辆
/// <para>用于流量控制统计</para>
/// </summary>
/// <param name="siteId">站点ID</param>
/// <returns>车辆集合</returns>
protected IEnumerable<Car> GetCarsAtOrHeadingToSite(int siteId)
{
try
{
return SimpleLib.GetAllCars()
.OfType<Car>()
.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<Car>();
}
}
/// <summary>
/// 统计当前在站或正在前往指定站点的车辆数量
/// </summary>
/// <param name="siteId">站点ID</param>
/// <returns>车辆数量</returns>
protected int CountCarsAtOrHeadingToSite(int siteId)
{
return GetCarsAtOrHeadingToSite(siteId).Count();
}
/// <summary>
/// 检查车辆是否空闲(未被占用、未有目标、未执行任务)
/// </summary>
/// <param name="car">车辆</param>
/// <returns>是否空闲</returns>
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 流量控制检查
/// <summary>
/// 检查目标站点是否满足流量控制条件
/// <para>统计当前在站或正在前往该站点的车辆数量,与最大允许数比较</para>
/// </summary>
/// <param name="targetSiteId">目标站点ID</param>
/// <param name="maxAllowed">最大允许车辆数(0或负数表示不限制)</param>
/// <returns>true=可以发车,false=已达上限需等待</returns>
protected bool CheckTrafficControl(int targetSiteId, int maxAllowed)
{
// 不限制流量的情况
if (maxAllowed <= 0)
return true;
int currentCount = CountCarsAtOrHeadingToSite(targetSiteId);
return currentCount < maxAllowed;
}
#endregion
#region 外部触发接口(子类重写)
/// <summary>
/// API 触发处理接口
/// <para>当任务的 StartType 为 Api 时,车辆到达当前站点后会调用此方法</para>
/// <para>子类重写此方法实现 API 触发的业务逻辑</para>
/// </summary>
/// <param name="currentSiteId">当前站点ID</param>
/// <param name="task">匹配的任务配置</param>
/// <param name="car">到达的车辆</param>
/// <returns>
/// 处理结果:
/// <list type="bullet">
/// <item><description><see cref="ExternalTriggerResult.UseTarget"/> - 使用子类指定的目标站点</description></item>
/// <item><description><see cref="ExternalTriggerResult.UseConfigTarget"/> - 使用配置文件中的目标站点</description></item>
/// <item><description><see cref="ExternalTriggerResult.Fail"/> - 业务失败,不分配任务</description></item>
/// </list>
/// </returns>
protected virtual ExternalTriggerResult OnApiTrigger(int currentSiteId, LoopTask task, Car car)
{
// 默认返回失败,子类需重写以启用 API 触发功能
return ExternalTriggerResult.Fail();
}
/// <summary>
/// PLC 信号触发处理接口
/// <para>当任务的 StartType 为 Plc 时,车辆到达当前站点后会调用此方法</para>
/// <para>子类重写此方法实现 PLC 触发的业务逻辑</para>
/// </summary>
/// <param name="currentSiteId">当前站点ID</param>
/// <param name="task">匹配的任务配置</param>
/// <param name="car">到达的车辆</param>
/// <returns>处理结果</returns>
protected virtual ExternalTriggerResult OnPlcTrigger(int currentSiteId, LoopTask task, Car car)
{
// 默认返回失败,子类需重写以启用 PLC 触发功能
return ExternalTriggerResult.Fail();
}
/// <summary>
/// 充电条件信号触发处理接口
/// <para>当任务的 StartType 为 Plc 时,车辆到达当前站点后会调用此方法</para>
/// <para>子类重写此方法实现 PLC 触发的业务逻辑</para>
/// </summary>
/// <param name="currentSiteId">当前站点ID</param>
/// <param name="task">匹配的任务配置</param>
/// <param name="car">到达的车辆</param>
/// <returns>处理结果</returns>
protected virtual ExternalTriggerResult OnChargeTrigger(int currentSiteId, LoopTask task, Car car)
{
// 默认返回失败,子类需重写已启用去充电触发功能
return ExternalTriggerResult.Fail();
}
/// <summary>
/// 按钮盒触发处理接口
/// <para>当任务的 StartType 为 ButtonBox 时,车辆到达当前站点后会调用此方法</para>
/// <para>子类重写此方法实现按钮盒触发的业务逻辑</para>
/// </summary>
/// <param name="currentSiteId">当前站点ID</param>
/// <param name="task">匹配的任务配置</param>
/// <param name="car">到达的车辆</param>
/// <returns>处理结果</returns>
protected virtual ExternalTriggerResult OnButtonTrigger(int currentSiteId, LoopTask task, Car car)
{
// 默认返回失败,子类需重写以启用按钮盒触发功能
return ExternalTriggerResult.Fail();
}
/// <summary>
/// 根据启动类型调用对应的子类触发接口
/// </summary>
/// <param name="startType">启动类型</param>
/// <param name="currentSiteId">当前站点ID</param>
/// <param name="task">匹配的任务配置</param>
/// <param name="car">到达的车辆</param>
/// <returns>子类返回的处理结果</returns>
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 任务策略管理
/// <summary>
/// 从任务策略刷新并应用任务列表
/// <para>读取配置文件内容并替换当前任务列表</para>
/// </summary>
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}");
}
}
/// <summary>
/// 任务策略配置文件变更处理
/// </summary>
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 核心业务逻辑
/// <summary>
/// 业务逻辑主循环
/// <para>在 _logicThread 线程中执行,每 500ms 处理一次任务调度</para>
/// </summary>
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}");
}
}
}
/// <summary>
/// 处理所有任务(主调度入口)
/// <para>按顺序处理:自动循环任务 → 外部触发任务 → 分流点任务 → 汇合点任务 → 循环启动动作</para>
/// </summary>
protected virtual void ProcessAutoLoopTasks()
{
// 获取任务快照(避免长时间持锁)
List<LoopTask> 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();
}
/// <summary>
/// 处理自动循环类型任务
/// <para>筛选 StartType=AutoLoop 且 Kind=Loop 的任务,按优先级处理</para>
/// </summary>
/// <param name="snapshot">任务快照</param>
private void ProcessAutoLoopTypeTasks(List<LoopTask> 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);
}
}
}
/// <summary>
/// 处理外部触发类型任务(API/PLC/ButtonBox
/// <para>依次处理各类型的外部触发任务</para>
/// </summary>
/// <param name="snapshot">任务快照</param>
private void ProcessExternalTriggerTasks(List<LoopTask> snapshot)
{
// 依次处理 API、PLC、ButtonBox 类型任务
ProcessExternalTasksByType(snapshot, TaskStartType.Api);
ProcessExternalTasksByType(snapshot, TaskStartType.Plc);
ProcessExternalTasksByType(snapshot, TaskStartType.ButtonBox);
ProcessExternalTasksByType(snapshot, TaskStartType.Charge);
}
/// <summary>
/// 按启动类型处理外部触发任务
/// <para>处理普通循环任务、分流点任务、汇合点任务</para>
/// </summary>
/// <param name="snapshot">任务快照</param>
/// <param name="startType">启动类型</param>
private void ProcessExternalTasksByType(List<LoopTask> 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);
}
/// <summary>
/// 处理外部触发的分流点任务
/// <para>按当前站点分组,优先级排序,调用子类接口后分配目标</para>
/// </summary>
/// <param name="snapshot">任务快照</param>
/// <param name="startType">启动类型</param>
private void ProcessExternalBranchTasks(List<LoopTask> 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; // 分流点只分配一个目标
}
}
}
}
/// <summary>
/// 处理外部触发的汇合点任务
/// <para>按目标站点分组(汇合点),每组中的策略按优先级排列,排列后每个策略根据流量判断,最后采用接口返回结果</para>
/// </summary>
/// <param name="snapshot">任务快照</param>
/// <param name="startType">启动类型</param>
private void ProcessExternalJoinTasks(List<LoopTask> 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);
}
}
}
}
/// <summary>
/// 循环启动动作
/// <para>处理站点配置的默认目标、执行路径规划导航、检测并处理脚本异常</para>
/// <para>注意:此方法不拆分,保持原有逻辑完整性</para>
/// </summary>
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)}");
}
}
/// <summary>
/// 导航车辆到目标站点
/// <para>创建路径规划并执行移动任务</para>
/// </summary>
/// <param name="car">车辆</param>
/// <param name="targetSite">目标站点</param>
/// <param name="action">动作参数,默认为 "/"</param>
/// <param name="reverse">是否倒车,默认为 false</param>
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);
}
}
/// <summary>
/// 处理自动分流点任务
/// <para>筛选 StartType=AutoLoop 且 Kind=BranchPoint 的任务</para>
/// <para>按当前站点分组,优先级排序,选择流量允许的目标</para>
/// </summary>
/// <param name="allTasks">任务列表</param>
protected virtual void ProcessBranchPointTasks(List<LoopTask> 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; // 分流点只分配一个目标
}
}
}
}
/// <summary>
/// 处理自动汇合点任务
/// <para>筛选 StartType=AutoLoop 且 Kind=JoinPoint 的任务</para>
/// <para>按目标站点分组,优先级排序,选择有车在站的最高优先级任务</para>
/// </summary>
/// <param name="allTasks">任务列表</param>
protected virtual void ProcessJoinPointTasks(List<LoopTask> 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; // 汇合点一次只放行一辆车
}
}
}
}
/// <summary>
/// 分流点目标选择
/// <para>按优先级选择流量允许的目标站点</para>
/// </summary>
/// <param name="branchTask">当前分流任务</param>
/// <param name="allTasks">所有任务列表</param>
/// <returns>选中的目标站点ID</returns>
protected virtual int SelectBranchTarget(LoopTask branchTask, List<LoopTask> 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;
}
/// <summary>
/// 汇合点通行判断
/// <para>检查当前任务是否有最高优先级通行权(是否有更高优先级的车在等待)</para>
/// </summary>
/// <param name="joinTask">当前汇合任务</param>
/// <param name="allTasks">所有任务列表</param>
/// <returns>true=可以通行,false=需要等待更高优先级的车</returns>
protected virtual bool CanJoinPass(LoopTask joinTask, List<LoopTask> 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;
}
/// <summary>
/// 为车辆分配目标站点
/// <para>设置车辆的 goalSite 标签和 loopAssigned 时间戳</para>
/// </summary>
/// <param name="car">车辆</param>
/// <param name="targetSiteId">目标站点ID</param>
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 线程生命周期
/// <summary>
/// 启动策略同步线程
/// <para>每秒同步一次配置文件,自动更新任务列表</para>
/// </summary>
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: 策略同步线程已启动");
}
/// <summary>
/// 启动业务逻辑线程
/// <para>每 500ms 处理一次任务调度</para>
/// </summary>
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: 业务逻辑线程已启动");
}
/// <summary>
/// 停止策略同步线程
/// </summary>
public virtual void StopLoop()
{
try
{
_strategyRunning = false;
_strategyThread?.Join(2000);
_strategyThread = null;
System.Diagnostics.Debug.WriteLine("[AbstractLoopMission] StopLoop: 策略同步线程已停止");
}
catch { }
}
/// <summary>
/// 停止业务逻辑线程
/// </summary>
public virtual void StopLogicLoop()
{
try
{
_logicRunning = false;
_logicThread?.Join(2000);
_logicThread = null;
System.Diagnostics.Debug.WriteLine("[AbstractLoopMission] StopLogicLoop: 业务逻辑线程已停止");
}
catch { }
}
/// <summary>
/// 启动所有线程(策略同步 + 业务逻辑)
/// </summary>
public virtual void StartAll()
{
// ApplyTaskStrategy();
MarkTaskStartSitesAsTerminal();
StartLoop();
StartLogicLoop();
status.status = "已启动";
}
/// <summary>
/// 将任务策略中的起始站点标记为 terminal=true
/// </summary>
private void MarkTaskStartSitesAsTerminal()
{
List<int> 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");
}
}
/// <summary>
/// 停止所有线程
/// </summary>
public virtual void StopAll()
{
StopLoop();
StopLogicLoop();
status.status = "已停止";
}
/// <summary>
/// 释放资源
/// <para>清理触发器适配器、停止所有线程、取消策略订阅</para>
/// </summary>
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 辅助类型
/// <summary>
/// 任务列表变更事件参数
/// </summary>
public class TaskListChangedEventArgs : EventArgs
{
/// <summary>变更类型</summary>
public TaskChangeType ChangeType { get; }
/// <summary>变更的任务索引(Replaced 时为 -1</summary>
public int Index { get; }
/// <summary>变更的任务(Replaced 时为 null</summary>
public LoopTask Task { get; }
/// <summary>
/// 创建任务列表变更事件参数
/// </summary>
/// <param name="changeType">变更类型</param>
/// <param name="index">任务索引</param>
/// <param name="task">任务对象</param>
public TaskListChangedEventArgs(TaskChangeType changeType, int index, LoopTask task)
{
ChangeType = changeType;
Index = index;
Task = task;
}
}
/// <summary>
/// 任务变更类型枚举
/// </summary>
public enum TaskChangeType
{
/// <summary>添加任务</summary>
Added,
/// <summary>更新任务</summary>
Updated,
/// <summary>移除任务</summary>
Removed,
/// <summary>替换全部任务</summary>
Replaced
}
/// <summary>
/// 循环点配置类
/// <para>用于定义循环点的规则和属性</para>
/// </summary>
public class LoopPoint
{
/// <summary>唯一标识</summary>
public string Id { get; set; } = Guid.NewGuid().ToString("N");
/// <summary>名称</summary>
public string Name { get; set; } = string.Empty;
/// <summary>自定义标签集合</summary>
public Dictionary<string, object> Tags { get; } = new Dictionary<string, object>();
/// <summary>进入规则</summary>
public IEnterRule EnterRule { get; set; }
/// <summary>退出规则</summary>
public IExitRule ExitRule { get; set; }
/// <summary>汇合规则</summary>
public IJoinRule JoinRule { get; set; }
/// <summary>分流规则</summary>
public IBranchRule BranchRule { get; set; }
}
/// <summary>
/// JSON 文件任务策略类
/// <para>从 tasklist.json 读取任务配置,支持文件变更自动刷新</para>
/// </summary>
public class JsonFileTaskStrategy : ITaskStrategy, IDisposable
{
/// <summary>JSON 文件路径</summary>
public string JsonPath { get; set; }
/// <summary>任务缓存列表</summary>
private List<LoopTask> _cache = new List<LoopTask>();
/// <summary>文件系统监听器</summary>
private FileSystemWatcher _watcher;
/// <summary>配置变更事件</summary>
public event EventHandler Changed;
/// <summary>
/// 创建 JSON 文件任务策略
/// </summary>
/// <param name="jsonPath">JSON 文件路径(null 时使用默认路径 tasklist.json</param>
public JsonFileTaskStrategy(string jsonPath = null)
{
JsonPath = string.IsNullOrWhiteSpace(jsonPath)
? Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "tasklist.json")
: jsonPath;
EnsureWatcher();
Refresh();
}
/// <summary>
/// 获取任务列表
/// </summary>
/// <returns>任务列表副本</returns>
public IEnumerable<LoopTask> GetTasks()
{
return _cache.ToArray();
}
/// <summary>
/// 刷新任务列表(从文件重新读取)
/// </summary>
public void Refresh()
{
try
{
if (!File.Exists(JsonPath))
{
_cache = new List<LoopTask>();
return;
}
string json = File.ReadAllText(JsonPath);
_cache = JsonConvert.DeserializeObject<List<LoopTask>>(json) ?? new List<LoopTask>();
}
catch
{
_cache = new List<LoopTask>();
}
}
/// <summary>
/// 确保文件监听器已启动
/// </summary>
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 路径查找辅助方法
/// <summary>
/// 查找从起点到终点之间的所有站点(基于轨道连接的广度优先搜索)
/// </summary>
/// <param name="startSiteId">起点站点ID</param>
/// <param name="endSiteId">终点站点ID</param>
/// <returns>从起点到终点的站点ID列表(包含起点和终点),未找到路径返回空列表</returns>
/// <example>
/// <code>
/// // 查找站点100到站点200之间的所有站点
/// var sites = GetSitesBetween(100, 200);
/// // 返回: [100, 150, 180, 200]
/// </code>
/// </example>
protected List<int> GetSitesBetween(int startSiteId, int endSiteId)
{
if (startSiteId == endSiteId)
return new List<int> { startSiteId };
try
{
// 构建站点邻接表
var adjacency = BuildSiteAdjacency();
// 广度优先搜索
var visited = new HashSet<int>();
var queue = new Queue<List<int>>();
queue.Enqueue(new List<int> { 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<int>(currentPath) { neighbor };
queue.Enqueue(newPath);
}
}
// 未找到路径
return new List<int>();
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"[AbstractLoopMission] GetSitesBetween({startSiteId}, {endSiteId}) error: {ex.Message}");
return new List<int>();
}
}
/// <summary>
/// 查找从起点到终点之间的所有站点对象
/// </summary>
/// <param name="startSiteId">起点站点ID</param>
/// <param name="endSiteId">终点站点ID</param>
/// <returns>从起点到终点的站点对象列表</returns>
protected List<Site> GetSiteObjectsBetween(int startSiteId, int endSiteId)
{
var siteIds = GetSitesBetween(startSiteId, endSiteId);
var result = new List<Site>();
foreach (var siteId in siteIds)
{
var site = SimpleLib.GetSite(siteId);
if (site != null)
result.Add(site);
}
return result;
}
/// <summary>
/// 构建站点邻接表(基于轨道连接,考虑轨道方向)
/// </summary>
/// <returns>邻接表:站点ID -> 可达的相邻站点ID列表</returns>
private Dictionary<int, HashSet<int>> BuildSiteAdjacency()
{
var adjacency = new Dictionary<int, HashSet<int>>();
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;
}
/// <summary>
/// 向邻接表添加边
/// </summary>
private void AddAdjacency(Dictionary<int, HashSet<int>> adjacency, int from, int to)
{
if (!adjacency.ContainsKey(from))
adjacency[from] = new HashSet<int>();
adjacency[from].Add(to);
}
/// <summary>
/// 检查路径上是否有其他车辆(不包含起点和终点)
/// </summary>
/// <param name="startSiteId">起点站点ID</param>
/// <param name="endSiteId">终点站点ID</param>
/// <param name="excludeCar">排除的车辆(通常是当前车辆)</param>
/// <returns>true=路径上有车辆,false=路径畅通</returns>
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<Car>())
{
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;
}
/// <summary>
/// 获取路径上的所有车辆
/// </summary>
/// <param name="startSiteId">起点站点ID</param>
/// <param name="endSiteId">终点站点ID</param>
/// <param name="includeEndpoints">是否包含起点和终点的车辆</param>
/// <returns>路径上的车辆列表</returns>
protected List<Car> 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<Car>();
foreach (var car in SimpleLib.GetAllCars().OfType<Car>())
{
if (car?.status?.holdingLocks == null)
continue;
foreach (var siteId in pathSites)
{
if (car.status.holdingLocks.Contains(siteId))
{
carsOnPath.Add(car);
break;
}
}
}
return carsOnPath;
}
/// <summary>
/// 计算两站点之间的路径长度(经过的站点数)
/// </summary>
/// <param name="startSiteId">起点站点ID</param>
/// <param name="endSiteId">终点站点ID</param>
/// <returns>路径长度(站点数),未找到路径返回 -1</returns>
protected int GetPathLength(int startSiteId, int endSiteId)
{
var path = GetSitesBetween(startSiteId, endSiteId);
return path.Count > 0 ? path.Count : -1;
}
#endregion
#region 任务策略匹配
/// <summary>
/// 路径缓存(避免重复计算)
/// </summary>
[JsonIgnore]
private readonly Dictionary<(int, int), List<int>> _pathCache = new Dictionary<(int, int), List<int>>();
/// <summary>
/// 路径缓存锁
/// </summary>
[JsonIgnore]
private readonly object _pathCacheLock = new object();
/// <summary>
/// 根据车辆当前站点匹配最优任务策略
/// <para>匹配逻辑:</para>
/// <list type="number">
/// <item>遍历所有任务策略,获取每个策略从当前点到目标点的完整路径</item>
/// <item>检查车辆当前站点是否在该路径上</item>
/// <item>返回包含当前站点且优先级最高的策略</item>
/// </list>
/// </summary>
/// <param name="car">车辆</param>
/// <returns>最优匹配结果,无匹配返回 null</returns>
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);
}
/// <summary>
/// 根据站点ID匹配最优任务策略
/// <para>遍历所有任务,找出包含该站点的路径,返回优先级最高的任务</para>
/// </summary>
/// <param name="siteId">当前站点ID</param>
/// <returns>最优匹配结果,无匹配返回 null</returns>
protected TaskPathMatchResult FindBestTaskForSite(int siteId)
{
List<LoopTask> snapshot;
lock (_tasksLock)
{
snapshot = Tasks.Where(T=>T.IsViaPoint).ToList();
}
var matchResults = new List<TaskPathMatchResult>();
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;
}
/// <summary>
/// 根据站点ID查找所有匹配的任务策略
/// <para>返回所有包含该站点的任务,按优先级排序</para>
/// </summary>
/// <param name="siteId">站点ID</param>
/// <returns>匹配结果列表(按优先级降序)</returns>
protected List<TaskPathMatchResult> FindAllTasksForSite(int siteId)
{
List<LoopTask> snapshot;
lock (_tasksLock)
{
snapshot = Tasks.ToList();
}
var matchResults = new List<TaskPathMatchResult>();
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();
}
/// <summary>
/// 获取车辆应该前往的下一个目标站点
/// <para>根据最优匹配策略返回目标站点ID</para>
/// </summary>
/// <param name="car">车辆</param>
/// <returns>目标站点ID,无匹配返回 -1</returns>
protected int GetRecommendedTargetForCar(Car car)
{
var match = FindBestTaskForCar(car);
return match?.Task?.TargetStationId ?? -1;
}
/// <summary>
/// 获取车辆的下一个中间站点
/// <para>返回当前位置的下一个路径节点</para>
/// </summary>
/// <param name="car">车辆</param>
/// <returns>下一站点ID,无匹配返回 -1</returns>
protected int GetNextSiteForCar(Car car)
{
var match = FindBestTaskForCar(car);
if (match?.RemainingPath == null || match.RemainingPath.Count < 2)
return -1;
return match.RemainingPath[1];
}
/// <summary>
/// 获取缓存的路径(避免重复计算)
/// </summary>
private List<int> GetSitesBetweenCached(int startSiteId, int endSiteId)
{
var key = (startSiteId, endSiteId);
lock (_pathCacheLock)
{
if (_pathCache.TryGetValue(key, out var cached))
return new List<int>(cached);
var path = GetSitesBetween(startSiteId, endSiteId);
_pathCache[key] = path;
return new List<int>(path);
}
}
/// <summary>
/// 清除路径缓存
/// <para>当任务策略变更或地图变更时调用</para>
/// </summary>
protected void ClearPathCache()
{
lock (_pathCacheLock)
{
_pathCache.Clear();
}
}
/// <summary>
/// 根据任务ID查找任务
/// </summary>
/// <param name="taskId">任务ID</param>
/// <returns>任务对象,未找到返回 null</returns>
protected LoopTask FindTaskById(int taskId)
{
lock (_tasksLock)
{
return Tasks.FirstOrDefault(t => t.Id == taskId);
}
}
/// <summary>
/// 判断车辆是否在指定任务的路径上
/// </summary>
/// <param name="car">车辆</param>
/// <param name="task">任务</param>
/// <returns>是否在路径上</returns>
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 任务匹配结果类型
/// <summary>
/// 任务路径匹配结果
/// </summary>
public class TaskPathMatchResult
{
/// <summary>匹配的任务</summary>
public LoopTask Task { get; set; }
/// <summary>车辆当前站点ID</summary>
public int CurrentSiteId { get; set; }
/// <summary>完整路径(从任务起点到终点)</summary>
public List<int> FullPath { get; set; } = new List<int>();
/// <summary>当前站点在路径中的索引</summary>
public int IndexInPath { get; set; }
/// <summary>剩余路径(从当前站点到终点)</summary>
public List<int> RemainingPath { get; set; } = new List<int>();
/// <summary>到目标的距离(剩余站点数)</summary>
public int DistanceToTarget { get; set; }
/// <summary>是否在任务起点</summary>
public bool IsAtStartPoint { get; set; }
/// <summary>是否在任务终点</summary>
public bool IsAtEndPoint { get; set; }
/// <summary>是否在路径中间</summary>
public bool IsOnMiddlePath { get; set; }
/// <summary>任务ID</summary>
public int TaskId => Task?.Id ?? 0;
/// <summary>任务优先级</summary>
public int TaskPriority => Task?.Priority ?? 0;
/// <summary>目标站点ID</summary>
public int TargetSiteId => Task?.TargetStationId ?? -1;
/// <summary>下一个站点ID(当前站点的下一跳)</summary>
public int NextSiteId => RemainingPath.Count > 1 ? RemainingPath[1] : -1;
/// <summary>路径进度百分比</summary>
public double ProgressPercent => FullPath.Count > 1
? (double)IndexInPath / (FullPath.Count - 1) * 100
: 0;
/// <summary>
/// 获取匹配信息描述
/// </summary>
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 void Stop()
{
StopAll();
}
[MethodMember(Name = "一键上线所有AGV", Description = "将所有已初始化的正常 AGV 标记为在线")]
public void OnlineAllCars()
{
CycleUiHelper.ConfirmThen("确认将所有已初始化的正常 AGV 一键上线?", () => Task.Run(OnlineAllCarsCore));
}
private void OnlineAllCarsCore()
{
int count = 0;
foreach (var car in SimpleLib.GetAllCars().OfType<Car>())
{
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 的环线任务分配")]
public void StopAllCarTasks()
{
CycleUiHelper.ConfirmThen("确认停止环线调度并结束所有 AGV 的环线任务?", () => Task.Run(StopAllCarTasksCore));
}
private void StopAllCarTasksCore()
{
int count = 0;
foreach (var car in SimpleLib.GetAllCars().OfType<Car>())
{
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);
}
}
}