Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
Ryan Nowak authored and rynowak committed May 24, 2019
1 parent 6115c18 commit 51fdeb6
Show file tree
Hide file tree
Showing 6 changed files with 507 additions and 827 deletions.
2 changes: 1 addition & 1 deletion src/Components/Analyzers/src/ComponentSymbols.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public static bool TryCreate(Compilation compilation, out ComponentSymbols symbo
return false;
}

var dictionary = compilation.GetTypeByMetadataName(typeof(Dictionary<,>).FullName);
var dictionary = compilation.GetTypeByMetadataName("System.Collections.Generic.Dictionary`2");
var @string = compilation.GetSpecialType(SpecialType.System_String);
var @object = compilation.GetSpecialType(SpecialType.System_Object);
if (dictionary == null || @string == null || @object == null)
Expand Down
11 changes: 0 additions & 11 deletions src/Components/Components/src/RenderTree/ArrayBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -97,17 +97,6 @@ internal int Append(T[] source, int startIndex, int length)
public void Overwrite(int index, in T value)
=> _items[index] = value;

/// <summary>
/// Removes the item at the specified index.
/// </summary>
/// <param name="index">The index of the item to remove.</param>
public void RemoveAt(int index)
{
Array.Copy(_items, index + 1, _items, index, _itemsInUse - 1 - index);
Array.Clear(_items, _itemsInUse - 1, 1); // Clear last item
_itemsInUse--;
}

/// <summary>
/// Removes the last item.
/// </summary>
Expand Down
166 changes: 140 additions & 26 deletions src/Components/Components/src/RenderTree/RenderTreeBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ public class RenderTreeBuilder
private readonly ArrayBuilder<RenderTreeFrame> _entries = new ArrayBuilder<RenderTreeFrame>(10);
private readonly Stack<int> _openElementIndices = new Stack<int>();
private RenderTreeFrameType? _lastNonAttributeFrameType;
private bool _hasSeenAddMultipleAttributes;
private Dictionary<string, int> _seenAttributeNames;

/// <summary>
/// The reserved parameter name used for supplying child content.
Expand All @@ -53,6 +55,14 @@ public RenderTreeBuilder(Renderer renderer)
/// <param name="elementName">A value representing the type of the element.</param>
public void OpenElement(int sequence, string elementName)
{
// We are entering a new scope, since we track the "duplicate attributes" per
// element/component we might need to clean them up now.
if (_hasSeenAddMultipleAttributes)
{
var indexOfLastElementOrComponent = _openElementIndices.Peek();
ProcessDuplicateAttributes(first: indexOfLastElementOrComponent + 1);
}

_openElementIndices.Push(_entries.Count);
Append(RenderTreeFrame.Element(sequence, elementName));
}
Expand All @@ -64,6 +74,14 @@ public void OpenElement(int sequence, string elementName)
public void CloseElement()
{
var indexOfEntryBeingClosed = _openElementIndices.Pop();

// We might be closing an element with only attributes, run the duplicate cleanup pass
// if necessary.
if (_hasSeenAddMultipleAttributes)
{
ProcessDuplicateAttributes(first: indexOfEntryBeingClosed + 1);
}

ref var entry = ref _entries.Buffer[indexOfEntryBeingClosed];
entry = entry.WithElementSubtreeLength(_entries.Count - indexOfEntryBeingClosed);
}
Expand Down Expand Up @@ -160,7 +178,7 @@ public void AddAttribute(int sequence, string name, bool value)
}
else
{
ClearAttributesWithName(name);
TrackAttributeName(name);
}
}

Expand All @@ -185,7 +203,7 @@ public void AddAttribute(int sequence, string name, string value)
}
else
{
ClearAttributesWithName(name);
TrackAttributeName(name);
}
}

Expand Down Expand Up @@ -286,7 +304,7 @@ public void AddAttribute(int sequence, string name, MulticastDelegate value)
}
else
{
ClearAttributesWithName(name);
TrackAttributeName(name);
}
}

Expand Down Expand Up @@ -327,6 +345,11 @@ public void AddAttribute(int sequence, string name, EventCallback value)
// just need to retain the delegate. This allows us to avoid an allocation.
Append(RenderTreeFrame.Attribute(sequence, name, value.Delegate));
}
else
{
// Track the attribute name if needed since we elided the frame.
TrackAttributeName(name);
}
}

/// <summary>
Expand Down Expand Up @@ -366,6 +389,11 @@ public void AddAttribute<T>(int sequence, string name, EventCallback<T> value)
// just need to retain the delegate. This allows us to avoid an allocation.
Append(RenderTreeFrame.Attribute(sequence, name, value.Delegate));
}
else
{
// Track the attribute name if needed since we elided the frame.
TrackAttributeName(name);
}
}

/// <summary>
Expand All @@ -386,7 +414,7 @@ public void AddAttribute(int sequence, string name, object value)
if (value == null)
{
// Treat 'null' attribute values for elements as a conditional attribute.
ClearAttributesWithName(name);
TrackAttributeName(name);
}
else if (value is bool boolValue)
{
Expand All @@ -397,7 +425,7 @@ public void AddAttribute(int sequence, string name, object value)
else
{
// Don't add anything for false bool value.
ClearAttributesWithName(name);
TrackAttributeName(name);
}
}
else if (value is IEventCallback callbackValue)
Expand All @@ -408,7 +436,7 @@ public void AddAttribute(int sequence, string name, object value)
}
else
{
ClearAttributesWithName(name);
TrackAttributeName(name);
}
}
else if (value is MulticastDelegate)
Expand Down Expand Up @@ -475,6 +503,8 @@ public void AddMultipleAttributes<T>(int sequence, IEnumerable<KeyValuePair<stri

if (attributes != null)
{
_hasSeenAddMultipleAttributes = true;

foreach (var attribute in attributes)
{
// This will call the AddAttribute(int, string, object) overload.
Expand Down Expand Up @@ -543,6 +573,14 @@ public void SetKey(object value)

private void OpenComponentUnchecked(int sequence, Type componentType)
{
// We are entering a new scope, since we track the "duplicate attributes" per
// element/component we might need to clean them up now.
if (_hasSeenAddMultipleAttributes)
{
var indexOfLastElementOrComponent = _openElementIndices.Peek();
ProcessDuplicateAttributes(first: indexOfLastElementOrComponent + 1);
}

_openElementIndices.Push(_entries.Count);
Append(RenderTreeFrame.ChildComponent(sequence, componentType));
}
Expand All @@ -554,6 +592,14 @@ private void OpenComponentUnchecked(int sequence, Type componentType)
public void CloseComponent()
{
var indexOfEntryBeingClosed = _openElementIndices.Pop();

// We might be closing a component with only attributes. Run the attribute cleanup pass
// if necessary.
if (_hasSeenAddMultipleAttributes)
{
ProcessDuplicateAttributes(first: indexOfEntryBeingClosed + 1);
}

ref var entry = ref _entries.Buffer[indexOfEntryBeingClosed];
entry = entry.WithComponentSubtreeLength(_entries.Count - indexOfEntryBeingClosed);
}
Expand Down Expand Up @@ -640,6 +686,8 @@ public void Clear()
_entries.Clear();
_openElementIndices.Clear();
_lastNonAttributeFrameType = null;
_hasSeenAddMultipleAttributes = false;
_seenAttributeNames?.Clear();
}

/// <summary>
Expand All @@ -652,11 +700,6 @@ public ArrayRange<RenderTreeFrame> GetFrames() =>
private void Append(in RenderTreeFrame frame)
{
var frameType = frame.FrameType;
if (frameType == RenderTreeFrameType.Attribute)
{
ClearAttributesWithName(frame.AttributeName);
}

_entries.Append(frame);

if (frameType != RenderTreeFrameType.Attribute)
Expand All @@ -665,28 +708,99 @@ private void Append(in RenderTreeFrame frame)
}
}

private void ClearAttributesWithName(string name)
// Internal for testing
internal void ProcessDuplicateAttributes(int first)
{
// When an AddAttribute or AddMultipleAttributes method is called, we need to clear
// any prior attributes that have the same attribute name for the current element
// or component.
//
// This is how we enforce the *last attribute wins* semantic.
Debug.Assert(_openElementIndices.Count > 0);
Debug.Assert(_hasSeenAddMultipleAttributes);

// Start at the last open element/component and iterate forward until
// we find a duplicate.
//
// Since we prevent duplicates, we can always stop after finding one.
for (var i = _openElementIndices.Peek(); i < _entries.Count; i++)
// When AddMultipleAttributes method has been called, we need to postprocess attributes while closing
// the element/component. However, we also don't know the end index we should look at because it
// will contain nested content.
var buffer = _entries.Buffer;
var last = _entries.Count - 1;

for (var i = first; i <= last; i++ )
{
if (_entries.Buffer[i].FrameType == RenderTreeFrameType.Attribute &&
string.Equals(name, _entries.Buffer[i].AttributeName, StringComparison.OrdinalIgnoreCase))
if (buffer[i].FrameType != RenderTreeFrameType.Attribute)
{
_entries.RemoveAt(i);
last = i - 1;
break;
}
}

// Now that we've found the last attribute, we can iterate backwards and process duplicates.
var seenAttributeNames = (_seenAttributeNames ??= new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase));
for (var i = last; i >= first; i--)
{
ref var frame = ref buffer[i];
Debug.Assert(frame.FrameType == RenderTreeFrameType.Attribute, $"Frame type is {frame.FrameType} at {i}");

if (!seenAttributeNames.TryGetValue(frame.AttributeName, out var index))
{
// This is the first time seeing this attribute name. Add to the dictionary and move on.
seenAttributeNames.Add(frame.AttributeName, i);
}
else if (index < i)
{
// This attribute is overriding a "silent frame" where we didn't create a frame for an AddAttribute call.
// This is the case for a null event handler, or bool false value.
//
// We need to update our tracking, in case the attribute appeared 3 or more times.
seenAttributeNames[frame.AttributeName] = i;
}
else if (index > i)
{
// This attribute has been overridden. For now, blank out its name to *mark* it. We'll do a pass
// later to wipe it out.
frame = default;
}
else
{
// OK so index == i. How is that possible? Well it's possible for a "silent frame" immediately
// followed by setting the same attribute. Think of it this way, when we create a "silent frame"
// we have to track that attribute name with *some* index.
//
// The only index value we can safely use is _entries.Count (next available). This is fine because
// we never use these indexes to look stuff up, only for comparison.
//
// That gets you here, and there's no action to take.
}
}

// This is the pass where we cleanup attributes that have been wiped out.
//
// We copy the entries we're keeping into the earlier parts of the list (preserving order).
var offset = first;
for (var i = first; i <= last; i++)
{
ref var frame = ref buffer[i];
if (frame.FrameType != RenderTreeFrameType.None)
{
buffer[offset++] = frame;
}
}

// Clean up now unused space at the end of the list.
for (; offset <= last; offset++)
{
_entries.RemoveLast();
}


seenAttributeNames.Clear();
_hasSeenAddMultipleAttributes = false;
}

// Internal for testing
internal void TrackAttributeName(string name)
{
if (!_hasSeenAddMultipleAttributes)
{
return;
}

var seenAttributeNames = (_seenAttributeNames ??= new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase));
seenAttributeNames[name] = _entries.Count; // See comment in ProcessAttributes for why this is OK.
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ namespace Microsoft.AspNetCore.Components.RenderTree
/// </summary>
public enum RenderTreeFrameType: int
{
/// <summary>
/// Used only for unintialized frames.
/// </summary>
None = 0,

/// <summary>
/// Represents a container for other frames.
/// </summary>
Expand Down
Loading

0 comments on commit 51fdeb6

Please sign in to comment.