// Copyright Epic Games, Inc. All Rights Reserved.
using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
namespace EpicGames.Horde.Compute
{
///
/// In-process buffer used to store compute messages
///
public abstract class ComputeBuffer : IDisposable
{
///
/// Maximum number of chunks in a buffer
///
public const int MaxChunks = 16;
///
/// Maximum number of readers
///
public const int MaxReaders = 16;
internal ComputeBufferDetail _detail;
///
/// Constructor
///
/// Resources shared between instances of the buffer
internal ComputeBuffer(ComputeBufferDetail detail)
{
_detail = detail;
}
///
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
///
/// Overridable dispose method
///
protected virtual void Dispose(bool disposing)
{
if (_detail != null)
{
_detail.Release();
_detail = null!;
}
}
///
/// Creates a new reader for this buffer
///
public ComputeBufferReader CreateReader()
{
int readerIdx = _detail.CreateReader();
_detail.AddRef();
return new ComputeBufferReader(_detail, readerIdx);
}
///
/// Writer for this buffer
///
public ComputeBufferWriter CreateWriter()
{
_detail.CreateWriter();
_detail.AddRef();
return new ComputeBufferWriter(_detail);
}
///
/// Creates a new reference to the underlying buffer. The underlying resources will only be destroyed once all instances are disposed of.
///
public abstract ComputeBuffer AddRef();
}
///
/// Read interface for a compute buffer
///
public sealed class ComputeBufferReader : IDisposable
{
ComputeBufferDetail _buffer;
readonly int _readerIdx;
internal ComputeBufferDetail Detail => _buffer;
internal ComputeBufferReader(ComputeBufferDetail buffer, int readerIdx)
{
_buffer = buffer;
_readerIdx = readerIdx;
}
///
/// Create a new reader instance using the same underlying buffer
///
public ComputeBufferReader AddRef()
{
_buffer.AddRef();
_buffer.AddReaderRef(_readerIdx);
return new ComputeBufferReader(_buffer, _readerIdx);
}
///
public void Dispose()
{
if (_buffer != null)
{
_buffer.ReleaseReaderRef(_readerIdx);
_buffer.Release();
_buffer = null!;
}
}
///
/// Detaches this reader from the underlying buffer
///
public void Detach() => _buffer.DetachReader(_readerIdx);
///
/// Whether this buffer is complete (no more data will be added)
///
public bool IsComplete => _buffer.IsComplete(_readerIdx);
///
/// Updates the read position
///
/// Size of data that was read
public void AdvanceReadPosition(int length) => _buffer.AdvanceReadPosition(_readerIdx, length);
///
/// Gets the next data to read
///
/// Memory to read from
public ReadOnlyMemory GetReadBuffer() => _buffer.GetReadBuffer(_readerIdx);
///
/// Read from a buffer into another buffer
///
/// Memory to receive the read data
/// Cancellation token for the operation
/// Number of bytes read
public async ValueTask ReadAsync(Memory buffer, CancellationToken cancellationToken = default)
{
for (; ; )
{
ReadOnlyMemory readMemory = GetReadBuffer();
if (IsComplete || readMemory.Length > 0)
{
int length = Math.Min(readMemory.Length, buffer.Length);
readMemory.Slice(0, length).CopyTo(buffer);
AdvanceReadPosition(length);
return length;
}
await WaitToReadAsync(1, cancellationToken);
}
}
///
/// Wait for data to be available, or for the buffer to be marked as complete
///
/// Minimum amount of data to read
/// Cancellation token for the operation
/// True if new data is available, false if the buffer is complete
public ValueTask WaitToReadAsync(int minLength, CancellationToken cancellationToken = default) => _buffer.WaitToReadAsync(_readerIdx, minLength, cancellationToken);
}
///
/// Buffer that can receive data from a remote machine.
///
public sealed class ComputeBufferWriter : IDisposable
{
ComputeBufferDetail _detail;
internal ComputeBufferDetail Detail => _detail;
internal ComputeBufferWriter(ComputeBufferDetail detail) => _detail = detail;
///
/// Create a new writer instance using the same underlying buffer
///
public ComputeBufferWriter AddRef()
{
_detail.AddRef();
_detail.AddWriterRef();
return new ComputeBufferWriter(_detail);
}
///
public void Dispose()
{
if (_detail != null)
{
_detail.ReleaseWriterRef();
_detail.Release();
_detail = null!;
}
}
///
public void AdvanceWritePosition(int size) => _detail.AdvanceWritePosition(size);
///
/// Gets memory to write to
///
/// Memory to be written to
public Memory GetWriteBuffer() => _detail.GetWriteBuffer();
///
/// Mark the output to this buffer as complete
///
/// Whether the writer was marked as complete. False if the writer has already been marked as complete.
public bool MarkComplete() => _detail.MarkComplete();
///
/// Writes data into a buffer from a memory block
///
/// The data to write
/// Cancellation token for the operation
public async ValueTask WriteAsync(ReadOnlyMemory buffer, CancellationToken cancellationToken = default)
{
while (buffer.Length > 0)
{
Memory writeMemory = GetWriteBuffer();
if (writeMemory.Length >= buffer.Length)
{
buffer.CopyTo(writeMemory);
AdvanceWritePosition(buffer.Length);
break;
}
await WaitToWriteAsync(buffer.Length, cancellationToken);
}
}
///
/// Gets memory to write to
///
/// Minimum size of the desired write buffer
/// Cancellation token for the operation
/// Memory to be written to
public ValueTask WaitToWriteAsync(int minLength, CancellationToken cancellationToken = default) => _detail.WaitToWriteAsync(minLength, cancellationToken);
}
///
/// State shared between buffer instances
///
[DebuggerTypeProxy(typeof(BufferDebugProxy))]
internal abstract class ComputeBufferDetail : IDisposable
{
internal const int HeaderSize = (2 + ComputeBuffer.MaxReaders + ComputeBuffer.MaxChunks) * sizeof(ulong);
///
/// Write state for a chunk
///
protected internal enum WriteState
{
///
/// Writer has moved to the next chunk
///
MovedToNext = 0,
///
/// Chunk is still being appended to
///
Writing = 2,
///
/// This chunk marks the end of the stream
///
Complete = 3,
}
///
/// Stores the state of a chunk in a 64-bit value, which can be updated atomically
///
protected internal record struct ChunkState(ulong Value)
{
// Written length of this chunk
public readonly int Length => (int)(Value & 0x7fffffff);
// Set of flags which are set for each reader that still has to read from a chunk
public readonly int ReaderFlags => (int)((Value >> 31) & 0x7fffffff);
// State of the writer
public readonly WriteState WriteState => (WriteState)(Value >> 62);
// Constructor
public ChunkState(WriteState writerState, int readerFlags, int length) : this(((ulong)writerState << 62) | ((ulong)readerFlags << 31) | (uint)length) { }
// Test whether a particular reader is still referencing the chunk
public readonly bool HasReaderFlag(int readerIdx) => (Value & (1UL << (31 + readerIdx))) != 0;
///
public override readonly string ToString() => $"{WriteState}, Length: {Length}, Readers: {ReaderFlags}";
}
///
/// Wraps a pointer to the state of a chunk
///
protected internal readonly unsafe struct ChunkStatePtr
{
readonly ulong* _data;
public ChunkStatePtr(ulong* data) => _data = data;
// Current value of the chunk state
public ChunkState Get() => new ChunkState(Interlocked.CompareExchange(ref *_data, 0, 0));
// Set the current state
public void Set(ChunkState value) => Interlocked.Exchange(ref *_data, value.Value);
// Attempt to update the chunk state
public bool TryUpdate(ChunkState prevState, ChunkState nextState) => Interlocked.CompareExchange(ref *_data, nextState.Value, prevState.Value) == prevState.Value;
// Append data to the chunk
public void Append(int length) => Interlocked.Add(ref *_data, (ulong)length);
// Move to the next chunk
public void MarkComplete() => Interlocked.Or(ref *_data, new ChunkState(WriteState.Complete, 0, 0).Value);
// Start reading the chunk with the given reader
public void StartReading(int readerIdx) => Interlocked.Or(ref *_data, new ChunkState(0, 1 << readerIdx, 0).Value);
// Clear the reader flag
public void FinishReading(int readerIdx) => Interlocked.And(ref *_data, ~new ChunkState(0, 1 << readerIdx, 0).Value);
// Move to the next chunk
public void FinishWriting() => Interlocked.And(ref *_data, ~new ChunkState(WriteState.Writing, 0, 0).Value);
///
public override string ToString() => Get().ToString();
}
///
/// State of a reader
///
protected internal record struct ReaderState(ulong Value)
{
public ReaderState(int chunkIdx, int offset, int refCount, bool detached)
: this((ulong)(uint)offset | ((ulong)(uint)chunkIdx << 32) | ((ulong)(uint)refCount << 40) | ((ulong)((detached ? (1UL << 63) : 0))))
{ }
public readonly int Offset => (int)(Value & 0xffffffff);
public readonly int ChunkIdx => (int)((Value >> 32) & 0xff);
public readonly int RefCount => (int)((Value >> 40) & 0x7fff);
public readonly bool Detached => (Value & (1UL << 63)) != 0;
///
public override readonly string ToString() => $"Chunk: {ChunkIdx}, Offset: {Offset}, RefCount: {RefCount}, Detached: {Detached}";
}
///
/// Wraps a pointer to the state of a writer
///
protected internal readonly unsafe struct ReaderStatePtr
{
readonly ulong* _data;
public ReaderStatePtr(ulong* data) => _data = data;
// Current value of the chunk state
public ReaderState Get() => new ReaderState(Interlocked.CompareExchange(ref *_data, 0, 0));
// Update current state
public void Set(ReaderState value) => Interlocked.Exchange(ref *_data, value.Value);
// Compare and swap
public bool TryUpdate(ReaderState prevState, ReaderState nextState) => Interlocked.CompareExchange(ref *_data, nextState.Value, prevState.Value) == prevState.Value;
}
///
/// State of the writer
///
protected internal record struct WriterState(ulong Value)
{
public WriterState(int chunkIdx, int readerFlags, int refCount, bool hasWrapped)
: this((ulong)(uint)chunkIdx | ((ulong)(uint)readerFlags << 32) | ((ulong)(uint)refCount << 48) | (hasWrapped ? (1UL << 63) : 0))
{ }
public readonly int ChunkIdx => (int)(Value & 0x7fffffff);
public readonly int ReaderFlags => (int)(Value >> 32) & 0xffff;
public readonly int RefCount => (int)(Value >> 48) & 0x7fff;
public readonly bool HasWrapped => (Value & (1UL << 63)) != 0;
///
public override readonly string ToString() => $"Chunk: {ChunkIdx}, ReaderFlags: {ReaderFlags}, RefCount: {RefCount}, HasWrapped: {HasWrapped}";
}
///
/// Wraps a pointer to the state of a writer
///
protected internal readonly unsafe struct WriterStatePtr
{
readonly ulong* _data;
public WriterStatePtr(ulong* data) => _data = data;
// Get the current value
public WriterState Get() => new WriterState(Interlocked.CompareExchange(ref *_data, 0, 0));
// Set the current value
public void Set(WriterState state) => Interlocked.Exchange(ref *_data, state.Value);
// Compare and swap
public bool TryUpdate(WriterState prevState, WriterState nextState) => Interlocked.CompareExchange(ref *_data, nextState.Value, prevState.Value) == prevState.Value;
}
///
/// Tracked state of the buffer
///
protected internal readonly unsafe struct HeaderPtr
{
readonly ulong* _data;
public HeaderPtr(ulong* data) => _data = data;
public HeaderPtr(ulong* data, int numReaders, int numChunks, int chunkLength)
{
_data = data;
data[0] = ((ulong)(uint)chunkLength << 32) | ((ulong)(uint)numChunks << 16) | (uint)numReaders;
GetChunkStatePtr(0).Set(new ChunkState(WriteState.Writing, 0, 0));
}
public int NumReaders => (int)(_data[0] & 0xffff);
public int NumChunks => (int)((_data[0] >> 16) & 0xffff);
public int ChunkLength => (int)(_data[0] >> 32);
public WriterStatePtr GetWriterStatePtr() => new WriterStatePtr(_data + 1);
public ReaderStatePtr GetReaderStatePtr(int readerIdx) => new ReaderStatePtr(_data + 2 + readerIdx);
public ChunkStatePtr GetChunkStatePtr(int chunkIdx) => new ChunkStatePtr(_data + 2 + ComputeBuffer.MaxReaders + chunkIdx);
}
class BufferDebugProxy
{
public int ChunkLength { get; }
public int RefCount { get; }
public WriterState Writer { get; }
public ReaderState[] Readers { get; }
public ChunkState[] Chunks { get; }
public BufferDebugProxy(ComputeBufferDetail buffer)
{
RefCount = buffer._refCount;
HeaderPtr headerPtr = buffer._headerPtr;
ChunkLength = headerPtr.ChunkLength;
Writer = headerPtr.GetWriterStatePtr().Get();
Chunks = new ChunkState[headerPtr.NumChunks];
for (int chunkIdx = 0; chunkIdx < headerPtr.NumChunks; chunkIdx++)
{
Chunks[chunkIdx] = headerPtr.GetChunkStatePtr(chunkIdx).Get();
}
Readers = new ReaderState[headerPtr.NumReaders];
for (int readerIdx = 0; readerIdx < headerPtr.NumReaders; readerIdx++)
{
Readers[readerIdx] = headerPtr.GetReaderStatePtr(readerIdx).Get();
}
}
}
HeaderPtr _headerPtr;
Memory[] _chunks;
int _refCount = 1;
///
/// Constructor
///
protected ComputeBufferDetail(HeaderPtr headerPtr, Memory[] chunks)
{
_headerPtr = headerPtr;
_chunks = chunks;
}
///
/// Increment the reference count on this object
///
public void AddRef()
{
Interlocked.Increment(ref _refCount);
}
///
/// Decrement the reference count on this object, and dispose of it once it reaches zero
///
public void Release()
{
if (Interlocked.Decrement(ref _refCount) == 0)
{
Dispose();
}
}
///
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
///
/// Overridable dispose method
///
protected virtual void Dispose(bool disposing)
{
_headerPtr = default;
_chunks = Array.Empty>();
}
///
/// Signals a read event
///
public abstract void SetReadEvent(int readerIdx);
///
/// Signals read events for every reader
///
public void SetAllReadEvents()
{
for (int readerIdx = 0; readerIdx < _headerPtr.NumReaders; readerIdx++)
{
SetReadEvent(readerIdx);
}
}
///
/// Waits for a read event to be signalled
///
public abstract Task WaitToReadAsync(int readerIdx, CancellationToken cancellationToken);
///
/// Signals the write event
///
public abstract void SetWriteEvent();
///
/// Waits for the write event to be signalled
///
public abstract Task WaitToWriteAsync(CancellationToken cancellationToken);
///
/// Allocate a new reader
///
public int CreateReader()
{
for (int readerIdx = 0; readerIdx < _headerPtr.NumReaders; readerIdx++)
{
ReaderStatePtr readerStatePtr = _headerPtr.GetReaderStatePtr(readerIdx);
for (; ; )
{
ReaderState readerState = readerStatePtr.Get();
if (readerState.RefCount > 0)
{
break;
}
if (readerStatePtr.TryUpdate(readerState, new ReaderState(0, 0, 1, false)))
{
WriterStatePtr writerStatePtr = _headerPtr.GetWriterStatePtr();
for (; ; )
{
WriterState writerState = writerStatePtr.Get();
if (writerState.HasWrapped)
{
throw new InvalidOperationException("Cannot create a new reader after writer has wrapped back to the first chunk");
}
if (writerStatePtr.TryUpdate(writerState, new WriterState(writerState.ChunkIdx, writerState.ReaderFlags | (1 << readerIdx), writerState.RefCount, writerState.HasWrapped)))
{
for (int writeChunkIdx = 0; writeChunkIdx <= writerState.ChunkIdx; writeChunkIdx++)
{
_headerPtr.GetChunkStatePtr(writeChunkIdx).StartReading(readerIdx);
}
return readerIdx;
}
}
}
}
}
throw new InvalidOperationException("Unable to allocate reader; all available readers are in use.");
}
public void AddReaderRef(int readerIdx)
{
ReaderStatePtr readerStatePtr = _headerPtr.GetReaderStatePtr(readerIdx);
for (; ; )
{
ReaderState readerState = readerStatePtr.Get();
if (readerState.RefCount == 0)
{
throw new InvalidOperationException("Refcount for reader is zero");
}
if (readerStatePtr.TryUpdate(readerState, new ReaderState(readerState.ChunkIdx, readerState.Offset, readerState.RefCount + 1, readerState.Detached)))
{
break;
}
}
}
public void ReleaseReaderRef(int readerIdx)
{
ReaderStatePtr readerStatePtr = _headerPtr.GetReaderStatePtr(readerIdx);
for (; ; )
{
ReaderState readerState = readerStatePtr.Get();
if (readerState.RefCount == 0)
{
throw new InvalidOperationException("Refcount for reader is already zero");
}
if (readerState.RefCount == 1)
{
for (int idx = 0; idx < _headerPtr.NumChunks; idx++)
{
_headerPtr.GetChunkStatePtr(idx).FinishReading(readerIdx);
}
}
if (readerStatePtr.TryUpdate(readerState, new ReaderState(readerState.ChunkIdx, readerState.Offset, readerState.RefCount - 1, readerState.Detached)))
{
break;
}
}
}
public void CreateWriter()
{
WriterStatePtr writerStatePtr = _headerPtr.GetWriterStatePtr();
for (; ; )
{
WriterState writerState = writerStatePtr.Get();
if (writerState.RefCount > 0)
{
throw new InvalidOperationException("Writer has already been created for this buffer");
}
if (writerStatePtr.TryUpdate(writerState, new WriterState(writerState.ChunkIdx, writerState.ReaderFlags, 1, writerState.HasWrapped)))
{
ChunkStatePtr chunkStatePtr = _headerPtr.GetChunkStatePtr(writerState.ChunkIdx);
for (; ; )
{
ChunkState chunkState = chunkStatePtr.Get();
if (chunkStatePtr.TryUpdate(chunkState, new ChunkState(WriteState.Writing, chunkState.ReaderFlags, chunkState.Length)))
{
break;
}
}
break;
}
}
}
public void AddWriterRef()
{
WriterStatePtr writerStatePtr = _headerPtr.GetWriterStatePtr();
for (; ; )
{
WriterState writerState = writerStatePtr.Get();
if (writerState.RefCount == 0)
{
throw new InvalidOperationException("Writer does not exist for this buffer");
}
if (writerStatePtr.TryUpdate(writerState, new WriterState(writerState.ChunkIdx, writerState.ReaderFlags, writerState.RefCount + 1, writerState.HasWrapped)))
{
break;
}
}
}
public void ReleaseWriterRef()
{
WriterStatePtr writerStatePtr = _headerPtr.GetWriterStatePtr();
for (; ; )
{
WriterState writerState = writerStatePtr.Get();
if (writerState.RefCount == 0)
{
throw new InvalidOperationException("Writer does not exist for this buffer");
}
if (writerState.RefCount == 1)
{
MarkComplete();
}
if (writerStatePtr.TryUpdate(writerState, new WriterState(writerState.ChunkIdx, writerState.ReaderFlags, writerState.RefCount - 1, writerState.HasWrapped)))
{
break;
}
}
}
///
public bool IsComplete(int readerIdx)
{
ReaderState readerState = _headerPtr.GetReaderStatePtr(readerIdx).Get();
if (readerState.Detached)
{
return true;
}
ChunkState chunkState = _headerPtr.GetChunkStatePtr(readerState.ChunkIdx).Get();
return chunkState.WriteState == WriteState.Complete && readerState.Offset == chunkState.Length;
}
///
public void DetachReader(int readerIdx)
{
ReaderStatePtr readerStatePtr = _headerPtr.GetReaderStatePtr(readerIdx);
for (; ; )
{
ReaderState readerState = readerStatePtr.Get();
if (readerStatePtr.TryUpdate(readerState, new ReaderState(readerState.ChunkIdx, readerState.Offset, readerState.RefCount, true)))
{
SetReadEvent(readerIdx);
break;
}
}
}
///
public void AdvanceReadPosition(int readerIdx, int offset)
{
ReaderStatePtr readerStatePtr = _headerPtr.GetReaderStatePtr(readerIdx);
for (; ; )
{
ReaderState readerState = readerStatePtr.Get();
if (readerStatePtr.TryUpdate(readerState, new ReaderState(readerState.ChunkIdx, readerState.Offset + offset, readerState.RefCount, readerState.Detached)))
{
SetReadEvent(readerIdx);
break;
}
}
}
///
public ReadOnlyMemory GetReadBuffer(int readerIdx)
{
ReaderState readerState = _headerPtr.GetReaderStatePtr(readerIdx).Get();
ChunkStatePtr chunkStatePtr = _headerPtr.GetChunkStatePtr(readerState.ChunkIdx);
ChunkState chunkState = chunkStatePtr.Get();
if (chunkState.HasReaderFlag(readerIdx))
{
return _chunks[readerState.ChunkIdx].Slice(readerState.Offset, chunkState.Length - readerState.Offset);
}
else
{
return ReadOnlyMemory.Empty;
}
}
///
public async ValueTask WaitToReadAsync(int readerIdx, int minLength, CancellationToken cancellationToken = default)
{
for (; ; )
{
ReaderStatePtr readerStatePtr = _headerPtr.GetReaderStatePtr(readerIdx);
ReaderState readerState = readerStatePtr.Get();
if (readerState.Detached)
{
return false;
}
ChunkStatePtr chunkStatePtr = _headerPtr.GetChunkStatePtr(readerState.ChunkIdx);
ChunkState chunkState = chunkStatePtr.Get();
if (!chunkState.HasReaderFlag(readerIdx))
{
// Wait until the current chunk is readable
await WaitToReadAsync(readerIdx, cancellationToken);
}
else if (readerState.Offset + minLength <= chunkState.Length)
{
// We have enough data in the chunk to be able to read a message
return true;
}
else if (chunkState.WriteState == WriteState.Writing)
{
// Wait until there is more data in the chunk
await WaitToReadAsync(readerIdx, cancellationToken);
}
else if (readerState.Offset < chunkState.Length || chunkState.WriteState == WriteState.Complete)
{
// Cannot read the requested amount of data from this chunk.
return false;
}
else if (chunkState.WriteState == WriteState.MovedToNext)
{
// Move to the next chunk
chunkStatePtr.FinishReading(readerIdx);
SetWriteEvent();
int nextChunkIdx = readerState.ChunkIdx + 1;
if (nextChunkIdx == _headerPtr.NumChunks)
{
nextChunkIdx = 0;
}
readerStatePtr.TryUpdate(readerState, new ReaderState(nextChunkIdx, 0, readerState.RefCount, readerState.Detached));
}
else
{
throw new NotImplementedException($"Invalid write state for buffer: {chunkState.WriteState}");
}
}
}
///
public bool MarkComplete()
{
WriterState writerState = _headerPtr.GetWriterStatePtr().Get();
ChunkStatePtr chunkStatePtr = _headerPtr.GetChunkStatePtr(writerState.ChunkIdx);
if (chunkStatePtr.Get().WriteState != WriteState.Complete)
{
chunkStatePtr.MarkComplete();
SetAllReadEvents();
return true;
}
return false;
}
///
public void AdvanceWritePosition(int size)
{
if (size > 0)
{
WriterState writerState = _headerPtr.GetWriterStatePtr().Get();
ChunkStatePtr chunkStatePtr = _headerPtr.GetChunkStatePtr(writerState.ChunkIdx);
ChunkState chunkState = chunkStatePtr.Get();
Debug.Assert(chunkState.WriteState == WriteState.Writing);
chunkStatePtr.Append(size);
SetAllReadEvents();
}
}
///
public Memory GetWriteBuffer()
{
WriterState writerState = _headerPtr.GetWriterStatePtr().Get();
ChunkState chunkState = _headerPtr.GetChunkStatePtr(writerState.ChunkIdx).Get();
if (chunkState.WriteState == WriteState.Writing)
{
return _chunks[writerState.ChunkIdx].Slice(chunkState.Length);
}
else
{
return Memory.Empty;
}
}
///
public async ValueTask WaitToWriteAsync(int minSize, CancellationToken cancellationToken = default)
{
if (minSize > _headerPtr.ChunkLength)
{
throw new ArgumentException("Requested read size is larger than chunk size.", nameof(minSize));
}
// Get the current chunk we're writing to
WriterState writerState = _headerPtr.GetWriterStatePtr().Get();
int writeChunkIdx = writerState.ChunkIdx;
ChunkStatePtr writeChunkStatePtr = _headerPtr.GetChunkStatePtr(writeChunkIdx);
// Check if we can append to this chunk
ChunkState chunkState = writeChunkStatePtr.Get();
if (chunkState.WriteState == WriteState.Writing)
{
int length = chunkState.Length;
if (length + minSize <= _headerPtr.ChunkLength)
{
return;
}
writeChunkStatePtr.FinishWriting();
SetAllReadEvents();
}
if (chunkState.WriteState == WriteState.Complete)
{
return;
}
// Otherwise get the next chunk to write to
int nextWriteChunkIdx = writeChunkIdx + 1;
if (nextWriteChunkIdx == _chunks.Length)
{
nextWriteChunkIdx = 0;
}
// Wait until all readers have finished with the chunk, and we can update the writer to match
ChunkStatePtr nextWriteChunkStatePtr = _headerPtr.GetChunkStatePtr(nextWriteChunkIdx);
for (; ; )
{
ChunkState nextWriteChunkState = nextWriteChunkStatePtr.Get();
if (nextWriteChunkState.ReaderFlags != 0)
{
await WaitToWriteAsync(cancellationToken);
}
else if (nextWriteChunkStatePtr.TryUpdate(nextWriteChunkState, new ChunkState(WriteState.Writing, writerState.ReaderFlags, 0)))
{
WriterStatePtr writerStatePtr = _headerPtr.GetWriterStatePtr();
if (writerStatePtr.TryUpdate(writerState, new WriterState(nextWriteChunkIdx, writerState.ReaderFlags, writerState.RefCount, nextWriteChunkIdx == 0)))
{
break;
}
else
{
writerState = writerStatePtr.Get();
}
}
}
}
}
}