Traffic Frights

- Fixed Trains ignoring their signal when nearby graph nodes are changed
- Fixed Trains not being notified of new signal blocks if they share an edge with the added/removed signal
- Fixed Navigation reserving chained signal blocks before verifying that it can be fully traversed
- Fixed Track placement requiring much shallower slopes for diagonal tracks
- Fixed long range navigation choosing detours near the beginning
- Temporarily patched and highlighted a problem with track traversal and long distances
This commit is contained in:
simibubi 2022-03-17 23:14:53 +01:00
parent a9a37b313c
commit 6dd3231b6d
9 changed files with 67 additions and 48 deletions

View file

@ -176,9 +176,9 @@ public class GlobalRailwayManager {
for (TrackGraph graph : trackNetworks.values()) for (TrackGraph graph : trackNetworks.values())
graph.tickPoints(false); graph.tickPoints(false);
// if (AllKeys.isKeyDown(GLFW.GLFW_KEY_K)) if (AllKeys.isKeyDown(GLFW.GLFW_KEY_K))
// trackNetworks.values() trackNetworks.values()
// .forEach(TrackGraph::debugViewReserved); .forEach(TrackGraph::debugViewReserved);
// if (AllKeys.isKeyDown(GLFW.GLFW_KEY_J) && AllKeys.altDown()) // if (AllKeys.isKeyDown(GLFW.GLFW_KEY_J) && AllKeys.altDown())
// trackNetworks.values() // trackNetworks.values()
// .forEach(TrackGraph::debugViewNodes); // .forEach(TrackGraph::debugViewNodes);

View file

@ -44,8 +44,9 @@ public class TrackEdge {
} }
public double incrementT(TrackNode node1, TrackNode node2, double currentT, double distance) { public double incrementT(TrackNode node1, TrackNode node2, double currentT, double distance) {
boolean tooFar = Math.abs(distance) > 5;
distance = distance / getLength(node1, node2); distance = distance / getLength(node1, node2);
return isTurn() ? turn.incrementT(currentT, distance) : currentT + distance; return !tooFar && isTurn() ? turn.incrementT(currentT, distance) : currentT + distance;
} }
public Vec3 getPosition(TrackNode node1, TrackNode node2, double t) { public Vec3 getPosition(TrackNode node1, TrackNode node2, double t) {

View file

@ -1,6 +1,7 @@
package com.simibubi.create.content.logistics.trains.entity; package com.simibubi.create.content.logistics.trains.entity;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.IdentityHashMap; import java.util.IdentityHashMap;
import java.util.List; import java.util.List;
@ -39,6 +40,7 @@ import net.minecraft.core.BlockPos;
import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.NbtUtils; import net.minecraft.nbt.NbtUtils;
import net.minecraft.nbt.Tag; import net.minecraft.nbt.Tag;
import net.minecraft.util.Mth;
import net.minecraft.world.level.Level; import net.minecraft.world.level.Level;
import net.minecraft.world.phys.Vec3; import net.minecraft.world.phys.Vec3;
@ -53,7 +55,7 @@ public class Navigation {
private TravellingPoint signalScout; private TravellingPoint signalScout;
public Pair<UUID, Boolean> waitingForSignal; public Pair<UUID, Boolean> waitingForSignal;
private Set<UUID> waitingForChainedGroups; private Map<UUID, SignalBoundary> waitingForChainedGroups;
public double distanceToSignal; public double distanceToSignal;
public int ticksWaitingForSignal; public int ticksWaitingForSignal;
@ -61,7 +63,7 @@ public class Navigation {
this.train = train; this.train = train;
currentPath = new ArrayList<>(); currentPath = new ArrayList<>();
signalScout = new TravellingPoint(); signalScout = new TravellingPoint();
waitingForChainedGroups = new HashSet<>(); waitingForChainedGroups = new HashMap<>();
} }
public void tick(Level level) { public void tick(Level level) {
@ -101,11 +103,11 @@ public class Navigation {
// Signals // Signals
if (train.graph != null) { if (train.graph != null) {
if (waitingForSignal != null && currentSignalResolved()) { if (waitingForSignal != null && currentSignalResolved()) {
SignalBoundary signal = train.graph.getPoint(EdgePointType.SIGNAL, waitingForSignal.getFirst()); UUID signalId = waitingForSignal.getFirst();
if (signal.types.get(waitingForSignal.getSecond()) == SignalType.CROSS_SIGNAL) SignalBoundary signal = train.graph.getPoint(EdgePointType.SIGNAL, signalId);
train.reservedSignalBlocks.addAll(waitingForChainedGroups); if (signal != null && signal.types.get(waitingForSignal.getSecond()) == SignalType.CROSS_SIGNAL)
waitingForChainedGroups.clear();
waitingForSignal = null; waitingForSignal = null;
waitingForChainedGroups.clear();
} }
TravellingPoint leadingPoint = !destinationBehindTrain ? train.carriages.get(0) TravellingPoint leadingPoint = !destinationBehindTrain ? train.carriages.get(0)
@ -126,16 +128,15 @@ public class Navigation {
signalScout.edge = leadingPoint.edge; signalScout.edge = leadingPoint.edge;
signalScout.position = leadingPoint.position; signalScout.position = leadingPoint.position;
double brakingDistanceNoFlicker = double brakingDistanceNoFlicker = brakingDistance + 3 - (brakingDistance % 3);
Math.max(preDepartureLookAhead, brakingDistance + 3 - (brakingDistance % 3)); double scanDistance = Mth.clamp(brakingDistanceNoFlicker, preDepartureLookAhead, distanceToDestination);
double scanDistance = Math.min(distanceToDestination, brakingDistanceNoFlicker);
MutableDouble crossSignalDistanceTracker = new MutableDouble(-1); MutableDouble crossSignalDistanceTracker = new MutableDouble(-1);
MutableObject<Pair<UUID, Boolean>> trackingCrossSignal = new MutableObject<>(null); MutableObject<Pair<UUID, Boolean>> trackingCrossSignal = new MutableObject<>(null);
waitingForChainedGroups.clear(); waitingForChainedGroups.clear();
// train.reservedSignalBlocks.clear();
signalScout.travel(train.graph, distanceToDestination * speedMod, controlSignalScout(), // Adding 50 to the distance due to unresolved inaccuracies in TravellingPoint::travel
signalScout.travel(train.graph, (distanceToDestination + 50) * speedMod, controlSignalScout(),
(distance, couple) -> { (distance, couple) -> {
// > scanDistance and not following down a cross signal // > scanDistance and not following down a cross signal
boolean crossSignalTracked = trackingCrossSignal.getValue() != null; boolean crossSignalTracked = trackingCrossSignal.getValue() != null;
@ -158,15 +159,11 @@ public class Navigation {
boolean crossSignal = signal.types.get(primary) == SignalType.CROSS_SIGNAL; boolean crossSignal = signal.types.get(primary) == SignalType.CROSS_SIGNAL;
boolean occupied = signalEdgeGroup.isOccupiedUnless(train); boolean occupied = signalEdgeGroup.isOccupiedUnless(train);
if (!occupied && distance < distanceToSignal + .25)
signalEdgeGroup.reserved = signal; // Reserve group for traversal, unless waiting at an
// earlier cross signal
if (!crossSignalTracked) { if (!crossSignalTracked) {
if (crossSignal) { // Now entering cross signal path if (crossSignal) { // Now entering cross signal path
trackingCrossSignal.setValue(Pair.of(boundary.id, primary)); trackingCrossSignal.setValue(Pair.of(boundary.id, primary));
crossSignalDistanceTracker.setValue(distance); crossSignalDistanceTracker.setValue(distance);
waitingForChainedGroups.add(entering); waitingForChainedGroups.put(entering, signal);
} }
if (occupied) { // Section is occupied if (occupied) { // Section is occupied
waitingForSignal = Pair.of(boundary.id, primary); waitingForSignal = Pair.of(boundary.id, primary);
@ -174,8 +171,14 @@ public class Navigation {
if (!crossSignal) if (!crossSignal)
return true; // Standard entry signal, do not collect any further segments return true; // Standard entry signal, do not collect any further segments
} }
} else { if (!occupied && !crossSignal && distance < distanceToSignal + .25
waitingForChainedGroups.add(entering); // Add group to chain && distance < brakingDistanceNoFlicker)
signalEdgeGroup.reserved = signal; // Reserve group for traversal
return false;
}
if (crossSignalTracked) {
waitingForChainedGroups.put(entering, signal); // Add group to chain
if (occupied) { // Section is occupied, but wait at the cross signal that started the chain if (occupied) { // Section is occupied, but wait at the cross signal that started the chain
waitingForSignal = trackingCrossSignal.getValue(); waitingForSignal = trackingCrossSignal.getValue();
distanceToSignal = crossSignalDistanceTracker.doubleValue(); distanceToSignal = crossSignalDistanceTracker.doubleValue();
@ -186,8 +189,7 @@ public class Navigation {
if (distance < distanceToSignal + .25) { if (distance < distanceToSignal + .25) {
// Collect and reset the signal chain because none were blocked // Collect and reset the signal chain because none were blocked
trackingCrossSignal.setValue(null); trackingCrossSignal.setValue(null);
train.reservedSignalBlocks.addAll(waitingForChainedGroups); reserveChain();
waitingForChainedGroups.clear();
return false; return false;
} else } else
return true; // End of a blocked signal chain return true; // End of a blocked signal chain
@ -202,20 +204,13 @@ public class Navigation {
curveDistanceTracker.setValue(distance); curveDistanceTracker.setValue(distance);
}); });
if (trackingCrossSignal.getValue() != null) { if (trackingCrossSignal.getValue() != null && waitingForSignal == null)
if (waitingForSignal == null) { reserveChain();
train.reservedSignalBlocks.addAll(waitingForChainedGroups);
waitingForChainedGroups.clear();
}
}
distanceToNextCurve = curveDistanceTracker.floatValue(); distanceToNextCurve = curveDistanceTracker.floatValue();
} else { } else
ticksWaitingForSignal++; ticksWaitingForSignal++;
// if chain signal try finding new path/destination every x ticks
}
} }
double targetDistance = waitingForSignal != null ? distanceToSignal : distanceToDestination; double targetDistance = waitingForSignal != null ? distanceToSignal : distanceToDestination;
@ -265,6 +260,16 @@ public class Navigation {
train.approachTargetSpeed(1); train.approachTargetSpeed(1);
} }
private void reserveChain() {
train.reservedSignalBlocks.addAll(waitingForChainedGroups.keySet());
waitingForChainedGroups.forEach((groupId, boundary) -> {
SignalEdgeGroup signalEdgeGroup = Create.RAILWAYS.signalEdgeGroups.get(groupId);
if (signalEdgeGroup != null)
signalEdgeGroup.reserved = boundary;
});
waitingForChainedGroups.clear();
}
private boolean currentSignalResolved() { private boolean currentSignalResolved() {
if (distanceToDestination < .5f) if (distanceToDestination < .5f)
return true; return true;
@ -274,10 +279,12 @@ public class Navigation {
// Cross Signal // Cross Signal
if (signal.types.get(waitingForSignal.getSecond()) == SignalType.CROSS_SIGNAL) { if (signal.types.get(waitingForSignal.getSecond()) == SignalType.CROSS_SIGNAL) {
for (UUID groupId : waitingForChainedGroups) { for (UUID groupId : waitingForChainedGroups.keySet()) {
SignalEdgeGroup signalEdgeGroup = Create.RAILWAYS.signalEdgeGroups.get(groupId); SignalEdgeGroup signalEdgeGroup = Create.RAILWAYS.signalEdgeGroups.get(groupId);
if (signalEdgeGroup == null) if (signalEdgeGroup == null) { // Migration, re-initialize chain
continue; waitingForSignal.setFirst(null);
return true;
}
if (signalEdgeGroup.isOccupiedUnless(train)) if (signalEdgeGroup.isOccupiedUnless(train))
return false; return false;
} }
@ -629,7 +636,7 @@ public class Navigation {
TrackEdge newEdge = target.getValue(); TrackEdge newEdge = target.getValue();
double newDistance = newEdge.getLength(node2, newNode) + distance; double newDistance = newEdge.getLength(node2, newNode) + distance;
int newPenalty = penalty; int newPenalty = penalty;
reachedVia.put(newEdge, Pair.of(validTargets.size() > 1, Couple.create(node1, node2))); reachedVia.putIfAbsent(newEdge, Pair.of(validTargets.size() > 1, Couple.create(node1, node2)));
frontier.add(new FrontierEntry(newDistance, newPenalty, node2, newNode, newEdge)); frontier.add(new FrontierEntry(newDistance, newPenalty, node2, newNode, newEdge));
} }
} }

View file

@ -123,16 +123,17 @@ public class Train {
status.tick(level); status.tick(level);
if (graph == null && !migratingPoints.isEmpty()) if (graph == null && !migratingPoints.isEmpty())
reattachToTracks(level); reattachToTracks(level);
if (graph == null) {
addToSignalGroups(occupiedSignalBlocks.keySet()); addToSignalGroups(occupiedSignalBlocks.keySet());
if (graph == null)
return; return;
}
if (updateSignalBlocks) { if (updateSignalBlocks) {
updateSignalBlocks = false; updateSignalBlocks = false;
collectInitiallyOccupiedSignalBlocks(); collectInitiallyOccupiedSignalBlocks();
} }
addToSignalGroups(occupiedSignalBlocks.keySet());
addToSignalGroups(reservedSignalBlocks); addToSignalGroups(reservedSignalBlocks);
} }

View file

@ -187,6 +187,15 @@ public class TravellingPoint {
double currentT = position / edgeLength; double currentT = position / edgeLength;
double incrementT = edge.incrementT(node1, node2, currentT, distance); double incrementT = edge.incrementT(node1, node2, currentT, distance);
position = incrementT * edgeLength; position = incrementT * edgeLength;
// FIXME: using incrementT like this becomes inaccurate at medium-long distances
// travelling points would travel only 50m instead of 100m due to the low
// incrementT at their starting position (e.g. bezier turn)
// In an ideal scenario the amount added to position would iterate the traversed
// edges for context first
// A workaround was added in TrackEdge::incrementT
List<Entry<TrackNode, TrackEdge>> validTargets = new ArrayList<>(); List<Entry<TrackNode, TrackEdge>> validTargets = new ArrayList<>();
boolean forward = distance > 0; boolean forward = distance > 0;

View file

@ -42,7 +42,7 @@ public class SignalBoundary extends TrackEdgePoint {
groups = Couple.create(null, null); groups = Couple.create(null, null);
sidesToUpdate = Couple.create(true, true); sidesToUpdate = Couple.create(true, true);
types = Couple.create(() -> SignalType.ENTRY_SIGNAL); types = Couple.create(() -> SignalType.ENTRY_SIGNAL);
cachedStates = Couple.create(() -> SignalState.GREEN); cachedStates = Couple.create(() -> SignalState.INVALID);
} }
public void setGroup(TrackNode side, UUID groupId) { public void setGroup(TrackNode side, UUID groupId) {

View file

@ -115,15 +115,16 @@ public class SignalPropagator {
if (startEdge == null) if (startEdge == null)
return; return;
if (!forCollection) if (!forCollection) {
notifyTrains(graph, startEdge, oppositeEdge);
Create.RAILWAYS.sync.edgeDataChanged(graph, node1, node2, startEdge, oppositeEdge); Create.RAILWAYS.sync.edgeDataChanged(graph, node1, node2, startEdge, oppositeEdge);
}
// Check for signal on the same edge // Check for signal on the same edge
SignalBoundary immediateBoundary = startEdge.getEdgeData() SignalBoundary immediateBoundary = startEdge.getEdgeData()
.next(EdgePointType.SIGNAL, node1, node2, startEdge, signal.getLocationOn(node1, node2, startEdge)); .next(EdgePointType.SIGNAL, node1, node2, startEdge, signal.getLocationOn(node1, node2, startEdge));
if (immediateBoundary != null) { if (immediateBoundary != null) {
if (boundaryCallback.test(Pair.of(node1, immediateBoundary))) boundaryCallback.test(Pair.of(node1, immediateBoundary));
notifyTrains(graph, startEdge, oppositeEdge);
return; return;
} }

View file

@ -34,7 +34,7 @@ public class SignalRenderer extends SafeTileEntityRenderer<SignalTileEntity> {
if (signalState.isRedLight(renderTime)) if (signalState.isRedLight(renderTime))
CachedBufferer.partial(AllBlockPartials.SIGNAL_ON, blockState) CachedBufferer.partial(AllBlockPartials.SIGNAL_ON, blockState)
.renderInto(ms, buffer.getBuffer(RenderType.solid())); .renderInto(ms, buffer.getBuffer(RenderType.solid()));
else if (signalState.isGreenLight(renderTime)) else
CachedBufferer.partial(AllBlockPartials.SIGNAL_OFF, blockState) CachedBufferer.partial(AllBlockPartials.SIGNAL_OFF, blockState)
.renderInto(ms, buffer.getBuffer(RenderType.solid())); .renderInto(ms, buffer.getBuffer(RenderType.solid()));

View file

@ -247,7 +247,7 @@ public class TrackPlacement {
int hDistance = info.end1Extent; int hDistance = info.end1Extent;
if (axis1.y == 0 || !Mth.equal(absAscend + 1, dist / axis1.length())) { if (axis1.y == 0 || !Mth.equal(absAscend + 1, dist / axis1.length())) {
info.end1Extent = 0; info.end1Extent = 0;
double minHDistance = Math.max(absAscend < 4 ? absAscend * 4 : absAscend * 3, 6); double minHDistance = Math.max(absAscend < 4 ? absAscend * 4 : absAscend * 3, 6) / axis1.length();
if (hDistance < minHDistance) if (hDistance < minHDistance)
return info.withMessage("too_steep"); return info.withMessage("too_steep");
if (hDistance > minHDistance) { if (hDistance > minHDistance) {