Skip to content

Commit

Permalink
Optimize render tree building via RenderTreeFrameArrayBuilder (#24484)
Browse files Browse the repository at this point in the history
  • Loading branch information
SteveSandersonMS committed Aug 3, 2020
1 parent 749450a commit 38e166f
Show file tree
Hide file tree
Showing 8 changed files with 401 additions and 238 deletions.
24 changes: 12 additions & 12 deletions src/Components/Components/src/ParameterView.cs
Original file line number Diff line number Diff line change
Expand Up @@ -142,8 +142,8 @@ internal bool DefinitelyEquals(ParameterView oldParameters)

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
Expand All @@ -162,21 +162,21 @@ internal bool DefinitelyEquals(ParameterView oldParameters)
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;
Expand Down Expand Up @@ -216,8 +216,8 @@ internal void CaptureSnapshot(ArrayBuilder<RenderTreeFrame> builder)
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)
Expand Down Expand Up @@ -303,7 +303,7 @@ internal RenderTreeFrameParameterEnumerator(RenderTreeFrame[] frames, int ownerI
{
_frames = frames;
_ownerIndex = ownerIndex;
_ownerDescendantsEndIndexExcl = ownerIndex + _frames[ownerIndex].ElementSubtreeLength;
_ownerDescendantsEndIndexExcl = ownerIndex + _frames[ownerIndex].ElementSubtreeLengthField;
_currentIndex = ownerIndex;
_current = default;
}
Expand All @@ -321,15 +321,15 @@ public bool MoveNext()

// ... 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;
}

_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;
}
Expand Down
141 changes: 71 additions & 70 deletions src/Components/Components/src/RenderTree/RenderTreeDiffBuilder.cs

Large diffs are not rendered by default.

176 changes: 103 additions & 73 deletions src/Components/Components/src/RenderTree/RenderTreeFrame.cs

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -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,
};
}
}
}
15 changes: 8 additions & 7 deletions src/Components/Components/src/RenderTree/Renderer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -292,19 +292,20 @@ public virtual Task DispatchEventAsync(ulong eventHandlerId, EventFieldInfo fiel

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)
Expand Down Expand Up @@ -342,7 +343,7 @@ internal void AssignEventHandlerId(ref RenderTreeFrame frame)
{
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
Expand All @@ -352,7 +353,7 @@ internal void AssignEventHandlerId(ref RenderTreeFrame frame)
// 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
Expand All @@ -364,7 +365,7 @@ internal void AssignEventHandlerId(ref RenderTreeFrame frame)
// 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>
Expand Down
Loading

0 comments on commit 38e166f

Please sign in to comment.