Skip to content

Commit c8caba5

Browse files
Named events tracking (#49298)
1 parent 246bac2 commit c8caba5

38 files changed

+831
-868
lines changed

src/Components/Components/perf/RenderTreeDiffBuilderBenchmark.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ public RenderTreeDiffBuilderBenchmark()
7979
public void ComputeDiff_SingleFormField()
8080
{
8181
builder.ClearStateForCurrentBatch();
82-
var diff = RenderTreeDiffBuilder.ComputeDiff(renderer, builder, 0, modified.GetFrames(), original.GetFrames(), original.GetNamedEvents());
82+
var diff = RenderTreeDiffBuilder.ComputeDiff(renderer, builder, 0, modified.GetFrames(), original.GetFrames());
8383
GC.KeepAlive(diff);
8484
}
8585

src/Components/Components/src/PublicAPI.Unshipped.txt

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,23 @@ Microsoft.AspNetCore.Components.NavigationManager.ToAbsoluteUri(string? relative
2727
Microsoft.AspNetCore.Components.Rendering.ComponentState.LogicalParentComponentState.get -> Microsoft.AspNetCore.Components.Rendering.ComponentState?
2828
Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder.AddComponentParameter(int sequence, string! name, Microsoft.AspNetCore.Components.IComponentRenderMode! renderMode) -> void
2929
Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder.AddComponentRenderMode(int sequence, Microsoft.AspNetCore.Components.IComponentRenderMode! renderMode) -> void
30-
Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder.SetEventHandlerName(string! eventHandlerName) -> void
3130
*REMOVED*Microsoft.AspNetCore.Components.RouteData.RouteData(System.Type! pageType, System.Collections.Generic.IReadOnlyDictionary<string!, object!>! routeValues) -> void
3231
*REMOVED*Microsoft.AspNetCore.Components.RouteData.RouteValues.get -> System.Collections.Generic.IReadOnlyDictionary<string!, object!>!
32+
Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder.AddNamedEvent(int sequence, string! eventType, string! assignedName) -> void
3333
Microsoft.AspNetCore.Components.RenderTree.ComponentFrameFlags
3434
Microsoft.AspNetCore.Components.RenderTree.ComponentFrameFlags.HasCallerSpecifiedRenderMode = 1 -> Microsoft.AspNetCore.Components.RenderTree.ComponentFrameFlags
35+
Microsoft.AspNetCore.Components.RenderTree.NamedEvent
36+
Microsoft.AspNetCore.Components.RenderTree.NamedEvent.AssignedName.get -> string!
37+
Microsoft.AspNetCore.Components.RenderTree.NamedEvent.ComponentId.get -> int
38+
Microsoft.AspNetCore.Components.RenderTree.NamedEvent.EventType.get -> string!
39+
Microsoft.AspNetCore.Components.RenderTree.NamedEvent.FrameIndex.get -> int
40+
Microsoft.AspNetCore.Components.RenderTree.NamedEvent.NamedEvent() -> void
41+
Microsoft.AspNetCore.Components.RenderTree.NamedEvent.NamedEvent(int componentId, int frameIndex, string! eventType, string! assignedName) -> void
42+
Microsoft.AspNetCore.Components.RenderTree.RenderBatch.AddedNamedEvents.get -> Microsoft.AspNetCore.Components.RenderTree.ArrayRange<Microsoft.AspNetCore.Components.RenderTree.NamedEvent>?
43+
Microsoft.AspNetCore.Components.RenderTree.RenderBatch.RemovedNamedEvents.get -> Microsoft.AspNetCore.Components.RenderTree.ArrayRange<Microsoft.AspNetCore.Components.RenderTree.NamedEvent>?
3544
Microsoft.AspNetCore.Components.RenderTree.RenderTreeFrame.ComponentFrameFlags.get -> Microsoft.AspNetCore.Components.RenderTree.ComponentFrameFlags
3645
Microsoft.AspNetCore.Components.RenderTree.RenderTreeFrameType.ComponentRenderMode = 9 -> Microsoft.AspNetCore.Components.RenderTree.RenderTreeFrameType
46+
Microsoft.AspNetCore.Components.RenderTree.RenderTreeFrameType.NamedEvent = 10 -> Microsoft.AspNetCore.Components.RenderTree.RenderTreeFrameType
3747
Microsoft.AspNetCore.Components.RouteData.RouteData(System.Type! pageType, System.Collections.Generic.IReadOnlyDictionary<string!, object?>! routeValues) -> void
3848
Microsoft.AspNetCore.Components.RouteData.RouteValues.get -> System.Collections.Generic.IReadOnlyDictionary<string!, object?>!
3949
Microsoft.AspNetCore.Components.Routing.IRoutingStateProvider
@@ -91,6 +101,6 @@ virtual Microsoft.AspNetCore.Components.RenderTree.Renderer.AddPendingTask(Micro
91101
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!
92102
virtual Microsoft.AspNetCore.Components.RenderTree.Renderer.DispatchEventAsync(ulong eventHandlerId, Microsoft.AspNetCore.Components.RenderTree.EventFieldInfo? fieldInfo, System.EventArgs! eventArgs, bool quiesce) -> System.Threading.Tasks.Task!
93103
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!
94-
virtual Microsoft.AspNetCore.Components.RenderTree.Renderer.ShouldTrackNamedEventHandlers() -> bool
95-
virtual Microsoft.AspNetCore.Components.RenderTree.Renderer.TrackNamedEventId(ulong eventHandlerId, int componentId, string! eventHandlerName) -> void
96104
~Microsoft.AspNetCore.Components.RenderTree.RenderTreeFrame.ComponentRenderMode.get -> Microsoft.AspNetCore.Components.IComponentRenderMode
105+
~Microsoft.AspNetCore.Components.RenderTree.RenderTreeFrame.NamedEventAssignedName.get -> string
106+
~Microsoft.AspNetCore.Components.RenderTree.RenderTreeFrame.NamedEventType.get -> string
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
namespace Microsoft.AspNetCore.Components.RenderTree;
5+
6+
/// <summary>
7+
/// Types in the Microsoft.AspNetCore.Components.RenderTree are not recommended for use outside
8+
/// of the Blazor framework. These types will change in a future release.
9+
/// </summary>
10+
/// <remarks>
11+
/// Constructs an instance of <see cref="NamedEvent"/>.
12+
/// </remarks>
13+
/// <param name="componentId">The ID of the component holding the named value.</param>
14+
/// <param name="frameIndex">The index of the <see cref="RenderTreeFrameType.NamedEvent"/> frame within the component's current render output.</param>
15+
/// <param name="eventType">The event type.</param>
16+
/// <param name="assignedName">The application-assigned name.</param>
17+
public readonly struct NamedEvent(int componentId, int frameIndex, string eventType, string assignedName)
18+
{
19+
/// <summary>
20+
/// The ID of the component holding the named event.
21+
/// </summary>
22+
public readonly int ComponentId { get; } = componentId;
23+
24+
/// <summary>
25+
/// The index of the <see cref="RenderTreeFrameType.NamedEvent"/> frame within the component's current render output.
26+
/// </summary>
27+
public readonly int FrameIndex { get; } = frameIndex;
28+
29+
/// <summary>
30+
/// The event type.
31+
/// </summary>
32+
public readonly string EventType { get; } = eventType;
33+
34+
/// <summary>
35+
/// The application-assigned name.
36+
/// </summary>
37+
public readonly string AssignedName { get; } = assignedName;
38+
}

src/Components/Components/src/RenderTree/RenderBatch.cs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,16 +33,30 @@ public readonly struct RenderBatch
3333
/// </summary>
3434
public ArrayRange<ulong> DisposedEventHandlerIDs { get; }
3535

36+
/// <summary>
37+
/// Gets the named events that were added, or null.
38+
/// </summary>
39+
public ArrayRange<NamedEvent>? AddedNamedEvents { get; }
40+
41+
/// <summary>
42+
/// Gets the named events that were removed, or null.
43+
/// </summary>
44+
public ArrayRange<NamedEvent>? RemovedNamedEvents { get; }
45+
3646
internal RenderBatch(
3747
ArrayRange<RenderTreeDiff> updatedComponents,
3848
ArrayRange<RenderTreeFrame> referenceFrames,
3949
ArrayRange<int> disposedComponentIDs,
40-
ArrayRange<ulong> disposedEventHandlerIDs)
50+
ArrayRange<ulong> disposedEventHandlerIDs,
51+
ArrayRange<NamedEvent>? addedNamedEvents,
52+
ArrayRange<NamedEvent>? removedNamedEvents)
4153
{
4254
UpdatedComponents = updatedComponents;
4355
ReferenceFrames = referenceFrames;
4456
DisposedComponentIDs = disposedComponentIDs;
4557
DisposedEventHandlerIDs = disposedEventHandlerIDs;
58+
AddedNamedEvents = addedNamedEvents;
59+
RemovedNamedEvents = removedNamedEvents;
4660
}
4761
}
4862

src/Components/Components/src/RenderTree/RenderTreeDiffBuilder.cs

Lines changed: 43 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,7 @@ public static RenderTreeDiff ComputeDiff(
2323
RenderBatchBuilder batchBuilder,
2424
int componentId,
2525
ArrayRange<RenderTreeFrame> oldTree,
26-
ArrayRange<RenderTreeFrame> newTree,
27-
Dictionary<string, int>? namedEventIndexes)
26+
ArrayRange<RenderTreeFrame> newTree)
2827
{
2928
var editsBuffer = batchBuilder.EditsBuffer;
3029
var editsBufferStartLength = editsBuffer.Count;
@@ -35,34 +34,11 @@ public static RenderTreeDiff ComputeDiff(
3534
var editsSegment = editsBuffer.ToSegment(editsBufferStartLength, editsBuffer.Count);
3635
var result = new RenderTreeDiff(componentId, editsSegment);
3736

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

64-
public static void DisposeFrames(RenderBatchBuilder batchBuilder, ArrayRange<RenderTreeFrame> frames)
65-
=> DisposeFramesInRange(batchBuilder, frames.Array, 0, frames.Count);
40+
public static void DisposeFrames(RenderBatchBuilder batchBuilder, int componentId, ArrayRange<RenderTreeFrame> frames)
41+
=> DisposeFramesInRange(batchBuilder, componentId, frames.Array, 0, frames.Count);
6642

6743
private static void AppendDiffEntriesForRange(
6844
ref DiffContext diffContext,
@@ -749,6 +725,22 @@ private static void AppendDiffEntriesForFramesWithSameSequence(
749725
break;
750726
}
751727

728+
case RenderTreeFrameType.NamedEvent:
729+
{
730+
// 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
731+
// the event type is always a constant. What can change is the frame index and the assigned name.
732+
if (oldFrameIndex != newFrameIndex
733+
|| !string.Equals(oldFrame.NamedEventAssignedName, newFrame.NamedEventAssignedName, StringComparison.Ordinal))
734+
{
735+
// We could track the updates as a concept in its own right, but this situation will be uncommon,
736+
// so it's enough to treat it as a delete+add
737+
diffContext.BatchBuilder.RemoveNamedEvent(diffContext.ComponentId, oldFrameIndex, ref oldFrame);
738+
diffContext.BatchBuilder.AddNamedEvent(diffContext.ComponentId, newFrameIndex, ref newFrame);
739+
}
740+
741+
break;
742+
}
743+
752744
// We don't handle attributes here, they have their own diff logic.
753745
// See AppendDiffEntriesForAttributeFrame
754746
default:
@@ -844,6 +836,11 @@ private static void InsertNewFrame(ref DiffContext diffContext, int newFrameInde
844836
InitializeNewComponentReferenceCaptureFrame(ref diffContext, ref newFrame);
845837
break;
846838
}
839+
case RenderTreeFrameType.NamedEvent:
840+
{
841+
InitializeNewNamedEvent(ref diffContext, newFrameIndex);
842+
break;
843+
}
847844
default:
848845
throw new NotImplementedException($"Unexpected frame type during {nameof(InsertNewFrame)}: {newFrame.FrameTypeField}");
849846
}
@@ -868,7 +865,7 @@ private static void RemoveOldFrame(ref DiffContext diffContext, int oldFrameInde
868865
case RenderTreeFrameType.Element:
869866
{
870867
var endIndexExcl = oldFrameIndex + oldFrame.ElementSubtreeLengthField;
871-
DisposeFramesInRange(diffContext.BatchBuilder, oldTree, oldFrameIndex, endIndexExcl);
868+
DisposeFramesInRange(diffContext.BatchBuilder, diffContext.ComponentId, oldTree, oldFrameIndex, endIndexExcl);
872869
diffContext.Edits.Append(RenderTreeEdit.RemoveFrame(diffContext.SiblingIndex));
873870
break;
874871
}
@@ -889,6 +886,11 @@ private static void RemoveOldFrame(ref DiffContext diffContext, int oldFrameInde
889886
diffContext.Edits.Append(RenderTreeEdit.RemoveFrame(diffContext.SiblingIndex));
890887
break;
891888
}
889+
case RenderTreeFrameType.NamedEvent:
890+
{
891+
diffContext.BatchBuilder.RemoveNamedEvent(diffContext.ComponentId, oldFrameIndex, ref diffContext.OldTree[oldFrameIndex]);
892+
break;
893+
}
892894
default:
893895
throw new NotImplementedException($"Unexpected frame type during {nameof(RemoveOldFrame)}: {oldFrame.FrameTypeField}");
894896
}
@@ -944,6 +946,9 @@ private static void InitializeNewSubtree(ref DiffContext diffContext, int frameI
944946
case RenderTreeFrameType.ComponentReferenceCapture:
945947
InitializeNewComponentReferenceCaptureFrame(ref diffContext, ref frame);
946948
break;
949+
case RenderTreeFrameType.NamedEvent:
950+
InitializeNewNamedEvent(ref diffContext, i);
951+
break;
947952
}
948953
}
949954
}
@@ -1001,7 +1006,12 @@ private static void InitializeNewComponentReferenceCaptureFrame(ref DiffContext
10011006
newFrame.ComponentReferenceCaptureActionField(componentInstance);
10021007
}
10031008

1004-
private static void DisposeFramesInRange(RenderBatchBuilder batchBuilder, RenderTreeFrame[] frames, int startIndex, int endIndexExcl)
1009+
private static void InitializeNewNamedEvent(ref DiffContext diffContext, int newTreeFrameIndex)
1010+
{
1011+
diffContext.BatchBuilder.AddNamedEvent(diffContext.ComponentId, newTreeFrameIndex, ref diffContext.NewTree[newTreeFrameIndex]);
1012+
}
1013+
1014+
private static void DisposeFramesInRange(RenderBatchBuilder batchBuilder, int componentId, RenderTreeFrame[] frames, int startIndex, int endIndexExcl)
10051015
{
10061016
for (var i = startIndex; i < endIndexExcl; i++)
10071017
{
@@ -1014,6 +1024,10 @@ private static void DisposeFramesInRange(RenderBatchBuilder batchBuilder, Render
10141024
{
10151025
batchBuilder.DisposedEventHandlerIds.Append(frame.AttributeEventHandlerIdField);
10161026
}
1027+
else if (frame.FrameTypeField == RenderTreeFrameType.NamedEvent)
1028+
{
1029+
batchBuilder.RemoveNamedEvent(componentId, i, ref frames[i]);
1030+
}
10171031
}
10181032
}
10191033

src/Components/Components/src/RenderTree/RenderTreeFrame.cs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,25 @@ public IComponentRenderMode ComponentRenderMode
282282
}
283283
}
284284

285+
// --------------------------------------------------------------------------------
286+
// RenderTreeFrameType.NamedEvent
287+
// --------------------------------------------------------------------------------
288+
289+
[FieldOffset(16)] internal string NamedEventTypeField;
290+
[FieldOffset(24)] internal string NamedEventAssignedNameField;
291+
292+
/// <summary>
293+
/// If the <see cref="FrameType"/> property equals <see cref="RenderTreeFrameType.NamedEvent"/>,
294+
/// gets the event type. Otherwise, the value is undefined.
295+
/// </summary>
296+
public string NamedEventType => NamedEventTypeField;
297+
298+
/// <summary>
299+
/// If the <see cref="FrameType"/> property equals <see cref="RenderTreeFrameType.NamedEvent"/>,
300+
/// gets the assigned name. Otherwise, the value is undefined.
301+
/// </summary>
302+
public string NamedEventAssignedName => NamedEventAssignedNameField;
303+
285304
// Element constructor
286305
private RenderTreeFrame(int sequence, int elementSubtreeLength, string elementName, object elementKey)
287306
: this()
@@ -395,6 +414,12 @@ internal static RenderTreeFrame ElementReferenceCapture(int sequence, Action<Ele
395414
internal static RenderTreeFrame ComponentReferenceCapture(int sequence, Action<object> componentReferenceCaptureAction, int parentFrameIndex)
396415
=> new RenderTreeFrame(sequence, componentReferenceCaptureAction: componentReferenceCaptureAction, parentFrameIndex: parentFrameIndex);
397416

417+
internal static RenderTreeFrame NamedEvent(int sequence, string eventType, string assignedName)
418+
=> new RenderTreeFrame { SequenceField = sequence, FrameTypeField = RenderTreeFrameType.NamedEvent, NamedEventTypeField = eventType, NamedEventAssignedNameField = assignedName };
419+
420+
internal static RenderTreeFrame ComponentRenderModeFrame(int sequence, IComponentRenderMode renderMode)
421+
=> new RenderTreeFrame { SequenceField = sequence, FrameTypeField = RenderTreeFrameType.ComponentRenderMode, ComponentRenderModeField = renderMode };
422+
398423
internal RenderTreeFrame WithElementSubtreeLength(int elementSubtreeLength)
399424
=> new RenderTreeFrame(SequenceField, elementSubtreeLength: elementSubtreeLength, ElementNameField, ElementKeyField);
400425

src/Components/Components/src/RenderTree/RenderTreeFrameArrayBuilder.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,4 +149,20 @@ public void AppendComponentRenderMode(int sequence, IComponentRenderMode renderM
149149
ComponentRenderModeField = renderMode,
150150
};
151151
}
152+
153+
public void AppendNamedEvent(int sequence, string eventType, string assignedName)
154+
{
155+
if (_itemsInUse == _items.Length)
156+
{
157+
GrowBuffer(_items.Length * 2);
158+
}
159+
160+
_items[_itemsInUse++] = new RenderTreeFrame
161+
{
162+
SequenceField = sequence,
163+
FrameTypeField = RenderTreeFrameType.NamedEvent,
164+
NamedEventTypeField = eventType,
165+
NamedEventAssignedNameField = assignedName,
166+
};
167+
}
152168
}

src/Components/Components/src/RenderTree/RenderTreeFrameType.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,4 +63,9 @@ public enum RenderTreeFrameType : short
6363
/// Represents an instruction to use a specified render mode for the component.
6464
/// </summary>
6565
ComponentRenderMode = 9,
66+
67+
/// <summary>
68+
/// Represents an application-assigned name for an event.
69+
/// </summary>
70+
NamedEvent = 10,
6671
}

src/Components/Components/src/RenderTree/Renderer.cs

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -636,22 +636,6 @@ internal void TrackReplacedEventHandlerId(ulong oldEventHandlerId, ulong newEven
636636
_eventHandlerIdReplacements.Add(oldEventHandlerId, newEventHandlerId);
637637
}
638638

639-
/// <summary>
640-
/// Tracks named events defined during rendering.
641-
/// </summary>
642-
/// <param name="eventHandlerId">The event handler ID associated with the named event.</param>
643-
/// <param name="componentId">The component ID defining the name.</param>
644-
/// <param name="eventHandlerName">The event name.</param>
645-
protected internal virtual void TrackNamedEventId(ulong eventHandlerId, int componentId, string eventHandlerName)
646-
{
647-
}
648-
649-
/// <summary>
650-
/// Indicates whether named event handlers should be tracked.
651-
/// </summary>
652-
/// <returns><c>true</c> if named event handlers should be tracked; <c>false</c> otherwise.</returns>
653-
protected internal virtual bool ShouldTrackNamedEventHandlers() => false;
654-
655639
private EventCallback GetRequiredEventCallback(ulong eventHandlerId)
656640
{
657641
if (!_eventBindings.TryGetValue(eventHandlerId, out var callback))

0 commit comments

Comments
 (0)