package com.zy.core.thread.impl;
|
|
import com.alibaba.fastjson.JSON;
|
import com.alibaba.fastjson.JSONObject;
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
import com.core.common.Cools;
|
import com.core.common.DateUtils;
|
import com.core.common.SpringUtils;
|
import com.zy.asrs.domain.vo.StationCycleCapacityVo;
|
import com.zy.asrs.domain.vo.StationCycleLoopVo;
|
import com.zy.asrs.entity.BasDevp;
|
import com.zy.asrs.entity.BasStationOpt;
|
import com.zy.asrs.entity.DeviceConfig;
|
import com.zy.asrs.entity.DeviceDataLog;
|
import com.zy.asrs.service.BasDevpService;
|
import com.zy.asrs.service.BasStationOptService;
|
import com.zy.asrs.service.StationCycleCapacityService;
|
import com.zy.asrs.utils.Utils;
|
import com.zy.common.model.NavigateNode;
|
import com.zy.common.utils.NavigateUtils;
|
import com.zy.common.utils.RedisUtil;
|
import com.zy.core.cache.MessageQueue;
|
import com.zy.core.cache.OutputQueue;
|
import com.zy.core.enums.RedisKeyType;
|
import com.zy.core.enums.SlaveType;
|
import com.zy.core.enums.StationCommandType;
|
import com.zy.core.model.CommandResponse;
|
import com.zy.core.model.Task;
|
import com.zy.core.model.command.StationCommand;
|
import com.zy.core.model.protocol.StationProtocol;
|
import com.zy.core.network.DeviceConnectPool;
|
import com.zy.core.network.ZyStationConnectDriver;
|
import com.zy.core.network.entity.ZyStationStatusEntity;
|
import com.zy.core.service.StationTaskLoopService;
|
import com.zy.core.thread.impl.v5.StationV5SegmentExecutor;
|
import com.zy.core.trace.StationTaskTraceRegistry;
|
import com.zy.core.utils.DeviceLogRedisKeyBuilder;
|
import lombok.Data;
|
import lombok.extern.slf4j.Slf4j;
|
|
import java.text.MessageFormat;
|
import java.util.ArrayList;
|
import java.util.ArrayDeque;
|
import java.util.Collections;
|
import java.util.Date;
|
import java.util.Deque;
|
import java.util.HashMap;
|
import java.util.HashSet;
|
import java.util.LinkedHashSet;
|
import java.util.List;
|
import java.util.Map;
|
import java.util.Objects;
|
import java.util.Set;
|
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.Executors;
|
|
@Data
|
@Slf4j
|
public class ZyStationV5Thread implements Runnable, com.zy.core.thread.StationThread {
|
|
private static final int RUN_BLOCK_REROUTE_STATE_EXPIRE_SECONDS = 60 * 60 * 24;
|
private static final int SHORT_PATH_REPEAT_AVOID_THRESHOLD = 2;
|
private static final int LOOP_REPEAT_TRIGGER_COUNT = 3;
|
private static final int LOCAL_LOOP_NEIGHBOR_HOP = 3;
|
|
private List<StationProtocol> statusList = new ArrayList<>();
|
private DeviceConfig deviceConfig;
|
private RedisUtil redisUtil;
|
private ZyStationConnectDriver zyStationConnectDriver;
|
private int deviceLogCollectTime = 200;
|
private boolean initStatus = false;
|
private long deviceDataLogTime = System.currentTimeMillis();
|
private ExecutorService executor = Executors.newFixedThreadPool(9999);
|
private StationV5SegmentExecutor segmentExecutor;
|
|
public ZyStationV5Thread(DeviceConfig deviceConfig, RedisUtil redisUtil) {
|
this.deviceConfig = deviceConfig;
|
this.redisUtil = redisUtil;
|
this.segmentExecutor = new StationV5SegmentExecutor(deviceConfig, redisUtil, this::sendCommand);
|
}
|
|
@Override
|
@SuppressWarnings("InfiniteLoopStatement")
|
public void run() {
|
this.connect();
|
deviceLogCollectTime = Utils.getDeviceLogCollectTime();
|
|
Thread readThread = new Thread(() -> {
|
while (true) {
|
try {
|
if (initStatus) {
|
deviceLogCollectTime = Utils.getDeviceLogCollectTime();
|
}
|
readStatus();
|
Thread.sleep(100);
|
} catch (Exception e) {
|
log.error("StationV5Thread Fail", e);
|
}
|
}
|
}, "DevpRead-" + deviceConfig.getDeviceNo());
|
readThread.start();
|
|
Thread processThread = new Thread(() -> {
|
while (true) {
|
try {
|
int step = 1;
|
Task task = MessageQueue.poll(SlaveType.Devp, deviceConfig.getDeviceNo());
|
if (task != null) {
|
step = task.getStep();
|
}
|
if (step == 2) {
|
StationCommand cmd = (StationCommand) task.getData();
|
executor.submit(() -> segmentExecutor.execute(cmd));
|
}
|
Thread.sleep(100);
|
} catch (Exception e) {
|
log.error("StationV5Process Fail", e);
|
}
|
}
|
}, "DevpProcess-" + deviceConfig.getDeviceNo());
|
processThread.start();
|
}
|
|
private void readStatus() {
|
if (zyStationConnectDriver == null) {
|
return;
|
}
|
|
if (statusList.isEmpty()) {
|
BasDevpService basDevpService = null;
|
try {
|
basDevpService = SpringUtils.getBean(BasDevpService.class);
|
} catch (Exception ignore) {
|
}
|
if (basDevpService == null) {
|
return;
|
}
|
|
BasDevp basDevp = basDevpService
|
.getOne(new QueryWrapper<BasDevp>().eq("devp_no", deviceConfig.getDeviceNo()));
|
if (basDevp == null) {
|
return;
|
}
|
|
List<ZyStationStatusEntity> list = JSONObject.parseArray(basDevp.getStationList(), ZyStationStatusEntity.class);
|
for (ZyStationStatusEntity entity : list) {
|
StationProtocol stationProtocol = new StationProtocol();
|
stationProtocol.setStationId(entity.getStationId());
|
statusList.add(stationProtocol);
|
}
|
initStatus = true;
|
}
|
|
List<ZyStationStatusEntity> zyStationStatusEntities = zyStationConnectDriver.getStatus();
|
for (ZyStationStatusEntity statusEntity : zyStationStatusEntities) {
|
for (StationProtocol stationProtocol : statusList) {
|
if (stationProtocol.getStationId().equals(statusEntity.getStationId())) {
|
stationProtocol.setTaskNo(statusEntity.getTaskNo());
|
stationProtocol.setTargetStaNo(statusEntity.getTargetStaNo());
|
stationProtocol.setAutoing(statusEntity.isAutoing());
|
stationProtocol.setLoading(statusEntity.isLoading());
|
stationProtocol.setInEnable(statusEntity.isInEnable());
|
stationProtocol.setOutEnable(statusEntity.isOutEnable());
|
stationProtocol.setEmptyMk(statusEntity.isEmptyMk());
|
stationProtocol.setFullPlt(statusEntity.isFullPlt());
|
stationProtocol.setPalletHeight(statusEntity.getPalletHeight());
|
stationProtocol.setError(statusEntity.getError());
|
stationProtocol.setErrorMsg(statusEntity.getErrorMsg());
|
stationProtocol.setBarcode(statusEntity.getBarcode());
|
stationProtocol.setRunBlock(statusEntity.isRunBlock());
|
stationProtocol.setEnableIn(statusEntity.isEnableIn());
|
stationProtocol.setWeight(statusEntity.getWeight());
|
stationProtocol.setTaskWriteIdx(statusEntity.getTaskWriteIdx());
|
}
|
|
if (!Cools.isEmpty(stationProtocol.getSystemWarning())) {
|
if (stationProtocol.isAutoing() && !stationProtocol.isLoading()) {
|
stationProtocol.setSystemWarning("");
|
}
|
}
|
}
|
}
|
|
OutputQueue.DEVP.offer(MessageFormat.format("【{0}】[id:{1}] <<<<< 实时数据更新成功",
|
DateUtils.convert(new Date()), deviceConfig.getDeviceNo()));
|
|
if (System.currentTimeMillis() - deviceDataLogTime > deviceLogCollectTime) {
|
DeviceDataLog deviceDataLog = new DeviceDataLog();
|
deviceDataLog.setOriginData(JSON.toJSONString(zyStationStatusEntities));
|
deviceDataLog.setWcsData(JSON.toJSONString(statusList));
|
deviceDataLog.setType(String.valueOf(SlaveType.Devp));
|
deviceDataLog.setDeviceNo(deviceConfig.getDeviceNo());
|
deviceDataLog.setCreateTime(new Date());
|
|
redisUtil.set(DeviceLogRedisKeyBuilder.build(deviceDataLog), deviceDataLog, 60 * 60 * 24);
|
deviceDataLogTime = System.currentTimeMillis();
|
}
|
}
|
|
@Override
|
public boolean connect() {
|
zyStationConnectDriver = new ZyStationConnectDriver(deviceConfig, redisUtil);
|
zyStationConnectDriver.start();
|
DeviceConnectPool.put(SlaveType.Devp, deviceConfig.getDeviceNo(), zyStationConnectDriver);
|
return true;
|
}
|
|
@Override
|
public void close() {
|
if (zyStationConnectDriver != null) {
|
zyStationConnectDriver.close();
|
}
|
if (executor != null) {
|
try {
|
executor.shutdownNow();
|
} catch (Exception ignore) {
|
}
|
}
|
}
|
|
@Override
|
public List<StationProtocol> getStatus() {
|
return statusList;
|
}
|
|
@Override
|
public Map<Integer, StationProtocol> getStatusMap() {
|
Map<Integer, StationProtocol> map = new HashMap<>();
|
for (StationProtocol stationProtocol : statusList) {
|
map.put(stationProtocol.getStationId(), stationProtocol);
|
}
|
return map;
|
}
|
|
@Override
|
public StationCommand getCommand(StationCommandType commandType,
|
Integer taskNo,
|
Integer stationId,
|
Integer targetStationId,
|
Integer palletSize) {
|
return getCommand(commandType, taskNo, stationId, targetStationId, palletSize, null);
|
}
|
|
@Override
|
public StationCommand getCommand(StationCommandType commandType,
|
Integer taskNo,
|
Integer stationId,
|
Integer targetStationId,
|
Integer palletSize,
|
Double pathLenFactor) {
|
StationCommand stationCommand = new StationCommand();
|
stationCommand.setTaskNo(taskNo);
|
stationCommand.setStationId(stationId);
|
stationCommand.setTargetStaNo(targetStationId);
|
stationCommand.setPalletSize(palletSize);
|
stationCommand.setCommandType(commandType);
|
|
if (commandType == StationCommandType.MOVE && !stationId.equals(targetStationId)) {
|
List<NavigateNode> nodes = calcPathNavigateNodes(taskNo, stationId, targetStationId, pathLenFactor);
|
return fillMoveCommandPath(stationCommand, nodes, taskNo, stationId, targetStationId);
|
}
|
return stationCommand;
|
}
|
|
@Override
|
public synchronized StationCommand getRunBlockRerouteCommand(Integer taskNo,
|
Integer stationId,
|
Integer targetStationId,
|
Integer palletSize) {
|
return getRunBlockRerouteCommand(taskNo, stationId, targetStationId, palletSize, null);
|
}
|
|
@Override
|
public synchronized StationCommand getRunBlockRerouteCommand(Integer taskNo,
|
Integer stationId,
|
Integer targetStationId,
|
Integer palletSize,
|
Double pathLenFactor) {
|
if (taskNo == null || taskNo <= 0 || stationId == null || targetStationId == null) {
|
return null;
|
}
|
if (Objects.equals(stationId, targetStationId)) {
|
return getCommand(StationCommandType.MOVE, taskNo, stationId, targetStationId, palletSize, pathLenFactor);
|
}
|
|
RunBlockRerouteState rerouteState = loadRunBlockRerouteState(taskNo, stationId);
|
StationTaskLoopService taskLoopService = loadStationTaskLoopService();
|
StationTaskLoopService.LoopEvaluation loopEvaluation = taskLoopService == null
|
? new StationTaskLoopService.LoopEvaluation(taskNo, stationId, StationTaskLoopService.LoopIdentitySnapshot.empty(), 0, 0, false)
|
: taskLoopService.evaluateLoop(taskNo, stationId, true);
|
log.info("输送线堵塞重规划环线识别,taskNo={}, stationId={}, scopeType={}, localStationCount={}, sourceLoopStationCount={}",
|
taskNo,
|
stationId,
|
loopEvaluation.getLoopIdentity().getScopeType(),
|
loopEvaluation.getLoopIdentity().getLocalStationCount(),
|
loopEvaluation.getLoopIdentity().getSourceLoopStationCount());
|
rerouteState.setTaskNo(taskNo);
|
rerouteState.setBlockStationId(stationId);
|
rerouteState.setLastTargetStationId(targetStationId);
|
rerouteState.setPlanCount((rerouteState.getPlanCount() == null ? 0 : rerouteState.getPlanCount()) + 1);
|
rerouteState.setLastPlanTime(System.currentTimeMillis());
|
|
List<List<NavigateNode>> candidatePathList = calcCandidatePathNavigateNodes(taskNo, stationId, targetStationId, pathLenFactor);
|
if (candidatePathList.isEmpty()) {
|
saveRunBlockRerouteState(rerouteState);
|
log.warn("输送线堵塞重规划失败,候选路径为空,taskNo={}, planCount={}, stationId={}, targetStationId={}",
|
taskNo, rerouteState.getPlanCount(), stationId, targetStationId);
|
return null;
|
}
|
|
StationCommand rerouteCommand = selectAvailableRerouteCommand(
|
rerouteState,
|
loopEvaluation,
|
candidatePathList,
|
taskNo,
|
stationId,
|
targetStationId,
|
palletSize
|
);
|
if (rerouteCommand == null) {
|
log.info("输送线堵塞重规划候选路线已全部试过,重置路线历史后重新开始,taskNo={}, planCount={}, stationId={}, targetStationId={}",
|
taskNo, rerouteState.getPlanCount(), stationId, targetStationId);
|
rerouteState.resetIssuedRoutes();
|
rerouteCommand = selectAvailableRerouteCommand(
|
rerouteState,
|
loopEvaluation,
|
candidatePathList,
|
taskNo,
|
stationId,
|
targetStationId,
|
palletSize
|
);
|
}
|
|
if (rerouteCommand != null) {
|
saveRunBlockRerouteState(rerouteState);
|
if (taskLoopService != null) {
|
taskLoopService.recordLoopIssue(loopEvaluation, "RUN_BLOCK_REROUTE");
|
}
|
log.info("输送线堵塞重规划选中候选路线,taskNo={}, planCount={}, stationId={}, targetStationId={}, route={}",
|
taskNo, rerouteState.getPlanCount(), stationId, targetStationId, JSON.toJSONString(rerouteCommand.getNavigatePath()));
|
return rerouteCommand;
|
}
|
|
saveRunBlockRerouteState(rerouteState);
|
log.warn("输送线堵塞重规划未找到可下发路线,taskNo={}, planCount={}, stationId={}, targetStationId={}, triedRoutes={}",
|
taskNo,
|
rerouteState.getPlanCount(),
|
stationId,
|
targetStationId,
|
JSON.toJSONString(rerouteState.getIssuedRoutePathList()));
|
return null;
|
}
|
|
@Override
|
public CommandResponse sendCommand(StationCommand command) {
|
CommandResponse commandResponse = null;
|
try {
|
commandResponse = zyStationConnectDriver.sendCommand(command);
|
} catch (Exception e) {
|
e.printStackTrace();
|
} finally {
|
BasStationOptService optService = SpringUtils.getBean(BasStationOptService.class);
|
List<ZyStationStatusEntity> statusListEntity = zyStationConnectDriver.getStatus();
|
ZyStationStatusEntity matched = null;
|
if (statusListEntity != null) {
|
for (ZyStationStatusEntity entity : statusListEntity) {
|
if (entity.getStationId() != null && entity.getStationId().equals(command.getStationId())) {
|
matched = entity;
|
break;
|
}
|
}
|
}
|
BasStationOpt basStationOpt = new BasStationOpt(
|
command.getTaskNo(),
|
command.getStationId(),
|
new Date(),
|
String.valueOf(command.getCommandType()),
|
command.getStationId(),
|
command.getTargetStaNo(),
|
null,
|
null,
|
null,
|
JSON.toJSONString(command),
|
JSON.toJSONString(matched),
|
1,
|
JSON.toJSONString(commandResponse)
|
);
|
if (optService != null) {
|
optService.save(basStationOpt);
|
}
|
}
|
return commandResponse;
|
}
|
|
@Override
|
public CommandResponse sendOriginCommand(String address, short[] data) {
|
return zyStationConnectDriver.sendOriginCommand(address, data);
|
}
|
|
@Override
|
public byte[] readOriginCommand(String address, int length) {
|
return zyStationConnectDriver.readOriginCommand(address, length);
|
}
|
|
private List<NavigateNode> calcPathNavigateNodes(Integer taskNo,
|
Integer startStationId,
|
Integer targetStationId,
|
Double pathLenFactor) {
|
NavigateUtils navigateUtils = SpringUtils.getBean(NavigateUtils.class);
|
if (navigateUtils == null) {
|
return new ArrayList<>();
|
}
|
return navigateUtils.calcByStationId(startStationId, targetStationId, taskNo, pathLenFactor);
|
}
|
|
private List<List<NavigateNode>> calcCandidatePathNavigateNodes(Integer taskNo,
|
Integer startStationId,
|
Integer targetStationId,
|
Double pathLenFactor) {
|
NavigateUtils navigateUtils = SpringUtils.getBean(NavigateUtils.class);
|
if (navigateUtils == null) {
|
return new ArrayList<>();
|
}
|
return navigateUtils.calcCandidatePathByStationId(startStationId, targetStationId, taskNo, pathLenFactor);
|
}
|
|
private StationCommand buildMoveCommand(Integer taskNo,
|
Integer stationId,
|
Integer targetStationId,
|
Integer palletSize,
|
List<NavigateNode> nodes) {
|
StationCommand stationCommand = new StationCommand();
|
stationCommand.setTaskNo(taskNo);
|
stationCommand.setStationId(stationId);
|
stationCommand.setTargetStaNo(targetStationId);
|
stationCommand.setPalletSize(palletSize);
|
stationCommand.setCommandType(StationCommandType.MOVE);
|
return fillMoveCommandPath(stationCommand, nodes, taskNo, stationId, targetStationId);
|
}
|
|
private StationCommand fillMoveCommandPath(StationCommand stationCommand,
|
List<NavigateNode> nodes,
|
Integer taskNo,
|
Integer stationId,
|
Integer targetStationId) {
|
List<Integer> path = new ArrayList<>();
|
List<Integer> liftTransferPath = new ArrayList<>();
|
for (NavigateNode node : nodes) {
|
JSONObject valueObject;
|
try {
|
valueObject = JSONObject.parseObject(node.getNodeValue());
|
} catch (Exception ignore) {
|
continue;
|
}
|
if (valueObject == null) {
|
continue;
|
}
|
Integer stationNo = valueObject.getInteger("stationId");
|
if (stationNo == null) {
|
continue;
|
}
|
path.add(stationNo);
|
if (Boolean.TRUE.equals(node.getIsLiftTransferPoint())) {
|
liftTransferPath.add(stationNo);
|
}
|
}
|
if (path.isEmpty()) {
|
log.warn("输送线命令生成失败,路径为空,taskNo={}, stationId={}, targetStationId={}",
|
taskNo, stationId, targetStationId);
|
return null;
|
}
|
stationCommand.setNavigatePath(path);
|
stationCommand.setLiftTransferPath(liftTransferPath);
|
stationCommand.setTargetStaNo(path.get(path.size() - 1));
|
return stationCommand;
|
}
|
|
private StationCommand selectAvailableRerouteCommand(RunBlockRerouteState rerouteState,
|
StationTaskLoopService.LoopEvaluation loopEvaluation,
|
List<List<NavigateNode>> candidatePathList,
|
Integer taskNo,
|
Integer stationId,
|
Integer targetStationId,
|
Integer palletSize) {
|
if (rerouteState == null || candidatePathList == null || candidatePathList.isEmpty()) {
|
return null;
|
}
|
|
Set<String> issuedRouteSignatureSet = rerouteState.getIssuedRouteSignatureSet();
|
List<RerouteCandidateCommand> candidateCommandList = new ArrayList<>();
|
for (List<NavigateNode> candidatePath : candidatePathList) {
|
StationCommand rerouteCommand = buildMoveCommand(taskNo, stationId, targetStationId, palletSize, candidatePath);
|
if (rerouteCommand == null || rerouteCommand.getNavigatePath() == null || rerouteCommand.getNavigatePath().isEmpty()) {
|
continue;
|
}
|
String routeSignature = buildPathSignature(rerouteCommand.getNavigatePath());
|
if (Cools.isEmpty(routeSignature)) {
|
continue;
|
}
|
RerouteCandidateCommand candidateCommand = new RerouteCandidateCommand();
|
candidateCommand.setCommand(rerouteCommand);
|
candidateCommand.setRouteSignature(routeSignature);
|
candidateCommand.setPathLength(rerouteCommand.getNavigatePath().size());
|
candidateCommand.setIssuedCount(rerouteState.getRouteIssueCountMap().getOrDefault(routeSignature, 0));
|
candidateCommand.setLoopFingerprint(loopEvaluation.getLoopIdentity().getLoopFingerprint());
|
candidateCommand.setLoopIssuedCount(loopEvaluation.getExpectedLoopIssueCount());
|
candidateCommand.setLoopTriggered(loopEvaluation.isLargeLoopTriggered());
|
candidateCommand.setCurrentLoopHitCount(countCurrentLoopStationHit(
|
rerouteCommand.getNavigatePath(),
|
loopEvaluation.getLoopIdentity().getStationIdSet()
|
));
|
candidateCommandList.add(candidateCommand);
|
}
|
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 TaskLoopRerouteState loadTaskLoopRerouteState(Integer taskNo) {
|
if (redisUtil == null || taskNo == null || taskNo <= 0) {
|
return new TaskLoopRerouteState();
|
}
|
Object stateObj = redisUtil.get(RedisKeyType.STATION_RUN_BLOCK_TASK_LOOP_STATE_.key + taskNo);
|
if (stateObj == null) {
|
return new TaskLoopRerouteState();
|
}
|
try {
|
TaskLoopRerouteState state = JSON.parseObject(String.valueOf(stateObj), TaskLoopRerouteState.class);
|
return state == null ? new TaskLoopRerouteState() : state.normalize();
|
} catch (Exception ignore) {
|
return new TaskLoopRerouteState();
|
}
|
}
|
|
private void saveTaskLoopRerouteState(TaskLoopRerouteState taskLoopRerouteState) {
|
if (redisUtil == null
|
|| taskLoopRerouteState == null
|
|| taskLoopRerouteState.getTaskNo() == null
|
|| taskLoopRerouteState.getTaskNo() <= 0) {
|
return;
|
}
|
taskLoopRerouteState.normalize();
|
redisUtil.set(
|
RedisKeyType.STATION_RUN_BLOCK_TASK_LOOP_STATE_.key + taskLoopRerouteState.getTaskNo(),
|
JSON.toJSONString(taskLoopRerouteState),
|
RUN_BLOCK_REROUTE_STATE_EXPIRE_SECONDS
|
);
|
}
|
|
private void touchTaskLoopRerouteState(TaskLoopRerouteState taskLoopRerouteState,
|
LoopIdentity currentLoopIdentity) {
|
if (taskLoopRerouteState == null || currentLoopIdentity == null || Cools.isEmpty(currentLoopIdentity.getLoopFingerprint())) {
|
return;
|
}
|
taskLoopRerouteState.getLoopIssueCountMap().put(
|
currentLoopIdentity.getLoopFingerprint(),
|
taskLoopRerouteState.getLoopIssueCountMap().getOrDefault(currentLoopIdentity.getLoopFingerprint(), 0) + 1
|
);
|
taskLoopRerouteState.setLastLoopFingerprint(currentLoopIdentity.getLoopFingerprint());
|
taskLoopRerouteState.setLastIssueTime(System.currentTimeMillis());
|
}
|
|
private int resolveCurrentLoopIssuedCount(TaskLoopRerouteState taskLoopRerouteState,
|
LoopIdentity currentLoopIdentity) {
|
if (taskLoopRerouteState == null || currentLoopIdentity == null || Cools.isEmpty(currentLoopIdentity.getLoopFingerprint())) {
|
return 0;
|
}
|
return taskLoopRerouteState.getLoopIssueCountMap().getOrDefault(currentLoopIdentity.getLoopFingerprint(), 0);
|
}
|
|
private int resolveExpectedLoopIssuedCount(TaskLoopRerouteState taskLoopRerouteState,
|
LoopIdentity currentLoopIdentity) {
|
if (currentLoopIdentity == null || Cools.isEmpty(currentLoopIdentity.getLoopFingerprint())) {
|
return 0;
|
}
|
return resolveCurrentLoopIssuedCount(taskLoopRerouteState, currentLoopIdentity) + 1;
|
}
|
|
private boolean isLoopRepeatTriggered(Integer loopIssuedCount) {
|
return loopIssuedCount != null && loopIssuedCount >= LOOP_REPEAT_TRIGGER_COUNT;
|
}
|
|
private void syncTaskTraceLoopAlert(Integer taskNo,
|
Integer blockedStationId,
|
LoopIdentity currentLoopIdentity,
|
int loopIssuedCount) {
|
StationTaskTraceRegistry traceRegistry;
|
try {
|
traceRegistry = SpringUtils.getBean(StationTaskTraceRegistry.class);
|
} catch (Exception ignore) {
|
return;
|
}
|
if (traceRegistry == null || taskNo == null || taskNo <= 0) {
|
return;
|
}
|
|
boolean active = currentLoopIdentity != null
|
&& !Cools.isEmpty(currentLoopIdentity.getLoopFingerprint())
|
&& isLoopRepeatTriggered(loopIssuedCount);
|
Map<String, Object> details = new HashMap<>();
|
details.put("blockedStationId", blockedStationId);
|
details.put("loopScopeType", currentLoopIdentity == null ? "" : currentLoopIdentity.getScopeType());
|
details.put("loopStationCount", currentLoopIdentity == null ? 0 : currentLoopIdentity.getLocalStationCount());
|
details.put("sourceLoopStationCount", currentLoopIdentity == null ? 0 : currentLoopIdentity.getSourceLoopStationCount());
|
details.put("loopRepeatCount", loopIssuedCount);
|
String loopAlertType = resolveLoopAlertType(currentLoopIdentity);
|
String loopAlertText = buildLoopAlertText(loopAlertType, currentLoopIdentity, loopIssuedCount);
|
traceRegistry.updateLoopHint(taskNo, active, loopAlertType, loopAlertText, loopIssuedCount, details);
|
}
|
|
private String resolveLoopAlertType(LoopIdentity currentLoopIdentity) {
|
if (currentLoopIdentity == null || Cools.isEmpty(currentLoopIdentity.getLoopFingerprint())) {
|
return "";
|
}
|
return "wholeLoop".equals(currentLoopIdentity.getScopeType()) ? "LARGE_LOOP" : "SMALL_LOOP";
|
}
|
|
private String buildLoopAlertText(String loopAlertType,
|
LoopIdentity currentLoopIdentity,
|
int loopIssuedCount) {
|
if (Cools.isEmpty(loopAlertType) || currentLoopIdentity == null || !isLoopRepeatTriggered(loopIssuedCount)) {
|
return "";
|
}
|
String typeLabel = "LARGE_LOOP".equals(loopAlertType) ? "大环线" : "小环线";
|
return typeLabel + "绕圈预警,累计重规划" + loopIssuedCount + "次,当前识别范围"
|
+ currentLoopIdentity.getLocalStationCount() + "站";
|
}
|
|
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 StationTaskLoopService loadStationTaskLoopService() {
|
try {
|
return SpringUtils.getBean(StationTaskLoopService.class);
|
} catch (Exception ignore) {
|
return null;
|
}
|
}
|
|
private String buildRunBlockRerouteStateKey(Integer taskNo, Integer blockStationId) {
|
return RedisKeyType.STATION_RUN_BLOCK_REROUTE_STATE_.key + taskNo + "_" + blockStationId;
|
}
|
|
private LoopIdentity resolveStationLoopIdentity(Integer stationId) {
|
if (stationId == null || stationId <= 0) {
|
return LoopIdentity.empty();
|
}
|
try {
|
StationCycleCapacityService stationCycleCapacityService = SpringUtils.getBean(StationCycleCapacityService.class);
|
if (stationCycleCapacityService == null) {
|
return LoopIdentity.empty();
|
}
|
StationCycleCapacityVo capacityVo = stationCycleCapacityService.getLatestSnapshot();
|
if (capacityVo == null || capacityVo.getLoopList() == null || capacityVo.getLoopList().isEmpty()) {
|
return LoopIdentity.empty();
|
}
|
for (StationCycleLoopVo loopVo : capacityVo.getLoopList()) {
|
List<Integer> loopStationIdList = normalizeLoopStationIdList(loopVo == null ? null : loopVo.getStationIdList());
|
if (loopStationIdList.isEmpty() || !loopStationIdList.contains(stationId)) {
|
continue;
|
}
|
Set<Integer> loopStationIdSet = new HashSet<>(loopStationIdList);
|
Map<Integer, Set<Integer>> stationGraph = loadUndirectedStationGraph();
|
List<Integer> localCycleStationIdList = resolveLocalCycleStationIdList(stationId, loopStationIdSet, stationGraph);
|
if (localCycleStationIdList.size() >= 3) {
|
return buildLoopIdentity(localCycleStationIdList, loopStationIdList.size(), "localCycle");
|
}
|
|
List<Integer> localNeighborhoodStationIdList = resolveLocalNeighborhoodStationIdList(stationId, loopStationIdSet, stationGraph);
|
if (localNeighborhoodStationIdList.size() >= 3 && localNeighborhoodStationIdList.size() < loopStationIdList.size()) {
|
return buildLoopIdentity(localNeighborhoodStationIdList, loopStationIdList.size(), "localNeighborhood");
|
}
|
return buildLoopIdentity(loopStationIdList, loopStationIdList.size(), "wholeLoop");
|
}
|
} catch (Exception ignore) {
|
}
|
return LoopIdentity.empty();
|
}
|
|
private LoopIdentity buildLoopIdentity(List<Integer> stationIdList,
|
int sourceLoopStationCount,
|
String scopeType) {
|
List<Integer> normalizedStationIdList = normalizeLoopStationIdList(stationIdList);
|
if (normalizedStationIdList.isEmpty()) {
|
return LoopIdentity.empty();
|
}
|
return new LoopIdentity(
|
buildLoopFingerprint(normalizedStationIdList),
|
new HashSet<>(normalizedStationIdList),
|
sourceLoopStationCount,
|
normalizedStationIdList.size(),
|
scopeType
|
);
|
}
|
|
private List<Integer> resolveLocalCycleStationIdList(Integer stationId,
|
Set<Integer> loopStationIdSet,
|
Map<Integer, Set<Integer>> stationGraph) {
|
if (stationId == null
|
|| stationId <= 0
|
|| loopStationIdSet == null
|
|| loopStationIdSet.isEmpty()
|
|| stationGraph == null
|
|| stationGraph.isEmpty()) {
|
return new ArrayList<>();
|
}
|
Set<Integer> localNeighborhoodStationIdSet = collectLoopNeighborhoodStationIdSet(
|
stationId,
|
loopStationIdSet,
|
stationGraph,
|
LOCAL_LOOP_NEIGHBOR_HOP
|
);
|
if (localNeighborhoodStationIdSet.size() < 3) {
|
return new ArrayList<>();
|
}
|
|
Set<Integer> directNeighborStationIdSet = filterLoopNeighborStationIdSet(
|
stationGraph.getOrDefault(stationId, Collections.emptySet()),
|
localNeighborhoodStationIdSet,
|
stationId
|
);
|
if (directNeighborStationIdSet.size() < 2) {
|
return new ArrayList<>();
|
}
|
|
List<Integer> bestCycleStationIdList = new ArrayList<>();
|
List<Integer> neighborStationIdList = new ArrayList<>(directNeighborStationIdSet);
|
for (int i = 0; i < neighborStationIdList.size(); i++) {
|
Integer leftNeighborStationId = neighborStationIdList.get(i);
|
if (leftNeighborStationId == null) {
|
continue;
|
}
|
for (int j = i + 1; j < neighborStationIdList.size(); j++) {
|
Integer rightNeighborStationId = neighborStationIdList.get(j);
|
if (rightNeighborStationId == null) {
|
continue;
|
}
|
List<Integer> pathBetweenNeighbors = findShortestScopePath(
|
leftNeighborStationId,
|
rightNeighborStationId,
|
stationId,
|
localNeighborhoodStationIdSet,
|
stationGraph
|
);
|
if (pathBetweenNeighbors.isEmpty()) {
|
continue;
|
}
|
List<Integer> cycleStationIdList = new ArrayList<>();
|
cycleStationIdList.add(stationId);
|
cycleStationIdList.addAll(pathBetweenNeighbors);
|
cycleStationIdList = normalizeLoopStationIdList(cycleStationIdList);
|
if (cycleStationIdList.size() < 3) {
|
continue;
|
}
|
if (bestCycleStationIdList.isEmpty() || cycleStationIdList.size() < bestCycleStationIdList.size()) {
|
bestCycleStationIdList = cycleStationIdList;
|
}
|
}
|
}
|
return bestCycleStationIdList;
|
}
|
|
private List<Integer> resolveLocalNeighborhoodStationIdList(Integer stationId,
|
Set<Integer> loopStationIdSet,
|
Map<Integer, Set<Integer>> stationGraph) {
|
return normalizeLoopStationIdList(new ArrayList<>(collectLoopNeighborhoodStationIdSet(
|
stationId,
|
loopStationIdSet,
|
stationGraph,
|
LOCAL_LOOP_NEIGHBOR_HOP
|
)));
|
}
|
|
private Set<Integer> collectLoopNeighborhoodStationIdSet(Integer stationId,
|
Set<Integer> loopStationIdSet,
|
Map<Integer, Set<Integer>> stationGraph,
|
int maxHop) {
|
Set<Integer> neighborhoodStationIdSet = new LinkedHashSet<>();
|
if (stationId == null
|
|| stationId <= 0
|
|| loopStationIdSet == null
|
|| loopStationIdSet.isEmpty()
|
|| !loopStationIdSet.contains(stationId)
|
|| stationGraph == null
|
|| stationGraph.isEmpty()
|
|| maxHop < 0) {
|
return neighborhoodStationIdSet;
|
}
|
|
Deque<StationHopNode> queue = new ArrayDeque<>();
|
queue.offer(new StationHopNode(stationId, 0));
|
neighborhoodStationIdSet.add(stationId);
|
while (!queue.isEmpty()) {
|
StationHopNode current = queue.poll();
|
if (current == null || current.getHop() >= maxHop) {
|
continue;
|
}
|
Set<Integer> neighborStationIdSet = filterLoopNeighborStationIdSet(
|
stationGraph.getOrDefault(current.getStationId(), Collections.emptySet()),
|
loopStationIdSet,
|
null
|
);
|
for (Integer neighborStationId : neighborStationIdSet) {
|
if (neighborStationId == null || !neighborhoodStationIdSet.add(neighborStationId)) {
|
continue;
|
}
|
queue.offer(new StationHopNode(neighborStationId, current.getHop() + 1));
|
}
|
}
|
return neighborhoodStationIdSet;
|
}
|
|
private Set<Integer> filterLoopNeighborStationIdSet(Set<Integer> candidateNeighborStationIdSet,
|
Set<Integer> allowedStationIdSet,
|
Integer excludedStationId) {
|
Set<Integer> result = new LinkedHashSet<>();
|
if (candidateNeighborStationIdSet == null || candidateNeighborStationIdSet.isEmpty()
|
|| allowedStationIdSet == null || allowedStationIdSet.isEmpty()) {
|
return result;
|
}
|
for (Integer stationId : candidateNeighborStationIdSet) {
|
if (stationId == null
|
|| !allowedStationIdSet.contains(stationId)
|
|| (excludedStationId != null && excludedStationId.equals(stationId))) {
|
continue;
|
}
|
result.add(stationId);
|
}
|
return result;
|
}
|
|
private List<Integer> findShortestScopePath(Integer startStationId,
|
Integer endStationId,
|
Integer excludedStationId,
|
Set<Integer> allowedStationIdSet,
|
Map<Integer, Set<Integer>> stationGraph) {
|
if (startStationId == null
|
|| endStationId == null
|
|| Objects.equals(startStationId, excludedStationId)
|
|| Objects.equals(endStationId, excludedStationId)
|
|| allowedStationIdSet == null
|
|| !allowedStationIdSet.contains(startStationId)
|
|| !allowedStationIdSet.contains(endStationId)
|
|| stationGraph == null
|
|| stationGraph.isEmpty()) {
|
return new ArrayList<>();
|
}
|
|
Deque<Integer> queue = new ArrayDeque<>();
|
Map<Integer, Integer> parentMap = new HashMap<>();
|
Set<Integer> visitedStationIdSet = new HashSet<>();
|
queue.offer(startStationId);
|
visitedStationIdSet.add(startStationId);
|
while (!queue.isEmpty()) {
|
Integer currentStationId = queue.poll();
|
if (currentStationId == null) {
|
continue;
|
}
|
if (Objects.equals(currentStationId, endStationId)) {
|
break;
|
}
|
Set<Integer> neighborStationIdSet = filterLoopNeighborStationIdSet(
|
stationGraph.getOrDefault(currentStationId, Collections.emptySet()),
|
allowedStationIdSet,
|
excludedStationId
|
);
|
for (Integer neighborStationId : neighborStationIdSet) {
|
if (neighborStationId == null || !visitedStationIdSet.add(neighborStationId)) {
|
continue;
|
}
|
parentMap.put(neighborStationId, currentStationId);
|
queue.offer(neighborStationId);
|
}
|
}
|
if (!visitedStationIdSet.contains(endStationId)) {
|
return new ArrayList<>();
|
}
|
|
List<Integer> pathStationIdList = new ArrayList<>();
|
Integer cursorStationId = endStationId;
|
while (cursorStationId != null) {
|
pathStationIdList.add(cursorStationId);
|
if (Objects.equals(cursorStationId, startStationId)) {
|
break;
|
}
|
cursorStationId = parentMap.get(cursorStationId);
|
}
|
if (pathStationIdList.isEmpty()
|
|| !Objects.equals(pathStationIdList.get(pathStationIdList.size() - 1), startStationId)) {
|
return new ArrayList<>();
|
}
|
Collections.reverse(pathStationIdList);
|
return pathStationIdList;
|
}
|
|
private Map<Integer, Set<Integer>> loadUndirectedStationGraph() {
|
NavigateUtils navigateUtils = SpringUtils.getBean(NavigateUtils.class);
|
if (navigateUtils == null) {
|
return new HashMap<>();
|
}
|
Map<Integer, Set<Integer>> stationGraph = navigateUtils.loadUndirectedStationGraphSnapshot();
|
return stationGraph == null ? new HashMap<>() : stationGraph;
|
}
|
|
private List<Integer> normalizeLoopStationIdList(List<Integer> stationIdList) {
|
if (stationIdList == null || stationIdList.isEmpty()) {
|
return new ArrayList<>();
|
}
|
List<Integer> normalizedList = new ArrayList<>();
|
Set<Integer> seenStationIdSet = new HashSet<>();
|
for (Integer stationId : stationIdList) {
|
if (stationId == null || stationId <= 0 || !seenStationIdSet.add(stationId)) {
|
continue;
|
}
|
normalizedList.add(stationId);
|
}
|
Collections.sort(normalizedList);
|
return normalizedList;
|
}
|
|
private String buildLoopFingerprint(List<Integer> stationIdList) {
|
if (stationIdList == null || stationIdList.isEmpty()) {
|
return "";
|
}
|
StringBuilder builder = new StringBuilder();
|
for (Integer stationId : stationIdList) {
|
if (stationId == null) {
|
continue;
|
}
|
if (builder.length() > 0) {
|
builder.append("|");
|
}
|
builder.append(stationId);
|
}
|
return builder.toString();
|
}
|
|
private int safeInt(Integer value) {
|
return value == null ? 0 : value;
|
}
|
|
@Data
|
private static class RunBlockRerouteState {
|
private Integer taskNo;
|
private Integer blockStationId;
|
private Integer planCount = 0;
|
private Integer lastTargetStationId;
|
private Long lastPlanTime;
|
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 Integer loopIssuedCount;
|
private Boolean loopTriggered;
|
private Integer currentLoopHitCount;
|
}
|
|
@Data
|
private static class TaskLoopRerouteState {
|
private Integer taskNo;
|
private String lastLoopFingerprint;
|
private Long lastIssueTime;
|
private Map<String, Integer> loopIssueCountMap = new HashMap<>();
|
|
private TaskLoopRerouteState normalize() {
|
if (loopIssueCountMap == null) {
|
loopIssueCountMap = new HashMap<>();
|
}
|
return this;
|
}
|
}
|
|
@Data
|
private static class LoopIdentity {
|
private String loopFingerprint;
|
private Set<Integer> stationIdSet = new HashSet<>();
|
private int sourceLoopStationCount;
|
private int localStationCount;
|
private String scopeType;
|
|
private LoopIdentity(String loopFingerprint,
|
Set<Integer> stationIdSet,
|
int sourceLoopStationCount,
|
int localStationCount,
|
String scopeType) {
|
this.loopFingerprint = loopFingerprint;
|
this.stationIdSet = stationIdSet == null ? new HashSet<>() : stationIdSet;
|
this.sourceLoopStationCount = sourceLoopStationCount;
|
this.localStationCount = localStationCount;
|
this.scopeType = scopeType == null ? "" : scopeType;
|
}
|
|
private static LoopIdentity empty() {
|
return new LoopIdentity("", new HashSet<>(), 0, 0, "none");
|
}
|
}
|
|
@Data
|
private static class StationHopNode {
|
private final Integer stationId;
|
private final int hop;
|
}
|
}
|