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

413 lines
16 KiB
Markdown
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.
# AbstractLoopMission 抽象环线任务基类
## 概述
`AbstractLoopMission` 是环线任务的抽象基类,提供了完整的循环任务调度框架。它支持多种启动类型、任务类别、流量控制和优先级调度,是所有具体环线任务实现的基础。
## 核心特性
| 特性 | 说明 |
|------|------|
| 多启动类型 | AutoLoop(自动循环)、Api、Plc、ButtonBox、Charge |
| 多任务类别 | Loop(普通循环)、BranchPoint(分流点)、JoinPoint(汇合点) |
| 流量控制 | 限制目标站点的最大车辆数 |
| 优先级调度 | 高优先级任务优先处理 |
| 配置热更新 | tasklist.json 文件变更自动刷新 |
| 路径缓存 | 避免重复计算路径,提升性能 |
| 条件触发 | 子类可重写事件回调,自定义触发条件 |
## 类图
![alt text](image.png)
## 快速开始
### 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();
}
}
#### 示例2PLC 触发 - 检查 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);
## 异常处理
### 脚本异常恢复
当车辆脚本状态为 `Error``Bad` 时,自动执行恢复流程:
1. 标记站点不可用
2. 禁止车辆调度
3. 重置车辆状态
4. 清理车辆标签
5. 执行车辆重置