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

View File

@ -78,7 +78,7 @@ namespace Microsoft.AspNetCore.Components.RenderTree
if (hasMoreOld) if (hasMoreOld)
{ {
ref var oldFrame = ref oldTree[oldStartIndex]; ref var oldFrame = ref oldTree[oldStartIndex];
oldSeq = oldFrame.Sequence; oldSeq = oldFrame.SequenceField;
oldKey = KeyValue(ref oldFrame); oldKey = KeyValue(ref oldFrame);
} }
else else
@ -90,7 +90,7 @@ namespace Microsoft.AspNetCore.Components.RenderTree
if (hasMoreNew) if (hasMoreNew)
{ {
ref var newFrame = ref newTree[newStartIndex]; ref var newFrame = ref newTree[newStartIndex];
newSeq = newFrame.Sequence; newSeq = newFrame.SequenceField;
newKey = KeyValue(ref newFrame); newKey = KeyValue(ref newFrame);
} }
else else
@ -195,7 +195,7 @@ namespace Microsoft.AspNetCore.Components.RenderTree
var newLoopsBackLater = false; var newLoopsBackLater = false;
for (var testIndex = newStartIndex + 1; testIndex < newEndIndexExcl; testIndex++) for (var testIndex = newStartIndex + 1; testIndex < newEndIndexExcl; testIndex++)
{ {
if (newTree[testIndex].Sequence < newSeq) if (newTree[testIndex].SequenceField < newSeq)
{ {
newLoopsBackLater = true; newLoopsBackLater = true;
break; break;
@ -218,7 +218,7 @@ namespace Microsoft.AspNetCore.Components.RenderTree
var oldLoopsBackLater = false; var oldLoopsBackLater = false;
for (var testIndex = oldStartIndex + 1; testIndex < oldEndIndexExcl; testIndex++) for (var testIndex = oldStartIndex + 1; testIndex < oldEndIndexExcl; testIndex++)
{ {
if (oldTree[testIndex].Sequence < oldSeq) if (oldTree[testIndex].SequenceField < oldSeq)
{ {
oldLoopsBackLater = true; oldLoopsBackLater = true;
break; break;
@ -350,13 +350,13 @@ namespace Microsoft.AspNetCore.Components.RenderTree
private static void ThrowExceptionForDuplicateKey(object key, in RenderTreeFrame frame) private static void ThrowExceptionForDuplicateKey(object key, in RenderTreeFrame frame)
{ {
switch (frame.FrameType) switch (frame.FrameTypeField)
{ {
case RenderTreeFrameType.Component: 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: 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: default:
throw new InvalidOperationException($"More than one sibling has the same key value, '{key}'. Key values must be unique."); 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) private static object KeyValue(ref RenderTreeFrame frame)
{ {
switch (frame.FrameType) switch (frame.FrameTypeField)
{ {
case RenderTreeFrameType.Element: case RenderTreeFrameType.Element:
return frame.ElementKey; return frame.ElementKeyField;
case RenderTreeFrameType.Component: case RenderTreeFrameType.Component:
return frame.ComponentKey; return frame.ComponentKeyField;
default: default:
return null; return null;
} }
@ -405,10 +405,10 @@ namespace Microsoft.AspNetCore.Components.RenderTree
while (hasMoreOld || hasMoreNew) while (hasMoreOld || hasMoreNew)
{ {
var oldSeq = hasMoreOld ? oldTree[oldStartIndex].Sequence : int.MaxValue; var oldSeq = hasMoreOld ? oldTree[oldStartIndex].SequenceField : int.MaxValue;
var newSeq = hasMoreNew ? newTree[newStartIndex].Sequence : int.MaxValue; var newSeq = hasMoreNew ? newTree[newStartIndex].SequenceField : int.MaxValue;
var oldAttributeName = oldTree[oldStartIndex].AttributeName; var oldAttributeName = oldTree[oldStartIndex].AttributeNameField;
var newAttributeName = newTree[newStartIndex].AttributeName; var newAttributeName = newTree[newStartIndex].AttributeNameField;
if (oldSeq == newSeq && if (oldSeq == newSeq &&
string.Equals(oldAttributeName, newAttributeName, StringComparison.Ordinal)) 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 // 3. iterate through the remaining attributes in the set and add them
for (var i = newStartIndex; i < newEndIndexExcl; i++) 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++) 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)) if (diffContext.AttributeDiffSet.TryGetValue(oldName, out var matchIndex))
{ {
// Has a match in the new tree, look for a diff // Has a match in the new tree, look for a diff
@ -519,10 +519,11 @@ namespace Microsoft.AspNetCore.Components.RenderTree
var newTree = diffContext.NewTree; var newTree = diffContext.NewTree;
ref var oldComponentFrame = ref oldTree[oldComponentIndex]; ref var oldComponentFrame = ref oldTree[oldComponentIndex];
ref var newComponentFrame = ref newTree[newComponentIndex]; ref var newComponentFrame = ref newTree[newComponentIndex];
var componentState = oldComponentFrame.ComponentState; var componentState = oldComponentFrame.ComponentStateField;
// Preserve the actual componentInstance // 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 // As an important rendering optimization, we want to skip parameter update
// notifications if we know for sure they haven't changed/mutated. The // 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) private static int NextSiblingIndex(in RenderTreeFrame frame, int frameIndex)
{ {
switch (frame.FrameType) switch (frame.FrameTypeField)
{ {
case RenderTreeFrameType.Component: case RenderTreeFrameType.Component:
return frameIndex + frame.ComponentSubtreeLength; return frameIndex + frame.ComponentSubtreeLengthField;
case RenderTreeFrameType.Element: case RenderTreeFrameType.Element:
return frameIndex + frame.ElementSubtreeLength; return frameIndex + frame.ElementSubtreeLengthField;
case RenderTreeFrameType.Region: case RenderTreeFrameType.Region:
return frameIndex + frame.RegionSubtreeLength; return frameIndex + frame.RegionSubtreeLengthField;
default: default:
return frameIndex + 1; 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 // 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. // builder logic manually or if two dissimilar frames matched by key. Treat as completely unrelated.
var newFrameType = newFrame.FrameType; var newFrameType = newFrame.FrameTypeField;
if (oldFrame.FrameType != newFrameType) if (oldFrame.FrameTypeField != newFrameType)
{ {
InsertNewFrame(ref diffContext, newFrameIndex); InsertNewFrame(ref diffContext, newFrameIndex);
RemoveOldFrame(ref diffContext, oldFrameIndex); RemoveOldFrame(ref diffContext, oldFrameIndex);
@ -586,8 +587,8 @@ namespace Microsoft.AspNetCore.Components.RenderTree
{ {
case RenderTreeFrameType.Text: case RenderTreeFrameType.Text:
{ {
var oldText = oldFrame.TextContent; var oldText = oldFrame.TextContentField;
var newText = newFrame.TextContent; var newText = newFrame.TextContentField;
if (!string.Equals(oldText, newText, StringComparison.Ordinal)) if (!string.Equals(oldText, newText, StringComparison.Ordinal))
{ {
var referenceFrameIndex = diffContext.ReferenceFrames.Append(newFrame); var referenceFrameIndex = diffContext.ReferenceFrames.Append(newFrame);
@ -599,8 +600,8 @@ namespace Microsoft.AspNetCore.Components.RenderTree
case RenderTreeFrameType.Markup: case RenderTreeFrameType.Markup:
{ {
var oldMarkup = oldFrame.MarkupContent; var oldMarkup = oldFrame.MarkupContentField;
var newMarkup = newFrame.MarkupContent; var newMarkup = newFrame.MarkupContentField;
if (!string.Equals(oldMarkup, newMarkup, StringComparison.Ordinal)) if (!string.Equals(oldMarkup, newMarkup, StringComparison.Ordinal))
{ {
var referenceFrameIndex = diffContext.ReferenceFrames.Append(newFrame); var referenceFrameIndex = diffContext.ReferenceFrames.Append(newFrame);
@ -612,8 +613,8 @@ namespace Microsoft.AspNetCore.Components.RenderTree
case RenderTreeFrameType.Element: case RenderTreeFrameType.Element:
{ {
var oldElementName = oldFrame.ElementName; var oldElementName = oldFrame.ElementNameField;
var newElementName = newFrame.ElementName; var newElementName = newFrame.ElementNameField;
if (string.Equals(oldElementName, newElementName, StringComparison.Ordinal)) if (string.Equals(oldElementName, newElementName, StringComparison.Ordinal))
{ {
var oldFrameAttributesEndIndexExcl = GetAttributesEndIndexExclusive(oldTree, oldFrameIndex); var oldFrameAttributesEndIndexExcl = GetAttributesEndIndexExclusive(oldTree, oldFrameIndex);
@ -626,8 +627,8 @@ namespace Microsoft.AspNetCore.Components.RenderTree
newFrameIndex + 1, newFrameAttributesEndIndexExcl); newFrameIndex + 1, newFrameAttributesEndIndexExcl);
// Diff the children // Diff the children
var oldFrameChildrenEndIndexExcl = oldFrameIndex + oldFrame.ElementSubtreeLength; var oldFrameChildrenEndIndexExcl = oldFrameIndex + oldFrame.ElementSubtreeLengthField;
var newFrameChildrenEndIndexExcl = newFrameIndex + newFrame.ElementSubtreeLength; var newFrameChildrenEndIndexExcl = newFrameIndex + newFrame.ElementSubtreeLengthField;
var hasChildrenToProcess = var hasChildrenToProcess =
oldFrameChildrenEndIndexExcl > oldFrameAttributesEndIndexExcl || oldFrameChildrenEndIndexExcl > oldFrameAttributesEndIndexExcl ||
newFrameChildrenEndIndexExcl > newFrameAttributesEndIndexExcl; newFrameChildrenEndIndexExcl > newFrameAttributesEndIndexExcl;
@ -661,14 +662,14 @@ namespace Microsoft.AspNetCore.Components.RenderTree
{ {
AppendDiffEntriesForRange( AppendDiffEntriesForRange(
ref diffContext, ref diffContext,
oldFrameIndex + 1, oldFrameIndex + oldFrame.RegionSubtreeLength, oldFrameIndex + 1, oldFrameIndex + oldFrame.RegionSubtreeLengthField,
newFrameIndex + 1, newFrameIndex + newFrame.RegionSubtreeLength); newFrameIndex + 1, newFrameIndex + newFrame.RegionSubtreeLengthField);
break; break;
} }
case RenderTreeFrameType.Component: case RenderTreeFrameType.Component:
{ {
if (oldFrame.ComponentType == newFrame.ComponentType) if (oldFrame.ComponentTypeField == newFrame.ComponentTypeField)
{ {
UpdateRetainedChildComponent( UpdateRetainedChildComponent(
ref diffContext, ref diffContext,
@ -698,7 +699,7 @@ namespace Microsoft.AspNetCore.Components.RenderTree
// We don't handle attributes here, they have their own diff logic. // We don't handle attributes here, they have their own diff logic.
// See AppendDiffEntriesForAttributeFrame // See AppendDiffEntriesForAttributeFrame
default: 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]; ref var newFrame = ref newTree[newFrameIndex];
// Using Equals to account for string comparisons, nulls, etc. // 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) if (valueChanged)
{ {
InitializeNewAttributeFrame(ref diffContext, ref newFrame); 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, // 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 // 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.Renderer.TrackReplacedEventHandlerId(oldFrame.AttributeEventHandlerIdField, newFrame.AttributeEventHandlerIdField);
diffContext.BatchBuilder.DisposedEventHandlerIds.Append(oldFrame.AttributeEventHandlerId); 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. // 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 // 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; var newTree = diffContext.NewTree;
ref var newFrame = ref newTree[newFrameIndex]; ref var newFrame = ref newTree[newFrameIndex];
switch (newFrame.FrameType) switch (newFrame.FrameTypeField)
{ {
case RenderTreeFrameType.Attribute: case RenderTreeFrameType.Attribute:
{ {
@ -756,7 +757,7 @@ namespace Microsoft.AspNetCore.Components.RenderTree
case RenderTreeFrameType.Element: case RenderTreeFrameType.Element:
{ {
InitializeNewSubtree(ref diffContext, newFrameIndex); 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.Edits.Append(RenderTreeEdit.PrependFrame(diffContext.SiblingIndex, referenceFrameIndex));
diffContext.SiblingIndex++; diffContext.SiblingIndex++;
break; break;
@ -764,7 +765,7 @@ namespace Microsoft.AspNetCore.Components.RenderTree
case RenderTreeFrameType.Region: case RenderTreeFrameType.Region:
{ {
var regionChildFrameIndex = newFrameIndex + 1; var regionChildFrameIndex = newFrameIndex + 1;
var regionChildFrameEndIndexExcl = newFrameIndex + newFrame.RegionSubtreeLength; var regionChildFrameEndIndexExcl = newFrameIndex + newFrame.RegionSubtreeLengthField;
while (regionChildFrameIndex < regionChildFrameEndIndexExcl) while (regionChildFrameIndex < regionChildFrameEndIndexExcl)
{ {
InsertNewFrame(ref diffContext, regionChildFrameIndex); InsertNewFrame(ref diffContext, regionChildFrameIndex);
@ -791,7 +792,7 @@ namespace Microsoft.AspNetCore.Components.RenderTree
break; break;
} }
default: 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; var oldTree = diffContext.OldTree;
ref var oldFrame = ref oldTree[oldFrameIndex]; ref var oldFrame = ref oldTree[oldFrameIndex];
switch (oldFrame.FrameType) switch (oldFrame.FrameTypeField)
{ {
case RenderTreeFrameType.Attribute: case RenderTreeFrameType.Attribute:
{ {
diffContext.Edits.Append(RenderTreeEdit.RemoveAttribute(diffContext.SiblingIndex, oldFrame.AttributeName)); diffContext.Edits.Append(RenderTreeEdit.RemoveAttribute(diffContext.SiblingIndex, oldFrame.AttributeNameField));
if (oldFrame.AttributeEventHandlerId > 0) if (oldFrame.AttributeEventHandlerIdField > 0)
{ {
diffContext.BatchBuilder.DisposedEventHandlerIds.Append(oldFrame.AttributeEventHandlerId); diffContext.BatchBuilder.DisposedEventHandlerIds.Append(oldFrame.AttributeEventHandlerIdField);
} }
break; break;
} }
case RenderTreeFrameType.Component: case RenderTreeFrameType.Component:
case RenderTreeFrameType.Element: case RenderTreeFrameType.Element:
{ {
var endIndexExcl = oldFrameIndex + oldFrame.ElementSubtreeLength; var endIndexExcl = oldFrameIndex + oldFrame.ElementSubtreeLengthField;
DisposeFramesInRange(diffContext.BatchBuilder, oldTree, oldFrameIndex, endIndexExcl); DisposeFramesInRange(diffContext.BatchBuilder, oldTree, oldFrameIndex, endIndexExcl);
diffContext.Edits.Append(RenderTreeEdit.RemoveFrame(diffContext.SiblingIndex)); diffContext.Edits.Append(RenderTreeEdit.RemoveFrame(diffContext.SiblingIndex));
break; break;
@ -821,7 +822,7 @@ namespace Microsoft.AspNetCore.Components.RenderTree
case RenderTreeFrameType.Region: case RenderTreeFrameType.Region:
{ {
var regionChildFrameIndex = oldFrameIndex + 1; var regionChildFrameIndex = oldFrameIndex + 1;
var regionChildFrameEndIndexExcl = oldFrameIndex + oldFrame.RegionSubtreeLength; var regionChildFrameEndIndexExcl = oldFrameIndex + oldFrame.RegionSubtreeLengthField;
while (regionChildFrameIndex < regionChildFrameEndIndexExcl) while (regionChildFrameIndex < regionChildFrameEndIndexExcl)
{ {
RemoveOldFrame(ref diffContext, regionChildFrameIndex); RemoveOldFrame(ref diffContext, regionChildFrameIndex);
@ -836,17 +837,17 @@ namespace Microsoft.AspNetCore.Components.RenderTree
break; break;
} }
default: 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) private static int GetAttributesEndIndexExclusive(RenderTreeFrame[] tree, int rootIndex)
{ {
var descendantsEndIndexExcl = rootIndex + tree[rootIndex].ElementSubtreeLength; var descendantsEndIndexExcl = rootIndex + tree[rootIndex].ElementSubtreeLengthField;
var index = rootIndex + 1; var index = rootIndex + 1;
for (; index < descendantsEndIndexExcl; index++) for (; index < descendantsEndIndexExcl; index++)
{ {
if (tree[index].FrameType != RenderTreeFrameType.Attribute) if (tree[index].FrameTypeField != RenderTreeFrameType.Attribute)
{ {
break; break;
} }
@ -872,11 +873,11 @@ namespace Microsoft.AspNetCore.Components.RenderTree
private static void InitializeNewSubtree(ref DiffContext diffContext, int frameIndex) private static void InitializeNewSubtree(ref DiffContext diffContext, int frameIndex)
{ {
var frames = diffContext.NewTree; var frames = diffContext.NewTree;
var endIndexExcl = frameIndex + frames[frameIndex].ElementSubtreeLength; var endIndexExcl = frameIndex + frames[frameIndex].ElementSubtreeLengthField;
for (var i = frameIndex; i < endIndexExcl; i++) for (var i = frameIndex; i < endIndexExcl; i++)
{ {
ref var frame = ref frames[i]; ref var frame = ref frames[i];
switch (frame.FrameType) switch (frame.FrameTypeField)
{ {
case RenderTreeFrameType.Component: case RenderTreeFrameType.Component:
InitializeNewComponentFrame(ref diffContext, i); InitializeNewComponentFrame(ref diffContext, i);
@ -899,14 +900,14 @@ namespace Microsoft.AspNetCore.Components.RenderTree
var frames = diffContext.NewTree; var frames = diffContext.NewTree;
ref var frame = ref frames[frameIndex]; ref var frame = ref frames[frameIndex];
if (frame.ComponentState != null) if (frame.ComponentStateField != null)
{ {
throw new InvalidOperationException($"Child component already exists during {nameof(InitializeNewComponentFrame)}"); throw new InvalidOperationException($"Child component already exists during {nameof(InitializeNewComponentFrame)}");
} }
var parentComponentId = diffContext.ComponentId; var parentComponentId = diffContext.ComponentId;
diffContext.Renderer.InstantiateChildComponentOnFrame(ref frame, parentComponentId); diffContext.Renderer.InstantiateChildComponentOnFrame(ref frame, parentComponentId);
var childComponentState = frame.ComponentState; var childComponentState = frame.ComponentStateField;
// Set initial parameters // Set initial parameters
var initialParametersLifetime = new ParameterViewLifetime(diffContext.BatchBuilder); 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 // We're following a simple heuristic here that's reflected in the ts runtime
// based on the common usage of attributes for DOM events. // based on the common usage of attributes for DOM events.
if ((newFrame.AttributeValue is MulticastDelegate || newFrame.AttributeValue is EventCallback) && if ((newFrame.AttributeValueField is MulticastDelegate || newFrame.AttributeValueField is EventCallback) &&
newFrame.AttributeName.Length >= 3 && newFrame.AttributeNameField.Length >= 3 &&
newFrame.AttributeName.StartsWith("on", StringComparison.Ordinal)) newFrame.AttributeNameField.StartsWith("on", StringComparison.Ordinal))
{ {
diffContext.Renderer.AssignEventHandlerId(ref newFrame); diffContext.Renderer.AssignEventHandlerId(ref newFrame);
} }
@ -931,14 +932,14 @@ namespace Microsoft.AspNetCore.Components.RenderTree
private static void InitializeNewElementReferenceCaptureFrame(ref DiffContext diffContext, ref RenderTreeFrame newFrame) private static void InitializeNewElementReferenceCaptureFrame(ref DiffContext diffContext, ref RenderTreeFrame newFrame)
{ {
var newElementReference = ElementReference.CreateWithUniqueId(diffContext.Renderer.ElementReferenceContext); var newElementReference = ElementReference.CreateWithUniqueId(diffContext.Renderer.ElementReferenceContext);
newFrame = newFrame.WithElementReferenceCaptureId(newElementReference.Id); newFrame.ElementReferenceCaptureIdField = newElementReference.Id;
newFrame.ElementReferenceCaptureAction(newElementReference); newFrame.ElementReferenceCaptureActionField(newElementReference);
} }
private static void InitializeNewComponentReferenceCaptureFrame(ref DiffContext diffContext, ref RenderTreeFrame newFrame) private static void InitializeNewComponentReferenceCaptureFrame(ref DiffContext diffContext, ref RenderTreeFrame newFrame)
{ {
ref var parentFrame = ref diffContext.NewTree[newFrame.ComponentReferenceCaptureParentFrameIndex]; ref var parentFrame = ref diffContext.NewTree[newFrame.ComponentReferenceCaptureParentFrameIndexField];
if (parentFrame.FrameType != RenderTreeFrameType.Component) if (parentFrame.FrameTypeField != RenderTreeFrameType.Component)
{ {
// Should never happen, but will help with diagnosis if it does // Should never happen, but will help with diagnosis if it does
throw new InvalidOperationException($"{nameof(RenderTreeFrameType.ComponentReferenceCapture)} frame references invalid parent index."); 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."); 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) 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++) for (var i = startIndex; i < endIndexExcl; i++)
{ {
ref var frame = ref frames[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. // Represents an entry in a tree of user interface (UI) items.
[StructLayout(LayoutKind.Explicit, Pack = 4)] [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, // 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 // 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 // Common
// -------------------------------------------------------------------------------- // --------------------------------------------------------------------------------
[FieldOffset(0)] internal int SequenceField;
[FieldOffset(4)] internal RenderTreeFrameType FrameTypeField;
/// <summary> /// <summary>
/// Gets the sequence number of the frame. Sequence numbers indicate the relative source /// 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 /// positions of the instructions that inserted the frames. Sequence numbers are only
/// comparable within the same sequence (typically, the same source method). /// comparable within the same sequence (typically, the same source method).
/// </summary> /// </summary>
[FieldOffset(0)] public readonly int Sequence; public int Sequence => SequenceField;
/// <summary> /// <summary>
/// Describes the type of this frame. /// Describes the type of this frame.
/// </summary> /// </summary>
[FieldOffset(4)] public readonly RenderTreeFrameType FrameType; public RenderTreeFrameType FrameType => FrameTypeField;
// -------------------------------------------------------------------------------- // --------------------------------------------------------------------------------
// RenderTreeFrameType.Element // RenderTreeFrameType.Element
// -------------------------------------------------------------------------------- // --------------------------------------------------------------------------------
[FieldOffset(8)] internal int ElementSubtreeLengthField;
[FieldOffset(16)] internal string ElementNameField;
[FieldOffset(24)] internal object ElementKeyField;
/// <summary> /// <summary>
/// If the <see cref="FrameType"/> property equals <see cref="RenderTreeFrameType.Element"/> /// 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. /// 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. /// The value is zero if the frame has not yet been closed.
/// </summary> /// </summary>
[FieldOffset(8)] public readonly int ElementSubtreeLength; public int ElementSubtreeLength => ElementSubtreeLengthField;
/// <summary> /// <summary>
/// If the <see cref="FrameType"/> property equals <see cref="RenderTreeFrameType.Element"/>, /// 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. /// gets a name representing the type of the element. Otherwise, the value is undefined.
/// </summary> /// </summary>
[FieldOffset(16)] public readonly string ElementName; public string ElementName => ElementNameField;
/// <summary> /// <summary>
/// If the <see cref="FrameType"/> property equals <see cref="RenderTreeFrameType.Element"/>, /// If the <see cref="FrameType"/> property equals <see cref="RenderTreeFrameType.Element"/>,
/// gets the element's diffing key, or null if none was specified. /// gets the element's diffing key, or null if none was specified.
/// </summary> /// </summary>
[FieldOffset(24)] public readonly object ElementKey; public object ElementKey => ElementKeyField;
// -------------------------------------------------------------------------------- // --------------------------------------------------------------------------------
// RenderTreeFrameType.Text // RenderTreeFrameType.Text
// -------------------------------------------------------------------------------- // --------------------------------------------------------------------------------
[FieldOffset(16)] internal string TextContentField;
/// <summary> /// <summary>
/// If the <see cref="FrameType"/> property equals <see cref="RenderTreeFrameType.Text"/>, /// If the <see cref="FrameType"/> property equals <see cref="RenderTreeFrameType.Text"/>,
/// gets the content of the text frame. Otherwise, the value is undefined. /// gets the content of the text frame. Otherwise, the value is undefined.
/// </summary> /// </summary>
[FieldOffset(16)] public readonly string TextContent; public string TextContent => TextContentField;
// -------------------------------------------------------------------------------- // --------------------------------------------------------------------------------
// RenderTreeFrameType.Attribute // RenderTreeFrameType.Attribute
// -------------------------------------------------------------------------------- // --------------------------------------------------------------------------------
[FieldOffset(8)] internal ulong AttributeEventHandlerIdField;
[FieldOffset(16)] internal string AttributeNameField;
[FieldOffset(24)] internal object AttributeValueField;
[FieldOffset(32)] internal string AttributeEventUpdatesAttributeNameField;
/// <summary> /// <summary>
/// If the <see cref="FrameType"/> property equals <see cref="RenderTreeFrameType.Attribute"/> /// If the <see cref="FrameType"/> property equals <see cref="RenderTreeFrameType.Attribute"/>
/// gets the ID of the corresponding event handler, if any. /// gets the ID of the corresponding event handler, if any.
/// </summary> /// </summary>
[FieldOffset(8)] public readonly ulong AttributeEventHandlerId; public ulong AttributeEventHandlerId => AttributeEventHandlerIdField;
/// <summary> /// <summary>
/// If the <see cref="FrameType"/> property equals <see cref="RenderTreeFrameType.Attribute"/>, /// If the <see cref="FrameType"/> property equals <see cref="RenderTreeFrameType.Attribute"/>,
/// gets the attribute name. Otherwise, the value is undefined. /// gets the attribute name. Otherwise, the value is undefined.
/// </summary> /// </summary>
[FieldOffset(16)] public readonly string AttributeName; public string AttributeName => AttributeNameField;
/// <summary> /// <summary>
/// If the <see cref="FrameType"/> property equals <see cref="RenderTreeFrameType.Attribute"/>, /// If the <see cref="FrameType"/> property equals <see cref="RenderTreeFrameType.Attribute"/>,
/// gets the attribute value. Otherwise, the value is undefined. /// gets the attribute value. Otherwise, the value is undefined.
/// </summary> /// </summary>
[FieldOffset(24)] public readonly object AttributeValue; public object AttributeValue => AttributeValueField;
/// <summary> /// <summary>
/// If the <see cref="FrameType"/> property equals <see cref="RenderTreeFrameType.Attribute"/>, /// 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 /// can be updated to represent the UI state prior to executing the event handler. This is
/// primarily used in two-way bindings. /// primarily used in two-way bindings.
/// </summary> /// </summary>
[FieldOffset(32)] public readonly string AttributeEventUpdatesAttributeName; public string AttributeEventUpdatesAttributeName => AttributeEventUpdatesAttributeNameField;
// -------------------------------------------------------------------------------- // --------------------------------------------------------------------------------
// RenderTreeFrameType.Component // 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> /// <summary>
/// If the <see cref="FrameType"/> property equals <see cref="RenderTreeFrameType.Component"/> /// 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. /// 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. /// The value is zero if the frame has not yet been closed.
/// </summary> /// </summary>
[FieldOffset(8)] public readonly int ComponentSubtreeLength; public int ComponentSubtreeLength => ComponentSubtreeLengthField;
/// <summary> /// <summary>
/// If the <see cref="FrameType"/> property equals <see cref="RenderTreeFrameType.Component"/>, /// If the <see cref="FrameType"/> property equals <see cref="RenderTreeFrameType.Component"/>,
/// gets the child component instance identifier. /// gets the child component instance identifier.
/// </summary> /// </summary>
[FieldOffset(12)] public readonly int ComponentId; public int ComponentId => ComponentIdField;
/// <summary> /// <summary>
/// If the <see cref="FrameType"/> property equals <see cref="RenderTreeFrameType.Component"/>, /// If the <see cref="FrameType"/> property equals <see cref="RenderTreeFrameType.Component"/>,
/// gets the type of the child component. /// gets the type of the child component.
/// </summary> /// </summary>
[FieldOffset(16)] public readonly Type ComponentType; public Type ComponentType => ComponentTypeField;
/// <summary> /// <summary>
/// If the <see cref="FrameType"/> property equals <see cref="RenderTreeFrameType.Component"/>, /// If the <see cref="FrameType"/> property equals <see cref="RenderTreeFrameType.Component"/>,
/// gets the child component state object. Otherwise, the value is undefined. /// gets the child component state object. Otherwise, the value is undefined.
/// </summary> /// </summary>
[FieldOffset(24)] internal readonly ComponentState ComponentState; internal ComponentState ComponentState => ComponentStateField;
/// <summary> /// <summary>
/// If the <see cref="FrameType"/> property equals <see cref="RenderTreeFrameType.Component"/>, /// If the <see cref="FrameType"/> property equals <see cref="RenderTreeFrameType.Component"/>,
/// gets the component's diffing key, or null if none was specified. /// gets the component's diffing key, or null if none was specified.
/// </summary> /// </summary>
[FieldOffset(32)] public readonly object ComponentKey; public object ComponentKey => ComponentKeyField;
/// <summary> /// <summary>
/// If the <see cref="FrameType"/> property equals <see cref="RenderTreeFrameType.Component"/>, /// If the <see cref="FrameType"/> property equals <see cref="RenderTreeFrameType.Component"/>,
/// gets the child component instance. Otherwise, the value is undefined. /// gets the child component instance. Otherwise, the value is undefined.
/// </summary> /// </summary>
public IComponent Component => ComponentState?.Component; public IComponent Component => ComponentStateField?.Component;
// -------------------------------------------------------------------------------- // --------------------------------------------------------------------------------
// RenderTreeFrameType.Region // RenderTreeFrameType.Region
// -------------------------------------------------------------------------------- // --------------------------------------------------------------------------------
[FieldOffset(8)] internal int RegionSubtreeLengthField;
/// <summary> /// <summary>
/// If the <see cref="FrameType"/> property equals <see cref="RenderTreeFrameType.Region"/> /// 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. /// 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. /// The value is zero if the frame has not yet been closed.
/// </summary> /// </summary>
[FieldOffset(8)] public readonly int RegionSubtreeLength; public int RegionSubtreeLength => RegionSubtreeLengthField;
// -------------------------------------------------------------------------------- // --------------------------------------------------------------------------------
// RenderTreeFrameType.ElementReferenceCapture // RenderTreeFrameType.ElementReferenceCapture
// -------------------------------------------------------------------------------- // --------------------------------------------------------------------------------
[FieldOffset(16)] internal string ElementReferenceCaptureIdField;
[FieldOffset(24)] internal Action<ElementReference> ElementReferenceCaptureActionField;
/// <summary> /// <summary>
/// If the <see cref="FrameType"/> property equals <see cref="RenderTreeFrameType.ElementReferenceCapture"/>, /// If the <see cref="FrameType"/> property equals <see cref="RenderTreeFrameType.ElementReferenceCapture"/>,
/// gets the ID of the reference capture. Otherwise, the value is undefined. /// gets the ID of the reference capture. Otherwise, the value is undefined.
/// </summary> /// </summary>
[FieldOffset(16)] public readonly string ElementReferenceCaptureId; public string ElementReferenceCaptureId => ElementReferenceCaptureIdField;
/// <summary> /// <summary>
/// If the <see cref="FrameType"/> property equals <see cref="RenderTreeFrameType.ElementReferenceCapture"/>, /// 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. /// gets the action that writes the reference to its target. Otherwise, the value is undefined.
/// </summary> /// </summary>
[FieldOffset(24)] public readonly Action<ElementReference> ElementReferenceCaptureAction; public Action<ElementReference> ElementReferenceCaptureAction => ElementReferenceCaptureActionField;
// -------------------------------------------------------------------------------- // --------------------------------------------------------------------------------
// RenderTreeFrameType.ComponentReferenceCapture // RenderTreeFrameType.ComponentReferenceCapture
// -------------------------------------------------------------------------------- // --------------------------------------------------------------------------------
[FieldOffset(8)] internal int ComponentReferenceCaptureParentFrameIndexField;
[FieldOffset(16)] internal Action<object> ComponentReferenceCaptureActionField;
/// <summary> /// <summary>
/// If the <see cref="FrameType"/> property equals <see cref="RenderTreeFrameType.ComponentReferenceCapture"/>, /// 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. /// 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 /// 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. /// the parent index at the point where it wants to initialize the ComponentReferenceCapture frame.
/// </summary> /// </summary>
[FieldOffset(8)] public readonly int ComponentReferenceCaptureParentFrameIndex; public int ComponentReferenceCaptureParentFrameIndex => ComponentReferenceCaptureParentFrameIndexField;
/// <summary> /// <summary>
/// If the <see cref="FrameType"/> property equals <see cref="RenderTreeFrameType.ComponentReferenceCapture"/>, /// 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. /// gets the action that writes the reference to its target. Otherwise, the value is undefined.
/// </summary> /// </summary>
[FieldOffset(16)] public readonly Action<object> ComponentReferenceCaptureAction; public Action<object> ComponentReferenceCaptureAction => ComponentReferenceCaptureActionField;
// -------------------------------------------------------------------------------- // --------------------------------------------------------------------------------
// RenderTreeFrameType.Markup // RenderTreeFrameType.Markup
// -------------------------------------------------------------------------------- // --------------------------------------------------------------------------------
[FieldOffset(16)] internal string MarkupContentField;
/// <summary> /// <summary>
/// If the <see cref="FrameType"/> property equals <see cref="RenderTreeFrameType.Markup"/>, /// If the <see cref="FrameType"/> property equals <see cref="RenderTreeFrameType.Markup"/>,
/// gets the content of the markup frame. Otherwise, the value is undefined. /// gets the content of the markup frame. Otherwise, the value is undefined.
/// </summary> /// </summary>
[FieldOffset(16)] public readonly string MarkupContent; public string MarkupContent => MarkupContentField;
// Element constructor // Element constructor
private RenderTreeFrame(int sequence, int elementSubtreeLength, string elementName, object elementKey) private RenderTreeFrame(int sequence, int elementSubtreeLength, string elementName, object elementKey)
: this() : this()
{ {
Sequence = sequence; SequenceField = sequence;
FrameType = RenderTreeFrameType.Element; FrameTypeField = RenderTreeFrameType.Element;
ElementSubtreeLength = elementSubtreeLength; ElementSubtreeLengthField = elementSubtreeLength;
ElementName = elementName; ElementNameField = elementName;
ElementKey = elementKey; ElementKeyField = elementKey;
} }
// Component constructor // Component constructor
private RenderTreeFrame(int sequence, int componentSubtreeLength, Type componentType, ComponentState componentState, object componentKey) private RenderTreeFrame(int sequence, int componentSubtreeLength, Type componentType, ComponentState componentState, object componentKey)
: this() : this()
{ {
Sequence = sequence; SequenceField = sequence;
FrameType = RenderTreeFrameType.Component; FrameTypeField = RenderTreeFrameType.Component;
ComponentSubtreeLength = componentSubtreeLength; ComponentSubtreeLengthField = componentSubtreeLength;
ComponentType = componentType; ComponentTypeField = componentType;
ComponentKey = componentKey; ComponentKeyField = componentKey;
if (componentState != null) if (componentState != null)
{ {
ComponentState = componentState; ComponentStateField = componentState;
ComponentId = componentState.ComponentId; ComponentIdField = componentState.ComponentId;
} }
} }
@ -255,25 +285,25 @@ namespace Microsoft.AspNetCore.Components.RenderTree
private RenderTreeFrame(int sequence, int regionSubtreeLength) private RenderTreeFrame(int sequence, int regionSubtreeLength)
: this() : this()
{ {
Sequence = sequence; SequenceField = sequence;
FrameType = RenderTreeFrameType.Region; FrameTypeField = RenderTreeFrameType.Region;
RegionSubtreeLength = regionSubtreeLength; RegionSubtreeLengthField = regionSubtreeLength;
} }
// Text/markup constructor // Text/markup constructor
private RenderTreeFrame(int sequence, bool isMarkup, string textOrMarkup) private RenderTreeFrame(int sequence, bool isMarkup, string textOrMarkup)
: this() : this()
{ {
Sequence = sequence; SequenceField = sequence;
if (isMarkup) if (isMarkup)
{ {
FrameType = RenderTreeFrameType.Markup; FrameTypeField = RenderTreeFrameType.Markup;
MarkupContent = textOrMarkup; MarkupContentField = textOrMarkup;
} }
else else
{ {
FrameType = RenderTreeFrameType.Text; FrameTypeField = RenderTreeFrameType.Text;
TextContent = textOrMarkup; TextContentField = textOrMarkup;
} }
} }
@ -281,32 +311,32 @@ namespace Microsoft.AspNetCore.Components.RenderTree
private RenderTreeFrame(int sequence, string attributeName, object attributeValue, ulong attributeEventHandlerId, string attributeEventUpdatesAttributeName) private RenderTreeFrame(int sequence, string attributeName, object attributeValue, ulong attributeEventHandlerId, string attributeEventUpdatesAttributeName)
: this() : this()
{ {
FrameType = RenderTreeFrameType.Attribute; FrameTypeField = RenderTreeFrameType.Attribute;
Sequence = sequence; SequenceField = sequence;
AttributeName = attributeName; AttributeNameField = attributeName;
AttributeValue = attributeValue; AttributeValueField = attributeValue;
AttributeEventHandlerId = attributeEventHandlerId; AttributeEventHandlerIdField = attributeEventHandlerId;
AttributeEventUpdatesAttributeName = attributeEventUpdatesAttributeName; AttributeEventUpdatesAttributeNameField = attributeEventUpdatesAttributeName;
} }
// Element reference capture constructor // Element reference capture constructor
private RenderTreeFrame(int sequence, Action<ElementReference> elementReferenceCaptureAction, string elementReferenceCaptureId) private RenderTreeFrame(int sequence, Action<ElementReference> elementReferenceCaptureAction, string elementReferenceCaptureId)
: this() : this()
{ {
FrameType = RenderTreeFrameType.ElementReferenceCapture; FrameTypeField = RenderTreeFrameType.ElementReferenceCapture;
Sequence = sequence; SequenceField = sequence;
ElementReferenceCaptureAction = elementReferenceCaptureAction; ElementReferenceCaptureActionField = elementReferenceCaptureAction;
ElementReferenceCaptureId = elementReferenceCaptureId; ElementReferenceCaptureIdField = elementReferenceCaptureId;
} }
// Component reference capture constructor // Component reference capture constructor
private RenderTreeFrame(int sequence, Action<object> componentReferenceCaptureAction, int parentFrameIndex) private RenderTreeFrame(int sequence, Action<object> componentReferenceCaptureAction, int parentFrameIndex)
: this() : this()
{ {
FrameType = RenderTreeFrameType.ComponentReferenceCapture; FrameTypeField = RenderTreeFrameType.ComponentReferenceCapture;
Sequence = sequence; SequenceField = sequence;
ComponentReferenceCaptureAction = componentReferenceCaptureAction; ComponentReferenceCaptureActionField = componentReferenceCaptureAction;
ComponentReferenceCaptureParentFrameIndex = parentFrameIndex; ComponentReferenceCaptureParentFrameIndexField = parentFrameIndex;
} }
internal static RenderTreeFrame Element(int sequence, string elementName) internal static RenderTreeFrame Element(int sequence, string elementName)
@ -337,61 +367,61 @@ namespace Microsoft.AspNetCore.Components.RenderTree
=> new RenderTreeFrame(sequence, componentReferenceCaptureAction: componentReferenceCaptureAction, parentFrameIndex: parentFrameIndex); => new RenderTreeFrame(sequence, componentReferenceCaptureAction: componentReferenceCaptureAction, parentFrameIndex: parentFrameIndex);
internal RenderTreeFrame WithElementSubtreeLength(int elementSubtreeLength) internal RenderTreeFrame WithElementSubtreeLength(int elementSubtreeLength)
=> new RenderTreeFrame(Sequence, elementSubtreeLength: elementSubtreeLength, ElementName, ElementKey); => new RenderTreeFrame(SequenceField, elementSubtreeLength: elementSubtreeLength, ElementNameField, ElementKeyField);
internal RenderTreeFrame WithComponentSubtreeLength(int componentSubtreeLength) 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) 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) 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) 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) 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) 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) internal RenderTreeFrame WithRegionSubtreeLength(int regionSubtreeLength)
=> new RenderTreeFrame(Sequence, regionSubtreeLength: regionSubtreeLength); => new RenderTreeFrame(SequenceField, regionSubtreeLength: regionSubtreeLength);
internal RenderTreeFrame WithElementReferenceCaptureId(string elementReferenceCaptureId) internal RenderTreeFrame WithElementReferenceCaptureId(string elementReferenceCaptureId)
=> new RenderTreeFrame(Sequence, elementReferenceCaptureAction: ElementReferenceCaptureAction, elementReferenceCaptureId); => new RenderTreeFrame(SequenceField, elementReferenceCaptureAction: ElementReferenceCaptureActionField, elementReferenceCaptureId);
internal RenderTreeFrame WithElementKey(object elementKey) internal RenderTreeFrame WithElementKey(object elementKey)
=> new RenderTreeFrame(Sequence, elementSubtreeLength: ElementSubtreeLength, ElementName, elementKey); => new RenderTreeFrame(SequenceField, elementSubtreeLength: ElementSubtreeLengthField, ElementNameField, elementKey);
internal RenderTreeFrame WithComponentKey(object componentKey) internal RenderTreeFrame WithComponentKey(object componentKey)
=> new RenderTreeFrame(Sequence, componentSubtreeLength: ComponentSubtreeLength, ComponentType, ComponentState, componentKey); => new RenderTreeFrame(SequenceField, componentSubtreeLength: ComponentSubtreeLengthField, ComponentTypeField, ComponentStateField, componentKey);
/// <inheritdoc /> /// <inheritdoc />
// Just to be nice for debugging and unit tests. // Just to be nice for debugging and unit tests.
public override string ToString() public override string ToString()
{ {
switch (FrameType) switch (FrameTypeField)
{ {
case RenderTreeFrameType.Attribute: case RenderTreeFrameType.Attribute:
return $"Attribute: (seq={Sequence}, id={AttributeEventHandlerId}) '{AttributeName}'='{AttributeValue}'"; return $"Attribute: (seq={Sequence}, id={AttributeEventHandlerId}) '{AttributeName}'='{AttributeValue}'";
case RenderTreeFrameType.Component: 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: 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: case RenderTreeFrameType.Region:
return $"Region: (seq={Sequence}, len={RegionSubtreeLength})"; return $"Region: (seq={Sequence}, len={RegionSubtreeLength})";
case RenderTreeFrameType.Text: case RenderTreeFrameType.Text:
return $"Text: (seq={Sequence}, len=n/a) {EscapeNewlines(TextContent)}"; return $"Text: (seq={Sequence}, len=n/a) {EscapeNewlines(TextContentField)}";
case RenderTreeFrameType.Markup: case RenderTreeFrameType.Markup:
return $"Markup: (seq={Sequence}, len=n/a) {EscapeNewlines(TextContent)}"; return $"Markup: (seq={Sequence}, len=n/a) {EscapeNewlines(TextContentField)}";
case RenderTreeFrameType.ElementReferenceCapture: case RenderTreeFrameType.ElementReferenceCapture:
return $"ElementReferenceCapture: (seq={Sequence}, len=n/a) {ElementReferenceCaptureAction}"; 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) 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)); 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)); 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); var newComponentState = AttachAndInitComponent(newComponent, parentComponentId);
frame = frame.WithComponent(newComponentState); frame.ComponentStateField = newComponentState;
frame.ComponentIdField = newComponentState.ComponentId;
} }
internal void AddToPendingTasks(Task task) internal void AddToPendingTasks(Task task)
@ -342,7 +343,7 @@ namespace Microsoft.AspNetCore.Components.RenderTree
{ {
var id = ++_lastEventHandlerId; 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. // 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 // 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. // the receiver.
_eventBindings.Add(id, callback); _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 // 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 // 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 // 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. // a callback to a component, and never when used to attaching a DOM event handler.
frame = frame.WithAttributeEventHandlerId(id); frame.AttributeEventHandlerIdField = id;
} }
/// <summary> /// <summary>

View File

@ -23,7 +23,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
private readonly static object BoxedFalse = false; 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 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 readonly Stack<int> _openElementIndices = new Stack<int>();
private RenderTreeFrameType? _lastNonAttributeFrameType; private RenderTreeFrameType? _lastNonAttributeFrameType;
private bool _hasSeenAddMultipleAttributes; private bool _hasSeenAddMultipleAttributes;
@ -53,7 +53,8 @@ namespace Microsoft.AspNetCore.Components.Rendering
} }
_openElementIndices.Push(_entries.Count); _openElementIndices.Push(_entries.Count);
Append(RenderTreeFrame.Element(sequence, elementName)); _entries.AppendElement(sequence, elementName);
_lastNonAttributeFrameType = RenderTreeFrameType.Element;
} }
/// <summary> /// <summary>
@ -71,8 +72,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
ProcessDuplicateAttributes(first: indexOfEntryBeingClosed + 1); ProcessDuplicateAttributes(first: indexOfEntryBeingClosed + 1);
} }
ref var entry = ref _entries.Buffer[indexOfEntryBeingClosed]; _entries.Buffer[indexOfEntryBeingClosed].ElementSubtreeLengthField = _entries.Count - indexOfEntryBeingClosed;
entry = entry.WithElementSubtreeLength(_entries.Count - indexOfEntryBeingClosed);
} }
/// <summary> /// <summary>
@ -82,7 +82,8 @@ namespace Microsoft.AspNetCore.Components.Rendering
/// <param name="markupContent">Content for the new markup frame.</param> /// <param name="markupContent">Content for the new markup frame.</param>
public void AddMarkupContent(int sequence, string? markupContent) public void AddMarkupContent(int sequence, string? markupContent)
{ {
Append(RenderTreeFrame.Markup(sequence, markupContent ?? string.Empty)); _entries.AppendMarkup(sequence, markupContent ?? string.Empty);
_lastNonAttributeFrameType = RenderTreeFrameType.Markup;
} }
/// <summary> /// <summary>
@ -92,7 +93,8 @@ namespace Microsoft.AspNetCore.Components.Rendering
/// <param name="textContent">Content for the new text frame.</param> /// <param name="textContent">Content for the new text frame.</param>
public void AddContent(int sequence, string? textContent) public void AddContent(int sequence, string? textContent)
{ {
Append(RenderTreeFrame.Text(sequence, textContent ?? string.Empty)); _entries.AppendText(sequence, textContent ?? string.Empty);
_lastNonAttributeFrameType = RenderTreeFrameType.Text;
} }
/// <summary> /// <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}"); 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> /// <summary>
@ -181,13 +184,13 @@ namespace Microsoft.AspNetCore.Components.Rendering
AssertCanAddAttribute(); AssertCanAddAttribute();
if (_lastNonAttributeFrameType == RenderTreeFrameType.Component) if (_lastNonAttributeFrameType == RenderTreeFrameType.Component)
{ {
Append(RenderTreeFrame.Attribute(sequence, name, value ? BoxedTrue : BoxedFalse)); _entries.AppendAttribute(sequence, name, value ? BoxedTrue : BoxedFalse);
} }
else if (value) else if (value)
{ {
// Don't add 'false' attributes for elements. We want booleans to map to the presence // 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. // 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 else
{ {
@ -212,7 +215,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
AssertCanAddAttribute(); AssertCanAddAttribute();
if (value != null || _lastNonAttributeFrameType == RenderTreeFrameType.Component) if (value != null || _lastNonAttributeFrameType == RenderTreeFrameType.Component)
{ {
Append(RenderTreeFrame.Attribute(sequence, name, value)); _entries.AppendAttribute(sequence, name, value);
} }
else else
{ {
@ -237,7 +240,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
AssertCanAddAttribute(); AssertCanAddAttribute();
if (value != null || _lastNonAttributeFrameType == RenderTreeFrameType.Component) if (value != null || _lastNonAttributeFrameType == RenderTreeFrameType.Component)
{ {
Append(RenderTreeFrame.Attribute(sequence, name, value)); _entries.AppendAttribute(sequence, name, value);
} }
else 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 // Since this is a component, we need to preserve the type of the EventCallback, so we have
// to box. // to box.
Append(RenderTreeFrame.Attribute(sequence, name, (object)value)); _entries.AppendAttribute(sequence, name, value);
} }
else if (value.RequiresExplicitReceiver) else if (value.RequiresExplicitReceiver)
{ {
// If we need to preserve the receiver, we just box the EventCallback // If we need to preserve the receiver, we just box the EventCallback
// so we can get it out on the other side. // 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) else if (value.HasDelegate)
{ {
// In the common case the receiver is also the delegate's target, so we // 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. // 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 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 // Since this is a component, we need to preserve the type of the EventCallback, so we have
// to box. // to box.
Append(RenderTreeFrame.Attribute(sequence, name, (object)value)); _entries.AppendAttribute(sequence, name, value);
} }
else if (value.RequiresExplicitReceiver) else if (value.RequiresExplicitReceiver)
{ {
// If we need to preserve the receiver - we convert this to an untyped EventCallback. We don't // 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. // 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) else if (value.HasDelegate)
{ {
// In the common case the receiver is also the delegate's target, so we // 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. // 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 else
{ {
@ -357,7 +360,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
{ {
if (boolValue) if (boolValue)
{ {
Append(RenderTreeFrame.Attribute(sequence, name, BoxedTrue)); _entries.AppendAttribute(sequence, name, BoxedTrue);
} }
else else
{ {
@ -369,7 +372,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
{ {
if (callbackValue.HasDelegate) if (callbackValue.HasDelegate)
{ {
Append(RenderTreeFrame.Attribute(sequence, name, callbackValue.UnpackForRenderTree())); _entries.AppendAttribute(sequence, name, callbackValue.UnpackForRenderTree());
} }
else else
{ {
@ -378,18 +381,18 @@ namespace Microsoft.AspNetCore.Components.Rendering
} }
else if (value is MulticastDelegate) else if (value is MulticastDelegate)
{ {
Append(RenderTreeFrame.Attribute(sequence, name, value)); _entries.AppendAttribute(sequence, name, value);
} }
else else
{ {
// The value is either a string, or should be treated as a string. // 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) else if (_lastNonAttributeFrameType == RenderTreeFrameType.Component)
{ {
// If this is a component, we always want to preserve the original type. // 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 else
{ {
@ -408,15 +411,16 @@ namespace Microsoft.AspNetCore.Components.Rendering
/// </summary> /// </summary>
/// <param name="sequence">An integer that represents the position of the instruction in the source code.</param> /// <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> /// <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}."); throw new ArgumentException($"The {nameof(frame.FrameType)} must be {RenderTreeFrameType.Attribute}.");
} }
AssertCanAddAttribute(); AssertCanAddAttribute();
Append(frame.WithAttributeSequence(sequence)); frame.SequenceField = sequence;
_entries.Append(frame);
} }
/// <summary> /// <summary>
@ -464,12 +468,12 @@ namespace Microsoft.AspNetCore.Components.Rendering
} }
ref var prevFrame = ref _entries.Buffer[_entries.Count - 1]; 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> /// <summary>
@ -516,16 +520,16 @@ namespace Microsoft.AspNetCore.Components.Rendering
var parentFrameIndexValue = parentFrameIndex.Value; var parentFrameIndexValue = parentFrameIndex.Value;
ref var parentFrame = ref _entries.Buffer[parentFrameIndexValue]; ref var parentFrame = ref _entries.Buffer[parentFrameIndexValue];
switch (parentFrame.FrameType) switch (parentFrame.FrameTypeField)
{ {
case RenderTreeFrameType.Element: 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; break;
case RenderTreeFrameType.Component: 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; break;
default: 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); _openElementIndices.Push(_entries.Count);
Append(RenderTreeFrame.ChildComponent(sequence, componentType)); _entries.AppendComponent(sequence, componentType);
_lastNonAttributeFrameType = RenderTreeFrameType.Component;
} }
/// <summary> /// <summary>
@ -558,8 +563,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
ProcessDuplicateAttributes(first: indexOfEntryBeingClosed + 1); ProcessDuplicateAttributes(first: indexOfEntryBeingClosed + 1);
} }
ref var entry = ref _entries.Buffer[indexOfEntryBeingClosed]; _entries.Buffer[indexOfEntryBeingClosed].ComponentSubtreeLengthField = _entries.Count - indexOfEntryBeingClosed;
entry = entry.WithComponentSubtreeLength(_entries.Count - indexOfEntryBeingClosed);
} }
/// <summary> /// <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}"); 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> /// <summary>
@ -591,12 +596,13 @@ namespace Microsoft.AspNetCore.Components.Rendering
} }
var parentFrameIndexValue = parentFrameIndex.Value; var parentFrameIndexValue = parentFrameIndex.Value;
if (_entries.Buffer[parentFrameIndexValue].FrameType != RenderTreeFrameType.Component) if (_entries.Buffer[parentFrameIndexValue].FrameTypeField != RenderTreeFrameType.Component)
{ {
throw new InvalidOperationException(ComponentReferenceCaptureInvalidParentMessage); throw new InvalidOperationException(ComponentReferenceCaptureInvalidParentMessage);
} }
Append(RenderTreeFrame.ComponentReferenceCapture(sequence, componentReferenceCaptureAction, parentFrameIndexValue)); _entries.AppendComponentReferenceCapture(sequence, componentReferenceCaptureAction, parentFrameIndexValue);
_lastNonAttributeFrameType = RenderTreeFrameType.ComponentReferenceCapture;
} }
/// <summary> /// <summary>
@ -614,7 +620,8 @@ namespace Microsoft.AspNetCore.Components.Rendering
} }
_openElementIndices.Push(_entries.Count); _openElementIndices.Push(_entries.Count);
Append(RenderTreeFrame.Region(sequence)); _entries.AppendRegion(sequence);
_lastNonAttributeFrameType = RenderTreeFrameType.Region;
} }
/// <summary> /// <summary>
@ -624,8 +631,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
public void CloseRegion() public void CloseRegion()
{ {
var indexOfEntryBeingClosed = _openElementIndices.Pop(); var indexOfEntryBeingClosed = _openElementIndices.Pop();
ref var entry = ref _entries.Buffer[indexOfEntryBeingClosed]; _entries.Buffer[indexOfEntryBeingClosed].RegionSubtreeLengthField = _entries.Count - indexOfEntryBeingClosed;
entry = entry.WithRegionSubtreeLength(_entries.Count - indexOfEntryBeingClosed);
} }
private void AssertCanAddAttribute() private void AssertCanAddAttribute()
@ -644,7 +650,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
{ {
var parentIndex = GetCurrentParentFrameIndex(); var parentIndex = GetCurrentParentFrameIndex();
return parentIndex.HasValue return parentIndex.HasValue
? _entries.Buffer[parentIndex.Value].FrameType ? _entries.Buffer[parentIndex.Value].FrameTypeField
: (RenderTreeFrameType?)null; : (RenderTreeFrameType?)null;
} }
@ -680,17 +686,6 @@ namespace Microsoft.AspNetCore.Components.Rendering
public ArrayRange<RenderTreeFrame> GetFrames() => public ArrayRange<RenderTreeFrame> GetFrames() =>
_entries.ToRange(); _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 for testing
internal void ProcessDuplicateAttributes(int first) internal void ProcessDuplicateAttributes(int first)
{ {
@ -704,7 +699,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
for (var i = first; i <= last; i++) for (var i = first; i <= last; i++)
{ {
if (buffer[i].FrameType != RenderTreeFrameType.Attribute) if (buffer[i].FrameTypeField != RenderTreeFrameType.Attribute)
{ {
last = i - 1; last = i - 1;
break; break;
@ -716,12 +711,12 @@ namespace Microsoft.AspNetCore.Components.Rendering
for (var i = last; i >= first; i--) for (var i = last; i >= first; i--)
{ {
ref var frame = ref buffer[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. // 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) 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. // 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. // 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) else if (index > i)
{ {
@ -760,7 +755,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
for (var i = first; i < _entries.Count; i++) for (var i = first; i < _entries.Count; i++)
{ {
ref var frame = ref buffer[i]; ref var frame = ref buffer[i];
if (frame.FrameType != RenderTreeFrameType.None) if (frame.FrameTypeField != RenderTreeFrameType.None)
{ {
buffer[offset++] = frame; buffer[offset++] = frame;
} }

View File

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

View File

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