Skip to content

Named events tracking #49298

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
Jul 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ public RenderTreeDiffBuilderBenchmark()
public void ComputeDiff_SingleFormField()
{
builder.ClearStateForCurrentBatch();
var diff = RenderTreeDiffBuilder.ComputeDiff(renderer, builder, 0, modified.GetFrames(), original.GetFrames(), original.GetNamedEvents());
var diff = RenderTreeDiffBuilder.ComputeDiff(renderer, builder, 0, modified.GetFrames(), original.GetFrames());
GC.KeepAlive(diff);
}

Expand Down
16 changes: 13 additions & 3 deletions src/Components/Components/src/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,23 @@ Microsoft.AspNetCore.Components.NavigationManager.ToAbsoluteUri(string? relative
Microsoft.AspNetCore.Components.Rendering.ComponentState.LogicalParentComponentState.get -> Microsoft.AspNetCore.Components.Rendering.ComponentState?
Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder.AddComponentParameter(int sequence, string! name, Microsoft.AspNetCore.Components.IComponentRenderMode! renderMode) -> void
Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder.AddComponentRenderMode(int sequence, Microsoft.AspNetCore.Components.IComponentRenderMode! renderMode) -> void
Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder.SetEventHandlerName(string! eventHandlerName) -> void
*REMOVED*Microsoft.AspNetCore.Components.RouteData.RouteData(System.Type! pageType, System.Collections.Generic.IReadOnlyDictionary<string!, object!>! routeValues) -> void
*REMOVED*Microsoft.AspNetCore.Components.RouteData.RouteValues.get -> System.Collections.Generic.IReadOnlyDictionary<string!, object!>!
Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder.AddNamedEvent(int sequence, string! eventType, string! assignedName) -> void
Microsoft.AspNetCore.Components.RenderTree.ComponentFrameFlags
Microsoft.AspNetCore.Components.RenderTree.ComponentFrameFlags.HasCallerSpecifiedRenderMode = 1 -> Microsoft.AspNetCore.Components.RenderTree.ComponentFrameFlags
Microsoft.AspNetCore.Components.RenderTree.NamedEvent
Microsoft.AspNetCore.Components.RenderTree.NamedEvent.AssignedName.get -> string!
Microsoft.AspNetCore.Components.RenderTree.NamedEvent.ComponentId.get -> int
Microsoft.AspNetCore.Components.RenderTree.NamedEvent.EventType.get -> string!
Microsoft.AspNetCore.Components.RenderTree.NamedEvent.FrameIndex.get -> int
Microsoft.AspNetCore.Components.RenderTree.NamedEvent.NamedEvent() -> void
Microsoft.AspNetCore.Components.RenderTree.NamedEvent.NamedEvent(int componentId, int frameIndex, string! eventType, string! assignedName) -> void
Microsoft.AspNetCore.Components.RenderTree.RenderBatch.AddedNamedEvents.get -> Microsoft.AspNetCore.Components.RenderTree.ArrayRange<Microsoft.AspNetCore.Components.RenderTree.NamedEvent>?
Microsoft.AspNetCore.Components.RenderTree.RenderBatch.RemovedNamedEvents.get -> Microsoft.AspNetCore.Components.RenderTree.ArrayRange<Microsoft.AspNetCore.Components.RenderTree.NamedEvent>?
Microsoft.AspNetCore.Components.RenderTree.RenderTreeFrame.ComponentFrameFlags.get -> Microsoft.AspNetCore.Components.RenderTree.ComponentFrameFlags
Microsoft.AspNetCore.Components.RenderTree.RenderTreeFrameType.ComponentRenderMode = 9 -> Microsoft.AspNetCore.Components.RenderTree.RenderTreeFrameType
Microsoft.AspNetCore.Components.RenderTree.RenderTreeFrameType.NamedEvent = 10 -> Microsoft.AspNetCore.Components.RenderTree.RenderTreeFrameType
Microsoft.AspNetCore.Components.RouteData.RouteData(System.Type! pageType, System.Collections.Generic.IReadOnlyDictionary<string!, object?>! routeValues) -> void
Microsoft.AspNetCore.Components.RouteData.RouteValues.get -> System.Collections.Generic.IReadOnlyDictionary<string!, object?>!
Microsoft.AspNetCore.Components.Routing.IRoutingStateProvider
Expand Down Expand Up @@ -91,6 +101,6 @@ virtual Microsoft.AspNetCore.Components.RenderTree.Renderer.AddPendingTask(Micro
virtual Microsoft.AspNetCore.Components.RenderTree.Renderer.CreateComponentState(int componentId, Microsoft.AspNetCore.Components.IComponent! component, Microsoft.AspNetCore.Components.Rendering.ComponentState? parentComponentState) -> Microsoft.AspNetCore.Components.Rendering.ComponentState!
virtual Microsoft.AspNetCore.Components.RenderTree.Renderer.DispatchEventAsync(ulong eventHandlerId, Microsoft.AspNetCore.Components.RenderTree.EventFieldInfo? fieldInfo, System.EventArgs! eventArgs, bool quiesce) -> System.Threading.Tasks.Task!
virtual Microsoft.AspNetCore.Components.RenderTree.Renderer.ResolveComponentForRenderMode(System.Type! componentType, int? parentComponentId, Microsoft.AspNetCore.Components.IComponentActivator! componentActivator, Microsoft.AspNetCore.Components.IComponentRenderMode! renderMode) -> Microsoft.AspNetCore.Components.IComponent!
virtual Microsoft.AspNetCore.Components.RenderTree.Renderer.ShouldTrackNamedEventHandlers() -> bool
virtual Microsoft.AspNetCore.Components.RenderTree.Renderer.TrackNamedEventId(ulong eventHandlerId, int componentId, string! eventHandlerName) -> void
~Microsoft.AspNetCore.Components.RenderTree.RenderTreeFrame.ComponentRenderMode.get -> Microsoft.AspNetCore.Components.IComponentRenderMode
~Microsoft.AspNetCore.Components.RenderTree.RenderTreeFrame.NamedEventAssignedName.get -> string
~Microsoft.AspNetCore.Components.RenderTree.RenderTreeFrame.NamedEventType.get -> string
38 changes: 38 additions & 0 deletions src/Components/Components/src/RenderTree/NamedEvent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace Microsoft.AspNetCore.Components.RenderTree;

/// <summary>
/// Types in the Microsoft.AspNetCore.Components.RenderTree are not recommended for use outside
/// of the Blazor framework. These types will change in a future release.
/// </summary>
/// <remarks>
/// Constructs an instance of <see cref="NamedEvent"/>.
/// </remarks>
/// <param name="componentId">The ID of the component holding the named value.</param>
/// <param name="frameIndex">The index of the <see cref="RenderTreeFrameType.NamedEvent"/> frame within the component's current render output.</param>
/// <param name="eventType">The event type.</param>
/// <param name="assignedName">The application-assigned name.</param>
public readonly struct NamedEvent(int componentId, int frameIndex, string eventType, string assignedName)
{
/// <summary>
/// The ID of the component holding the named event.
/// </summary>
public readonly int ComponentId { get; } = componentId;

/// <summary>
/// The index of the <see cref="RenderTreeFrameType.NamedEvent"/> frame within the component's current render output.
/// </summary>
public readonly int FrameIndex { get; } = frameIndex;

/// <summary>
/// The event type.
/// </summary>
public readonly string EventType { get; } = eventType;

/// <summary>
/// The application-assigned name.
/// </summary>
public readonly string AssignedName { get; } = assignedName;
}
16 changes: 15 additions & 1 deletion src/Components/Components/src/RenderTree/RenderBatch.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,16 +33,30 @@ public readonly struct RenderBatch
/// </summary>
public ArrayRange<ulong> DisposedEventHandlerIDs { get; }

/// <summary>
/// Gets the named events that were added, or null.
/// </summary>
public ArrayRange<NamedEvent>? AddedNamedEvents { get; }

/// <summary>
/// Gets the named events that were removed, or null.
/// </summary>
public ArrayRange<NamedEvent>? RemovedNamedEvents { get; }

internal RenderBatch(
ArrayRange<RenderTreeDiff> updatedComponents,
ArrayRange<RenderTreeFrame> referenceFrames,
ArrayRange<int> disposedComponentIDs,
ArrayRange<ulong> disposedEventHandlerIDs)
ArrayRange<ulong> disposedEventHandlerIDs,
ArrayRange<NamedEvent>? addedNamedEvents,
ArrayRange<NamedEvent>? removedNamedEvents)
{
UpdatedComponents = updatedComponents;
ReferenceFrames = referenceFrames;
DisposedComponentIDs = disposedComponentIDs;
DisposedEventHandlerIDs = disposedEventHandlerIDs;
AddedNamedEvents = addedNamedEvents;
RemovedNamedEvents = removedNamedEvents;
}
}

72 changes: 43 additions & 29 deletions src/Components/Components/src/RenderTree/RenderTreeDiffBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,7 @@ public static RenderTreeDiff ComputeDiff(
RenderBatchBuilder batchBuilder,
int componentId,
ArrayRange<RenderTreeFrame> oldTree,
ArrayRange<RenderTreeFrame> newTree,
Dictionary<string, int>? namedEventIndexes)
ArrayRange<RenderTreeFrame> newTree)
{
var editsBuffer = batchBuilder.EditsBuffer;
var editsBufferStartLength = editsBuffer.Count;
Expand All @@ -35,34 +34,11 @@ public static RenderTreeDiff ComputeDiff(
var editsSegment = editsBuffer.ToSegment(editsBufferStartLength, editsBuffer.Count);
var result = new RenderTreeDiff(componentId, editsSegment);

// Named event handlers name must be unique globally and stable over the time period we are deciding where to
// dispatch a given named event.
// Once a component has defined a named event handler with a concrete name, no other component instance can
// define a named event handler with that name.
//
// At this stage, we only ensure that the named event handler is unique per component instance, as that,
// combined with the check that the EndpointRenderer does, is enough to ensure the uniqueness and the stability
// of the named event handler over time **globally**.
//
// Tracking and uniqueness are enforced when we are trying to dispatch an event to a named event handler, since in
// any other case we don't actually track the named event handlers. We do this because:
// 1) We don't want to break the user's app if we don't have to.
// 2) We don't have to pay the cost of continously tracking all events all the time to throw.
// That's why raising the error is delayed until we are forced to make a decission.
if (namedEventIndexes != null)
{
foreach (var (name, index) in namedEventIndexes)
{
ref var frame = ref newTree.Array[index];
renderer.TrackNamedEventId(frame.AttributeEventHandlerId, componentId, name);
}
}

return result;
}

public static void DisposeFrames(RenderBatchBuilder batchBuilder, ArrayRange<RenderTreeFrame> frames)
=> DisposeFramesInRange(batchBuilder, frames.Array, 0, frames.Count);
public static void DisposeFrames(RenderBatchBuilder batchBuilder, int componentId, ArrayRange<RenderTreeFrame> frames)
=> DisposeFramesInRange(batchBuilder, componentId, frames.Array, 0, frames.Count);

private static void AppendDiffEntriesForRange(
ref DiffContext diffContext,
Expand Down Expand Up @@ -749,6 +725,22 @@ private static void AppendDiffEntriesForFramesWithSameSequence(
break;
}

case RenderTreeFrameType.NamedEvent:
{
// We don't have a use case for the event types changing, so we don't even check that. We assume for a given sequence number
// the event type is always a constant. What can change is the frame index and the assigned name.
if (oldFrameIndex != newFrameIndex
|| !string.Equals(oldFrame.NamedEventAssignedName, newFrame.NamedEventAssignedName, StringComparison.Ordinal))
{
// We could track the updates as a concept in its own right, but this situation will be uncommon,
// so it's enough to treat it as a delete+add
diffContext.BatchBuilder.RemoveNamedEvent(diffContext.ComponentId, oldFrameIndex, ref oldFrame);
diffContext.BatchBuilder.AddNamedEvent(diffContext.ComponentId, newFrameIndex, ref newFrame);
}

break;
}

// We don't handle attributes here, they have their own diff logic.
// See AppendDiffEntriesForAttributeFrame
default:
Expand Down Expand Up @@ -844,6 +836,11 @@ private static void InsertNewFrame(ref DiffContext diffContext, int newFrameInde
InitializeNewComponentReferenceCaptureFrame(ref diffContext, ref newFrame);
break;
}
case RenderTreeFrameType.NamedEvent:
{
InitializeNewNamedEvent(ref diffContext, newFrameIndex);
break;
}
default:
throw new NotImplementedException($"Unexpected frame type during {nameof(InsertNewFrame)}: {newFrame.FrameTypeField}");
}
Expand All @@ -868,7 +865,7 @@ private static void RemoveOldFrame(ref DiffContext diffContext, int oldFrameInde
case RenderTreeFrameType.Element:
{
var endIndexExcl = oldFrameIndex + oldFrame.ElementSubtreeLengthField;
DisposeFramesInRange(diffContext.BatchBuilder, oldTree, oldFrameIndex, endIndexExcl);
DisposeFramesInRange(diffContext.BatchBuilder, diffContext.ComponentId, oldTree, oldFrameIndex, endIndexExcl);
diffContext.Edits.Append(RenderTreeEdit.RemoveFrame(diffContext.SiblingIndex));
break;
}
Expand All @@ -889,6 +886,11 @@ private static void RemoveOldFrame(ref DiffContext diffContext, int oldFrameInde
diffContext.Edits.Append(RenderTreeEdit.RemoveFrame(diffContext.SiblingIndex));
break;
}
case RenderTreeFrameType.NamedEvent:
{
diffContext.BatchBuilder.RemoveNamedEvent(diffContext.ComponentId, oldFrameIndex, ref diffContext.OldTree[oldFrameIndex]);
break;
}
default:
throw new NotImplementedException($"Unexpected frame type during {nameof(RemoveOldFrame)}: {oldFrame.FrameTypeField}");
}
Expand Down Expand Up @@ -944,6 +946,9 @@ private static void InitializeNewSubtree(ref DiffContext diffContext, int frameI
case RenderTreeFrameType.ComponentReferenceCapture:
InitializeNewComponentReferenceCaptureFrame(ref diffContext, ref frame);
break;
case RenderTreeFrameType.NamedEvent:
InitializeNewNamedEvent(ref diffContext, i);
break;
}
}
}
Expand Down Expand Up @@ -1001,7 +1006,12 @@ private static void InitializeNewComponentReferenceCaptureFrame(ref DiffContext
newFrame.ComponentReferenceCaptureActionField(componentInstance);
}

private static void DisposeFramesInRange(RenderBatchBuilder batchBuilder, RenderTreeFrame[] frames, int startIndex, int endIndexExcl)
private static void InitializeNewNamedEvent(ref DiffContext diffContext, int newTreeFrameIndex)
{
diffContext.BatchBuilder.AddNamedEvent(diffContext.ComponentId, newTreeFrameIndex, ref diffContext.NewTree[newTreeFrameIndex]);
}

private static void DisposeFramesInRange(RenderBatchBuilder batchBuilder, int componentId, RenderTreeFrame[] frames, int startIndex, int endIndexExcl)
{
for (var i = startIndex; i < endIndexExcl; i++)
{
Expand All @@ -1014,6 +1024,10 @@ private static void DisposeFramesInRange(RenderBatchBuilder batchBuilder, Render
{
batchBuilder.DisposedEventHandlerIds.Append(frame.AttributeEventHandlerIdField);
}
else if (frame.FrameTypeField == RenderTreeFrameType.NamedEvent)
{
batchBuilder.RemoveNamedEvent(componentId, i, ref frames[i]);
}
}
}

Expand Down
25 changes: 25 additions & 0 deletions src/Components/Components/src/RenderTree/RenderTreeFrame.cs
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,25 @@ public IComponentRenderMode ComponentRenderMode
}
}

// --------------------------------------------------------------------------------
// RenderTreeFrameType.NamedEvent
// --------------------------------------------------------------------------------

[FieldOffset(16)] internal string NamedEventTypeField;
[FieldOffset(24)] internal string NamedEventAssignedNameField;

/// <summary>
/// If the <see cref="FrameType"/> property equals <see cref="RenderTreeFrameType.NamedEvent"/>,
/// gets the event type. Otherwise, the value is undefined.
/// </summary>
public string NamedEventType => NamedEventTypeField;

/// <summary>
/// If the <see cref="FrameType"/> property equals <see cref="RenderTreeFrameType.NamedEvent"/>,
/// gets the assigned name. Otherwise, the value is undefined.
/// </summary>
public string NamedEventAssignedName => NamedEventAssignedNameField;

// Element constructor
private RenderTreeFrame(int sequence, int elementSubtreeLength, string elementName, object elementKey)
: this()
Expand Down Expand Up @@ -395,6 +414,12 @@ internal static RenderTreeFrame ElementReferenceCapture(int sequence, Action<Ele
internal static RenderTreeFrame ComponentReferenceCapture(int sequence, Action<object> componentReferenceCaptureAction, int parentFrameIndex)
=> new RenderTreeFrame(sequence, componentReferenceCaptureAction: componentReferenceCaptureAction, parentFrameIndex: parentFrameIndex);

internal static RenderTreeFrame NamedEvent(int sequence, string eventType, string assignedName)
=> new RenderTreeFrame { SequenceField = sequence, FrameTypeField = RenderTreeFrameType.NamedEvent, NamedEventTypeField = eventType, NamedEventAssignedNameField = assignedName };

internal static RenderTreeFrame ComponentRenderModeFrame(int sequence, IComponentRenderMode renderMode)
=> new RenderTreeFrame { SequenceField = sequence, FrameTypeField = RenderTreeFrameType.ComponentRenderMode, ComponentRenderModeField = renderMode };
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unrelated, but I realised the ComponentRenderMode frames were not yet handled by RenderBatchWriter. It just needs to disregard them. This helper supports the unit test for that.


internal RenderTreeFrame WithElementSubtreeLength(int elementSubtreeLength)
=> new RenderTreeFrame(SequenceField, elementSubtreeLength: elementSubtreeLength, ElementNameField, ElementKeyField);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,4 +149,20 @@ public void AppendComponentRenderMode(int sequence, IComponentRenderMode renderM
ComponentRenderModeField = renderMode,
};
}

public void AppendNamedEvent(int sequence, string eventType, string assignedName)
{
if (_itemsInUse == _items.Length)
{
GrowBuffer(_items.Length * 2);
}

_items[_itemsInUse++] = new RenderTreeFrame
{
SequenceField = sequence,
FrameTypeField = RenderTreeFrameType.NamedEvent,
NamedEventTypeField = eventType,
NamedEventAssignedNameField = assignedName,
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -63,4 +63,9 @@ public enum RenderTreeFrameType : short
/// Represents an instruction to use a specified render mode for the component.
/// </summary>
ComponentRenderMode = 9,

/// <summary>
/// Represents an application-assigned name for an event.
/// </summary>
NamedEvent = 10,
}
16 changes: 0 additions & 16 deletions src/Components/Components/src/RenderTree/Renderer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -636,22 +636,6 @@ internal void TrackReplacedEventHandlerId(ulong oldEventHandlerId, ulong newEven
_eventHandlerIdReplacements.Add(oldEventHandlerId, newEventHandlerId);
}

/// <summary>
/// Tracks named events defined during rendering.
/// </summary>
/// <param name="eventHandlerId">The event handler ID associated with the named event.</param>
/// <param name="componentId">The component ID defining the name.</param>
/// <param name="eventHandlerName">The event name.</param>
protected internal virtual void TrackNamedEventId(ulong eventHandlerId, int componentId, string eventHandlerName)
{
}

/// <summary>
/// Indicates whether named event handlers should be tracked.
/// </summary>
/// <returns><c>true</c> if named event handlers should be tracked; <c>false</c> otherwise.</returns>
protected internal virtual bool ShouldTrackNamedEventHandlers() => false;

private EventCallback GetRequiredEventCallback(ulong eventHandlerId)
{
if (!_eventBindings.TryGetValue(eventHandlerId, out var callback))
Expand Down
Loading