feat: add bounded visualization snapshots

This commit is contained in:
梁薄云
2026-08-06 09:27:58 +08:00
parent 55d8e1eebf
commit ab8e4010f8
6 changed files with 269 additions and 0 deletions
@@ -0,0 +1,55 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
namespace TrajectoryPlanningVisualization;
public sealed class BoundedCycleHistory
{
private readonly object sync = new object();
private readonly int capacity;
private readonly Queue<VisualizationCycleSummary> entries = new Queue<VisualizationCycleSummary>();
private readonly HashSet<long> versions = new HashSet<long>();
public BoundedCycleHistory(int capacity)
{
if (capacity <= 0)
{
throw new ArgumentOutOfRangeException(nameof(capacity));
}
this.capacity = capacity;
}
public void Add(VisualizationCycleSummary summary)
{
if (summary == null)
{
throw new ArgumentNullException(nameof(summary));
}
lock (sync)
{
if (!versions.Add(summary.CycleVersion))
{
return;
}
entries.Enqueue(summary);
while (entries.Count > capacity)
{
VisualizationCycleSummary removed = entries.Dequeue();
versions.Remove(removed.CycleVersion);
}
}
}
public IReadOnlyList<VisualizationCycleSummary> Snapshot()
{
lock (sync)
{
return new ReadOnlyCollection<VisualizationCycleSummary>(
new List<VisualizationCycleSummary>(entries));
}
}
}
@@ -0,0 +1,46 @@
using System;
using System.Threading;
namespace TrajectoryPlanningVisualization;
public sealed class VisualizationFrame
{
public VisualizationFrame(long version, PlanningVisualizationDynamicSnapshot snapshot)
{
Version = version;
Snapshot = snapshot ?? throw new ArgumentNullException(nameof(snapshot));
}
public long Version { get; }
public PlanningVisualizationDynamicSnapshot Snapshot { get; }
}
public sealed class LatestVisualizationFrameStore
{
private VisualizationFrame latest;
private long nextVersion;
public void Publish(PlanningVisualizationDynamicSnapshot snapshot)
{
if (snapshot == null)
{
throw new ArgumentNullException(nameof(snapshot));
}
long version = Interlocked.Increment(ref nextVersion);
Interlocked.Exchange(ref latest, new VisualizationFrame(version, snapshot));
}
public bool TryReadAfter(long version, out VisualizationFrame frame)
{
VisualizationFrame current = Volatile.Read(ref latest);
if (current == null || current.Version <= version)
{
frame = null;
return false;
}
frame = current;
return true;
}
}
@@ -0,0 +1,45 @@
using System;
namespace TrajectoryPlanningVisualization;
public sealed class PlanningVisualizationOptions
{
public int Port { get; set; } = 0;
public int RefreshRateHz { get; set; } = 10;
public int HistoryCycleLimit { get; set; } = 60;
public PlanningVisualizationOptionsSnapshot CreateValidatedSnapshot()
{
if (Port < 0 || Port > 65535)
{
throw new ArgumentOutOfRangeException(nameof(Port), "Port must be in the range 0 to 65535.");
}
if (RefreshRateHz <= 0)
{
throw new ArgumentOutOfRangeException(nameof(RefreshRateHz), "Refresh rate must be positive.");
}
if (HistoryCycleLimit <= 0)
{
throw new ArgumentOutOfRangeException(nameof(HistoryCycleLimit), "History cycle limit must be positive.");
}
return new PlanningVisualizationOptionsSnapshot(Port, RefreshRateHz, HistoryCycleLimit);
}
}
public sealed class PlanningVisualizationOptionsSnapshot
{
internal PlanningVisualizationOptionsSnapshot(int port, int refreshRateHz, int historyCycleLimit)
{
Port = port;
RefreshRateHz = refreshRateHz;
HistoryCycleLimit = historyCycleLimit;
}
public int Port { get; }
public int RefreshRateHz { get; }
public int HistoryCycleLimit { get; }
public int MaximumClients => 2;
}
@@ -0,0 +1,21 @@
using System.Globalization;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
namespace TrajectoryPlanningVisualization;
public static class VisualizationJson
{
private static readonly JsonSerializerSettings Settings = new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver(),
Culture = CultureInfo.InvariantCulture,
DateFormatHandling = DateFormatHandling.IsoDateFormat,
Formatting = Formatting.None
};
public static string Serialize(object value)
{
return JsonConvert.SerializeObject(value, Settings);
}
}