In tree diffs, omit trailing Continue entries and skip over unmodified subtrees

This commit is contained in:
Steve Sanderson 2018-01-22 01:55:10 +00:00
parent b021e19598
commit 7c30d51be9
2 changed files with 103 additions and 25 deletions

View File

@ -9,7 +9,7 @@ namespace Microsoft.Blazor.RenderTree
{
private const int MinBufferLength = 10;
private RenderTreeDiffEntry[] _entries = new RenderTreeDiffEntry[10];
private int _entriesInUse = 0;
private int _entriesInUse;
public ArraySegment<RenderTreeDiffEntry> ComputeDifference(
ArraySegment<RenderTreeNode> oldTree,
@ -17,6 +17,7 @@ namespace Microsoft.Blazor.RenderTree
{
_entriesInUse = 0;
AppendDiffEntriesForRange(oldTree.Array, 0, oldTree.Count, newTree.Array, 0, newTree.Count);
TrimTrailingContinueNodes();
// If the previous usage of the buffer showed that we have allocated
// much more space than needed, free up the excess memory
@ -292,6 +293,19 @@ namespace Microsoft.Blazor.RenderTree
private void Append(RenderTreeDiffEntry entry)
{
if (entry.Type == RenderTreeDiffEntryType.StepOut)
{
TrimTrailingContinueNodes();
// If the preceding node is now a StepIn, then we can coalesce the StepIn+StepOut
// down to a single Continue
if (_entriesInUse > 0 && _entries[_entriesInUse - 1].Type == RenderTreeDiffEntryType.StepIn)
{
_entriesInUse--;
entry = RenderTreeDiffEntry.Continue();
}
}
if (_entriesInUse == _entries.Length)
{
Array.Resize(ref _entries, _entries.Length * 2);
@ -299,5 +313,13 @@ namespace Microsoft.Blazor.RenderTree
_entries[_entriesInUse++] = entry;
}
private void TrimTrailingContinueNodes()
{
while (_entriesInUse > 0 && _entries[_entriesInUse - 1].Type == RenderTreeDiffEntryType.Continue)
{
_entriesInUse--;
}
}
}
}

View File

@ -28,8 +28,7 @@ namespace Microsoft.Blazor.Test
var result = diff.ComputeDifference(oldTree.GetNodes(), newTree.GetNodes());
// Assert
Assert.Collection(result,
entry => Assert.Equal(RenderTreeDiffEntryType.Continue, entry.Type));
Assert.Empty(result);
}
public static IEnumerable<object[]> RecognizesEquivalentNodesAsSameCases()
@ -69,8 +68,7 @@ namespace Microsoft.Blazor.Test
{
Assert.Equal(RenderTreeDiffEntryType.PrependNode, entry.Type);
Assert.Equal(1, entry.NewTreeIndex);
},
entry => Assert.Equal(RenderTreeDiffEntryType.Continue, entry.Type));
});
}
[Fact]
@ -92,8 +90,7 @@ namespace Microsoft.Blazor.Test
// Assert
Assert.Collection(result,
entry => Assert.Equal(RenderTreeDiffEntryType.Continue, entry.Type),
entry => Assert.Equal(RenderTreeDiffEntryType.RemoveNode, entry.Type),
entry => Assert.Equal(RenderTreeDiffEntryType.Continue, entry.Type));
entry => Assert.Equal(RenderTreeDiffEntryType.RemoveNode, entry.Type));
}
[Fact]
@ -117,8 +114,7 @@ namespace Microsoft.Blazor.Test
Assert.Collection(result,
entry => Assert.Equal(RenderTreeDiffEntryType.Continue, entry.Type),
entry => Assert.Equal(RenderTreeDiffEntryType.RemoveNode, entry.Type),
entry => Assert.Equal(RenderTreeDiffEntryType.RemoveNode, entry.Type),
entry => Assert.Equal(RenderTreeDiffEntryType.Continue, entry.Type));
entry => Assert.Equal(RenderTreeDiffEntryType.RemoveNode, entry.Type));
}
[Fact]
@ -150,8 +146,7 @@ namespace Microsoft.Blazor.Test
{
Assert.Equal(RenderTreeDiffEntryType.PrependNode, entry.Type);
Assert.Equal(2, entry.NewTreeIndex);
},
entry => Assert.Equal(RenderTreeDiffEntryType.Continue, entry.Type));
});
}
[Fact]
@ -241,8 +236,7 @@ namespace Microsoft.Blazor.Test
{
Assert.Equal(RenderTreeDiffEntryType.PrependNode, entry.Type);
Assert.Equal(2, entry.NewTreeIndex);
},
entry => Assert.Equal(RenderTreeDiffEntryType.Continue, entry.Type));
});
}
[Fact]
@ -266,8 +260,7 @@ namespace Microsoft.Blazor.Test
Assert.Collection(result,
entry => Assert.Equal(RenderTreeDiffEntryType.Continue, entry.Type),
entry => Assert.Equal(RenderTreeDiffEntryType.RemoveNode, entry.Type),
entry => Assert.Equal(RenderTreeDiffEntryType.RemoveNode, entry.Type),
entry => Assert.Equal(RenderTreeDiffEntryType.Continue, entry.Type));
entry => Assert.Equal(RenderTreeDiffEntryType.RemoveNode, entry.Type));
}
[Fact]
@ -387,8 +380,7 @@ namespace Microsoft.Blazor.Test
{
Assert.Equal(RenderTreeDiffEntryType.SetAttribute, entry.Type);
Assert.Equal(2, entry.NewTreeIndex);
},
entry => Assert.Equal(RenderTreeDiffEntryType.Continue, entry.Type));
});
}
[Fact]
@ -415,8 +407,7 @@ namespace Microsoft.Blazor.Test
{
Assert.Equal(RenderTreeDiffEntryType.RemoveAttribute, entry.Type);
Assert.Equal("will be removed", entry.RemovedAttributeName);
},
entry => Assert.Equal(RenderTreeDiffEntryType.Continue, entry.Type));
});
}
[Fact]
@ -444,8 +435,7 @@ namespace Microsoft.Blazor.Test
{
Assert.Equal(RenderTreeDiffEntryType.SetAttribute, entry.Type);
Assert.Equal(2, entry.NewTreeIndex);
},
entry => Assert.Equal(RenderTreeDiffEntryType.Continue, entry.Type));
});
}
[Fact]
@ -476,8 +466,7 @@ namespace Microsoft.Blazor.Test
{
Assert.Equal(RenderTreeDiffEntryType.SetAttribute, entry.Type);
Assert.Equal(2, entry.NewTreeIndex);
},
entry => Assert.Equal(RenderTreeDiffEntryType.Continue, entry.Type));
});
}
[Fact]
@ -508,8 +497,7 @@ namespace Microsoft.Blazor.Test
{
Assert.Equal(RenderTreeDiffEntryType.RemoveAttribute, entry.Type);
Assert.Equal("oldname", entry.RemovedAttributeName);
},
entry => Assert.Equal(RenderTreeDiffEntryType.Continue, entry.Type));
});
}
[Fact]
@ -553,6 +541,74 @@ namespace Microsoft.Blazor.Test
entry => Assert.Equal(RenderTreeDiffEntryType.StepOut, entry.Type));
}
[Fact]
public void SkipsUnmodifiedSubtrees()
{
// Arrange
var oldTree = new RenderTreeBuilder(new FakeRenderer());
var newTree = new RenderTreeBuilder(new FakeRenderer());
var diff = new RenderTreeDiff();
oldTree.OpenElement(10, "root");
oldTree.AddText(11, "Text that will change");
oldTree.OpenElement(12, "Subtree that will not change");
oldTree.OpenElement(13, "Another");
oldTree.AddText(14, "Text that will not change");
oldTree.CloseElement();
oldTree.CloseElement();
oldTree.CloseElement();
newTree.OpenElement(10, "root");
newTree.AddText(11, "Text that has changed");
newTree.OpenElement(12, "Subtree that will not change");
newTree.OpenElement(13, "Another");
newTree.AddText(14, "Text that will not change");
newTree.CloseElement();
newTree.CloseElement();
newTree.CloseElement();
// Act
var result = diff.ComputeDifference(oldTree.GetNodes(), newTree.GetNodes());
// Assert
Assert.Collection(result,
entry => Assert.Equal(RenderTreeDiffEntryType.StepIn, entry.Type),
entry =>
{
Assert.Equal(RenderTreeDiffEntryType.UpdateText, entry.Type);
Assert.Equal(1, entry.NewTreeIndex);
},
entry => Assert.Equal(RenderTreeDiffEntryType.StepOut, entry.Type));
}
[Fact]
public void SkipsUnmodifiedTrailingSiblings()
{
// Arrange
var oldTree = new RenderTreeBuilder(new FakeRenderer());
var newTree = new RenderTreeBuilder(new FakeRenderer());
var diff = new RenderTreeDiff();
oldTree.AddText(10, "text1");
oldTree.AddText(11, "text2");
oldTree.AddText(12, "text3");
oldTree.AddText(13, "text4");
newTree.AddText(10, "text1");
newTree.AddText(11, "text2modified");
newTree.AddText(12, "text3");
newTree.AddText(13, "text4");
// Act
var result = diff.ComputeDifference(oldTree.GetNodes(), newTree.GetNodes());
// Assert
Assert.Collection(result,
entry => Assert.Equal(RenderTreeDiffEntryType.Continue, entry.Type),
entry =>
{
Assert.Equal(RenderTreeDiffEntryType.UpdateText, entry.Type);
Assert.Equal(1, entry.NewTreeIndex);
});
}
private class FakeRenderer : Renderer
{
internal protected override void UpdateDisplay(int componentId, ArraySegment<RenderTreeNode> renderTree)