diff --git a/javaTest/src/main/java/com/ft546/wcs/shadow/history/simulation/CorridorReservationManager.java b/javaTest/src/main/java/com/ft546/wcs/shadow/history/simulation/CorridorReservationManager.java index e7a8be3..b55ca1f 100644 --- a/javaTest/src/main/java/com/ft546/wcs/shadow/history/simulation/CorridorReservationManager.java +++ b/javaTest/src/main/java/com/ft546/wcs/shadow/history/simulation/CorridorReservationManager.java @@ -56,6 +56,98 @@ public final class CorridorReservationManager { return ReservationDecision.granted(); } + /** + * 原子替换一个活动任务尚未通过的走廊方向预约。 + * + *

方法先把当前任务自己的旧预约视为可替换资源,对新路线做完整预检;任一新边与其他任务方向冲突时 + * 直接拒绝,旧预约保持原样。只有全部新边通过后才在同一次调用中提交移除和新增,因此重规划不会形成 + * “旧路线已释放、新路线尚未取得”的安全空窗。 + * + * @param taskId 正在重规划的任务 + * @param newRoute 从车辆当前位置开始的新剩余路线 + * @param effectivePriority 当前饥饿保护后的有效优先级 + * @param arrivalMillis 任务到达队列的仿真时间 + * @return 替换是否成功以及失败时优先持有冲突方向的任务 + */ + public ReservationDecision replaceRoute(String taskId, List newRoute, + int effectivePriority, long arrivalMillis) { + if (taskId == null || taskId.trim().isEmpty() + || newRoute == null || newRoute.isEmpty()) { + throw new IllegalArgumentException("task id and new route are required"); + } + + List requests = new ArrayList(); + for (int i = 1; i < newRoute.size(); i++) { + EdgeRequest request = request(newRoute.get(i - 1), newRoute.get(i)); + requests.add(request); + EdgeReservation existing = edges.get(request.edgeId); + if (existing == null || existing.direction == request.direction) continue; + + // 当前任务旧预约可以被翻转;其他任务仍持有旧方向时,新路线不能提交。 + Set otherHolders = new LinkedHashSet(existing.holders); + otherHolders.remove(taskId); + if (!otherHolders.isEmpty()) { + return ReservationDecision.denied(bestOwner(otherHolders), + "CORRIDOR_DIRECTION_OWNED"); + } + } + + // 预检全部通过后才开始提交。先移除当前任务,绝不触碰同向共享该边的其他任务。 + Set oldEdges = taskEdges.get(taskId); + if (oldEdges != null) { + for (String edgeId : new ArrayList(oldEdges)) { + EdgeReservation reservation = edges.get(edgeId); + if (reservation == null) continue; + reservation.holders.remove(taskId); + if (reservation.holders.isEmpty()) edges.remove(edgeId); + } + } + + Rank rank = new Rank(taskId, effectivePriority, arrivalMillis); + ranks.put(taskId, rank); + Set replacementEdges = new LinkedHashSet(); + for (EdgeRequest request : requests) { + EdgeReservation reservation = edges.get(request.edgeId); + if (reservation == null) { + reservation = new EdgeReservation(request.direction); + edges.put(request.edgeId, reservation); + } + reservation.holders.add(taskId); + replacementEdges.add(request.edgeId); + } + if (replacementEdges.isEmpty()) { + taskEdges.remove(taskId); + } else { + taskEdges.put(taskId, replacementEdges); + } + return ReservationDecision.granted(); + } + + /** + * 冻结当前全部走廊方向和持有任务,供路线质量评价和审计报告使用。 + * + * @return 按物理边规范键排序的不可变预约列表 + */ + public List snapshot() { + List edgeIds = new ArrayList(edges.keySet()); + Collections.sort(edgeIds); + List result = + new ArrayList(); + for (String edgeId : edgeIds) { + EdgeReservation reservation = edges.get(edgeId); + int separator = edgeId.indexOf('|'); + String left = edgeId.substring(0, separator); + String right = edgeId.substring(separator + 1); + List holders = new ArrayList(reservation.holders); + Collections.sort(holders); + result.add(new CorridorReservationSnapshot( + reservation.direction > 0 ? left : right, + reservation.direction > 0 ? right : left, + holders)); + } + return Collections.unmodifiableList(result); + } + /** * 在车辆通过一条边后立即释放该段走廊,允许后续同向或反向任务重新竞争资源。 * @param taskId 执行该处理所需的 task id 输入。 @@ -207,6 +299,29 @@ public final class CorridorReservationManager { public String getReason() { return reason; } } + /** 一条物理走廊当前的有向预约及全部合法同向持有者。 */ + public static final class CorridorReservationSnapshot { + private final String fromSite; + private final String toSite; + private final List taskIds; + + private CorridorReservationSnapshot(String fromSite, String toSite, + List taskIds) { + this.fromSite = fromSite; + this.toSite = toSite; + this.taskIds = Collections.unmodifiableList(new ArrayList(taskIds)); + } + + /** 返回预约方向的起点。 */ + public String getFromSite() { return fromSite; } + + /** 返回预约方向的终点。 */ + public String getToSite() { return toSite; } + + /** 返回共享该方向预约的任务编号,按编号稳定排序。 */ + public List getTaskIds() { return taskIds; } + } + private static final class EdgeReservation { private final int direction; private final Set holders = new LinkedHashSet(); diff --git a/javaTest/src/test/java/com/ft546/wcs/shadow/history/simulation/CorridorReservationManagerTest.java b/javaTest/src/test/java/com/ft546/wcs/shadow/history/simulation/CorridorReservationManagerTest.java index 0f11da9..6f3075f 100644 --- a/javaTest/src/test/java/com/ft546/wcs/shadow/history/simulation/CorridorReservationManagerTest.java +++ b/javaTest/src/test/java/com/ft546/wcs/shadow/history/simulation/CorridorReservationManagerTest.java @@ -3,6 +3,8 @@ package com.ft546.wcs.shadow.history.simulation; import org.junit.jupiter.api.Test; import java.util.Arrays; +import java.util.LinkedHashSet; +import java.util.Set; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -50,4 +52,53 @@ class CorridorReservationManagerTest { .isGranted()); assertEquals("FIRST", manager.ownerFor("A", "B")); } + + @Test + void rejectedReplacementKeepsEveryOldReservation() { + CorridorReservationManager manager = new CorridorReservationManager(); + assertTrue(manager.reserve("TASK-1", Arrays.asList("A", "B", "C"), 1, 10L) + .isGranted()); + assertTrue(manager.reserve("TASK-2", Arrays.asList("C", "D"), 2, 20L) + .isGranted()); + + CorridorReservationManager.ReservationDecision denied = manager.replaceRoute( + "TASK-1", Arrays.asList("A", "D", "C"), 1, 10L); + + assertFalse(denied.isGranted()); + assertEquals(new LinkedHashSet(Arrays.asList("A->B", "B->C")), + edgesFor(manager, "TASK-1")); + assertEquals("TASK-1", manager.ownerFor("A", "B")); + assertEquals("TASK-1", manager.ownerFor("B", "C")); + } + + @Test + void successfulReplacementCommitsNewRouteAndPreservesSharedOldHolder() { + CorridorReservationManager manager = new CorridorReservationManager(); + assertTrue(manager.reserve("TASK-1", Arrays.asList("A", "B", "C"), 1, 10L) + .isGranted()); + assertTrue(manager.reserve("TASK-2", Arrays.asList("A", "B", "C"), 2, 20L) + .isGranted()); + + CorridorReservationManager.ReservationDecision replaced = manager.replaceRoute( + "TASK-1", Arrays.asList("A", "D", "C"), 1, 10L); + + assertTrue(replaced.isGranted()); + assertEquals(new LinkedHashSet(Arrays.asList("A->D", "D->C")), + edgesFor(manager, "TASK-1")); + assertEquals(new LinkedHashSet(Arrays.asList("A->B", "B->C")), + edgesFor(manager, "TASK-2")); + assertEquals("TASK-2", manager.ownerFor("A", "B")); + assertEquals("TASK-1", manager.ownerFor("A", "D")); + } + + private Set edgesFor(CorridorReservationManager manager, String taskId) { + Set edges = new LinkedHashSet(); + for (CorridorReservationManager.CorridorReservationSnapshot reservation + : manager.snapshot()) { + if (reservation.getTaskIds().contains(taskId)) { + edges.add(reservation.getFromSite() + "->" + reservation.getToSite()); + } + } + return edges; + } }