Optimize render tree building via RenderTreeFrameArrayBuilder (#24484)

This commit is contained in:
Steve Sanderson 2020-08-03 18:42:31 +01:00 committed by GitHub
parent 749450ac7b
commit 38e166fd59
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 401 additions and 238 deletions

View File

@ -142,8 +142,8 @@ namespace Microsoft.AspNetCore.Components
var oldIndex = oldParameters._ownerIndex;
var newIndex = _ownerIndex;
var oldEndIndexExcl = oldIndex + oldParameters._frames[oldIndex].ComponentSubtreeLength;
var newEndIndexExcl = newIndex + _frames[newIndex].ComponentSubtreeLength;
var oldEndIndexExcl = oldIndex + oldParameters._frames[oldIndex].ComponentSubtreeLengthField;
var newEndIndexExcl = newIndex + _frames[newIndex].ComponentSubtreeLengthField;
while (true)
{
// First, stop if we've reached the end of either subtree
@ -162,21 +162,21 @@ namespace Microsoft.AspNetCore.Components
ref var newFrame = ref _frames[newIndex];
// Stop if we've reached the end of either subtree's sequence of attributes
oldFinished = oldFrame.FrameType != RenderTreeFrameType.Attribute;
newFinished = newFrame.FrameType != RenderTreeFrameType.Attribute;
oldFinished = oldFrame.FrameTypeField != RenderTreeFrameType.Attribute;
newFinished = newFrame.FrameTypeField != RenderTreeFrameType.Attribute;
if (oldFinished || newFinished)
{
return oldFinished == newFinished; // Same only if we have same number of parameters
}
else
{
if (!string.Equals(oldFrame.AttributeName, newFrame.AttributeName, StringComparison.Ordinal))
if (!string.Equals(oldFrame.AttributeNameField, newFrame.AttributeNameField, StringComparison.Ordinal))
{
return false; // Different names
}
var oldValue = oldFrame.AttributeValue;
var newValue = newFrame.AttributeValue;
var oldValue = oldFrame.AttributeValueField;
var newValue = newFrame.AttributeValueField;
if (ChangeDetection.MayHaveChanged(oldValue, newValue))
{
return false;
@ -216,8 +216,8 @@ namespace Microsoft.AspNetCore.Components
public static ParameterView FromDictionary(IDictionary<string, object> parameters)
{
var frames = new RenderTreeFrame[parameters.Count + 1];
frames[0] = RenderTreeFrame.Element(0, GeneratedParameterViewElementName)
.WithElementSubtreeLength(frames.Length);
frames[0] = RenderTreeFrame.Element(0, GeneratedParameterViewElementName);
frames[0].ElementSubtreeLengthField = frames.Length;
var i = 0;
foreach (var kvp in parameters)
@ -303,7 +303,7 @@ namespace Microsoft.AspNetCore.Components
{
_frames = frames;
_ownerIndex = ownerIndex;
_ownerDescendantsEndIndexExcl = ownerIndex + _frames[ownerIndex].ElementSubtreeLength;
_ownerDescendantsEndIndexExcl = ownerIndex + _frames[ownerIndex].ElementSubtreeLengthField;
_currentIndex = ownerIndex;
_current = default;
}
@ -321,7 +321,7 @@ namespace Microsoft.AspNetCore.Components
// ... or if you get to its first non-attribute descendant (because attributes
// are always before any other type of descendant)
if (_frames[nextIndex].FrameType != RenderTreeFrameType.Attribute)
if (_frames[nextIndex].FrameTypeField != RenderTreeFrameType.Attribute)
{
return false;
}
@ -329,7 +329,7 @@ namespace Microsoft.AspNetCore.Components
_currentIndex = nextIndex;
ref var frame = ref _frames[_currentIndex];
_current = new ParameterValue(frame.AttributeName, frame.AttributeValue, false);
_current = new ParameterValue(frame.AttributeNameField, frame.AttributeValueField, false);
return true;
}

View File

@ -78,7 +78,7 @@ namespace Microsoft.AspNetCore.Components.RenderTree
if (hasMoreOld)
{
ref var oldFrame = ref oldTree[oldStartIndex];
oldSeq = oldFrame.Sequence;
oldSeq = oldFrame.SequenceField;
oldKey = KeyValue(ref oldFrame);
}
else
@ -90,7 +90,7 @@ namespace Microsoft.AspNetCore.Components.RenderTree
if (hasMoreNew)
{
ref var newFrame = ref newTree[newStartIndex];
newSeq = newFrame.Sequence;
newSeq = newFrame.SequenceField;
newKey = KeyValue(ref newFrame);
}
else
@ -195,7 +195,7 @@ namespace Microsoft.AspNetCore.Components.RenderTree
var newLoopsBackLater = false;
for (var testIndex = newStartIndex + 1; testIndex < newEndIndexExcl; testIndex++)
{
if (newTree[testIndex].Sequence < newSeq)
if (newTree[testIndex].SequenceField < newSeq)
{
newLoopsBackLater = true;
break;
@ -218,7 +218,7 @@ namespace Microsoft.AspNetCore.Components.RenderTree
var oldLoopsBackLater = false;
for (var testIndex = oldStartIndex + 1; testIndex < oldEndIndexExcl; testIndex++)
{
if (oldTree[testIndex].Sequence < oldSeq)
if (oldTree[testIndex].SequenceField < oldSeq)
{
oldLoopsBackLater = true;
break;
@ -350,13 +350,13 @@ namespace Microsoft.AspNetCore.Components.RenderTree
private static void ThrowExceptionForDuplicateKey(object key, in RenderTreeFrame frame)
{
switch (frame.FrameType)
switch (frame.FrameTypeField)
{
case RenderTreeFrameType.Component:
throw new InvalidOperationException($"More than one sibling of component '{frame.ComponentType}' has the same key value, '{key}'. Key values must be unique.");
throw new InvalidOperationException($"More than one sibling of component '{frame.ComponentTypeField}' has the same key value, '{key}'. Key values must be unique.");
case RenderTreeFrameType.Element:
throw new InvalidOperationException($"More than one sibling of element '{frame.ElementName}' has the same key value, '{key}'. Key values must be unique.");
throw new InvalidOperationException($"More than one sibling of element '{frame.ElementNameField}' has the same key value, '{key}'. Key values must be unique.");
default:
throw new InvalidOperationException($"More than one sibling has the same key value, '{key}'. Key values must be unique.");
@ -365,12 +365,12 @@ namespace Microsoft.AspNetCore.Components.RenderTree
private static object KeyValue(ref RenderTreeFrame frame)
{
switch (frame.FrameType)
switch (frame.FrameTypeField)
{
case RenderTreeFrameType.Element:
return frame.ElementKey;
return frame.ElementKeyField;
case RenderTreeFrameType.Component:
return frame.ComponentKey;
return frame.ComponentKeyField;
default:
return null;
}
@ -405,10 +405,10 @@ namespace Microsoft.AspNetCore.Components.RenderTree
while (hasMoreOld || hasMoreNew)
{
var oldSeq = hasMoreOld ? oldTree[oldStartIndex].Sequence : int.MaxValue;
var newSeq = hasMoreNew ? newTree[newStartIndex].Sequence : int.MaxValue;
var oldAttributeName = oldTree[oldStartIndex].AttributeName;
var newAttributeName = newTree[newStartIndex].AttributeName;
var oldSeq = hasMoreOld ? oldTree[oldStartIndex].SequenceField : int.MaxValue;
var newSeq = hasMoreNew ? newTree[newStartIndex].SequenceField : int.MaxValue;
var oldAttributeName = oldTree[oldStartIndex].AttributeNameField;
var newAttributeName = newTree[newStartIndex].AttributeNameField;
if (oldSeq == newSeq &&
string.Equals(oldAttributeName, newAttributeName, StringComparison.Ordinal))
@ -481,12 +481,12 @@ namespace Microsoft.AspNetCore.Components.RenderTree
// 3. iterate through the remaining attributes in the set and add them
for (var i = newStartIndex; i < newEndIndexExcl; i++)
{
diffContext.AttributeDiffSet[newTree[i].AttributeName] = i;
diffContext.AttributeDiffSet[newTree[i].AttributeNameField] = i;
}
for (var i = oldStartIndex; i < oldEndIndexExcl; i++)
{
var oldName = oldTree[i].AttributeName;
var oldName = oldTree[i].AttributeNameField;
if (diffContext.AttributeDiffSet.TryGetValue(oldName, out var matchIndex))
{
// Has a match in the new tree, look for a diff
@ -519,10 +519,11 @@ namespace Microsoft.AspNetCore.Components.RenderTree
var newTree = diffContext.NewTree;
ref var oldComponentFrame = ref oldTree[oldComponentIndex];
ref var newComponentFrame = ref newTree[newComponentIndex];
var componentState = oldComponentFrame.ComponentState;
var componentState = oldComponentFrame.ComponentStateField;
// Preserve the actual componentInstance
newComponentFrame = newComponentFrame.WithComponent(componentState);
newComponentFrame.ComponentStateField = componentState;
newComponentFrame.ComponentIdField = componentState.ComponentId;
// As an important rendering optimization, we want to skip parameter update
// notifications if we know for sure they haven't changed/mutated. The
@ -545,14 +546,14 @@ namespace Microsoft.AspNetCore.Components.RenderTree
private static int NextSiblingIndex(in RenderTreeFrame frame, int frameIndex)
{
switch (frame.FrameType)
switch (frame.FrameTypeField)
{
case RenderTreeFrameType.Component:
return frameIndex + frame.ComponentSubtreeLength;
return frameIndex + frame.ComponentSubtreeLengthField;
case RenderTreeFrameType.Element:
return frameIndex + frame.ElementSubtreeLength;
return frameIndex + frame.ElementSubtreeLengthField;
case RenderTreeFrameType.Region:
return frameIndex + frame.RegionSubtreeLength;
return frameIndex + frame.RegionSubtreeLengthField;
default:
return frameIndex + 1;
}
@ -570,8 +571,8 @@ namespace Microsoft.AspNetCore.Components.RenderTree
// This can't happen for sequence-matched frames from .razor components, but it can happen if you write your
// builder logic manually or if two dissimilar frames matched by key. Treat as completely unrelated.
var newFrameType = newFrame.FrameType;
if (oldFrame.FrameType != newFrameType)
var newFrameType = newFrame.FrameTypeField;
if (oldFrame.FrameTypeField != newFrameType)
{
InsertNewFrame(ref diffContext, newFrameIndex);
RemoveOldFrame(ref diffContext, oldFrameIndex);
@ -586,8 +587,8 @@ namespace Microsoft.AspNetCore.Components.RenderTree
{
case RenderTreeFrameType.Text:
{
var oldText = oldFrame.TextContent;
var newText = newFrame.TextContent;
var oldText = oldFrame.TextContentField;
var newText = newFrame.TextContentField;
if (!string.Equals(oldText, newText, StringComparison.Ordinal))
{
var referenceFrameIndex = diffContext.ReferenceFrames.Append(newFrame);
@ -599,8 +600,8 @@ namespace Microsoft.AspNetCore.Components.RenderTree
case RenderTreeFrameType.Markup:
{
var oldMarkup = oldFrame.MarkupContent;
var newMarkup = newFrame.MarkupContent;
var oldMarkup = oldFrame.MarkupContentField;
var newMarkup = newFrame.MarkupContentField;
if (!string.Equals(oldMarkup, newMarkup, StringComparison.Ordinal))
{
var referenceFrameIndex = diffContext.ReferenceFrames.Append(newFrame);
@ -612,8 +613,8 @@ namespace Microsoft.AspNetCore.Components.RenderTree
case RenderTreeFrameType.Element:
{
var oldElementName = oldFrame.ElementName;
var newElementName = newFrame.ElementName;
var oldElementName = oldFrame.ElementNameField;
var newElementName = newFrame.ElementNameField;
if (string.Equals(oldElementName, newElementName, StringComparison.Ordinal))
{
var oldFrameAttributesEndIndexExcl = GetAttributesEndIndexExclusive(oldTree, oldFrameIndex);
@ -626,8 +627,8 @@ namespace Microsoft.AspNetCore.Components.RenderTree
newFrameIndex + 1, newFrameAttributesEndIndexExcl);
// Diff the children
var oldFrameChildrenEndIndexExcl = oldFrameIndex + oldFrame.ElementSubtreeLength;
var newFrameChildrenEndIndexExcl = newFrameIndex + newFrame.ElementSubtreeLength;
var oldFrameChildrenEndIndexExcl = oldFrameIndex + oldFrame.ElementSubtreeLengthField;
var newFrameChildrenEndIndexExcl = newFrameIndex + newFrame.ElementSubtreeLengthField;
var hasChildrenToProcess =
oldFrameChildrenEndIndexExcl > oldFrameAttributesEndIndexExcl ||
newFrameChildrenEndIndexExcl > newFrameAttributesEndIndexExcl;
@ -661,14 +662,14 @@ namespace Microsoft.AspNetCore.Components.RenderTree
{
AppendDiffEntriesForRange(
ref diffContext,
oldFrameIndex + 1, oldFrameIndex + oldFrame.RegionSubtreeLength,
newFrameIndex + 1, newFrameIndex + newFrame.RegionSubtreeLength);
oldFrameIndex + 1, oldFrameIndex + oldFrame.RegionSubtreeLengthField,
newFrameIndex + 1, newFrameIndex + newFrame.RegionSubtreeLengthField);
break;
}
case RenderTreeFrameType.Component:
{
if (oldFrame.ComponentType == newFrame.ComponentType)
if (oldFrame.ComponentTypeField == newFrame.ComponentTypeField)
{
UpdateRetainedChildComponent(
ref diffContext,
@ -698,7 +699,7 @@ namespace Microsoft.AspNetCore.Components.RenderTree
// We don't handle attributes here, they have their own diff logic.
// See AppendDiffEntriesForAttributeFrame
default:
throw new NotImplementedException($"Encountered unsupported frame type during diffing: {newTree[newFrameIndex].FrameType}");
throw new NotImplementedException($"Encountered unsupported frame type during diffing: {newTree[newFrameIndex].FrameTypeField}");
}
}
@ -715,7 +716,7 @@ namespace Microsoft.AspNetCore.Components.RenderTree
ref var newFrame = ref newTree[newFrameIndex];
// Using Equals to account for string comparisons, nulls, etc.
var valueChanged = !Equals(oldFrame.AttributeValue, newFrame.AttributeValue);
var valueChanged = !Equals(oldFrame.AttributeValueField, newFrame.AttributeValueField);
if (valueChanged)
{
InitializeNewAttributeFrame(ref diffContext, ref newFrame);
@ -724,13 +725,13 @@ namespace Microsoft.AspNetCore.Components.RenderTree
// If we're replacing an old event handler ID with a new one, register the old one for disposal,
// plus keep track of the old->new chain until the old one is fully disposed
if (oldFrame.AttributeEventHandlerId > 0)
if (oldFrame.AttributeEventHandlerIdField > 0)
{
diffContext.Renderer.TrackReplacedEventHandlerId(oldFrame.AttributeEventHandlerId, newFrame.AttributeEventHandlerId);
diffContext.BatchBuilder.DisposedEventHandlerIds.Append(oldFrame.AttributeEventHandlerId);
diffContext.Renderer.TrackReplacedEventHandlerId(oldFrame.AttributeEventHandlerIdField, newFrame.AttributeEventHandlerIdField);
diffContext.BatchBuilder.DisposedEventHandlerIds.Append(oldFrame.AttributeEventHandlerIdField);
}
}
else if (oldFrame.AttributeEventHandlerId > 0)
else if (oldFrame.AttributeEventHandlerIdField > 0)
{
// Retain the event handler ID by copying the old frame over the new frame.
// this will prevent us from needing to dispose the old event handler
@ -743,7 +744,7 @@ namespace Microsoft.AspNetCore.Components.RenderTree
{
var newTree = diffContext.NewTree;
ref var newFrame = ref newTree[newFrameIndex];
switch (newFrame.FrameType)
switch (newFrame.FrameTypeField)
{
case RenderTreeFrameType.Attribute:
{
@ -756,7 +757,7 @@ namespace Microsoft.AspNetCore.Components.RenderTree
case RenderTreeFrameType.Element:
{
InitializeNewSubtree(ref diffContext, newFrameIndex);
var referenceFrameIndex = diffContext.ReferenceFrames.Append(newTree, newFrameIndex, newFrame.ElementSubtreeLength);
var referenceFrameIndex = diffContext.ReferenceFrames.Append(newTree, newFrameIndex, newFrame.ElementSubtreeLengthField);
diffContext.Edits.Append(RenderTreeEdit.PrependFrame(diffContext.SiblingIndex, referenceFrameIndex));
diffContext.SiblingIndex++;
break;
@ -764,7 +765,7 @@ namespace Microsoft.AspNetCore.Components.RenderTree
case RenderTreeFrameType.Region:
{
var regionChildFrameIndex = newFrameIndex + 1;
var regionChildFrameEndIndexExcl = newFrameIndex + newFrame.RegionSubtreeLength;
var regionChildFrameEndIndexExcl = newFrameIndex + newFrame.RegionSubtreeLengthField;
while (regionChildFrameIndex < regionChildFrameEndIndexExcl)
{
InsertNewFrame(ref diffContext, regionChildFrameIndex);
@ -791,7 +792,7 @@ namespace Microsoft.AspNetCore.Components.RenderTree
break;
}
default:
throw new NotImplementedException($"Unexpected frame type during {nameof(InsertNewFrame)}: {newFrame.FrameType}");
throw new NotImplementedException($"Unexpected frame type during {nameof(InsertNewFrame)}: {newFrame.FrameTypeField}");
}
}
@ -799,21 +800,21 @@ namespace Microsoft.AspNetCore.Components.RenderTree
{
var oldTree = diffContext.OldTree;
ref var oldFrame = ref oldTree[oldFrameIndex];
switch (oldFrame.FrameType)
switch (oldFrame.FrameTypeField)
{
case RenderTreeFrameType.Attribute:
{
diffContext.Edits.Append(RenderTreeEdit.RemoveAttribute(diffContext.SiblingIndex, oldFrame.AttributeName));
if (oldFrame.AttributeEventHandlerId > 0)
diffContext.Edits.Append(RenderTreeEdit.RemoveAttribute(diffContext.SiblingIndex, oldFrame.AttributeNameField));
if (oldFrame.AttributeEventHandlerIdField > 0)
{
diffContext.BatchBuilder.DisposedEventHandlerIds.Append(oldFrame.AttributeEventHandlerId);
diffContext.BatchBuilder.DisposedEventHandlerIds.Append(oldFrame.AttributeEventHandlerIdField);
}
break;
}
case RenderTreeFrameType.Component:
case RenderTreeFrameType.Element:
{
var endIndexExcl = oldFrameIndex + oldFrame.ElementSubtreeLength;
var endIndexExcl = oldFrameIndex + oldFrame.ElementSubtreeLengthField;
DisposeFramesInRange(diffContext.BatchBuilder, oldTree, oldFrameIndex, endIndexExcl);
diffContext.Edits.Append(RenderTreeEdit.RemoveFrame(diffContext.SiblingIndex));
break;
@ -821,7 +822,7 @@ namespace Microsoft.AspNetCore.Components.RenderTree
case RenderTreeFrameType.Region:
{
var regionChildFrameIndex = oldFrameIndex + 1;
var regionChildFrameEndIndexExcl = oldFrameIndex + oldFrame.RegionSubtreeLength;
var regionChildFrameEndIndexExcl = oldFrameIndex + oldFrame.RegionSubtreeLengthField;
while (regionChildFrameIndex < regionChildFrameEndIndexExcl)
{
RemoveOldFrame(ref diffContext, regionChildFrameIndex);
@ -836,17 +837,17 @@ namespace Microsoft.AspNetCore.Components.RenderTree
break;
}
default:
throw new NotImplementedException($"Unexpected frame type during {nameof(RemoveOldFrame)}: {oldFrame.FrameType}");
throw new NotImplementedException($"Unexpected frame type during {nameof(RemoveOldFrame)}: {oldFrame.FrameTypeField}");
}
}
private static int GetAttributesEndIndexExclusive(RenderTreeFrame[] tree, int rootIndex)
{
var descendantsEndIndexExcl = rootIndex + tree[rootIndex].ElementSubtreeLength;
var descendantsEndIndexExcl = rootIndex + tree[rootIndex].ElementSubtreeLengthField;
var index = rootIndex + 1;
for (; index < descendantsEndIndexExcl; index++)
{
if (tree[index].FrameType != RenderTreeFrameType.Attribute)
if (tree[index].FrameTypeField != RenderTreeFrameType.Attribute)
{
break;
}
@ -872,11 +873,11 @@ namespace Microsoft.AspNetCore.Components.RenderTree
private static void InitializeNewSubtree(ref DiffContext diffContext, int frameIndex)
{
var frames = diffContext.NewTree;
var endIndexExcl = frameIndex + frames[frameIndex].ElementSubtreeLength;
var endIndexExcl = frameIndex + frames[frameIndex].ElementSubtreeLengthField;
for (var i = frameIndex; i < endIndexExcl; i++)
{
ref var frame = ref frames[i];
switch (frame.FrameType)
switch (frame.FrameTypeField)
{
case RenderTreeFrameType.Component:
InitializeNewComponentFrame(ref diffContext, i);
@ -899,14 +900,14 @@ namespace Microsoft.AspNetCore.Components.RenderTree
var frames = diffContext.NewTree;
ref var frame = ref frames[frameIndex];
if (frame.ComponentState != null)
if (frame.ComponentStateField != null)
{
throw new InvalidOperationException($"Child component already exists during {nameof(InitializeNewComponentFrame)}");
}
var parentComponentId = diffContext.ComponentId;
diffContext.Renderer.InstantiateChildComponentOnFrame(ref frame, parentComponentId);
var childComponentState = frame.ComponentState;
var childComponentState = frame.ComponentStateField;
// Set initial parameters
var initialParametersLifetime = new ParameterViewLifetime(diffContext.BatchBuilder);
@ -920,9 +921,9 @@ namespace Microsoft.AspNetCore.Components.RenderTree
//
// We're following a simple heuristic here that's reflected in the ts runtime
// based on the common usage of attributes for DOM events.
if ((newFrame.AttributeValue is MulticastDelegate || newFrame.AttributeValue is EventCallback) &&
newFrame.AttributeName.Length >= 3 &&
newFrame.AttributeName.StartsWith("on", StringComparison.Ordinal))
if ((newFrame.AttributeValueField is MulticastDelegate || newFrame.AttributeValueField is EventCallback) &&
newFrame.AttributeNameField.Length >= 3 &&
newFrame.AttributeNameField.StartsWith("on", StringComparison.Ordinal))
{
diffContext.Renderer.AssignEventHandlerId(ref newFrame);
}
@ -931,14 +932,14 @@ namespace Microsoft.AspNetCore.Components.RenderTree
private static void InitializeNewElementReferenceCaptureFrame(ref DiffContext diffContext, ref RenderTreeFrame newFrame)
{
var newElementReference = ElementReference.CreateWithUniqueId(diffContext.Renderer.ElementReferenceContext);
newFrame = newFrame.WithElementReferenceCaptureId(newElementReference.Id);
newFrame.ElementReferenceCaptureAction(newElementReference);
newFrame.ElementReferenceCaptureIdField = newElementReference.Id;
newFrame.ElementReferenceCaptureActionField(newElementReference);
}
private static void InitializeNewComponentReferenceCaptureFrame(ref DiffContext diffContext, ref RenderTreeFrame newFrame)
{
ref var parentFrame = ref diffContext.NewTree[newFrame.ComponentReferenceCaptureParentFrameIndex];
if (parentFrame.FrameType != RenderTreeFrameType.Component)
ref var parentFrame = ref diffContext.NewTree[newFrame.ComponentReferenceCaptureParentFrameIndexField];
if (parentFrame.FrameTypeField != RenderTreeFrameType.Component)
{
// Should never happen, but will help with diagnosis if it does
throw new InvalidOperationException($"{nameof(RenderTreeFrameType.ComponentReferenceCapture)} frame references invalid parent index.");
@ -951,7 +952,7 @@ namespace Microsoft.AspNetCore.Components.RenderTree
throw new InvalidOperationException($"Trying to initialize {nameof(RenderTreeFrameType.ComponentReferenceCapture)} frame before parent component was assigned.");
}
newFrame.ComponentReferenceCaptureAction(componentInstance);
newFrame.ComponentReferenceCaptureActionField(componentInstance);
}
private static void DisposeFramesInRange(RenderBatchBuilder batchBuilder, RenderTreeFrame[] frames, int startIndex, int endIndexExcl)
@ -959,13 +960,13 @@ namespace Microsoft.AspNetCore.Components.RenderTree
for (var i = startIndex; i < endIndexExcl; i++)
{
ref var frame = ref frames[i];
if (frame.FrameType == RenderTreeFrameType.Component && frame.ComponentState != null)
if (frame.FrameTypeField == RenderTreeFrameType.Component && frame.ComponentStateField != null)
{
batchBuilder.ComponentDisposalQueue.Enqueue(frame.ComponentId);
batchBuilder.ComponentDisposalQueue.Enqueue(frame.ComponentIdField);
}
else if (frame.FrameType == RenderTreeFrameType.Attribute && frame.AttributeEventHandlerId > 0)
else if (frame.FrameTypeField == RenderTreeFrameType.Attribute && frame.AttributeEventHandlerIdField > 0)
{
batchBuilder.DisposedEventHandlerIds.Append(frame.AttributeEventHandlerId);
batchBuilder.DisposedEventHandlerIds.Append(frame.AttributeEventHandlerIdField);
}
}
}

View File

@ -22,7 +22,7 @@ namespace Microsoft.AspNetCore.Components.RenderTree
//
// Represents an entry in a tree of user interface (UI) items.
[StructLayout(LayoutKind.Explicit, Pack = 4)]
public readonly struct RenderTreeFrame
public struct RenderTreeFrame
{
// Note that the struct layout has to be valid in both 32-bit and 64-bit runtime platforms,
// which means that all reference-type fields need to take up 8 bytes (except for the last
@ -48,72 +48,86 @@ namespace Microsoft.AspNetCore.Components.RenderTree
// Common
// --------------------------------------------------------------------------------
[FieldOffset(0)] internal int SequenceField;
[FieldOffset(4)] internal RenderTreeFrameType FrameTypeField;
/// <summary>
/// Gets the sequence number of the frame. Sequence numbers indicate the relative source
/// positions of the instructions that inserted the frames. Sequence numbers are only
/// comparable within the same sequence (typically, the same source method).
/// </summary>
[FieldOffset(0)] public readonly int Sequence;
public int Sequence => SequenceField;
/// <summary>
/// Describes the type of this frame.
/// </summary>
[FieldOffset(4)] public readonly RenderTreeFrameType FrameType;
public RenderTreeFrameType FrameType => FrameTypeField;
// --------------------------------------------------------------------------------
// RenderTreeFrameType.Element
// --------------------------------------------------------------------------------
[FieldOffset(8)] internal int ElementSubtreeLengthField;
[FieldOffset(16)] internal string ElementNameField;
[FieldOffset(24)] internal object ElementKeyField;
/// <summary>
/// If the <see cref="FrameType"/> property equals <see cref="RenderTreeFrameType.Element"/>
/// gets the number of frames in the subtree for which this frame is the root.
/// The value is zero if the frame has not yet been closed.
/// </summary>
[FieldOffset(8)] public readonly int ElementSubtreeLength;
public int ElementSubtreeLength => ElementSubtreeLengthField;
/// <summary>
/// If the <see cref="FrameType"/> property equals <see cref="RenderTreeFrameType.Element"/>,
/// gets a name representing the type of the element. Otherwise, the value is undefined.
/// </summary>
[FieldOffset(16)] public readonly string ElementName;
public string ElementName => ElementNameField;
/// <summary>
/// If the <see cref="FrameType"/> property equals <see cref="RenderTreeFrameType.Element"/>,
/// gets the element's diffing key, or null if none was specified.
/// </summary>
[FieldOffset(24)] public readonly object ElementKey;
public object ElementKey => ElementKeyField;
// --------------------------------------------------------------------------------
// RenderTreeFrameType.Text
// --------------------------------------------------------------------------------
[FieldOffset(16)] internal string TextContentField;
/// <summary>
/// If the <see cref="FrameType"/> property equals <see cref="RenderTreeFrameType.Text"/>,
/// gets the content of the text frame. Otherwise, the value is undefined.
/// </summary>
[FieldOffset(16)] public readonly string TextContent;
public string TextContent => TextContentField;
// --------------------------------------------------------------------------------
// RenderTreeFrameType.Attribute
// --------------------------------------------------------------------------------
[FieldOffset(8)] internal ulong AttributeEventHandlerIdField;
[FieldOffset(16)] internal string AttributeNameField;
[FieldOffset(24)] internal object AttributeValueField;
[FieldOffset(32)] internal string AttributeEventUpdatesAttributeNameField;
/// <summary>
/// If the <see cref="FrameType"/> property equals <see cref="RenderTreeFrameType.Attribute"/>
/// gets the ID of the corresponding event handler, if any.
/// </summary>
[FieldOffset(8)] public readonly ulong AttributeEventHandlerId;
public ulong AttributeEventHandlerId => AttributeEventHandlerIdField;
/// <summary>
/// If the <see cref="FrameType"/> property equals <see cref="RenderTreeFrameType.Attribute"/>,
/// gets the attribute name. Otherwise, the value is undefined.
/// </summary>
[FieldOffset(16)] public readonly string AttributeName;
public string AttributeName => AttributeNameField;
/// <summary>
/// If the <see cref="FrameType"/> property equals <see cref="RenderTreeFrameType.Attribute"/>,
/// gets the attribute value. Otherwise, the value is undefined.
/// </summary>
[FieldOffset(24)] public readonly object AttributeValue;
public object AttributeValue => AttributeValueField;
/// <summary>
/// If the <see cref="FrameType"/> property equals <see cref="RenderTreeFrameType.Attribute"/>,
@ -121,80 +135,94 @@ namespace Microsoft.AspNetCore.Components.RenderTree
/// can be updated to represent the UI state prior to executing the event handler. This is
/// primarily used in two-way bindings.
/// </summary>
[FieldOffset(32)] public readonly string AttributeEventUpdatesAttributeName;
public string AttributeEventUpdatesAttributeName => AttributeEventUpdatesAttributeNameField;
// --------------------------------------------------------------------------------
// RenderTreeFrameType.Component
// --------------------------------------------------------------------------------
[FieldOffset(8)] internal int ComponentSubtreeLengthField;
[FieldOffset(12)] internal int ComponentIdField;
[FieldOffset(16)] internal Type ComponentTypeField;
[FieldOffset(24)] internal ComponentState ComponentStateField;
[FieldOffset(32)] internal object ComponentKeyField;
/// <summary>
/// If the <see cref="FrameType"/> property equals <see cref="RenderTreeFrameType.Component"/>
/// gets the number of frames in the subtree for which this frame is the root.
/// The value is zero if the frame has not yet been closed.
/// </summary>
[FieldOffset(8)] public readonly int ComponentSubtreeLength;
public int ComponentSubtreeLength => ComponentSubtreeLengthField;
/// <summary>
/// If the <see cref="FrameType"/> property equals <see cref="RenderTreeFrameType.Component"/>,
/// gets the child component instance identifier.
/// </summary>
[FieldOffset(12)] public readonly int ComponentId;
public int ComponentId => ComponentIdField;
/// <summary>
/// If the <see cref="FrameType"/> property equals <see cref="RenderTreeFrameType.Component"/>,
/// gets the type of the child component.
/// </summary>
[FieldOffset(16)] public readonly Type ComponentType;
public Type ComponentType => ComponentTypeField;
/// <summary>
/// If the <see cref="FrameType"/> property equals <see cref="RenderTreeFrameType.Component"/>,
/// gets the child component state object. Otherwise, the value is undefined.
/// </summary>
[FieldOffset(24)] internal readonly ComponentState ComponentState;
internal ComponentState ComponentState => ComponentStateField;
/// <summary>
/// If the <see cref="FrameType"/> property equals <see cref="RenderTreeFrameType.Component"/>,
/// gets the component's diffing key, or null if none was specified.
/// </summary>
[FieldOffset(32)] public readonly object ComponentKey;
public object ComponentKey => ComponentKeyField;
/// <summary>
/// If the <see cref="FrameType"/> property equals <see cref="RenderTreeFrameType.Component"/>,
/// gets the child component instance. Otherwise, the value is undefined.
/// </summary>
public IComponent Component => ComponentState?.Component;
public IComponent Component => ComponentStateField?.Component;
// --------------------------------------------------------------------------------
// RenderTreeFrameType.Region
// --------------------------------------------------------------------------------
[FieldOffset(8)] internal int RegionSubtreeLengthField;
/// <summary>
/// If the <see cref="FrameType"/> property equals <see cref="RenderTreeFrameType.Region"/>
/// gets the number of frames in the subtree for which this frame is the root.
/// The value is zero if the frame has not yet been closed.
/// </summary>
[FieldOffset(8)] public readonly int RegionSubtreeLength;
public int RegionSubtreeLength => RegionSubtreeLengthField;
// --------------------------------------------------------------------------------
// RenderTreeFrameType.ElementReferenceCapture
// --------------------------------------------------------------------------------
[FieldOffset(16)] internal string ElementReferenceCaptureIdField;
[FieldOffset(24)] internal Action<ElementReference> ElementReferenceCaptureActionField;
/// <summary>
/// If the <see cref="FrameType"/> property equals <see cref="RenderTreeFrameType.ElementReferenceCapture"/>,
/// gets the ID of the reference capture. Otherwise, the value is undefined.
/// </summary>
[FieldOffset(16)] public readonly string ElementReferenceCaptureId;
public string ElementReferenceCaptureId => ElementReferenceCaptureIdField;
/// <summary>
/// If the <see cref="FrameType"/> property equals <see cref="RenderTreeFrameType.ElementReferenceCapture"/>,
/// gets the action that writes the reference to its target. Otherwise, the value is undefined.
/// </summary>
[FieldOffset(24)] public readonly Action<ElementReference> ElementReferenceCaptureAction;
public Action<ElementReference> ElementReferenceCaptureAction => ElementReferenceCaptureActionField;
// --------------------------------------------------------------------------------
// RenderTreeFrameType.ComponentReferenceCapture
// --------------------------------------------------------------------------------
[FieldOffset(8)] internal int ComponentReferenceCaptureParentFrameIndexField;
[FieldOffset(16)] internal Action<object> ComponentReferenceCaptureActionField;
/// <summary>
/// If the <see cref="FrameType"/> property equals <see cref="RenderTreeFrameType.ComponentReferenceCapture"/>,
/// gets the index of the parent frame representing the component being captured. Otherwise, the value is undefined.
@ -205,49 +233,51 @@ namespace Microsoft.AspNetCore.Components.RenderTree
/// initialization logic in RenderTreeDiffBuilder to walk the frames hierarchically, then it would know
/// the parent index at the point where it wants to initialize the ComponentReferenceCapture frame.
/// </summary>
[FieldOffset(8)] public readonly int ComponentReferenceCaptureParentFrameIndex;
public int ComponentReferenceCaptureParentFrameIndex => ComponentReferenceCaptureParentFrameIndexField;
/// <summary>
/// If the <see cref="FrameType"/> property equals <see cref="RenderTreeFrameType.ComponentReferenceCapture"/>,
/// gets the action that writes the reference to its target. Otherwise, the value is undefined.
/// </summary>
[FieldOffset(16)] public readonly Action<object> ComponentReferenceCaptureAction;
public Action<object> ComponentReferenceCaptureAction => ComponentReferenceCaptureActionField;
// --------------------------------------------------------------------------------
// RenderTreeFrameType.Markup
// --------------------------------------------------------------------------------
[FieldOffset(16)] internal string MarkupContentField;
/// <summary>
/// If the <see cref="FrameType"/> property equals <see cref="RenderTreeFrameType.Markup"/>,
/// gets the content of the markup frame. Otherwise, the value is undefined.
/// </summary>
[FieldOffset(16)] public readonly string MarkupContent;
public string MarkupContent => MarkupContentField;
// Element constructor
private RenderTreeFrame(int sequence, int elementSubtreeLength, string elementName, object elementKey)
: this()
{
Sequence = sequence;
FrameType = RenderTreeFrameType.Element;
ElementSubtreeLength = elementSubtreeLength;
ElementName = elementName;
ElementKey = elementKey;
SequenceField = sequence;
FrameTypeField = RenderTreeFrameType.Element;
ElementSubtreeLengthField = elementSubtreeLength;
ElementNameField = elementName;
ElementKeyField = elementKey;
}
// Component constructor
private RenderTreeFrame(int sequence, int componentSubtreeLength, Type componentType, ComponentState componentState, object componentKey)
: this()
{
Sequence = sequence;
FrameType = RenderTreeFrameType.Component;
ComponentSubtreeLength = componentSubtreeLength;
ComponentType = componentType;
ComponentKey = componentKey;
SequenceField = sequence;
FrameTypeField = RenderTreeFrameType.Component;
ComponentSubtreeLengthField = componentSubtreeLength;
ComponentTypeField = componentType;
ComponentKeyField = componentKey;
if (componentState != null)
{
ComponentState = componentState;
ComponentId = componentState.ComponentId;
ComponentStateField = componentState;
ComponentIdField = componentState.ComponentId;
}
}
@ -255,25 +285,25 @@ namespace Microsoft.AspNetCore.Components.RenderTree
private RenderTreeFrame(int sequence, int regionSubtreeLength)
: this()
{
Sequence = sequence;
FrameType = RenderTreeFrameType.Region;
RegionSubtreeLength = regionSubtreeLength;
SequenceField = sequence;
FrameTypeField = RenderTreeFrameType.Region;
RegionSubtreeLengthField = regionSubtreeLength;
}
// Text/markup constructor
private RenderTreeFrame(int sequence, bool isMarkup, string textOrMarkup)
: this()
{
Sequence = sequence;
SequenceField = sequence;
if (isMarkup)
{
FrameType = RenderTreeFrameType.Markup;
MarkupContent = textOrMarkup;
FrameTypeField = RenderTreeFrameType.Markup;
MarkupContentField = textOrMarkup;
}
else
{
FrameType = RenderTreeFrameType.Text;
TextContent = textOrMarkup;
FrameTypeField = RenderTreeFrameType.Text;
TextContentField = textOrMarkup;
}
}
@ -281,32 +311,32 @@ namespace Microsoft.AspNetCore.Components.RenderTree
private RenderTreeFrame(int sequence, string attributeName, object attributeValue, ulong attributeEventHandlerId, string attributeEventUpdatesAttributeName)
: this()
{
FrameType = RenderTreeFrameType.Attribute;
Sequence = sequence;
AttributeName = attributeName;
AttributeValue = attributeValue;
AttributeEventHandlerId = attributeEventHandlerId;
AttributeEventUpdatesAttributeName = attributeEventUpdatesAttributeName;
FrameTypeField = RenderTreeFrameType.Attribute;
SequenceField = sequence;
AttributeNameField = attributeName;
AttributeValueField = attributeValue;
AttributeEventHandlerIdField = attributeEventHandlerId;
AttributeEventUpdatesAttributeNameField = attributeEventUpdatesAttributeName;
}
// Element reference capture constructor
private RenderTreeFrame(int sequence, Action<ElementReference> elementReferenceCaptureAction, string elementReferenceCaptureId)
: this()
{
FrameType = RenderTreeFrameType.ElementReferenceCapture;
Sequence = sequence;
ElementReferenceCaptureAction = elementReferenceCaptureAction;
ElementReferenceCaptureId = elementReferenceCaptureId;
FrameTypeField = RenderTreeFrameType.ElementReferenceCapture;
SequenceField = sequence;
ElementReferenceCaptureActionField = elementReferenceCaptureAction;
ElementReferenceCaptureIdField = elementReferenceCaptureId;
}
// Component reference capture constructor
private RenderTreeFrame(int sequence, Action<object> componentReferenceCaptureAction, int parentFrameIndex)
: this()
{
FrameType = RenderTreeFrameType.ComponentReferenceCapture;
Sequence = sequence;
ComponentReferenceCaptureAction = componentReferenceCaptureAction;
ComponentReferenceCaptureParentFrameIndex = parentFrameIndex;
FrameTypeField = RenderTreeFrameType.ComponentReferenceCapture;
SequenceField = sequence;
ComponentReferenceCaptureActionField = componentReferenceCaptureAction;
ComponentReferenceCaptureParentFrameIndexField = parentFrameIndex;
}
internal static RenderTreeFrame Element(int sequence, string elementName)
@ -337,61 +367,61 @@ namespace Microsoft.AspNetCore.Components.RenderTree
=> new RenderTreeFrame(sequence, componentReferenceCaptureAction: componentReferenceCaptureAction, parentFrameIndex: parentFrameIndex);
internal RenderTreeFrame WithElementSubtreeLength(int elementSubtreeLength)
=> new RenderTreeFrame(Sequence, elementSubtreeLength: elementSubtreeLength, ElementName, ElementKey);
=> new RenderTreeFrame(SequenceField, elementSubtreeLength: elementSubtreeLength, ElementNameField, ElementKeyField);
internal RenderTreeFrame WithComponentSubtreeLength(int componentSubtreeLength)
=> new RenderTreeFrame(Sequence, componentSubtreeLength: componentSubtreeLength, ComponentType, ComponentState, ComponentKey);
=> new RenderTreeFrame(SequenceField, componentSubtreeLength: componentSubtreeLength, ComponentTypeField, ComponentStateField, ComponentKeyField);
internal RenderTreeFrame WithAttributeSequence(int sequence)
=> new RenderTreeFrame(sequence, attributeName: AttributeName, AttributeValue, AttributeEventHandlerId, AttributeEventUpdatesAttributeName);
=> new RenderTreeFrame(sequence, attributeName: AttributeNameField, AttributeValueField, AttributeEventHandlerIdField, AttributeEventUpdatesAttributeNameField);
internal RenderTreeFrame WithComponent(ComponentState componentState)
=> new RenderTreeFrame(Sequence, componentSubtreeLength: ComponentSubtreeLength, ComponentType, componentState, ComponentKey);
=> new RenderTreeFrame(SequenceField, componentSubtreeLength: ComponentSubtreeLengthField, ComponentTypeField, componentState, ComponentKeyField);
internal RenderTreeFrame WithAttributeEventHandlerId(ulong eventHandlerId)
=> new RenderTreeFrame(Sequence, attributeName: AttributeName, AttributeValue, eventHandlerId, AttributeEventUpdatesAttributeName);
=> new RenderTreeFrame(SequenceField, attributeName: AttributeNameField, AttributeValueField, eventHandlerId, AttributeEventUpdatesAttributeNameField);
internal RenderTreeFrame WithAttributeValue(object attributeValue)
=> new RenderTreeFrame(Sequence, attributeName: AttributeName, attributeValue, AttributeEventHandlerId, AttributeEventUpdatesAttributeName);
=> new RenderTreeFrame(SequenceField, attributeName: AttributeNameField, attributeValue, AttributeEventHandlerIdField, AttributeEventUpdatesAttributeNameField);
internal RenderTreeFrame WithAttributeEventUpdatesAttributeName(string attributeUpdatesAttributeName)
=> new RenderTreeFrame(Sequence, attributeName: AttributeName, AttributeValue, AttributeEventHandlerId, attributeUpdatesAttributeName);
=> new RenderTreeFrame(SequenceField, attributeName: AttributeNameField, AttributeValueField, AttributeEventHandlerIdField, attributeUpdatesAttributeName);
internal RenderTreeFrame WithRegionSubtreeLength(int regionSubtreeLength)
=> new RenderTreeFrame(Sequence, regionSubtreeLength: regionSubtreeLength);
=> new RenderTreeFrame(SequenceField, regionSubtreeLength: regionSubtreeLength);
internal RenderTreeFrame WithElementReferenceCaptureId(string elementReferenceCaptureId)
=> new RenderTreeFrame(Sequence, elementReferenceCaptureAction: ElementReferenceCaptureAction, elementReferenceCaptureId);
=> new RenderTreeFrame(SequenceField, elementReferenceCaptureAction: ElementReferenceCaptureActionField, elementReferenceCaptureId);
internal RenderTreeFrame WithElementKey(object elementKey)
=> new RenderTreeFrame(Sequence, elementSubtreeLength: ElementSubtreeLength, ElementName, elementKey);
=> new RenderTreeFrame(SequenceField, elementSubtreeLength: ElementSubtreeLengthField, ElementNameField, elementKey);
internal RenderTreeFrame WithComponentKey(object componentKey)
=> new RenderTreeFrame(Sequence, componentSubtreeLength: ComponentSubtreeLength, ComponentType, ComponentState, componentKey);
=> new RenderTreeFrame(SequenceField, componentSubtreeLength: ComponentSubtreeLengthField, ComponentTypeField, ComponentStateField, componentKey);
/// <inheritdoc />
// Just to be nice for debugging and unit tests.
public override string ToString()
{
switch (FrameType)
switch (FrameTypeField)
{
case RenderTreeFrameType.Attribute:
return $"Attribute: (seq={Sequence}, id={AttributeEventHandlerId}) '{AttributeName}'='{AttributeValue}'";
case RenderTreeFrameType.Component:
return $"Component: (seq={Sequence}, key={ComponentKey ?? "(none)"}, len={ComponentSubtreeLength}) {ComponentType}";
return $"Component: (seq={Sequence}, key={ComponentKeyField ?? "(none)"}, len={ComponentSubtreeLength}) {ComponentType}";
case RenderTreeFrameType.Element:
return $"Element: (seq={Sequence}, key={ElementKey ?? "(none)"}, len={ElementSubtreeLength}) {ElementName}";
return $"Element: (seq={Sequence}, key={ElementKeyField ?? "(none)"}, len={ElementSubtreeLength}) {ElementName}";
case RenderTreeFrameType.Region:
return $"Region: (seq={Sequence}, len={RegionSubtreeLength})";
case RenderTreeFrameType.Text:
return $"Text: (seq={Sequence}, len=n/a) {EscapeNewlines(TextContent)}";
return $"Text: (seq={Sequence}, len=n/a) {EscapeNewlines(TextContentField)}";
case RenderTreeFrameType.Markup:
return $"Markup: (seq={Sequence}, len=n/a) {EscapeNewlines(TextContent)}";
return $"Markup: (seq={Sequence}, len=n/a) {EscapeNewlines(TextContentField)}";
case RenderTreeFrameType.ElementReferenceCapture:
return $"ElementReferenceCapture: (seq={Sequence}, len=n/a) {ElementReferenceCaptureAction}";

View File

@ -0,0 +1,137 @@
// 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.Components.RenderTree
{
/// <summary>
/// A special subclass of <see cref="ArrayBuilder{T}"/> that contains methods optimized for appending <see cref="RenderTreeFrame"/> entries.
/// </summary>
internal class RenderTreeFrameArrayBuilder : ArrayBuilder<RenderTreeFrame>
{
// You may notice a repeated block at the top of each of these methods. This is intentionally inlined into each
// method because doing so improves intensive rendering scenarios by around 1% (based on the FastGrid benchmark).
public void AppendElement(int sequence, string elementName)
{
if (_itemsInUse == _items.Length)
{
GrowBuffer(_items.Length * 2);
}
_items[_itemsInUse++] = new RenderTreeFrame
{
SequenceField = sequence,
FrameTypeField = RenderTreeFrameType.Element,
ElementNameField = elementName,
};
}
public void AppendText(int sequence, string textContent)
{
if (_itemsInUse == _items.Length)
{
GrowBuffer(_items.Length * 2);
}
_items[_itemsInUse++] = new RenderTreeFrame
{
SequenceField = sequence,
FrameTypeField = RenderTreeFrameType.Text,
TextContentField = textContent,
};
}
public void AppendMarkup(int sequence, string markupContent)
{
if (_itemsInUse == _items.Length)
{
GrowBuffer(_items.Length * 2);
}
_items[_itemsInUse++] = new RenderTreeFrame
{
SequenceField = sequence,
FrameTypeField = RenderTreeFrameType.Markup,
MarkupContentField = markupContent,
};
}
public void AppendAttribute(int sequence, string attributeName, object? attributeValue)
{
if (_itemsInUse == _items.Length)
{
GrowBuffer(_items.Length * 2);
}
_items[_itemsInUse++] = new RenderTreeFrame
{
SequenceField = sequence,
FrameTypeField = RenderTreeFrameType.Attribute,
AttributeNameField = attributeName,
AttributeValueField = attributeValue,
};
}
public void AppendComponent(int sequence, Type componentType)
{
if (_itemsInUse == _items.Length)
{
GrowBuffer(_items.Length * 2);
}
_items[_itemsInUse++] = new RenderTreeFrame
{
SequenceField = sequence,
FrameTypeField = RenderTreeFrameType.Component,
ComponentTypeField = componentType,
};
}
public void AppendElementReferenceCapture(int sequence, Action<ElementReference> elementReferenceCaptureAction)
{
if (_itemsInUse == _items.Length)
{
GrowBuffer(_items.Length * 2);
}
_items[_itemsInUse++] = new RenderTreeFrame
{
SequenceField = sequence,
FrameTypeField = RenderTreeFrameType.ElementReferenceCapture,
ElementReferenceCaptureActionField = elementReferenceCaptureAction,
};
}
public void AppendComponentReferenceCapture(int sequence, Action<object?> componentReferenceCaptureAction, int parentFrameIndexValue)
{
if (_itemsInUse == _items.Length)
{
GrowBuffer(_items.Length * 2);
}
_items[_itemsInUse++] = new RenderTreeFrame
{
SequenceField = sequence,
FrameTypeField = RenderTreeFrameType.ComponentReferenceCapture,
ComponentReferenceCaptureActionField = componentReferenceCaptureAction,
ComponentReferenceCaptureParentFrameIndexField = parentFrameIndexValue,
};
}
public void AppendRegion(int sequence)
{
if (_itemsInUse == _items.Length)
{
GrowBuffer(_items.Length * 2);
}
_items[_itemsInUse++] = new RenderTreeFrame
{
SequenceField = sequence,
FrameTypeField = RenderTreeFrameType.Region,
};
}
}
}

View File

@ -292,19 +292,20 @@ namespace Microsoft.AspNetCore.Components.RenderTree
internal void InstantiateChildComponentOnFrame(ref RenderTreeFrame frame, int parentComponentId)
{
if (frame.FrameType != RenderTreeFrameType.Component)
if (frame.FrameTypeField != RenderTreeFrameType.Component)
{
throw new ArgumentException($"The frame's {nameof(RenderTreeFrame.FrameType)} property must equal {RenderTreeFrameType.Component}", nameof(frame));
}
if (frame.ComponentState != null)
if (frame.ComponentStateField != null)
{
throw new ArgumentException($"The frame already has a non-null component instance", nameof(frame));
}
var newComponent = InstantiateComponent(frame.ComponentType);
var newComponent = InstantiateComponent(frame.ComponentTypeField);
var newComponentState = AttachAndInitComponent(newComponent, parentComponentId);
frame = frame.WithComponent(newComponentState);
frame.ComponentStateField = newComponentState;
frame.ComponentIdField = newComponentState.ComponentId;
}
internal void AddToPendingTasks(Task task)
@ -342,7 +343,7 @@ namespace Microsoft.AspNetCore.Components.RenderTree
{
var id = ++_lastEventHandlerId;
if (frame.AttributeValue is EventCallback callback)
if (frame.AttributeValueField is EventCallback callback)
{
// We hit this case when a EventCallback object is produced that needs an explicit receiver.
// Common cases for this are "chained bind" or "chained event handler" when a component
@ -352,7 +353,7 @@ namespace Microsoft.AspNetCore.Components.RenderTree
// the receiver.
_eventBindings.Add(id, callback);
}
else if (frame.AttributeValue is MulticastDelegate @delegate)
else if (frame.AttributeValueField is MulticastDelegate @delegate)
{
// This is the common case for a delegate, where the receiver of the event
// is the same as delegate.Target. In this case since the receiver is implicit we can
@ -364,7 +365,7 @@ namespace Microsoft.AspNetCore.Components.RenderTree
// NOTE: we do not to handle EventCallback<T> here. EventCallback<T> is only used when passing
// a callback to a component, and never when used to attaching a DOM event handler.
frame = frame.WithAttributeEventHandlerId(id);
frame.AttributeEventHandlerIdField = id;
}
/// <summary>

View File

@ -23,7 +23,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
private readonly static object BoxedFalse = false;
private readonly static string ComponentReferenceCaptureInvalidParentMessage = $"Component reference captures may only be added as children of frames of type {RenderTreeFrameType.Component}";
private readonly ArrayBuilder<RenderTreeFrame> _entries = new ArrayBuilder<RenderTreeFrame>();
private readonly RenderTreeFrameArrayBuilder _entries = new RenderTreeFrameArrayBuilder();
private readonly Stack<int> _openElementIndices = new Stack<int>();
private RenderTreeFrameType? _lastNonAttributeFrameType;
private bool _hasSeenAddMultipleAttributes;
@ -53,7 +53,8 @@ namespace Microsoft.AspNetCore.Components.Rendering
}
_openElementIndices.Push(_entries.Count);
Append(RenderTreeFrame.Element(sequence, elementName));
_entries.AppendElement(sequence, elementName);
_lastNonAttributeFrameType = RenderTreeFrameType.Element;
}
/// <summary>
@ -71,8 +72,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
ProcessDuplicateAttributes(first: indexOfEntryBeingClosed + 1);
}
ref var entry = ref _entries.Buffer[indexOfEntryBeingClosed];
entry = entry.WithElementSubtreeLength(_entries.Count - indexOfEntryBeingClosed);
_entries.Buffer[indexOfEntryBeingClosed].ElementSubtreeLengthField = _entries.Count - indexOfEntryBeingClosed;
}
/// <summary>
@ -82,7 +82,8 @@ namespace Microsoft.AspNetCore.Components.Rendering
/// <param name="markupContent">Content for the new markup frame.</param>
public void AddMarkupContent(int sequence, string? markupContent)
{
Append(RenderTreeFrame.Markup(sequence, markupContent ?? string.Empty));
_entries.AppendMarkup(sequence, markupContent ?? string.Empty);
_lastNonAttributeFrameType = RenderTreeFrameType.Markup;
}
/// <summary>
@ -92,7 +93,8 @@ namespace Microsoft.AspNetCore.Components.Rendering
/// <param name="textContent">Content for the new text frame.</param>
public void AddContent(int sequence, string? textContent)
{
Append(RenderTreeFrame.Text(sequence, textContent ?? string.Empty));
_entries.AppendText(sequence, textContent ?? string.Empty);
_lastNonAttributeFrameType = RenderTreeFrameType.Text;
}
/// <summary>
@ -161,7 +163,8 @@ namespace Microsoft.AspNetCore.Components.Rendering
throw new InvalidOperationException($"Valueless attributes may only be added immediately after frames of type {RenderTreeFrameType.Element}");
}
Append(RenderTreeFrame.Attribute(sequence, name, BoxedTrue));
_entries.AppendAttribute(sequence, name, BoxedTrue);
}
/// <summary>
@ -181,13 +184,13 @@ namespace Microsoft.AspNetCore.Components.Rendering
AssertCanAddAttribute();
if (_lastNonAttributeFrameType == RenderTreeFrameType.Component)
{
Append(RenderTreeFrame.Attribute(sequence, name, value ? BoxedTrue : BoxedFalse));
_entries.AppendAttribute(sequence, name, value ? BoxedTrue : BoxedFalse);
}
else if (value)
{
// Don't add 'false' attributes for elements. We want booleans to map to the presence
// or absence of an attribute, and false => "False" which isn't falsy in js.
Append(RenderTreeFrame.Attribute(sequence, name, BoxedTrue));
_entries.AppendAttribute(sequence, name, BoxedTrue);
}
else
{
@ -212,7 +215,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
AssertCanAddAttribute();
if (value != null || _lastNonAttributeFrameType == RenderTreeFrameType.Component)
{
Append(RenderTreeFrame.Attribute(sequence, name, value));
_entries.AppendAttribute(sequence, name, value);
}
else
{
@ -237,7 +240,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
AssertCanAddAttribute();
if (value != null || _lastNonAttributeFrameType == RenderTreeFrameType.Component)
{
Append(RenderTreeFrame.Attribute(sequence, name, value));
_entries.AppendAttribute(sequence, name, value);
}
else
{
@ -268,19 +271,19 @@ namespace Microsoft.AspNetCore.Components.Rendering
{
// Since this is a component, we need to preserve the type of the EventCallback, so we have
// to box.
Append(RenderTreeFrame.Attribute(sequence, name, (object)value));
_entries.AppendAttribute(sequence, name, value);
}
else if (value.RequiresExplicitReceiver)
{
// If we need to preserve the receiver, we just box the EventCallback
// so we can get it out on the other side.
Append(RenderTreeFrame.Attribute(sequence, name, (object)value));
_entries.AppendAttribute(sequence, name, value);
}
else if (value.HasDelegate)
{
// In the common case the receiver is also the delegate's target, so we
// just need to retain the delegate. This allows us to avoid an allocation.
Append(RenderTreeFrame.Attribute(sequence, name, value.Delegate));
_entries.AppendAttribute(sequence, name, value.Delegate);
}
else
{
@ -312,19 +315,19 @@ namespace Microsoft.AspNetCore.Components.Rendering
{
// Since this is a component, we need to preserve the type of the EventCallback, so we have
// to box.
Append(RenderTreeFrame.Attribute(sequence, name, (object)value));
_entries.AppendAttribute(sequence, name, value);
}
else if (value.RequiresExplicitReceiver)
{
// If we need to preserve the receiver - we convert this to an untyped EventCallback. We don't
// need to preserve the type of an EventCallback<T> when it's invoked from the DOM.
Append(RenderTreeFrame.Attribute(sequence, name, (object)value.AsUntyped()));
_entries.AppendAttribute(sequence, name, value.AsUntyped());
}
else if (value.HasDelegate)
{
// In the common case the receiver is also the delegate's target, so we
// just need to retain the delegate. This allows us to avoid an allocation.
Append(RenderTreeFrame.Attribute(sequence, name, value.Delegate));
_entries.AppendAttribute(sequence, name, value.Delegate);
}
else
{
@ -357,7 +360,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
{
if (boolValue)
{
Append(RenderTreeFrame.Attribute(sequence, name, BoxedTrue));
_entries.AppendAttribute(sequence, name, BoxedTrue);
}
else
{
@ -369,7 +372,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
{
if (callbackValue.HasDelegate)
{
Append(RenderTreeFrame.Attribute(sequence, name, callbackValue.UnpackForRenderTree()));
_entries.AppendAttribute(sequence, name, callbackValue.UnpackForRenderTree());
}
else
{
@ -378,18 +381,18 @@ namespace Microsoft.AspNetCore.Components.Rendering
}
else if (value is MulticastDelegate)
{
Append(RenderTreeFrame.Attribute(sequence, name, value));
_entries.AppendAttribute(sequence, name, value);
}
else
{
// The value is either a string, or should be treated as a string.
Append(RenderTreeFrame.Attribute(sequence, name, value.ToString()));
_entries.AppendAttribute(sequence, name, value.ToString());
}
}
else if (_lastNonAttributeFrameType == RenderTreeFrameType.Component)
{
// If this is a component, we always want to preserve the original type.
Append(RenderTreeFrame.Attribute(sequence, name, value));
_entries.AppendAttribute(sequence, name, value);
}
else
{
@ -408,15 +411,16 @@ namespace Microsoft.AspNetCore.Components.Rendering
/// </summary>
/// <param name="sequence">An integer that represents the position of the instruction in the source code.</param>
/// <param name="frame">A <see cref="RenderTreeFrame"/> holding the name and value of the attribute.</param>
public void AddAttribute(int sequence, in RenderTreeFrame frame)
public void AddAttribute(int sequence, RenderTreeFrame frame)
{
if (frame.FrameType != RenderTreeFrameType.Attribute)
if (frame.FrameTypeField != RenderTreeFrameType.Attribute)
{
throw new ArgumentException($"The {nameof(frame.FrameType)} must be {RenderTreeFrameType.Attribute}.");
}
AssertCanAddAttribute();
Append(frame.WithAttributeSequence(sequence));
frame.SequenceField = sequence;
_entries.Append(frame);
}
/// <summary>
@ -464,12 +468,12 @@ namespace Microsoft.AspNetCore.Components.Rendering
}
ref var prevFrame = ref _entries.Buffer[_entries.Count - 1];
if (prevFrame.FrameType != RenderTreeFrameType.Attribute)
if (prevFrame.FrameTypeField != RenderTreeFrameType.Attribute)
{
throw new InvalidOperationException($"Incorrect frame type: '{prevFrame.FrameType}'");
throw new InvalidOperationException($"Incorrect frame type: '{prevFrame.FrameTypeField}'");
}
prevFrame = prevFrame.WithAttributeEventUpdatesAttributeName(updatesAttributeName);
prevFrame.AttributeEventUpdatesAttributeNameField = updatesAttributeName;
}
/// <summary>
@ -516,16 +520,16 @@ namespace Microsoft.AspNetCore.Components.Rendering
var parentFrameIndexValue = parentFrameIndex.Value;
ref var parentFrame = ref _entries.Buffer[parentFrameIndexValue];
switch (parentFrame.FrameType)
switch (parentFrame.FrameTypeField)
{
case RenderTreeFrameType.Element:
parentFrame = parentFrame.WithElementKey(value); // It's a ref var, so this writes to the array
parentFrame.ElementKeyField = value; // It's a ref var, so this writes to the array
break;
case RenderTreeFrameType.Component:
parentFrame = parentFrame.WithComponentKey(value); // It's a ref var, so this writes to the array
parentFrame.ComponentKeyField = value; // It's a ref var, so this writes to the array
break;
default:
throw new InvalidOperationException($"Cannot set a key on a frame of type {parentFrame.FrameType}.");
throw new InvalidOperationException($"Cannot set a key on a frame of type {parentFrame.FrameTypeField}.");
}
}
@ -540,7 +544,8 @@ namespace Microsoft.AspNetCore.Components.Rendering
}
_openElementIndices.Push(_entries.Count);
Append(RenderTreeFrame.ChildComponent(sequence, componentType));
_entries.AppendComponent(sequence, componentType);
_lastNonAttributeFrameType = RenderTreeFrameType.Component;
}
/// <summary>
@ -558,8 +563,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
ProcessDuplicateAttributes(first: indexOfEntryBeingClosed + 1);
}
ref var entry = ref _entries.Buffer[indexOfEntryBeingClosed];
entry = entry.WithComponentSubtreeLength(_entries.Count - indexOfEntryBeingClosed);
_entries.Buffer[indexOfEntryBeingClosed].ComponentSubtreeLengthField = _entries.Count - indexOfEntryBeingClosed;
}
/// <summary>
@ -574,7 +578,8 @@ namespace Microsoft.AspNetCore.Components.Rendering
throw new InvalidOperationException($"Element reference captures may only be added as children of frames of type {RenderTreeFrameType.Element}");
}
Append(RenderTreeFrame.ElementReferenceCapture(sequence, elementReferenceCaptureAction));
_entries.AppendElementReferenceCapture(sequence, elementReferenceCaptureAction);
_lastNonAttributeFrameType = RenderTreeFrameType.ElementReferenceCapture;
}
/// <summary>
@ -591,12 +596,13 @@ namespace Microsoft.AspNetCore.Components.Rendering
}
var parentFrameIndexValue = parentFrameIndex.Value;
if (_entries.Buffer[parentFrameIndexValue].FrameType != RenderTreeFrameType.Component)
if (_entries.Buffer[parentFrameIndexValue].FrameTypeField != RenderTreeFrameType.Component)
{
throw new InvalidOperationException(ComponentReferenceCaptureInvalidParentMessage);
}
Append(RenderTreeFrame.ComponentReferenceCapture(sequence, componentReferenceCaptureAction, parentFrameIndexValue));
_entries.AppendComponentReferenceCapture(sequence, componentReferenceCaptureAction, parentFrameIndexValue);
_lastNonAttributeFrameType = RenderTreeFrameType.ComponentReferenceCapture;
}
/// <summary>
@ -614,7 +620,8 @@ namespace Microsoft.AspNetCore.Components.Rendering
}
_openElementIndices.Push(_entries.Count);
Append(RenderTreeFrame.Region(sequence));
_entries.AppendRegion(sequence);
_lastNonAttributeFrameType = RenderTreeFrameType.Region;
}
/// <summary>
@ -624,8 +631,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
public void CloseRegion()
{
var indexOfEntryBeingClosed = _openElementIndices.Pop();
ref var entry = ref _entries.Buffer[indexOfEntryBeingClosed];
entry = entry.WithRegionSubtreeLength(_entries.Count - indexOfEntryBeingClosed);
_entries.Buffer[indexOfEntryBeingClosed].RegionSubtreeLengthField = _entries.Count - indexOfEntryBeingClosed;
}
private void AssertCanAddAttribute()
@ -644,7 +650,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
{
var parentIndex = GetCurrentParentFrameIndex();
return parentIndex.HasValue
? _entries.Buffer[parentIndex.Value].FrameType
? _entries.Buffer[parentIndex.Value].FrameTypeField
: (RenderTreeFrameType?)null;
}
@ -680,17 +686,6 @@ namespace Microsoft.AspNetCore.Components.Rendering
public ArrayRange<RenderTreeFrame> GetFrames() =>
_entries.ToRange();
private void Append(in RenderTreeFrame frame)
{
var frameType = frame.FrameType;
_entries.Append(frame);
if (frameType != RenderTreeFrameType.Attribute)
{
_lastNonAttributeFrameType = frame.FrameType;
}
}
// Internal for testing
internal void ProcessDuplicateAttributes(int first)
{
@ -704,7 +699,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
for (var i = first; i <= last; i++)
{
if (buffer[i].FrameType != RenderTreeFrameType.Attribute)
if (buffer[i].FrameTypeField != RenderTreeFrameType.Attribute)
{
last = i - 1;
break;
@ -716,12 +711,12 @@ namespace Microsoft.AspNetCore.Components.Rendering
for (var i = last; i >= first; i--)
{
ref var frame = ref buffer[i];
Debug.Assert(frame.FrameType == RenderTreeFrameType.Attribute, $"Frame type is {frame.FrameType} at {i}");
Debug.Assert(frame.FrameTypeField == RenderTreeFrameType.Attribute, $"Frame type is {frame.FrameTypeField} at {i}");
if (!seenAttributeNames.TryGetValue(frame.AttributeName, out var index))
if (!seenAttributeNames.TryGetValue(frame.AttributeNameField, out var index))
{
// This is the first time seeing this attribute name. Add to the dictionary and move on.
seenAttributeNames.Add(frame.AttributeName, i);
seenAttributeNames.Add(frame.AttributeNameField, i);
}
else if (index < i)
{
@ -729,7 +724,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
// This is the case for a null event handler, or bool false value.
//
// We need to update our tracking, in case the attribute appeared 3 or more times.
seenAttributeNames[frame.AttributeName] = i;
seenAttributeNames[frame.AttributeNameField] = i;
}
else if (index > i)
{
@ -760,7 +755,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
for (var i = first; i < _entries.Count; i++)
{
ref var frame = ref buffer[i];
if (frame.FrameType != RenderTreeFrameType.None)
if (frame.FrameTypeField != RenderTreeFrameType.None)
{
buffer[offset++] = frame;
}

View File

@ -24,21 +24,21 @@ namespace Microsoft.AspNetCore.Components.Rendering
for (var frameIndex = 0; frameIndex < framesLength; frameIndex++)
{
ref var frame = ref framesArray[frameIndex];
switch (frame.FrameType)
switch (frame.FrameTypeField)
{
case RenderTreeFrameType.Element:
closestElementFrameIndex = frameIndex;
break;
case RenderTreeFrameType.Attribute:
if (frame.AttributeEventHandlerId == eventHandlerId)
if (frame.AttributeEventHandlerIdField == eventHandlerId)
{
if (!string.IsNullOrEmpty(frame.AttributeEventUpdatesAttributeName))
if (!string.IsNullOrEmpty(frame.AttributeEventUpdatesAttributeNameField))
{
UpdateFrameToMatchClientState(
renderTreeBuilder,
framesArray,
closestElementFrameIndex,
frame.AttributeEventUpdatesAttributeName,
frame.AttributeEventUpdatesAttributeNameField,
newFieldValue);
}
@ -55,20 +55,20 @@ namespace Microsoft.AspNetCore.Components.Rendering
{
// Find the attribute frame
ref var elementFrame = ref framesArray[elementFrameIndex];
var elementSubtreeEndIndexExcl = elementFrameIndex + elementFrame.ElementSubtreeLength;
var elementSubtreeEndIndexExcl = elementFrameIndex + elementFrame.ElementSubtreeLengthField;
for (var attributeFrameIndex = elementFrameIndex + 1; attributeFrameIndex < elementSubtreeEndIndexExcl; attributeFrameIndex++)
{
ref var attributeFrame = ref framesArray[attributeFrameIndex];
if (attributeFrame.FrameType != RenderTreeFrameType.Attribute)
if (attributeFrame.FrameTypeField != RenderTreeFrameType.Attribute)
{
// We're now looking at the descendants not attributes, so the search is over
break;
}
if (attributeFrame.AttributeName == attributeName)
if (attributeFrame.AttributeNameField == attributeName)
{
// Found an existing attribute we can update
attributeFrame = attributeFrame.WithAttributeValue(attributeValue);
attributeFrame.AttributeValueField = attributeValue;
return;
}
}
@ -84,25 +84,25 @@ namespace Microsoft.AspNetCore.Components.Rendering
for (var otherFrameIndex = elementFrameIndex; otherFrameIndex >= 0; otherFrameIndex--)
{
ref var otherFrame = ref framesArray[otherFrameIndex];
switch (otherFrame.FrameType)
switch (otherFrame.FrameTypeField)
{
case RenderTreeFrameType.Element:
{
var otherFrameSubtreeLength = otherFrame.ElementSubtreeLength;
var otherFrameSubtreeLength = otherFrame.ElementSubtreeLengthField;
var otherFrameEndIndexExcl = otherFrameIndex + otherFrameSubtreeLength;
if (otherFrameEndIndexExcl > elementFrameIndex) // i.e., contains the element we're inserting into
{
otherFrame = otherFrame.WithElementSubtreeLength(otherFrameSubtreeLength + 1);
otherFrame.ElementSubtreeLengthField = otherFrameSubtreeLength + 1;
}
break;
}
case RenderTreeFrameType.Region:
{
var otherFrameSubtreeLength = otherFrame.RegionSubtreeLength;
var otherFrameSubtreeLength = otherFrame.RegionSubtreeLengthField;
var otherFrameEndIndexExcl = otherFrameIndex + otherFrameSubtreeLength;
if (otherFrameEndIndexExcl > elementFrameIndex) // i.e., contains the element we're inserting into
{
otherFrame = otherFrame.WithRegionSubtreeLength(otherFrameSubtreeLength + 1);
otherFrame.RegionSubtreeLengthField = otherFrameSubtreeLength + 1;
}
break;
}

View File

@ -28,8 +28,8 @@ namespace Microsoft.AspNetCore.Components.RenderTree
internal class ArrayBuilder<T> : IDisposable
{
// The following fields are memory mapped to the WASM client. Do not re-order or use auto-properties.
private T[] _items;
private int _itemsInUse;
protected T[] _items;
protected int _itemsInUse;
private static readonly T[] Empty = Array.Empty<T>();
private readonly ArrayPool<T> _arrayPool;
@ -139,7 +139,6 @@ namespace Microsoft.AspNetCore.Components.RenderTree
ThrowIndexOutOfBoundsException();
}
// Same expansion logic as elsewhere
if (_itemsInUse == _items.Length)
{
GrowBuffer(_items.Length * 2);
@ -162,7 +161,7 @@ namespace Microsoft.AspNetCore.Components.RenderTree
_itemsInUse = 0;
}
private void GrowBuffer(int desiredCapacity)
protected void GrowBuffer(int desiredCapacity)
{
// When we dispose, we set the count back to zero and return the array.
//