Add the concept of a "Region" render tree frame

This commit is contained in:
Steve Sanderson 2018-02-14 21:35:26 +00:00
parent 043e623d5b
commit a9822216f1
5 changed files with 115 additions and 11 deletions

View File

@ -57,17 +57,6 @@ namespace Microsoft.AspNetCore.Blazor.RenderTree
entry = entry.WithElementSubtreeLength(_entries.Count - indexOfEntryBeingClosed);
}
/// <summary>
/// Marks a previously appended component frame as closed. Calls to this method
/// must be balanced with calls to <see cref="OpenComponent{TComponent}"/>.
/// </summary>
public void CloseComponent()
{
var indexOfEntryBeingClosed = _openElementIndices.Pop();
ref var entry = ref _entries.Buffer[indexOfEntryBeingClosed];
entry = entry.WithComponentSubtreeLength(_entries.Count - indexOfEntryBeingClosed);
}
/// <summary>
/// Appends a frame representing text content.
/// </summary>
@ -171,6 +160,39 @@ namespace Microsoft.AspNetCore.Blazor.RenderTree
Append(RenderTreeFrame.ChildComponent<TComponent>(sequence));
}
/// <summary>
/// Marks a previously appended component frame as closed. Calls to this method
/// must be balanced with calls to <see cref="OpenComponent{TComponent}"/>.
/// </summary>
public void CloseComponent()
{
var indexOfEntryBeingClosed = _openElementIndices.Pop();
ref var entry = ref _entries.Buffer[indexOfEntryBeingClosed];
entry = entry.WithComponentSubtreeLength(_entries.Count - indexOfEntryBeingClosed);
}
/// <summary>
/// Appends a frame denoting the start of a region (that is, a tree fragment that is
/// processed as a unit for the purposes of diffing).
/// </summary>
/// <param name="sequence">An integer that represents the position of the instruction in the source code.</param>
public void OpenRegion(int sequence)
{
_openElementIndices.Push(_entries.Count);
Append(RenderTreeFrame.Region(sequence));
}
/// <summary>
/// Marks a previously appended region frame as closed. Calls to this method
/// must be balanced with calls to <see cref="OpenRegion"/>.
/// </summary>
internal void CloseRegion()
{
var indexOfEntryBeingClosed = _openElementIndices.Pop();
ref var entry = ref _entries.Buffer[indexOfEntryBeingClosed];
entry = entry.WithRegionSubtreeLength(_entries.Count - indexOfEntryBeingClosed);
}
private void AssertCanAddAttribute()
{
if (_lastNonAttributeFrameType != RenderTreeFrameType.Element

View File

@ -118,6 +118,17 @@ namespace Microsoft.AspNetCore.Blazor.RenderTree
/// </summary>
[FieldOffset(24)] public readonly IComponent Component;
// --------------------------------------------------------------------------------
// RenderTreeFrameType.Region
// --------------------------------------------------------------------------------
/// <summary>
/// If the <see cref="FrameType"/> property equals <see cref="RenderTreeFrameType.Region"/>
/// gets the number of frames in the subtree for which this frame is the root.
/// The value is zero if the frame has not yet been closed.
/// </summary>
[FieldOffset(8)] public readonly int RegionSubtreeLength;
private RenderTreeFrame(int sequence, string elementName, int elementSubtreeLength)
: this()
{
@ -170,6 +181,14 @@ namespace Microsoft.AspNetCore.Blazor.RenderTree
AttributeEventHandlerId = eventHandlerId;
}
private RenderTreeFrame(int sequence, int regionSubtreeLength)
: this()
{
FrameType = RenderTreeFrameType.Region;
Sequence = sequence;
RegionSubtreeLength = regionSubtreeLength;
}
internal static RenderTreeFrame Element(int sequence, string elementName)
=> new RenderTreeFrame(sequence, elementName: elementName, elementSubtreeLength: 0);
@ -185,6 +204,9 @@ namespace Microsoft.AspNetCore.Blazor.RenderTree
internal static RenderTreeFrame ChildComponent<T>(int sequence) where T : IComponent
=> new RenderTreeFrame(sequence, typeof(T), 0);
internal static RenderTreeFrame Region(int sequence)
=> new RenderTreeFrame(sequence, regionSubtreeLength: 0);
internal RenderTreeFrame WithElementSubtreeLength(int elementSubtreeLength)
=> new RenderTreeFrame(Sequence, elementName: ElementName, elementSubtreeLength: elementSubtreeLength);
@ -199,5 +221,8 @@ namespace Microsoft.AspNetCore.Blazor.RenderTree
internal RenderTreeFrame WithAttributeEventHandlerId(int eventHandlerId)
=> new RenderTreeFrame(Sequence, AttributeName, AttributeValue, eventHandlerId);
internal RenderTreeFrame WithRegionSubtreeLength(int regionSubtreeLength)
=> new RenderTreeFrame(Sequence, regionSubtreeLength: regionSubtreeLength);
}
}

View File

@ -27,5 +27,13 @@ namespace Microsoft.AspNetCore.Blazor.RenderTree
/// Represents a child component.
/// </summary>
Component = 4,
/// <summary>
/// Defines the boundary around range of sibling frames that should be treated as an
/// unsplittable group for the purposes of diffing. This is typically used when appending
/// a tree fragment generated by external code, because the sequence numbers in that tree
/// fragment are not comparable to sequence numbers outside it.
/// </summary>
Region = 5,
}
}

View File

@ -245,6 +245,20 @@ namespace Microsoft.AspNetCore.Blazor.Test
});
}
[Fact]
public void CannotAddAttributeToRegion()
{
// Arrange
var builder = new RenderTreeBuilder(new TestRenderer());
// Act/Assert
Assert.Throws<InvalidOperationException>(() =>
{
builder.OpenRegion(0);
builder.AddAttribute(1, "name", "value");
});
}
[Fact]
public void CanAddChildComponents()
{
@ -272,6 +286,34 @@ namespace Microsoft.AspNetCore.Blazor.Test
frame => AssertFrame.Attribute(frame, "child2attribute", "C"));
}
[Fact]
public void CanAddRegions()
{
// Arrange
var builder = new RenderTreeBuilder(new TestRenderer());
// Act
builder.OpenElement(10, "parent"); // 0: <parent>
builder.OpenRegion(11); // 1: [region
builder.AddText(3, "Hello"); // 2: Hello
builder.OpenRegion(4); // 3: [region
builder.OpenElement(3, "another"); // 4: <another>
builder.CloseElement(); // </another>
builder.CloseRegion(); // ]
builder.AddText(6, "Goodbye"); // 5: Goodbye
builder.CloseRegion(); // ]
builder.CloseElement(); // </parent>
// Assert
Assert.Collection(builder.GetFrames(),
frame => AssertFrame.Element(frame, "parent", 6, 10),
frame => AssertFrame.Region(frame, 5, 11),
frame => AssertFrame.Text(frame, "Hello", 3),
frame => AssertFrame.Region(frame, 2, 4),
frame => AssertFrame.Element(frame, "another", 1, 3),
frame => AssertFrame.Text(frame, "Goodbye", 6));
}
[Fact]
public void CanClear()
{

View File

@ -67,6 +67,13 @@ namespace Microsoft.AspNetCore.Blazor.Test.Shared
Assert.Equal(componentId, frame.ComponentId);
}
public static void Region(RenderTreeFrame frame, int subtreeLength, int? sequence = null)
{
Assert.Equal(RenderTreeFrameType.Region, frame.FrameType);
Assert.Equal(subtreeLength, frame.RegionSubtreeLength);
AssertFrame.Sequence(frame, sequence);
}
public static void Whitespace(RenderTreeFrame frame, int? sequence = null)
{
Assert.Equal(RenderTreeFrameType.Text, frame.FrameType);