fix: enforce deterministic dispatch boundaries

This commit is contained in:
zgw
2026-07-30 11:38:57 +08:00
parent ae338b7e35
commit 677fb540c2
8 changed files with 180 additions and 10 deletions

View File

@@ -3,6 +3,7 @@ package com.ft546.wcs.demo;
import com.ft546.wcs.demo.center.CycleResult;
import com.ft546.wcs.demo.center.LegacyDispatchCenter;
import com.ft546.wcs.demo.center.QueueAdmissionService;
import com.ft546.wcs.demo.center.VehicleAssignmentDecision;
import com.ft546.wcs.demo.center.VehicleAssignmentService;
import com.ft546.wcs.demo.model.DispatchTask;
import com.ft546.wcs.demo.model.Site;
@@ -14,6 +15,7 @@ import com.ft546.wcs.demo.traffic.GridPathPlanner;
import com.ft546.wcs.demo.traffic.TrafficController;
import java.io.PrintStream;
import java.util.Arrays;
public final class DemoApplication {
@@ -58,6 +60,7 @@ public final class DemoApplication {
out.println("FT546 WCS LEGACY DISPATCH AND TRAFFIC DEMO");
out.println("MODE LEGACY / IO DISABLED / IN-MEMORY ONLY");
printCrossFloorAssignmentCheck(out, map);
for (int cycle = 0; cycle < 6; cycle++) {
printCycle(out, center.runCycle());
@@ -87,6 +90,30 @@ public final class DemoApplication {
}
}
private static void printCrossFloorAssignmentCheck(PrintStream out, GridMap map) {
DispatchTask checkTask = new DispatchTask(
"CHECK-CROSS-FLOOR",
DispatchTask.TaskType.OUTBOUND,
"A",
"TARGET",
1,
1
);
VehicleAssignmentDecision decision = new VehicleAssignmentService().select(
checkTask,
Arrays.asList(
new VehicleSnapshot("1", 2, "F2-NEAR", 5, 1, 80, true, true, false),
new VehicleSnapshot("2", 1, "F1-FAR", 5, 1, 80, true, true, false)
),
map.getSites()
);
out.println(
"ASSIGN-CHECK crossFloorWinner="
+ decision.getVehicle().get().getVehicleId()
+ " reason=" + decision.getReason()
);
}
private static GridMap createMap() {
GridMap map = new GridMap();
map.addSite(new Site("A", 1, 0, 0, "START"));
@@ -95,6 +122,8 @@ public final class DemoApplication {
map.addSite(new Site("CACHE", 1, 1, 1, "CACHE"));
map.addSite(new Site("V3", 1, 0, 1, "V3-CHANNEL"));
map.addSite(new Site("ISOLATED", 1, 9, 9, "ISLAND"));
map.addSite(new Site("F2-NEAR", 2, 0, 0, "F2-CHECK"));
map.addSite(new Site("F1-FAR", 1, 9, 9, "F1-CHECK"));
map.connect("A", "B");
map.connect("B", "TARGET");
map.connect("B", "CACHE");

View File

@@ -16,13 +16,13 @@ import java.util.Optional;
public final class LegacyDispatchCenter {
private final InMemoryWcsState state;
private final QueueAdmissionService admission;
private final QueueAdmission admission;
private final TrafficController traffic;
private final SimulatedProtocolAdapter protocol;
public LegacyDispatchCenter(
InMemoryWcsState state,
QueueAdmissionService admission,
QueueAdmission admission,
TrafficController traffic,
SimulatedProtocolAdapter protocol
) {
@@ -42,8 +42,6 @@ public final class LegacyDispatchCenter {
int cycleNumber = state.nextCycle();
state.addEvent("CYCLE " + cycleNumber);
admission.createPendingQueue(state);
List<VehicleSnapshot> vehicles =
new ArrayList<VehicleSnapshot>(state.getVehicles());
Collections.sort(vehicles, new Comparator<VehicleSnapshot>() {
@@ -53,6 +51,8 @@ public final class LegacyDispatchCenter {
}
});
admission.createPendingQueue(state, vehicles);
List<String> processed = new ArrayList<String>();
for (VehicleSnapshot frozenVehicle : vehicles) {
processed.add(frozenVehicle.getVehicleId());

View File

@@ -0,0 +1,17 @@
package com.ft546.wcs.demo.center;
import com.ft546.wcs.demo.model.DispatchQueueEntry;
import com.ft546.wcs.demo.model.VehicleSnapshot;
import com.ft546.wcs.demo.repository.InMemoryWcsState;
import java.util.List;
import java.util.Optional;
public interface QueueAdmission {
Optional<DispatchQueueEntry> createPendingQueue(
InMemoryWcsState state,
List<VehicleSnapshot> frozenVehicles
);
int getInvocationCount();
}

View File

@@ -10,7 +10,7 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
public final class QueueAdmissionService {
public final class QueueAdmissionService implements QueueAdmission {
private final VehicleAssignmentService assignmentService;
private final GridMap map;
private int invocationCount;
@@ -26,9 +26,13 @@ public final class QueueAdmissionService {
this.map = map;
}
public Optional<DispatchQueueEntry> createPendingQueue(InMemoryWcsState state) {
if (state == null) {
throw new IllegalArgumentException("state must not be null");
@Override
public Optional<DispatchQueueEntry> createPendingQueue(
InMemoryWcsState state,
List<VehicleSnapshot> frozenVehicles
) {
if (state == null || frozenVehicles == null) {
throw new IllegalArgumentException("state and frozen vehicles are required");
}
invocationCount++;
if (state.getPendingTasks().isEmpty()) {
@@ -37,7 +41,7 @@ public final class QueueAdmissionService {
DispatchTask task = state.getPendingTasks().get(0);
List<VehicleSnapshot> available = new ArrayList<VehicleSnapshot>();
for (VehicleSnapshot vehicle : state.getVehicles()) {
for (VehicleSnapshot vehicle : frozenVehicles) {
if (!state.findQueueByVehicle(vehicle.getVehicleId()).isPresent()) {
available.add(vehicle);
}
@@ -65,6 +69,7 @@ public final class QueueAdmissionService {
return Optional.of(queue);
}
@Override
public int getInvocationCount() {
return invocationCount;
}

View File

@@ -28,6 +28,12 @@ public final class VehicleAssignmentService {
"unknown task source site: " + task.getSourceSite()
);
}
if (!sites.containsKey(task.getTargetSite())) {
return VehicleAssignmentDecision.rejected(
DecisionCode.REJECT_INVALID_SITE,
"unknown task target site: " + task.getTargetSite()
);
}
List<Candidate> candidates = new ArrayList<Candidate>();
boolean eligibleWithUnknownSite = false;