using System.Collections.Concurrent;
namespace XFEToolBox.Client.Core.Console;
///
/// A thread-safe, single-consumer buffer that preserves console Write/WriteLine semantics.
///
/// Metadata needed by the output renderer.
public sealed class ConsoleOutputBuffer
{
private readonly ConcurrentQueue> queue = new();
private readonly object enqueueLock = new();
private bool lastLineIsEnded = true;
private long generation;
private int count;
///
/// Gets the number of entries waiting to be consumed.
///
public int Count => Volatile.Read(ref count);
///
/// Gets whether no entries are waiting to be consumed.
///
public bool IsEmpty => Count == 0;
///
/// Gets the current buffer generation. Clearing the buffer starts a new generation.
///
public long Generation => Volatile.Read(ref generation);
///
/// Enqueues output and atomically determines whether it starts a new logical line.
///
public BufferedConsoleOutput Enqueue(
string text,
TMetadata metadata,
bool isLineEnd,
bool forceNewLine = false) =>
Enqueue(_ => text, metadata, isLineEnd, forceNewLine);
///
/// Enqueues output using a factory that can add a prefix only when a new line starts.
///
public BufferedConsoleOutput Enqueue(
Func textFactory,
TMetadata metadata,
bool isLineEnd,
bool forceNewLine = false)
{
ArgumentNullException.ThrowIfNull(textFactory);
lock (enqueueLock)
{
var startsNewLine = forceNewLine || lastLineIsEnded;
var output = new BufferedConsoleOutput(
textFactory(startsNewLine),
metadata,
startsNewLine,
isLineEnd,
generation);
queue.Enqueue(output);
Interlocked.Increment(ref count);
lastLineIsEnded = isLineEnd;
return output;
}
}
///
/// Attempts to read the next entry. This method is intended for a single consumer.
///
public bool TryDequeue(out BufferedConsoleOutput output)
{
if (!queue.TryDequeue(out output))
return false;
Interlocked.Decrement(ref count);
return true;
}
///
/// Returns whether an entry belongs to the current, non-cleared generation.
///
public bool IsCurrent(BufferedConsoleOutput output) => output.Generation == Generation;
///
/// Clears pending output, resets Write/WriteLine state, and returns discarded entries.
///
public IReadOnlyList> Clear()
{
var discarded = new List>();
lock (enqueueLock)
{
Interlocked.Increment(ref generation);
lastLineIsEnded = true;
while (queue.TryDequeue(out var output))
{
Interlocked.Decrement(ref count);
discarded.Add(output);
}
}
return discarded;
}
}
///
/// One buffered console write operation.
///
public readonly record struct BufferedConsoleOutput(
string Text,
TMetadata Metadata,
bool StartsNewLine,
bool IsLineEnd,
long Generation);