Files
git.stella-ops.org/src/__Libraries/StellaOps.ElkSharp/ElkLayoutDiagnostics.cs
2026-03-24 08:38:09 +02:00

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);
}
}
}