From b021e195980d7b5cd6542d09b3470953657404e5 Mon Sep 17 00:00:00 2001 From: Steve Sanderson Date: Sun, 21 Jan 2018 21:30:01 +0000 Subject: [PATCH] In diffing, support elements with descendants --- .../RenderTree/RenderTreeDiff.cs | 46 ++++++++++++++++--- .../RenderTree/RenderTreeDiffEntry.cs | 10 ++++ .../RenderTree/RenderTreeDiffEntryType.cs | 2 + .../RenderTreeDiffTest.cs | 41 +++++++++++++++++ 4 files changed, 93 insertions(+), 6 deletions(-) diff --git a/src/Microsoft.Blazor/RenderTree/RenderTreeDiff.cs b/src/Microsoft.Blazor/RenderTree/RenderTreeDiff.cs index 9605ac4003..8d6e6dbe01 100644 --- a/src/Microsoft.Blazor/RenderTree/RenderTreeDiff.cs +++ b/src/Microsoft.Blazor/RenderTree/RenderTreeDiff.cs @@ -187,13 +187,32 @@ namespace Microsoft.Blazor.RenderTree var newElementName = newTree[newNodeIndex].ElementName; if (string.Equals(oldElementName, newElementName, StringComparison.Ordinal)) { - // Recurse into the element. This covers diffing the attributes as well as - // the descendants. - AppendDiffEntriesForRange( - oldTree, oldNodeIndex + 1, oldTree[oldNodeIndex].ElementDescendantsEndIndex + 1, - newTree, newNodeIndex + 1, newTree[newNodeIndex].ElementDescendantsEndIndex + 1); + var oldNodeAttributesEndIndexExcl = GetAttributesEndIndexExclusive(oldTree, oldNodeIndex); + var newNodeAttributesEndIndexExcl = GetAttributesEndIndexExclusive(newTree, newNodeIndex); - Append(RenderTreeDiffEntry.Continue()); + // Diff the attributes + AppendDiffEntriesForRange( + oldTree, oldNodeIndex + 1, oldNodeAttributesEndIndexExcl, + newTree, newNodeIndex + 1, newNodeAttributesEndIndexExcl); + + // Diff the children + var oldNodeChildrenEndIndexExcl = oldTree[oldNodeIndex].ElementDescendantsEndIndex + 1; + var newNodeChildrenEndIndexExcl = newTree[newNodeIndex].ElementDescendantsEndIndex + 1; + var hasChildrenToProcess = + oldNodeChildrenEndIndexExcl > oldNodeAttributesEndIndexExcl || + newNodeChildrenEndIndexExcl > newNodeAttributesEndIndexExcl; + if (hasChildrenToProcess) + { + Append(RenderTreeDiffEntry.StepIn()); + AppendDiffEntriesForRange( + oldTree, oldNodeAttributesEndIndexExcl, oldNodeChildrenEndIndexExcl, + newTree, newNodeAttributesEndIndexExcl, newNodeChildrenEndIndexExcl); + Append(RenderTreeDiffEntry.StepOut()); + } + else + { + Append(RenderTreeDiffEntry.Continue()); + } } else { @@ -256,6 +275,21 @@ namespace Microsoft.Blazor.RenderTree } } + private int GetAttributesEndIndexExclusive(RenderTreeNode[] tree, int rootIndex) + { + var descendantsEndIndex = tree[rootIndex].ElementDescendantsEndIndex; + var index = rootIndex + 1; + for (; index <= descendantsEndIndex; index++) + { + if (tree[index].NodeType != RenderTreeNodeType.Attribute) + { + break; + } + } + + return index; + } + private void Append(RenderTreeDiffEntry entry) { if (_entriesInUse == _entries.Length) diff --git a/src/Microsoft.Blazor/RenderTree/RenderTreeDiffEntry.cs b/src/Microsoft.Blazor/RenderTree/RenderTreeDiffEntry.cs index e718e2278f..368239f36a 100644 --- a/src/Microsoft.Blazor/RenderTree/RenderTreeDiffEntry.cs +++ b/src/Microsoft.Blazor/RenderTree/RenderTreeDiffEntry.cs @@ -45,5 +45,15 @@ namespace Microsoft.Blazor.RenderTree Type = RenderTreeDiffEntryType.RemoveAttribute, RemovedAttributeName = name }; + + internal static RenderTreeDiffEntry StepIn() => new RenderTreeDiffEntry + { + Type = RenderTreeDiffEntryType.StepIn + }; + + internal static RenderTreeDiffEntry StepOut() => new RenderTreeDiffEntry + { + Type = RenderTreeDiffEntryType.StepOut + }; } } diff --git a/src/Microsoft.Blazor/RenderTree/RenderTreeDiffEntryType.cs b/src/Microsoft.Blazor/RenderTree/RenderTreeDiffEntryType.cs index 25f95a15b6..3aaa535a01 100644 --- a/src/Microsoft.Blazor/RenderTree/RenderTreeDiffEntryType.cs +++ b/src/Microsoft.Blazor/RenderTree/RenderTreeDiffEntryType.cs @@ -11,5 +11,7 @@ namespace Microsoft.Blazor.RenderTree SetAttribute = 4, RemoveAttribute = 5, UpdateText = 6, + StepIn = 7, + StepOut = 8, } } diff --git a/test/Microsoft.Blazor.Test/RenderTreeDiffTest.cs b/test/Microsoft.Blazor.Test/RenderTreeDiffTest.cs index 98cd620eba..f54048f1f4 100644 --- a/test/Microsoft.Blazor.Test/RenderTreeDiffTest.cs +++ b/test/Microsoft.Blazor.Test/RenderTreeDiffTest.cs @@ -512,6 +512,47 @@ namespace Microsoft.Blazor.Test entry => Assert.Equal(RenderTreeDiffEntryType.Continue, entry.Type)); } + [Fact] + public void DiffsElementsHierarchically() + { + // Arrange + var oldTree = new RenderTreeBuilder(new FakeRenderer()); + var newTree = new RenderTreeBuilder(new FakeRenderer()); + var diff = new RenderTreeDiff(); + oldTree.OpenElement(10, "root"); + oldTree.OpenElement(11, "child"); + oldTree.OpenElement(12, "grandchild"); + oldTree.AddText(13, "grandchild old text"); + oldTree.CloseElement(); + oldTree.CloseElement(); + oldTree.CloseElement(); + + newTree.OpenElement(10, "root"); + newTree.OpenElement(11, "child"); + newTree.OpenElement(12, "grandchild"); + newTree.AddText(13, "grandchild new text"); + 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.StepIn, entry.Type), + entry => Assert.Equal(RenderTreeDiffEntryType.StepIn, entry.Type), + entry => + { + Assert.Equal(RenderTreeDiffEntryType.UpdateText, entry.Type); + Assert.Equal(3, entry.NewTreeIndex); + }, + entry => Assert.Equal(RenderTreeDiffEntryType.StepOut, entry.Type), + entry => Assert.Equal(RenderTreeDiffEntryType.StepOut, entry.Type), + entry => Assert.Equal(RenderTreeDiffEntryType.StepOut, entry.Type)); + } + private class FakeRenderer : Renderer { internal protected override void UpdateDisplay(int componentId, ArraySegment renderTree)