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(); 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(); var repairBuildLocks = new ConcurrentDictionary(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, }); } }