Last active
February 13, 2023 19:26
-
-
Save neon-sunset/fbff70d340bdaa8551ae703a185e4d95 to your computer and use it in GitHub Desktop.
A small helper to wrap a common pattern of stackalloc with fallback to array pool into a single data structure.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // This code is licensed under MIT license | |
| // (c) 2022-2023 neon-sunset | |
| using System.Buffers; | |
| using System.Diagnostics.CodeAnalysis; | |
| using System.Runtime.CompilerServices; | |
| using System.Runtime.InteropServices; | |
| using System.Text; | |
| [StructLayout(LayoutKind.Auto)] | |
| public ref struct Buffer<T> | |
| { | |
| private T[]? _rented; | |
| public Span<T> Span { get; } | |
| [MethodImpl(MethodImplOptions.AggressiveInlining)] | |
| public Buffer(Span<T> initialBuffer, int length) | |
| { | |
| if (length <= 0) | |
| { | |
| LengthOutOfRange(); | |
| } | |
| else if (length <= initialBuffer.Length) | |
| { | |
| _rented = null; | |
| Span = initialBuffer[..length]; | |
| } | |
| else | |
| { | |
| _rented = ArrayPool<T>.Shared.Rent(length); | |
| Span = _rented.AsSpan(..length); | |
| } | |
| } | |
| [MethodImpl(MethodImplOptions.AggressiveInlining)] | |
| internal Buffer(T[]? rented, int length) | |
| { | |
| _rented = rented; | |
| Span = rented.AsSpan(..length); | |
| } | |
| [MethodImpl(MethodImplOptions.AggressiveInlining)] | |
| public Buffer(int length) | |
| { | |
| if (length <= 0) | |
| { | |
| LengthOutOfRange(); | |
| } | |
| _rented = ArrayPool<T>.Shared.Rent(length); | |
| Span = _rented.AsSpan(..length); | |
| } | |
| [MethodImpl(MethodImplOptions.AggressiveInlining)] | |
| public static implicit operator Span<T>(Buffer<T> buffer) => buffer.Span; | |
| [MethodImpl(MethodImplOptions.AggressiveInlining)] | |
| public static implicit operator ReadOnlySpan<T>(Buffer<T> buffer) => buffer.Span; | |
| [MethodImpl(MethodImplOptions.AggressiveInlining)] | |
| public void Dispose() | |
| { | |
| if (_rented != null) | |
| { | |
| (T[] pooled, _rented) = (_rented, null); | |
| ReturnPooled(pooled); | |
| } | |
| } | |
| // To help JIT in case this is a cold path to not bloat codegen in the finally block | |
| private static void ReturnPooled(T[] pooled) => ArrayPool<T>.Shared.Return(pooled); | |
| [DoesNotReturn] | |
| private static void LengthOutOfRange() | |
| { | |
| throw new ArgumentOutOfRangeException("length"); | |
| } | |
| } | |
| public static class Buffer | |
| { | |
| public static Buffer<T> Create<T>(Span<T> initialBuffer, int length) => new(initialBuffer, length); | |
| public static Buffer<T> Create<T>(int length) => new(length); | |
| public static Buffer<byte> Encode(ReadOnlySpan<char> source, Encoding encoding, Span<byte> initialBuffer = default) | |
| { | |
| byte[]? toReturn = null; | |
| var maxLength = encoding.GetMaxByteCount(source.Length); | |
| var destination = initialBuffer.Length >= maxLength | |
| ? initialBuffer | |
| : (toReturn = ArrayPool<byte>.Shared.Rent(maxLength)); | |
| var bytesWritten = encoding.GetBytes(source, destination); | |
| return toReturn is null | |
| ? new Buffer<byte>(destination, bytesWritten) | |
| : new Buffer<byte>(toReturn, bytesWritten); | |
| } | |
| public static Buffer<char> Decode(ReadOnlySpan<byte> source, Encoding encoding, Span<char> initialBuffer = default) | |
| { | |
| char[]? toReturn = null; | |
| var maxLength = encoding.GetMaxCharCount(source.Length); | |
| var destination = initialBuffer.Length >= maxLength | |
| ? initialBuffer | |
| : (toReturn = ArrayPool<char>.Shared.Rent(maxLength)); | |
| var bytesWritten = encoding.GetChars(source, destination); | |
| return toReturn is null | |
| ? new Buffer<char>(destination, bytesWritten) | |
| : new Buffer<char>(toReturn, bytesWritten); | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment