Move common resizable-array logic into shared ArrayBuilder. Replace ArraySegment with ArrayRange since Offset is never used.
This commit is contained in:
parent
cc2d097c99
commit
6e4d0dbca4
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<LangVersion>7.2</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,119 @@
|
|||
// 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.Runtime.CompilerServices;
|
||||
|
||||
namespace Microsoft.Blazor.RenderTree
|
||||
{
|
||||
/// <summary>
|
||||
/// Implements a list that uses an array of objects to store the elements.
|
||||
/// </summary>
|
||||
public class ArrayBuilder<T>
|
||||
{
|
||||
private const int MinCapacity = 10;
|
||||
private T[] _items;
|
||||
private int _itemsInUse;
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a new instance of <see cref="ArrayBuilder{T}"/>.
|
||||
/// </summary>
|
||||
public ArrayBuilder() : this(MinCapacity)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a new instance of <see cref="ArrayBuilder{T}"/>.
|
||||
/// </summary>
|
||||
public ArrayBuilder(int capacity)
|
||||
{
|
||||
_items = new T[capacity < MinCapacity ? MinCapacity : capacity];
|
||||
_itemsInUse = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of items.
|
||||
/// </summary>
|
||||
public int Count => _itemsInUse;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the underlying buffer.
|
||||
/// </summary>
|
||||
public T[] Buffer => _items;
|
||||
|
||||
/// <summary>
|
||||
/// Appends a new item, automatically resizing the underlying array if necessary.
|
||||
/// </summary>
|
||||
/// <param name="item">The item to append.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Append(T item)
|
||||
{
|
||||
if (_itemsInUse == _items.Length)
|
||||
{
|
||||
SetCapacity(_itemsInUse * 2, preserveContents: true);
|
||||
}
|
||||
|
||||
_items[_itemsInUse++] = item;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the last item.
|
||||
/// </summary>
|
||||
public void RemoveLast()
|
||||
{
|
||||
_itemsInUse--;
|
||||
_items[_itemsInUse] = default(T); // Release to GC
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Marks the array as empty, also shrinking the underlying storage if it was
|
||||
/// not being used to near its full capacity.
|
||||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
var previousItemsInUse = _itemsInUse;
|
||||
_itemsInUse = 0;
|
||||
|
||||
if (_items.Length > previousItemsInUse * 1.5)
|
||||
{
|
||||
SetCapacity((previousItemsInUse + _items.Length) / 2, preserveContents: false);
|
||||
}
|
||||
else
|
||||
{
|
||||
Array.Clear(_items, 0, _itemsInUse); // Release to GC
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Produces an <see cref="ArrayRange{T}"/> structure describing the current contents.
|
||||
/// </summary>
|
||||
/// <returns>The <see cref="ArrayRange{T}"/>.</returns>
|
||||
public ArrayRange<T> ToRange()
|
||||
=> new ArrayRange<T>(_items, _itemsInUse);
|
||||
|
||||
private void SetCapacity(int desiredCapacity, bool preserveContents)
|
||||
{
|
||||
if (desiredCapacity < _itemsInUse)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(desiredCapacity), $"The value cannot be less than {nameof(Count)}");
|
||||
}
|
||||
|
||||
var newCapacity = desiredCapacity < MinCapacity ? MinCapacity : desiredCapacity;
|
||||
if (newCapacity != _items.Length)
|
||||
{
|
||||
var newItems = new T[newCapacity];
|
||||
|
||||
if (preserveContents)
|
||||
{
|
||||
Array.Copy(_items, newItems, _itemsInUse);
|
||||
}
|
||||
|
||||
_items = newItems;
|
||||
}
|
||||
else if (!preserveContents)
|
||||
{
|
||||
Array.Clear(_items, 0, _items.Length);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
// 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;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.Blazor.RenderTree
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a range of elements in an array that are in use.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The array item type.</typeparam>
|
||||
public readonly struct ArrayRange<T> : IEnumerable, IEnumerable<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the underlying array instance.
|
||||
/// </summary>
|
||||
public T[] Array { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of items in the array that are considered to be in use.
|
||||
/// </summary>
|
||||
public int Count { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Constructs an instance of <see cref="ArrayRange{T}"/>.
|
||||
/// </summary>
|
||||
/// <param name="array">The array.</param>
|
||||
/// <param name="count">The number of items in the array that are in use.</param>
|
||||
public ArrayRange(T[] array, int count)
|
||||
{
|
||||
Array = array;
|
||||
Count = count;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
IEnumerator<T> IEnumerable<T>.GetEnumerator()
|
||||
=> ((IEnumerable<T>)new ArraySegment<T>(Array, 0, Count)).GetEnumerator();
|
||||
|
||||
/// <inheritdoc />
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
=> ((IEnumerable)new ArraySegment<T>(Array, 0, Count)).GetEnumerator();
|
||||
}
|
||||
}
|
||||
|
|
@ -13,10 +13,8 @@ namespace Microsoft.Blazor.RenderTree
|
|||
/// </summary>
|
||||
public class RenderTreeBuilder
|
||||
{
|
||||
private const int MinBufferLength = 10;
|
||||
private readonly Renderer _renderer;
|
||||
private RenderTreeNode[] _entries = new RenderTreeNode[100];
|
||||
private int _entriesInUse = 0;
|
||||
private readonly ArrayBuilder<RenderTreeNode> _entries = new ArrayBuilder<RenderTreeNode>(10);
|
||||
private readonly Stack<int> _openElementIndices = new Stack<int>();
|
||||
private RenderTreeNodeType? _lastNonAttributeNodeType;
|
||||
|
||||
|
|
@ -39,7 +37,7 @@ namespace Microsoft.Blazor.RenderTree
|
|||
/// <param name="elementName">A value representing the type of the element.</param>
|
||||
public void OpenElement(int sequence, string elementName)
|
||||
{
|
||||
_openElementIndices.Push(_entriesInUse);
|
||||
_openElementIndices.Push(_entries.Count);
|
||||
Append(RenderTreeNode.Element(sequence, elementName));
|
||||
}
|
||||
|
||||
|
|
@ -50,7 +48,7 @@ namespace Microsoft.Blazor.RenderTree
|
|||
public void CloseElement()
|
||||
{
|
||||
var indexOfEntryBeingClosed = _openElementIndices.Pop();
|
||||
_entries[indexOfEntryBeingClosed].CloseElement(_entriesInUse - 1);
|
||||
_entries.Buffer[indexOfEntryBeingClosed].CloseElement(_entries.Count - 1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -149,35 +147,21 @@ namespace Microsoft.Blazor.RenderTree
|
|||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
// If the previous usage of the buffer showed that we have allocated
|
||||
// much more space than needed, free up the excess memory
|
||||
var shrinkToLength = Math.Max(MinBufferLength, _entries.Length / 2);
|
||||
if (_entriesInUse < shrinkToLength)
|
||||
{
|
||||
Array.Resize(ref _entries, shrinkToLength);
|
||||
}
|
||||
|
||||
_entriesInUse = 0;
|
||||
_entries.Clear();
|
||||
_openElementIndices.Clear();
|
||||
_lastNonAttributeNodeType = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the <see cref="RenderTreeNode"/> values that have been appended.
|
||||
/// The return value's <see cref="ArraySegment{T}.Offset"/> is always zero.
|
||||
/// </summary>
|
||||
/// <returns>An array segment of <see cref="RenderTreeNode"/> values.</returns>
|
||||
public ArraySegment<RenderTreeNode> GetNodes() =>
|
||||
new ArraySegment<RenderTreeNode>(_entries, 0, _entriesInUse);
|
||||
/// <returns>An array range of <see cref="RenderTreeNode"/> values.</returns>
|
||||
public ArrayRange<RenderTreeNode> GetNodes() =>
|
||||
_entries.ToRange();
|
||||
|
||||
private void Append(RenderTreeNode node)
|
||||
{
|
||||
if (_entriesInUse == _entries.Length)
|
||||
{
|
||||
Array.Resize(ref _entries, _entries.Length * 2);
|
||||
}
|
||||
|
||||
_entries[_entriesInUse++] = node;
|
||||
_entries.Append(node);
|
||||
|
||||
var nodeType = node.NodeType;
|
||||
if (nodeType != RenderTreeNodeType.Attribute)
|
||||
|
|
|
|||
|
|
@ -9,22 +9,22 @@ namespace Microsoft.Blazor.RenderTree
|
|||
/// Describes changes to a component's render tree between successive renders,
|
||||
/// as well as the resulting state.
|
||||
/// </summary>
|
||||
public struct RenderTreeDiff
|
||||
public readonly struct RenderTreeDiff
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the changes to the render tree since a previous state.
|
||||
/// </summary>
|
||||
public ArraySegment<RenderTreeEdit> Edits { get; private set; }
|
||||
public ArrayRange<RenderTreeEdit> Edits { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the latest render tree. That is, the result of applying the <see cref="Edits"/>
|
||||
/// to the previous state.
|
||||
/// </summary>
|
||||
public ArraySegment<RenderTreeNode> CurrentState { get; private set; }
|
||||
public ArrayRange<RenderTreeNode> CurrentState { get; }
|
||||
|
||||
internal RenderTreeDiff(
|
||||
ArraySegment<RenderTreeEdit> entries,
|
||||
ArraySegment<RenderTreeNode> referenceTree)
|
||||
ArrayRange<RenderTreeEdit> entries,
|
||||
ArrayRange<RenderTreeNode> referenceTree)
|
||||
{
|
||||
Edits = entries;
|
||||
CurrentState = referenceTree;
|
||||
|
|
|
|||
|
|
@ -7,29 +7,17 @@ namespace Microsoft.Blazor.RenderTree
|
|||
{
|
||||
internal class RenderTreeDiffComputer
|
||||
{
|
||||
private const int MinBufferLength = 10;
|
||||
private RenderTreeEdit[] _entries = new RenderTreeEdit[10];
|
||||
private int _entriesInUse;
|
||||
private readonly ArrayBuilder<RenderTreeEdit> _entries = new ArrayBuilder<RenderTreeEdit>(10);
|
||||
|
||||
public RenderTreeDiff ComputeDifference(
|
||||
ArraySegment<RenderTreeNode> oldTree,
|
||||
ArraySegment<RenderTreeNode> newTree)
|
||||
ArrayRange<RenderTreeNode> oldTree,
|
||||
ArrayRange<RenderTreeNode> newTree)
|
||||
{
|
||||
_entriesInUse = 0;
|
||||
_entries.Clear();
|
||||
var siblingIndex = 0;
|
||||
AppendDiffEntriesForRange(oldTree.Array, 0, oldTree.Count, newTree.Array, 0, newTree.Count, ref siblingIndex);
|
||||
|
||||
// If the previous usage of the buffer showed that we have allocated
|
||||
// much more space than needed, free up the excess memory
|
||||
var shrinkToLength = Math.Max(MinBufferLength, _entries.Length / 2);
|
||||
if (_entriesInUse < shrinkToLength)
|
||||
{
|
||||
Array.Resize(ref _entries, shrinkToLength);
|
||||
}
|
||||
|
||||
return new RenderTreeDiff(
|
||||
new ArraySegment<RenderTreeEdit>(_entries, 0, _entriesInUse),
|
||||
newTree);
|
||||
return new RenderTreeDiff(_entries.ToRange(), newTree);
|
||||
}
|
||||
|
||||
private void AppendDiffEntriesForRange(
|
||||
|
|
@ -303,19 +291,15 @@ namespace Microsoft.Blazor.RenderTree
|
|||
if (entry.Type == RenderTreeEditType.StepOut)
|
||||
{
|
||||
// If the preceding node is a StepIn, then the StepOut cancels it out
|
||||
if (_entriesInUse > 0 && _entries[_entriesInUse - 1].Type == RenderTreeEditType.StepIn)
|
||||
var previousIndex = _entries.Count - 1;
|
||||
if (previousIndex >= 0 && _entries.Buffer[previousIndex].Type == RenderTreeEditType.StepIn)
|
||||
{
|
||||
_entriesInUse--;
|
||||
_entries.RemoveLast();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (_entriesInUse == _entries.Length)
|
||||
{
|
||||
Array.Resize(ref _entries, _entries.Length * 2);
|
||||
}
|
||||
|
||||
_entries[_entriesInUse++] = entry;
|
||||
_entries.Append(entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -55,11 +55,11 @@ namespace Microsoft.Blazor.Rendering
|
|||
_renderer.UpdateDisplay(_componentId, diff);
|
||||
}
|
||||
|
||||
private void EnsureChildComponentsInstantiated(ArraySegment<RenderTreeNode> renderTree)
|
||||
private void EnsureChildComponentsInstantiated(ArrayRange<RenderTreeNode> renderTree)
|
||||
{
|
||||
var array = renderTree.Array;
|
||||
var offsetPlusCount = renderTree.Offset + renderTree.Count;
|
||||
for (var i = renderTree.Offset; i < offsetPlusCount; i++)
|
||||
var count = renderTree.Count;
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
if (array[i].NodeType == RenderTreeNodeType.Component
|
||||
&& array[i].Component == null)
|
||||
|
|
|
|||
|
|
@ -336,7 +336,7 @@ namespace Microsoft.Blazor.Build.Test
|
|||
=> node.NodeType != RenderTreeNodeType.Text
|
||||
|| !string.IsNullOrWhiteSpace(node.TextContent);
|
||||
|
||||
private static ArraySegment<RenderTreeNode> GetRenderTree(IComponent component)
|
||||
private static ArrayRange<RenderTreeNode> GetRenderTree(IComponent component)
|
||||
{
|
||||
var treeBuilder = new RenderTreeBuilder(new TestRenderer());
|
||||
component.BuildRenderTree(treeBuilder);
|
||||
|
|
|
|||
|
|
@ -31,7 +31,6 @@ namespace Microsoft.Blazor.Test
|
|||
// Assert
|
||||
var nodes = builder.GetNodes();
|
||||
Assert.NotNull(nodes.Array);
|
||||
Assert.Equal(0, nodes.Offset);
|
||||
Assert.Empty(nodes);
|
||||
}
|
||||
|
||||
|
|
@ -49,7 +48,6 @@ namespace Microsoft.Blazor.Test
|
|||
|
||||
// Assert
|
||||
var nodes = builder.GetNodes();
|
||||
Assert.Equal(0, nodes.Offset);
|
||||
Assert.Collection(nodes,
|
||||
node => AssertNode.Text(node, "First item"),
|
||||
node => AssertNode.Text(node, string.Empty),
|
||||
|
|
@ -69,7 +67,6 @@ namespace Microsoft.Blazor.Test
|
|||
|
||||
// Assert
|
||||
var nodes = builder.GetNodes();
|
||||
Assert.Equal(0, nodes.Offset);
|
||||
Assert.Collection(nodes,
|
||||
node => AssertNode.Text(node, "1234"),
|
||||
node => AssertNode.Text(node, string.Empty));
|
||||
|
|
@ -103,7 +100,7 @@ namespace Microsoft.Blazor.Test
|
|||
// Assert
|
||||
var nodes = builder.GetNodes();
|
||||
Assert.Equal(2, nodes.Count);
|
||||
AssertNode.Element(nodes[1], "my element", 1);
|
||||
AssertNode.Element(nodes.Array[1], "my element", 1);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -122,7 +119,7 @@ namespace Microsoft.Blazor.Test
|
|||
// Assert
|
||||
var nodes = builder.GetNodes();
|
||||
Assert.Equal(4, nodes.Count);
|
||||
AssertNode.Element(nodes[0], "my element", 2);
|
||||
AssertNode.Element(nodes.Array[0], "my element", 2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
|
|||
|
|
@ -313,8 +313,8 @@ namespace Microsoft.Blazor.Test
|
|||
|
||||
private class TestRenderer : Renderer
|
||||
{
|
||||
public IDictionary<int, ArraySegment<RenderTreeNode>> RenderTreesByComponentId { get; }
|
||||
= new Dictionary<int, ArraySegment<RenderTreeNode>>();
|
||||
public IDictionary<int, ArrayRange<RenderTreeNode>> RenderTreesByComponentId { get; }
|
||||
= new Dictionary<int, ArrayRange<RenderTreeNode>>();
|
||||
|
||||
public new int AssignComponentId(IComponent component)
|
||||
=> base.AssignComponentId(component);
|
||||
|
|
|
|||
Loading…
Reference in New Issue