Files
StandardSence/LooMission.md
T
2026-06-14 11:19:15 +08:00

16 KiB

AbstractLoopMission 抽象环线任务基类

概述

AbstractLoopMission 是环线任务的抽象基类,提供了完整的循环任务调度框架。它支持多种启动类型、任务类别、流量控制和优先级调度,是所有具体环线任务实现的基础。

核心特性

| 特性 | 说明 |

|------|------| | 多启动类型 | AutoLoop(自动循环)、Api、Plc、ButtonBox、Charge | | 多任务类别 | Loop(普通循环)、BranchPoint(分流点)、JoinPoint(汇合点) | | 流量控制 | 限制目标站点的最大车辆数 | | 优先级调度 | 高优先级任务优先处理 | | 配置热更新 | tasklist.json 文件变更自动刷新 | | 路径缓存 | 避免重复计算路径,提升性能 | | 条件触发 | 子类可重写事件回调,自定义触发条件 |

类图

alt text

快速开始

1. 配置任务列表

在 任务进程方法显示界面中配置任务或者在tasklist.json 中配置任务 [ { "Id": 1, "Name": "主线循环", "CurrentStationId": 100, "TargetStationId": 200, "StartType": "AutoLoop", "Kind": "Loop", "Priority": 10, "TrafficControl": 2, "IsViaPoint": true }, { "Id": 2, "Name": "分流点A", "CurrentStationId": 150, "TargetStationId": 201, "StartType": "AutoLoop", "Kind": "BranchPoint", "Priority": 8, "TrafficControl": 1 }, { "Id": 3, "Name": "汇合点B", "CurrentStationId": 180, "TargetStationId": 300, "StartType": "Plc", "Kind": "JoinPoint", "Priority": 5, "TrafficControl": 1 } ]

2. 创建子类

public class MyLoopMission : AbstractLoopMission { protected override ExternalTriggerResult OnApiTrigger(int currentSiteId, LoopTask task, Car car) { // 业务逻辑判断 if (ShouldProcessTask(task, car)) { // 方式1:使用子类指定的目标站点 return ExternalTriggerResult.UseTarget(200); // 方式2:使用配置文件中的目标站点 // return ExternalTriggerResult.UseConfigTarget(); }
// 业务失败,不分配任务 return ExternalTriggerResult.Fail(); }

protected override ExternalTriggerResult OnPlcTrigger(int currentSiteId, LoopTask task, Car car) { // PLC 信号触发逻辑 if (CheckPlcSignal(currentSiteId)) { return ExternalTriggerResult.UseConfigTarget(); } return ExternalTriggerResult.Fail(); } }

任务类别说明

Loop(普通循环)

车辆从当前站点移动到目标站点的简单任务。 站点A ──────────────► 站点B

BranchPoint(分流点)

一个站点可以分流到多个目标站点,按优先级和流量控制选择目标。 ┌──► 目标站点1 (优先级高) 分流点 ────┼──► 目标站点2 └──► 目标站点3 (优先级低)

JoinPoint(汇合点)

多个站点汇合到同一个目标站点,按优先级决定放行顺序。 来源站点1 ──┐ 来源站点2 ──┼──► 汇合点 来源站点3 ──┘

流量控制

通过 TrafficControl 属性限制目标站点的最大车辆数: // 检查流量控制 if (!CheckTrafficControl(targetSiteId, task.TrafficControl)) { // 目标站点流量已满,等待 continue; }

  • TrafficControl = 0:不限制
  • TrafficControl = 1:目标站点最多 1 辆车
  • TrafficControl = N:目标站点最多 N 辆车

统计范围包括:

  1. 已在目标站点的车辆(holdingLocks 包含该站点)
  2. 正在前往目标站点的车辆(pendingLocks 最后一个为该站点)

3. 启动任务

var mission = new MyLoopMission(); // 启动所有线程(策略同步 + 业务逻辑) mission.StartAll(); // 或者分别启动 // mission.StartLoop(); // 启动策略同步线程 // mission.StartLogicLoop(); // 启动业务逻辑线程 // 停止任务 // mission.StopAll(); // 释放资源 // mission.Dispose();

启动类型说明

| 类型 | 枚举值 | 说明 | 子类接口 |

|------|--------|------|----------| | AutoLoop | TaskStartType.AutoLoop | 自动循环,车辆到站自动触发 | 无需重写 | | Api | TaskStartType.Api | API 外部调用触发 | OnApiTrigger() | | Plc | TaskStartType.Plc | PLC 信号触发 | OnPlcTrigger() | | ButtonBox | TaskStartType.ButtonBox | 按钮盒触发 | OnButtonTrigger() | | Charge | TaskStartType.Charge | 充电条件触发 | OnChargeTrigger() |

子类可重写事件回调

概述

AbstractLoopMission 提供了四个可重写的事件回调方法,子类可以通过重写这些方法实现自定义的触发条件逻辑。当车辆到达配置的当前站点时,系统会根据任务的 StartType 调用对应的回调方法。

回调方法签名

方法 触发条件 默认行为
OnApiTrigger(int currentSiteId, LoopTask task, Car car) StartType = Api 返回 Fail()
OnPlcTrigger(int currentSiteId, LoopTask task, Car car) StartType = Plc 返回 Fail()
OnButtonTrigger(int currentSiteId, LoopTask task, Car car) StartType = ButtonBox 返回 Fail()
OnChargeTrigger(int currentSiteId, LoopTask task, Car car) StartType = Charge 返回 Fail()

回调参数说明

参数 类型 说明
currentSiteId int 车辆当前所在站点ID
task LoopTask 匹配到的任务配置
car Car 到达站点的车辆对象

返回值说明

回调方法必须返回 ExternalTriggerResult 对象:

返回方式 说明 使用场景
ExternalTriggerResult.UseTarget(siteId) 成功,使用子类指定的目标站点 需要动态计算目标站点时
ExternalTriggerResult.UseConfigTarget() 成功,使用配置文件中的目标站点 条件满足,使用预设目标时
ExternalTriggerResult.Fail() 失败,不分配任务 条件不满足,等待下次检查时

使用示例

示例1:API 触发 - 检查外部系统状态

public class ApiLoopMission : AbstractLoopMission { protected override ExternalTriggerResult OnApiTrigger(int currentSiteId, LoopTask task, Car car) { // 检查外部 API 是否允许发车 var apiResult = ExternalApiService.CheckCanDispatch(currentSiteId, car.id); if (apiResult.Success) { // API 返回指定目标 if (apiResult.TargetSiteId.HasValue) { return ExternalTriggerResult.UseTarget(apiResult.TargetSiteId.Value); } // 使用配置目标 return ExternalTriggerResult.UseConfigTarget(); }
// 条件不满足,等待 return ExternalTriggerResult.Fail(); } }

示例2:PLC 触发 - 检查 PLC 信号状态

public class PlcLoopMission : AbstractLoopMission { protected override ExternalTriggerResult OnPlcTrigger(int currentSiteId, LoopTask task, Car car) { // 读取 PLC 信号 bool plcSignal = PlcManager.ReadBool($"Station{currentSiteId}.AllowDispatch"); if (plcSignal) { // 根据 PLC 数据决定目标 int plcTarget = PlcManager.ReadInt($"Station{currentSiteId}.TargetStation"); if (plcTarget > 0) { return ExternalTriggerResult.UseTarget(plcTarget); } return ExternalTriggerResult.UseConfigTarget(); } return ExternalTriggerResult.Fail(); } }

示例3:按钮盒触发 - 检查按钮状态

public class ButtonBoxLoopMission : AbstractLoopMission { protected override ExternalTriggerResult OnButtonTrigger(int currentSiteId, LoopTask task, Car car) { // 检查按钮盒是否按下 var buttonBox = ButtonBoxManager.GetByStation(currentSiteId); if (buttonBox != null && buttonBox.IsPressed) { // 重置按钮状态 buttonBox.Reset(); // 根据按钮类型选择目标 switch (buttonBox.PressedButton) { case ButtonType.Green: return ExternalTriggerResult.UseTarget(task.TargetStationId); case ButtonType.Yellow: return ExternalTriggerResult.UseTarget(GetAlternativeTarget(currentSiteId)); default: return ExternalTriggerResult.UseConfigTarget(); } }

return ExternalTriggerResult.Fail();

} }

示例4:充电触发 - 检查电量条件

public class ChargeLoopMission : AbstractLoopMission { private const int LOW_BATTERY_THRESHOLD = 20; protected override ExternalTriggerResult OnChargeTrigger(int currentSiteId, LoopTask task, Car car) { // 获取车辆电量 int batteryLevel = car.GetBatteryLevel(); // 检查是否需要充电 if (batteryLevel <= LOW_BATTERY_THRESHOLD) { // 查找最近的空闲充电站 int chargeStation = FindNearestAvailableChargeStation(currentSiteId); if (chargeStation > 0) { Diagnosis.Post($"车辆 {car.name} 电量 {batteryLevel}%,前往充电站 {chargeStation}", "Charge", true); return ExternalTriggerResult.UseTarget(chargeStation); } } // 电量充足或无可用充电站,不触发充电任务 return ExternalTriggerResult.Fail(); }

private int FindNearestAvailableChargeStation(int currentSiteId) { // 实现查找最近充电站逻辑 return ChargeStationManager.FindNearest(currentSiteId); } }

示例5:组合条件触发

public class ComplexLoopMission : AbstractLoopMission { protected override ExternalTriggerResult OnApiTrigger(int currentSiteId, LoopTask task, Car car) { // 组合多个条件判断 // 条件1:检查车辆是否携带货物 bool hasLoad = car.tags.ContainsKey("hasLoad") && car.tags["hasLoad"] == "true"; // 条件2:检查目标站点是否可用 var targetSite = SimpleLib.GetSite(task.TargetStationId); bool targetAvailable = targetSite != null && !targetSite.IsDisabled(); // 条件3:检查时间窗口 bool inTimeWindow = DateTime.Now.Hour >= 8 && DateTime.Now.Hour <= 20; // 条件4:检查优先级车辆 bool isPriorityCar = car.tags.ContainsKey("priority");

// 组合判断
if (hasLoad && targetAvailable && (inTimeWindow || isPriorityCar))
{
    return ExternalTriggerResult.UseConfigTarget();
}

// 记录不满足条件的原因
if (!hasLoad) Diagnosis.Post($"车辆 {car.name} 未携带货物", "ApiTrigger", true);
if (!targetAvailable) Diagnosis.Post($"目标站点 {task.TargetStationId} 不可用", "ApiTrigger", true);
if (!inTimeWindow && !isPriorityCar) Diagnosis.Post($"当前不在工作时间窗口", "ApiTrigger", true);

return ExternalTriggerResult.Fail();

} }

回调执行流程

车辆到达站点 │ ▼ 匹配任务配置 (CurrentStationId) │ ▼ 检查车辆可用性 (Commons.SelectCar) │ ▼ 根据 StartType 调用对应回调 │ ├ ─ AutoLoop ──► 自动处理,无需回调 ├─ Api ────────► OnApiTrigger() ├─ Plc ────────► OnPlcTrigger() ├─ ButtonBox ──► OnButtonTrigger() └─ Charge ─────► OnChargeTrigger() │ ▼ 检查返回结果 │ ├─ Fail() ──────────► 跳过,等待下次检查 │ └─ Success ─────────► 确定目标站点 │ ├─ CustomTargetSiteId 有值 ──► 使用子类指定目标 └─ CustomTargetSiteId 为空 ──► 使用配置文件目标 │ ▼ 流量控制检查 │ ├─ 通过 ──► AssignCarToTarget() └─ 不通过 ──► 等待

汇合点回调执行流程

按目标站点分组 │ ▼ 遍历每个汇合点组 │ ▼ 组内任务按优先级降序排列 │ ▼ 遍历排序后的任务 │ ├─► 流量控制检查 ──► 不通过 ──► 跳过 │ ├─► 查找车辆 ──► 未找到 ──► 跳过 │ ├─► 检查车辆可用 ──► 不可用 ──► 跳过 │ ├─► 调用子类接口 ──► 返回失败 ──► 跳过 │ ├─► 确定最终目标(子类指定 > 配置) │ ├─► 子类指定不同目标时再次检查流量 │ └─► 分配目标站点

注意事项

外部触发结果

ExternalTriggerResult 用于子类返回触发处理结果:

方法 说明
UseTarget(siteId) 使用子类指定的目标站点
UseConfigTarget() 使用配置文件中的目标站点
Fail() 业务失败,不分配任务

protected override ExternalTriggerResult OnApiTrigger(int currentSiteId, LoopTask task, Car car) { // 场景1:动态计算目标 int dynamicTarget = CalculateTarget(car); return ExternalTriggerResult.UseTarget(dynamicTarget); // 场景2:使用配置目标 return ExternalTriggerResult.UseConfigTarget();

// 场景3:条件不满足 return ExternalTriggerResult.Fail(); }

  1. 默认返回 Fail:所有回调方法默认返回 Fail(),子类必须重写才能启用对应的触发功能。

  2. 线程安全:回调方法在业务逻辑线程中执行,访问共享资源时需注意线程安全。

  3. 执行频率:业务逻辑线程每 500ms 执行一次,回调方法应避免长时间阻塞。

  4. 异常处理:回调方法中的异常会被捕获并记录,不会影响其他任务的处理。

  5. 流量控制:即使回调返回成功,仍会进行流量控制检查,可能因流量已满而等待。

路径查找与任务匹配

路径查找

基于轨道连接的广度优先搜索,支持轨道方向: // 获取两站点之间的路径 var path = GetSitesBetween(100, 200); // 返回: [100, 150, 180, 200] // 检查路径上是否有车辆 bool hasCar = HasCarOnPath(100, 200, excludeCar); // 获取路径长度 int length = GetPathLength(100, 200);

任务策略匹配

根据车辆当前位置匹配最优任务: // 查找车辆的最优任务 var match = FindBestTaskForCar(car); if (match != null) { Console.WriteLine($"任务ID: {match.TaskId}"); Console.WriteLine($"目标站点: {match.TargetSiteId}"); Console.WriteLine($"下一站点: {match.NextSiteId}"); Console.WriteLine($"剩余距离: {match.DistanceToTarget}"); Console.WriteLine($"进度: {match.ProgressPercent:F1}%"); }

匹配优先级:

  1. 起点匹配优先
  2. 任务优先级高的优先
  3. 距离目标近的优先

注意:如果站点是路径的终点,则不匹配该任务。

TaskListChanged 事件

任务列表变更时触发: mission.TaskListChanged += (sender, e) => { switch (e.ChangeType) { case TaskChangeType.Added: Console.WriteLine($"添加任务: 索引={e.Index}"); break; case TaskChangeType.Updated: Console.WriteLine($"更新任务: 索引={e.Index}"); break; case TaskChangeType.Removed: Console.WriteLine($"移除任务: 索引={e.Index}"); break; case TaskChangeType.Replaced: Console.WriteLine("任务列表已替换"); break; } };

线程模型

线程 名称 间隔 职责
策略同步线程 AbstractLoopMission_Strategy 1000ms 同步配置文件,更新任务列表
业务逻辑线程 AbstractLoopMission_Logic 500ms 处理任务调度,分配车辆目标

// 分别控制线程 mission.StartLoop(); // 启动策略同步 mission.StartLogicLoop(); // 启动业务逻辑 mission.StopLoop(); // 停止策略同步 mission.StopLogicLoop(); // 停止业务逻辑

常量配置

| 常量 | 值 | 说明 |

|------|------|------|

| STRATEGY_SYNC_INTERVAL_MS | 1000 | 策略同步间隔(毫秒) | | LOGIC_LOOP_INTERVAL_MS | 500 | 业务逻辑间隔(毫秒) | | FILE_CHANGE_DEBOUNCE_MS | 50 | 文件变更防抖延迟(毫秒) | | ERROR_RECOVERY_DELAY_MS | 3000 | 异常恢复等待时间(毫秒) | | SCRIPT_ERROR_TRIGGER_DELAY_MS | 3000 | 脚本异常检测延迟(毫秒) |

辅助方法

车辆查找

// 查找到达指定站点的车辆 Car car = FindCarArrivedAtSite(siteId); // 获取在站或前往站点的车辆 var cars = GetCarsAtOrHeadingToSite(siteId); // 统计车辆数量 int count = CountCarsAtOrHeadingToSite(siteId); // 检查车辆是否空闲 bool idle = IsCarIdle(car);

车辆分配

// 分配目标站点 AssignCarToTarget(car, targetSiteId); // 导航到目标站点 await GoSite(car, targetSite, action: "/", reverse: false);

异常处理

脚本异常恢复

当车辆脚本状态为 ErrorBad 时,自动执行恢复流程:

  1. 标记站点不可用
  2. 禁止车辆调度
  3. 重置车辆状态
  4. 清理车辆标签
  5. 执行车辆重置