package com.zy.core.thread.impl.v5;
|
|
import com.alibaba.fastjson.JSON;
|
import com.core.common.Cools;
|
import com.zy.common.utils.RedisUtil;
|
import com.zy.core.enums.RedisKeyType;
|
import com.zy.core.model.command.StationCommand;
|
import com.zy.core.service.StationTaskLoopService;
|
import lombok.Data;
|
|
import java.util.ArrayList;
|
import java.util.HashMap;
|
import java.util.LinkedHashSet;
|
import java.util.List;
|
import java.util.Map;
|
import java.util.Set;
|
|
public class StationV5RunBlockReroutePlanner {
|
|
private static final int RUN_BLOCK_REROUTE_STATE_EXPIRE_SECONDS = 60 * 60 * 24;
|
private static final int SHORT_PATH_REPEAT_AVOID_THRESHOLD = 2;
|
|
private final RedisUtil redisUtil;
|
|
public StationV5RunBlockReroutePlanner(RedisUtil redisUtil) {
|
this.redisUtil = redisUtil;
|
}
|
|
public PlanResult plan(Integer taskNo,
|
Integer blockStationId,
|
StationTaskLoopService.LoopEvaluation loopEvaluation,
|
List<StationCommand> candidateCommands) {
|
RunBlockRerouteState rerouteState = loadRunBlockRerouteState(taskNo, blockStationId);
|
rerouteState.setTaskNo(taskNo);
|
rerouteState.setBlockStationId(blockStationId);
|
rerouteState.setPlanCount((rerouteState.getPlanCount() == null ? 0 : rerouteState.getPlanCount()) + 1);
|
|
StationCommand rerouteCommand = selectAvailableRerouteCommand(rerouteState, loopEvaluation, candidateCommands);
|
if (rerouteCommand == null && candidateCommands != null && !candidateCommands.isEmpty()) {
|
rerouteState.resetIssuedRoutes();
|
rerouteCommand = selectAvailableRerouteCommand(rerouteState, loopEvaluation, candidateCommands);
|
}
|
|
saveRunBlockRerouteState(rerouteState);
|
return new PlanResult(
|
rerouteCommand,
|
rerouteState.getPlanCount() == null ? 0 : rerouteState.getPlanCount(),
|
copyRoutePathList(rerouteState.getIssuedRoutePathList())
|
);
|
}
|
|
private StationCommand selectAvailableRerouteCommand(RunBlockRerouteState rerouteState,
|
StationTaskLoopService.LoopEvaluation loopEvaluation,
|
List<StationCommand> candidateCommands) {
|
if (rerouteState == null || candidateCommands == null || candidateCommands.isEmpty()) {
|
return null;
|
}
|
|
Set<String> issuedRouteSignatureSet = rerouteState.getIssuedRouteSignatureSet();
|
StationTaskLoopService.LoopIdentitySnapshot loopIdentity = loopEvaluation == null
|
? StationTaskLoopService.LoopIdentitySnapshot.empty()
|
: loopEvaluation.getLoopIdentity();
|
if (loopIdentity == null) {
|
loopIdentity = StationTaskLoopService.LoopIdentitySnapshot.empty();
|
}
|
|
List<RerouteCandidateCommand> candidateCommandList = new ArrayList<>();
|
for (StationCommand candidateCommand : candidateCommands) {
|
if (candidateCommand == null || candidateCommand.getNavigatePath() == null || candidateCommand.getNavigatePath().isEmpty()) {
|
continue;
|
}
|
String routeSignature = buildPathSignature(candidateCommand.getNavigatePath());
|
if (Cools.isEmpty(routeSignature)) {
|
continue;
|
}
|
RerouteCandidateCommand rerouteCandidateCommand = new RerouteCandidateCommand();
|
rerouteCandidateCommand.setCommand(candidateCommand);
|
rerouteCandidateCommand.setRouteSignature(routeSignature);
|
rerouteCandidateCommand.setPathLength(candidateCommand.getNavigatePath().size());
|
rerouteCandidateCommand.setIssuedCount(rerouteState.getRouteIssueCountMap().getOrDefault(routeSignature, 0));
|
rerouteCandidateCommand.setLoopFingerprint(loopIdentity.getLoopFingerprint());
|
rerouteCandidateCommand.setLoopTriggered(loopEvaluation != null && loopEvaluation.isLargeLoopTriggered());
|
rerouteCandidateCommand.setCurrentLoopHitCount(countCurrentLoopStationHit(
|
candidateCommand.getNavigatePath(),
|
loopIdentity.getStationIdSet()
|
));
|
candidateCommandList.add(rerouteCandidateCommand);
|
}
|
if (candidateCommandList.isEmpty()) {
|
return null;
|
}
|
|
List<RerouteCandidateCommand> orderedCandidateCommandList = reorderCandidateCommandsForLoopRelease(candidateCommandList);
|
for (RerouteCandidateCommand candidateCommand : orderedCandidateCommandList) {
|
if (candidateCommand == null || candidateCommand.getCommand() == null) {
|
continue;
|
}
|
if (issuedRouteSignatureSet.contains(candidateCommand.getRouteSignature())) {
|
continue;
|
}
|
|
StationCommand rerouteCommand = candidateCommand.getCommand();
|
issuedRouteSignatureSet.add(candidateCommand.getRouteSignature());
|
rerouteState.getIssuedRoutePathList().add(new ArrayList<>(rerouteCommand.getNavigatePath()));
|
rerouteState.setLastSelectedRoute(new ArrayList<>(rerouteCommand.getNavigatePath()));
|
rerouteState.getRouteIssueCountMap().put(
|
candidateCommand.getRouteSignature(),
|
rerouteState.getRouteIssueCountMap().getOrDefault(candidateCommand.getRouteSignature(), 0) + 1
|
);
|
return rerouteCommand;
|
}
|
return null;
|
}
|
|
private List<RerouteCandidateCommand> reorderCandidateCommandsForLoopRelease(List<RerouteCandidateCommand> candidateCommandList) {
|
if (candidateCommandList == null || candidateCommandList.isEmpty()) {
|
return new ArrayList<>();
|
}
|
|
int shortestPathLength = Integer.MAX_VALUE;
|
int shortestPathLoopHitCount = Integer.MAX_VALUE;
|
boolean shortestPathOverused = false;
|
boolean currentLoopOverused = false;
|
boolean hasLongerCandidate = false;
|
for (RerouteCandidateCommand candidateCommand : candidateCommandList) {
|
if (candidateCommand == null || candidateCommand.getPathLength() == null || candidateCommand.getPathLength() <= 0) {
|
continue;
|
}
|
shortestPathLength = Math.min(shortestPathLength, candidateCommand.getPathLength());
|
}
|
if (shortestPathLength == Integer.MAX_VALUE) {
|
return candidateCommandList;
|
}
|
|
for (RerouteCandidateCommand candidateCommand : candidateCommandList) {
|
if (candidateCommand == null || candidateCommand.getPathLength() == null || candidateCommand.getPathLength() <= 0) {
|
continue;
|
}
|
if (candidateCommand.getPathLength() == shortestPathLength) {
|
shortestPathLoopHitCount = Math.min(shortestPathLoopHitCount, safeInt(candidateCommand.getCurrentLoopHitCount()));
|
}
|
if (candidateCommand.getPathLength() > shortestPathLength) {
|
hasLongerCandidate = true;
|
}
|
if (candidateCommand.getPathLength() == shortestPathLength
|
&& candidateCommand.getIssuedCount() != null
|
&& candidateCommand.getIssuedCount() >= SHORT_PATH_REPEAT_AVOID_THRESHOLD) {
|
shortestPathOverused = true;
|
}
|
if (!Cools.isEmpty(candidateCommand.getLoopFingerprint())
|
&& Boolean.TRUE.equals(candidateCommand.getLoopTriggered())) {
|
currentLoopOverused = true;
|
}
|
}
|
if (!shortestPathOverused && !currentLoopOverused) {
|
return candidateCommandList;
|
}
|
if (shortestPathLoopHitCount == Integer.MAX_VALUE) {
|
shortestPathLoopHitCount = 0;
|
}
|
|
boolean hasLoopExitCandidate = false;
|
for (RerouteCandidateCommand candidateCommand : candidateCommandList) {
|
if (candidateCommand == null) {
|
continue;
|
}
|
if (safeInt(candidateCommand.getCurrentLoopHitCount()) < shortestPathLoopHitCount) {
|
hasLoopExitCandidate = true;
|
break;
|
}
|
}
|
if (!hasLongerCandidate && !hasLoopExitCandidate) {
|
return candidateCommandList;
|
}
|
|
List<RerouteCandidateCommand> reorderedList = new ArrayList<>();
|
if (currentLoopOverused && hasLoopExitCandidate) {
|
for (RerouteCandidateCommand candidateCommand : candidateCommandList) {
|
if (candidateCommand == null) {
|
continue;
|
}
|
if (safeInt(candidateCommand.getCurrentLoopHitCount()) < shortestPathLoopHitCount) {
|
appendCandidateIfAbsent(reorderedList, candidateCommand);
|
}
|
}
|
}
|
for (RerouteCandidateCommand candidateCommand : candidateCommandList) {
|
if (candidateCommand != null
|
&& candidateCommand.getPathLength() != null
|
&& candidateCommand.getPathLength() > shortestPathLength) {
|
appendCandidateIfAbsent(reorderedList, candidateCommand);
|
}
|
}
|
for (RerouteCandidateCommand candidateCommand : candidateCommandList) {
|
if (candidateCommand == null || candidateCommand.getPathLength() == null) {
|
continue;
|
}
|
appendCandidateIfAbsent(reorderedList, candidateCommand);
|
}
|
return reorderedList;
|
}
|
|
private void appendCandidateIfAbsent(List<RerouteCandidateCommand> reorderedList,
|
RerouteCandidateCommand candidateCommand) {
|
if (reorderedList == null || candidateCommand == null) {
|
return;
|
}
|
if (!reorderedList.contains(candidateCommand)) {
|
reorderedList.add(candidateCommand);
|
}
|
}
|
|
private RunBlockRerouteState loadRunBlockRerouteState(Integer taskNo, Integer blockStationId) {
|
if (redisUtil == null || taskNo == null || taskNo <= 0 || blockStationId == null || blockStationId <= 0) {
|
return new RunBlockRerouteState();
|
}
|
Object stateObj = redisUtil.get(buildRunBlockRerouteStateKey(taskNo, blockStationId));
|
if (stateObj == null) {
|
return new RunBlockRerouteState();
|
}
|
try {
|
RunBlockRerouteState state = JSON.parseObject(String.valueOf(stateObj), RunBlockRerouteState.class);
|
return state == null ? new RunBlockRerouteState() : state.normalize();
|
} catch (Exception ignore) {
|
return new RunBlockRerouteState();
|
}
|
}
|
|
private void saveRunBlockRerouteState(RunBlockRerouteState rerouteState) {
|
if (redisUtil == null
|
|| rerouteState == null
|
|| rerouteState.getTaskNo() == null
|
|| rerouteState.getTaskNo() <= 0
|
|| rerouteState.getBlockStationId() == null
|
|| rerouteState.getBlockStationId() <= 0) {
|
return;
|
}
|
rerouteState.normalize();
|
redisUtil.set(
|
buildRunBlockRerouteStateKey(rerouteState.getTaskNo(), rerouteState.getBlockStationId()),
|
JSON.toJSONString(rerouteState),
|
RUN_BLOCK_REROUTE_STATE_EXPIRE_SECONDS
|
);
|
}
|
|
private String buildRunBlockRerouteStateKey(Integer taskNo, Integer blockStationId) {
|
return RedisKeyType.STATION_RUN_BLOCK_REROUTE_STATE_.key + taskNo + "_" + blockStationId;
|
}
|
|
private int countCurrentLoopStationHit(List<Integer> path, Set<Integer> currentLoopStationIdSet) {
|
if (path == null || path.isEmpty() || currentLoopStationIdSet == null || currentLoopStationIdSet.isEmpty()) {
|
return 0;
|
}
|
int hitCount = 0;
|
for (Integer stationId : path) {
|
if (stationId != null && currentLoopStationIdSet.contains(stationId)) {
|
hitCount++;
|
}
|
}
|
return hitCount;
|
}
|
|
private String buildPathSignature(List<Integer> path) {
|
if (path == null || path.isEmpty()) {
|
return "";
|
}
|
StringBuilder builder = new StringBuilder();
|
for (Integer stationNo : path) {
|
if (stationNo == null) {
|
continue;
|
}
|
if (builder.length() > 0) {
|
builder.append("->");
|
}
|
builder.append(stationNo);
|
}
|
return builder.toString();
|
}
|
|
private int safeInt(Integer value) {
|
return value == null ? 0 : value;
|
}
|
|
private List<List<Integer>> copyRoutePathList(List<List<Integer>> source) {
|
List<List<Integer>> copy = new ArrayList<>();
|
if (source == null || source.isEmpty()) {
|
return copy;
|
}
|
for (List<Integer> route : source) {
|
copy.add(route == null ? new ArrayList<>() : new ArrayList<>(route));
|
}
|
return copy;
|
}
|
|
@Data
|
public static class PlanResult {
|
private final StationCommand command;
|
private final int planCount;
|
private final List<List<Integer>> issuedRoutePathList;
|
}
|
|
@Data
|
private static class RunBlockRerouteState {
|
private Integer taskNo;
|
private Integer blockStationId;
|
private Integer planCount = 0;
|
private List<List<Integer>> issuedRoutePathList = new ArrayList<>();
|
private List<Integer> lastSelectedRoute = new ArrayList<>();
|
private Set<String> issuedRouteSignatureSet = new LinkedHashSet<>();
|
private Map<String, Integer> routeIssueCountMap = new HashMap<>();
|
|
private RunBlockRerouteState normalize() {
|
if (planCount == null || planCount < 0) {
|
planCount = 0;
|
}
|
if (issuedRoutePathList == null) {
|
issuedRoutePathList = new ArrayList<>();
|
}
|
if (lastSelectedRoute == null) {
|
lastSelectedRoute = new ArrayList<>();
|
}
|
if (issuedRouteSignatureSet == null) {
|
issuedRouteSignatureSet = new LinkedHashSet<>();
|
}
|
if (routeIssueCountMap == null) {
|
routeIssueCountMap = new HashMap<>();
|
}
|
for (List<Integer> routePath : issuedRoutePathList) {
|
if (routePath == null || routePath.isEmpty()) {
|
continue;
|
}
|
String pathSignature = buildPathSignatureText(routePath);
|
if (!Cools.isEmpty(pathSignature)) {
|
issuedRouteSignatureSet.add(pathSignature);
|
routeIssueCountMap.putIfAbsent(pathSignature, 1);
|
}
|
}
|
return this;
|
}
|
|
private void resetIssuedRoutes() {
|
this.issuedRoutePathList = new ArrayList<>();
|
this.lastSelectedRoute = new ArrayList<>();
|
this.issuedRouteSignatureSet = new LinkedHashSet<>();
|
}
|
|
private static String buildPathSignatureText(List<Integer> routePath) {
|
if (routePath == null || routePath.isEmpty()) {
|
return "";
|
}
|
StringBuilder builder = new StringBuilder();
|
for (Integer stationId : routePath) {
|
if (stationId == null) {
|
continue;
|
}
|
if (builder.length() > 0) {
|
builder.append("->");
|
}
|
builder.append(stationId);
|
}
|
return builder.toString();
|
}
|
}
|
|
@Data
|
private static class RerouteCandidateCommand {
|
private StationCommand command;
|
private String routeSignature;
|
private Integer pathLength;
|
private Integer issuedCount;
|
private String loopFingerprint;
|
private Boolean loopTriggered;
|
private Integer currentLoopHitCount;
|
}
|
}
|