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);
            });
        }
Пример #3
0
        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());
        }
Пример #5
0
    private void CleanupComponentStateResources(RenderBatchBuilder batchBuilder)
    {
        // We don't expect these things to throw.
        RenderTreeDiffBuilder.DisposeFrames(batchBuilder, CurrentRenderTree.GetFrames());

        if (_hasAnyCascadingParameterSubscriptions)
        {
            RemoveCascadingParameterSubscriptions();
        }

        DisposeBuffers();
    }
Пример #6
0
 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());
        }
Пример #8
0
 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;
 }
Пример #9
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()));
        }
Пример #10
0
        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));
        }
Пример #11
0
    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();
    }
Пример #12
0
    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);
        }
Пример #14
0
 public static void DisposeFrames(RenderBatchBuilder batchBuilder, ArrayRange <RenderTreeFrame> frames)
 => DisposeFramesInRange(batchBuilder, frames.Array, 0, frames.Count);
Пример #15
0
 public ParameterViewLifetime(RenderBatchBuilder owner)
 {
     _owner = owner;
     _stamp = owner.ParameterViewValidityStamp;
 }
Пример #16
0
        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;
                    }