File: MemoryBuilder`1.cs
Web Access
Project: src\src\Razor\src\Shared\Microsoft.AspNetCore.Razor.Utilities.Shared\Microsoft.AspNetCore.Razor.Utilities.Shared.csproj (Microsoft.AspNetCore.Razor.Utilities.Shared)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System;
using System.Buffers;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
 
// Inspired by https://github.com/dotnet/runtime/blob/9c7ee976fd771c183e98cf629e3776bba4e45ccc/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/ValueListBuilder.cs
 
namespace Microsoft.AspNetCore.Razor;
 
/// <summary>
///  Temporary builder that uses <see cref="ArrayPool{T}"/> to back a <see cref="Memory{T}"/>.
/// </summary>
internal ref struct MemoryBuilder<T>
{
    private Memory<T> _memory;
    private T[]? _arrayFromPool;
    private int _length;
    private bool _clearArray;
 
    public MemoryBuilder(int initialCapacity = 0, bool clearArray = false)
    {
        ArgHelper.ThrowIfNegative(initialCapacity);
 
        if (initialCapacity > 0)
        {
            _arrayFromPool = ArrayPool<T>.Shared.Rent(initialCapacity);
            _memory = _arrayFromPool;
        }
 
        _clearArray = clearArray;
    }
 
    public void Dispose()
    {
        var toReturn = _arrayFromPool;
        if (toReturn is not null)
        {
            ArrayPool<T>.Shared.Return(toReturn, _clearArray);
 
            _memory = default;
            _arrayFromPool = null;
            _length = 0;
            _clearArray = false;
        }
    }
 
    public readonly bool IsEmpty => _length == 0;
 
    public int Length
    {
        readonly get => _length;
        set
        {
            Debug.Assert(value >= 0);
            Debug.Assert(value <= _memory.Length);
 
            _length = value;
        }
    }
 
    public readonly ref T this[int index]
    {
        get
        {
            Debug.Assert(index >= 0 && index < _length);
 
            return ref _memory.Span[index];
        }
    }
 
    public readonly Memory<T> AsMemory()
        => _memory[.._length];
 
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public void Append(T item)
    {
        var index = _length;
        var memory = _memory;
 
        if ((uint)index < (uint)memory.Length)
        {
            memory.Span[index] = item;
            _length = index + 1;
        }
        else
        {
            AppendWithResize(item);
        }
    }
 
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public void Append(ReadOnlySpan<T> source)
    {
        var index = _length;
        var memory = _memory;
 
        if (source.Length == 1 && (uint)index < (uint)memory.Length)
        {
            memory.Span[index] = source[0];
            _length = index + 1;
        }
        else
        {
            AppendWithResize(source);
        }
    }
 
    [MethodImpl(MethodImplOptions.NoInlining)]
    private void AppendWithResize(T item)
    {
        Debug.Assert(_length == _memory.Length);
        var index = _length;
        Grow(1);
        _memory.Span[index] = item;
        _length = index + 1;
    }
 
    [MethodImpl(MethodImplOptions.NoInlining)]
    private void AppendWithResize(ReadOnlySpan<T> source)
    {
        if ((uint)(_length + source.Length) > (uint)_memory.Length)
        {
            Grow(_memory.Length - _length + source.Length);
        }
 
        source.CopyTo(_memory.Span[_length..]);
        _length += source.Length;
    }
 
    private void Grow(int additionalCapacityRequired = 1)
    {
        Debug.Assert(additionalCapacityRequired > 0);
 
        const int ArrayMaxLength = 0x7FFFFFC7; // same as Array.MaxLength
 
        // Double the size of the array. If it's currently empty, default to size 4.
        var nextCapacity = Math.Max(
            _memory.Length != 0 ? _memory.Length * 2 : 4,
            _memory.Length + additionalCapacityRequired);
 
        // If nextCapacity exceeds the possible length of an array, then we want to downgrade to
        // either ArrayMaxLength, if that's large enough to hold an additional item, or
        // _memory.Length + 1, if that's larger than ArrayMaxLength. Essentially, we don't want
        // to simply clamp to ArrayMaxLength if that isn't actually large enough. Instead, if
        // we've grown too large, we want to OOM when Rent is called below.
        if ((uint)nextCapacity > ArrayMaxLength)
        {
            // Note: it's not possible for _memory.Length + 1 to overflow because that would mean
            // _memory is pointing to an array with length int.MaxValue, which is larger than
            // Array.MaxLength. We would have OOM'd before getting here.
 
            nextCapacity = Math.Max(_memory.Length + 1, ArrayMaxLength);
        }
 
        Debug.Assert(nextCapacity > _memory.Length);
 
        var newArray = ArrayPool<T>.Shared.Rent(nextCapacity);
        _memory.Span.CopyTo(newArray);
 
        var toReturn = _arrayFromPool;
        _memory = newArray;
        _arrayFromPool = newArray;
 
        if (toReturn != null)
        {
            ArrayPool<T>.Shared.Return(toReturn, _clearArray);
        }
    }
 
    public void Push(T item)
    {
        Append(item);
    }
 
    public readonly T Peek()
    {
        return this[^1];
    }
 
    public T Pop()
    {
        var item = this[^1];
        _length--;
 
        return item;
    }
 
    public bool TryPop([MaybeNullWhen(false)] out T result)
    {
        if (IsEmpty)
        {
            result = default;
            return false;
        }
 
        result = Pop();
        return true;
    }
}
 
/// <summary>
///  Encapsulates a method that operates on a <see cref="MemoryBuilder{T}"/> and an argument, typically for building content.
/// </summary>
/// <typeparam name="T">
///  The type of elements in the memory builder.
/// </typeparam>
/// <typeparam name="TArg">
///  The type of the argument passed to the delegate.
/// </typeparam>
/// <param name="builder">
///  A reference to the memory builder to operate on.
/// </param>
/// <param name="arg">
///  The argument to pass to the delegate.
/// </param>
/// <returns>
///  A string result from the operation.
/// </returns>
internal delegate void MemoryBuilderAction<T, in TArg>(ref MemoryBuilder<T> builder, TArg arg);
 
/// <summary>
///  Encapsulates a method that operates on a <see cref="MemoryBuilder{T}"/> and an argument, returning a result of type <typeparamref name="TResult"/>.
/// </summary>
/// <typeparam name="T">
///  The type of elements in the memory builder.
/// </typeparam>
/// <typeparam name="TArg">
///  The type of the argument passed to the delegate.
/// </typeparam>
/// <typeparam name="TResult">
///  The type of the result returned by the delegate.
/// </typeparam>
/// <param name="builder">
///  A reference to the memory builder to operate on.
/// </param>
/// <param name="arg">
///  The argument to pass to the delegate.
/// </param>
/// <returns>
///  A result of type <typeparamref name="TResult"/> from the operation.
/// </returns>
internal delegate TResult MemoryBuilderFunc<T, in TArg, out TResult>(ref MemoryBuilder<T> builder, TArg arg);