feat(javaTest): orchestrate cross-floor dispatch
This commit is contained in:
@@ -32,6 +32,42 @@ public final class ClearanceMovePlanner {
|
||||
Set<String> occupiedSites,
|
||||
Set<ResourceKey> reservedResources,
|
||||
GridMap map) {
|
||||
return planInternal(vehicleId, currentSite, ownerTaskId, ownerRemainingRoute,
|
||||
occupiedSites, reservedResources, map, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 在父业务路线尚未预约和激活之前,把占住其终点的空闲车移到路线外的安全支路。
|
||||
*
|
||||
* <p>与活动任务清障不同,此时父车辆尚未进入通道,所以清障路径可以暂时经过未来业务路线;但是
|
||||
* 清障终点仍必须位于该路线之外,避免只把阻塞车从终点挪到前置节点后再次挡住父任务。调用方必须
|
||||
* 等清障完成后才能提交父路线。
|
||||
*
|
||||
* @param vehicleId 占住业务终点、需要先让位的车辆。
|
||||
* @param currentSite 当前被占用的业务终点。
|
||||
* @param ownerTaskId 等待取得路线承诺的父任务。
|
||||
* @param ownerRoute 父任务准备提交但尚未预约的完整路线。
|
||||
* @param occupiedSites 全车队当前占点。
|
||||
* @param reservedResources 已由其他物理动作持有的资源。
|
||||
* @param map 当前项目路网。
|
||||
* @return 能把车辆停到父路线外时返回逐节点清障动作,否则返回空。
|
||||
*/
|
||||
public Optional<ClearanceMove> planBeforeRouteCommit(
|
||||
String vehicleId, String currentSite, String ownerTaskId,
|
||||
List<String> ownerRoute, Set<String> occupiedSites,
|
||||
Set<ResourceKey> reservedResources, GridMap map) {
|
||||
return planInternal(vehicleId, currentSite, ownerTaskId, ownerRoute,
|
||||
occupiedSites, reservedResources, map, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 统一选择清障终点;只有准入前清障可以穿过尚未生效的父路线。
|
||||
*/
|
||||
private Optional<ClearanceMove> planInternal(
|
||||
String vehicleId, String currentSite, String ownerTaskId,
|
||||
List<String> ownerRemainingRoute, Set<String> occupiedSites,
|
||||
Set<ResourceKey> reservedResources, GridMap map,
|
||||
boolean mayTraverseOwnerRoute) {
|
||||
if (vehicleId == null || currentSite == null || ownerTaskId == null
|
||||
|| ownerRemainingRoute == null || occupiedSites == null
|
||||
|| reservedResources == null || map == null) {
|
||||
@@ -47,6 +83,19 @@ public final class ClearanceMovePlanner {
|
||||
}
|
||||
excluded.remove(currentSite);
|
||||
|
||||
// 准入前父路线还没有形成物理承诺,只限制清障终点;真正的占车和节点资源仍是路径硬障碍。
|
||||
Set<String> pathObstacles = new LinkedHashSet<String>(excluded);
|
||||
if (mayTraverseOwnerRoute) {
|
||||
pathObstacles.removeAll(ownerRemainingRoute);
|
||||
pathObstacles.addAll(occupiedSites);
|
||||
for (ResourceKey resource : reservedResources) {
|
||||
if (resource.value().startsWith("NODE:")) {
|
||||
pathObstacles.add(resource.value().substring("NODE:".length()));
|
||||
}
|
||||
}
|
||||
pathObstacles.remove(currentSite);
|
||||
}
|
||||
|
||||
GridPathPlanner planner = new GridPathPlanner();
|
||||
List<Candidate> candidates = new ArrayList<Candidate>();
|
||||
// 枚举全图而不是只看邻居,使被阻塞车辆能找到最近的可达让路站点。
|
||||
@@ -54,7 +103,8 @@ public final class ClearanceMovePlanner {
|
||||
Collections.sort(siteIds);
|
||||
for (String target : siteIds) {
|
||||
if (target.equals(currentSite) || excluded.contains(target)) continue;
|
||||
List<String> route = planner.shortestPath(map, currentSite, target, excluded);
|
||||
List<String> route = planner.shortestPath(
|
||||
map, currentSite, target, pathObstacles);
|
||||
if (!route.isEmpty()) {
|
||||
candidates.add(new Candidate(target, route,
|
||||
map.getNeighbors(target).size() <= 1));
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -14,6 +14,7 @@ public final class HistoricalSimulationInput {
|
||||
private final GridMap gridMap;
|
||||
private final EffectiveTrafficMap trafficMap;
|
||||
private final List<VehicleSnapshot> initialVehicles;
|
||||
private final FloorTransferTopology floorTransferTopology;
|
||||
|
||||
/**
|
||||
* 创建 {@code HistoricalSimulationInput} 实例并初始化其内部状态。
|
||||
@@ -29,11 +30,36 @@ public final class HistoricalSimulationInput {
|
||||
List<HistoricalSimulationFailure> failedInputs,
|
||||
GridMap gridMap, EffectiveTrafficMap trafficMap,
|
||||
List<VehicleSnapshot> initialVehicles) {
|
||||
this(tasks, failedInputs, gridMap, trafficMap, initialVehicles,
|
||||
new FloorTransferTopology(Collections.<Integer, String>emptyMap()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建带项目换层端口目录的仿真输入。
|
||||
*
|
||||
* <p>换层端口与普通地图边分开保存。中心调度只能通过该目录生成换层作业,不能在
|
||||
* {@link GridMap} 中虚构跨层边;没有配置端口的测试输入继续只支持同层调度。
|
||||
* @param tasks 可按 create_date 进入调度队列的业务任务。
|
||||
* @param failedInputs 输入阶段已经关闭的非法历史记录。
|
||||
* @param gridMap 只包含同层物理邻接关系的完整地图。
|
||||
* @param trafficMap 节点、边和交通资源容量定义。
|
||||
* @param initialVehicles 本次仿真的初始车队快照。
|
||||
* @param floorTransferTopology 项目楼层到换层交接站点的独立配置。
|
||||
*/
|
||||
HistoricalSimulationInput(List<HistoricalSimulationTask> tasks,
|
||||
List<HistoricalSimulationFailure> failedInputs,
|
||||
GridMap gridMap, EffectiveTrafficMap trafficMap,
|
||||
List<VehicleSnapshot> initialVehicles,
|
||||
FloorTransferTopology floorTransferTopology) {
|
||||
this.tasks = immutable(tasks);
|
||||
this.failedInputs = immutable(failedInputs);
|
||||
this.gridMap = gridMap;
|
||||
this.trafficMap = trafficMap;
|
||||
this.initialVehicles = immutable(initialVehicles);
|
||||
if (floorTransferTopology == null) {
|
||||
throw new IllegalArgumentException("floor transfer topology is required");
|
||||
}
|
||||
this.floorTransferTopology = floorTransferTopology;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -72,6 +98,9 @@ public final class HistoricalSimulationInput {
|
||||
*/
|
||||
public List<VehicleSnapshot> getInitialVehicles() { return initialVehicles; }
|
||||
|
||||
/** 返回项目换层端口目录;该目录不向普通路径规划器暴露跨层边。 */
|
||||
public FloorTransferTopology getFloorTransferTopology() { return floorTransferTopology; }
|
||||
|
||||
/**
|
||||
* 执行“all simulation ids”所表示的处理流程。
|
||||
*
|
||||
|
||||
@@ -10,7 +10,9 @@ import com.ft546.wcs.shadow.history.HistoricalQueueRecord;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public final class HistoricalSimulationInputFactory {
|
||||
/**
|
||||
@@ -75,7 +77,24 @@ public final class HistoricalSimulationInputFactory {
|
||||
|
||||
List<VehicleSnapshot> vehicles = initialVehicles(compiled);
|
||||
return new HistoricalSimulationInput(tasks, failures, compiled.getGridMap(),
|
||||
compiled.getTrafficMap(), vehicles);
|
||||
compiled.getTrafficMap(), vehicles, floorTransferTopology(compiled));
|
||||
}
|
||||
|
||||
/**
|
||||
* 按 FT546 现有业务别名规则,把每层 {@code 07050<floor>} 登记为换层交接站点。
|
||||
*
|
||||
* <p>这里只建立项目设备端口配置,不调用 {@code GridMap.connect},因此路径规划器仍然看不到
|
||||
* 任何普通跨层边。某层没有该真实站点时保持未配置,让中心调度明确判定跨层不可用。
|
||||
*/
|
||||
private FloorTransferTopology floorTransferTopology(
|
||||
HistoricalGridMapCompiler.CompiledHistoricalMap compiled) {
|
||||
Map<Integer, String> ports = new LinkedHashMap<Integer, String>();
|
||||
for (int floor = 1; floor <= 3; floor++) {
|
||||
String siteId = "07050" + floor;
|
||||
HistoricalMapCell cell = compiled.getPassableSites().get(siteId);
|
||||
if (cell != null && cell.getZ() == floor) ports.put(floor, siteId);
|
||||
}
|
||||
return new FloorTransferTopology(ports);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -51,7 +51,8 @@ public final class PointToPointSimulationInputFactory {
|
||||
CREATED_AT.plusMinutes(1), sourceSite, targetSite, history, 0);
|
||||
return new HistoricalSimulationInput(
|
||||
Collections.singletonList(task), Collections.emptyList(),
|
||||
base.getGridMap(), base.getTrafficMap(), base.getInitialVehicles());
|
||||
base.getGridMap(), base.getTrafficMap(), base.getInitialVehicles(),
|
||||
base.getFloorTransferTopology());
|
||||
}
|
||||
|
||||
private String taskTypeCode(DispatchTask.TaskType taskType) {
|
||||
|
||||
@@ -26,6 +26,8 @@ public final class SimulationTaskResult {
|
||||
private final Map<String, Integer> retryReasons;
|
||||
private final List<RoutePlanningEvidence> planningEvidence;
|
||||
private final int rerouteCount;
|
||||
private final AssignmentMode assignmentMode;
|
||||
private final List<FloorTransferJob> floorTransferJobs;
|
||||
|
||||
/**
|
||||
* 创建 {@code SimulationTaskResult} 实例并初始化其内部状态。
|
||||
@@ -48,6 +50,8 @@ public final class SimulationTaskResult {
|
||||
* @param retryReasons 执行该处理所需的 retry reasons 输入。
|
||||
* @param planningEvidence 每个实际采用路径版本的接近段和服务段决策证据。
|
||||
* @param rerouteCount 运行中成功替换剩余路线的次数。
|
||||
* @param assignmentMode 中心采用的直接派车或先换层编排模式;未分配失败任务为 {@code null}。
|
||||
* @param floorTransferJobs 父任务实际创建过的幂等换层作业证据。
|
||||
*/
|
||||
SimulationTaskResult(String simulationTaskId, HistoricalQueueRecord record,
|
||||
SimulationTaskStatus status, String reason,
|
||||
@@ -58,7 +62,8 @@ public final class SimulationTaskResult {
|
||||
Map<String, Integer> holdReasons,
|
||||
Map<String, Integer> retryReasons,
|
||||
List<RoutePlanningEvidence> planningEvidence,
|
||||
int rerouteCount) {
|
||||
int rerouteCount, AssignmentMode assignmentMode,
|
||||
List<FloorTransferJob> floorTransferJobs) {
|
||||
this.simulationTaskId = simulationTaskId;
|
||||
this.record = record;
|
||||
this.status = status;
|
||||
@@ -76,6 +81,8 @@ public final class SimulationTaskResult {
|
||||
this.retryReasons = immutableMap(retryReasons);
|
||||
this.planningEvidence = immutable(planningEvidence);
|
||||
this.rerouteCount = rerouteCount;
|
||||
this.assignmentMode = assignmentMode;
|
||||
this.floorTransferJobs = immutable(floorTransferJobs);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -189,6 +196,15 @@ public final class SimulationTaskResult {
|
||||
|
||||
/** 返回运行中因连续交通 HOLD 成功切换路线的次数。 */
|
||||
public int getRerouteCount() { return rerouteCount; }
|
||||
|
||||
/** 返回中心最终采用的直接派车或先换层编排模式。 */
|
||||
public AssignmentMode getAssignmentMode() { return assignmentMode; }
|
||||
|
||||
/** 返回该父任务实际创建过的全部换层作业,供幂等和重派审计。 */
|
||||
public List<FloorTransferJob> getFloorTransferJobs() { return floorTransferJobs; }
|
||||
|
||||
/** 返回该父任务产生的换层业务效果数;重复提交不会增加该计数。 */
|
||||
public int getFloorTransferCount() { return floorTransferJobs.size(); }
|
||||
/**
|
||||
* 返回当前对象保存的“waiting millis”信息。
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user