ElkSharp: gateway face overflow redirect, under-node push-first routing, boundary-slot snap

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
master
2026-04-01 10:35:23 +03:00
parent 5af14cf212
commit f275b8a267
30 changed files with 5632 additions and 2647 deletions

View File

@@ -5,6 +5,7 @@ internal static partial class ElkEdgeRouterIterative
private static ElkRoutedEdge[] ApplyFinalDetourPolish(
ElkRoutedEdge[] edges,
ElkPositionedNode[] nodes,
ElkLayoutDirection direction,
double minLineClearance,
IReadOnlyCollection<string>? restrictedEdgeIds)
{
@@ -40,38 +41,94 @@ internal static partial class ElkEdgeRouterIterative
continue;
}
var focused = (IReadOnlyCollection<string>)[edgeId];
var candidateEdges = ComposeTransactionalFinalDetourCandidate(
result,
nodes,
minLineClearance,
focused);
candidateEdges = ChoosePreferredHardRuleLayout(result, candidateEdges, nodes);
if (ReferenceEquals(candidateEdges, result))
var directFocus = (IReadOnlyCollection<string>)[edgeId];
var expandedFocus = ExpandWinningSolutionFocus(result, [edgeId])
.Where(id => restrictedSet is null || restrictedSet.Contains(id))
.OrderBy(id => id, StringComparer.Ordinal)
.ToArray();
if (expandedFocus.Length == 0)
{
expandedFocus = [edgeId];
}
var bestCandidateEdges = result;
var bestCandidateScore = currentScore;
var bestCandidateRetryState = currentRetryState;
void ConsiderDetourCandidate(ElkRoutedEdge[] candidate)
{
candidate = ChoosePreferredHardRuleLayout(result, candidate, nodes);
if (ReferenceEquals(candidate, result))
{
return;
}
var candidateScore = ElkEdgeRoutingScoring.ComputeScore(candidate, nodes);
var candidateRetryState = BuildRetryState(
candidateScore,
HighwayProcessingEnabled
? ElkEdgeRouterHighway.DetectRemainingBrokenHighways(candidate, nodes).Count
: 0);
var improvedDetours = candidateRetryState.ExcessiveDetourViolations < currentRetryState.ExcessiveDetourViolations;
if (HasHardRuleRegression(candidateRetryState, currentRetryState)
|| (!improvedDetours
&& !IsBetterBoundarySlotRepairCandidate(
candidateScore,
candidateRetryState,
currentScore,
currentRetryState)))
{
return;
}
if (ReferenceEquals(bestCandidateEdges, result)
|| IsBetterCandidate(candidateScore, candidateRetryState, bestCandidateScore, bestCandidateRetryState))
{
bestCandidateEdges = candidate;
bestCandidateScore = candidateScore;
bestCandidateRetryState = candidateRetryState;
}
}
ConsiderDetourCandidate(
ComposeDirectionalTransactionalFinalDetourCandidate(
result,
nodes,
direction,
minLineClearance,
directFocus));
if (expandedFocus.Length != 1
|| !string.Equals(expandedFocus[0], edgeId, StringComparison.Ordinal))
{
ConsiderDetourCandidate(
ComposeDirectionalTransactionalFinalDetourCandidate(
result,
nodes,
direction,
minLineClearance,
expandedFocus));
}
var focusedSeed = ElkEdgePostProcessor.PreferShortestBoundaryShortcuts(result, nodes, directFocus);
if (!ReferenceEquals(focusedSeed, result))
{
ConsiderDetourCandidate(
BuildFinalRestabilizedCandidate(
focusedSeed,
nodes,
direction,
minLineClearance,
expandedFocus));
}
if (ReferenceEquals(bestCandidateEdges, result))
{
continue;
}
var candidateScore = ElkEdgeRoutingScoring.ComputeScore(candidateEdges, nodes);
var candidateRetryState = BuildRetryState(
candidateScore,
HighwayProcessingEnabled
? ElkEdgeRouterHighway.DetectRemainingBrokenHighways(candidateEdges, nodes).Count
: 0);
var improvedDetours = candidateRetryState.ExcessiveDetourViolations < currentRetryState.ExcessiveDetourViolations;
if (HasHardRuleRegression(candidateRetryState, currentRetryState)
|| (!improvedDetours
&& !IsBetterBoundarySlotRepairCandidate(
candidateScore,
candidateRetryState,
currentScore,
currentRetryState)))
{
continue;
}
result = candidateEdges;
result = bestCandidateEdges;
improved = true;
break;
}
@@ -128,6 +185,21 @@ internal static partial class ElkEdgeRouterIterative
ElkPositionedNode[] nodes,
double minLineClearance,
IReadOnlyCollection<string> focusedEdgeIds)
{
return ComposeDirectionalTransactionalFinalDetourCandidate(
baseline,
nodes,
ElkLayoutDirection.LeftToRight,
minLineClearance,
focusedEdgeIds);
}
private static ElkRoutedEdge[] ComposeDirectionalTransactionalFinalDetourCandidate(
ElkRoutedEdge[] baseline,
ElkPositionedNode[] nodes,
ElkLayoutDirection direction,
double minLineClearance,
IReadOnlyCollection<string> focusedEdgeIds)
{
var candidate = ElkEdgePostProcessor.PreferShortestBoundaryShortcuts(baseline, nodes, focusedEdgeIds);
if (ReferenceEquals(candidate, baseline))
@@ -163,7 +235,12 @@ internal static partial class ElkEdgeRouterIterative
focusedEdgeIds,
enforceAllNodeEndpoints: true);
candidate = ApplyPostSlotDetourClosure(candidate, nodes, minLineClearance, focusedEdgeIds);
return candidate;
return BuildFinalRestabilizedCandidate(
candidate,
nodes,
direction,
minLineClearance,
focusedEdgeIds);
}
internal static ElkRoutedEdge[] ApplyPostSlotDetourClosure(