Store event handler IDs as longs. Fixes #12058 (#12305)

* Change event handler IDs to be longs

* Update unit tests

* Enable detailed errors for test app

* CR: Explicitly check if we exceed Number.MAX_SAFE_INTEGER

* Update ref assemblies
This commit is contained in:
Steve Sanderson 2019-07-18 17:53:07 +01:00 committed by GitHub
parent 010ffe6121
commit 4e04b81415
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 181 additions and 120 deletions

View File

@ -62,7 +62,7 @@ namespace Microsoft.AspNetCore.Blazor.Rendering
public override Microsoft.AspNetCore.Components.Dispatcher Dispatcher { get { throw null; } } public override Microsoft.AspNetCore.Components.Dispatcher Dispatcher { get { throw null; } }
public System.Threading.Tasks.Task AddComponentAsync(System.Type componentType, string domElementSelector) { throw null; } public System.Threading.Tasks.Task AddComponentAsync(System.Type componentType, string domElementSelector) { throw null; }
public System.Threading.Tasks.Task AddComponentAsync<TComponent>(string domElementSelector) where TComponent : Microsoft.AspNetCore.Components.IComponent { throw null; } public System.Threading.Tasks.Task AddComponentAsync<TComponent>(string domElementSelector) where TComponent : Microsoft.AspNetCore.Components.IComponent { throw null; }
public override System.Threading.Tasks.Task DispatchEventAsync(int eventHandlerId, Microsoft.AspNetCore.Components.Rendering.EventFieldInfo eventFieldInfo, Microsoft.AspNetCore.Components.UIEventArgs eventArgs) { throw null; } public override System.Threading.Tasks.Task DispatchEventAsync(ulong eventHandlerId, Microsoft.AspNetCore.Components.Rendering.EventFieldInfo eventFieldInfo, Microsoft.AspNetCore.Components.UIEventArgs eventArgs) { throw null; }
protected override void Dispose(bool disposing) { } protected override void Dispose(bool disposing) { }
protected override void HandleException(System.Exception exception) { } protected override void HandleException(System.Exception exception) { }
protected override System.Threading.Tasks.Task UpdateDisplayAsync(in Microsoft.AspNetCore.Components.Rendering.RenderBatch batch) { throw null; } protected override System.Threading.Tasks.Task UpdateDisplayAsync(in Microsoft.AspNetCore.Components.Rendering.RenderBatch batch) { throw null; }

View File

@ -120,7 +120,7 @@ namespace Microsoft.AspNetCore.Blazor.Rendering
} }
/// <inheritdoc /> /// <inheritdoc />
public override Task DispatchEventAsync(int eventHandlerId, EventFieldInfo eventFieldInfo, UIEventArgs eventArgs) public override Task DispatchEventAsync(ulong eventHandlerId, EventFieldInfo eventFieldInfo, UIEventArgs eventArgs)
{ {
// Be sure we only run one event handler at once. Although they couldn't run // Be sure we only run one event handler at once. Although they couldn't run
// simultaneously anyway (there's only one thread), they could run nested on // simultaneously anyway (there's only one thread), they could run nested on
@ -183,12 +183,12 @@ namespace Microsoft.AspNetCore.Blazor.Rendering
readonly struct IncomingEventInfo readonly struct IncomingEventInfo
{ {
public readonly int EventHandlerId; public readonly ulong EventHandlerId;
public readonly EventFieldInfo EventFieldInfo; public readonly EventFieldInfo EventFieldInfo;
public readonly UIEventArgs EventArgs; public readonly UIEventArgs EventArgs;
public readonly TaskCompletionSource<object> TaskCompletionSource; public readonly TaskCompletionSource<object> TaskCompletionSource;
public IncomingEventInfo(int eventHandlerId, EventFieldInfo eventFieldInfo, UIEventArgs eventArgs) public IncomingEventInfo(ulong eventHandlerId, EventFieldInfo eventFieldInfo, UIEventArgs eventArgs)
{ {
EventHandlerId = eventHandlerId; EventHandlerId = eventHandlerId;
EventFieldInfo = eventFieldInfo; EventFieldInfo = eventFieldInfo;

View File

@ -724,7 +724,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
{ {
private readonly object _dummy; private readonly object _dummy;
public Microsoft.AspNetCore.Components.RenderTree.ArrayRange<int> DisposedComponentIDs { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } public Microsoft.AspNetCore.Components.RenderTree.ArrayRange<int> DisposedComponentIDs { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
public Microsoft.AspNetCore.Components.RenderTree.ArrayRange<int> DisposedEventHandlerIDs { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } public Microsoft.AspNetCore.Components.RenderTree.ArrayRange<ulong> DisposedEventHandlerIDs { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
public Microsoft.AspNetCore.Components.RenderTree.ArrayRange<Microsoft.AspNetCore.Components.RenderTree.RenderTreeFrame> ReferenceFrames { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } public Microsoft.AspNetCore.Components.RenderTree.ArrayRange<Microsoft.AspNetCore.Components.RenderTree.RenderTreeFrame> ReferenceFrames { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
public Microsoft.AspNetCore.Components.RenderTree.ArrayRange<Microsoft.AspNetCore.Components.RenderTree.RenderTreeDiff> UpdatedComponents { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } public Microsoft.AspNetCore.Components.RenderTree.ArrayRange<Microsoft.AspNetCore.Components.RenderTree.RenderTreeDiff> UpdatedComponents { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
} }
@ -735,7 +735,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
public event System.UnhandledExceptionEventHandler UnhandledSynchronizationException { add { } remove { } } public event System.UnhandledExceptionEventHandler UnhandledSynchronizationException { add { } remove { } }
protected internal virtual void AddToRenderQueue(int componentId, Microsoft.AspNetCore.Components.RenderFragment renderFragment) { } protected internal virtual void AddToRenderQueue(int componentId, Microsoft.AspNetCore.Components.RenderFragment renderFragment) { }
protected internal int AssignRootComponentId(Microsoft.AspNetCore.Components.IComponent component) { throw null; } protected internal int AssignRootComponentId(Microsoft.AspNetCore.Components.IComponent component) { throw null; }
public virtual System.Threading.Tasks.Task DispatchEventAsync(int eventHandlerId, Microsoft.AspNetCore.Components.Rendering.EventFieldInfo fieldInfo, Microsoft.AspNetCore.Components.UIEventArgs eventArgs) { throw null; } public virtual System.Threading.Tasks.Task DispatchEventAsync(ulong eventHandlerId, Microsoft.AspNetCore.Components.Rendering.EventFieldInfo fieldInfo, Microsoft.AspNetCore.Components.UIEventArgs eventArgs) { throw null; }
public void Dispose() { } public void Dispose() { }
protected virtual void Dispose(bool disposing) { } protected virtual void Dispose(bool disposing) { }
protected abstract void HandleException(System.Exception exception); protected abstract void HandleException(System.Exception exception);

View File

@ -10,7 +10,7 @@ namespace Microsoft.AspNetCore.Components.RenderTree
/// <summary> /// <summary>
/// Represents an entry in a tree of user interface (UI) items. /// Represents an entry in a tree of user interface (UI) items.
/// </summary> /// </summary>
[StructLayout(LayoutKind.Explicit)] [StructLayout(LayoutKind.Explicit, Pack = 4)]
public readonly struct RenderTreeFrame public readonly struct RenderTreeFrame
{ {
// Note that the struct layout has to be valid in both 32-bit and 64-bit runtime platforms, // Note that the struct layout has to be valid in both 32-bit and 64-bit runtime platforms,
@ -90,7 +90,7 @@ namespace Microsoft.AspNetCore.Components.RenderTree
/// If the <see cref="FrameType"/> property equals <see cref="RenderTreeFrameType.Attribute"/> /// If the <see cref="FrameType"/> property equals <see cref="RenderTreeFrameType.Attribute"/>
/// gets the ID of the corresponding event handler, if any. /// gets the ID of the corresponding event handler, if any.
/// </summary> /// </summary>
[FieldOffset(8)] public readonly int AttributeEventHandlerId; [FieldOffset(8)] public readonly ulong AttributeEventHandlerId;
/// <summary> /// <summary>
/// If the <see cref="FrameType"/> property equals <see cref="RenderTreeFrameType.Attribute"/>, /// If the <see cref="FrameType"/> property equals <see cref="RenderTreeFrameType.Attribute"/>,
@ -267,7 +267,7 @@ namespace Microsoft.AspNetCore.Components.RenderTree
} }
// Attribute constructor // 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() : this()
{ {
FrameType = RenderTreeFrameType.Attribute; FrameType = RenderTreeFrameType.Attribute;
@ -337,7 +337,7 @@ namespace Microsoft.AspNetCore.Components.RenderTree
internal RenderTreeFrame WithComponent(ComponentState componentState) internal RenderTreeFrame WithComponent(ComponentState componentState)
=> new RenderTreeFrame(Sequence, componentSubtreeLength: ComponentSubtreeLength, ComponentType, componentState, ComponentKey); => 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); => new RenderTreeFrame(Sequence, attributeName: AttributeName, AttributeValue, eventHandlerId, AttributeEventUpdatesAttributeName);
internal RenderTreeFrame WithAttributeValue(object attributeValue) internal RenderTreeFrame WithAttributeValue(object attributeValue)

View File

@ -1,4 +1,4 @@
// Copyright (c) .NET Foundation. All rights reserved. // 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. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNetCore.Components.RenderTree; using Microsoft.AspNetCore.Components.RenderTree;
@ -30,13 +30,13 @@ namespace Microsoft.AspNetCore.Components.Rendering
/// <summary> /// <summary>
/// Gets the IDs of the event handlers that were disposed. /// Gets the IDs of the event handlers that were disposed.
/// </summary> /// </summary>
public ArrayRange<int> DisposedEventHandlerIDs { get; } public ArrayRange<ulong> DisposedEventHandlerIDs { get; }
internal RenderBatch( internal RenderBatch(
ArrayRange<RenderTreeDiff> updatedComponents, ArrayRange<RenderTreeDiff> updatedComponents,
ArrayRange<RenderTreeFrame> referenceFrames, ArrayRange<RenderTreeFrame> referenceFrames,
ArrayRange<int> disposedComponentIDs, ArrayRange<int> disposedComponentIDs,
ArrayRange<int> disposedEventHandlerIDs) ArrayRange<ulong> disposedEventHandlerIDs)
{ {
UpdatedComponents = updatedComponents; UpdatedComponents = updatedComponents;
ReferenceFrames = referenceFrames; ReferenceFrames = referenceFrames;

View File

@ -18,7 +18,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
// Primary result data // Primary result data
public ArrayBuilder<RenderTreeDiff> UpdatedComponentDiffs { get; } = new ArrayBuilder<RenderTreeDiff>(); public ArrayBuilder<RenderTreeDiff> UpdatedComponentDiffs { get; } = new ArrayBuilder<RenderTreeDiff>();
public ArrayBuilder<int> DisposedComponentIds { get; } = new ArrayBuilder<int>(); public ArrayBuilder<int> DisposedComponentIds { get; } = new ArrayBuilder<int>();
public ArrayBuilder<int> DisposedEventHandlerIds { get; } = new ArrayBuilder<int>(); public ArrayBuilder<ulong> DisposedEventHandlerIds { get; } = new ArrayBuilder<ulong>();
// Buffers referenced by UpdatedComponentDiffs // Buffers referenced by UpdatedComponentDiffs
public ArrayBuilder<RenderTreeEdit> EditsBuffer { get; } = new ArrayBuilder<RenderTreeEdit>(64); public ArrayBuilder<RenderTreeEdit> EditsBuffer { get; } = new ArrayBuilder<RenderTreeEdit>(64);

View File

@ -7,7 +7,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
{ {
internal class RenderTreeUpdater internal class RenderTreeUpdater
{ {
public static void UpdateToMatchClientState(RenderTreeBuilder renderTreeBuilder, int eventHandlerId, object newFieldValue) public static void UpdateToMatchClientState(RenderTreeBuilder renderTreeBuilder, ulong eventHandlerId, object newFieldValue)
{ {
// We only allow the client to supply string or bool currently, since those are the only kinds of // We only allow the client to supply string or bool currently, since those are the only kinds of
// values we output on attributes that go to the client // values we output on attributes that go to the client

View File

@ -22,8 +22,8 @@ namespace Microsoft.AspNetCore.Components.Rendering
private static readonly Action<ILogger, int, Type, Exception> _disposingComponent = private static readonly Action<ILogger, int, Type, Exception> _disposingComponent =
LoggerMessage.Define<int, Type>(LogLevel.Debug, new EventId(4, "DisposingComponent"), "Disposing component {ComponentId} of type {ComponentType}"); LoggerMessage.Define<int, Type>(LogLevel.Debug, new EventId(4, "DisposingComponent"), "Disposing component {ComponentId} of type {ComponentType}");
private static readonly Action<ILogger, int, string, Exception> _handlingEvent = private static readonly Action<ILogger, ulong, string, Exception> _handlingEvent =
LoggerMessage.Define<int, string>(LogLevel.Debug, new EventId(5, "HandlingEvent"), "Handling event {EventId} of type '{EventType}'"); LoggerMessage.Define<ulong, string>(LogLevel.Debug, new EventId(5, "HandlingEvent"), "Handling event {EventId} of type '{EventType}'");
public static void InitializingComponent(ILogger logger, ComponentState componentState, ComponentState parentComponentState) public static void InitializingComponent(ILogger logger, ComponentState componentState, ComponentState parentComponentState)
{ {
@ -56,7 +56,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
} }
} }
internal static void HandlingEvent(ILogger<Renderer> logger, int eventHandlerId, UIEventArgs eventArgs) internal static void HandlingEvent(ILogger<Renderer> logger, ulong eventHandlerId, UIEventArgs eventArgs)
{ {
_handlingEvent(logger, eventHandlerId, eventArgs?.Type ?? "null", null); _handlingEvent(logger, eventHandlerId, eventArgs?.Type ?? "null", null);
} }

View File

@ -20,13 +20,13 @@ namespace Microsoft.AspNetCore.Components.Rendering
private readonly IServiceProvider _serviceProvider; private readonly IServiceProvider _serviceProvider;
private readonly Dictionary<int, ComponentState> _componentStateById = new Dictionary<int, ComponentState>(); private readonly Dictionary<int, ComponentState> _componentStateById = new Dictionary<int, ComponentState>();
private readonly RenderBatchBuilder _batchBuilder = new RenderBatchBuilder(); private readonly RenderBatchBuilder _batchBuilder = new RenderBatchBuilder();
private readonly Dictionary<int, EventCallback> _eventBindings = new Dictionary<int, EventCallback>(); private readonly Dictionary<ulong, EventCallback> _eventBindings = new Dictionary<ulong, EventCallback>();
private readonly Dictionary<int, int> _eventHandlerIdReplacements = new Dictionary<int, int>(); private readonly Dictionary<ulong, ulong> _eventHandlerIdReplacements = new Dictionary<ulong, ulong>();
private readonly ILogger<Renderer> _logger; private readonly ILogger<Renderer> _logger;
private int _nextComponentId = 0; // TODO: change to 'long' when Mono .NET->JS interop supports it private int _nextComponentId = 0; // TODO: change to 'long' when Mono .NET->JS interop supports it
private bool _isBatchInProgress; private bool _isBatchInProgress;
private int _lastEventHandlerId = 0; private ulong _lastEventHandlerId;
private List<Task> _pendingTasks; private List<Task> _pendingTasks;
/// <summary> /// <summary>
@ -206,7 +206,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
/// A <see cref="Task"/> which will complete once all asynchronous processing related to the event /// A <see cref="Task"/> which will complete once all asynchronous processing related to the event
/// has completed. /// has completed.
/// </returns> /// </returns>
public virtual Task DispatchEventAsync(int eventHandlerId, EventFieldInfo fieldInfo, UIEventArgs eventArgs) public virtual Task DispatchEventAsync(ulong eventHandlerId, EventFieldInfo fieldInfo, UIEventArgs eventArgs)
{ {
EnsureSynchronizationContext(); EnsureSynchronizationContext();
@ -354,7 +354,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
} }
} }
internal void TrackReplacedEventHandlerId(int oldEventHandlerId, int newEventHandlerId) internal void TrackReplacedEventHandlerId(ulong oldEventHandlerId, ulong newEventHandlerId)
{ {
// Tracking the chain of old->new replacements allows us to interpret incoming EventFieldInfo // Tracking the chain of old->new replacements allows us to interpret incoming EventFieldInfo
// values even if they refer to an event handler ID that's since been superseded. This is essential // values even if they refer to an event handler ID that's since been superseded. This is essential
@ -362,7 +362,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
_eventHandlerIdReplacements.Add(oldEventHandlerId, newEventHandlerId); _eventHandlerIdReplacements.Add(oldEventHandlerId, newEventHandlerId);
} }
private int FindLatestEventHandlerIdInChain(int eventHandlerId) private ulong FindLatestEventHandlerIdInChain(ulong eventHandlerId)
{ {
while (_eventHandlerIdReplacements.TryGetValue(eventHandlerId, out var replacementEventHandlerId)) while (_eventHandlerIdReplacements.TryGetValue(eventHandlerId, out var replacementEventHandlerId))
{ {
@ -573,7 +573,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
} }
} }
private void RemoveEventHandlerIds(ArrayRange<int> eventHandlerIds, Task afterTaskIgnoreErrors) private void RemoveEventHandlerIds(ArrayRange<ulong> eventHandlerIds, Task afterTaskIgnoreErrors)
{ {
if (eventHandlerIds.Count == 0) if (eventHandlerIds.Count == 0)
{ {
@ -598,7 +598,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
// Factor out the async part into a separate local method purely so, in the // Factor out the async part into a separate local method purely so, in the
// synchronous case, there's no state machine or task construction // synchronous case, there's no state machine or task construction
async Task ContinueAfterTask(ArrayRange<int> eventHandlerIds, Task afterTaskIgnoreErrors) async Task ContinueAfterTask(ArrayRange<ulong> eventHandlerIds, Task afterTaskIgnoreErrors)
{ {
// We need to delay the actual removal (e.g., until we've confirmed the client // We need to delay the actual removal (e.g., until we've confirmed the client
// has processed the batch and hence can be sure not to reuse the handler IDs // has processed the batch and hence can be sure not to reuse the handler IDs
@ -637,7 +637,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
} }
} }
private void UpdateRenderTreeToMatchClientState(int eventHandlerId, EventFieldInfo fieldInfo) private void UpdateRenderTreeToMatchClientState(ulong eventHandlerId, EventFieldInfo fieldInfo)
{ {
var componentState = GetOptionalComponentState(fieldInfo.ComponentId); var componentState = GetOptionalComponentState(fieldInfo.ComponentId);
if (componentState != null) if (componentState != null)

View File

@ -859,7 +859,7 @@ namespace Microsoft.AspNetCore.Components.Test
Assert.Equal(0, entry.ReferenceFrameIndex); Assert.Equal(0, entry.ReferenceFrameIndex);
}); });
AssertFrame.Attribute(referenceFrames[0], "onbar", addedHandler); AssertFrame.Attribute(referenceFrames[0], "onbar", addedHandler);
Assert.NotEqual(0, removedEventHandlerFrame.AttributeEventHandlerId); Assert.NotEqual(default, removedEventHandlerFrame.AttributeEventHandlerId);
Assert.Equal( Assert.Equal(
new[] { removedEventHandlerFrame.AttributeEventHandlerId }, new[] { removedEventHandlerFrame.AttributeEventHandlerId },
batchBuilder.DisposedEventHandlerIDs.AsEnumerable()); batchBuilder.DisposedEventHandlerIDs.AsEnumerable());
@ -1592,7 +1592,7 @@ namespace Microsoft.AspNetCore.Components.Test
Assert.Empty(result.Edits); Assert.Empty(result.Edits);
AssertFrame.Attribute(oldAttributeFrame, "ontest", retainedHandler); AssertFrame.Attribute(oldAttributeFrame, "ontest", retainedHandler);
AssertFrame.Attribute(newAttributeFrame, "ontest", retainedHandler); AssertFrame.Attribute(newAttributeFrame, "ontest", retainedHandler);
Assert.NotEqual(0, oldAttributeFrame.AttributeEventHandlerId); Assert.NotEqual(default, oldAttributeFrame.AttributeEventHandlerId);
Assert.Equal(oldAttributeFrame.AttributeEventHandlerId, newAttributeFrame.AttributeEventHandlerId); Assert.Equal(oldAttributeFrame.AttributeEventHandlerId, newAttributeFrame.AttributeEventHandlerId);
Assert.Empty(batchBuilder.DisposedEventHandlerIDs.AsEnumerable()); Assert.Empty(batchBuilder.DisposedEventHandlerIDs.AsEnumerable());
} }
@ -1619,7 +1619,7 @@ namespace Microsoft.AspNetCore.Components.Test
Assert.Single(result.Edits); Assert.Single(result.Edits);
AssertFrame.Attribute(oldAttributeFrame, "ontest", retainedHandler); AssertFrame.Attribute(oldAttributeFrame, "ontest", retainedHandler);
AssertFrame.Attribute(newAttributeFrame, "ontest", retainedHandler); AssertFrame.Attribute(newAttributeFrame, "ontest", retainedHandler);
Assert.NotEqual(0, oldAttributeFrame.AttributeEventHandlerId); Assert.NotEqual(default, oldAttributeFrame.AttributeEventHandlerId);
Assert.Equal(oldAttributeFrame.AttributeEventHandlerId, newAttributeFrame.AttributeEventHandlerId); Assert.Equal(oldAttributeFrame.AttributeEventHandlerId, newAttributeFrame.AttributeEventHandlerId);
Assert.Empty(batchBuilder.DisposedEventHandlerIDs.AsEnumerable()); Assert.Empty(batchBuilder.DisposedEventHandlerIDs.AsEnumerable());
} }

View File

@ -3313,7 +3313,7 @@ namespace Microsoft.AspNetCore.Components.Test
Assert.Equal(RenderTreeEditType.SetAttribute, edit.Type); Assert.Equal(RenderTreeEditType.SetAttribute, edit.Type);
var attributeFrame = batch2.ReferenceFrames[edit.ReferenceFrameIndex]; var attributeFrame = batch2.ReferenceFrames[edit.ReferenceFrameIndex];
AssertFrame.Attribute(attributeFrame, "ontestevent", typeof(Action<UIChangeEventArgs>)); AssertFrame.Attribute(attributeFrame, "ontestevent", typeof(Action<UIChangeEventArgs>));
Assert.NotEqual(0, attributeFrame.AttributeEventHandlerId); Assert.NotEqual(default, attributeFrame.AttributeEventHandlerId);
Assert.NotEqual(eventHandlerId, attributeFrame.AttributeEventHandlerId); Assert.NotEqual(eventHandlerId, attributeFrame.AttributeEventHandlerId);
}); });
} }
@ -3365,7 +3365,7 @@ namespace Microsoft.AspNetCore.Components.Test
Assert.Equal(RenderTreeEditType.SetAttribute, edit.Type); Assert.Equal(RenderTreeEditType.SetAttribute, edit.Type);
var attributeFrame = latestBatch.ReferenceFrames[edit.ReferenceFrameIndex]; var attributeFrame = latestBatch.ReferenceFrames[edit.ReferenceFrameIndex];
AssertFrame.Attribute(attributeFrame, "ontestevent", typeof(Action<UIChangeEventArgs>)); AssertFrame.Attribute(attributeFrame, "ontestevent", typeof(Action<UIChangeEventArgs>));
Assert.NotEqual(0, attributeFrame.AttributeEventHandlerId); Assert.NotEqual(default, attributeFrame.AttributeEventHandlerId);
Assert.NotEqual(eventHandlerId, attributeFrame.AttributeEventHandlerId); Assert.NotEqual(eventHandlerId, attributeFrame.AttributeEventHandlerId);
}); });
} }

View File

@ -130,16 +130,15 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
void Write(in RenderTreeFrame frame) void Write(in RenderTreeFrame frame)
{ {
// TODO: Change this to write as a short, saving 2 bytes per frame
_binaryWriter.Write((int)frame.FrameType); _binaryWriter.Write((int)frame.FrameType);
// We want each frame to take up the same number of bytes, so that the // 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 // recipient can index into the array directly instead of having to
// walk through it. // 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 // common size. For smaller frames, we add padding to expand it to
// 12 bytes (i.e., 3 x 4-byte ints). // 16 bytes.
// The total size then for each frame is 16 bytes (frame type, then
// 3 other ints).
switch (frame.FrameType) switch (frame.FrameType)
{ {
case RenderTreeFrameType.Attribute: case RenderTreeFrameType.Attribute:
@ -160,41 +159,41 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
var attributeValueString = frame.AttributeValue as string; var attributeValueString = frame.AttributeValue as string;
WriteString(attributeValueString, allowDeduplication: string.IsNullOrEmpty(attributeValueString)); WriteString(attributeValueString, allowDeduplication: string.IsNullOrEmpty(attributeValueString));
} }
_binaryWriter.Write(frame.AttributeEventHandlerId); _binaryWriter.Write(frame.AttributeEventHandlerId); // 8 bytes
break; break;
case RenderTreeFrameType.Component: case RenderTreeFrameType.Component:
_binaryWriter.Write(frame.ComponentSubtreeLength); _binaryWriter.Write(frame.ComponentSubtreeLength);
_binaryWriter.Write(frame.ComponentId); _binaryWriter.Write(frame.ComponentId);
WritePadding(_binaryWriter, 4); WritePadding(_binaryWriter, 8);
break; break;
case RenderTreeFrameType.ComponentReferenceCapture: case RenderTreeFrameType.ComponentReferenceCapture:
// The client doesn't need to know about these. But we still have // The client doesn't need to know about these. But we still have
// to include them in the array otherwise the ReferenceFrameIndex // to include them in the array otherwise the ReferenceFrameIndex
// values in the edits data would be wrong. // values in the edits data would be wrong.
WritePadding(_binaryWriter, 12); WritePadding(_binaryWriter, 16);
break; break;
case RenderTreeFrameType.Element: case RenderTreeFrameType.Element:
_binaryWriter.Write(frame.ElementSubtreeLength); _binaryWriter.Write(frame.ElementSubtreeLength);
WriteString(frame.ElementName, allowDeduplication: true); WriteString(frame.ElementName, allowDeduplication: true);
WritePadding(_binaryWriter, 4); WritePadding(_binaryWriter, 8);
break; break;
case RenderTreeFrameType.ElementReferenceCapture: case RenderTreeFrameType.ElementReferenceCapture:
WriteString(frame.ElementReferenceCaptureId, allowDeduplication: false); WriteString(frame.ElementReferenceCaptureId, allowDeduplication: false);
WritePadding(_binaryWriter, 8); WritePadding(_binaryWriter, 12);
break; break;
case RenderTreeFrameType.Region: case RenderTreeFrameType.Region:
_binaryWriter.Write(frame.RegionSubtreeLength); _binaryWriter.Write(frame.RegionSubtreeLength);
WritePadding(_binaryWriter, 8); WritePadding(_binaryWriter, 12);
break; break;
case RenderTreeFrameType.Text: case RenderTreeFrameType.Text:
WriteString( WriteString(
frame.TextContent, frame.TextContent,
allowDeduplication: string.IsNullOrWhiteSpace(frame.TextContent)); allowDeduplication: string.IsNullOrWhiteSpace(frame.TextContent));
WritePadding(_binaryWriter, 8); WritePadding(_binaryWriter, 12);
break; break;
case RenderTreeFrameType.Markup: case RenderTreeFrameType.Markup:
WriteString(frame.MarkupContent, allowDeduplication: false); WriteString(frame.MarkupContent, allowDeduplication: false);
WritePadding(_binaryWriter, 8); WritePadding(_binaryWriter, 12);
break; break;
default: default:
throw new ArgumentException($"Unsupported frame type: {frame.FrameType}"); throw new ArgumentException($"Unsupported frame type: {frame.FrameType}");
@ -216,6 +215,21 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
return startPos; 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) void WriteString(string value, bool allowDeduplication)
{ {
if (value == null) if (value == null)

View File

@ -76,7 +76,7 @@ namespace Microsoft.AspNetCore.Components.Server
new ArrayRange<RenderTreeDiff>(), new ArrayRange<RenderTreeDiff>(),
new ArrayRange<RenderTreeFrame>(), new ArrayRange<RenderTreeFrame>(),
new ArrayRange<int>(), new ArrayRange<int>(),
new ArrayRange<int>(new[] { 123, int.MaxValue, int.MinValue, 456 }, 3) // Only use first 3 to show that param is respected new ArrayRange<ulong>(new ulong[] { 123, ulong.MaxValue, ulong.MinValue, 456 }, 3) // Only use first 3 to show that param is respected
)); ));
// Assert // Assert
@ -84,15 +84,15 @@ namespace Microsoft.AspNetCore.Components.Server
0, // Length of UpdatedComponents 0, // Length of UpdatedComponents
0, // Length of ReferenceFrames 0, // Length of ReferenceFrames
0, // Length of DisposedComponentIds 0, // Length of DisposedComponentIds
3, 123, int.MaxValue, int.MinValue, // DisposedEventHandlerIds as length-prefixed array 3, (ulong)123, ulong.MaxValue, ulong.MinValue, // DisposedEventHandlerIds as length-prefixed array
0, // Index of UpdatedComponents 0, // Index of UpdatedComponents
4, // Index of ReferenceFrames 4, // Index of ReferenceFrames
8, // Index of DisposedComponentIds 8, // Index of DisposedComponentIds
12, // Index of DisposedEventHandlerIds 12, // Index of DisposedEventHandlerIds
28 // Index of strings 40 // Index of strings
); );
Assert.Equal(48, bytes.Length); // No other data Assert.Equal(60, bytes.Length); // No other data
} }
[Fact] [Fact]
@ -200,7 +200,7 @@ namespace Microsoft.AspNetCore.Components.Server
RenderTreeFrame.Attribute(123, "Attribute with string value", "String value"), RenderTreeFrame.Attribute(123, "Attribute with string value", "String value"),
RenderTreeFrame.Attribute(124, "Attribute with nonstring value", 1), RenderTreeFrame.Attribute(124, "Attribute with nonstring value", 1),
RenderTreeFrame.Attribute(125, "Attribute with delegate value", new Action(() => { })) RenderTreeFrame.Attribute(125, "Attribute with delegate value", new Action(() => { }))
.WithAttributeEventHandlerId(789), .WithAttributeEventHandlerId(((ulong)uint.MaxValue) + 1),
RenderTreeFrame.ChildComponent(126, typeof(object)) RenderTreeFrame.ChildComponent(126, typeof(object))
.WithComponentSubtreeLength(5678) .WithComponentSubtreeLength(5678)
.WithComponent(new ComponentState(renderer, 2000, new FakeComponent(), null)), .WithComponent(new ComponentState(renderer, 2000, new FakeComponent(), null)),
@ -230,22 +230,22 @@ namespace Microsoft.AspNetCore.Components.Server
var referenceFramesStartIndex = ReadInt(bytes, bytes.Length - 16); var referenceFramesStartIndex = ReadInt(bytes, bytes.Length - 16);
AssertBinaryContents(bytes, referenceFramesStartIndex, AssertBinaryContents(bytes, referenceFramesStartIndex,
16, // Number of frames 16, // Number of frames
RenderTreeFrameType.Attribute, "Attribute with string value", "String value", 0, RenderTreeFrameType.Attribute, "Attribute with string value", "String value", 0, 0,
RenderTreeFrameType.Attribute, "Attribute with nonstring value", NullStringMarker, 0, RenderTreeFrameType.Attribute, "Attribute with nonstring value", NullStringMarker, 0, 0,
RenderTreeFrameType.Attribute, "Attribute with delegate value", NullStringMarker, 789, RenderTreeFrameType.Attribute, "Attribute with delegate value", NullStringMarker, ((ulong)uint.MaxValue) + 1,
RenderTreeFrameType.Component, 5678, 2000, 0, RenderTreeFrameType.Component, 5678, 2000, 0, 0,
RenderTreeFrameType.ComponentReferenceCapture, 0, 0, 0, RenderTreeFrameType.ComponentReferenceCapture, 0, 0, 0, 0,
RenderTreeFrameType.Element, 1234, "Some element", 0, RenderTreeFrameType.Element, 1234, "Some element", 0, 0,
RenderTreeFrameType.ElementReferenceCapture, "my unique ID", 0, 0, RenderTreeFrameType.ElementReferenceCapture, "my unique ID", 0, 0, 0,
RenderTreeFrameType.Region, 1234, 0, 0, RenderTreeFrameType.Region, 1234, 0, 0, 0,
RenderTreeFrameType.Text, "Some text", 0, 0, RenderTreeFrameType.Text, "Some text", 0, 0, 0,
RenderTreeFrameType.Markup, "Some markup", 0, 0, RenderTreeFrameType.Markup, "Some markup", 0, 0, 0,
RenderTreeFrameType.Text, "\n\t ", 0, 0, RenderTreeFrameType.Text, "\n\t ", 0, 0, 0,
RenderTreeFrameType.Attribute, "Attribute with string value", "String value", 0, RenderTreeFrameType.Attribute, "Attribute with string value", "String value", 0, 0,
RenderTreeFrameType.Element, 999, "Some element", 0, RenderTreeFrameType.Element, 999, "Some element", 0, 0,
RenderTreeFrameType.Text, "Some text", 0, 0, RenderTreeFrameType.Text, "Some text", 0, 0, 0,
RenderTreeFrameType.Markup, "Some markup", 0, 0, RenderTreeFrameType.Markup, "Some markup", 0, 0, 0,
RenderTreeFrameType.Text, "\n\t ", 0, 0 RenderTreeFrameType.Text, "\n\t ", 0, 0, 0
); );
Assert.Equal(new[] Assert.Equal(new[]
@ -320,6 +320,10 @@ namespace Microsoft.AspNetCore.Components.Server
{ {
Assert.Equal(expectedInt, reader.ReadInt32()); Assert.Equal(expectedInt, reader.ReadInt32());
} }
else if (expectedEntry is ulong expectedUlong)
{
Assert.Equal(expectedUlong, reader.ReadUInt64());
}
else if (expectedEntry is string || expectedEntry == NullStringMarker) else if (expectedEntry is string || expectedEntry == NullStringMarker)
{ {
// For strings, we have to look up the value in the table of strings // For strings, we have to look up the value in the table of strings

View File

@ -60,10 +60,10 @@ namespace Microsoft.AspNetCore.Components.Test.Helpers
public new Task RenderRootComponentAsync(int componentId, ParameterCollection parameters) public new Task RenderRootComponentAsync(int componentId, ParameterCollection parameters)
=> Dispatcher.InvokeAsync(() => base.RenderRootComponentAsync(componentId, parameters)); => Dispatcher.InvokeAsync(() => base.RenderRootComponentAsync(componentId, parameters));
public Task DispatchEventAsync(int eventHandlerId, UIEventArgs args) public Task DispatchEventAsync(ulong eventHandlerId, UIEventArgs args)
=> Dispatcher.InvokeAsync(() => base.DispatchEventAsync(eventHandlerId, null, args)); => Dispatcher.InvokeAsync(() => base.DispatchEventAsync(eventHandlerId, null, args));
public new Task DispatchEventAsync(int eventHandlerId, EventFieldInfo eventFieldInfo, UIEventArgs args) public new Task DispatchEventAsync(ulong eventHandlerId, EventFieldInfo eventFieldInfo, UIEventArgs args)
=> Dispatcher.InvokeAsync(() => base.DispatchEventAsync(eventHandlerId, eventFieldInfo, args)); => Dispatcher.InvokeAsync(() => base.DispatchEventAsync(eventHandlerId, eventFieldInfo, args));
private static Task UnwrapTask(Task task) private static Task UnwrapTask(Task task)

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -13,6 +13,8 @@ let invoke_method: (method: MethodHandle, target: System_Object, argsArrayPtr: n
let mono_string_get_utf8: (managedString: System_String) => Mono.Utf8Ptr; let mono_string_get_utf8: (managedString: System_String) => Mono.Utf8Ptr;
let mono_string: (jsString: string) => System_String; let mono_string: (jsString: string) => System_String;
const appBinDirName = 'appBinDir'; const appBinDirName = 'appBinDir';
const uint64HighOrderShift = Math.pow(2, 32);
const maxSafeNumberHighPart = Math.pow(2, 21) - 1; // The high-order int32 from Number.MAX_SAFE_INTEGER
export const monoPlatform: Platform = { export const monoPlatform: Platform = {
start: function start(loadAssemblyUrls: string[]) { start: function start(loadAssemblyUrls: string[]) {
@ -122,6 +124,19 @@ export const monoPlatform: Platform = {
return Module.getValue((baseAddress as any as number) + (fieldOffset || 0), 'i32'); return Module.getValue((baseAddress as any as number) + (fieldOffset || 0), 'i32');
}, },
readUint64Field: function readHeapUint64(baseAddress: Pointer, fieldOffset?: number): number {
// Module.getValue(..., 'i64') doesn't work because the implementation treats 'i64' as
// being the same as 'i32'. Also we must take care to read both halves as unsigned.
const address = (baseAddress as any as number) + (fieldOffset || 0);
const heapU32Index = address >> 2;
const highPart = Module.HEAPU32[heapU32Index + 1];
if (highPart > maxSafeNumberHighPart) {
throw new Error(`Cannot read uint64 with high order part ${highPart}, because the result would exceed Number.MAX_SAFE_INTEGER.`);
}
return (highPart * uint64HighOrderShift) + Module.HEAPU32[heapU32Index];
},
readFloatField: function readHeapFloat(baseAddress: Pointer, fieldOffset?: number): number { readFloatField: function readHeapFloat(baseAddress: Pointer, fieldOffset?: number): number {
return Module.getValue((baseAddress as any as number) + (fieldOffset || 0), 'float'); return Module.getValue((baseAddress as any as number) + (fieldOffset || 0), 'float');
}, },

View File

@ -16,6 +16,7 @@ export interface Platform {
getObjectFieldsBaseAddress(referenceTypedObject: System_Object): Pointer; getObjectFieldsBaseAddress(referenceTypedObject: System_Object): Pointer;
readInt16Field(baseAddress: Pointer, fieldOffset?: number): number; readInt16Field(baseAddress: Pointer, fieldOffset?: number): number;
readInt32Field(baseAddress: Pointer, fieldOffset?: number): number; readInt32Field(baseAddress: Pointer, fieldOffset?: number): number;
readUint64Field(baseAddress: Pointer, fieldOffset?: number): number;
readFloatField(baseAddress: Pointer, fieldOffset?: number): number; readFloatField(baseAddress: Pointer, fieldOffset?: number): number;
readObjectField<T extends System_Object>(baseAddress: Pointer, fieldOffset?: number): T; readObjectField<T extends System_Object>(baseAddress: Pointer, fieldOffset?: number): T;
readStringField(baseAddress: Pointer, fieldOffset?: number): string | null; readStringField(baseAddress: Pointer, fieldOffset?: number): string | null;

View File

@ -2,11 +2,13 @@ import { RenderBatch, ArrayRange, RenderTreeDiff, ArrayValues, RenderTreeEdit, E
import { decodeUtf8 } from './Utf8Decoder'; import { decodeUtf8 } from './Utf8Decoder';
const updatedComponentsEntryLength = 4; // Each is a single int32 giving the location of the data const updatedComponentsEntryLength = 4; // Each is a single int32 giving the location of the data
const referenceFramesEntryLength = 16; // 1 byte for frame type, then 3 bytes for type-specific data const referenceFramesEntryLength = 20; // 1 int for frame type, then 16 bytes for type-specific data
const disposedComponentIdsEntryLength = 4; // Each is an int32 giving the ID const disposedComponentIdsEntryLength = 4; // Each is an int32 giving the ID
const disposedEventHandlerIdsEntryLength = 4; // Each is an int32 giving the ID const disposedEventHandlerIdsEntryLength = 8; // Each is an int64 giving the ID
const editsEntryLength = 16; // 4 ints const editsEntryLength = 16; // 4 ints
const stringTableEntryLength = 4; // Each is an int32 giving the string data location, or -1 for null const stringTableEntryLength = 4; // Each is an int32 giving the string data location, or -1 for null
const uint64HighPartShift = Math.pow(2, 32);
const maxSafeNumberHighPart = Math.pow(2, 21) - 1; // The high-order int32 from Number.MAX_SAFE_INTEGER
export class OutOfProcessRenderBatch implements RenderBatch { export class OutOfProcessRenderBatch implements RenderBatch {
constructor(private batchData: Uint8Array) { constructor(private batchData: Uint8Array) {
@ -51,7 +53,7 @@ export class OutOfProcessRenderBatch implements RenderBatch {
disposedEventHandlerIdsEntry(values: ArrayValues<number>, index: number): number { disposedEventHandlerIdsEntry(values: ArrayValues<number>, index: number): number {
const entryPos = (values as any) + index * disposedEventHandlerIdsEntryLength; const entryPos = (values as any) + index * disposedEventHandlerIdsEntryLength;
return readInt32LE(this.batchData, entryPos); return readUint64LE(this.batchData, entryPos);
} }
diffReader: RenderTreeDiffReader; diffReader: RenderTreeDiffReader;
@ -160,7 +162,7 @@ class OutOfProcessRenderTreeFrameReader implements RenderTreeFrameReader {
} }
attributeEventHandlerId(frame: RenderTreeFrame) { attributeEventHandlerId(frame: RenderTreeFrame) {
return readInt32LE(this.batchDataUint8, frame as any + 12); // 4th int return readUint64LE(this.batchDataUint8, frame as any + 12); // Bytes 12-19
} }
} }
@ -235,6 +237,24 @@ function readInt32LE(buffer: Uint8Array, position: number): any {
| (buffer[position + 3] << 24); | (buffer[position + 3] << 24);
} }
function readUint32LE(buffer: Uint8Array, position: number): any {
return (buffer[position])
+ (buffer[position + 1] << 8)
+ (buffer[position + 2] << 16)
+ ((buffer[position + 3] << 24) >>> 0); // The >>> 0 coerces the value to unsigned
}
function readUint64LE(buffer: Uint8Array, position: number): any {
// This cannot be done using bit-shift operators in JavaScript, because
// those all implicitly convert to int32
const highPart = readUint32LE(buffer, position + 4);
if (highPart > maxSafeNumberHighPart) {
throw new Error(`Cannot read uint64 with high order part ${highPart}, because the result would exceed Number.MAX_SAFE_INTEGER.`);
}
return (highPart * uint64HighPartShift) + readUint32LE(buffer, position);
}
function readLEB128(buffer: Uint8Array, position: number) { function readLEB128(buffer: Uint8Array, position: number) {
let result = 0; let result = 0;
let shift = 0; let shift = 0;

View File

@ -43,8 +43,8 @@ export class SharedMemoryRenderBatch implements RenderBatch {
} }
disposedEventHandlerIdsEntry(values: ArrayValues<number>, index: number) { disposedEventHandlerIdsEntry(values: ArrayValues<number>, index: number) {
const pointer = arrayValuesEntry(values, index, /* int length */ 4); const pointer = arrayValuesEntry(values, index, /* long length */ 8);
return platform.readInt32Field(pointer as any as Pointer); return platform.readUint64Field(pointer as any as Pointer);
} }
arrayRangeReader = arrayRangeReader; arrayRangeReader = arrayRangeReader;
@ -108,7 +108,7 @@ const frameReader = {
markupContent: (frame: RenderTreeFrame) => platform.readStringField(frame as any, 16)!, markupContent: (frame: RenderTreeFrame) => platform.readStringField(frame as any, 16)!,
attributeName: (frame: RenderTreeFrame) => platform.readStringField(frame as any, 16), attributeName: (frame: RenderTreeFrame) => platform.readStringField(frame as any, 16),
attributeValue: (frame: RenderTreeFrame) => platform.readStringField(frame as any, 24), attributeValue: (frame: RenderTreeFrame) => platform.readStringField(frame as any, 24),
attributeEventHandlerId: (frame: RenderTreeFrame) => platform.readInt32Field(frame as any, 8), attributeEventHandlerId: (frame: RenderTreeFrame) => platform.readUint64Field(frame as any, 8),
}; };
function arrayValuesEntry<T>(arrayValues: ArrayValues<T>, index: number, itemSize: number): T { function arrayValuesEntry<T>(arrayValues: ArrayValues<T>, index: number, itemSize: number): T {

View File

@ -13,7 +13,7 @@ namespace Microsoft.AspNetCore.Components.Web
public int BrowserRendererId { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } public int BrowserRendererId { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
public string EventArgsType { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } public string EventArgsType { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
public Microsoft.AspNetCore.Components.Rendering.EventFieldInfo EventFieldInfo { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } public Microsoft.AspNetCore.Components.Rendering.EventFieldInfo EventFieldInfo { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
public int EventHandlerId { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } public ulong EventHandlerId { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
} }
} }
} }

View File

@ -128,7 +128,7 @@ namespace Microsoft.AspNetCore.Components.Web
/// <summary> /// <summary>
/// For framework use only. /// For framework use only.
/// </summary> /// </summary>
public int EventHandlerId { get; set; } public ulong EventHandlerId { get; set; }
/// <summary> /// <summary>
/// For framework use only. /// For framework use only.

View File

@ -150,7 +150,7 @@ namespace Ignitor
RenderTreeFrame.Attribute(123, "Attribute with string value", "String value"), RenderTreeFrame.Attribute(123, "Attribute with string value", "String value"),
RenderTreeFrame.Attribute(124, "Attribute with nonstring value", 1), RenderTreeFrame.Attribute(124, "Attribute with nonstring value", 1),
RenderTreeFrame.Attribute(125, "Attribute with delegate value", new Action(() => { })) RenderTreeFrame.Attribute(125, "Attribute with delegate value", new Action(() => { }))
.WithAttributeEventHandlerId(789), .WithAttributeEventHandlerId((ulong)uint.MaxValue + 1),
RenderTreeFrame.ChildComponent(126, typeof(object)) RenderTreeFrame.ChildComponent(126, typeof(object))
.WithComponentSubtreeLength(5678) .WithComponentSubtreeLength(5678)
.WithComponent(new ComponentState(renderer, 2000, new FakeComponent(), null)), .WithComponent(new ComponentState(renderer, 2000, new FakeComponent(), null)),
@ -180,22 +180,22 @@ namespace Ignitor
var referenceFramesStartIndex = ReadInt(bytes, bytes.Length - 16); var referenceFramesStartIndex = ReadInt(bytes, bytes.Length - 16);
AssertBinaryContents(bytes, referenceFramesStartIndex, AssertBinaryContents(bytes, referenceFramesStartIndex,
16, // Number of frames 16, // Number of frames
RenderTreeFrameType.Attribute, "Attribute with string value", "String value", 0, RenderTreeFrameType.Attribute, "Attribute with string value", "String value", 0, 0,
RenderTreeFrameType.Attribute, "Attribute with nonstring value", NullStringMarker, 0, RenderTreeFrameType.Attribute, "Attribute with nonstring value", NullStringMarker, 0, 0,
RenderTreeFrameType.Attribute, "Attribute with delegate value", NullStringMarker, 789, RenderTreeFrameType.Attribute, "Attribute with delegate value", NullStringMarker, (ulong)uint.MaxValue + 1,
RenderTreeFrameType.Component, 5678, 2000, 0, RenderTreeFrameType.Component, 5678, 2000, 0, 0,
RenderTreeFrameType.ComponentReferenceCapture, 0, 0, 0, RenderTreeFrameType.ComponentReferenceCapture, 0, 0, 0, 0,
RenderTreeFrameType.Element, 1234, "Some element", 0, RenderTreeFrameType.Element, 1234, "Some element", 0, 0,
RenderTreeFrameType.ElementReferenceCapture, "my unique ID", 0, 0, RenderTreeFrameType.ElementReferenceCapture, "my unique ID", 0, 0, 0,
RenderTreeFrameType.Region, 1234, 0, 0, RenderTreeFrameType.Region, 1234, 0, 0, 0,
RenderTreeFrameType.Text, "Some text", 0, 0, RenderTreeFrameType.Text, "Some text", 0, 0, 0,
RenderTreeFrameType.Markup, "Some markup", 0, 0, RenderTreeFrameType.Markup, "Some markup", 0, 0, 0,
RenderTreeFrameType.Text, "\n\t ", 0, 0, RenderTreeFrameType.Text, "\n\t ", 0, 0, 0,
RenderTreeFrameType.Attribute, "Attribute with string value", "String value", 0, RenderTreeFrameType.Attribute, "Attribute with string value", "String value", 0, 0,
RenderTreeFrameType.Element, 999, "Some element", 0, RenderTreeFrameType.Element, 999, "Some element", 0, 0,
RenderTreeFrameType.Text, "Some text", 0, 0, RenderTreeFrameType.Text, "Some text", 0, 0, 0,
RenderTreeFrameType.Markup, "Some markup", 0, 0, RenderTreeFrameType.Markup, "Some markup", 0, 0, 0,
RenderTreeFrameType.Text, "\n\t ", 0, 0 RenderTreeFrameType.Text, "\n\t ", 0, 0, 0
); );
Assert.Equal(new[] Assert.Equal(new[]
@ -279,6 +279,10 @@ namespace Ignitor
{ {
Assert.Equal(expectedInt, reader.ReadInt32()); Assert.Equal(expectedInt, reader.ReadInt32());
} }
else if (expectedEntry is ulong expectedUlong)
{
Assert.Equal(expectedUlong, reader.ReadUInt64());
}
else if (expectedEntry is string || expectedEntry == NullStringMarker) else if (expectedEntry is string || expectedEntry == NullStringMarker)
{ {
// For strings, we have to look up the value in the table of strings // For strings, we have to look up the value in the table of strings

View File

@ -17,7 +17,10 @@ namespace ComponentsApp.Server
{ {
services.AddMvc(); services.AddMvc();
services.AddSingleton<CircuitHandler, LoggingCircuitHandler>(); services.AddSingleton<CircuitHandler, LoggingCircuitHandler>();
services.AddServerSideBlazor(); services.AddServerSideBlazor(options =>
{
options.JSInteropDetailedErrors = true;
});
services.AddSingleton<WeatherForecastService, DefaultWeatherForecastService>(); services.AddSingleton<WeatherForecastService, DefaultWeatherForecastService>();
} }

View File

@ -93,7 +93,7 @@ namespace Ignitor
} }
private void DisposeEventHandler(int eventHandlerId) private void DisposeEventHandler(ulong eventHandlerId)
{ {
} }

View File

@ -101,7 +101,7 @@ namespace Ignitor
public class ElementEventDescriptor public class ElementEventDescriptor
{ {
public ElementEventDescriptor(string eventName, int eventId) public ElementEventDescriptor(string eventName, ulong eventId)
{ {
EventName = eventName ?? throw new ArgumentNullException(nameof(eventName)); EventName = eventName ?? throw new ArgumentNullException(nameof(eventName));
EventId = eventId; EventId = eventId;
@ -109,7 +109,7 @@ namespace Ignitor
public string EventName { get; } public string EventName { get; }
public int EventId { get; } public ulong EventId { get; }
} }
public async Task ClickAsync(HubConnection connection) public async Task ClickAsync(HubConnection connection)

View File

@ -14,6 +14,8 @@ namespace Ignitor
{ {
public static class RenderBatchReader public static class RenderBatchReader
{ {
private const int ReferenceFrameSize = 20;
private static readonly Renderer Renderer = new FakeRenderer(); private static readonly Renderer Renderer = new FakeRenderer();
public static RenderBatch Read(ReadOnlySpan<byte> data) public static RenderBatch Read(ReadOnlySpan<byte> data)
@ -131,69 +133,67 @@ namespace Ignitor
private static ArrayRange<RenderTreeFrame> ReadReferenceFrames(ReadOnlySpan<byte> data, string[] strings) private static ArrayRange<RenderTreeFrame> ReadReferenceFrames(ReadOnlySpan<byte> data, string[] strings)
{ {
var result = new RenderTreeFrame[data.Length / 16]; var result = new RenderTreeFrame[data.Length / ReferenceFrameSize];
for (var i = 0; i < data.Length; i += 16) for (var i = 0; i < data.Length; i += ReferenceFrameSize)
{ {
var frameData = data.Slice(i, 16); var frameData = data.Slice(i, ReferenceFrameSize);
var type = (RenderTreeFrameType)BitConverter.ToInt32(frameData.Slice(0, 4)); var type = (RenderTreeFrameType)BitConverter.ToInt32(frameData.Slice(0, 4));
// We want each frame to take up the same number of bytes, so that the // 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 // recipient can index into the array directly instead of having to
// walk through it. // 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 // common size. For smaller frames, we add padding to expand it to
// 12 bytes (i.e., 3 x 4-byte ints). // 16 bytes.
// The total size then for each frame is 16 bytes (frame type, then
// 3 other ints).
switch (type) switch (type)
{ {
case RenderTreeFrameType.Attribute: case RenderTreeFrameType.Attribute:
var attributeName = ReadString(frameData.Slice(4, 4), strings); var attributeName = ReadString(frameData.Slice(4, 4), strings);
var attributeValue = ReadString(frameData.Slice(8, 4), strings); var attributeValue = ReadString(frameData.Slice(8, 4), strings);
var attributeEventHandlerId = BitConverter.ToInt32(frameData.Slice(12, 4)); var attributeEventHandlerId = BitConverter.ToUInt64(frameData.Slice(12, 8));
result[i / 16] = RenderTreeFrame.Attribute(0, attributeName, attributeValue).WithAttributeEventHandlerId(attributeEventHandlerId); result[i / ReferenceFrameSize] = RenderTreeFrame.Attribute(0, attributeName, attributeValue).WithAttributeEventHandlerId(attributeEventHandlerId);
break; break;
case RenderTreeFrameType.Component: case RenderTreeFrameType.Component:
var componentSubtreeLength = BitConverter.ToInt32(frameData.Slice(4, 4)); var componentSubtreeLength = BitConverter.ToInt32(frameData.Slice(4, 4));
var componentId = BitConverter.ToInt32(frameData.Slice(8, 4)); // Nowhere to put this without creating a ComponentState var componentId = BitConverter.ToInt32(frameData.Slice(8, 4)); // Nowhere to put this without creating a ComponentState
result[i / 16] = RenderTreeFrame.ChildComponent(0, componentType: null) result[i / ReferenceFrameSize] = RenderTreeFrame.ChildComponent(0, componentType: null)
.WithComponentSubtreeLength(componentSubtreeLength) .WithComponentSubtreeLength(componentSubtreeLength)
.WithComponent(new ComponentState(Renderer, componentId, new FakeComponent(), null)); .WithComponent(new ComponentState(Renderer, componentId, new FakeComponent(), null));
break; break;
case RenderTreeFrameType.ComponentReferenceCapture: case RenderTreeFrameType.ComponentReferenceCapture:
// Client doesn't process these, skip. // Client doesn't process these, skip.
result[i / 16] = RenderTreeFrame.ComponentReferenceCapture(0, null, 0); result[i / ReferenceFrameSize] = RenderTreeFrame.ComponentReferenceCapture(0, null, 0);
break; break;
case RenderTreeFrameType.Element: case RenderTreeFrameType.Element:
var elementSubtreeLength = BitConverter.ToInt32(frameData.Slice(4, 4)); var elementSubtreeLength = BitConverter.ToInt32(frameData.Slice(4, 4));
var elementName = ReadString(frameData.Slice(8, 4), strings); var elementName = ReadString(frameData.Slice(8, 4), strings);
result[i / 16] = RenderTreeFrame.Element(0, elementName).WithElementSubtreeLength(elementSubtreeLength); result[i / ReferenceFrameSize] = RenderTreeFrame.Element(0, elementName).WithElementSubtreeLength(elementSubtreeLength);
break; break;
case RenderTreeFrameType.ElementReferenceCapture: case RenderTreeFrameType.ElementReferenceCapture:
var referenceCaptureId = ReadString(frameData.Slice(4, 4), strings); var referenceCaptureId = ReadString(frameData.Slice(4, 4), strings);
result[i / 16] = RenderTreeFrame.ElementReferenceCapture(0, null) result[i / ReferenceFrameSize] = RenderTreeFrame.ElementReferenceCapture(0, null)
.WithElementReferenceCaptureId(referenceCaptureId); .WithElementReferenceCaptureId(referenceCaptureId);
break; break;
case RenderTreeFrameType.Region: case RenderTreeFrameType.Region:
var regionSubtreeLength = BitConverter.ToInt32(frameData.Slice(4, 4)); var regionSubtreeLength = BitConverter.ToInt32(frameData.Slice(4, 4));
result[i / 16] = RenderTreeFrame.Region(0).WithRegionSubtreeLength(regionSubtreeLength); result[i / ReferenceFrameSize] = RenderTreeFrame.Region(0).WithRegionSubtreeLength(regionSubtreeLength);
break; break;
case RenderTreeFrameType.Text: case RenderTreeFrameType.Text:
var text = ReadString(frameData.Slice(4, 4), strings); var text = ReadString(frameData.Slice(4, 4), strings);
result[i / 16] = RenderTreeFrame.Text(0, text); result[i / ReferenceFrameSize] = RenderTreeFrame.Text(0, text);
break; break;
case RenderTreeFrameType.Markup: case RenderTreeFrameType.Markup:
var markup = ReadString(frameData.Slice(4, 4), strings); var markup = ReadString(frameData.Slice(4, 4), strings);
result[i / 16] = RenderTreeFrame.Markup(0, markup); result[i / ReferenceFrameSize] = RenderTreeFrame.Markup(0, markup);
break; break;
default: default:
@ -209,9 +209,9 @@ namespace Ignitor
return new ArrayRange<int>(Array.Empty<int>(), 0); return new ArrayRange<int>(Array.Empty<int>(), 0);
} }
private static ArrayRange<int> ReadDisposedEventHandlerIds(ReadOnlySpan<byte> data) private static ArrayRange<ulong> ReadDisposedEventHandlerIds(ReadOnlySpan<byte> data)
{ {
return new ArrayRange<int>(Array.Empty<int>(), 0); return new ArrayRange<ulong>(Array.Empty<ulong>(), 0);
} }
private static string ReadString(ReadOnlySpan<byte> data, string[] strings) private static string ReadString(ReadOnlySpan<byte> data, string[] strings)
@ -276,7 +276,7 @@ namespace Ignitor
{ {
// This is a count-prefixed contiguous array of RenderTreeFrame. // This is a count-prefixed contiguous array of RenderTreeFrame.
var count = BitConverter.ToInt32(data.Slice(_referenceFrames, 4)); var count = BitConverter.ToInt32(data.Slice(_referenceFrames, 4));
return data.Slice(_referenceFrames + 4, count * 16); return data.Slice(_referenceFrames + 4, count * ReferenceFrameSize);
} }
public ReadOnlySpan<byte> GetStringTableIndexes(ReadOnlySpan<byte> data) public ReadOnlySpan<byte> GetStringTableIndexes(ReadOnlySpan<byte> data)