Skip to content

Commit

Permalink
Tweak ImmutableArrayBuilder<T> type
Browse files Browse the repository at this point in the history
  • Loading branch information
Sergio0694 committed May 16, 2023
1 parent f87610a commit 161519c
Showing 1 changed file with 59 additions and 111 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,36 +16,44 @@ namespace CommunityToolkit.Mvvm.SourceGenerators.Helpers;
/// A helper type to build sequences of values with pooled buffers.
/// </summary>
/// <typeparam name="T">The type of items to create sequences for.</typeparam>
internal struct ImmutableArrayBuilder<T> : IDisposable
internal ref struct ImmutableArrayBuilder<T>
{
/// <summary>
/// The rented <see cref="Writer"/> instance to use.
/// The underlying <typeparamref name="T"/> array.
/// </summary>
private Writer? writer;
private T?[]? array;

/// <summary>
/// The starting offset within <see cref="array"/>.
/// </summary>
private int index;

/// <summary>
/// Creates a <see cref="ImmutableArrayBuilder{T}"/> value with a pooled underlying data writer.
/// </summary>
/// <returns>A <see cref="ImmutableArrayBuilder{T}"/> instance to write data to.</returns>
public static ImmutableArrayBuilder<T> Rent()
{
return new(new Writer());
T?[] array = ArrayPool<T?>.Shared.Rent(typeof(T) == typeof(char) ? 1024 : 8);

return new(array);
}

/// <summary>
/// Creates a new <see cref="ImmutableArrayBuilder{T}"/> object with the specified parameters.
/// </summary>
/// <param name="writer">The target data writer to use.</param>
private ImmutableArrayBuilder(Writer writer)
/// <param name="array">The initial array to use as backing storage.</param>
private ImmutableArrayBuilder(T?[] array)
{
this.writer = writer;
this.array = array;
this.index = 0;
}

/// <inheritdoc cref="ImmutableArray{T}.Builder.Count"/>
public int Count
public readonly int Count
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => this.writer!.Count;
get => this.index;
}

/// <summary>
Expand All @@ -54,154 +62,94 @@ public int Count
public readonly ReadOnlySpan<T> WrittenSpan
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => this.writer!.WrittenSpan;
get => new(this.array!, 0, this.index);
}

/// <inheritdoc cref="ImmutableArray{T}.Builder.Add(T)"/>
public readonly void Add(T item)
public void Add(T item)
{
this.writer!.Add(item);
EnsureCapacity(1);

this.array![this.index++] = item;
}

/// <summary>
/// Adds the specified items to the end of the array.
/// </summary>
/// <param name="items">The items to add at the end of the array.</param>
public readonly void AddRange(ReadOnlySpan<T> items)
public void AddRange(ReadOnlySpan<T> items)
{
this.writer!.AddRange(items);
EnsureCapacity(items.Length);

items.CopyTo(this.array!.AsSpan(this.index)!);

this.index += items.Length;
}

/// <inheritdoc cref="ImmutableArray{T}.Builder.ToImmutable"/>
public readonly ImmutableArray<T> ToImmutable()
{
T[] array = this.writer!.WrittenSpan.ToArray();
T[] array = new ReadOnlySpan<T>(this.array!, 0, this.index).ToArray();

return Unsafe.As<T[], ImmutableArray<T>>(ref array);
}

/// <inheritdoc cref="ImmutableArray{T}.Builder.ToArray"/>
public readonly T[] ToArray()
{
return this.writer!.WrittenSpan.ToArray();
return new ReadOnlySpan<T>(this.array!, 0, this.index).ToArray();
}

/// <inheritdoc/>
public override readonly string ToString()
{
return this.writer!.WrittenSpan.ToString();
return new ReadOnlySpan<T>(this.array!, 0, this.index).ToString();
}

/// <inheritdoc/>
/// <inheritdoc cref="IDisposable.Dispose"/>
public void Dispose()
{
Writer? writer = this.writer;
T?[]? array = this.array;

this.writer = null;
this.array = null;
this.index = 0;

writer?.Dispose();
if (array is not null)
{
ArrayPool<T?>.Shared.Return(array, clearArray: typeof(T) != typeof(char));
}
}

/// <summary>
/// A class handling the actual buffer writing.
/// Ensures that <see cref="array"/> has enough free space to contain a given number of new items.
/// </summary>
private sealed class Writer : IDisposable
/// <param name="requestedSize">The minimum number of items to ensure space for in <see cref="array"/>.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void EnsureCapacity(int requestedSize)
{
/// <summary>
/// The underlying <typeparamref name="T"/> array.
/// </summary>
private T?[]? array;

/// <summary>
/// The starting offset within <see cref="array"/>.
/// </summary>
private int index;

/// <summary>
/// Creates a new <see cref="Writer"/> instance with the specified parameters.
/// </summary>
public Writer()
{
this.array = ArrayPool<T?>.Shared.Rent(typeof(T) == typeof(char) ? 1024 : 8);
this.index = 0;
}

/// <inheritdoc cref="ImmutableArrayBuilder{T}.Count"/>
public int Count
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => this.index;
}

/// <inheritdoc cref="ImmutableArrayBuilder{T}.WrittenSpan"/>
public ReadOnlySpan<T> WrittenSpan
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new(this.array!, 0, this.index);
}

/// <inheritdoc cref="ImmutableArrayBuilder{T}.Add"/>
public void Add(T value)
{
EnsureCapacity(1);

this.array![this.index++] = value;
}

/// <inheritdoc cref="ImmutableArrayBuilder{T}.AddRange"/>
public void AddRange(ReadOnlySpan<T> items)
{
EnsureCapacity(items.Length);

items.CopyTo(this.array.AsSpan(this.index)!);

this.index += items.Length;
}

/// <inheritdoc/>
public void Dispose()
{
T?[]? array = this.array;

this.array = null;

if (array is not null)
{
ArrayPool<T?>.Shared.Return(array, clearArray: typeof(T) != typeof(char));
}
}

/// <summary>
/// Ensures that <see cref="array"/> has enough free space to contain a given number of new items.
/// </summary>
/// <param name="requestedSize">The minimum number of items to ensure space for in <see cref="array"/>.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void EnsureCapacity(int requestedSize)
if (requestedSize > this.array!.Length - this.index)
{
if (requestedSize > this.array!.Length - this.index)
{
ResizeBuffer(requestedSize);
}
ResizeBuffer(requestedSize);
}
}

/// <summary>
/// Resizes <see cref="array"/> to ensure it can fit the specified number of new items.
/// </summary>
/// <param name="sizeHint">The minimum number of items to ensure space for in <see cref="array"/>.</param>
[MethodImpl(MethodImplOptions.NoInlining)]
private void ResizeBuffer(int sizeHint)
{
int minimumSize = this.index + sizeHint;
/// <summary>
/// Resizes <see cref="array"/> to ensure it can fit the specified number of new items.
/// </summary>
/// <param name="sizeHint">The minimum number of items to ensure space for in <see cref="array"/>.</param>
[MethodImpl(MethodImplOptions.NoInlining)]
private void ResizeBuffer(int sizeHint)
{
int minimumSize = this.index + sizeHint;

T?[] oldArray = this.array!;
T?[] newArray = ArrayPool<T?>.Shared.Rent(minimumSize);
T?[] oldArray = this.array!;
T?[] newArray = ArrayPool<T?>.Shared.Rent(minimumSize);

Array.Copy(oldArray, newArray, this.index);
Array.Copy(oldArray, newArray, this.index);

this.array = newArray;
this.array = newArray;

ArrayPool<T?>.Shared.Return(oldArray, clearArray: typeof(T) != typeof(char));
}
ArrayPool<T?>.Shared.Return(oldArray, clearArray: typeof(T) != typeof(char));
}
}

Expand Down

0 comments on commit 161519c

Please sign in to comment.