209 lines
6.9 KiB
C#
209 lines
6.9 KiB
C#
using System;
|
|
using System.IO;
|
|
using System.Threading;
|
|
|
|
namespace StellaOps.ElkSharp;
|
|
|
|
internal sealed class ElkLayoutDiagnosticsCapture : IDisposable
|
|
{
|
|
private readonly ElkLayoutRunDiagnostics? previous;
|
|
|
|
internal ElkLayoutDiagnosticsCapture(ElkLayoutRunDiagnostics diagnostics, ElkLayoutRunDiagnostics? previous)
|
|
{
|
|
Diagnostics = diagnostics;
|
|
this.previous = previous;
|
|
}
|
|
|
|
internal ElkLayoutRunDiagnostics Diagnostics { get; }
|
|
|
|
public void Dispose()
|
|
{
|
|
ElkLayoutDiagnostics.Restore(previous);
|
|
}
|
|
}
|
|
|
|
internal sealed class ElkLayoutRunDiagnostics
|
|
{
|
|
internal object SyncRoot { get; } = new();
|
|
|
|
public EdgeRoutingScore? InitialScore { get; set; }
|
|
public EdgeRoutingScore? FinalScore { get; set; }
|
|
public int BaselineBrokenShortHighwayCount { get; set; }
|
|
public int FinalBrokenShortHighwayCount { get; set; }
|
|
public int CompletedPasses { get; set; }
|
|
public List<ElkEdgeRefinementAttemptDiagnostics> Attempts { get; } = [];
|
|
public List<ElkIterativeStrategyDiagnostics> IterativeStrategies { get; } = [];
|
|
public int SelectedStrategyIndex { get; set; } = -1;
|
|
public EdgeRoutingScore? IterativeBaselineScore { get; set; }
|
|
public List<ElkHighwayDiagnostics> DetectedHighways { get; } = [];
|
|
public List<string> ProgressLog { get; } = [];
|
|
public string? ProgressLogPath { get; set; }
|
|
}
|
|
|
|
internal sealed class ElkHighwayDiagnostics
|
|
{
|
|
public required string TargetNodeId { get; init; }
|
|
public required string SharedAxis { get; init; }
|
|
public required double SharedCoord { get; init; }
|
|
public required string[] EdgeIds { get; init; }
|
|
public required double MinRatio { get; init; }
|
|
public required bool WasBroken { get; init; }
|
|
public required string Reason { get; init; }
|
|
}
|
|
|
|
internal sealed class ElkIterativeStrategyDiagnostics
|
|
{
|
|
public required int StrategyIndex { get; init; }
|
|
public required string OrderingName { get; init; }
|
|
public int Attempts { get; set; }
|
|
public double TotalDurationMs { get; set; }
|
|
public EdgeRoutingScore? BestScore { get; set; }
|
|
public required string Outcome { get; init; }
|
|
public double BendPenalty { get; init; }
|
|
public double DiagonalPenalty { get; init; }
|
|
public double SoftObstacleWeight { get; init; }
|
|
|
|
[System.Text.Json.Serialization.JsonIgnore]
|
|
public ElkRoutedEdge[]? BestEdges { get; set; }
|
|
|
|
public List<ElkIterativeAttemptDiagnostics> AttemptDetails { get; } = [];
|
|
}
|
|
|
|
internal sealed class ElkIterativeAttemptDiagnostics
|
|
{
|
|
public required int Attempt { get; init; }
|
|
public double TotalDurationMs { get; init; }
|
|
public required EdgeRoutingScore Score { get; init; }
|
|
public required string Outcome { get; init; }
|
|
public ElkIterativeRouteDiagnostics? RouteDiagnostics { get; init; }
|
|
public List<ElkIterativePhaseDiagnostics> PhaseTimings { get; } = [];
|
|
|
|
[System.Text.Json.Serialization.JsonIgnore]
|
|
public ElkRoutedEdge[]? Edges { get; init; }
|
|
}
|
|
|
|
internal sealed class ElkIterativeRouteDiagnostics
|
|
{
|
|
public required string Mode { get; init; }
|
|
public int TotalEdges { get; init; }
|
|
public int RoutedEdges { get; init; }
|
|
public int SkippedEdges { get; init; }
|
|
public int RoutedSections { get; init; }
|
|
public int FallbackSections { get; init; }
|
|
public int SoftObstacleSegments { get; init; }
|
|
public IReadOnlyCollection<string> RepairedEdgeIds { get; init; } = [];
|
|
public IReadOnlyCollection<string> RepairReasons { get; init; } = [];
|
|
}
|
|
|
|
internal sealed class ElkIterativePhaseDiagnostics
|
|
{
|
|
public required string Phase { get; init; }
|
|
public double DurationMs { get; init; }
|
|
}
|
|
|
|
internal sealed class ElkEdgeRefinementAttemptDiagnostics
|
|
{
|
|
public required int PassIndex { get; init; }
|
|
public required string EdgeId { get; init; }
|
|
public required int Severity { get; init; }
|
|
public required EdgeRoutingScore BaselineScore { get; init; }
|
|
public int AttemptCount { get; set; }
|
|
public int? AcceptedTrialIndex { get; set; }
|
|
public EdgeRoutingScore? AcceptedScore { get; set; }
|
|
public List<ElkEdgeRefinementTrialDiagnostics> Trials { get; } = [];
|
|
}
|
|
|
|
internal sealed class ElkEdgeRefinementTrialDiagnostics
|
|
{
|
|
public required int TrialIndex { get; init; }
|
|
public required double Margin { get; init; }
|
|
public required double BendPenalty { get; init; }
|
|
public required double SoftObstacleWeight { get; init; }
|
|
public required string Outcome { get; init; }
|
|
public EdgeRoutingScore? CandidateScore { get; init; }
|
|
public bool Accepted { get; set; }
|
|
public IReadOnlyCollection<ElkDiagnosticSectionPath> Sections { get; init; } = [];
|
|
}
|
|
|
|
internal sealed class ElkDiagnosticSectionPath
|
|
{
|
|
public required ElkPoint StartPoint { get; init; }
|
|
public required IReadOnlyCollection<ElkPoint> BendPoints { get; init; }
|
|
public required ElkPoint EndPoint { get; init; }
|
|
}
|
|
|
|
internal static class ElkLayoutDiagnostics
|
|
{
|
|
private static readonly AsyncLocal<ElkLayoutRunDiagnostics?> CurrentDiagnostics = new();
|
|
|
|
internal static ElkLayoutRunDiagnostics? Current => CurrentDiagnostics.Value;
|
|
|
|
internal static ElkLayoutDiagnosticsCapture BeginCapture()
|
|
{
|
|
var previous = CurrentDiagnostics.Value;
|
|
var diagnostics = new ElkLayoutRunDiagnostics();
|
|
CurrentDiagnostics.Value = diagnostics;
|
|
return new ElkLayoutDiagnosticsCapture(diagnostics, previous);
|
|
}
|
|
|
|
internal static ElkLayoutDiagnosticsCapture Attach(ElkLayoutRunDiagnostics diagnostics)
|
|
{
|
|
var previous = CurrentDiagnostics.Value;
|
|
CurrentDiagnostics.Value = diagnostics;
|
|
return new ElkLayoutDiagnosticsCapture(diagnostics, previous);
|
|
}
|
|
|
|
internal static void Restore(ElkLayoutRunDiagnostics? previous)
|
|
{
|
|
CurrentDiagnostics.Value = previous;
|
|
}
|
|
|
|
internal static void LogProgress(string message)
|
|
{
|
|
var diagnostics = CurrentDiagnostics.Value;
|
|
if (diagnostics is null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var line = $"[{DateTime.UtcNow:O}] {message}";
|
|
lock (diagnostics.SyncRoot)
|
|
{
|
|
diagnostics.ProgressLog.Add(line);
|
|
Console.WriteLine(line);
|
|
if (!string.IsNullOrWhiteSpace(diagnostics.ProgressLogPath))
|
|
{
|
|
File.AppendAllText(diagnostics.ProgressLogPath, line + Environment.NewLine);
|
|
}
|
|
}
|
|
}
|
|
|
|
internal static void AddDetectedHighway(ElkHighwayDiagnostics diagnostic)
|
|
{
|
|
var diagnostics = CurrentDiagnostics.Value;
|
|
if (diagnostics is null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
lock (diagnostics.SyncRoot)
|
|
{
|
|
diagnostics.DetectedHighways.Add(diagnostic);
|
|
}
|
|
}
|
|
|
|
internal static void AddRefinementAttempt(ElkEdgeRefinementAttemptDiagnostics attempt)
|
|
{
|
|
var diagnostics = CurrentDiagnostics.Value;
|
|
if (diagnostics is null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
lock (diagnostics.SyncRoot)
|
|
{
|
|
diagnostics.Attempts.Add(attempt);
|
|
}
|
|
}
|
|
}
|