206 lines
8.5 KiB
C#
206 lines
8.5 KiB
C#
using System.Collections.Concurrent;
|
|
using System.Diagnostics;
|
|
using System.Globalization;
|
|
|
|
namespace StellaOps.ElkSharp;
|
|
|
|
internal static partial class ElkEdgeRouterIterative
|
|
{
|
|
private static RouteAllEdgesResult RepairPenalizedEdges(
|
|
ElkRoutedEdge[] existingEdges,
|
|
ElkPositionedNode[] nodes,
|
|
double baseObstacleMargin,
|
|
RoutingStrategy strategy,
|
|
RepairPlan repairPlan,
|
|
CancellationToken cancellationToken,
|
|
int maxParallelRepairBuilds,
|
|
bool trustIndependentParallelBuilds = false)
|
|
{
|
|
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;
|
|
var repairSet = repairPlan.EdgeIndices.ToHashSet();
|
|
var routeRepairEdgeIdSet = repairPlan.RouteRepairEdgeIds.ToHashSet(StringComparer.Ordinal);
|
|
var collectorRepairSet = repairPlan.Reasons.Contains("collector-corridors", StringComparer.Ordinal)
|
|
? repairSet
|
|
.Where(edgeIndex => edgeIndex >= 0
|
|
&& edgeIndex < existingEdges.Length
|
|
&& ElkEdgePostProcessor.IsRepeatCollectorLabel(existingEdges[edgeIndex].Label))
|
|
.Where(edgeIndex => !routeRepairEdgeIdSet.Contains(existingEdges[edgeIndex].Id))
|
|
.ToHashSet()
|
|
: [];
|
|
var preferredShortestEdgeIdSet = repairPlan.PreferredShortestEdgeIds.ToHashSet(StringComparer.Ordinal);
|
|
if (collectorRepairSet.Count > 0)
|
|
{
|
|
var collectorEdgeIds = collectorRepairSet
|
|
.Select(edgeIndex => existingEdges[edgeIndex].Id)
|
|
.ToArray();
|
|
routedEdges = ElkRepeatCollectorCorridors.SeparateSharedLanes(routedEdges, nodes, collectorEdgeIds);
|
|
routedEdgeCount += collectorRepairSet.Count;
|
|
}
|
|
|
|
var aStarRepairSet = repairSet
|
|
.Where(edgeIndex => !collectorRepairSet.Contains(edgeIndex))
|
|
.ToHashSet();
|
|
var spreadEndpoints = SpreadTargetEndpoints(existingEdges, nodesById, graphMinY, graphMaxY, strategy.MinLineClearance);
|
|
var softObstacles = new List<OrthogonalSoftObstacle>();
|
|
|
|
for (var edgeIndex = 0; edgeIndex < existingEdges.Length; edgeIndex++)
|
|
{
|
|
if (aStarRepairSet.Contains(edgeIndex))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
foreach (var segment in ElkEdgeRoutingGeometry.FlattenSegments(routedEdges[edgeIndex]))
|
|
{
|
|
softObstacles.Add(new OrthogonalSoftObstacle(segment.Start, segment.End));
|
|
}
|
|
}
|
|
|
|
var orderedRepairIndices = strategy.EdgeOrder
|
|
.Where(aStarRepairSet.Contains)
|
|
.Concat(aStarRepairSet.Where(edgeIndex => !strategy.EdgeOrder.Contains(edgeIndex)))
|
|
.Distinct()
|
|
.ToArray();
|
|
var repairBuilderParallelism = (trustIndependentParallelBuilds || CanParallelizeRepairBuilds(orderedRepairIndices, existingEdges))
|
|
? DetermineRepairBuildParallelism(orderedRepairIndices.Length, maxParallelRepairBuilds)
|
|
: 1;
|
|
var builtRepairResults = new ConcurrentDictionary<int, RepairEdgeBuildResult>();
|
|
var repairBuildLocks = new ConcurrentDictionary<string, object>(StringComparer.Ordinal);
|
|
if (repairBuilderParallelism > 1 && orderedRepairIndices.Length > 1)
|
|
{
|
|
var immutableSoftObstacles = softObstacles.ToArray();
|
|
var parallelOptions = new ParallelOptions
|
|
{
|
|
CancellationToken = cancellationToken,
|
|
MaxDegreeOfParallelism = repairBuilderParallelism,
|
|
};
|
|
|
|
Parallel.ForEach(
|
|
orderedRepairIndices,
|
|
parallelOptions,
|
|
edgeIndex =>
|
|
{
|
|
if (edgeIndex < 0 || edgeIndex >= existingEdges.Length)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var edge = existingEdges[edgeIndex];
|
|
var lockKeys = trustIndependentParallelBuilds
|
|
? []
|
|
: GetRepairBuildLockKeys(edge);
|
|
ExecuteWithRepairBuildLocks(
|
|
repairBuildLocks,
|
|
lockKeys,
|
|
() =>
|
|
{
|
|
builtRepairResults[edgeIndex] = BuildRepairEdgeResult(
|
|
edgeIndex,
|
|
existingEdges,
|
|
nodes,
|
|
obstacles,
|
|
spreadEndpoints,
|
|
nodesById,
|
|
immutableSoftObstacles,
|
|
routeRepairEdgeIdSet,
|
|
preferredShortestEdgeIdSet,
|
|
repairPlan.Reasons,
|
|
graphMinY,
|
|
graphMaxY,
|
|
strategy,
|
|
cancellationToken);
|
|
});
|
|
});
|
|
}
|
|
|
|
foreach (var edgeIndex in orderedRepairIndices)
|
|
{
|
|
cancellationToken.ThrowIfCancellationRequested();
|
|
|
|
if (edgeIndex < 0 || edgeIndex >= existingEdges.Length)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
var buildResult = builtRepairResults.TryGetValue(edgeIndex, out var parallelBuildResult)
|
|
? parallelBuildResult
|
|
: BuildRepairEdgeResult(
|
|
edgeIndex,
|
|
existingEdges,
|
|
nodes,
|
|
obstacles,
|
|
spreadEndpoints,
|
|
nodesById,
|
|
softObstacles,
|
|
routeRepairEdgeIdSet,
|
|
preferredShortestEdgeIdSet,
|
|
repairPlan.Reasons,
|
|
graphMinY,
|
|
graphMaxY,
|
|
strategy,
|
|
cancellationToken);
|
|
if (buildResult.WasSkipped)
|
|
{
|
|
skippedEdgeCount++;
|
|
foreach (var segment in ElkEdgeRoutingGeometry.FlattenSegments(buildResult.Edge))
|
|
{
|
|
softObstacles.Add(new OrthogonalSoftObstacle(segment.Start, segment.End));
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
routedSectionCount += buildResult.RoutedSections;
|
|
fallbackSectionCount += buildResult.FallbackSections;
|
|
routedEdgeCount++;
|
|
routedEdges[edgeIndex] = buildResult.Edge;
|
|
|
|
foreach (var segment in ElkEdgeRoutingGeometry.FlattenSegments(routedEdges[edgeIndex]))
|
|
{
|
|
softObstacles.Add(new OrthogonalSoftObstacle(segment.Start, segment.End));
|
|
}
|
|
}
|
|
|
|
var repeatRouteRepairIds = repairPlan.RouteRepairEdgeIds
|
|
.Where(edgeId => routedEdges.Any(edge =>
|
|
string.Equals(edge.Id, edgeId, StringComparison.Ordinal)
|
|
&& ElkEdgePostProcessor.IsRepeatCollectorLabel(edge.Label)))
|
|
.ToArray();
|
|
if (repeatRouteRepairIds.Length > 0)
|
|
{
|
|
routedEdges = ElkRepeatCollectorCorridors.SeparateSharedLanes(routedEdges, nodes, repeatRouteRepairIds);
|
|
}
|
|
|
|
return new RouteAllEdgesResult(
|
|
routedEdges,
|
|
new ElkIterativeRouteDiagnostics
|
|
{
|
|
Mode = "local-repair",
|
|
TotalEdges = existingEdges.Length,
|
|
RoutedEdges = routedEdgeCount,
|
|
SkippedEdges = skippedEdgeCount,
|
|
RoutedSections = routedSectionCount,
|
|
FallbackSections = fallbackSectionCount,
|
|
SoftObstacleSegments = softObstacles.Count,
|
|
RepairedEdgeIds = repairPlan.EdgeIds,
|
|
RepairReasons = repairPlan.Reasons,
|
|
BuilderMode = repairBuilderParallelism > 1 ? "parallel-locked-local-build" : "sequential-local-build",
|
|
BuilderParallelism = repairBuilderParallelism,
|
|
});
|
|
}
|
|
|
|
}
|