Refactor ElkSharp hybrid routing and document speed path

This commit is contained in:
master
2026-03-29 19:33:46 +03:00
parent 7d6bc2b0ab
commit e8f7ad7652
89 changed files with 13280 additions and 10732 deletions

View File

@@ -0,0 +1,134 @@
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Globalization;
namespace StellaOps.ElkSharp;
internal static partial class ElkEdgeRouterIterative
{
private static RouteAllEdgesResult? RouteAllEdges(
ElkRoutedEdge[] existingEdges,
ElkPositionedNode[] nodes,
double baseObstacleMargin,
RoutingStrategy strategy,
CancellationToken cancellationToken)
{
var routedEdges = new ElkRoutedEdge[existingEdges.Length];
Array.Copy(existingEdges, routedEdges, existingEdges.Length);
var obstacleMargin = Math.Max(
baseObstacleMargin,
Math.Max(strategy.MinLineClearance + 4d, strategy.RoutingParams.Margin));
var obstacles = BuildObstacles(nodes, obstacleMargin);
var graphMinY = nodes.Length > 0 ? nodes.Min(n => n.Y) : 0d;
var graphMaxY = nodes.Length > 0 ? nodes.Max(n => n.Y + n.Height) : 0d;
var nodesById = nodes.ToDictionary(n => n.Id, StringComparer.Ordinal);
var routedEdgeCount = 0;
var skippedEdgeCount = 0;
var routedSectionCount = 0;
var fallbackSectionCount = 0;
// Spread endpoints: distribute edges arriving at the same target side
var spreadEndpoints = SpreadTargetEndpoints(existingEdges, nodesById, graphMinY, graphMaxY, strategy.MinLineClearance);
var softObstacles = new List<OrthogonalSoftObstacle>();
foreach (var edgeIndex in strategy.EdgeOrder)
{
if (edgeIndex < 0 || edgeIndex >= existingEdges.Length)
{
continue;
}
cancellationToken.ThrowIfCancellationRequested();
var edge = existingEdges[edgeIndex];
// Skip edges that need special routing (backward, ports, corridors, collectors)
if (!CanRepairEdgeLocally(edge, nodes, graphMinY, graphMaxY))
{
skippedEdgeCount++;
foreach (var segment in ElkEdgeRoutingGeometry.FlattenSegments(edge))
{
softObstacles.Add(new OrthogonalSoftObstacle(segment.Start, segment.End));
}
continue;
}
var newSections = new List<ElkEdgeSection>(edge.Sections.Count);
foreach (var section in edge.Sections)
{
var endPoint = spreadEndpoints.TryGetValue(edge.Id, out var spread)
? spread
: section.EndPoint;
var (startPoint, adjustedEndPoint) = ResolveRoutingEndpoints(
section.StartPoint,
endPoint,
edge.SourceNodeId,
edge.TargetNodeId,
nodesById);
var rerouted = ElkEdgeRouterAStar8Dir.Route(
startPoint,
adjustedEndPoint,
obstacles,
edge.SourceNodeId ?? "",
edge.TargetNodeId ?? "",
strategy.RoutingParams,
softObstacles,
cancellationToken);
if (rerouted is not null && rerouted.Count >= 2)
{
routedSectionCount++;
newSections.Add(new ElkEdgeSection
{
StartPoint = rerouted[0],
EndPoint = rerouted[^1],
BendPoints = rerouted.Skip(1).Take(rerouted.Count - 2).ToArray(),
});
}
else
{
fallbackSectionCount++;
newSections.Add(section);
}
}
routedEdgeCount++;
routedEdges[edgeIndex] = new ElkRoutedEdge
{
Id = edge.Id,
SourceNodeId = edge.SourceNodeId,
TargetNodeId = edge.TargetNodeId,
SourcePortId = edge.SourcePortId,
TargetPortId = edge.TargetPortId,
Kind = edge.Kind,
Label = edge.Label,
Sections = newSections,
};
foreach (var segment in ElkEdgeRoutingGeometry.FlattenSegments(routedEdges[edgeIndex]))
{
softObstacles.Add(new OrthogonalSoftObstacle(segment.Start, segment.End));
}
}
return new RouteAllEdgesResult(
routedEdges,
new ElkIterativeRouteDiagnostics
{
Mode = "full-strategy",
TotalEdges = existingEdges.Length,
RoutedEdges = routedEdgeCount,
SkippedEdges = skippedEdgeCount,
RoutedSections = routedSectionCount,
FallbackSections = fallbackSectionCount,
SoftObstacleSegments = softObstacles.Count,
});
}
}