parent
d668e77e9e
commit
6c80c42511
|
|
@ -73,6 +73,7 @@ namespace Microsoft.AspNetCore.Blazor.Build.Test
|
||||||
"StandaloneApp.dll",
|
"StandaloneApp.dll",
|
||||||
"StandaloneApp.pdb",
|
"StandaloneApp.pdb",
|
||||||
"System.dll",
|
"System.dll",
|
||||||
|
"System.Buffers.dll",
|
||||||
"System.Collections.Concurrent.dll",
|
"System.Collections.Concurrent.dll",
|
||||||
"System.Collections.dll",
|
"System.Collections.dll",
|
||||||
"System.ComponentModel.Composition.dll",
|
"System.ComponentModel.Composition.dll",
|
||||||
|
|
|
||||||
|
|
@ -763,7 +763,7 @@ namespace Microsoft.AspNetCore.Components.RenderTree
|
||||||
public ArrayRange(T[] array, int count) { throw null; }
|
public ArrayRange(T[] array, int count) { throw null; }
|
||||||
public Microsoft.AspNetCore.Components.RenderTree.ArrayRange<T> Clone() { throw null; }
|
public Microsoft.AspNetCore.Components.RenderTree.ArrayRange<T> Clone() { throw null; }
|
||||||
}
|
}
|
||||||
public partial class RenderTreeBuilder
|
public partial class RenderTreeBuilder : System.IDisposable
|
||||||
{
|
{
|
||||||
public const string ChildContent = "ChildContent";
|
public const string ChildContent = "ChildContent";
|
||||||
public RenderTreeBuilder(Microsoft.AspNetCore.Components.Rendering.Renderer renderer) { }
|
public RenderTreeBuilder(Microsoft.AspNetCore.Components.Rendering.Renderer renderer) { }
|
||||||
|
|
@ -794,6 +794,7 @@ namespace Microsoft.AspNetCore.Components.RenderTree
|
||||||
public void OpenElement(int sequence, string elementName) { }
|
public void OpenElement(int sequence, string elementName) { }
|
||||||
public void SetKey(object value) { }
|
public void SetKey(object value) { }
|
||||||
public void SetUpdatesAttributeName(string updatesAttributeName) { }
|
public void SetUpdatesAttributeName(string updatesAttributeName) { }
|
||||||
|
void System.IDisposable.Dispose() { }
|
||||||
}
|
}
|
||||||
[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
|
[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
|
||||||
public readonly partial struct RenderTreeDiff
|
public readonly partial struct RenderTreeDiff
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,8 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Buffers;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Components.RenderTree
|
namespace Microsoft.AspNetCore.Components.RenderTree
|
||||||
|
|
@ -15,26 +17,25 @@ namespace Microsoft.AspNetCore.Components.RenderTree
|
||||||
/// components can be long-lived and re-render frequently, with the rendered size
|
/// components can be long-lived and re-render frequently, with the rendered size
|
||||||
/// varying dramatically depending on the user's navigation in the app.
|
/// varying dramatically depending on the user's navigation in the app.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal class ArrayBuilder<T>
|
internal class ArrayBuilder<T> : IDisposable
|
||||||
{
|
{
|
||||||
private const int MinCapacity = 10;
|
// The following fields are memory mapped to the WASM client. Do not re-order or use auto-properties.
|
||||||
private T[] _items;
|
private T[] _items;
|
||||||
private int _itemsInUse;
|
private int _itemsInUse;
|
||||||
|
|
||||||
/// <summary>
|
private static readonly T[] Empty = Array.Empty<T>();
|
||||||
/// Constructs a new instance of <see cref="ArrayBuilder{T}"/>.
|
private readonly ArrayPool<T> _arrayPool;
|
||||||
/// </summary>
|
private readonly int _minCapacity;
|
||||||
public ArrayBuilder() : this(MinCapacity)
|
private bool _disposed;
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Constructs a new instance of <see cref="ArrayBuilder{T}"/>.
|
/// Constructs a new instance of <see cref="ArrayBuilder{T}"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ArrayBuilder(int capacity)
|
public ArrayBuilder(int minCapacity = 32, ArrayPool<T> arrayPool = null)
|
||||||
{
|
{
|
||||||
_items = new T[capacity < MinCapacity ? MinCapacity : capacity];
|
_arrayPool = arrayPool ?? ArrayPool<T>.Shared;
|
||||||
_itemsInUse = 0;
|
_minCapacity = minCapacity;
|
||||||
|
_items = Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -57,7 +58,7 @@ namespace Microsoft.AspNetCore.Components.RenderTree
|
||||||
{
|
{
|
||||||
if (_itemsInUse == _items.Length)
|
if (_itemsInUse == _items.Length)
|
||||||
{
|
{
|
||||||
SetCapacity(_items.Length * 2, preserveContents: true);
|
GrowBuffer(_items.Length * 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
var indexOfAppendedItem = _itemsInUse++;
|
var indexOfAppendedItem = _itemsInUse++;
|
||||||
|
|
@ -72,13 +73,13 @@ namespace Microsoft.AspNetCore.Components.RenderTree
|
||||||
var requiredCapacity = _itemsInUse + length;
|
var requiredCapacity = _itemsInUse + length;
|
||||||
if (_items.Length < requiredCapacity)
|
if (_items.Length < requiredCapacity)
|
||||||
{
|
{
|
||||||
var candidateCapacity = _items.Length * 2;
|
var candidateCapacity = Math.Max(_items.Length * 2, _minCapacity);
|
||||||
while (candidateCapacity < requiredCapacity)
|
while (candidateCapacity < requiredCapacity)
|
||||||
{
|
{
|
||||||
candidateCapacity *= 2;
|
candidateCapacity *= 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
SetCapacity(candidateCapacity, preserveContents: true);
|
GrowBuffer(candidateCapacity);
|
||||||
}
|
}
|
||||||
|
|
||||||
Array.Copy(source, startIndex, _items, _itemsInUse, length);
|
Array.Copy(source, startIndex, _items, _itemsInUse, length);
|
||||||
|
|
@ -95,34 +96,51 @@ namespace Microsoft.AspNetCore.Components.RenderTree
|
||||||
/// <param name="value">The value.</param>
|
/// <param name="value">The value.</param>
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public void Overwrite(int index, in T value)
|
public void Overwrite(int index, in T value)
|
||||||
=> _items[index] = value;
|
{
|
||||||
|
if (index > _itemsInUse)
|
||||||
|
{
|
||||||
|
ThrowIndexOutOfBoundsException();
|
||||||
|
}
|
||||||
|
|
||||||
|
_items[index] = value;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Removes the last item.
|
/// Removes the last item.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void RemoveLast()
|
public void RemoveLast()
|
||||||
{
|
{
|
||||||
|
if (_itemsInUse == 0)
|
||||||
|
{
|
||||||
|
ThrowIndexOutOfBoundsException();
|
||||||
|
}
|
||||||
|
|
||||||
_itemsInUse--;
|
_itemsInUse--;
|
||||||
_items[_itemsInUse] = default(T); // Release to GC
|
_items[_itemsInUse] = default; // Release to GC
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Inserts the item at the specified index, moving the contents of the subsequent entries along by one.
|
/// Inserts the item at the specified index, moving the contents of the subsequent entries along by one.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="insertAtIndex">The index at which the value is to be inserted.</param>
|
/// <param name="index">The index at which the value is to be inserted.</param>
|
||||||
/// <param name="value">The value to insert.</param>
|
/// <param name="value">The value to insert.</param>
|
||||||
public void InsertExpensive(int insertAtIndex, T value)
|
public void InsertExpensive(int index, T value)
|
||||||
{
|
{
|
||||||
|
if (index > _itemsInUse)
|
||||||
|
{
|
||||||
|
ThrowIndexOutOfBoundsException();
|
||||||
|
}
|
||||||
|
|
||||||
// Same expansion logic as elsewhere
|
// Same expansion logic as elsewhere
|
||||||
if (_itemsInUse == _items.Length)
|
if (_itemsInUse == _items.Length)
|
||||||
{
|
{
|
||||||
SetCapacity(_items.Length * 2, preserveContents: true);
|
GrowBuffer(_items.Length * 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
Array.Copy(_items, insertAtIndex, _items, insertAtIndex + 1, _itemsInUse - insertAtIndex);
|
Array.Copy(_items, index, _items, index + 1, _itemsInUse - index);
|
||||||
_itemsInUse++;
|
_itemsInUse++;
|
||||||
|
|
||||||
_items[insertAtIndex] = value;
|
_items[index] = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -131,17 +149,9 @@ namespace Microsoft.AspNetCore.Components.RenderTree
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void Clear()
|
public void Clear()
|
||||||
{
|
{
|
||||||
var previousItemsInUse = _itemsInUse;
|
ReturnBuffer();
|
||||||
|
_items = Empty;
|
||||||
_itemsInUse = 0;
|
_itemsInUse = 0;
|
||||||
|
|
||||||
if (_items.Length > previousItemsInUse * 1.5)
|
|
||||||
{
|
|
||||||
SetCapacity((previousItemsInUse + _items.Length) / 2, preserveContents: false);
|
|
||||||
}
|
|
||||||
else if (previousItemsInUse > 0)
|
|
||||||
{
|
|
||||||
Array.Clear(_items, 0, previousItemsInUse); // Release to GC
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -160,29 +170,42 @@ namespace Microsoft.AspNetCore.Components.RenderTree
|
||||||
public ArrayBuilderSegment<T> ToSegment(int fromIndexInclusive, int toIndexExclusive)
|
public ArrayBuilderSegment<T> ToSegment(int fromIndexInclusive, int toIndexExclusive)
|
||||||
=> new ArrayBuilderSegment<T>(this, fromIndexInclusive, toIndexExclusive - fromIndexInclusive);
|
=> new ArrayBuilderSegment<T>(this, fromIndexInclusive, toIndexExclusive - fromIndexInclusive);
|
||||||
|
|
||||||
private void SetCapacity(int desiredCapacity, bool preserveContents)
|
private void GrowBuffer(int desiredCapacity)
|
||||||
{
|
{
|
||||||
if (desiredCapacity < _itemsInUse)
|
var newCapacity = Math.Max(desiredCapacity, _minCapacity);
|
||||||
{
|
Debug.Assert(newCapacity > _items.Length);
|
||||||
throw new ArgumentOutOfRangeException(nameof(desiredCapacity), $"The value cannot be less than {nameof(Count)}");
|
|
||||||
}
|
|
||||||
|
|
||||||
var newCapacity = desiredCapacity < MinCapacity ? MinCapacity : desiredCapacity;
|
var newItems = _arrayPool.Rent(newCapacity);
|
||||||
if (newCapacity != _items.Length)
|
Array.Copy(_items, newItems, _itemsInUse);
|
||||||
{
|
|
||||||
var newItems = new T[newCapacity];
|
|
||||||
|
|
||||||
if (preserveContents)
|
// Return the old buffer and start using the new buffer
|
||||||
{
|
ReturnBuffer();
|
||||||
Array.Copy(_items, newItems, _itemsInUse);
|
_items = newItems;
|
||||||
}
|
}
|
||||||
|
|
||||||
_items = newItems;
|
private void ReturnBuffer()
|
||||||
}
|
{
|
||||||
else if (!preserveContents)
|
if (!ReferenceEquals(_items, Empty))
|
||||||
{
|
{
|
||||||
Array.Clear(_items, 0, _items.Length);
|
// ArrayPool<>.Return with clearArray: true calls Array.Clear on the entire buffer.
|
||||||
|
// In the most common case, _itemsInUse would be much smaller than _items.Length so we'll specifically clear that subset.
|
||||||
|
Array.Clear(_items, 0, _itemsInUse);
|
||||||
|
_arrayPool.Return(_items);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (!_disposed)
|
||||||
|
{
|
||||||
|
_disposed = true;
|
||||||
|
ReturnBuffer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ThrowIndexOutOfBoundsException()
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException("index");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ namespace Microsoft.AspNetCore.Components.RenderTree
|
||||||
/// <typeparam name="T">The type of the elements in the array</typeparam>
|
/// <typeparam name="T">The type of the elements in the array</typeparam>
|
||||||
public readonly struct ArrayBuilderSegment<T> : IEnumerable<T>
|
public readonly struct ArrayBuilderSegment<T> : IEnumerable<T>
|
||||||
{
|
{
|
||||||
|
// The following fields are memory mapped to the WASM client. Do not re-order or use auto-properties.
|
||||||
private readonly ArrayBuilder<T> _builder;
|
private readonly ArrayBuilder<T> _builder;
|
||||||
private readonly int _offset;
|
private readonly int _offset;
|
||||||
private readonly int _count;
|
private readonly int _count;
|
||||||
|
|
|
||||||
|
|
@ -17,14 +17,14 @@ namespace Microsoft.AspNetCore.Components.RenderTree
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Provides methods for building a collection of <see cref="RenderTreeFrame"/> entries.
|
/// Provides methods for building a collection of <see cref="RenderTreeFrame"/> entries.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class RenderTreeBuilder
|
public class RenderTreeBuilder : IDisposable
|
||||||
{
|
{
|
||||||
private readonly static object BoxedTrue = true;
|
private readonly static object BoxedTrue = true;
|
||||||
private readonly static object BoxedFalse = false;
|
private readonly static object BoxedFalse = false;
|
||||||
private readonly static string ComponentReferenceCaptureInvalidParentMessage = $"Component reference captures may only be added as children of frames of type {RenderTreeFrameType.Component}";
|
private readonly static string ComponentReferenceCaptureInvalidParentMessage = $"Component reference captures may only be added as children of frames of type {RenderTreeFrameType.Component}";
|
||||||
|
|
||||||
private readonly Renderer _renderer;
|
private readonly Renderer _renderer;
|
||||||
private readonly ArrayBuilder<RenderTreeFrame> _entries = new ArrayBuilder<RenderTreeFrame>(10);
|
private readonly ArrayBuilder<RenderTreeFrame> _entries = new ArrayBuilder<RenderTreeFrame>();
|
||||||
private readonly Stack<int> _openElementIndices = new Stack<int>();
|
private readonly Stack<int> _openElementIndices = new Stack<int>();
|
||||||
private RenderTreeFrameType? _lastNonAttributeFrameType;
|
private RenderTreeFrameType? _lastNonAttributeFrameType;
|
||||||
private bool _hasSeenAddMultipleAttributes;
|
private bool _hasSeenAddMultipleAttributes;
|
||||||
|
|
@ -796,5 +796,10 @@ namespace Microsoft.AspNetCore.Components.RenderTree
|
||||||
var seenAttributeNames = (_seenAttributeNames ??= new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase));
|
var seenAttributeNames = (_seenAttributeNames ??= new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase));
|
||||||
seenAttributeNames[name] = _entries.Count; // See comment in ProcessAttributes for why this is OK.
|
seenAttributeNames[name] = _entries.Count; // See comment in ProcessAttributes for why this is OK.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void IDisposable.Dispose()
|
||||||
|
{
|
||||||
|
_entries.Dispose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
||||||
/// within the context of a <see cref="Renderer"/>. This is an internal implementation
|
/// within the context of a <see cref="Renderer"/>. This is an internal implementation
|
||||||
/// detail of <see cref="Renderer"/>.
|
/// detail of <see cref="Renderer"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal class ComponentState
|
internal class ComponentState : IDisposable
|
||||||
{
|
{
|
||||||
private readonly Renderer _renderer;
|
private readonly Renderer _renderer;
|
||||||
private readonly IReadOnlyList<CascadingParameterState> _cascadingParameters;
|
private readonly IReadOnlyList<CascadingParameterState> _cascadingParameters;
|
||||||
|
|
@ -91,6 +91,8 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
||||||
{
|
{
|
||||||
RemoveCascadingParameterSubscriptions();
|
RemoveCascadingParameterSubscriptions();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DisposeBuffers();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task NotifyRenderCompletedAsync()
|
public Task NotifyRenderCompletedAsync()
|
||||||
|
|
@ -172,5 +174,22 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
DisposeBuffers();
|
||||||
|
|
||||||
|
if (Component is IDisposable disposable)
|
||||||
|
{
|
||||||
|
disposable.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DisposeBuffers()
|
||||||
|
{
|
||||||
|
((IDisposable)_renderTreeBuilderPrevious).Dispose();
|
||||||
|
((IDisposable)CurrrentRenderTree).Dispose();
|
||||||
|
_latestDirectParametersSnapshot?.Dispose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Microsoft.AspNetCore.Components.RenderTree;
|
using Microsoft.AspNetCore.Components.RenderTree;
|
||||||
|
|
||||||
|
|
@ -12,7 +13,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
||||||
/// and the intermediate states (such as the queue of components still to
|
/// and the intermediate states (such as the queue of components still to
|
||||||
/// be rendered).
|
/// be rendered).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal class RenderBatchBuilder
|
internal class RenderBatchBuilder : IDisposable
|
||||||
{
|
{
|
||||||
// Primary result data
|
// Primary result data
|
||||||
public ArrayBuilder<RenderTreeDiff> UpdatedComponentDiffs { get; } = new ArrayBuilder<RenderTreeDiff>();
|
public ArrayBuilder<RenderTreeDiff> UpdatedComponentDiffs { get; } = new ArrayBuilder<RenderTreeDiff>();
|
||||||
|
|
@ -20,8 +21,8 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
||||||
public ArrayBuilder<int> DisposedEventHandlerIds { get; } = new ArrayBuilder<int>();
|
public ArrayBuilder<int> DisposedEventHandlerIds { get; } = new ArrayBuilder<int>();
|
||||||
|
|
||||||
// Buffers referenced by UpdatedComponentDiffs
|
// Buffers referenced by UpdatedComponentDiffs
|
||||||
public ArrayBuilder<RenderTreeEdit> EditsBuffer { get; } = new ArrayBuilder<RenderTreeEdit>();
|
public ArrayBuilder<RenderTreeEdit> EditsBuffer { get; } = new ArrayBuilder<RenderTreeEdit>(64);
|
||||||
public ArrayBuilder<RenderTreeFrame> ReferenceFramesBuffer { get; } = new ArrayBuilder<RenderTreeFrame>();
|
public ArrayBuilder<RenderTreeFrame> ReferenceFramesBuffer { get; } = new ArrayBuilder<RenderTreeFrame>(64);
|
||||||
|
|
||||||
// State of render pipeline
|
// State of render pipeline
|
||||||
public Queue<RenderQueueEntry> ComponentRenderQueue { get; } = new Queue<RenderQueueEntry>();
|
public Queue<RenderQueueEntry> ComponentRenderQueue { get; } = new Queue<RenderQueueEntry>();
|
||||||
|
|
@ -56,5 +57,14 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
||||||
ReferenceFramesBuffer.ToRange(),
|
ReferenceFramesBuffer.ToRange(),
|
||||||
DisposedComponentIds.ToRange(),
|
DisposedComponentIds.ToRange(),
|
||||||
DisposedEventHandlerIds.ToRange());
|
DisposedEventHandlerIds.ToRange());
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
EditsBuffer.Dispose();
|
||||||
|
ReferenceFramesBuffer.Dispose();
|
||||||
|
UpdatedComponentDiffs.Dispose();
|
||||||
|
DisposedComponentIds.Dispose();
|
||||||
|
DisposedEventHandlerIds.Dispose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -663,13 +663,15 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
disposable.Dispose();
|
componentState.Dispose();
|
||||||
}
|
}
|
||||||
catch (Exception exception)
|
catch (Exception exception)
|
||||||
{
|
{
|
||||||
HandleException(exception);
|
HandleException(exception);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_batchBuilder.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@ using System.Reflection;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Components.RenderTree;
|
using Microsoft.AspNetCore.Components.RenderTree;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Extensions.Logging.Abstractions;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Components.Routing
|
namespace Microsoft.AspNetCore.Components.Routing
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -13,11 +13,12 @@ using Xunit;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Components.Test
|
namespace Microsoft.AspNetCore.Components.Test
|
||||||
{
|
{
|
||||||
public class RenderTreeDiffBuilderTest
|
public class RenderTreeDiffBuilderTest : IDisposable
|
||||||
{
|
{
|
||||||
private readonly Renderer renderer;
|
private readonly Renderer renderer;
|
||||||
private readonly RenderTreeBuilder oldTree;
|
private readonly RenderTreeBuilder oldTree;
|
||||||
private readonly RenderTreeBuilder newTree;
|
private readonly RenderTreeBuilder newTree;
|
||||||
|
private RenderBatchBuilder batchBuilder;
|
||||||
|
|
||||||
public RenderTreeDiffBuilderTest()
|
public RenderTreeDiffBuilderTest()
|
||||||
{
|
{
|
||||||
|
|
@ -26,6 +27,14 @@ namespace Microsoft.AspNetCore.Components.Test
|
||||||
newTree = new RenderTreeBuilder(renderer);
|
newTree = new RenderTreeBuilder(renderer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void IDisposable.Dispose()
|
||||||
|
{
|
||||||
|
renderer.Dispose();
|
||||||
|
((IDisposable)oldTree).Dispose();
|
||||||
|
((IDisposable)newTree).Dispose();
|
||||||
|
batchBuilder?.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[MemberData(nameof(RecognizesEquivalentFramesAsSameCases))]
|
[MemberData(nameof(RecognizesEquivalentFramesAsSameCases))]
|
||||||
public void RecognizesEquivalentFramesAsSame(RenderFragment appendFragment)
|
public void RecognizesEquivalentFramesAsSame(RenderFragment appendFragment)
|
||||||
|
|
@ -208,7 +217,8 @@ namespace Microsoft.AspNetCore.Components.Test
|
||||||
oldTree.SetKey("retained key");
|
oldTree.SetKey("retained key");
|
||||||
oldTree.AddAttribute(1, "ParamName", "Param old value");
|
oldTree.AddAttribute(1, "ParamName", "Param old value");
|
||||||
oldTree.CloseComponent();
|
oldTree.CloseComponent();
|
||||||
GetRenderedBatch(new RenderTreeBuilder(renderer), oldTree, false); // Assign initial IDs
|
using var initial = new RenderTreeBuilder(renderer);
|
||||||
|
GetRenderedBatch(initial, oldTree, false); // Assign initial IDs
|
||||||
var oldComponent = GetComponents<CaptureSetParametersComponent>(oldTree).Single();
|
var oldComponent = GetComponents<CaptureSetParametersComponent>(oldTree).Single();
|
||||||
|
|
||||||
newTree.OpenComponent<CaptureSetParametersComponent>(0);
|
newTree.OpenComponent<CaptureSetParametersComponent>(0);
|
||||||
|
|
@ -227,12 +237,12 @@ namespace Microsoft.AspNetCore.Components.Test
|
||||||
// param on the second component.
|
// param on the second component.
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var batch = GetRenderedBatch(initializeFromFrames: false);
|
var batchBuilder = GetRenderedBatch(initializeFromFrames: false);
|
||||||
var newComponents = GetComponents<CaptureSetParametersComponent>(newTree);
|
var newComponents = GetComponents<CaptureSetParametersComponent>(newTree);
|
||||||
|
|
||||||
// Assert: Inserts new component at position 0
|
// Assert: Inserts new component at position 0
|
||||||
Assert.Equal(1, batch.UpdatedComponents.Count);
|
Assert.Equal(1, batchBuilder.UpdatedComponents.Count);
|
||||||
Assert.Collection(batch.UpdatedComponents.Array[0].Edits,
|
Assert.Collection(batchBuilder.UpdatedComponents.Array[0].Edits,
|
||||||
entry => AssertEdit(entry, RenderTreeEditType.PrependFrame, 0));
|
entry => AssertEdit(entry, RenderTreeEditType.PrependFrame, 0));
|
||||||
|
|
||||||
// Assert: Retains old component instance in position 1, and updates its params
|
// Assert: Retains old component instance in position 1, and updates its params
|
||||||
|
|
@ -255,7 +265,8 @@ namespace Microsoft.AspNetCore.Components.Test
|
||||||
oldTree.CloseComponent();
|
oldTree.CloseComponent();
|
||||||
|
|
||||||
// Instantiate initial components
|
// Instantiate initial components
|
||||||
GetRenderedBatch(new RenderTreeBuilder(renderer), oldTree, false);
|
using var initial = new RenderTreeBuilder(renderer);
|
||||||
|
GetRenderedBatch(initial, oldTree, false);
|
||||||
var oldComponents = GetComponents(oldTree);
|
var oldComponents = GetComponents(oldTree);
|
||||||
|
|
||||||
newTree.OpenComponent<FakeComponent>(0);
|
newTree.OpenComponent<FakeComponent>(0);
|
||||||
|
|
@ -286,7 +297,8 @@ namespace Microsoft.AspNetCore.Components.Test
|
||||||
oldTree.CloseComponent();
|
oldTree.CloseComponent();
|
||||||
|
|
||||||
// Instantiate initial component
|
// Instantiate initial component
|
||||||
GetRenderedBatch(new RenderTreeBuilder(renderer), oldTree, false);
|
using var renderTreeBuilder = new RenderTreeBuilder(renderer);
|
||||||
|
GetRenderedBatch(renderTreeBuilder, oldTree, false);
|
||||||
var oldComponent = GetComponents(oldTree).Single();
|
var oldComponent = GetComponents(oldTree).Single();
|
||||||
Assert.NotNull(oldComponent);
|
Assert.NotNull(oldComponent);
|
||||||
|
|
||||||
|
|
@ -724,10 +736,11 @@ namespace Microsoft.AspNetCore.Components.Test
|
||||||
// Arrange
|
// Arrange
|
||||||
oldTree.OpenComponent<FakeComponent>(123);
|
oldTree.OpenComponent<FakeComponent>(123);
|
||||||
oldTree.CloseComponent();
|
oldTree.CloseComponent();
|
||||||
GetRenderedBatch(new RenderTreeBuilder(renderer), oldTree, false); // Assign initial IDs
|
using var initial = new RenderTreeBuilder(renderer);
|
||||||
|
GetRenderedBatch(initial, oldTree, false); // Assign initial IDs
|
||||||
newTree.OpenComponent<FakeComponent2>(123);
|
newTree.OpenComponent<FakeComponent2>(123);
|
||||||
newTree.CloseComponent();
|
newTree.CloseComponent();
|
||||||
var batchBuilder = new RenderBatchBuilder();
|
using var batchBuilder = new RenderBatchBuilder();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var diff = RenderTreeDiffBuilder.ComputeDiff(renderer, batchBuilder, 0, oldTree.GetFrames(), newTree.GetFrames());
|
var diff = RenderTreeDiffBuilder.ComputeDiff(renderer, batchBuilder, 0, oldTree.GetFrames(), newTree.GetFrames());
|
||||||
|
|
@ -835,7 +848,7 @@ namespace Microsoft.AspNetCore.Components.Test
|
||||||
newTree.CloseElement();
|
newTree.CloseElement();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var (result, referenceFrames, batch) = GetSingleUpdatedComponentWithBatch(initializeFromFrames: true);
|
var (result, referenceFrames, batchBuilder) = GetSingleUpdatedComponentWithBatch(initializeFromFrames: true);
|
||||||
var removedEventHandlerFrame = oldTree.GetFrames().Array[2];
|
var removedEventHandlerFrame = oldTree.GetFrames().Array[2];
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
|
|
@ -849,7 +862,7 @@ namespace Microsoft.AspNetCore.Components.Test
|
||||||
Assert.NotEqual(0, removedEventHandlerFrame.AttributeEventHandlerId);
|
Assert.NotEqual(0, removedEventHandlerFrame.AttributeEventHandlerId);
|
||||||
Assert.Equal(
|
Assert.Equal(
|
||||||
new[] { removedEventHandlerFrame.AttributeEventHandlerId },
|
new[] { removedEventHandlerFrame.AttributeEventHandlerId },
|
||||||
batch.DisposedEventHandlerIDs.AsEnumerable());
|
batchBuilder.DisposedEventHandlerIDs.AsEnumerable());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -1539,7 +1552,9 @@ namespace Microsoft.AspNetCore.Components.Test
|
||||||
newTree.CloseComponent(); // </FakeComponent2>
|
newTree.CloseComponent(); // </FakeComponent2>
|
||||||
newTree.CloseElement(); // </container>
|
newTree.CloseElement(); // </container>
|
||||||
|
|
||||||
RenderTreeDiffBuilder.ComputeDiff(renderer, new RenderBatchBuilder(), 0, new RenderTreeBuilder(renderer).GetFrames(), oldTree.GetFrames());
|
using var batchBuilder = new RenderBatchBuilder();
|
||||||
|
using var renderTreeBuilder = new RenderTreeBuilder(renderer);
|
||||||
|
RenderTreeDiffBuilder.ComputeDiff(renderer, batchBuilder, 0, renderTreeBuilder.GetFrames(), oldTree.GetFrames());
|
||||||
var originalFakeComponentInstance = oldTree.GetFrames().Array[2].Component;
|
var originalFakeComponentInstance = oldTree.GetFrames().Array[2].Component;
|
||||||
var originalFakeComponent2Instance = oldTree.GetFrames().Array[3].Component;
|
var originalFakeComponent2Instance = oldTree.GetFrames().Array[3].Component;
|
||||||
|
|
||||||
|
|
@ -1569,7 +1584,7 @@ namespace Microsoft.AspNetCore.Components.Test
|
||||||
newTree.CloseElement();
|
newTree.CloseElement();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var (result, referenceFrames, batch) = GetSingleUpdatedComponentWithBatch(initializeFromFrames: true);
|
var (result, referenceFrames, batchBuilder) = GetSingleUpdatedComponentWithBatch(initializeFromFrames: true);
|
||||||
var oldAttributeFrame = oldTree.GetFrames().Array[1];
|
var oldAttributeFrame = oldTree.GetFrames().Array[1];
|
||||||
var newAttributeFrame = newTree.GetFrames().Array[1];
|
var newAttributeFrame = newTree.GetFrames().Array[1];
|
||||||
|
|
||||||
|
|
@ -1579,7 +1594,7 @@ namespace Microsoft.AspNetCore.Components.Test
|
||||||
AssertFrame.Attribute(newAttributeFrame, "ontest", retainedHandler);
|
AssertFrame.Attribute(newAttributeFrame, "ontest", retainedHandler);
|
||||||
Assert.NotEqual(0, oldAttributeFrame.AttributeEventHandlerId);
|
Assert.NotEqual(0, oldAttributeFrame.AttributeEventHandlerId);
|
||||||
Assert.Equal(oldAttributeFrame.AttributeEventHandlerId, newAttributeFrame.AttributeEventHandlerId);
|
Assert.Equal(oldAttributeFrame.AttributeEventHandlerId, newAttributeFrame.AttributeEventHandlerId);
|
||||||
Assert.Empty(batch.DisposedEventHandlerIDs.AsEnumerable());
|
Assert.Empty(batchBuilder.DisposedEventHandlerIDs.AsEnumerable());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -1596,7 +1611,7 @@ namespace Microsoft.AspNetCore.Components.Test
|
||||||
newTree.CloseElement();
|
newTree.CloseElement();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var (result, referenceFrames, batch) = GetSingleUpdatedComponentWithBatch(initializeFromFrames: true);
|
var (result, referenceFrames, batchBuilder) = GetSingleUpdatedComponentWithBatch(initializeFromFrames: true);
|
||||||
var oldAttributeFrame = oldTree.GetFrames().Array[1];
|
var oldAttributeFrame = oldTree.GetFrames().Array[1];
|
||||||
var newAttributeFrame = newTree.GetFrames().Array[2];
|
var newAttributeFrame = newTree.GetFrames().Array[2];
|
||||||
|
|
||||||
|
|
@ -1606,7 +1621,7 @@ namespace Microsoft.AspNetCore.Components.Test
|
||||||
AssertFrame.Attribute(newAttributeFrame, "ontest", retainedHandler);
|
AssertFrame.Attribute(newAttributeFrame, "ontest", retainedHandler);
|
||||||
Assert.NotEqual(0, oldAttributeFrame.AttributeEventHandlerId);
|
Assert.NotEqual(0, oldAttributeFrame.AttributeEventHandlerId);
|
||||||
Assert.Equal(oldAttributeFrame.AttributeEventHandlerId, newAttributeFrame.AttributeEventHandlerId);
|
Assert.Equal(oldAttributeFrame.AttributeEventHandlerId, newAttributeFrame.AttributeEventHandlerId);
|
||||||
Assert.Empty(batch.DisposedEventHandlerIDs.AsEnumerable());
|
Assert.Empty(batchBuilder.DisposedEventHandlerIDs.AsEnumerable());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -1623,7 +1638,9 @@ namespace Microsoft.AspNetCore.Components.Test
|
||||||
newTree.AddAttribute(14, nameof(FakeComponent.ObjectProperty), objectWillNotChange);
|
newTree.AddAttribute(14, nameof(FakeComponent.ObjectProperty), objectWillNotChange);
|
||||||
newTree.CloseComponent();
|
newTree.CloseComponent();
|
||||||
|
|
||||||
RenderTreeDiffBuilder.ComputeDiff(renderer, new RenderBatchBuilder(), 0, new RenderTreeBuilder(renderer).GetFrames(), oldTree.GetFrames());
|
using var batchBuilder = new RenderBatchBuilder();
|
||||||
|
using var renderTree = new RenderTreeBuilder(renderer);
|
||||||
|
RenderTreeDiffBuilder.ComputeDiff(renderer, batchBuilder, 0, renderTree.GetFrames(), oldTree.GetFrames());
|
||||||
var originalComponentInstance = (FakeComponent)oldTree.GetFrames().Array[0].Component;
|
var originalComponentInstance = (FakeComponent)oldTree.GetFrames().Array[0].Component;
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
|
|
@ -1661,7 +1678,9 @@ namespace Microsoft.AspNetCore.Components.Test
|
||||||
tree.CloseComponent();
|
tree.CloseComponent();
|
||||||
}
|
}
|
||||||
|
|
||||||
RenderTreeDiffBuilder.ComputeDiff(renderer, new RenderBatchBuilder(), 0, new RenderTreeBuilder(renderer).GetFrames(), oldTree.GetFrames());
|
using var batchBuilder = new RenderBatchBuilder();
|
||||||
|
using var renderTreeBuilder = new RenderTreeBuilder(renderer);
|
||||||
|
RenderTreeDiffBuilder.ComputeDiff(renderer, batchBuilder, 0, renderTreeBuilder.GetFrames(), oldTree.GetFrames());
|
||||||
var originalComponentInstance = (CaptureSetParametersComponent)oldTree.GetFrames().Array[0].Component;
|
var originalComponentInstance = (CaptureSetParametersComponent)oldTree.GetFrames().Array[0].Component;
|
||||||
Assert.Equal(1, originalComponentInstance.SetParametersCallCount);
|
Assert.Equal(1, originalComponentInstance.SetParametersCallCount);
|
||||||
|
|
||||||
|
|
@ -1689,7 +1708,9 @@ namespace Microsoft.AspNetCore.Components.Test
|
||||||
tree.CloseComponent();
|
tree.CloseComponent();
|
||||||
}
|
}
|
||||||
|
|
||||||
RenderTreeDiffBuilder.ComputeDiff(renderer, new RenderBatchBuilder(), 0, new RenderTreeBuilder(renderer).GetFrames(), oldTree.GetFrames());
|
using var batchBuilder = new RenderBatchBuilder();
|
||||||
|
using var renderTreeBuilder = new RenderTreeBuilder(renderer);
|
||||||
|
RenderTreeDiffBuilder.ComputeDiff(renderer, batchBuilder, 0, renderTreeBuilder.GetFrames(), oldTree.GetFrames());
|
||||||
var componentInstance = (CaptureSetParametersComponent)oldTree.GetFrames().Array[0].Component;
|
var componentInstance = (CaptureSetParametersComponent)oldTree.GetFrames().Array[0].Component;
|
||||||
Assert.Equal(1, componentInstance.SetParametersCallCount);
|
Assert.Equal(1, componentInstance.SetParametersCallCount);
|
||||||
|
|
||||||
|
|
@ -1713,8 +1734,9 @@ namespace Microsoft.AspNetCore.Components.Test
|
||||||
newTree.OpenComponent<DisposableComponent>(30); // <DisposableComponent>
|
newTree.OpenComponent<DisposableComponent>(30); // <DisposableComponent>
|
||||||
newTree.CloseComponent(); // </DisposableComponent>
|
newTree.CloseComponent(); // </DisposableComponent>
|
||||||
|
|
||||||
var batchBuilder = new RenderBatchBuilder();
|
using var batchBuilder = new RenderBatchBuilder();
|
||||||
RenderTreeDiffBuilder.ComputeDiff(renderer, batchBuilder, 0, new RenderTreeBuilder(renderer).GetFrames(), oldTree.GetFrames());
|
using var renderTree = new RenderTreeBuilder(renderer);
|
||||||
|
RenderTreeDiffBuilder.ComputeDiff(renderer, batchBuilder, 0, renderTree.GetFrames(), oldTree.GetFrames());
|
||||||
|
|
||||||
// Act/Assert
|
// Act/Assert
|
||||||
// Note that we track NonDisposableComponent was disposed even though it's not IDisposable,
|
// Note that we track NonDisposableComponent was disposed even though it's not IDisposable,
|
||||||
|
|
@ -1923,7 +1945,8 @@ namespace Microsoft.AspNetCore.Components.Test
|
||||||
oldTree.AddAttribute(1, nameof(FakeComponent.StringProperty), "Second param");
|
oldTree.AddAttribute(1, nameof(FakeComponent.StringProperty), "Second param");
|
||||||
oldTree.CloseComponent();
|
oldTree.CloseComponent();
|
||||||
|
|
||||||
GetRenderedBatch(new RenderTreeBuilder(renderer), oldTree, false); // Assign initial IDs
|
using var renderTreeBuilder = new RenderTreeBuilder(renderer);
|
||||||
|
GetRenderedBatch(renderTreeBuilder, oldTree, false); // Assign initial IDs
|
||||||
var oldComponents = GetComponents<CaptureSetParametersComponent>(oldTree);
|
var oldComponents = GetComponents<CaptureSetParametersComponent>(oldTree);
|
||||||
|
|
||||||
newTree.OpenComponent<CaptureSetParametersComponent>(0);
|
newTree.OpenComponent<CaptureSetParametersComponent>(0);
|
||||||
|
|
@ -2124,12 +2147,19 @@ namespace Microsoft.AspNetCore.Components.Test
|
||||||
{
|
{
|
||||||
if (initializeFromFrames)
|
if (initializeFromFrames)
|
||||||
{
|
{
|
||||||
var emptyFrames = new RenderTreeBuilder(renderer).GetFrames();
|
using var renderTreeBuilder = new RenderTreeBuilder(renderer);
|
||||||
|
using var initializeBatchBuilder = new RenderBatchBuilder();
|
||||||
|
|
||||||
|
var emptyFrames = renderTreeBuilder.GetFrames();
|
||||||
var oldFrames = from.GetFrames();
|
var oldFrames = from.GetFrames();
|
||||||
RenderTreeDiffBuilder.ComputeDiff(renderer, new RenderBatchBuilder(), 0, emptyFrames, oldFrames);
|
|
||||||
|
RenderTreeDiffBuilder.ComputeDiff(renderer, initializeBatchBuilder, 0, emptyFrames, oldFrames);
|
||||||
}
|
}
|
||||||
|
|
||||||
var batchBuilder = new RenderBatchBuilder();
|
batchBuilder?.Dispose();
|
||||||
|
// This gets disposed as part of the test type's Dispose
|
||||||
|
batchBuilder = new RenderBatchBuilder();
|
||||||
|
|
||||||
var diff = RenderTreeDiffBuilder.ComputeDiff(renderer, batchBuilder, 0, from.GetFrames(), to.GetFrames());
|
var diff = RenderTreeDiffBuilder.ComputeDiff(renderer, batchBuilder, 0, from.GetFrames(), to.GetFrames());
|
||||||
batchBuilder.UpdatedComponentDiffs.Append(diff);
|
batchBuilder.UpdatedComponentDiffs.Append(diff);
|
||||||
return batchBuilder.ToBatch();
|
return batchBuilder.ToBatch();
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
||||||
public void BasicPropertiesWork()
|
public void BasicPropertiesWork()
|
||||||
{
|
{
|
||||||
// Arrange: builder containing 1..5
|
// Arrange: builder containing 1..5
|
||||||
var builder = new ArrayBuilder<int>();
|
using var builder = new ArrayBuilder<int>();
|
||||||
builder.Append(new[] { 1, 2, 3, 4, 5 }, 0, 5);
|
builder.Append(new[] { 1, 2, 3, 4, 5 }, 0, 5);
|
||||||
|
|
||||||
// Act: take segment containing 2..3
|
// Act: take segment containing 2..3
|
||||||
|
|
@ -32,7 +32,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
||||||
public void StillWorksAfterUnderlyingCapacityChange()
|
public void StillWorksAfterUnderlyingCapacityChange()
|
||||||
{
|
{
|
||||||
// Arrange: builder containing 1..8
|
// Arrange: builder containing 1..8
|
||||||
var builder = new ArrayBuilder<int>(capacity: 10);
|
using var builder = new ArrayBuilder<int>(minCapacity: 10, new TestArrayPool<int>());
|
||||||
builder.Append(new[] { 1, 2, 3, 4, 5, 6, 7, 8 }, 0, 8);
|
builder.Append(new[] { 1, 2, 3, 4, 5, 6, 7, 8 }, 0, 8);
|
||||||
var originalBuffer = builder.Buffer;
|
var originalBuffer = builder.Buffer;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,317 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System.Buffers;
|
||||||
|
using System.Linq;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Components.RenderTree
|
||||||
|
{
|
||||||
|
public class ArrayBuilderTest
|
||||||
|
{
|
||||||
|
private readonly TestArrayPool<int> ArrayPool = new TestArrayPool<int>();
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Append_SingleItem()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var value = 7;
|
||||||
|
using var builder = CreateArrayBuilder();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
builder.Append(value);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(1, builder.Count);
|
||||||
|
Assert.Equal(value, builder.Buffer[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Append_ThreeItem()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var value1 = 7;
|
||||||
|
var value2 = 22;
|
||||||
|
var value3 = 3;
|
||||||
|
using var builder = CreateArrayBuilder();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
builder.Append(value1);
|
||||||
|
builder.Append(value2);
|
||||||
|
builder.Append(value3);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(3, builder.Count);
|
||||||
|
Assert.Equal(new[] { value1, value2, value3 }, builder.Buffer.Take(3));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Append_FillBuffer()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var capacity = 8;
|
||||||
|
using var builder = new ArrayBuilder<int>(minCapacity: capacity);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
for (var i = 0; i < capacity; i++)
|
||||||
|
{
|
||||||
|
builder.Append(5);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(capacity, builder.Count);
|
||||||
|
Assert.Equal(Enumerable.Repeat(5, capacity), builder.Buffer.Take(capacity));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void AppendArray_CopySubset()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var array = Enumerable.Repeat(8, 5).ToArray();
|
||||||
|
using var builder = CreateArrayBuilder();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
builder.Append(array, 0, 2);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(2, builder.Count);
|
||||||
|
Assert.Equal(new[] { 8, 8 }, builder.Buffer.Take(2));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void AppendArray_CopyArray()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var array = Enumerable.Repeat(8, 5).ToArray();
|
||||||
|
using var builder = CreateArrayBuilder();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
builder.Append(array, 0, array.Length);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(array.Length, builder.Count);
|
||||||
|
Assert.Equal(array, builder.Buffer.Take(array.Length));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void AppendArray_AfterPriorInsertion()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var array = Enumerable.Repeat(8, 5).ToArray();
|
||||||
|
using var builder = CreateArrayBuilder();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
builder.Append(118);
|
||||||
|
builder.Append(array, 0, 2);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(3, builder.Count);
|
||||||
|
Assert.Equal(new[] { 118, 8, 8 }, builder.Buffer.Take(3));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
// These are at boundaries of our capacity increments.
|
||||||
|
[InlineData(1023)]
|
||||||
|
[InlineData(1024)]
|
||||||
|
[InlineData(1025)]
|
||||||
|
public void AppendArray_LargerThanBuffer(int size)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var array = Enumerable.Repeat(17, size).ToArray();
|
||||||
|
using var builder = CreateArrayBuilder();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
builder.Append(array, 0, array.Length);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(array.Length, builder.Count);
|
||||||
|
Assert.Equal(array, builder.Buffer.Take(array.Length));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Overwrite_Works()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
using var builder = CreateArrayBuilder();
|
||||||
|
builder.Append(7);
|
||||||
|
builder.Append(3);
|
||||||
|
builder.Append(9);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
builder.Overwrite(1, 2);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(3, builder.Count);
|
||||||
|
Assert.Equal(new[]{ 7, 2, 9}, builder.Buffer.Take(3));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Insert_Works()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
using var builder = CreateArrayBuilder();
|
||||||
|
builder.Append(7);
|
||||||
|
builder.Append(3);
|
||||||
|
builder.Append(9);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
builder.InsertExpensive(1, 2);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(4, builder.Count);
|
||||||
|
Assert.Equal(new[] { 7, 2, 3, 9 }, builder.Buffer.Take(4));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Insert_WhenBufferIsAtCapacity()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
using var builder = CreateArrayBuilder(2);
|
||||||
|
builder.Append(new[] { 1, 3 }, 0, 2);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
builder.InsertExpensive(1, 2);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(3, builder.Count);
|
||||||
|
Assert.Equal(new[] { 1, 2, 3 }, builder.Buffer.Take(3));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void RemoveLast_Works()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
using var builder = CreateArrayBuilder();
|
||||||
|
builder.Append(1);
|
||||||
|
builder.Append(2);
|
||||||
|
builder.Append(3);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
builder.RemoveLast();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(2, builder.Count);
|
||||||
|
Assert.Equal(new[] { 1, 2, }, builder.Buffer.Take(2));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void RemoveLast_LastEntry()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
int[] buffer;
|
||||||
|
using (var builder = CreateArrayBuilder())
|
||||||
|
{
|
||||||
|
builder.Append(1);
|
||||||
|
buffer = builder.Buffer;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
builder.RemoveLast();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(0, builder.Count);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Also verify that the buffer is indeed returned in this case.
|
||||||
|
var returnedBuffer = Assert.Single(ArrayPool.ReturnedBuffers);
|
||||||
|
Assert.Same(buffer, returnedBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Clear_ReturnsBuffer()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
using var builder = CreateArrayBuilder();
|
||||||
|
builder.Append(1);
|
||||||
|
var buffer = builder.Buffer;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
builder.Clear();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(0, builder.Count);
|
||||||
|
var returnedBuffer = Assert.Single(ArrayPool.ReturnedBuffers);
|
||||||
|
Assert.Same(buffer, returnedBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Dispose_WithEmptyBuffer_DoesNotReturnIt()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var builder = CreateArrayBuilder();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
builder.Dispose();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Empty(ArrayPool.ReturnedBuffers);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Dispose_NonEmptyBufferIsReturned()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var builder = CreateArrayBuilder();
|
||||||
|
builder.Append(1);
|
||||||
|
var buffer = builder.Buffer;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
builder.Dispose();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Single(ArrayPool.ReturnedBuffers);
|
||||||
|
var returnedBuffer = Assert.Single(ArrayPool.ReturnedBuffers);
|
||||||
|
Assert.Same(buffer, returnedBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void DoubleDispose_DoesNotReturnBufferTwice()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var builder = CreateArrayBuilder();
|
||||||
|
builder.Append(1);
|
||||||
|
var buffer = builder.Buffer;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
builder.Dispose();
|
||||||
|
builder.Dispose();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Single(ArrayPool.ReturnedBuffers);
|
||||||
|
var returnedBuffer = Assert.Single(ArrayPool.ReturnedBuffers);
|
||||||
|
Assert.Same(buffer, returnedBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void UnusedBufferIsReturned_OnResize()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var builder = CreateArrayBuilder(2);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
for (var i = 0; i < 10; i++)
|
||||||
|
{
|
||||||
|
builder.Append(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Collection(
|
||||||
|
ArrayPool.ReturnedBuffers,
|
||||||
|
buffer => Assert.Equal(2, buffer.Length),
|
||||||
|
buffer => Assert.Equal(4, buffer.Length),
|
||||||
|
buffer => Assert.Equal(8, buffer.Length));
|
||||||
|
|
||||||
|
// Clear this because this is no longer interesting.
|
||||||
|
ArrayPool.ReturnedBuffers.Clear();
|
||||||
|
|
||||||
|
var buffer = builder.Buffer;
|
||||||
|
builder.Dispose();
|
||||||
|
|
||||||
|
Assert.Same(buffer, Assert.Single(ArrayPool.ReturnedBuffers));
|
||||||
|
}
|
||||||
|
|
||||||
|
private ArrayBuilder<int> CreateArrayBuilder(int capacity = 32)
|
||||||
|
{
|
||||||
|
return new ArrayBuilder<int>(capacity, ArrayPool);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System.Buffers;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Components.RenderTree
|
||||||
|
{
|
||||||
|
internal class TestArrayPool<T> : ArrayPool<T>
|
||||||
|
{
|
||||||
|
public override T[] Rent(int minimumLength)
|
||||||
|
{
|
||||||
|
return new T[minimumLength];
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<T[]> ReturnedBuffers = new List<T[]>();
|
||||||
|
|
||||||
|
public override void Return(T[] array, bool clearArray = false)
|
||||||
|
{
|
||||||
|
ReturnedBuffers.Add(array);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue