-
Notifications
You must be signed in to change notification settings - Fork 10.2k
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
Store event handler IDs as longs. Fixes #12058 #12305
Changes from 3 commits
86b5aeb
d5ecb78
c901793
2ccf583
709761d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,7 +10,7 @@ namespace Microsoft.AspNetCore.Components.RenderTree | |
/// <summary> | ||
/// Represents an entry in a tree of user interface (UI) items. | ||
/// </summary> | ||
[StructLayout(LayoutKind.Explicit)] | ||
[StructLayout(LayoutKind.Explicit, Pack = 4)] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The existing TS-side code already expects 36. We could change it to 40 but with this line, there's no need. |
||
public readonly struct RenderTreeFrame | ||
{ | ||
// Note that the struct layout has to be valid in both 32-bit and 64-bit runtime platforms, | ||
|
@@ -90,7 +90,7 @@ public readonly struct RenderTreeFrame | |
/// If the <see cref="FrameType"/> property equals <see cref="RenderTreeFrameType.Attribute"/> | ||
/// gets the ID of the corresponding event handler, if any. | ||
/// </summary> | ||
[FieldOffset(8)] public readonly int AttributeEventHandlerId; | ||
[FieldOffset(8)] public readonly ulong AttributeEventHandlerId; | ||
|
||
/// <summary> | ||
/// If the <see cref="FrameType"/> property equals <see cref="RenderTreeFrameType.Attribute"/>, | ||
|
@@ -267,7 +267,7 @@ private RenderTreeFrame(int sequence, bool isMarkup, string textOrMarkup) | |
} | ||
|
||
// Attribute constructor | ||
private RenderTreeFrame(int sequence, string attributeName, object attributeValue, int attributeEventHandlerId, string attributeEventUpdatesAttributeName) | ||
private RenderTreeFrame(int sequence, string attributeName, object attributeValue, ulong attributeEventHandlerId, string attributeEventUpdatesAttributeName) | ||
: this() | ||
{ | ||
FrameType = RenderTreeFrameType.Attribute; | ||
|
@@ -337,7 +337,7 @@ internal RenderTreeFrame WithAttributeSequence(int sequence) | |
internal RenderTreeFrame WithComponent(ComponentState componentState) | ||
=> new RenderTreeFrame(Sequence, componentSubtreeLength: ComponentSubtreeLength, ComponentType, componentState, ComponentKey); | ||
|
||
internal RenderTreeFrame WithAttributeEventHandlerId(int eventHandlerId) | ||
internal RenderTreeFrame WithAttributeEventHandlerId(ulong eventHandlerId) | ||
=> new RenderTreeFrame(Sequence, attributeName: AttributeName, AttributeValue, eventHandlerId, AttributeEventUpdatesAttributeName); | ||
|
||
internal RenderTreeFrame WithAttributeValue(object attributeValue) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -18,7 +18,7 @@ internal class RenderBatchBuilder : IDisposable | |
// Primary result data | ||
public ArrayBuilder<RenderTreeDiff> UpdatedComponentDiffs { get; } = new ArrayBuilder<RenderTreeDiff>(); | ||
public ArrayBuilder<int> DisposedComponentIds { get; } = new ArrayBuilder<int>(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should component ids be on the same boat? I can imagine that if we don't want to ever wrap around an event, it might be even more important for components, as it might be the case that 1 event triggers multiple component instantiations. Thoughts? I know the struct layout might get more complicated if we add 4 more bytes. But I think its a matter of being better safe than sorry. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I’m open to it, but it will be quite a lot more disruptive than the event handler ID change, as component IDs are used in so many more places. To run out of IDs, you’d need to do something like instantiate 1000 components per second continuously for 25 days (in one circuit). I think we could say that is impractical for lots of reasons. One cheaper option would just be to change any public API for component IDs to be ulong, which leaves us the option of changing the underlying storage in the future if we want. |
||
public ArrayBuilder<int> DisposedEventHandlerIds { get; } = new ArrayBuilder<int>(); | ||
public ArrayBuilder<ulong> DisposedEventHandlerIds { get; } = new ArrayBuilder<ulong>(); | ||
|
||
// Buffers referenced by UpdatedComponentDiffs | ||
public ArrayBuilder<RenderTreeEdit> EditsBuffer { get; } = new ArrayBuilder<RenderTreeEdit>(64); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -130,16 +130,15 @@ int Write(in ArrayRange<RenderTreeFrame> frames) | |
|
||
void Write(in RenderTreeFrame frame) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unfortunately, for server-side Blazor, this means reference frames now take 20 bytes each instead of 16 before, and in most of the frame types, a large part of those 20 bytes are just padding. We could reduce it to 14 bytes per frame by:
However I'm not doing this now because it can be done non-breakingly in the future if we want. Also, making this data as hyper-packed as possible may turn out to be counterproductive because if we ever need to add any new data, that would trip the field size and force us to re-expand again, which would be disruptive. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Filed #12306 and put it right on the backlog There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sounds good! I was going to suggest to make the frame type a byte directly as I wouldn't expect us to have more than 256 frame types, but I realize that would disrupt the alignment and possibly slow things down. |
||
{ | ||
// TODO: Change this to write as a short, saving 2 bytes per frame | ||
_binaryWriter.Write((int)frame.FrameType); | ||
|
||
// We want each frame to take up the same number of bytes, so that the | ||
// recipient can index into the array directly instead of having to | ||
// walk through it. | ||
// Since we can fit every frame type into 3 ints, use that as the | ||
// Since we can fit every frame type into 16 bytes, use that as the | ||
// common size. For smaller frames, we add padding to expand it to | ||
// 12 bytes (i.e., 3 x 4-byte ints). | ||
// The total size then for each frame is 16 bytes (frame type, then | ||
// 3 other ints). | ||
// 16 bytes. | ||
switch (frame.FrameType) | ||
{ | ||
case RenderTreeFrameType.Attribute: | ||
|
@@ -160,41 +159,41 @@ void Write(in RenderTreeFrame frame) | |
var attributeValueString = frame.AttributeValue as string; | ||
WriteString(attributeValueString, allowDeduplication: string.IsNullOrEmpty(attributeValueString)); | ||
} | ||
_binaryWriter.Write(frame.AttributeEventHandlerId); | ||
_binaryWriter.Write(frame.AttributeEventHandlerId); // 8 bytes | ||
break; | ||
case RenderTreeFrameType.Component: | ||
_binaryWriter.Write(frame.ComponentSubtreeLength); | ||
_binaryWriter.Write(frame.ComponentId); | ||
WritePadding(_binaryWriter, 4); | ||
WritePadding(_binaryWriter, 8); | ||
break; | ||
case RenderTreeFrameType.ComponentReferenceCapture: | ||
// The client doesn't need to know about these. But we still have | ||
// to include them in the array otherwise the ReferenceFrameIndex | ||
// values in the edits data would be wrong. | ||
WritePadding(_binaryWriter, 12); | ||
WritePadding(_binaryWriter, 16); | ||
break; | ||
case RenderTreeFrameType.Element: | ||
_binaryWriter.Write(frame.ElementSubtreeLength); | ||
WriteString(frame.ElementName, allowDeduplication: true); | ||
WritePadding(_binaryWriter, 4); | ||
WritePadding(_binaryWriter, 8); | ||
break; | ||
case RenderTreeFrameType.ElementReferenceCapture: | ||
WriteString(frame.ElementReferenceCaptureId, allowDeduplication: false); | ||
WritePadding(_binaryWriter, 8); | ||
WritePadding(_binaryWriter, 12); | ||
break; | ||
case RenderTreeFrameType.Region: | ||
_binaryWriter.Write(frame.RegionSubtreeLength); | ||
WritePadding(_binaryWriter, 8); | ||
WritePadding(_binaryWriter, 12); | ||
break; | ||
case RenderTreeFrameType.Text: | ||
WriteString( | ||
frame.TextContent, | ||
allowDeduplication: string.IsNullOrWhiteSpace(frame.TextContent)); | ||
WritePadding(_binaryWriter, 8); | ||
WritePadding(_binaryWriter, 12); | ||
break; | ||
case RenderTreeFrameType.Markup: | ||
WriteString(frame.MarkupContent, allowDeduplication: false); | ||
WritePadding(_binaryWriter, 8); | ||
WritePadding(_binaryWriter, 12); | ||
break; | ||
default: | ||
throw new ArgumentException($"Unsupported frame type: {frame.FrameType}"); | ||
|
@@ -216,6 +215,21 @@ int Write(in ArrayRange<int> numbers) | |
return startPos; | ||
} | ||
|
||
int Write(in ArrayRange<ulong> numbers) | ||
{ | ||
var startPos = (int)_binaryWriter.BaseStream.Position; | ||
_binaryWriter.Write(numbers.Count); | ||
|
||
var array = numbers.Array; | ||
var count = numbers.Count; | ||
for (var index = 0; index < count; index++) | ||
{ | ||
_binaryWriter.Write(array[index]); | ||
} | ||
|
||
return startPos; | ||
} | ||
|
||
void WriteString(string value, bool allowDeduplication) | ||
{ | ||
if (value == null) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know our events are always positive and that we are never going to wrap around an ulong, but I'm worried about using unsigned types in here just because we pretty much don't ever use them and because I'm not confident on all the integration issues this might bring, from serialization, to method invocation, etc. Id rather if possible we simply use a long value as it achieves our goal and is less likely to cause issues in the future.
That said, It might be a case of me being paranoid about this for no reason. I'm just scared of the unknowns using this brings, as I don't think there's an API (or very few APIs) in ASP.NET or .NET that specifically use them.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I picked ulong because it was easier to make robust:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't share your concerns 😆 I've used ulong before.