fix(shadow): repair field routing and harden evidence collection
This commit is contained in:
@@ -14,6 +14,7 @@ import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
@@ -114,27 +115,9 @@ public final class DefaultVehicleAssignmentPolicy implements VehicleAssignmentPo
|
||||
List<String> resources = new ArrayList<String>(approach.getResourceKeys());
|
||||
resources.addAll(execution.getResourceKeys().subList(
|
||||
1, execution.getResourceKeys().size()));
|
||||
return new RoutePlan(nodes, resources,
|
||||
approach.getDistanceCost() + execution.getDistanceCost(),
|
||||
countTurns(nodes, topology), topology.version());
|
||||
}
|
||||
|
||||
private static int countTurns(List<String> nodes, EffectiveTopology topology) {
|
||||
int turns = 0;
|
||||
int previousX = 0;
|
||||
int previousY = 0;
|
||||
for (int i = 1; i < nodes.size(); i++) {
|
||||
TopologyNode from = topology.node(nodes.get(i - 1));
|
||||
TopologyNode to = topology.node(nodes.get(i));
|
||||
int directionX = Integer.compare(to.getX(), from.getX());
|
||||
int directionY = Integer.compare(to.getY(), from.getY());
|
||||
if (i > 1 && (previousX != directionX || previousY != directionY)) {
|
||||
turns++;
|
||||
}
|
||||
previousX = directionX;
|
||||
previousY = directionY;
|
||||
}
|
||||
return turns;
|
||||
// Returning over the approach is valid geometry, but reserves each resource once.
|
||||
return RoutePlan.fromTopology(nodes, new ArrayList<String>(new LinkedHashSet<String>(resources)),
|
||||
approach.getDistanceCost() + execution.getDistanceCost(), topology);
|
||||
}
|
||||
|
||||
private static final class ScoredVehicle {
|
||||
|
||||
@@ -163,8 +163,7 @@ public final class AStarRoutePlanner implements RoutePlanner {
|
||||
}
|
||||
|
||||
private RoutePlan toRoutePlan(EffectiveTopology topology) {
|
||||
return new RoutePlan(nodeIds, resourceKeys, distanceCost,
|
||||
turnCount, topology.version());
|
||||
return RoutePlan.fromTopology(nodeIds, resourceKeys, distanceCost, topology);
|
||||
}
|
||||
|
||||
private int getEstimatedTotalCost() {
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.ruoyi.wcs.domain.traffic;
|
||||
|
||||
import com.ruoyi.wcs.domain.topology.EffectiveTopology;
|
||||
import com.ruoyi.wcs.domain.topology.TopologyNode;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
@@ -10,9 +12,18 @@ public final class RoutePlan {
|
||||
private final int distanceCost;
|
||||
private final int turnCount;
|
||||
private final long topologyVersion;
|
||||
private final List<String> turnNodeIds;
|
||||
|
||||
public RoutePlan(List<String> nodeIds, List<String> resourceKeys,
|
||||
int distanceCost, int turnCount, long topologyVersion) {
|
||||
this(nodeIds, resourceKeys, distanceCost, turnCount, topologyVersion,
|
||||
nodeIds != null && nodeIds.size() <= 2 && turnCount == 0
|
||||
? Collections.<String>emptyList() : null);
|
||||
}
|
||||
|
||||
private RoutePlan(List<String> nodeIds, List<String> resourceKeys,
|
||||
int distanceCost, int turnCount, long topologyVersion,
|
||||
List<String> turnNodeIds) {
|
||||
this.nodeIds = immutable(nodeIds, "nodeIds");
|
||||
this.resourceKeys = immutable(resourceKeys, "resourceKeys");
|
||||
if (this.nodeIds.isEmpty()) {
|
||||
@@ -24,6 +35,34 @@ public final class RoutePlan {
|
||||
this.distanceCost = distanceCost;
|
||||
this.turnCount = turnCount;
|
||||
this.topologyVersion = topologyVersion;
|
||||
this.turnNodeIds = turnNodeIds == null ? null : immutable(turnNodeIds, "turnNodeIds");
|
||||
}
|
||||
|
||||
public static RoutePlan fromTopology(List<String> nodes, List<String> resources,
|
||||
int distanceCost, EffectiveTopology topology) {
|
||||
List<String> turns = new ArrayList<String>();
|
||||
int previousX = 0, previousY = 0;
|
||||
for (int i = 1; i < nodes.size(); i++) {
|
||||
TopologyNode from = topology.node(nodes.get(i - 1));
|
||||
TopologyNode to = topology.node(nodes.get(i));
|
||||
int directionX = Integer.compare(to.getX(), from.getX());
|
||||
int directionY = Integer.compare(to.getY(), from.getY());
|
||||
if (i > 1 && (previousX != directionX || previousY != directionY)) {
|
||||
turns.add(nodes.get(i - 1));
|
||||
}
|
||||
previousX = directionX;
|
||||
previousY = directionY;
|
||||
}
|
||||
return new RoutePlan(nodes, resources, distanceCost, turns.size(), topology.version(), turns);
|
||||
}
|
||||
|
||||
public boolean hasTurnGeometry() {
|
||||
return turnNodeIds != null;
|
||||
}
|
||||
|
||||
public List<String> getTurnNodeIds() {
|
||||
if (turnNodeIds == null) throw new IllegalStateException("route turn geometry is unavailable");
|
||||
return turnNodeIds;
|
||||
}
|
||||
|
||||
public List<String> getNodeIds() {
|
||||
|
||||
@@ -28,6 +28,21 @@ class DefaultVehicleAssignmentPolicyTest {
|
||||
private final VehicleAssignmentPolicy policy = new DefaultVehicleAssignmentPolicy(
|
||||
new AStarRoutePlanner(), DispatchPolicy.NONE);
|
||||
|
||||
@Test
|
||||
void returningOverApproachRouteDoesNotDuplicateReservationResources() {
|
||||
DispatchTask task = new DispatchTask("O1", "S1", "A", "B", 10, 60L, 30,
|
||||
Collections.<DeviceCapability>emptySet());
|
||||
VehicleAssignmentDecision assigned = policy.assign(task,
|
||||
Collections.singletonList(vehicle("V1", "C", 90, true,
|
||||
VehicleSnapshot.ConnectionState.ONLINE)), topology);
|
||||
assertEquals(Arrays.asList("C", "B", "A", "B"), assigned.getRoute().getNodeIds());
|
||||
assertEquals(5, assigned.getRoute().getResourceKeys().size());
|
||||
com.ruoyi.wcs.domain.traffic.ReservationRequest request =
|
||||
new com.ruoyi.wcs.domain.traffic.ReservationRequest("R1", "O1", "S1", "V1",
|
||||
assigned.getRoute().getResourceKeys());
|
||||
assertTrue(new com.ruoyi.wcs.domain.traffic.ResourceLedger(topology).reserve(request).isGranted());
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsCloserVehicleWithoutRequiredCapability() {
|
||||
VehicleAssignmentDecision decision = policy.assign(taskRequiringLoad(),
|
||||
|
||||
Reference in New Issue
Block a user