Files

2181 lines
81 KiB
C#
Raw Permalink Normal View History

2026-06-14 11:19:15 +08:00
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 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>
2026-08-14 09:49:23 +08:00
protected virtual Car FindCarArrivedAtSite(int siteId)
2026-06-14 11:19:15 +08:00
{
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);
2026-08-14 09:49:23 +08:00
// 编译并执行移动脚本(默认 Forecast)
2026-06-14 11:19:15 +08:00
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)
{
2026-07-17 13:36:01 +08:00
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
{
}
2026-06-14 11:19:15 +08:00
// 记录异常并等待后重试
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")
2026-06-14 11:19:15 +08:00
: 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();
}
}
}