using System; using System.Net.Sockets; using System.Text; using System.Threading; using System.Threading.Tasks; namespace TrajectoryPlanningVisualization; internal sealed class SseClientConnection : IDisposable { private readonly TcpClient client; private readonly CancellationToken cancellationToken; private readonly AutoResetEvent pendingSignal = new AutoResetEvent(false); private readonly Task writerTask; private string pendingPayload; private int completeAfterWrite; private int disposed; public SseClientConnection(TcpClient client, CancellationToken cancellationToken) { this.client = client ?? throw new ArgumentNullException(nameof(client)); this.cancellationToken = cancellationToken; writerTask = Task.Run((Action)WriteLoop); } public Task Completion => writerTask; public void Offer(string payload) { if (payload == null || Volatile.Read(ref disposed) != 0) { return; } Interlocked.Exchange(ref pendingPayload, payload); pendingSignal.Set(); } public void OfferTerminal(string payload) { if (payload == null || Volatile.Read(ref disposed) != 0) { return; } Interlocked.Exchange(ref pendingPayload, payload); Interlocked.Exchange(ref completeAfterWrite, 1); pendingSignal.Set(); } public bool WaitForCompletion(TimeSpan timeout) { try { return writerTask.Wait(timeout); } catch (AggregateException) { return true; } } public void Dispose() { if (Interlocked.Exchange(ref disposed, 1) != 0) { return; } pendingSignal.Set(); try { client.Close(); } finally { pendingSignal.Dispose(); } } private void WriteLoop() { try { NetworkStream stream = client.GetStream(); stream.WriteTimeout = 1000; while (!cancellationToken.IsCancellationRequested && Volatile.Read(ref disposed) == 0) { string payload = Interlocked.Exchange(ref pendingPayload, null); if (payload == null) { pendingSignal.WaitOne(50); continue; } byte[] bytes = Encoding.UTF8.GetBytes(payload); stream.Write(bytes, 0, bytes.Length); stream.Flush(); if (Interlocked.Exchange(ref completeAfterWrite, 0) != 0) { return; } } } catch (SocketException) { } catch (System.IO.IOException) { } catch (ObjectDisposedException) { } } }