using System; using System.Runtime.CompilerServices; using TrajectoryPlanningVisualization; [assembly: InternalsVisibleTo("EMPlannerVerificationHost")] namespace MultiWheelC.TrajectoryPlanning.TrajectoryObservation; internal interface ITrajectoryObservationVisualizationSink { PlanningVisualizationSessionInfo Start(PlanningVisualizationOptions options, PlanningVisualizationStaticSnapshot snapshot); void Publish(PlanningVisualizationDynamicSnapshot snapshot); void Stop(); } internal sealed class TrajectoryObservationVisualizationPublisher { private readonly TrajectoryObservationSettings settings; private readonly ITrajectoryObservationVisualizationSink sink; private readonly Action log; private DateTimeOffset nextPublishAtUtc; private bool started; private bool stopped; private bool sinkStopped; private string faultReason = string.Empty; internal TrajectoryObservationVisualizationPublisher(TrajectoryObservationSettings settings, ITrajectoryObservationVisualizationSink sink, Action log) { this.settings = settings ?? throw new ArgumentNullException(nameof(settings)); this.sink = sink ?? throw new ArgumentNullException(nameof(sink)); this.log = log ?? throw new ArgumentNullException(nameof(log)); } internal string FaultReason => faultReason; internal PlanningVisualizationSessionInfo Start(PlanningVisualizationStaticSnapshot snapshot) { if (!settings.EnableWebVisualization || stopped || faultReason.Length != 0) return null; if (started) return null; try { if (snapshot == null) throw new ArgumentNullException(nameof(snapshot)); PlanningVisualizationSessionInfo session = sink.Start(new PlanningVisualizationOptions { Port = settings.WebVisualizationPort, RefreshRateHz = Math.Max(1, (int)Math.Round(settings.WebRefreshRateHz, MidpointRounding.AwayFromZero)), HistoryCycleLimit = settings.VisualizationHistoryCycleLimit, }, snapshot); if (session == null) throw new InvalidOperationException("网页会话未返回访问地址。"); started = true; return session; } catch (Exception exception) { Fuse(exception); return null; } } internal bool TryPublish(DateTimeOffset now, Func snapshotFactory) { if (snapshotFactory == null) throw new ArgumentNullException(nameof(snapshotFactory)); if (!settings.EnableWebVisualization || !started || stopped || faultReason.Length != 0) return false; if (now < nextPublishAtUtc) return false; try { PlanningVisualizationDynamicSnapshot snapshot = snapshotFactory(); if (snapshot == null) throw new InvalidOperationException("网页动态快照为空。"); nextPublishAtUtc = now + TimeSpan.FromSeconds(1d / settings.WebRefreshRateHz); sink.Publish(snapshot); return true; } catch (Exception exception) { Fuse(exception); return false; } } internal void Disable(Exception exception) { if (exception == null) throw new ArgumentNullException(nameof(exception)); Fuse(exception); } internal void Stop() { if (stopped) return; stopped = true; StopSink(); } private void Fuse(Exception exception) { if (faultReason.Length != 0) return; faultReason = "网页可视化已关闭:" + exception.GetType().Name + ":" + exception.Message; StopSink(); log(faultReason); } private void StopSink() { if (sinkStopped) return; sinkStopped = true; try { sink.Stop(); } catch { } } } internal sealed class PlanningVisualizationSessionSink : ITrajectoryObservationVisualizationSink { private PlanningVisualizationSession session; public PlanningVisualizationSessionInfo Start(PlanningVisualizationOptions options, PlanningVisualizationStaticSnapshot snapshot) { if (session != null) return session.Start(snapshot); session = new PlanningVisualizationSession(options); return session.Start(snapshot); } public void Publish(PlanningVisualizationDynamicSnapshot snapshot) { session?.Publish(snapshot); } public void Stop() { PlanningVisualizationSession current = session; session = null; current?.Dispose(); } }