using System.Collections.Concurrent; using System.Diagnostics; using System.Globalization; namespace StellaOps.ElkSharp; internal static partial class ElkEdgeRouterIterative { internal static ElkRoutedEdge[] BuildFinalBoundarySlotCandidate( ElkRoutedEdge[] edges, ElkPositionedNode[] nodes, ElkLayoutDirection direction, double minLineClearance, IReadOnlyCollection? restrictedEdgeIds = null, bool allowLateRestabilizedClosure = true) { var focusEdgeIds = restrictedEdgeIds?.Count > 0 ? restrictedEdgeIds : edges .Select(edge => edge.Id) .OrderBy(edgeId => edgeId, StringComparer.Ordinal) .ToArray(); var useLeanRestrictedBoundarySlotPass = restrictedEdgeIds?.Count > 0 && (!allowLateRestabilizedClosure || restrictedEdgeIds.Count <= 2); var useUltraLeanRestrictedBoundarySlotPass = restrictedEdgeIds?.Count > 0 && restrictedEdgeIds.Count <= MaxWinnerPolishBatchedRootEdges + 1; ElkLayoutDiagnostics.LogProgress( $"Boundary-slot candidate start: focus={focusEdgeIds.Count} allowLateRestabilizedClosure={allowLateRestabilizedClosure}"); var best = edges; var candidate = ElkEdgePostProcessor.SnapBoundarySlotAssignments( edges, nodes, minLineClearance, restrictedEdgeIds, enforceAllNodeEndpoints: true); best = ChoosePreferredBoundarySlotRepairLayout(best, candidate, nodes); ElkLayoutDiagnostics.LogProgress("Boundary-slot candidate after initial snap"); var terminalClosureCandidate = useLeanRestrictedBoundarySlotPass ? ApplyHybridTerminalRuleCleanupRound( candidate, nodes, direction, minLineClearance, restrictedEdgeIds) : CloseRemainingTerminalViolations( candidate, nodes, direction, minLineClearance, restrictedEdgeIds); candidate = ChoosePreferredBoundarySlotRepairLayout(candidate, terminalClosureCandidate, nodes); best = ChoosePreferredBoundarySlotRepairLayout(best, candidate, nodes); ElkLayoutDiagnostics.LogProgress("Boundary-slot candidate after terminal closure"); if (focusEdgeIds.Count > 0) { candidate = ElkEdgePostProcessor.SeparateMixedNodeFaceLaneConflicts( candidate, nodes, minLineClearance, focusEdgeIds); candidate = ElkEdgePostProcessor.SeparateSharedLaneConflicts( candidate, nodes, minLineClearance, focusEdgeIds); best = ChoosePreferredBoundarySlotRepairLayout(best, candidate, nodes); ElkLayoutDiagnostics.LogProgress("Boundary-slot candidate after face/shared-lane separation"); } candidate = ClampBelowGraphEdges(candidate, nodes, restrictedEdgeIds); candidate = ElkEdgePostProcessor.NormalizeBoundaryAngles(candidate, nodes); candidate = ElkEdgePostProcessor.NormalizeSourceExitAngles(candidate, nodes); candidate = ElkEdgePostProcessor.SnapBoundarySlotAssignments( candidate, nodes, minLineClearance, restrictedEdgeIds, enforceAllNodeEndpoints: true); best = ChoosePreferredBoundarySlotRepairLayout(best, candidate, nodes); ElkLayoutDiagnostics.LogProgress("Boundary-slot candidate after normalization snap"); if (useUltraLeanRestrictedBoundarySlotPass) { ElkLayoutDiagnostics.LogProgress("Boundary-slot candidate complete (ultra-lean restricted path)"); return ChoosePreferredBoundarySlotRepairLayout(best, candidate, nodes); } candidate = ApplyPostSlotDetourClosure(candidate, nodes, minLineClearance, restrictedEdgeIds); best = ChoosePreferredBoundarySlotRepairLayout(best, candidate, nodes); ElkLayoutDiagnostics.LogProgress("Boundary-slot candidate after detour closure"); candidate = ElkEdgePostProcessor.SnapBoundarySlotAssignments( candidate, nodes, minLineClearance, restrictedEdgeIds, enforceAllNodeEndpoints: true); best = ChoosePreferredBoundarySlotRepairLayout(best, candidate, nodes); ElkLayoutDiagnostics.LogProgress("Boundary-slot candidate after detour snap"); if (useLeanRestrictedBoundarySlotPass) { ElkLayoutDiagnostics.LogProgress("Boundary-slot candidate complete (lean restricted path)"); return ChoosePreferredBoundarySlotRepairLayout(best, candidate, nodes); } if (restrictedEdgeIds?.Count > 0) { candidate = ChoosePreferredBoundarySlotRepairLayout( candidate, CloseRemainingTerminalViolations(candidate, nodes, direction, minLineClearance, restrictedEdgeIds), nodes); best = ChoosePreferredBoundarySlotRepairLayout(best, candidate, nodes); candidate = ElkEdgePostProcessor.SnapBoundarySlotAssignments( candidate, nodes, minLineClearance, restrictedEdgeIds, enforceAllNodeEndpoints: true); best = ChoosePreferredBoundarySlotRepairLayout(best, candidate, nodes); ElkLayoutDiagnostics.LogProgress("Boundary-slot candidate after restricted terminal recheck"); } candidate = ApplyLateBoundarySlotRestabilization( candidate, nodes, minLineClearance, focusEdgeIds); best = ChoosePreferredBoundarySlotRepairLayout(best, candidate, nodes); ElkLayoutDiagnostics.LogProgress("Boundary-slot candidate after late restabilization"); candidate = ChoosePreferredBoundarySlotRepairLayout( candidate, ElkEdgePostProcessor.SnapBoundarySlotAssignments( candidate, nodes, minLineClearance, restrictedEdgeIds, enforceAllNodeEndpoints: true), nodes); best = ChoosePreferredBoundarySlotRepairLayout(best, candidate, nodes); ElkLayoutDiagnostics.LogProgress("Boundary-slot candidate after late restabilization snap"); if (focusEdgeIds.Count > 0) { var lateHardRuleCandidate = ApplyAggressiveSharedLaneClosure( candidate, nodes, direction, minLineClearance, focusEdgeIds); lateHardRuleCandidate = ElkEdgePostProcessor.SnapBoundarySlotAssignments( lateHardRuleCandidate, nodes, minLineClearance, restrictedEdgeIds, enforceAllNodeEndpoints: true); candidate = ChoosePreferredHardRuleLayout(candidate, lateHardRuleCandidate, nodes); candidate = ChoosePreferredBoundarySlotRepairLayout( candidate, ElkEdgePostProcessor.SnapBoundarySlotAssignments( candidate, nodes, minLineClearance, restrictedEdgeIds, enforceAllNodeEndpoints: true), nodes); best = ChoosePreferredBoundarySlotRepairLayout(best, candidate, nodes); ElkLayoutDiagnostics.LogProgress("Boundary-slot candidate after late hard-rule closure"); } if (focusEdgeIds.Count > 0 && (ElkEdgeRoutingScoring.CountBoundarySlotViolations(candidate, nodes) > 0 || ElkEdgeRoutingScoring.CountGatewaySourceExitViolations(candidate, nodes) > 0)) { var lateClosureCandidate = ApplyLateFocusedBoundarySlotClosure( candidate, nodes, direction, minLineClearance, focusEdgeIds, restrictedEdgeIds); candidate = ChoosePreferredBoundarySlotRepairLayout(candidate, lateClosureCandidate, nodes); candidate = ChoosePreferredBoundarySlotRepairLayout( candidate, ElkEdgePostProcessor.SnapBoundarySlotAssignments( candidate, nodes, minLineClearance, restrictedEdgeIds, enforceAllNodeEndpoints: true), nodes); best = ChoosePreferredBoundarySlotRepairLayout(best, candidate, nodes); ElkLayoutDiagnostics.LogProgress("Boundary-slot candidate after focused late closure"); } if (allowLateRestabilizedClosure && focusEdgeIds.Count > 0 && focusEdgeIds.Count <= MaxLateRestabilizedClosureFocusEdges) { var stagedLateRestabilizedCandidate = ElkEdgePostProcessor.SnapBoundarySlotAssignments( edges, nodes, minLineClearance, restrictedEdgeIds, enforceAllNodeEndpoints: true); stagedLateRestabilizedCandidate = BuildFinalRestabilizedCandidate( stagedLateRestabilizedCandidate, nodes, direction, minLineClearance, restrictedEdgeIds); stagedLateRestabilizedCandidate = ChoosePreferredBoundarySlotRepairLayout( stagedLateRestabilizedCandidate, ElkEdgePostProcessor.SnapBoundarySlotAssignments( stagedLateRestabilizedCandidate, nodes, minLineClearance, restrictedEdgeIds, enforceAllNodeEndpoints: true), nodes); candidate = ChoosePreferredBoundarySlotRepairLayout(candidate, stagedLateRestabilizedCandidate, nodes); best = ChoosePreferredBoundarySlotRepairLayout(best, candidate, nodes); ElkLayoutDiagnostics.LogProgress("Boundary-slot candidate after staged late restabilized closure"); } ElkLayoutDiagnostics.LogProgress("Boundary-slot candidate complete"); return ChoosePreferredBoundarySlotRepairLayout(best, candidate, nodes); } }