using Ryujinx.Common; using Ryujinx.Common.Memory; using System; using System.Buffers; namespace Ryujinx.Audio.Backends.Common { /// /// A ring buffer that grow if data written to it is too big to fit. /// public class DynamicRingBuffer { private const int RingBufferAlignment = 2048; private readonly object _lock = new(); private MemoryOwner _bufferOwner; private Memory _buffer; private int _size; private int _headOffset; private int _tailOffset; public int Length => _size; public DynamicRingBuffer(int initialCapacity = RingBufferAlignment) { _bufferOwner = MemoryOwner.RentCleared(initialCapacity); _buffer = _bufferOwner.Memory; } public void Clear() { _size = 0; _headOffset = 0; _tailOffset = 0; } public void Clear(int size) { if (size == 0) { return; } lock (_lock) { if (size > _size) { size = _size; } _headOffset = (_headOffset + size) % _buffer.Length; _size -= size; if (_size == 0) { _headOffset = 0; _tailOffset = 0; } } } private void SetCapacityLocked(int capacity) { MemoryOwner newBufferOwner = MemoryOwner.RentCleared(capacity); Memory newBuffer = newBufferOwner.Memory; if (_size > 0) { if (_headOffset < _tailOffset) { _buffer.Slice(_headOffset, _size).CopyTo(newBuffer); } else { _buffer[_headOffset..].CopyTo(newBuffer); _buffer[.._tailOffset].CopyTo(newBuffer[(_buffer.Length - _headOffset)..]); } } _bufferOwner.Dispose(); _bufferOwner = newBufferOwner; _buffer = newBuffer; _headOffset = 0; _tailOffset = _size; } public void Write(ReadOnlySpan buffer, int index, int count) { if (count == 0) { return; } lock (_lock) { if ((_size + count) > _buffer.Length) { SetCapacityLocked(BitUtils.AlignUp(_size + count, RingBufferAlignment)); } if (_headOffset < _tailOffset) { int tailLength = _buffer.Length - _tailOffset; if (tailLength >= count) { buffer.Slice(index, count).CopyTo(_buffer.Span[_tailOffset..]); } else { buffer.Slice(index, tailLength).CopyTo(_buffer.Span[_tailOffset..]); buffer.Slice(index + tailLength, count - tailLength).CopyTo(_buffer.Span); } } else { buffer.Slice(index, count).CopyTo(_buffer.Span[_tailOffset..]); } _size += count; _tailOffset = (_tailOffset + count) % _buffer.Length; } } public int Read(Span buffer, int index, int count) { if (count == 0) { return 0; } lock (_lock) { if (count > _size) { count = _size; } if (_headOffset < _tailOffset) { _buffer.Span.Slice(_headOffset, count).CopyTo(buffer[index..]); } else { int tailLength = _buffer.Length - _headOffset; if (tailLength >= count) { _buffer.Span.Slice(_headOffset, count).CopyTo(buffer[index..]); } else { _buffer.Span.Slice(_headOffset, tailLength).CopyTo(buffer[index..]); _buffer.Span[..(count - tailLength)].CopyTo(buffer[(index + tailLength)..]); } } _size -= count; _headOffset = (_headOffset + count) % _buffer.Length; if (_size == 0) { _headOffset = 0; _tailOffset = 0; } return count; } } } }