#nullable enable using System; using System.Buffers; using System.Threading; using System.Threading.Tasks; namespace Ayu.Discord.Voice { public sealed class PoopyBufferImmortalized : ISongBuffer { private readonly byte[] _buffer; private readonly byte[] _outputArray; private CancellationToken _cancellationToken; private bool _isStopped; public int ReadPosition { get; private set; } public int WritePosition { get; private set; } public int ContentLength => WritePosition >= ReadPosition ? WritePosition - ReadPosition : (_buffer.Length - ReadPosition) + WritePosition; public int FreeSpace => _buffer.Length - ContentLength; public bool Stopped => _cancellationToken.IsCancellationRequested || _isStopped; public PoopyBufferImmortalized(int frameSize) { _buffer = ArrayPool.Shared.Rent(1_000_000); _outputArray = new byte[frameSize]; ReadPosition = 0; WritePosition = 0; } public void Stop() => _isStopped = true; // this method needs a rewrite public Task BufferAsync(ITrackDataSource source, CancellationToken cancellationToken) { _cancellationToken = cancellationToken; var bufferingCompleted = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); Task.Run(async () => { var output = ArrayPool.Shared.Rent(38400); try { int read; while (!Stopped && (read = source.Read(output)) > 0) { while (!Stopped && FreeSpace <= read) { bufferingCompleted.TrySetResult(true); await Task.Delay(100, cancellationToken).ConfigureAwait(false); } if (Stopped) break; Write(output, read); } } finally { ArrayPool.Shared.Return(output); bufferingCompleted.TrySetResult(true); } }, cancellationToken); return bufferingCompleted.Task; } private void Write(byte[] input, int writeCount) { if (WritePosition + writeCount < _buffer.Length) { Buffer.BlockCopy(input, 0, _buffer, WritePosition, writeCount); WritePosition += writeCount; return; } var wroteNormally = _buffer.Length - WritePosition; Buffer.BlockCopy(input, 0, _buffer, WritePosition, wroteNormally); var wroteFromStart = writeCount - wroteNormally; Buffer.BlockCopy(input, wroteNormally, _buffer, 0, wroteFromStart); WritePosition = wroteFromStart; } public Span Read(int count, out int length) { var toRead = Math.Min(ContentLength, count); var wp = WritePosition; if (ContentLength == 0) { length = 0; return Span.Empty; } if (wp > ReadPosition || ReadPosition + toRead <= _buffer.Length) { // thsi can be achieved without copying if // writer never writes until the end, // but leaves a single chunk free Span toReturn = _outputArray; ((Span) _buffer).Slice(ReadPosition, toRead).CopyTo(toReturn); ReadPosition += toRead; length = toRead; return toReturn; } else { Span toReturn = _outputArray; var toEnd = _buffer.Length - ReadPosition; var bufferSpan = (Span) _buffer; bufferSpan.Slice(ReadPosition, toEnd).CopyTo(toReturn); var fromStart = toRead - toEnd; bufferSpan.Slice(0, fromStart).CopyTo(toReturn.Slice(toEnd)); ReadPosition = fromStart; length = toEnd + fromStart; return toReturn; } } public void Dispose() => ArrayPool.Shared.Return(_buffer); public void Reset() { ReadPosition = 0; WritePosition = 0; } } }