Major refactor of responsibilities in rendering code. Not quite done
with this yet either.
This commit is contained in:
parent
91314ee8c8
commit
15ddcd03b0
|
|
@ -15,31 +15,32 @@ export class BrowserRenderer {
|
|||
this.childComponentLocations[componentId] = element;
|
||||
}
|
||||
|
||||
public updateComponent(componentId: number, edits: System_Array<RenderTreeEditPointer>, editsLength: number, referenceTree: System_Array<RenderTreeFramePointer>) {
|
||||
public updateComponent(componentId: number, edits: System_Array<RenderTreeEditPointer>, editsOffset: number, editsLength: number, referenceFrames: System_Array<RenderTreeFramePointer>) {
|
||||
const element = this.childComponentLocations[componentId];
|
||||
if (!element) {
|
||||
throw new Error(`No element is currently associated with component ${componentId}`);
|
||||
}
|
||||
|
||||
this.applyEdits(componentId, element, 0, edits, editsLength, referenceTree);
|
||||
this.applyEdits(componentId, element, 0, edits, editsOffset, editsLength, referenceFrames);
|
||||
}
|
||||
|
||||
public disposeComponent(componentId: number) {
|
||||
delete this.childComponentLocations[componentId];
|
||||
}
|
||||
|
||||
applyEdits(componentId: number, parent: Element, childIndex: number, edits: System_Array<RenderTreeEditPointer>, editsLength: number, referenceTree: System_Array<RenderTreeFramePointer>) {
|
||||
applyEdits(componentId: number, parent: Element, childIndex: number, edits: System_Array<RenderTreeEditPointer>, editsOffset: number, editsLength: number, referenceFrames: System_Array<RenderTreeFramePointer>) {
|
||||
let currentDepth = 0;
|
||||
let childIndexAtCurrentDepth = childIndex;
|
||||
for (let editIndex = 0; editIndex < editsLength; editIndex++) {
|
||||
const maxEditIndexExcl = editsOffset + editsLength;
|
||||
for (let editIndex = editsOffset; editIndex < maxEditIndexExcl; editIndex++) {
|
||||
const edit = getRenderTreeEditPtr(edits, editIndex);
|
||||
const editType = renderTreeEdit.type(edit);
|
||||
switch (editType) {
|
||||
case EditType.prependFrame: {
|
||||
const frameIndex = renderTreeEdit.newTreeIndex(edit);
|
||||
const frame = getTreeFramePtr(referenceTree, frameIndex);
|
||||
const frame = getTreeFramePtr(referenceFrames, frameIndex);
|
||||
const siblingIndex = renderTreeEdit.siblingIndex(edit);
|
||||
this.insertFrame(componentId, parent, childIndexAtCurrentDepth + siblingIndex, referenceTree, frame, frameIndex);
|
||||
this.insertFrame(componentId, parent, childIndexAtCurrentDepth + siblingIndex, referenceFrames, frame, frameIndex);
|
||||
break;
|
||||
}
|
||||
case EditType.removeFrame: {
|
||||
|
|
@ -49,7 +50,7 @@ export class BrowserRenderer {
|
|||
}
|
||||
case EditType.setAttribute: {
|
||||
const frameIndex = renderTreeEdit.newTreeIndex(edit);
|
||||
const frame = getTreeFramePtr(referenceTree, frameIndex);
|
||||
const frame = getTreeFramePtr(referenceFrames, frameIndex);
|
||||
const siblingIndex = renderTreeEdit.siblingIndex(edit);
|
||||
const element = parent.childNodes[childIndexAtCurrentDepth + siblingIndex] as HTMLElement;
|
||||
this.applyAttribute(componentId, element, frame);
|
||||
|
|
@ -62,7 +63,7 @@ export class BrowserRenderer {
|
|||
}
|
||||
case EditType.updateText: {
|
||||
const frameIndex = renderTreeEdit.newTreeIndex(edit);
|
||||
const frame = getTreeFramePtr(referenceTree, frameIndex);
|
||||
const frame = getTreeFramePtr(referenceFrames, frameIndex);
|
||||
const siblingIndex = renderTreeEdit.siblingIndex(edit);
|
||||
const domTextNode = parent.childNodes[childIndexAtCurrentDepth + siblingIndex] as Text;
|
||||
domTextNode.textContent = renderTreeFrame.textContent(frame);
|
||||
|
|
|
|||
|
|
@ -7,7 +7,8 @@ import { RenderTreeEditPointer } from './RenderTreeEdit';
|
|||
|
||||
export const renderBatch = {
|
||||
updatedComponents: (obj: RenderBatchPointer) => platform.readStructField<ArrayRangePointer<RenderTreeDiffPointer>>(obj, 0),
|
||||
disposedComponentIds: (obj: RenderBatchPointer) => platform.readStructField<ArrayRangePointer<number>>(obj, arrayRangeStructLength),
|
||||
referenceFrames: (obj: RenderBatchPointer) => platform.readStructField<ArrayRangePointer<RenderTreeFramePointer>>(obj, arrayRangeStructLength),
|
||||
disposedComponentIds: (obj: RenderBatchPointer) => platform.readStructField<ArrayRangePointer<number>>(obj, arrayRangeStructLength + arrayRangeStructLength),
|
||||
};
|
||||
|
||||
const arrayRangeStructLength = 8;
|
||||
|
|
@ -16,15 +17,22 @@ export const arrayRange = {
|
|||
count: <T>(obj: ArrayRangePointer<T>) => platform.readInt32Field(obj, 4),
|
||||
};
|
||||
|
||||
export const renderTreeDiffStructLength = 4 + 2 * arrayRangeStructLength;
|
||||
const arraySegmentStructLength = 12;
|
||||
export const arraySegment = {
|
||||
array: <T>(obj: ArraySegmentPointer<T>) => platform.readObjectField<System_Array<T>>(obj, 0),
|
||||
offset: <T>(obj: ArraySegmentPointer<T>) => platform.readInt32Field(obj, 4),
|
||||
count: <T>(obj: ArraySegmentPointer<T>) => platform.readInt32Field(obj, 8),
|
||||
};
|
||||
|
||||
export const renderTreeDiffStructLength = 4 + arraySegmentStructLength;
|
||||
export const renderTreeDiff = {
|
||||
componentId: (obj: RenderTreeDiffPointer) => platform.readInt32Field(obj, 0),
|
||||
edits: (obj: RenderTreeDiffPointer) => platform.readStructField<ArrayRangePointer<RenderTreeEditPointer>>(obj, 4),
|
||||
currentState: (obj: RenderTreeDiffPointer) => platform.readStructField<ArrayRangePointer<RenderTreeFramePointer>>(obj, 4 + arrayRangeStructLength),
|
||||
edits: (obj: RenderTreeDiffPointer) => platform.readStructField<ArraySegmentPointer<RenderTreeEditPointer>>(obj, 4),
|
||||
};
|
||||
|
||||
// Nominal types to ensure only valid pointers are passed to the functions above.
|
||||
// At runtime the values are just numbers.
|
||||
export interface RenderBatchPointer extends Pointer { RenderBatchPointer__DO_NOT_IMPLEMENT: any }
|
||||
export interface ArrayRangePointer<T> extends Pointer { ArrayRangePointer__DO_NOT_IMPLEMENT: any }
|
||||
export interface ArraySegmentPointer<T> extends Pointer { ArraySegmentPointer__DO_NOT_IMPLEMENT: any }
|
||||
export interface RenderTreeDiffPointer extends Pointer { RenderTreeDiffPointer__DO_NOT_IMPLEMENT: any }
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { System_Object, System_String, System_Array, MethodHandle, Pointer } from '../Platform/Platform';
|
||||
import { platform } from '../Environment';
|
||||
import { renderBatch as renderBatchStruct, arrayRange, renderTreeDiffStructLength, renderTreeDiff, RenderBatchPointer, RenderTreeDiffPointer } from './RenderBatch';
|
||||
import { renderBatch as renderBatchStruct, arrayRange, arraySegment, renderTreeDiffStructLength, renderTreeDiff, RenderBatchPointer, RenderTreeDiffPointer } from './RenderBatch';
|
||||
import { BrowserRenderer } from './BrowserRenderer';
|
||||
|
||||
type BrowserRendererRegistry = { [browserRendererId: number]: BrowserRenderer };
|
||||
|
|
@ -30,17 +30,19 @@ export function renderBatch(browserRendererId: number, batch: RenderBatchPointer
|
|||
const updatedComponents = renderBatchStruct.updatedComponents(batch);
|
||||
const updatedComponentsLength = arrayRange.count(updatedComponents);
|
||||
const updatedComponentsArray = arrayRange.array(updatedComponents);
|
||||
const referenceFramesStruct = renderBatchStruct.referenceFrames(batch);
|
||||
const referenceFrames = arrayRange.array(referenceFramesStruct);
|
||||
|
||||
for (let i = 0; i < updatedComponentsLength; i++) {
|
||||
const diff = platform.getArrayEntryPtr(updatedComponentsArray, i, renderTreeDiffStructLength);
|
||||
const componentId = renderTreeDiff.componentId(diff);
|
||||
|
||||
const editsArrayRange = renderTreeDiff.edits(diff);
|
||||
const currentStateArrayRange = renderTreeDiff.currentState(diff);
|
||||
const editsArraySegment = renderTreeDiff.edits(diff);
|
||||
const edits = arraySegment.array(editsArraySegment);
|
||||
const editsOffset = arraySegment.offset(editsArraySegment);
|
||||
const editsLength = arraySegment.count(editsArraySegment);
|
||||
|
||||
const edits = arrayRange.array(editsArrayRange);
|
||||
const editsLength = arrayRange.count(editsArrayRange);
|
||||
const tree = arrayRange.array(currentStateArrayRange);
|
||||
browserRenderer.updateComponent(componentId, edits, editsLength, tree);
|
||||
browserRenderer.updateComponent(componentId, edits, editsOffset, editsLength, referenceFrames);
|
||||
}
|
||||
|
||||
const disposedComponentIds = renderBatchStruct.disposedComponentIds(batch);
|
||||
|
|
|
|||
|
|
@ -132,6 +132,15 @@ namespace Microsoft.AspNetCore.Blazor.RenderTree
|
|||
public ArrayRange<T> ToRange()
|
||||
=> new ArrayRange<T>(_items, _itemsInUse);
|
||||
|
||||
/// <summary>
|
||||
/// Produces an <see cref="ArraySegment{T}"/> structure describing the selected contents.
|
||||
/// </summary>
|
||||
/// <param name="fromIndexInclusive">The index of the first item in the segment.</param>
|
||||
/// <param name="toIndexExclusive">One plus the index of the last item in the segment.</param>
|
||||
/// <returns>The <see cref="ArraySegment{T}"/>.</returns>
|
||||
public ArraySegment<T> ToSegment(int fromIndexInclusive, int toIndexExclusive)
|
||||
=> new ArraySegment<T>(_items, fromIndexInclusive, toIndexExclusive - fromIndexInclusive);
|
||||
|
||||
private void SetCapacity(int desiredCapacity, bool preserveContents)
|
||||
{
|
||||
if (desiredCapacity < _itemsInUse)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.AspNetCore.Blazor.RenderTree
|
||||
{
|
||||
/// <summary>
|
||||
|
|
@ -16,23 +18,14 @@ namespace Microsoft.AspNetCore.Blazor.RenderTree
|
|||
/// <summary>
|
||||
/// Gets the changes to the render tree since a previous state.
|
||||
/// </summary>
|
||||
public readonly ArrayRange<RenderTreeEdit> Edits;
|
||||
|
||||
/// <summary>
|
||||
/// Gets render frames that may be referenced by entries in <see cref="Edits"/>.
|
||||
/// For example, edit entries of type <see cref="RenderTreeEditType.PrependFrame"/>
|
||||
/// will point to an entry in this array to specify the subtree to be prepended.
|
||||
/// </summary>
|
||||
public readonly ArrayRange<RenderTreeFrame> ReferenceFrames;
|
||||
public readonly ArraySegment<RenderTreeEdit> Edits;
|
||||
|
||||
internal RenderTreeDiff(
|
||||
int componentId,
|
||||
ArrayRange<RenderTreeEdit> entries,
|
||||
ArrayRange<RenderTreeFrame> referenceFrames)
|
||||
ArraySegment<RenderTreeEdit> entries)
|
||||
{
|
||||
ComponentId = componentId;
|
||||
Edits = entries;
|
||||
ReferenceFrames = referenceFrames;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,50 +8,69 @@ using Microsoft.AspNetCore.Blazor.Rendering;
|
|||
|
||||
namespace Microsoft.AspNetCore.Blazor.RenderTree
|
||||
{
|
||||
internal class RenderTreeDiffBuilder
|
||||
internal static class RenderTreeDiffBuilder
|
||||
{
|
||||
private readonly Renderer _renderer;
|
||||
private readonly ArrayBuilder<RenderTreeEdit> _entries = new ArrayBuilder<RenderTreeEdit>(10);
|
||||
private readonly ArrayBuilder<RenderTreeFrame> _referenceFrames = new ArrayBuilder<RenderTreeFrame>(10);
|
||||
|
||||
public RenderTreeDiffBuilder(Renderer renderer)
|
||||
private struct DiffContext
|
||||
{
|
||||
_renderer = renderer ?? throw new ArgumentNullException(nameof(renderer));
|
||||
// Exists only so that the various methods in this class can call each other without
|
||||
// constantly building up long lists of parameters. Is private to this class, so the
|
||||
// fact that it's a mutable struct is manageable.
|
||||
// Always pass by ref to avoid copying, and because the 'SiblingIndex' is mutable.
|
||||
|
||||
public readonly Renderer Renderer;
|
||||
public readonly RenderBatchBuilder BatchBuilder;
|
||||
public readonly RenderTreeFrame[] OldTree;
|
||||
public readonly RenderTreeFrame[] NewTree;
|
||||
public readonly ArrayBuilder<RenderTreeEdit> Edits;
|
||||
public readonly ArrayBuilder<RenderTreeFrame> ReferenceFrames;
|
||||
public int SiblingIndex;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <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(
|
||||
public static RenderTreeDiff ComputeDiff(
|
||||
Renderer renderer,
|
||||
RenderBatchBuilder batchBuilder,
|
||||
int componentId,
|
||||
ArrayRange<RenderTreeFrame> oldTree,
|
||||
ArrayRange<RenderTreeFrame> newTree)
|
||||
{
|
||||
_entries.Clear();
|
||||
_referenceFrames.Clear();
|
||||
var siblingIndex = 0;
|
||||
var editsBuffer = batchBuilder.EditsBuffer;
|
||||
var editsBufferStartLength = editsBuffer.Count;
|
||||
|
||||
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()));
|
||||
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);
|
||||
}
|
||||
|
||||
private void AppendDiffEntriesForRange(
|
||||
RenderBatchBuilder batchBuilder,
|
||||
RenderTreeFrame[] oldTree, int oldStartIndex, int oldEndIndexExcl,
|
||||
RenderTreeFrame[] newTree, int newStartIndex, int newEndIndexExcl,
|
||||
ref int siblingIndex)
|
||||
public static void DisposeFrames(RenderBatchBuilder batchBuilder, ArrayRange<RenderTreeFrame> frames)
|
||||
=> DisposeFramesInRange(batchBuilder, frames.Array, 0, frames.Count);
|
||||
|
||||
private static void AppendDiffEntriesForRange(
|
||||
ref DiffContext diffContext,
|
||||
int oldStartIndex, int oldEndIndexExcl,
|
||||
int newStartIndex, int newEndIndexExcl)
|
||||
{
|
||||
var hasMoreOld = oldEndIndexExcl > oldStartIndex;
|
||||
var hasMoreNew = newEndIndexExcl > newStartIndex;
|
||||
var prevOldSeq = -1;
|
||||
var prevNewSeq = -1;
|
||||
var oldTree = diffContext.OldTree;
|
||||
var newTree = diffContext.NewTree;
|
||||
while (hasMoreOld || hasMoreNew)
|
||||
{
|
||||
var oldSeq = hasMoreOld ? oldTree[oldStartIndex].Sequence : int.MaxValue;
|
||||
|
|
@ -59,7 +78,7 @@ namespace Microsoft.AspNetCore.Blazor.RenderTree
|
|||
|
||||
if (oldSeq == newSeq)
|
||||
{
|
||||
AppendDiffEntriesForFramesWithSameSequence(batchBuilder, oldTree, oldStartIndex, newTree, newStartIndex, ref siblingIndex);
|
||||
AppendDiffEntriesForFramesWithSameSequence(ref diffContext, oldStartIndex, newStartIndex);
|
||||
oldStartIndex = NextSiblingIndex(oldTree[oldStartIndex], oldStartIndex);
|
||||
newStartIndex = NextSiblingIndex(newTree[newStartIndex], newStartIndex);
|
||||
hasMoreOld = oldEndIndexExcl > oldStartIndex;
|
||||
|
|
@ -134,14 +153,14 @@ namespace Microsoft.AspNetCore.Blazor.RenderTree
|
|||
|
||||
if (treatAsInsert)
|
||||
{
|
||||
InsertNewFrame(batchBuilder, newTree, newStartIndex, ref siblingIndex);
|
||||
InsertNewFrame(ref diffContext, newStartIndex);
|
||||
newStartIndex = NextSiblingIndex(newTree[newStartIndex], newStartIndex);
|
||||
hasMoreNew = newEndIndexExcl > newStartIndex;
|
||||
prevNewSeq = newSeq;
|
||||
}
|
||||
else
|
||||
{
|
||||
RemoveOldFrame(batchBuilder, oldTree, oldStartIndex, siblingIndex);
|
||||
RemoveOldFrame(ref diffContext, oldStartIndex);
|
||||
oldStartIndex = NextSiblingIndex(oldTree[oldStartIndex], oldStartIndex);
|
||||
hasMoreOld = oldEndIndexExcl > oldStartIndex;
|
||||
prevOldSeq = oldSeq;
|
||||
|
|
@ -150,16 +169,18 @@ namespace Microsoft.AspNetCore.Blazor.RenderTree
|
|||
}
|
||||
}
|
||||
|
||||
private void UpdateRetainedChildComponent(
|
||||
RenderBatchBuilder batchBuilder,
|
||||
RenderTreeFrame[] oldTree, int oldComponentIndex,
|
||||
RenderTreeFrame[] newTree, int newComponentIndex)
|
||||
private static void UpdateRetainedChildComponent(
|
||||
ref DiffContext diffContext,
|
||||
int oldComponentIndex,
|
||||
int newComponentIndex)
|
||||
{
|
||||
// The algorithm here is the same as in AppendDiffEntriesForRange, except that
|
||||
// here we don't optimise for loops - we assume that both sequences are forward-only.
|
||||
// That's because this is true for all currently supported scenarios, and it means
|
||||
// fewer steps here.
|
||||
|
||||
var oldTree = diffContext.OldTree;
|
||||
var newTree = diffContext.NewTree;
|
||||
ref var oldComponentFrame = ref oldTree[oldComponentIndex];
|
||||
ref var newComponentFrame = ref newTree[newComponentIndex];
|
||||
var componentId = oldComponentFrame.ComponentId;
|
||||
|
|
@ -241,7 +262,7 @@ namespace Microsoft.AspNetCore.Blazor.RenderTree
|
|||
|
||||
if (hasSetAnyProperty)
|
||||
{
|
||||
TriggerChildComponentRender(batchBuilder, newComponentFrame);
|
||||
diffContext.BatchBuilder.ComponentRenderQueue.Enqueue(newComponentFrame.ComponentId);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -291,12 +312,13 @@ namespace Microsoft.AspNetCore.Blazor.RenderTree
|
|||
return frameIndex + distanceToNextSibling;
|
||||
}
|
||||
|
||||
private void AppendDiffEntriesForFramesWithSameSequence(
|
||||
RenderBatchBuilder batchBuilder,
|
||||
RenderTreeFrame[] oldTree, int oldFrameIndex,
|
||||
RenderTreeFrame[] newTree, int newFrameIndex,
|
||||
ref int siblingIndex)
|
||||
private static void AppendDiffEntriesForFramesWithSameSequence(
|
||||
ref DiffContext diffContext,
|
||||
int oldFrameIndex,
|
||||
int newFrameIndex)
|
||||
{
|
||||
var oldTree = diffContext.OldTree;
|
||||
var newTree = diffContext.NewTree;
|
||||
ref var oldFrame = ref oldTree[oldFrameIndex];
|
||||
ref var newFrame = ref newTree[newFrameIndex];
|
||||
|
||||
|
|
@ -304,7 +326,7 @@ namespace Microsoft.AspNetCore.Blazor.RenderTree
|
|||
// to the same sequence number (and if not, the behaviour is undefined).
|
||||
// TODO: Consider supporting dissimilar types at same sequence for custom IComponent implementations.
|
||||
// It should only be a matter of calling RemoveOldFrame+InsertNewFrame
|
||||
switch (newTree[newFrameIndex].FrameType)
|
||||
switch (newFrame.FrameType)
|
||||
{
|
||||
case RenderTreeFrameType.Text:
|
||||
{
|
||||
|
|
@ -312,10 +334,10 @@ namespace Microsoft.AspNetCore.Blazor.RenderTree
|
|||
var newText = newFrame.TextContent;
|
||||
if (!string.Equals(oldText, newText, StringComparison.Ordinal))
|
||||
{
|
||||
var referenceFrameIndex = _referenceFrames.Append(newFrame);
|
||||
_entries.Append(RenderTreeEdit.UpdateText(siblingIndex, referenceFrameIndex));
|
||||
var referenceFrameIndex = diffContext.ReferenceFrames.Append(newFrame);
|
||||
diffContext.Edits.Append(RenderTreeEdit.UpdateText(diffContext.SiblingIndex, referenceFrameIndex));
|
||||
}
|
||||
siblingIndex++;
|
||||
diffContext.SiblingIndex++;
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
@ -330,10 +352,9 @@ namespace Microsoft.AspNetCore.Blazor.RenderTree
|
|||
|
||||
// Diff the attributes
|
||||
AppendDiffEntriesForRange(
|
||||
batchBuilder,
|
||||
oldTree, oldFrameIndex + 1, oldFrameAttributesEndIndexExcl,
|
||||
newTree, newFrameIndex + 1, newFrameAttributesEndIndexExcl,
|
||||
ref siblingIndex);
|
||||
ref diffContext,
|
||||
oldFrameIndex + 1, oldFrameAttributesEndIndexExcl,
|
||||
newFrameIndex + 1, newFrameAttributesEndIndexExcl);
|
||||
|
||||
// Diff the children
|
||||
var oldFrameChildrenEndIndexExcl = oldFrameIndex + oldFrame.ElementSubtreeLength;
|
||||
|
|
@ -343,26 +364,26 @@ namespace Microsoft.AspNetCore.Blazor.RenderTree
|
|||
newFrameChildrenEndIndexExcl > newFrameAttributesEndIndexExcl;
|
||||
if (hasChildrenToProcess)
|
||||
{
|
||||
_entries.Append(RenderTreeEdit.StepIn(siblingIndex));
|
||||
var childSiblingIndex = 0;
|
||||
diffContext.Edits.Append(RenderTreeEdit.StepIn(diffContext.SiblingIndex));
|
||||
var prevSiblingIndex = diffContext.SiblingIndex;
|
||||
diffContext.SiblingIndex = 0;
|
||||
AppendDiffEntriesForRange(
|
||||
batchBuilder,
|
||||
oldTree, oldFrameAttributesEndIndexExcl, oldFrameChildrenEndIndexExcl,
|
||||
newTree, newFrameAttributesEndIndexExcl, newFrameChildrenEndIndexExcl,
|
||||
ref childSiblingIndex);
|
||||
AppendStepOut();
|
||||
siblingIndex++;
|
||||
ref diffContext,
|
||||
oldFrameAttributesEndIndexExcl, oldFrameChildrenEndIndexExcl,
|
||||
newFrameAttributesEndIndexExcl, newFrameChildrenEndIndexExcl);
|
||||
AppendStepOut(ref diffContext);
|
||||
diffContext.SiblingIndex = prevSiblingIndex + 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
siblingIndex++;
|
||||
diffContext.SiblingIndex++;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Elements with different names are treated as completely unrelated
|
||||
RemoveOldFrame(batchBuilder, oldTree, oldFrameIndex, siblingIndex);
|
||||
InsertNewFrame(batchBuilder, newTree, newFrameIndex, ref siblingIndex);
|
||||
RemoveOldFrame(ref diffContext, oldFrameIndex);
|
||||
InsertNewFrame(ref diffContext, newFrameIndex);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
|
@ -372,16 +393,16 @@ namespace Microsoft.AspNetCore.Blazor.RenderTree
|
|||
if (oldFrame.ComponentType == newFrame.ComponentType)
|
||||
{
|
||||
UpdateRetainedChildComponent(
|
||||
batchBuilder,
|
||||
oldTree, oldFrameIndex,
|
||||
newTree, newFrameIndex);
|
||||
siblingIndex++;
|
||||
ref diffContext,
|
||||
oldFrameIndex,
|
||||
newFrameIndex);
|
||||
diffContext.SiblingIndex++;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Child components of different types are treated as completely unrelated
|
||||
RemoveOldFrame(batchBuilder, oldTree, oldFrameIndex, siblingIndex);
|
||||
InsertNewFrame(batchBuilder, newTree, newFrameIndex, ref siblingIndex);
|
||||
RemoveOldFrame(ref diffContext, oldFrameIndex);
|
||||
InsertNewFrame(ref diffContext, newFrameIndex);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
|
@ -398,11 +419,11 @@ namespace Microsoft.AspNetCore.Blazor.RenderTree
|
|||
{
|
||||
if (oldFrame.AttributeEventHandlerId > 0)
|
||||
{
|
||||
batchBuilder.AddDisposedEventHandlerId(oldFrame.AttributeEventHandlerId);
|
||||
diffContext.BatchBuilder.AddDisposedEventHandlerId(oldFrame.AttributeEventHandlerId);
|
||||
}
|
||||
InitializeNewAttributeFrame(ref newFrame);
|
||||
var referenceFrameIndex = _referenceFrames.Append(newFrame);
|
||||
_entries.Append(RenderTreeEdit.SetAttribute(siblingIndex, referenceFrameIndex));
|
||||
InitializeNewAttributeFrame(ref diffContext, ref newFrame);
|
||||
var referenceFrameIndex = diffContext.ReferenceFrames.Append(newFrame);
|
||||
diffContext.Edits.Append(RenderTreeEdit.SetAttribute(diffContext.SiblingIndex, referenceFrameIndex));
|
||||
}
|
||||
else if (oldFrame.AttributeEventHandlerId > 0)
|
||||
{
|
||||
|
|
@ -415,8 +436,8 @@ namespace Microsoft.AspNetCore.Blazor.RenderTree
|
|||
// Since this code path is never reachable for Razor components (because you
|
||||
// can't have two different attribute names from the same source sequence), we
|
||||
// could consider removing the 'name equality' check entirely for perf
|
||||
RemoveOldFrame(batchBuilder, oldTree, oldFrameIndex, siblingIndex);
|
||||
InsertNewFrame(batchBuilder, newTree, newFrameIndex, ref siblingIndex);
|
||||
RemoveOldFrame(ref diffContext, oldFrameIndex);
|
||||
InsertNewFrame(ref diffContext, newFrameIndex);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
|
@ -426,48 +447,50 @@ namespace Microsoft.AspNetCore.Blazor.RenderTree
|
|||
}
|
||||
}
|
||||
|
||||
private void InsertNewFrame(RenderBatchBuilder batchBuilder, RenderTreeFrame[] newTree, int newFrameIndex, ref int siblingIndex)
|
||||
private static void InsertNewFrame(ref DiffContext diffContext, int newFrameIndex)
|
||||
{
|
||||
var newTree = diffContext.NewTree;
|
||||
ref var newFrame = ref newTree[newFrameIndex];
|
||||
switch (newFrame.FrameType)
|
||||
{
|
||||
case RenderTreeFrameType.Attribute:
|
||||
{
|
||||
InitializeNewAttributeFrame(ref newFrame);
|
||||
var referenceFrameIndex = _referenceFrames.Append(newFrame);
|
||||
_entries.Append(RenderTreeEdit.SetAttribute(siblingIndex, referenceFrameIndex));
|
||||
InitializeNewAttributeFrame(ref diffContext, ref newFrame);
|
||||
var referenceFrameIndex = diffContext.ReferenceFrames.Append(newFrame);
|
||||
diffContext.Edits.Append(RenderTreeEdit.SetAttribute(diffContext.SiblingIndex, referenceFrameIndex));
|
||||
break;
|
||||
}
|
||||
case RenderTreeFrameType.Component:
|
||||
case RenderTreeFrameType.Element:
|
||||
{
|
||||
InitializeNewSubtree(batchBuilder, newTree, newFrameIndex);
|
||||
var referenceFrameIndex = _referenceFrames.Append(newTree, newFrameIndex, newFrame.ElementSubtreeLength);
|
||||
_entries.Append(RenderTreeEdit.PrependFrame(siblingIndex, referenceFrameIndex));
|
||||
siblingIndex++;
|
||||
InitializeNewSubtree(ref diffContext, newFrameIndex);
|
||||
var referenceFrameIndex = diffContext.ReferenceFrames.Append(newTree, newFrameIndex, newFrame.ElementSubtreeLength);
|
||||
diffContext.Edits.Append(RenderTreeEdit.PrependFrame(diffContext.SiblingIndex, referenceFrameIndex));
|
||||
diffContext.SiblingIndex++;
|
||||
break;
|
||||
}
|
||||
case RenderTreeFrameType.Text:
|
||||
{
|
||||
var referenceFrameIndex = _referenceFrames.Append(newFrame);
|
||||
_entries.Append(RenderTreeEdit.PrependFrame(siblingIndex, referenceFrameIndex));
|
||||
siblingIndex++;
|
||||
var referenceFrameIndex = diffContext.ReferenceFrames.Append(newFrame);
|
||||
diffContext.Edits.Append(RenderTreeEdit.PrependFrame(diffContext.SiblingIndex, referenceFrameIndex));
|
||||
diffContext.SiblingIndex++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void RemoveOldFrame(RenderBatchBuilder batchBuilder, RenderTreeFrame[] oldTree, int oldFrameIndex, int siblingIndex)
|
||||
private static void RemoveOldFrame(ref DiffContext diffContext, int oldFrameIndex)
|
||||
{
|
||||
var oldTree = diffContext.OldTree;
|
||||
ref var oldFrame = ref oldTree[oldFrameIndex];
|
||||
switch (oldFrame.FrameType)
|
||||
{
|
||||
case RenderTreeFrameType.Attribute:
|
||||
{
|
||||
_entries.Append(RenderTreeEdit.RemoveAttribute(siblingIndex, oldFrame.AttributeName));
|
||||
diffContext.Edits.Append(RenderTreeEdit.RemoveAttribute(diffContext.SiblingIndex, oldFrame.AttributeName));
|
||||
if (oldFrame.AttributeEventHandlerId > 0)
|
||||
{
|
||||
batchBuilder.AddDisposedEventHandlerId(oldFrame.AttributeEventHandlerId);
|
||||
diffContext.BatchBuilder.AddDisposedEventHandlerId(oldFrame.AttributeEventHandlerId);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
|
@ -475,13 +498,13 @@ namespace Microsoft.AspNetCore.Blazor.RenderTree
|
|||
case RenderTreeFrameType.Element:
|
||||
{
|
||||
var endIndexExcl = oldFrameIndex + oldFrame.ElementSubtreeLength;
|
||||
DisposeFramesInRange(batchBuilder, oldTree, oldFrameIndex, endIndexExcl);
|
||||
_entries.Append(RenderTreeEdit.RemoveFrame(siblingIndex));
|
||||
DisposeFramesInRange(diffContext.BatchBuilder, oldTree, oldFrameIndex, endIndexExcl);
|
||||
diffContext.Edits.Append(RenderTreeEdit.RemoveFrame(diffContext.SiblingIndex));
|
||||
break;
|
||||
}
|
||||
case RenderTreeFrameType.Text:
|
||||
{
|
||||
_entries.Append(RenderTreeEdit.RemoveFrame(siblingIndex));
|
||||
diffContext.Edits.Append(RenderTreeEdit.RemoveFrame(diffContext.SiblingIndex));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -502,22 +525,23 @@ namespace Microsoft.AspNetCore.Blazor.RenderTree
|
|||
return index;
|
||||
}
|
||||
|
||||
private void AppendStepOut()
|
||||
private static void AppendStepOut(ref DiffContext diffContext)
|
||||
{
|
||||
// If the preceding frame is a StepIn, then the StepOut cancels it out
|
||||
var previousIndex = _entries.Count - 1;
|
||||
if (previousIndex >= 0 && _entries.Buffer[previousIndex].Type == RenderTreeEditType.StepIn)
|
||||
var previousIndex = diffContext.Edits.Count - 1;
|
||||
if (previousIndex >= 0 && diffContext.Edits.Buffer[previousIndex].Type == RenderTreeEditType.StepIn)
|
||||
{
|
||||
_entries.RemoveLast();
|
||||
diffContext.Edits.RemoveLast();
|
||||
}
|
||||
else
|
||||
{
|
||||
_entries.Append(RenderTreeEdit.StepOut());
|
||||
diffContext.Edits.Append(RenderTreeEdit.StepOut());
|
||||
}
|
||||
}
|
||||
|
||||
private void InitializeNewSubtree(RenderBatchBuilder batchBuilder, RenderTreeFrame[] frames, int frameIndex)
|
||||
private static void InitializeNewSubtree(ref DiffContext diffContext, int frameIndex)
|
||||
{
|
||||
var frames = diffContext.NewTree;
|
||||
var endIndexExcl = frameIndex + frames[frameIndex].ElementSubtreeLength;
|
||||
for (var i = frameIndex; i < endIndexExcl; i++)
|
||||
{
|
||||
|
|
@ -525,17 +549,18 @@ namespace Microsoft.AspNetCore.Blazor.RenderTree
|
|||
switch (frame.FrameType)
|
||||
{
|
||||
case RenderTreeFrameType.Component:
|
||||
InitializeNewComponentFrame(batchBuilder, frames, i);
|
||||
InitializeNewComponentFrame(ref diffContext, i);
|
||||
break;
|
||||
case RenderTreeFrameType.Attribute:
|
||||
InitializeNewAttributeFrame(ref frame);
|
||||
InitializeNewAttributeFrame(ref diffContext, ref frame);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void InitializeNewComponentFrame(RenderBatchBuilder batchBuilder, RenderTreeFrame[] frames, int frameIndex)
|
||||
private static void InitializeNewComponentFrame(ref DiffContext diffContext, int frameIndex)
|
||||
{
|
||||
var frames = diffContext.NewTree;
|
||||
ref var frame = ref frames[frameIndex];
|
||||
|
||||
if (frame.Component != null)
|
||||
|
|
@ -543,7 +568,7 @@ namespace Microsoft.AspNetCore.Blazor.RenderTree
|
|||
throw new InvalidOperationException($"Child component already exists during {nameof(InitializeNewComponentFrame)}");
|
||||
}
|
||||
|
||||
_renderer.InstantiateChildComponent(ref frame);
|
||||
diffContext.Renderer.InstantiateChildComponent(ref frame);
|
||||
var childComponentInstance = frame.Component;
|
||||
|
||||
// All descendants of a component are its properties
|
||||
|
|
@ -557,45 +582,25 @@ namespace Microsoft.AspNetCore.Blazor.RenderTree
|
|||
attributeFrame.AttributeValue);
|
||||
}
|
||||
|
||||
TriggerChildComponentRender(batchBuilder, frame);
|
||||
diffContext.BatchBuilder.ComponentRenderQueue.Enqueue(frame.ComponentId);
|
||||
}
|
||||
|
||||
private void InitializeNewAttributeFrame(ref RenderTreeFrame newFrame)
|
||||
private static void InitializeNewAttributeFrame(ref DiffContext diffContext, ref RenderTreeFrame newFrame)
|
||||
{
|
||||
if (newFrame.AttributeValue is UIEventHandler)
|
||||
{
|
||||
_renderer.AssignEventHandlerId(ref newFrame);
|
||||
diffContext.Renderer.AssignEventHandlerId(ref newFrame);
|
||||
}
|
||||
}
|
||||
|
||||
private void TriggerChildComponentRender(RenderBatchBuilder batchBuilder, in RenderTreeFrame frame)
|
||||
{
|
||||
if (frame.Component is IHandlePropertiesChanged notifyableComponent)
|
||||
{
|
||||
// TODO: Ensure any exceptions thrown here are handled equivalently to
|
||||
// unhandled exceptions during rendering.
|
||||
notifyableComponent.OnPropertiesChanged();
|
||||
}
|
||||
|
||||
// TODO: Consider moving the responsibility for triggering re-rendering
|
||||
// into the OnPropertiesChanged handler (if implemented) so that components
|
||||
// can control whether any given set of property changes cause re-rendering.
|
||||
// Not doing so yet because it's unclear that the usage patterns would be
|
||||
// good to use.
|
||||
_renderer.RenderInExistingBatch(batchBuilder, frame.ComponentId);
|
||||
}
|
||||
|
||||
internal void DisposeFrames(RenderBatchBuilder batchBuilder, ArrayRange<RenderTreeFrame> frames)
|
||||
=> DisposeFramesInRange(batchBuilder, frames.Array, 0, frames.Count);
|
||||
|
||||
private void DisposeFramesInRange(RenderBatchBuilder batchBuilder, RenderTreeFrame[] frames, int startIndex, int endIndexExcl)
|
||||
private static void DisposeFramesInRange(RenderBatchBuilder batchBuilder, RenderTreeFrame[] frames, int startIndex, int endIndexExcl)
|
||||
{
|
||||
for (var i = startIndex; i < endIndexExcl; i++)
|
||||
{
|
||||
ref var frame = ref frames[i];
|
||||
if (frame.FrameType == RenderTreeFrameType.Component && frame.Component != null)
|
||||
{
|
||||
_renderer.DisposeInExistingBatch(batchBuilder, frame.ComponentId);
|
||||
batchBuilder.ComponentDisposalQueue.Enqueue(frame.ComponentId);
|
||||
}
|
||||
else if (frame.FrameType == RenderTreeFrameType.Attribute && frame.AttributeEventHandlerId > 0)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@ namespace Microsoft.AspNetCore.Blazor.Rendering
|
|||
private readonly int _componentId; // TODO: Change the type to 'long' when the Mono runtime has more complete support for passing longs in .NET->JS calls
|
||||
private readonly IComponent _component;
|
||||
private readonly Renderer _renderer;
|
||||
private readonly RenderTreeDiffBuilder _diffBuilder;
|
||||
private RenderTreeBuilder _renderTreeBuilderCurrent;
|
||||
private RenderTreeBuilder _renderTreeBuilderPrevious;
|
||||
|
||||
|
|
@ -32,7 +31,6 @@ namespace Microsoft.AspNetCore.Blazor.Rendering
|
|||
_componentId = componentId;
|
||||
_component = component ?? throw new ArgumentNullException(nameof(component));
|
||||
_renderer = renderer ?? throw new ArgumentNullException(nameof(renderer));
|
||||
_diffBuilder = new RenderTreeDiffBuilder(renderer);
|
||||
_renderTreeBuilderCurrent = new RenderTreeBuilder(renderer);
|
||||
_renderTreeBuilderPrevious = new RenderTreeBuilder(renderer);
|
||||
}
|
||||
|
|
@ -41,18 +39,33 @@ namespace Microsoft.AspNetCore.Blazor.Rendering
|
|||
/// Regenerates the <see cref="RenderTree"/> and adds the changes to the
|
||||
/// <paramref name="batchBuilder"/>.
|
||||
/// </summary>
|
||||
public void Render(RenderBatchBuilder batchBuilder)
|
||||
public void Render(Renderer renderer, RenderBatchBuilder batchBuilder)
|
||||
{
|
||||
if (_component is IHandlePropertiesChanged notifyableComponent)
|
||||
{
|
||||
notifyableComponent.OnPropertiesChanged();
|
||||
}
|
||||
|
||||
// Swap the old and new tree builders
|
||||
(_renderTreeBuilderCurrent, _renderTreeBuilderPrevious) = (_renderTreeBuilderPrevious, _renderTreeBuilderCurrent);
|
||||
|
||||
_renderTreeBuilderCurrent.Clear();
|
||||
_component.BuildRenderTree(_renderTreeBuilderCurrent);
|
||||
_diffBuilder.ApplyNewRenderTreeVersion(
|
||||
|
||||
var diff = RenderTreeDiffBuilder.ComputeDiff(
|
||||
_renderer,
|
||||
batchBuilder,
|
||||
_componentId,
|
||||
_renderTreeBuilderPrevious.GetFrames(),
|
||||
_renderTreeBuilderCurrent.GetFrames());
|
||||
batchBuilder.UpdatedComponentDiffs.Append(diff);
|
||||
|
||||
// Process disposal queue now in case it causes further component renders to be enqueued
|
||||
while (batchBuilder.ComponentDisposalQueue.Count > 0)
|
||||
{
|
||||
var disposeComponentId = batchBuilder.ComponentDisposalQueue.Dequeue();
|
||||
renderer.DisposeInExistingBatch(batchBuilder, disposeComponentId);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -66,7 +79,7 @@ namespace Microsoft.AspNetCore.Blazor.Rendering
|
|||
disposable.Dispose();
|
||||
}
|
||||
|
||||
_diffBuilder.DisposeFrames(batchBuilder, _renderTreeBuilderCurrent.GetFrames());
|
||||
RenderTreeDiffBuilder.DisposeFrames(batchBuilder, _renderTreeBuilderCurrent.GetFrames());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,13 @@ namespace Microsoft.AspNetCore.Blazor.Rendering
|
|||
/// </summary>
|
||||
public ArrayRange<RenderTreeDiff> UpdatedComponents { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets render frames that may be referenced by entries in <see cref="UpdatedComponents"/>.
|
||||
/// For example, edit entries of type <see cref="RenderTreeEditType.PrependFrame"/>
|
||||
/// will point to an entry in this array to specify the subtree to be prepended.
|
||||
/// </summary>
|
||||
public ArrayRange<RenderTreeFrame> ReferenceFrames { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the IDs of the components that were disposed.
|
||||
/// </summary>
|
||||
|
|
@ -22,9 +29,11 @@ namespace Microsoft.AspNetCore.Blazor.Rendering
|
|||
|
||||
internal RenderBatch(
|
||||
ArrayRange<RenderTreeDiff> updatedComponents,
|
||||
ArrayRange<RenderTreeFrame> referenceFrames,
|
||||
ArrayRange<int> disposedComponentIDs)
|
||||
{
|
||||
UpdatedComponents = updatedComponents;
|
||||
ReferenceFrames = referenceFrames;
|
||||
DisposedComponentIDs = disposedComponentIDs;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,43 +1,47 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.AspNetCore.Blazor.RenderTree;
|
||||
|
||||
namespace Microsoft.AspNetCore.Blazor.Rendering
|
||||
{
|
||||
internal class RenderBatchBuilder
|
||||
{
|
||||
private ArrayBuilder<RenderTreeDiff> _updatedComponentDiffs = new ArrayBuilder<RenderTreeDiff>();
|
||||
private ArrayBuilder<int> _disposedComponentIds = new ArrayBuilder<int>();
|
||||
private ArrayBuilder<int> _disposedEventHandlerIds = new ArrayBuilder<int>();
|
||||
public ArrayBuilder<RenderTreeEdit> EditsBuffer { get; } = new ArrayBuilder<RenderTreeEdit>();
|
||||
public ArrayBuilder<RenderTreeFrame> ReferenceFramesBuffer { get; } = new ArrayBuilder<RenderTreeFrame>();
|
||||
|
||||
public int ReserveUpdatedComponentSlotId()
|
||||
{
|
||||
int id = _updatedComponentDiffs.Count;
|
||||
_updatedComponentDiffs.Append(default);
|
||||
return id;
|
||||
}
|
||||
public Queue<int> ComponentRenderQueue { get; } = new Queue<int>();
|
||||
|
||||
public void SetUpdatedComponent(int updatedComponentSlotId, RenderTreeDiff diff)
|
||||
=> _updatedComponentDiffs.Overwrite(updatedComponentSlotId, diff);
|
||||
public Queue<int> ComponentDisposalQueue { get; } = new Queue<int>();
|
||||
|
||||
public ArrayBuilder<RenderTreeDiff> UpdatedComponentDiffs { get; set; }
|
||||
= new ArrayBuilder<RenderTreeDiff>();
|
||||
|
||||
private readonly ArrayBuilder<int> _disposedComponentIds = new ArrayBuilder<int>();
|
||||
|
||||
private readonly ArrayBuilder<int> _disposedEventHandlerIds = new ArrayBuilder<int>();
|
||||
|
||||
public ArrayRange<int> GetDisposedEventHandlerIds()
|
||||
=> _disposedEventHandlerIds.ToRange();
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
_updatedComponentDiffs.Clear();
|
||||
EditsBuffer.Clear();
|
||||
ReferenceFramesBuffer.Clear();
|
||||
ComponentRenderQueue.Clear();
|
||||
UpdatedComponentDiffs.Clear();
|
||||
_disposedComponentIds.Clear();
|
||||
_disposedEventHandlerIds.Clear();
|
||||
}
|
||||
|
||||
public RenderBatch ToBatch()
|
||||
=> new RenderBatch(
|
||||
_updatedComponentDiffs.ToRange(),
|
||||
UpdatedComponentDiffs.ToRange(),
|
||||
ReferenceFramesBuffer.ToRange(),
|
||||
_disposedComponentIds.ToRange());
|
||||
|
||||
public void AddDisposedComponent(int componentId)
|
||||
public void AddDisposedComponentId(int componentId)
|
||||
=> _disposedComponentIds.Append(componentId);
|
||||
|
||||
public void AddDisposedEventHandlerId(int attributeEventHandlerId)
|
||||
|
|
|
|||
|
|
@ -87,6 +87,14 @@ namespace Microsoft.AspNetCore.Blazor.Rendering
|
|||
try
|
||||
{
|
||||
RenderInExistingBatch(_sharedRenderBatchBuilder, componentId);
|
||||
|
||||
// Process
|
||||
while (_sharedRenderBatchBuilder.ComponentRenderQueue.Count > 0)
|
||||
{
|
||||
var nextComponentIdToRender = _sharedRenderBatchBuilder.ComponentRenderQueue.Dequeue();
|
||||
RenderInExistingBatch(_sharedRenderBatchBuilder, nextComponentIdToRender);
|
||||
}
|
||||
|
||||
UpdateDisplay(_sharedRenderBatchBuilder.ToBatch());
|
||||
RemoveEventHandlerIds(_sharedRenderBatchBuilder.GetDisposedEventHandlerIds());
|
||||
}
|
||||
|
|
@ -98,14 +106,12 @@ namespace Microsoft.AspNetCore.Blazor.Rendering
|
|||
}
|
||||
|
||||
internal void RenderInExistingBatch(RenderBatchBuilder batchBuilder, int componentId)
|
||||
{
|
||||
GetRequiredComponentState(componentId).Render(batchBuilder);
|
||||
}
|
||||
=> GetRequiredComponentState(componentId).Render(this, batchBuilder);
|
||||
|
||||
internal void DisposeInExistingBatch(RenderBatchBuilder batchBuilder, int componentId)
|
||||
{
|
||||
GetRequiredComponentState(componentId).NotifyDisposed(batchBuilder);
|
||||
batchBuilder.AddDisposedComponent(componentId);
|
||||
batchBuilder.AddDisposedComponentId(componentId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -17,14 +17,12 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
private readonly Renderer renderer;
|
||||
private readonly RenderTreeBuilder oldTree;
|
||||
private readonly RenderTreeBuilder newTree;
|
||||
private RenderTreeDiffBuilder diff;
|
||||
|
||||
public RenderTreeDiffBuilderTest()
|
||||
{
|
||||
renderer = new FakeRenderer();
|
||||
oldTree = new RenderTreeBuilder(renderer);
|
||||
newTree = new RenderTreeBuilder(renderer);
|
||||
diff = new RenderTreeDiffBuilder(renderer);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
|
|
@ -36,7 +34,7 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
appendAction(newTree);
|
||||
|
||||
// Act
|
||||
var result = GetSingleUpdatedComponent();
|
||||
var (result, referenceFrames) = GetSingleUpdatedComponent();
|
||||
|
||||
// Assert
|
||||
Assert.Empty(result.Edits);
|
||||
|
|
@ -67,7 +65,7 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
newTree.AddText(2, "text2");
|
||||
|
||||
// Act
|
||||
var result = GetSingleUpdatedComponent();
|
||||
var (result, referenceFrames) = GetSingleUpdatedComponent();
|
||||
|
||||
// Assert
|
||||
Assert.Collection(result.Edits,
|
||||
|
|
@ -75,7 +73,7 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
{
|
||||
AssertEdit(entry, RenderTreeEditType.PrependFrame, 1);
|
||||
Assert.Equal(0, entry.ReferenceFrameIndex);
|
||||
AssertFrame.Text(result.ReferenceFrames.Array[0], "text1", 1);
|
||||
AssertFrame.Text(referenceFrames[0], "text1", 1);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -90,7 +88,7 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
newTree.AddText(2, "text2");
|
||||
|
||||
// Act
|
||||
var result = GetSingleUpdatedComponent();
|
||||
var (result, referenceFrames) = GetSingleUpdatedComponent();
|
||||
|
||||
// Assert
|
||||
Assert.Collection(result.Edits,
|
||||
|
|
@ -109,7 +107,7 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
newTree.AddText(0, "x"); // Loop start
|
||||
|
||||
// Act
|
||||
var result = GetSingleUpdatedComponent();
|
||||
var (result, referenceFrames) = GetSingleUpdatedComponent();
|
||||
|
||||
// Assert
|
||||
Assert.Collection(result.Edits,
|
||||
|
|
@ -129,7 +127,7 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
newTree.AddText(10, "x"); // Loop start
|
||||
|
||||
// Act
|
||||
var result = GetSingleUpdatedComponent();
|
||||
var (result, referenceFrames) = GetSingleUpdatedComponent();
|
||||
|
||||
// Assert
|
||||
Assert.Collection(result.Edits,
|
||||
|
|
@ -143,9 +141,8 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
AssertEdit(entry, RenderTreeEditType.PrependFrame, 2);
|
||||
Assert.Equal(1, entry.ReferenceFrameIndex);
|
||||
});
|
||||
Assert.Collection(result.ReferenceFrames,
|
||||
frame => AssertFrame.Text(frame, "x", 11),
|
||||
frame => AssertFrame.Text(frame, "x", 12));
|
||||
AssertFrame.Text(referenceFrames[0], "x", 11);
|
||||
AssertFrame.Text(referenceFrames[1], "x", 12);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -160,7 +157,7 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
newTree.AddText(1, "x");
|
||||
|
||||
// Act
|
||||
var result = GetSingleUpdatedComponent();
|
||||
var (result, referenceFrames) = GetSingleUpdatedComponent();
|
||||
|
||||
// Assert
|
||||
Assert.Collection(result.Edits,
|
||||
|
|
@ -180,7 +177,7 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
newTree.AddText(11, "x"); // Will be added
|
||||
|
||||
// Act
|
||||
var result = GetSingleUpdatedComponent();
|
||||
var (result, referenceFrames) = GetSingleUpdatedComponent();
|
||||
|
||||
// Assert
|
||||
Assert.Collection(result.Edits,
|
||||
|
|
@ -194,9 +191,8 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
AssertEdit(entry, RenderTreeEditType.PrependFrame, 3);
|
||||
Assert.Equal(1, entry.ReferenceFrameIndex);
|
||||
});
|
||||
Assert.Collection(result.ReferenceFrames,
|
||||
frame => AssertFrame.Text(frame, "x", 10),
|
||||
frame => AssertFrame.Text(frame, "x", 11));
|
||||
AssertFrame.Text(referenceFrames[0], "x", 10);
|
||||
AssertFrame.Text(referenceFrames[1], "x", 11);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -211,7 +207,7 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
newTree.AddText(12, "x");
|
||||
|
||||
// Act
|
||||
var result = GetSingleUpdatedComponent();
|
||||
var (result, referenceFrames) = GetSingleUpdatedComponent();
|
||||
|
||||
// Assert
|
||||
Assert.Collection(result.Edits,
|
||||
|
|
@ -225,9 +221,8 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
AssertEdit(entry, RenderTreeEditType.PrependFrame, 2);
|
||||
Assert.Equal(1, entry.ReferenceFrameIndex);
|
||||
});
|
||||
Assert.Collection(result.ReferenceFrames,
|
||||
frame => AssertFrame.Text(frame, "x", 10),
|
||||
frame => AssertFrame.Text(frame, "x", 11));
|
||||
AssertFrame.Text(referenceFrames[0], "x", 10);
|
||||
AssertFrame.Text(referenceFrames[1], "x", 11);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -242,7 +237,7 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
newTree.AddText(2, "x"); // Note that the '0' and '1' items are not present on this iteration
|
||||
|
||||
// Act
|
||||
var result = GetSingleUpdatedComponent();
|
||||
var (result, referenceFrames) = GetSingleUpdatedComponent();
|
||||
|
||||
// Assert
|
||||
Assert.Collection(result.Edits,
|
||||
|
|
@ -258,7 +253,7 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
newTree.AddText(1, "text");
|
||||
|
||||
// Act
|
||||
var result = GetSingleUpdatedComponent();
|
||||
var (result, referenceFrames) = GetSingleUpdatedComponent();
|
||||
|
||||
// Assert
|
||||
Assert.Collection(result.Edits,
|
||||
|
|
@ -276,7 +271,7 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
newTree.AddText(182, "new text 2");
|
||||
|
||||
// Act
|
||||
var result = GetSingleUpdatedComponent();
|
||||
var (result, referenceFrames) = GetSingleUpdatedComponent();
|
||||
|
||||
// Assert
|
||||
Assert.Collection(result.Edits,
|
||||
|
|
@ -306,7 +301,7 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
newTree.CloseElement();
|
||||
|
||||
// Act
|
||||
var result = GetSingleUpdatedComponent();
|
||||
var (result, referenceFrames) = GetSingleUpdatedComponent();
|
||||
|
||||
// Assert
|
||||
Assert.Collection(result.Edits,
|
||||
|
|
@ -327,34 +322,22 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
GetRenderedBatch(new RenderTreeBuilder(renderer), oldTree); // Assign initial IDs
|
||||
newTree.OpenComponent<FakeComponent2>(123);
|
||||
newTree.CloseComponent();
|
||||
var batchBuilder = new RenderBatchBuilder();
|
||||
|
||||
// Act
|
||||
var renderBatch = GetRenderedBatch();
|
||||
var diff = RenderTreeDiffBuilder.ComputeDiff(renderer, batchBuilder, 0, oldTree.GetFrames(), newTree.GetFrames());
|
||||
|
||||
// Assert
|
||||
Assert.Collection(renderBatch.DisposedComponentIDs,
|
||||
disposedComponentId => Assert.Equal(0, disposedComponentId));
|
||||
// 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: First updated component is the root with one child being
|
||||
// prepended, and its earlier incarnation being removed
|
||||
Assert.Equal(2, renderBatch.UpdatedComponents.Count);
|
||||
var updatedComponent1 = renderBatch.UpdatedComponents.Array[0];
|
||||
Assert.Collection(updatedComponent1.Edits,
|
||||
// Assert: Got correct info in diff
|
||||
Assert.Collection(diff.Edits,
|
||||
entry => AssertEdit(entry, RenderTreeEditType.RemoveFrame, 0),
|
||||
entry =>
|
||||
{
|
||||
AssertEdit(entry, RenderTreeEditType.PrependFrame, 0);
|
||||
Assert.Equal(0, entry.ReferenceFrameIndex);
|
||||
Assert.IsType<FakeComponent2>(updatedComponent1.ReferenceFrames.Array[0].Component);
|
||||
});
|
||||
|
||||
// Assert: Second updated component is the new FakeComponent2
|
||||
var updatedComponent2 = renderBatch.UpdatedComponents.Array[1];
|
||||
Assert.Collection(updatedComponent2.Edits,
|
||||
entry =>
|
||||
{
|
||||
AssertEdit(entry, RenderTreeEditType.PrependFrame, 0);
|
||||
Assert.Equal(0, entry.ReferenceFrameIndex);
|
||||
Assert.IsType<FakeComponent2>(batchBuilder.ReferenceFramesBuffer.Buffer[entry.ReferenceFrameIndex].Component);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -371,7 +354,7 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
newTree.CloseElement();
|
||||
|
||||
// Act
|
||||
var result = GetSingleUpdatedComponent();
|
||||
var (result, referenceFrames) = GetSingleUpdatedComponent();
|
||||
|
||||
// Assert
|
||||
Assert.Collection(result.Edits,
|
||||
|
|
@ -380,8 +363,7 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
AssertEdit(entry, RenderTreeEditType.SetAttribute, 0);
|
||||
Assert.Equal(0, entry.ReferenceFrameIndex);
|
||||
});
|
||||
Assert.Collection(result.ReferenceFrames,
|
||||
frame => AssertFrame.Attribute(frame, "added", "added value"));
|
||||
AssertFrame.Attribute(referenceFrames[0], "added", "added value");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -397,7 +379,7 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
newTree.CloseElement();
|
||||
|
||||
// Act
|
||||
var result = GetSingleUpdatedComponent();
|
||||
var (result, referenceFrames) = GetSingleUpdatedComponent();
|
||||
|
||||
// Assert
|
||||
Assert.Collection(result.Edits,
|
||||
|
|
@ -422,7 +404,7 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
newTree.CloseElement();
|
||||
|
||||
// Act
|
||||
var result = GetSingleUpdatedComponent();
|
||||
var (result, referenceFrames) = GetSingleUpdatedComponent();
|
||||
|
||||
// Assert
|
||||
Assert.Collection(result.Edits,
|
||||
|
|
@ -431,8 +413,7 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
AssertEdit(entry, RenderTreeEditType.SetAttribute, 0);
|
||||
Assert.Equal(0, entry.ReferenceFrameIndex);
|
||||
});
|
||||
Assert.Collection(result.ReferenceFrames,
|
||||
frame => AssertFrame.Attribute(frame, "will change", "did change value"));
|
||||
AssertFrame.Attribute(referenceFrames[0], "will change", "did change value");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -452,7 +433,7 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
newTree.CloseElement();
|
||||
|
||||
// Act
|
||||
var result = GetSingleUpdatedComponent();
|
||||
var (result, referenceFrames) = GetSingleUpdatedComponent();
|
||||
|
||||
// Assert
|
||||
Assert.Collection(result.Edits,
|
||||
|
|
@ -461,8 +442,7 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
AssertEdit(entry, RenderTreeEditType.SetAttribute, 0);
|
||||
Assert.Equal(0, entry.ReferenceFrameIndex);
|
||||
});
|
||||
Assert.Collection(result.ReferenceFrames,
|
||||
frame => AssertFrame.Attribute(frame, "will change", addedHandler));
|
||||
AssertFrame.Attribute(referenceFrames[0], "will change", addedHandler);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -477,7 +457,7 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
newTree.CloseElement();
|
||||
|
||||
// Act
|
||||
var result = GetSingleUpdatedComponent();
|
||||
var (result, referenceFrames) = GetSingleUpdatedComponent();
|
||||
|
||||
// Assert
|
||||
Assert.Collection(result.Edits,
|
||||
|
|
@ -491,8 +471,7 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
AssertEdit(entry, RenderTreeEditType.SetAttribute, 0);
|
||||
Assert.Equal(0, entry.ReferenceFrameIndex);
|
||||
});
|
||||
Assert.Collection(result.ReferenceFrames,
|
||||
frame => AssertFrame.Attribute(frame, "newname", "same value"));
|
||||
AssertFrame.Attribute(referenceFrames[0], "newname", "same value");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -518,7 +497,7 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
newTree.CloseElement();
|
||||
|
||||
// Act
|
||||
var result = GetSingleUpdatedComponent();
|
||||
var (result, referenceFrames) = GetSingleUpdatedComponent();
|
||||
|
||||
// Assert
|
||||
Assert.Collection(result.Edits,
|
||||
|
|
@ -533,8 +512,7 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
entry => AssertEdit(entry, RenderTreeEditType.StepOut, 0),
|
||||
entry => AssertEdit(entry, RenderTreeEditType.StepOut, 0),
|
||||
entry => AssertEdit(entry, RenderTreeEditType.StepOut, 0));
|
||||
Assert.Collection(result.ReferenceFrames,
|
||||
frame => AssertFrame.Text(frame, "grandchild new text", 13));
|
||||
AssertFrame.Text(referenceFrames[0], "grandchild new text", 13);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -560,7 +538,7 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
newTree.CloseElement();
|
||||
|
||||
// Act
|
||||
var result = GetSingleUpdatedComponent();
|
||||
var (result, referenceFrames) = GetSingleUpdatedComponent();
|
||||
|
||||
// Assert
|
||||
Assert.Collection(result.Edits,
|
||||
|
|
@ -571,8 +549,7 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
Assert.Equal(0, entry.ReferenceFrameIndex);
|
||||
},
|
||||
entry => AssertEdit(entry, RenderTreeEditType.StepOut, 0));
|
||||
Assert.Collection(result.ReferenceFrames,
|
||||
frame => AssertFrame.Text(frame, "Text that has changed", 11));
|
||||
AssertFrame.Text(referenceFrames[0], "Text that has changed", 11);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -589,7 +566,7 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
newTree.AddText(13, "text4");
|
||||
|
||||
// Act
|
||||
var result = GetSingleUpdatedComponent();
|
||||
var (result, referenceFrames) = GetSingleUpdatedComponent();
|
||||
|
||||
// Assert
|
||||
Assert.Collection(result.Edits,
|
||||
|
|
@ -598,8 +575,7 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
AssertEdit(entry, RenderTreeEditType.UpdateText, 1);
|
||||
Assert.Equal(0, entry.ReferenceFrameIndex);
|
||||
});
|
||||
Assert.Collection(result.ReferenceFrames,
|
||||
frame => AssertFrame.Text(frame, "text2modified", 11));
|
||||
AssertFrame.Text(referenceFrames[0], "text2modified", 11);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -621,11 +597,8 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
var renderBatch = GetRenderedBatch();
|
||||
|
||||
// Assert
|
||||
Assert.Equal(3, renderBatch.UpdatedComponents.Count);
|
||||
|
||||
// First component is the root one
|
||||
var firstComponentDiff = renderBatch.UpdatedComponents.Array[0];
|
||||
Assert.Collection(firstComponentDiff.Edits,
|
||||
var diff = renderBatch.UpdatedComponents.Single();
|
||||
Assert.Collection(diff.Edits,
|
||||
entry => AssertEdit(entry, RenderTreeEditType.StepIn, 1),
|
||||
entry =>
|
||||
{
|
||||
|
|
@ -638,23 +611,8 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
Assert.Equal(1, entry.ReferenceFrameIndex);
|
||||
},
|
||||
entry => AssertEdit(entry, RenderTreeEditType.StepOut, 0));
|
||||
Assert.Collection(firstComponentDiff.ReferenceFrames,
|
||||
frame => AssertFrame.ComponentWithInstance<FakeComponent>(frame, 0, 12),
|
||||
frame => AssertFrame.ComponentWithInstance<FakeComponent2>(frame, 1, 13));
|
||||
|
||||
// Second in batch is the first child component
|
||||
var secondComponentDiff = renderBatch.UpdatedComponents.Array[1];
|
||||
Assert.Equal(0, secondComponentDiff.ComponentId);
|
||||
Assert.Empty(secondComponentDiff.Edits); // Because FakeComponent produces no frames
|
||||
Assert.Empty(secondComponentDiff.ReferenceFrames); // Because FakeComponent produces no frames
|
||||
|
||||
// Third in batch is the second child component
|
||||
var thirdComponentDiff = renderBatch.UpdatedComponents.Array[2];
|
||||
Assert.Equal(1, thirdComponentDiff.ComponentId);
|
||||
Assert.Collection(thirdComponentDiff.Edits,
|
||||
entry => AssertEdit(entry, RenderTreeEditType.PrependFrame, 0));
|
||||
Assert.Collection(thirdComponentDiff.ReferenceFrames,
|
||||
frame => AssertFrame.Text(frame, $"Hello from {nameof(FakeComponent2)}"));
|
||||
AssertFrame.ComponentWithInstance<FakeComponent>(renderBatch.ReferenceFrames.Array[0], 0, 12);
|
||||
AssertFrame.ComponentWithInstance<FakeComponent2>(renderBatch.ReferenceFrames.Array[1], 1, 13);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -673,8 +631,7 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
var componentInstance = newTree.GetFrames().First().Component as FakeComponent;
|
||||
|
||||
// Assert
|
||||
Assert.Equal(2, renderBatch.UpdatedComponents.Count);
|
||||
|
||||
Assert.Equal(1, renderBatch.UpdatedComponents.Count);
|
||||
var rootComponentDiff = renderBatch.UpdatedComponents.Array[0];
|
||||
AssertEdit(rootComponentDiff.Edits.Single(), RenderTreeEditType.PrependFrame, 0);
|
||||
Assert.NotNull(componentInstance);
|
||||
|
|
@ -695,7 +652,7 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
// Act/Assert
|
||||
var ex = Assert.Throws<InvalidOperationException>(() =>
|
||||
{
|
||||
diff.ApplyNewRenderTreeVersion(new RenderBatchBuilder(), 0, oldTree.GetFrames(), newTree.GetFrames());
|
||||
RenderTreeDiffBuilder.ComputeDiff(renderer, new RenderBatchBuilder(), 0, oldTree.GetFrames(), newTree.GetFrames());
|
||||
});
|
||||
Assert.Equal($"Component of type '{typeof(FakeComponent).FullName}' does not have a property matching the name 'SomeUnknownProperty'.", ex.Message);
|
||||
}
|
||||
|
|
@ -712,7 +669,7 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
// Act/Assert
|
||||
var ex = Assert.Throws<InvalidOperationException>(() =>
|
||||
{
|
||||
diff.ApplyNewRenderTreeVersion(new RenderBatchBuilder(), 0, oldTree.GetFrames(), newTree.GetFrames());
|
||||
RenderTreeDiffBuilder.ComputeDiff(renderer, new RenderBatchBuilder(), 0, oldTree.GetFrames(), newTree.GetFrames());
|
||||
});
|
||||
Assert.StartsWith($"Unable to set property '{nameof(FakeComponent.ReadonlyProperty)}' on " +
|
||||
$"component of type '{typeof(FakeComponent).FullName}'.", ex.Message);
|
||||
|
|
@ -735,14 +692,14 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
newTree.CloseComponent(); // </FakeComponent>
|
||||
newTree.OpenComponent<FakeComponent2>(13); // 3: <FakeComponent2>
|
||||
newTree.CloseComponent(); // </FakeComponent2>
|
||||
newTree.CloseElement(); // </container
|
||||
newTree.CloseElement(); // </container>
|
||||
|
||||
diff.ApplyNewRenderTreeVersion(new RenderBatchBuilder(), 0, new RenderTreeBuilder(renderer).GetFrames(), oldTree.GetFrames());
|
||||
RenderTreeDiffBuilder.ComputeDiff(renderer, new RenderBatchBuilder(), 0, new RenderTreeBuilder(renderer).GetFrames(), oldTree.GetFrames());
|
||||
var originalFakeComponentInstance = oldTree.GetFrames().Array[2].Component;
|
||||
var originalFakeComponent2Instance = oldTree.GetFrames().Array[3].Component;
|
||||
|
||||
// Act
|
||||
var result = GetSingleUpdatedComponent();
|
||||
var (result, referenceFrames) = GetSingleUpdatedComponent();
|
||||
var newFrame1 = newTree.GetFrames().Array[2];
|
||||
var newFrame2 = newTree.GetFrames().Array[3];
|
||||
|
||||
|
|
@ -768,7 +725,7 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
newTree.AddAttribute(14, nameof(FakeComponent.ObjectProperty), objectWillNotChange);
|
||||
newTree.CloseComponent();
|
||||
|
||||
diff.ApplyNewRenderTreeVersion(new RenderBatchBuilder(), 0, new RenderTreeBuilder(renderer).GetFrames(), oldTree.GetFrames());
|
||||
RenderTreeDiffBuilder.ComputeDiff(renderer, new RenderBatchBuilder(), 0, new RenderTreeBuilder(renderer).GetFrames(), oldTree.GetFrames());
|
||||
var originalComponentInstance = (FakeComponent)oldTree.GetFrames().Array[0].Component;
|
||||
originalComponentInstance.ObjectProperty = null; // So we can see it doesn't get reassigned
|
||||
|
||||
|
|
@ -777,67 +734,14 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
var newComponentInstance = (FakeComponent)oldTree.GetFrames().Array[0].Component;
|
||||
|
||||
// Assert
|
||||
Assert.Equal(2, renderBatch.UpdatedComponents.Count);
|
||||
Assert.Equal(1, renderBatch.UpdatedComponents.Count); // Because the diff builder only queues child component renders; it doesn't actually perfom them itself
|
||||
Assert.Same(originalComponentInstance, newComponentInstance);
|
||||
Assert.Equal("String did change", newComponentInstance.StringProperty);
|
||||
Assert.Null(newComponentInstance.ObjectProperty); // To observe that the property wasn't even written, we nulled it out on the original
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NotifiesIHandlePropertiesChangedBeforeFirstRender()
|
||||
{
|
||||
// Arrange
|
||||
newTree.OpenComponent<HandlePropertiesChangedComponent>(0);
|
||||
newTree.CloseComponent();
|
||||
|
||||
// Act
|
||||
var batch = GetRenderedBatch();
|
||||
var diffForChildComponent = batch.UpdatedComponents.Array[1];
|
||||
|
||||
// Assert
|
||||
Assert.Collection(diffForChildComponent.ReferenceFrames,
|
||||
frame => AssertFrame.Text(frame, "Notifications: 1", 0));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NotifiesIHandlePropertiesChangedWhenChanged()
|
||||
{
|
||||
// Arrange
|
||||
var newTree1 = new RenderTreeBuilder(renderer);
|
||||
var newTree2 = new RenderTreeBuilder(renderer);
|
||||
oldTree.OpenComponent<HandlePropertiesChangedComponent>(0);
|
||||
oldTree.AddAttribute(1, nameof(HandlePropertiesChangedComponent.IntProperty), 123);
|
||||
oldTree.CloseComponent();
|
||||
newTree1.OpenComponent<HandlePropertiesChangedComponent>(0);
|
||||
newTree1.AddAttribute(1, nameof(HandlePropertiesChangedComponent.IntProperty), 123);
|
||||
newTree1.CloseComponent();
|
||||
newTree2.OpenComponent<HandlePropertiesChangedComponent>(0);
|
||||
newTree2.AddAttribute(1, nameof(HandlePropertiesChangedComponent.IntProperty), 456);
|
||||
newTree2.CloseComponent();
|
||||
|
||||
// Act/Assert 0: Initial render
|
||||
var batch0 = GetRenderedBatch(new RenderTreeBuilder(renderer), oldTree);
|
||||
var diffForChildComponent0 = batch0.UpdatedComponents.Array[1];
|
||||
var childComponentFrame = batch0.UpdatedComponents.Array[0].ReferenceFrames.Array[0];
|
||||
var childComponentInstance = (HandlePropertiesChangedComponent)childComponentFrame.Component;
|
||||
Assert.Equal(1, childComponentInstance.NotificationsCount);
|
||||
Assert.Collection(diffForChildComponent0.ReferenceFrames,
|
||||
frame => AssertFrame.Text(frame, "Notifications: 1", 0));
|
||||
|
||||
// Act/Assert 1: If properties didn't change, we don't notify
|
||||
GetRenderedBatch(oldTree, newTree1);
|
||||
Assert.Equal(1, childComponentInstance.NotificationsCount);
|
||||
|
||||
// Act/Assert 2: If properties did change, we do notify
|
||||
var batch2 = GetRenderedBatch(newTree1, newTree2);
|
||||
var diffForChildComponent2 = batch2.UpdatedComponents.Array[1];
|
||||
Assert.Equal(2, childComponentInstance.NotificationsCount);
|
||||
Assert.Collection(diffForChildComponent2.ReferenceFrames,
|
||||
frame => AssertFrame.Text(frame, "Notifications: 2", 0));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CallsDisposeOnlyOnRemovedChildComponents()
|
||||
public void QueuesRemovedChildComponentsForDisposal()
|
||||
{
|
||||
// Arrange
|
||||
oldTree.OpenComponent<DisposableComponent>(10); // <DisposableComponent>
|
||||
|
|
@ -849,29 +753,23 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
newTree.OpenComponent<DisposableComponent>(30); // <DisposableComponent>
|
||||
newTree.CloseComponent(); // </DisposableComponent>
|
||||
|
||||
diff.ApplyNewRenderTreeVersion(new RenderBatchBuilder(), 0, new RenderTreeBuilder(renderer).GetFrames(), oldTree.GetFrames());
|
||||
var disposableComponent1 = (DisposableComponent)oldTree.GetFrames().Array[0].Component;
|
||||
var nonDisposableComponent = (NonDisposableComponent)oldTree.GetFrames().Array[1].Component;
|
||||
var disposableComponent2 = (DisposableComponent)oldTree.GetFrames().Array[2].Component;
|
||||
var batchBuilder = new RenderBatchBuilder();
|
||||
RenderTreeDiffBuilder.ComputeDiff(renderer, batchBuilder, 0, new RenderTreeBuilder(renderer).GetFrames(), oldTree.GetFrames());
|
||||
|
||||
// Act
|
||||
var renderedBatch = GetRenderedBatch();
|
||||
|
||||
// Assert: We track NonDisposableComponent was disposed even though it's not IDisposable
|
||||
Assert.Equal(renderedBatch.DisposedComponentIDs, new[] { 0, 1 });
|
||||
|
||||
// Assert: We did call Dispose on the disposed DisposableComponent
|
||||
Assert.Equal(1, disposableComponent1.DisposalCount);
|
||||
|
||||
// Assert: We didn't dispose the retained component
|
||||
Assert.Equal(0, disposableComponent2.DisposalCount);
|
||||
// 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);
|
||||
}
|
||||
|
||||
private RenderTreeDiff GetSingleUpdatedComponent()
|
||||
private (RenderTreeDiff, RenderTreeFrame[]) GetSingleUpdatedComponent()
|
||||
{
|
||||
var diffsInBatch = GetRenderedBatch().UpdatedComponents;
|
||||
var batch = GetRenderedBatch();
|
||||
var diffsInBatch = batch.UpdatedComponents;
|
||||
Assert.Equal(1, diffsInBatch.Count);
|
||||
return diffsInBatch.Array[0];
|
||||
return (diffsInBatch.Array[0], batch.ReferenceFrames.ToArray());
|
||||
}
|
||||
|
||||
private RenderBatch GetRenderedBatch()
|
||||
|
|
@ -880,7 +778,8 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
private RenderBatch GetRenderedBatch(RenderTreeBuilder from, RenderTreeBuilder to)
|
||||
{
|
||||
var batchBuilder = new RenderBatchBuilder();
|
||||
diff.ApplyNewRenderTreeVersion(batchBuilder, 0, from.GetFrames(), to.GetFrames());
|
||||
var diff = RenderTreeDiffBuilder.ComputeDiff(renderer, batchBuilder, 0, from.GetFrames(), to.GetFrames());
|
||||
batchBuilder.UpdatedComponentDiffs.Append(diff);
|
||||
return batchBuilder.ToBatch();
|
||||
}
|
||||
|
||||
|
|
@ -912,23 +811,6 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
}
|
||||
}
|
||||
|
||||
private class HandlePropertiesChangedComponent : IComponent, IHandlePropertiesChanged
|
||||
{
|
||||
public int NotificationsCount { get; private set; }
|
||||
|
||||
public int IntProperty { get; set; }
|
||||
|
||||
public void BuildRenderTree(RenderTreeBuilder builder)
|
||||
{
|
||||
builder.AddText(0, $"Notifications: {NotificationsCount}");
|
||||
}
|
||||
|
||||
public void OnPropertiesChanged()
|
||||
{
|
||||
NotificationsCount++;
|
||||
}
|
||||
}
|
||||
|
||||
private class DisposableComponent : IComponent, IDisposable
|
||||
{
|
||||
public int DisposalCount { get; private set; }
|
||||
|
|
|
|||
|
|
@ -31,16 +31,16 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
renderer.RenderNewBatch(componentId);
|
||||
|
||||
// Assert
|
||||
var diff = renderer.Batches.Single().DiffsByComponentId[componentId].Single();
|
||||
var batch = renderer.Batches.Single();
|
||||
var diff = batch.DiffsByComponentId[componentId].Single();
|
||||
Assert.Collection(diff.Edits,
|
||||
edit =>
|
||||
{
|
||||
Assert.Equal(RenderTreeEditType.PrependFrame, edit.Type);
|
||||
Assert.Equal(0, edit.ReferenceFrameIndex);
|
||||
});
|
||||
Assert.Collection(diff.ReferenceFrames,
|
||||
frame => AssertFrame.Element(frame, "my element", 2),
|
||||
frame => AssertFrame.Text(frame, "some text"));
|
||||
AssertFrame.Element(batch.ReferenceFrames[0], "my element", 2);
|
||||
AssertFrame.Text(batch.ReferenceFrames[1], "some text");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -60,11 +60,14 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
var componentId = renderer.AssignComponentId(component);
|
||||
renderer.RenderNewBatch(componentId);
|
||||
var batch = renderer.Batches.Single();
|
||||
var componentFrame = batch.DiffsByComponentId[componentId].Single().ReferenceFrames
|
||||
var componentFrame = batch.ReferenceFrames
|
||||
.Single(frame => frame.FrameType == RenderTreeFrameType.Component);
|
||||
var nestedComponentId = componentFrame.ComponentId;
|
||||
var nestedComponentDiff = batch.DiffsByComponentId[nestedComponentId].Single();
|
||||
|
||||
// We rendered both components
|
||||
Assert.Equal(2, batch.DiffsByComponentId.Count);
|
||||
|
||||
// The nested component exists
|
||||
Assert.IsType<MessageComponent>(componentFrame.Component);
|
||||
|
||||
|
|
@ -73,10 +76,10 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
edit =>
|
||||
{
|
||||
Assert.Equal(RenderTreeEditType.PrependFrame, edit.Type);
|
||||
Assert.Equal(0, edit.ReferenceFrameIndex);
|
||||
AssertFrame.Text(
|
||||
batch.ReferenceFrames[edit.ReferenceFrameIndex],
|
||||
"Nested component output");
|
||||
});
|
||||
Assert.Collection(nestedComponentDiff.ReferenceFrames,
|
||||
frame => AssertFrame.Text(frame, "Nested component output"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -89,28 +92,28 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
|
||||
// Act/Assert: first render
|
||||
renderer.RenderNewBatch(componentId);
|
||||
var firstDiff = renderer.Batches.Single().DiffsByComponentId[componentId].Single();
|
||||
var batch = renderer.Batches.Single();
|
||||
var firstDiff = batch.DiffsByComponentId[componentId].Single();
|
||||
Assert.Collection(firstDiff.Edits,
|
||||
edit =>
|
||||
{
|
||||
Assert.Equal(RenderTreeEditType.PrependFrame, edit.Type);
|
||||
Assert.Equal(0, edit.ReferenceFrameIndex);
|
||||
AssertFrame.Text(batch.ReferenceFrames[0], "Initial message");
|
||||
});
|
||||
Assert.Collection(firstDiff.ReferenceFrames,
|
||||
frame => AssertFrame.Text(frame, "Initial message"));
|
||||
|
||||
// Act/Assert: second render
|
||||
component.Message = "Modified message";
|
||||
renderer.RenderNewBatch(componentId);
|
||||
var secondDiff = renderer.Batches.Skip(1).Single().DiffsByComponentId[componentId].Single();
|
||||
Assert.Collection(firstDiff.Edits,
|
||||
var secondBatch = renderer.Batches.Skip(1).Single();
|
||||
var secondDiff = secondBatch.DiffsByComponentId[componentId].Single();
|
||||
Assert.Collection(secondDiff.Edits,
|
||||
edit =>
|
||||
{
|
||||
Assert.Equal(RenderTreeEditType.UpdateText, edit.Type);
|
||||
Assert.Equal(0, edit.ReferenceFrameIndex);
|
||||
AssertFrame.Text(secondBatch.ReferenceFrames[0], "Modified message");
|
||||
});
|
||||
Assert.Collection(firstDiff.ReferenceFrames,
|
||||
frame => AssertFrame.Text(frame, "Modified message"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -125,8 +128,7 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
});
|
||||
var parentComponentId = renderer.AssignComponentId(parentComponent);
|
||||
renderer.RenderNewBatch(parentComponentId);
|
||||
var nestedComponentFrame = renderer.Batches.Single().DiffsByComponentId[parentComponentId]
|
||||
.Single()
|
||||
var nestedComponentFrame = renderer.Batches.Single()
|
||||
.ReferenceFrames
|
||||
.Single(frame => frame.FrameType == RenderTreeFrameType.Component);
|
||||
var nestedComponent = (MessageComponent)nestedComponentFrame.Component;
|
||||
|
|
@ -135,28 +137,28 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
// Assert: inital render
|
||||
nestedComponent.Message = "Render 1";
|
||||
renderer.RenderNewBatch(nestedComponentId);
|
||||
var firstDiff = renderer.Batches[1].DiffsByComponentId[nestedComponentId].Single();
|
||||
var batch = renderer.Batches[1];
|
||||
var firstDiff = batch.DiffsByComponentId[nestedComponentId].Single();
|
||||
Assert.Collection(firstDiff.Edits,
|
||||
edit =>
|
||||
{
|
||||
Assert.Equal(RenderTreeEditType.UpdateText, edit.Type);
|
||||
Assert.Equal(0, edit.ReferenceFrameIndex);
|
||||
AssertFrame.Text(batch.ReferenceFrames[0], "Render 1");
|
||||
});
|
||||
Assert.Collection(firstDiff.ReferenceFrames,
|
||||
frame => AssertFrame.Text(frame, "Render 1"));
|
||||
|
||||
// Act/Assert: re-render
|
||||
nestedComponent.Message = "Render 2";
|
||||
renderer.RenderNewBatch(nestedComponentId);
|
||||
var secondDiff = renderer.Batches[2].DiffsByComponentId[nestedComponentId].Single();
|
||||
Assert.Collection(firstDiff.Edits,
|
||||
var secondBatch = renderer.Batches[2];
|
||||
var secondDiff = secondBatch.DiffsByComponentId[nestedComponentId].Single();
|
||||
Assert.Collection(secondDiff.Edits,
|
||||
edit =>
|
||||
{
|
||||
Assert.Equal(RenderTreeEditType.UpdateText, edit.Type);
|
||||
Assert.Equal(0, edit.ReferenceFrameIndex);
|
||||
AssertFrame.Text(secondBatch.ReferenceFrames[0], "Render 2");
|
||||
});
|
||||
Assert.Collection(firstDiff.ReferenceFrames,
|
||||
frame => AssertFrame.Text(frame, "Render 2"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -173,7 +175,7 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
var componentId = renderer.AssignComponentId(component);
|
||||
renderer.RenderNewBatch(componentId);
|
||||
|
||||
var eventHandlerId = renderer.Batches.Single().DiffsByComponentId[componentId].Single()
|
||||
var eventHandlerId = renderer.Batches.Single()
|
||||
.ReferenceFrames
|
||||
.First(frame => frame.AttributeValue != null)
|
||||
.AttributeEventHandlerId;
|
||||
|
|
@ -203,8 +205,7 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
renderer.RenderNewBatch(parentComponentId);
|
||||
|
||||
// Arrange: Render nested component
|
||||
var nestedComponentFrame = renderer.Batches.Single().DiffsByComponentId[parentComponentId]
|
||||
.Single()
|
||||
var nestedComponentFrame = renderer.Batches.Single()
|
||||
.ReferenceFrames
|
||||
.Single(frame => frame.FrameType == RenderTreeFrameType.Component);
|
||||
var nestedComponent = (EventComponent)nestedComponentFrame.Component;
|
||||
|
|
@ -213,7 +214,7 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
renderer.RenderNewBatch(nestedComponentId);
|
||||
|
||||
// Find nested component's event handler ID
|
||||
var eventHandlerId = renderer.Batches[1].DiffsByComponentId[nestedComponentId].Single()
|
||||
var eventHandlerId = renderer.Batches[1]
|
||||
.ReferenceFrames
|
||||
.First(frame => frame.AttributeValue != null)
|
||||
.AttributeEventHandlerId;
|
||||
|
|
@ -363,8 +364,6 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
renderer.RenderNewBatch(rootComponentId);
|
||||
|
||||
var nestedComponentFrame = renderer.Batches.Single()
|
||||
.DiffsByComponentId[rootComponentId]
|
||||
.Single()
|
||||
.ReferenceFrames
|
||||
.Single(frame => frame.FrameType == RenderTreeFrameType.Component);
|
||||
var nestedComponentInstance = (MessageComponent)nestedComponentFrame.Component;
|
||||
|
|
@ -382,8 +381,7 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
Assert.Equal(RenderTreeEditType.UpdateText, edit.Type);
|
||||
Assert.Equal(0, edit.ReferenceFrameIndex);
|
||||
});
|
||||
Assert.Collection(diff.ReferenceFrames,
|
||||
frame => AssertFrame.Text(frame, "Modified message"));
|
||||
AssertFrame.Text(batch.ReferenceFrames[0], "Modified message");
|
||||
Assert.False(batch.DiffsByComponentId.ContainsKey(nestedComponentFrame.ComponentId));
|
||||
}
|
||||
|
||||
|
|
@ -406,8 +404,7 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
var rootComponentId = renderer.AssignComponentId(component);
|
||||
renderer.RenderNewBatch(rootComponentId);
|
||||
|
||||
var originalComponentFrame = renderer.Batches.Single().DiffsByComponentId[rootComponentId]
|
||||
.Single()
|
||||
var originalComponentFrame = renderer.Batches.Single()
|
||||
.ReferenceFrames
|
||||
.Single(frame => frame.FrameType == RenderTreeFrameType.Component);
|
||||
var childComponentInstance = (FakeComponent)originalComponentFrame.Component;
|
||||
|
|
@ -443,8 +440,7 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
var rootComponentId = renderer.AssignComponentId(component);
|
||||
renderer.RenderNewBatch(rootComponentId);
|
||||
|
||||
var childComponentId = renderer.Batches.Single().DiffsByComponentId[rootComponentId]
|
||||
.Single()
|
||||
var childComponentId = renderer.Batches.Single()
|
||||
.ReferenceFrames
|
||||
.Single(frame => frame.FrameType == RenderTreeFrameType.Component)
|
||||
.ComponentId;
|
||||
|
|
@ -461,8 +457,67 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
Assert.Equal(RenderTreeEditType.UpdateText, edit.Type);
|
||||
Assert.Equal(0, edit.ReferenceFrameIndex);
|
||||
});
|
||||
Assert.Collection(diff.ReferenceFrames,
|
||||
frame => AssertFrame.Text(frame, "second"));
|
||||
AssertFrame.Text(renderer.Batches[1].ReferenceFrames[0], "second");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NotifiesIHandlePropertiesChangedBeforeFirstRender()
|
||||
{
|
||||
// Arrange
|
||||
var renderer = new TestRenderer();
|
||||
var component = new HandlePropertiesChangedComponent();
|
||||
var rootComponentId = renderer.AssignComponentId(component);
|
||||
|
||||
// Act
|
||||
renderer.RenderNewBatch(rootComponentId);
|
||||
|
||||
// Assert
|
||||
AssertFrame.Text(renderer.Batches.Single().ReferenceFrames[0], "Notifications: 1", 0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NotifiesIHandlePropertiesChangedWhenChanged()
|
||||
{
|
||||
// Arrange
|
||||
var renderer = new TestRenderer();
|
||||
var component = new ConditionalParentComponent<HandlePropertiesChangedComponent>
|
||||
{
|
||||
IncludeChild = true,
|
||||
ChildParameters = new Dictionary<string, object>
|
||||
{
|
||||
{ nameof(HandlePropertiesChangedComponent.IntProperty), 123 }
|
||||
}
|
||||
};
|
||||
var rootComponentId = renderer.AssignComponentId(component);
|
||||
|
||||
// Act/Assert 0: Initial render
|
||||
renderer.RenderNewBatch(rootComponentId);
|
||||
var batch1 = renderer.Batches.Single();
|
||||
var childComponentFrame = batch1
|
||||
.ReferenceFrames.Where(frame => frame.FrameType == RenderTreeFrameType.Component).Single();
|
||||
var childComponentId = childComponentFrame.ComponentId;
|
||||
var diffForChildComponent0 = batch1.DiffsByComponentId[childComponentId].Single();
|
||||
var childComponentInstance = (HandlePropertiesChangedComponent)childComponentFrame.Component;
|
||||
Assert.Equal(1, childComponentInstance.NotificationsCount);
|
||||
Assert.Collection(diffForChildComponent0.Edits,
|
||||
edit =>
|
||||
{
|
||||
Assert.Equal(RenderTreeEditType.PrependFrame, edit.Type);
|
||||
AssertFrame.Text(batch1.ReferenceFrames[edit.ReferenceFrameIndex], "Notifications: 1", 0);
|
||||
});
|
||||
|
||||
// Act/Assert 1: If properties didn't change, we don't notify
|
||||
renderer.RenderNewBatch(rootComponentId);
|
||||
Assert.Equal(1, childComponentInstance.NotificationsCount);
|
||||
|
||||
// Act/Assert 2: If properties did change, we do notify
|
||||
component.ChildParameters[nameof(HandlePropertiesChangedComponent.IntProperty)] = 456;
|
||||
renderer.RenderNewBatch(rootComponentId);
|
||||
Assert.Equal(2, childComponentInstance.NotificationsCount);
|
||||
var batch3 = renderer.Batches.Skip(2).Single();
|
||||
var diffForChildComponent2 = batch3.DiffsByComponentId[childComponentId].Single();
|
||||
Assert.Equal(2, childComponentInstance.NotificationsCount);
|
||||
AssertFrame.Text(batch3.ReferenceFrames[0], "Notifications: 2", 0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -473,7 +528,6 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
var firstRender = true;
|
||||
var component = new TestComponent(builder =>
|
||||
{
|
||||
builder.OpenElement(7, "some element");
|
||||
if (firstRender)
|
||||
{
|
||||
// Nested descendants
|
||||
|
|
@ -483,27 +537,28 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
}
|
||||
builder.OpenComponent<FakeComponent>(200);
|
||||
builder.CloseComponent();
|
||||
builder.CloseElement();
|
||||
});
|
||||
|
||||
var rootComponentId = renderer.AssignComponentId(component);
|
||||
|
||||
// Act/Assert 1: First render, capturing child component IDs
|
||||
renderer.RenderNewBatch(rootComponentId);
|
||||
var childComponentIds = renderer.Batches.Single().DiffsByComponentId[rootComponentId]
|
||||
.Single()
|
||||
.ReferenceFrames
|
||||
var batch = renderer.Batches.Single();
|
||||
var rootComponentDiff = batch.DiffsByComponentId[rootComponentId].Single();
|
||||
var childComponentIds = rootComponentDiff
|
||||
.Edits
|
||||
.Select(edit => batch.ReferenceFrames[edit.ReferenceFrameIndex])
|
||||
.Where(frame => frame.FrameType == RenderTreeFrameType.Component)
|
||||
.Select(frame => frame.ComponentId)
|
||||
.ToList();
|
||||
Assert.Equal(new[] { 1, 3 }, childComponentIds);
|
||||
Assert.Equal(new[] { 1, 2 }, childComponentIds);
|
||||
|
||||
// Act: Second render
|
||||
firstRender = false;
|
||||
renderer.RenderNewBatch(rootComponentId);
|
||||
|
||||
// Assert: Applicable children are included in disposal list
|
||||
Assert.Equal(new[] { 2, 1 }, renderer.Batches[1].DisposedComponentIDs);
|
||||
Assert.Equal(new[] { 1, 3 }, renderer.Batches[1].DisposedComponentIDs);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -516,7 +571,7 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
var component = new EventComponent { Handler = origEventHandler };
|
||||
var componentId = renderer.AssignComponentId(component);
|
||||
renderer.RenderNewBatch(componentId);
|
||||
var origEventHandlerId = renderer.Batches.Single().DiffsByComponentId[componentId].Single()
|
||||
var origEventHandlerId = renderer.Batches.Single()
|
||||
.ReferenceFrames
|
||||
.Where(f => f.FrameType == RenderTreeFrameType.Attribute)
|
||||
.Single(f => f.AttributeEventHandlerId != 0)
|
||||
|
|
@ -553,7 +608,7 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
var component = new EventComponent { Handler = origEventHandler };
|
||||
var componentId = renderer.AssignComponentId(component);
|
||||
renderer.RenderNewBatch(componentId);
|
||||
var origEventHandlerId = renderer.Batches.Single().DiffsByComponentId[componentId].Single()
|
||||
var origEventHandlerId = renderer.Batches.Single()
|
||||
.ReferenceFrames
|
||||
.Where(f => f.FrameType == RenderTreeFrameType.Attribute)
|
||||
.Single(f => f.AttributeEventHandlerId != 0)
|
||||
|
|
@ -593,13 +648,17 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
};
|
||||
var rootComponentId = renderer.AssignComponentId(component);
|
||||
renderer.RenderNewBatch(rootComponentId);
|
||||
var childComponentId = renderer.Batches.Single().DiffsByComponentId[rootComponentId].Single()
|
||||
.ReferenceFrames
|
||||
var batch = renderer.Batches.Single();
|
||||
var rootComponentDiff = batch.DiffsByComponentId[rootComponentId].Single();
|
||||
var rootComponentFrame = batch.ReferenceFrames[0];
|
||||
var childComponentFrame = rootComponentDiff.Edits
|
||||
.Select(e => batch.ReferenceFrames[e.ReferenceFrameIndex])
|
||||
.Where(f => f.FrameType == RenderTreeFrameType.Component)
|
||||
.Single()
|
||||
.ComponentId;
|
||||
var eventHandlerId = renderer.Batches.Single().DiffsByComponentId[childComponentId].Single()
|
||||
.ReferenceFrames
|
||||
.Single();
|
||||
var childComponentId = childComponentFrame.ComponentId;
|
||||
var childComponentDiff = batch.DiffsByComponentId[childComponentFrame.ComponentId].Single();
|
||||
var eventHandlerId = batch.ReferenceFrames
|
||||
.Skip(childComponentDiff.Edits[0].ReferenceFrameIndex) // Search from where the child component frames start
|
||||
.Where(f => f.FrameType == RenderTreeFrameType.Attribute)
|
||||
.Single(f => f.AttributeEventHandlerId != 0)
|
||||
.AttributeEventHandlerId;
|
||||
|
|
@ -631,7 +690,7 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
var component = new EventComponent { Handler = origEventHandler };
|
||||
var componentId = renderer.AssignComponentId(component);
|
||||
renderer.RenderNewBatch(componentId);
|
||||
var origEventHandlerId = renderer.Batches.Single().DiffsByComponentId[componentId].Single()
|
||||
var origEventHandlerId = renderer.Batches.Single()
|
||||
.ReferenceFrames
|
||||
.Where(f => f.FrameType == RenderTreeFrameType.Attribute)
|
||||
.Single(f => f.AttributeEventHandlerId != 0)
|
||||
|
|
@ -692,6 +751,8 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
capturedBatch.AddDiff(renderTreeDiff);
|
||||
}
|
||||
|
||||
// Clone other data, as underlying storage will get reused by later batches
|
||||
capturedBatch.ReferenceFrames = renderBatch.ReferenceFrames.ToArray();
|
||||
capturedBatch.DisposedComponentIDs = renderBatch.DisposedComponentIDs.ToList();
|
||||
}
|
||||
}
|
||||
|
|
@ -702,6 +763,7 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
= new Dictionary<int, List<RenderTreeDiff>>();
|
||||
|
||||
public IList<int> DisposedComponentIDs { get; set; }
|
||||
public RenderTreeFrame[] ReferenceFrames { get; set; }
|
||||
|
||||
internal void AddDiff(RenderTreeDiff diff)
|
||||
{
|
||||
|
|
@ -710,7 +772,11 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
{
|
||||
DiffsByComponentId.Add(componentId, new List<RenderTreeDiff>());
|
||||
}
|
||||
DiffsByComponentId[componentId].Add(diff);
|
||||
|
||||
// Clone the diff, because its underlying storage will get reused in subsequent batches
|
||||
DiffsByComponentId[componentId].Add(new RenderTreeDiff(
|
||||
diff.ComponentId,
|
||||
new ArraySegment<RenderTreeEdit>(diff.Edits.ToArray())));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -796,6 +862,23 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
}
|
||||
}
|
||||
|
||||
private class HandlePropertiesChangedComponent : IComponent, IHandlePropertiesChanged
|
||||
{
|
||||
public int NotificationsCount { get; private set; }
|
||||
|
||||
public int IntProperty { get; set; }
|
||||
|
||||
public void BuildRenderTree(RenderTreeBuilder builder)
|
||||
{
|
||||
builder.AddText(0, $"Notifications: {NotificationsCount}");
|
||||
}
|
||||
|
||||
public void OnPropertiesChanged()
|
||||
{
|
||||
NotificationsCount++;
|
||||
}
|
||||
}
|
||||
|
||||
void AssertCanBeCollected(Func<object> targetFactory)
|
||||
{
|
||||
// We have to construct the WeakReference in a separate scope
|
||||
|
|
|
|||
Loading…
Reference in New Issue