private RenderBatch GetRenderedBatch(RenderTreeBuilder from, RenderTreeBuilder to) { var batchBuilder = new RenderBatchBuilder(); diff.ApplyNewRenderTreeVersion(batchBuilder, 0, from.GetFrames(), to.GetFrames()); return(batchBuilder.ToBatch()); }
public void RecognizesComponentTypeChangesAtSameSequenceNumber() { // Arrange oldTree.OpenComponent <FakeComponent>(123); oldTree.CloseComponent(); GetRenderedBatch(new RenderTreeBuilder(renderer), oldTree); // Assign initial IDs newTree.OpenComponent <FakeComponent2>(123); newTree.CloseComponent(); var batchBuilder = new RenderBatchBuilder(); // Act var diff = RenderTreeDiffBuilder.ComputeDiff(renderer, batchBuilder, 0, oldTree.GetFrames(), newTree.GetFrames()); // Assert: We're going to dispose the old component, and render the new one Assert.Equal(new[] { 0 }, batchBuilder.ComponentDisposalQueue); Assert.Equal(new[] { 1 }, batchBuilder.ComponentRenderQueue); // Assert: Got correct info in diff Assert.Collection(diff.Edits, entry => AssertEdit(entry, RenderTreeEditType.RemoveFrame, 0), entry => { AssertEdit(entry, RenderTreeEditType.PrependFrame, 0); Assert.IsType <FakeComponent2>(batchBuilder.ReferenceFramesBuffer.Buffer[entry.ReferenceFrameIndex].Component); }); }
public RenderTreeDiffBuilderBenchmark() { builder = new RenderBatchBuilder(); renderer = new FakeRenderer(); // A simple component for basic tests -- this is similar to what MVC scaffolding generates // for bootstrap3 form fields, but modified to be more Blazorey. original = new RenderTreeBuilder(renderer); original.OpenElement(0, "div"); original.AddAttribute(1, "class", "form-group"); original.OpenElement(2, "label"); original.AddAttribute(3, "class", "control-label"); original.AddAttribute(4, "for", "name"); original.AddAttribute(5, "data-unvalidated", true); original.AddContent(6, "Car"); original.CloseElement(); original.OpenElement(7, "input"); original.AddAttribute(8, "class", "form-control"); original.AddAttribute(9, "type", "text"); original.AddAttribute(10, "name", "name"); // Notice the gap in sequence numbers original.AddAttribute(12, "value", ""); original.CloseElement(); original.OpenElement(13, "span"); original.AddAttribute(14, "class", "text-danger field-validation-valid"); original.AddContent(15, ""); original.CloseElement(); original.CloseElement(); // Now simulate some input modified = new RenderTreeBuilder(renderer); modified.OpenElement(0, "div"); modified.AddAttribute(1, "class", "form-group"); modified.OpenElement(2, "label"); modified.AddAttribute(3, "class", "control-label"); modified.AddAttribute(4, "for", "name"); modified.AddAttribute(5, "data-unvalidated", false); modified.AddContent(6, "Car"); modified.CloseElement(); modified.OpenElement(7, "input"); modified.AddAttribute(8, "class", "form-control"); modified.AddAttribute(9, "type", "text"); modified.AddAttribute(10, "name", "name"); modified.AddAttribute(11, "data-validation-state", "invalid"); modified.AddAttribute(12, "value", "Lamborghini"); modified.CloseElement(); modified.OpenElement(13, "span"); modified.AddAttribute(14, "class", "text-danger field-validation-invalid"); // changed modified.AddContent(15, "No, you can't afford that."); modified.CloseElement(); modified.CloseElement(); }
private RenderBatch GetRenderedBatch(RenderTreeBuilder from, RenderTreeBuilder to) { var batchBuilder = new RenderBatchBuilder(); var diff = RenderTreeDiffBuilder.ComputeDiff(renderer, batchBuilder, 0, from.GetFrames(), to.GetFrames()); batchBuilder.UpdatedComponentDiffs.Append(diff); return(batchBuilder.ToBatch()); }
private void CleanupComponentStateResources(RenderBatchBuilder batchBuilder) { // We don't expect these things to throw. RenderTreeDiffBuilder.DisposeFrames(batchBuilder, CurrentRenderTree.GetFrames()); if (_hasAnyCascadingParameterSubscriptions) { RemoveCascadingParameterSubscriptions(); } DisposeBuffers(); }
public DiffContext( Renderer renderer, RenderBatchBuilder batchBuilder, RenderTreeFrame[] oldTree, RenderTreeFrame[] newTree) { Renderer = renderer; BatchBuilder = batchBuilder; OldTree = oldTree; NewTree = newTree; Edits = batchBuilder.EditsBuffer; ReferenceFrames = batchBuilder.ReferenceFramesBuffer; SiblingIndex = 0; }
private RenderBatch GetRenderedBatch(RenderTreeBuilder from, RenderTreeBuilder to, bool initializeFromFrames) { if (initializeFromFrames) { var emptyFrames = new RenderTreeBuilder(renderer).GetFrames(); var oldFrames = from.GetFrames(); RenderTreeDiffBuilder.ComputeDiff(renderer, new RenderBatchBuilder(), 0, emptyFrames, oldFrames); } var batchBuilder = new RenderBatchBuilder(); var diff = RenderTreeDiffBuilder.ComputeDiff(renderer, batchBuilder, 0, from.GetFrames(), to.GetFrames()); batchBuilder.UpdatedComponentDiffs.Append(diff); return(batchBuilder.ToBatch()); }
public DiffContext( Renderer renderer, RenderBatchBuilder batchBuilder, int componentId, RenderTreeFrame[] oldTree, RenderTreeFrame[] newTree) { Renderer = renderer; BatchBuilder = batchBuilder; ComponentId = componentId; OldTree = oldTree; NewTree = newTree; Edits = batchBuilder.EditsBuffer; ReferenceFrames = batchBuilder.ReferenceFramesBuffer; AttributeDiffSet = batchBuilder.AttributeDiffSet; SiblingIndex = 0; }
/// <summary> /// As well as computing the diff between the two trees, this method also has the side-effect /// of instantiating child components on newly-inserted Component frames, and copying the existing /// component instances onto retained Component frames. It's particularly convenient to do that /// here because we have the right information and are already walking the trees to do the diff. /// </summary> public void ApplyNewRenderTreeVersion( RenderBatchBuilder batchBuilder, int componentId, ArrayRange <RenderTreeFrame> oldTree, ArrayRange <RenderTreeFrame> newTree) { _entries.Clear(); _referenceFrames.Clear(); var siblingIndex = 0; var slotId = batchBuilder.ReserveUpdatedComponentSlotId(); AppendDiffEntriesForRange(batchBuilder, oldTree.Array, 0, oldTree.Count, newTree.Array, 0, newTree.Count, ref siblingIndex); batchBuilder.SetUpdatedComponent( slotId, new RenderTreeDiff(componentId, _entries.ToRange(), _referenceFrames.ToRange())); }
public static RenderTreeDiff ComputeDiff( Renderer renderer, RenderBatchBuilder batchBuilder, int componentId, ArrayRange <RenderTreeFrame> oldTree, ArrayRange <RenderTreeFrame> newTree) { var editsBuffer = batchBuilder.EditsBuffer; var editsBufferStartLength = editsBuffer.Count; var diffContext = new DiffContext(renderer, batchBuilder, oldTree.Array, newTree.Array); AppendDiffEntriesForRange(ref diffContext, 0, oldTree.Count, 0, newTree.Count); var editsSegment = editsBuffer.ToSegment(editsBufferStartLength, editsBuffer.Count); return(new RenderTreeDiff(componentId, editsSegment)); }
public void RenderIntoBatch(RenderBatchBuilder batchBuilder, RenderFragment renderFragment, out Exception?renderFragmentException) { renderFragmentException = null; // A component might be in the render queue already before getting disposed by an // earlier entry in the render queue. In that case, rendering is a no-op. if (_componentWasDisposed) { return; } _nextRenderTree.Clear(); try { renderFragment(_nextRenderTree); } catch (Exception ex) { // If an exception occurs in the render fragment delegate, we won't process the diff in any way, so child components, // event handlers, etc., will all be left untouched as if this component didn't re-render at all. The Renderer will // then forcibly clear the descendant subtree by rendering an empty fragment for this component. renderFragmentException = ex; return; } // We don't want to make errors from this be recoverable, because there's no legitimate reason for them to happen _nextRenderTree.AssertTreeIsValid(Component); // Swap the old and new tree builders (CurrentRenderTree, _nextRenderTree) = (_nextRenderTree, CurrentRenderTree); var diff = RenderTreeDiffBuilder.ComputeDiff( _renderer, batchBuilder, ComponentId, _nextRenderTree.GetFrames(), CurrentRenderTree.GetFrames()); batchBuilder.UpdatedComponentDiffs.Append(diff); batchBuilder.InvalidateParameterViews(); }
public bool TryDisposeInBatch(RenderBatchBuilder batchBuilder, [NotNullWhen(false)] out Exception?exception) { _componentWasDisposed = true; exception = null; try { if (Component is IDisposable disposable) { disposable.Dispose(); } } catch (Exception ex) { exception = ex; } CleanupComponentStateResources(batchBuilder); return(exception == null); }
public void QueuesRemovedChildComponentsForDisposal() { // Arrange oldTree.OpenComponent <DisposableComponent>(10); // <DisposableComponent> oldTree.CloseComponent(); // </DisposableComponent> oldTree.OpenComponent <NonDisposableComponent>(20); // <NonDisposableComponent> oldTree.CloseComponent(); // </NonDisposableComponent> oldTree.OpenComponent <DisposableComponent>(30); // <DisposableComponent> oldTree.CloseComponent(); // </DisposableComponent> newTree.OpenComponent <DisposableComponent>(30); // <DisposableComponent> newTree.CloseComponent(); // </DisposableComponent> var batchBuilder = new RenderBatchBuilder(); RenderTreeDiffBuilder.ComputeDiff(renderer, batchBuilder, 0, new RenderTreeBuilder(renderer).GetFrames(), oldTree.GetFrames()); // Act/Assert // Note that we track NonDisposableComponent was disposed even though it's not IDisposable, // because it's up to the upstream renderer to decide what "disposing" a component means Assert.Empty(batchBuilder.ComponentDisposalQueue); RenderTreeDiffBuilder.ComputeDiff(renderer, batchBuilder, 0, oldTree.GetFrames(), newTree.GetFrames()); Assert.Equal(new[] { 0, 1 }, batchBuilder.ComponentDisposalQueue); }
public static void DisposeFrames(RenderBatchBuilder batchBuilder, ArrayRange <RenderTreeFrame> frames) => DisposeFramesInRange(batchBuilder, frames.Array, 0, frames.Count);
public ParameterViewLifetime(RenderBatchBuilder owner) { _owner = owner; _stamp = owner.ParameterViewValidityStamp; }
private void AppendDiffEntriesForRange( RenderBatchBuilder batchBuilder, RenderTreeFrame[] oldTree, int oldStartIndex, int oldEndIndexExcl, RenderTreeFrame[] newTree, int newStartIndex, int newEndIndexExcl, ref int siblingIndex) { var hasMoreOld = oldEndIndexExcl > oldStartIndex; var hasMoreNew = newEndIndexExcl > newStartIndex; var prevOldSeq = -1; var prevNewSeq = -1; while (hasMoreOld || hasMoreNew) { var oldSeq = hasMoreOld ? oldTree[oldStartIndex].Sequence : int.MaxValue; var newSeq = hasMoreNew ? newTree[newStartIndex].Sequence : int.MaxValue; if (oldSeq == newSeq) { AppendDiffEntriesForFramesWithSameSequence(batchBuilder, oldTree, oldStartIndex, newTree, newStartIndex, ref siblingIndex); oldStartIndex = NextSiblingIndex(oldTree[oldStartIndex], oldStartIndex); newStartIndex = NextSiblingIndex(newTree[newStartIndex], newStartIndex); hasMoreOld = oldEndIndexExcl > oldStartIndex; hasMoreNew = newEndIndexExcl > newStartIndex; prevOldSeq = oldSeq; prevNewSeq = newSeq; } else { bool treatAsInsert; var oldLoopedBack = oldSeq <= prevOldSeq; var newLoopedBack = newSeq <= prevNewSeq; if (oldLoopedBack == newLoopedBack) { // Both sequences are proceeding through the same loop block, so do a simple // preordered merge join (picking from whichever side brings us closer to being // back in sync) treatAsInsert = newSeq < oldSeq; if (oldLoopedBack) { // If both old and new have now looped back, we must reset their 'looped back' // tracker so we can treat them as proceeding through the same loop block prevOldSeq = prevNewSeq = -1; } } else if (oldLoopedBack) { // Old sequence looped back but new one didn't // The new sequence either has some extra trailing elements in the current loop block // which we should insert, or omits some old trailing loop blocks which we should delete // TODO: Find a way of not recomputing this next flag on every iteration var newLoopsBackLater = false; for (var testIndex = newStartIndex + 1; testIndex < newEndIndexExcl; testIndex++) { if (newTree[testIndex].Sequence < newSeq) { newLoopsBackLater = true; break; } } // If the new sequence loops back later to an earlier point than this, // then we know it's part of the existing loop block (so should be inserted). // If not, then it's unrelated to the previous loop block (so we should treat // the old items as trailing loop blocks to be removed). treatAsInsert = newLoopsBackLater; } else { // New sequence looped back but old one didn't // The old sequence either has some extra trailing elements in the current loop block // which we should delete, or the new sequence has extra trailing loop blocks which we // should insert // TODO: Find a way of not recomputing this next flag on every iteration var oldLoopsBackLater = false; for (var testIndex = oldStartIndex + 1; testIndex < oldEndIndexExcl; testIndex++) { if (oldTree[testIndex].Sequence < oldSeq) { oldLoopsBackLater = true; break; } } // If the old sequence loops back later to an earlier point than this, // then we know it's part of the existing loop block (so should be removed). // If not, then it's unrelated to the previous loop block (so we should treat // the new items as trailing loop blocks to be inserted). treatAsInsert = !oldLoopsBackLater; } if (treatAsInsert) { ref var newFrame = ref newTree[newStartIndex]; var newFrameType = newFrame.FrameType; if (newFrameType == RenderTreeFrameType.Attribute) { var referenceFrameIndex = _referenceFrames.Append(newFrame); Append(RenderTreeEdit.SetAttribute(siblingIndex, referenceFrameIndex)); newStartIndex++; } else { if (newFrameType == RenderTreeFrameType.Element || newFrameType == RenderTreeFrameType.Component) { InstantiateChildComponents(batchBuilder, newTree, newStartIndex); } var referenceFrameIndex = AppendSubtreeToReferenceFrames(newTree, newStartIndex); Append(RenderTreeEdit.PrependFrame(siblingIndex, referenceFrameIndex)); newStartIndex = NextSiblingIndex(newFrame, newStartIndex); siblingIndex++; } hasMoreNew = newEndIndexExcl > newStartIndex; prevNewSeq = newSeq; }