parent
d668e77e9e
commit
6c80c42511
|
|
@ -73,6 +73,7 @@ namespace Microsoft.AspNetCore.Blazor.Build.Test
|
|||
"StandaloneApp.dll",
|
||||
"StandaloneApp.pdb",
|
||||
"System.dll",
|
||||
"System.Buffers.dll",
|
||||
"System.Collections.Concurrent.dll",
|
||||
"System.Collections.dll",
|
||||
"System.ComponentModel.Composition.dll",
|
||||
|
|
|
|||
|
|
@ -763,7 +763,7 @@ namespace Microsoft.AspNetCore.Components.RenderTree
|
|||
public ArrayRange(T[] array, int count) { 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 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 SetKey(object value) { }
|
||||
public void SetUpdatesAttributeName(string updatesAttributeName) { }
|
||||
void System.IDisposable.Dispose() { }
|
||||
}
|
||||
[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
|
||||
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.
|
||||
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
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
|
||||
/// varying dramatically depending on the user's navigation in the app.
|
||||
/// </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 int _itemsInUse;
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a new instance of <see cref="ArrayBuilder{T}"/>.
|
||||
/// </summary>
|
||||
public ArrayBuilder() : this(MinCapacity)
|
||||
{
|
||||
}
|
||||
private static readonly T[] Empty = Array.Empty<T>();
|
||||
private readonly ArrayPool<T> _arrayPool;
|
||||
private readonly int _minCapacity;
|
||||
private bool _disposed;
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a new instance of <see cref="ArrayBuilder{T}"/>.
|
||||
/// </summary>
|
||||
public ArrayBuilder(int capacity)
|
||||
public ArrayBuilder(int minCapacity = 32, ArrayPool<T> arrayPool = null)
|
||||
{
|
||||
_items = new T[capacity < MinCapacity ? MinCapacity : capacity];
|
||||
_itemsInUse = 0;
|
||||
_arrayPool = arrayPool ?? ArrayPool<T>.Shared;
|
||||
_minCapacity = minCapacity;
|
||||
_items = Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -57,7 +58,7 @@ namespace Microsoft.AspNetCore.Components.RenderTree
|
|||
{
|
||||
if (_itemsInUse == _items.Length)
|
||||
{
|
||||
SetCapacity(_items.Length * 2, preserveContents: true);
|
||||
GrowBuffer(_items.Length * 2);
|
||||
}
|
||||
|
||||
var indexOfAppendedItem = _itemsInUse++;
|
||||
|
|
@ -72,13 +73,13 @@ namespace Microsoft.AspNetCore.Components.RenderTree
|
|||
var requiredCapacity = _itemsInUse + length;
|
||||
if (_items.Length < requiredCapacity)
|
||||
{
|
||||
var candidateCapacity = _items.Length * 2;
|
||||
var candidateCapacity = Math.Max(_items.Length * 2, _minCapacity);
|
||||
while (candidateCapacity < requiredCapacity)
|
||||
{
|
||||
candidateCapacity *= 2;
|
||||
}
|
||||
|
||||
SetCapacity(candidateCapacity, preserveContents: true);
|
||||
GrowBuffer(candidateCapacity);
|
||||
}
|
||||
|
||||
Array.Copy(source, startIndex, _items, _itemsInUse, length);
|
||||
|
|
@ -95,34 +96,51 @@ namespace Microsoft.AspNetCore.Components.RenderTree
|
|||
/// <param name="value">The value.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Overwrite(int index, in T value)
|
||||
=> _items[index] = value;
|
||||
{
|
||||
if (index > _itemsInUse)
|
||||
{
|
||||
ThrowIndexOutOfBoundsException();
|
||||
}
|
||||
|
||||
_items[index] = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the last item.
|
||||
/// </summary>
|
||||
public void RemoveLast()
|
||||
{
|
||||
if (_itemsInUse == 0)
|
||||
{
|
||||
ThrowIndexOutOfBoundsException();
|
||||
}
|
||||
|
||||
_itemsInUse--;
|
||||
_items[_itemsInUse] = default(T); // Release to GC
|
||||
_items[_itemsInUse] = default; // Release to GC
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Inserts the item at the specified index, moving the contents of the subsequent entries along by one.
|
||||
/// </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>
|
||||
public void InsertExpensive(int insertAtIndex, T value)
|
||||
public void InsertExpensive(int index, T value)
|
||||
{
|
||||
if (index > _itemsInUse)
|
||||
{
|
||||
ThrowIndexOutOfBoundsException();
|
||||
}
|
||||
|
||||
// Same expansion logic as elsewhere
|
||||
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++;
|
||||
|
||||
_items[insertAtIndex] = value;
|
||||
_items[index] = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -131,17 +149,9 @@ namespace Microsoft.AspNetCore.Components.RenderTree
|
|||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
var previousItemsInUse = _itemsInUse;
|
||||
ReturnBuffer();
|
||||
_items = Empty;
|
||||
_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>
|
||||
|
|
@ -160,29 +170,42 @@ namespace Microsoft.AspNetCore.Components.RenderTree
|
|||
public ArrayBuilderSegment<T> ToSegment(int fromIndexInclusive, int toIndexExclusive)
|
||||
=> new ArrayBuilderSegment<T>(this, fromIndexInclusive, toIndexExclusive - fromIndexInclusive);
|
||||
|
||||
private void SetCapacity(int desiredCapacity, bool preserveContents)
|
||||
private void GrowBuffer(int desiredCapacity)
|
||||
{
|
||||
if (desiredCapacity < _itemsInUse)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(desiredCapacity), $"The value cannot be less than {nameof(Count)}");
|
||||
}
|
||||
var newCapacity = Math.Max(desiredCapacity, _minCapacity);
|
||||
Debug.Assert(newCapacity > _items.Length);
|
||||
|
||||
var newCapacity = desiredCapacity < MinCapacity ? MinCapacity : desiredCapacity;
|
||||
if (newCapacity != _items.Length)
|
||||
{
|
||||
var newItems = new T[newCapacity];
|
||||
var newItems = _arrayPool.Rent(newCapacity);
|
||||
Array.Copy(_items, newItems, _itemsInUse);
|
||||
|
||||
if (preserveContents)
|
||||
{
|
||||
Array.Copy(_items, newItems, _itemsInUse);
|
||||
}
|
||||
// Return the old buffer and start using the new buffer
|
||||
ReturnBuffer();
|
||||
_items = newItems;
|
||||
}
|
||||
|
||||
_items = newItems;
|
||||
}
|
||||
else if (!preserveContents)
|
||||
private void ReturnBuffer()
|
||||
{
|
||||
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>
|
||||
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 int _offset;
|
||||
private readonly int _count;
|
||||
|
|
|
|||
|
|
@ -17,14 +17,14 @@ namespace Microsoft.AspNetCore.Components.RenderTree
|
|||
/// <summary>
|
||||
/// Provides methods for building a collection of <see cref="RenderTreeFrame"/> entries.
|
||||
/// </summary>
|
||||
public class RenderTreeBuilder
|
||||
public class RenderTreeBuilder : IDisposable
|
||||
{
|
||||
private readonly static object BoxedTrue = true;
|
||||
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 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 RenderTreeFrameType? _lastNonAttributeFrameType;
|
||||
private bool _hasSeenAddMultipleAttributes;
|
||||
|
|
@ -796,5 +796,10 @@ namespace Microsoft.AspNetCore.Components.RenderTree
|
|||
var seenAttributeNames = (_seenAttributeNames ??= new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase));
|
||||
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
|
||||
/// detail of <see cref="Renderer"/>.
|
||||
/// </summary>
|
||||
internal class ComponentState
|
||||
internal class ComponentState : IDisposable
|
||||
{
|
||||
private readonly Renderer _renderer;
|
||||
private readonly IReadOnlyList<CascadingParameterState> _cascadingParameters;
|
||||
|
|
@ -91,6 +91,8 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
|||
{
|
||||
RemoveCascadingParameterSubscriptions();
|
||||
}
|
||||
|
||||
DisposeBuffers();
|
||||
}
|
||||
|
||||
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.
|
||||
// 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 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
|
||||
/// be rendered).
|
||||
/// </summary>
|
||||
internal class RenderBatchBuilder
|
||||
internal class RenderBatchBuilder : IDisposable
|
||||
{
|
||||
// Primary result data
|
||||
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>();
|
||||
|
||||
// Buffers referenced by UpdatedComponentDiffs
|
||||
public ArrayBuilder<RenderTreeEdit> EditsBuffer { get; } = new ArrayBuilder<RenderTreeEdit>();
|
||||
public ArrayBuilder<RenderTreeFrame> ReferenceFramesBuffer { get; } = new ArrayBuilder<RenderTreeFrame>();
|
||||
public ArrayBuilder<RenderTreeEdit> EditsBuffer { get; } = new ArrayBuilder<RenderTreeEdit>(64);
|
||||
public ArrayBuilder<RenderTreeFrame> ReferenceFramesBuffer { get; } = new ArrayBuilder<RenderTreeFrame>(64);
|
||||
|
||||
// State of render pipeline
|
||||
public Queue<RenderQueueEntry> ComponentRenderQueue { get; } = new Queue<RenderQueueEntry>();
|
||||
|
|
@ -56,5 +57,14 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
|||
ReferenceFramesBuffer.ToRange(),
|
||||
DisposedComponentIds.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
|
||||
{
|
||||
disposable.Dispose();
|
||||
componentState.Dispose();
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
HandleException(exception);
|
||||
}
|
||||
}
|
||||
|
||||
_batchBuilder.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ using System.Reflection;
|
|||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Components.RenderTree;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
|
||||
namespace Microsoft.AspNetCore.Components.Routing
|
||||
{
|
||||
|
|
|
|||
|
|
@ -13,11 +13,12 @@ using Xunit;
|
|||
|
||||
namespace Microsoft.AspNetCore.Components.Test
|
||||
{
|
||||
public class RenderTreeDiffBuilderTest
|
||||
public class RenderTreeDiffBuilderTest : IDisposable
|
||||
{
|
||||
private readonly Renderer renderer;
|
||||
private readonly RenderTreeBuilder oldTree;
|
||||
private readonly RenderTreeBuilder newTree;
|
||||
private RenderBatchBuilder batchBuilder;
|
||||
|
||||
public RenderTreeDiffBuilderTest()
|
||||
{
|
||||
|
|
@ -26,6 +27,14 @@ namespace Microsoft.AspNetCore.Components.Test
|
|||
newTree = new RenderTreeBuilder(renderer);
|
||||
}
|
||||
|
||||
void IDisposable.Dispose()
|
||||
{
|
||||
renderer.Dispose();
|
||||
((IDisposable)oldTree).Dispose();
|
||||
((IDisposable)newTree).Dispose();
|
||||
batchBuilder?.Dispose();
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(RecognizesEquivalentFramesAsSameCases))]
|
||||
public void RecognizesEquivalentFramesAsSame(RenderFragment appendFragment)
|
||||
|
|
@ -208,7 +217,8 @@ namespace Microsoft.AspNetCore.Components.Test
|
|||
oldTree.SetKey("retained key");
|
||||
oldTree.AddAttribute(1, "ParamName", "Param old value");
|
||||
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();
|
||||
|
||||
newTree.OpenComponent<CaptureSetParametersComponent>(0);
|
||||
|
|
@ -227,12 +237,12 @@ namespace Microsoft.AspNetCore.Components.Test
|
|||
// param on the second component.
|
||||
|
||||
// Act
|
||||
var batch = GetRenderedBatch(initializeFromFrames: false);
|
||||
var batchBuilder = GetRenderedBatch(initializeFromFrames: false);
|
||||
var newComponents = GetComponents<CaptureSetParametersComponent>(newTree);
|
||||
|
||||
// Assert: Inserts new component at position 0
|
||||
Assert.Equal(1, batch.UpdatedComponents.Count);
|
||||
Assert.Collection(batch.UpdatedComponents.Array[0].Edits,
|
||||
Assert.Equal(1, batchBuilder.UpdatedComponents.Count);
|
||||
Assert.Collection(batchBuilder.UpdatedComponents.Array[0].Edits,
|
||||
entry => AssertEdit(entry, RenderTreeEditType.PrependFrame, 0));
|
||||
|
||||
// Assert: Retains old component instance in position 1, and updates its params
|
||||
|
|
@ -255,7 +265,8 @@ namespace Microsoft.AspNetCore.Components.Test
|
|||
oldTree.CloseComponent();
|
||||
|
||||
// Instantiate initial components
|
||||
GetRenderedBatch(new RenderTreeBuilder(renderer), oldTree, false);
|
||||
using var initial = new RenderTreeBuilder(renderer);
|
||||
GetRenderedBatch(initial, oldTree, false);
|
||||
var oldComponents = GetComponents(oldTree);
|
||||
|
||||
newTree.OpenComponent<FakeComponent>(0);
|
||||
|
|
@ -286,7 +297,8 @@ namespace Microsoft.AspNetCore.Components.Test
|
|||
oldTree.CloseComponent();
|
||||
|
||||
// Instantiate initial component
|
||||
GetRenderedBatch(new RenderTreeBuilder(renderer), oldTree, false);
|
||||
using var renderTreeBuilder = new RenderTreeBuilder(renderer);
|
||||
GetRenderedBatch(renderTreeBuilder, oldTree, false);
|
||||
var oldComponent = GetComponents(oldTree).Single();
|
||||
Assert.NotNull(oldComponent);
|
||||
|
||||
|
|
@ -724,10 +736,11 @@ namespace Microsoft.AspNetCore.Components.Test
|
|||
// Arrange
|
||||
oldTree.OpenComponent<FakeComponent>(123);
|
||||
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.CloseComponent();
|
||||
var batchBuilder = new RenderBatchBuilder();
|
||||
using var batchBuilder = new RenderBatchBuilder();
|
||||
|
||||
// Act
|
||||
var diff = RenderTreeDiffBuilder.ComputeDiff(renderer, batchBuilder, 0, oldTree.GetFrames(), newTree.GetFrames());
|
||||
|
|
@ -835,7 +848,7 @@ namespace Microsoft.AspNetCore.Components.Test
|
|||
newTree.CloseElement();
|
||||
|
||||
// Act
|
||||
var (result, referenceFrames, batch) = GetSingleUpdatedComponentWithBatch(initializeFromFrames: true);
|
||||
var (result, referenceFrames, batchBuilder) = GetSingleUpdatedComponentWithBatch(initializeFromFrames: true);
|
||||
var removedEventHandlerFrame = oldTree.GetFrames().Array[2];
|
||||
|
||||
// Assert
|
||||
|
|
@ -849,7 +862,7 @@ namespace Microsoft.AspNetCore.Components.Test
|
|||
Assert.NotEqual(0, removedEventHandlerFrame.AttributeEventHandlerId);
|
||||
Assert.Equal(
|
||||
new[] { removedEventHandlerFrame.AttributeEventHandlerId },
|
||||
batch.DisposedEventHandlerIDs.AsEnumerable());
|
||||
batchBuilder.DisposedEventHandlerIDs.AsEnumerable());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -1539,7 +1552,9 @@ namespace Microsoft.AspNetCore.Components.Test
|
|||
newTree.CloseComponent(); // </FakeComponent2>
|
||||
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 originalFakeComponent2Instance = oldTree.GetFrames().Array[3].Component;
|
||||
|
||||
|
|
@ -1569,7 +1584,7 @@ namespace Microsoft.AspNetCore.Components.Test
|
|||
newTree.CloseElement();
|
||||
|
||||
// Act
|
||||
var (result, referenceFrames, batch) = GetSingleUpdatedComponentWithBatch(initializeFromFrames: true);
|
||||
var (result, referenceFrames, batchBuilder) = GetSingleUpdatedComponentWithBatch(initializeFromFrames: true);
|
||||
var oldAttributeFrame = oldTree.GetFrames().Array[1];
|
||||
var newAttributeFrame = newTree.GetFrames().Array[1];
|
||||
|
||||
|
|
@ -1579,7 +1594,7 @@ namespace Microsoft.AspNetCore.Components.Test
|
|||
AssertFrame.Attribute(newAttributeFrame, "ontest", retainedHandler);
|
||||
Assert.NotEqual(0, oldAttributeFrame.AttributeEventHandlerId);
|
||||
Assert.Equal(oldAttributeFrame.AttributeEventHandlerId, newAttributeFrame.AttributeEventHandlerId);
|
||||
Assert.Empty(batch.DisposedEventHandlerIDs.AsEnumerable());
|
||||
Assert.Empty(batchBuilder.DisposedEventHandlerIDs.AsEnumerable());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -1596,7 +1611,7 @@ namespace Microsoft.AspNetCore.Components.Test
|
|||
newTree.CloseElement();
|
||||
|
||||
// Act
|
||||
var (result, referenceFrames, batch) = GetSingleUpdatedComponentWithBatch(initializeFromFrames: true);
|
||||
var (result, referenceFrames, batchBuilder) = GetSingleUpdatedComponentWithBatch(initializeFromFrames: true);
|
||||
var oldAttributeFrame = oldTree.GetFrames().Array[1];
|
||||
var newAttributeFrame = newTree.GetFrames().Array[2];
|
||||
|
||||
|
|
@ -1606,7 +1621,7 @@ namespace Microsoft.AspNetCore.Components.Test
|
|||
AssertFrame.Attribute(newAttributeFrame, "ontest", retainedHandler);
|
||||
Assert.NotEqual(0, oldAttributeFrame.AttributeEventHandlerId);
|
||||
Assert.Equal(oldAttributeFrame.AttributeEventHandlerId, newAttributeFrame.AttributeEventHandlerId);
|
||||
Assert.Empty(batch.DisposedEventHandlerIDs.AsEnumerable());
|
||||
Assert.Empty(batchBuilder.DisposedEventHandlerIDs.AsEnumerable());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -1623,7 +1638,9 @@ namespace Microsoft.AspNetCore.Components.Test
|
|||
newTree.AddAttribute(14, nameof(FakeComponent.ObjectProperty), objectWillNotChange);
|
||||
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;
|
||||
|
||||
// Act
|
||||
|
|
@ -1661,7 +1678,9 @@ namespace Microsoft.AspNetCore.Components.Test
|
|||
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;
|
||||
Assert.Equal(1, originalComponentInstance.SetParametersCallCount);
|
||||
|
||||
|
|
@ -1689,7 +1708,9 @@ namespace Microsoft.AspNetCore.Components.Test
|
|||
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;
|
||||
Assert.Equal(1, componentInstance.SetParametersCallCount);
|
||||
|
||||
|
|
@ -1713,8 +1734,9 @@ namespace Microsoft.AspNetCore.Components.Test
|
|||
newTree.OpenComponent<DisposableComponent>(30); // <DisposableComponent>
|
||||
newTree.CloseComponent(); // </DisposableComponent>
|
||||
|
||||
var batchBuilder = new RenderBatchBuilder();
|
||||
RenderTreeDiffBuilder.ComputeDiff(renderer, batchBuilder, 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());
|
||||
|
||||
// Act/Assert
|
||||
// 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.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);
|
||||
|
||||
newTree.OpenComponent<CaptureSetParametersComponent>(0);
|
||||
|
|
@ -2124,12 +2147,19 @@ namespace Microsoft.AspNetCore.Components.Test
|
|||
{
|
||||
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();
|
||||
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());
|
||||
batchBuilder.UpdatedComponentDiffs.Append(diff);
|
||||
return batchBuilder.ToBatch();
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
|||
public void BasicPropertiesWork()
|
||||
{
|
||||
// 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);
|
||||
|
||||
// Act: take segment containing 2..3
|
||||
|
|
@ -32,7 +32,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
|||
public void StillWorksAfterUnderlyingCapacityChange()
|
||||
{
|
||||
// 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);
|
||||
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