Make ArrayBuilder<T>.ToSegment() safe even when reusing underlying arrays (#11903)
This commit is contained in:
parent
7d545d40aa
commit
6f6d099113
|
|
@ -713,6 +713,18 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
|||
}
|
||||
namespace Microsoft.AspNetCore.Components.RenderTree
|
||||
{
|
||||
[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
|
||||
public readonly partial struct ArrayBuilderSegment<T> : System.Collections.Generic.IEnumerable<T>, System.Collections.IEnumerable
|
||||
{
|
||||
private readonly object _dummy;
|
||||
private readonly int _dummyPrimitive;
|
||||
public T[] Array { get { throw null; } }
|
||||
public int Count { get { throw null; } }
|
||||
public T this[int index] { get { throw null; } }
|
||||
public int Offset { get { throw null; } }
|
||||
System.Collections.Generic.IEnumerator<T> System.Collections.Generic.IEnumerable<T>.GetEnumerator() { throw null; }
|
||||
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { throw null; }
|
||||
}
|
||||
[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
|
||||
public readonly partial struct ArrayRange<T>
|
||||
{
|
||||
|
|
@ -759,7 +771,7 @@ namespace Microsoft.AspNetCore.Components.RenderTree
|
|||
public readonly partial struct RenderTreeDiff
|
||||
{
|
||||
public readonly int ComponentId;
|
||||
public readonly System.ArraySegment<Microsoft.AspNetCore.Components.RenderTree.RenderTreeEdit> Edits;
|
||||
public readonly Microsoft.AspNetCore.Components.RenderTree.ArrayBuilderSegment<Microsoft.AspNetCore.Components.RenderTree.RenderTreeEdit> Edits;
|
||||
}
|
||||
[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Explicit)]
|
||||
public readonly partial struct RenderTreeEdit
|
||||
|
|
|
|||
|
|
@ -152,13 +152,13 @@ namespace Microsoft.AspNetCore.Components.RenderTree
|
|||
=> new ArrayRange<T>(_items, _itemsInUse);
|
||||
|
||||
/// <summary>
|
||||
/// Produces an <see cref="ArraySegment{T}"/> structure describing the selected contents.
|
||||
/// Produces an <see cref="ArrayBuilderSegment{T}"/> structure describing the selected contents.
|
||||
/// </summary>
|
||||
/// <param name="fromIndexInclusive">The index of the first item in the segment.</param>
|
||||
/// <param name="toIndexExclusive">One plus the index of the last item in the segment.</param>
|
||||
/// <returns>The <see cref="ArraySegment{T}"/>.</returns>
|
||||
public ArraySegment<T> ToSegment(int fromIndexInclusive, int toIndexExclusive)
|
||||
=> new ArraySegment<T>(_items, fromIndexInclusive, toIndexExclusive - fromIndexInclusive);
|
||||
public ArrayBuilderSegment<T> ToSegment(int fromIndexInclusive, int toIndexExclusive)
|
||||
=> new ArrayBuilderSegment<T>(this, fromIndexInclusive, toIndexExclusive - fromIndexInclusive);
|
||||
|
||||
private void SetCapacity(int desiredCapacity, bool preserveContents)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -0,0 +1,59 @@
|
|||
// 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.
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.AspNetCore.Components.RenderTree
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a range of elements within an instance of <see cref="ArrayBuilder{T}"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the elements in the array</typeparam>
|
||||
public readonly struct ArrayBuilderSegment<T> : IEnumerable<T>
|
||||
{
|
||||
private readonly ArrayBuilder<T> _builder;
|
||||
private readonly int _offset;
|
||||
private readonly int _count;
|
||||
|
||||
internal ArrayBuilderSegment(ArrayBuilder<T> builder, int offset, int count)
|
||||
{
|
||||
_builder = builder;
|
||||
_offset = offset;
|
||||
_count = count;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current underlying array holding the segment's elements.
|
||||
/// </summary>
|
||||
public T[] Array => _builder?.Buffer;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the offset into the underlying array holding the segment's elements.
|
||||
/// </summary>
|
||||
public int Offset => _offset;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of items in the segment.
|
||||
/// </summary>
|
||||
public int Count => _count;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the specified item from the segment.
|
||||
/// </summary>
|
||||
/// <param name="index">The index into the segment.</param>
|
||||
/// <returns>The array entry at the specified index within the segment.</returns>
|
||||
public T this[int index]
|
||||
=> _builder.Buffer[_offset + index];
|
||||
|
||||
IEnumerator<T> IEnumerable<T>.GetEnumerator()
|
||||
=> ((IEnumerable<T>)new ArraySegment<T>(_builder.Buffer, _offset, _count)).GetEnumerator();
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
=> ((IEnumerable)new ArraySegment<T>(_builder.Buffer, _offset, _count)).GetEnumerator();
|
||||
|
||||
// TODO: If this assembly later moves to netstandard2.1, consider adding a public
|
||||
// GetEnumerator method that returns ArraySegment.Enumerator to avoid boxing.
|
||||
}
|
||||
}
|
||||
|
|
@ -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.
|
||||
|
||||
using System;
|
||||
|
|
@ -18,11 +18,11 @@ namespace Microsoft.AspNetCore.Components.RenderTree
|
|||
/// <summary>
|
||||
/// Gets the changes to the render tree since a previous state.
|
||||
/// </summary>
|
||||
public readonly ArraySegment<RenderTreeEdit> Edits;
|
||||
public readonly ArrayBuilderSegment<RenderTreeEdit> Edits;
|
||||
|
||||
internal RenderTreeDiff(
|
||||
int componentId,
|
||||
ArraySegment<RenderTreeEdit> entries)
|
||||
ArrayBuilderSegment<RenderTreeEdit> entries)
|
||||
{
|
||||
ComponentId = componentId;
|
||||
Edits = entries;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,55 @@
|
|||
// 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.
|
||||
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Components.RenderTree;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Components.Rendering
|
||||
{
|
||||
public class ArrayBuilderSegmentTest
|
||||
{
|
||||
[Fact]
|
||||
public void BasicPropertiesWork()
|
||||
{
|
||||
// Arrange: builder containing 1..5
|
||||
var builder = new ArrayBuilder<int>();
|
||||
builder.Append(new[] { 1, 2, 3, 4, 5 }, 0, 5);
|
||||
|
||||
// Act: take segment containing 2..3
|
||||
var segment = builder.ToSegment(1, 3);
|
||||
|
||||
// Act
|
||||
Assert.Same(builder.Buffer, segment.Array);
|
||||
Assert.Equal(1, segment.Offset);
|
||||
Assert.Equal(2, segment.Count);
|
||||
Assert.Equal(2, segment[0]);
|
||||
Assert.Equal(3, segment[1]);
|
||||
Assert.Equal(new[] { 2, 3 }, segment);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void StillWorksAfterUnderlyingCapacityChange()
|
||||
{
|
||||
// Arrange: builder containing 1..8
|
||||
var builder = new ArrayBuilder<int>(capacity: 10);
|
||||
builder.Append(new[] { 1, 2, 3, 4, 5, 6, 7, 8 }, 0, 8);
|
||||
var originalBuffer = builder.Buffer;
|
||||
|
||||
// Act/Assert 1: take segment containing 1..5
|
||||
var segment = builder.ToSegment(0, 5);
|
||||
Assert.Equal(new[] { 1, 2, 3, 4, 5 }, segment);
|
||||
Assert.Same(originalBuffer, segment.Array);
|
||||
|
||||
// Act 2: grow the builder enough to force a resize
|
||||
builder.Append(new[] { 9, 10, 11 }, 0, 3);
|
||||
Array.Clear(originalBuffer, 0, originalBuffer.Length); // Extra proof that we're not using the original storage
|
||||
|
||||
// Assert 2
|
||||
Assert.Same(builder.Buffer, segment.Array);
|
||||
Assert.NotSame(originalBuffer, segment.Array); // Since there was a resize
|
||||
Assert.Equal(new[] { 1, 2, 3, 4, 5 }, segment);
|
||||
Assert.Equal(new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 }, builder.ToSegment(0, builder.Count));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -32,7 +32,7 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
|
|||
if (cancellationStatus.Canceled)
|
||||
{
|
||||
// Avoid creating a circuit host if other component earlier in the pipeline already triggered
|
||||
// cancelation (e.g., by navigating or throwing). Instead render nothing.
|
||||
// cancellation (e.g., by navigating or throwing). Instead render nothing.
|
||||
return new ComponentPrerenderResult(Array.Empty<string>());
|
||||
}
|
||||
var circuitHost = GetOrCreateCircuitHost(context, cancellationStatus);
|
||||
|
|
|
|||
|
|
@ -152,11 +152,13 @@ namespace Microsoft.AspNetCore.Components.Server
|
|||
RenderTreeEdit.UpdateMarkup(108, 109),
|
||||
RenderTreeEdit.RemoveAttribute(110, "Some removed attribute"), // To test deduplication
|
||||
};
|
||||
var editsBuilder = new ArrayBuilder<RenderTreeEdit>();
|
||||
editsBuilder.Append(edits, 0, edits.Length);
|
||||
var editsSegment = editsBuilder.ToSegment(1, edits.Length); // Skip first to show offset is respected
|
||||
var bytes = Serialize(new RenderBatch(
|
||||
new ArrayRange<RenderTreeDiff>(new[]
|
||||
{
|
||||
new RenderTreeDiff(123, new ArraySegment<RenderTreeEdit>(
|
||||
edits, 1, edits.Length - 1)) // Skip first to show offset is respected
|
||||
new RenderTreeDiff(123, editsSegment)
|
||||
}, 1),
|
||||
default,
|
||||
default,
|
||||
|
|
|
|||
|
|
@ -34,9 +34,11 @@ namespace Microsoft.AspNetCore.Components.Test.Helpers
|
|||
}
|
||||
|
||||
// Clone the diff, because its underlying storage will get reused in subsequent batches
|
||||
var cloneBuilder = new ArrayBuilder<RenderTreeEdit>();
|
||||
cloneBuilder.Append(diff.Edits.ToArray(), 0, diff.Edits.Count);
|
||||
var diffClone = new RenderTreeDiff(
|
||||
diff.ComponentId,
|
||||
new ArraySegment<RenderTreeEdit>(diff.Edits.ToArray()));
|
||||
cloneBuilder.ToSegment(0, diff.Edits.Count));
|
||||
DiffsByComponentId[componentId].Add(diffClone);
|
||||
DiffsInOrder.Add(diffClone);
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -1,4 +1,4 @@
|
|||
import { RenderBatch, ArraySegment, RenderTreeEdit, RenderTreeFrame, EditType, FrameType, ArrayValues } from './RenderBatch/RenderBatch';
|
||||
import { RenderBatch, ArrayBuilderSegment, RenderTreeEdit, RenderTreeFrame, EditType, FrameType, ArrayValues } from './RenderBatch/RenderBatch';
|
||||
import { EventDelegator } from './EventDelegator';
|
||||
import { EventForDotNet, UIEventArgs } from './EventForDotNet';
|
||||
import { LogicalElement, PermutationListEntry, toLogicalElement, insertLogicalChild, removeLogicalChild, getLogicalParent, getLogicalChild, createAndInsertLogicalContainer, isSvgElement, getLogicalChildrenArray, getLogicalSiblingEnd, permuteLogicalChildren, getClosestDomElement } from './LogicalElements';
|
||||
|
|
@ -29,7 +29,7 @@ export class BrowserRenderer {
|
|||
rootComponentsPendingFirstRender[componentId] = element;
|
||||
}
|
||||
|
||||
public updateComponent(batch: RenderBatch, componentId: number, edits: ArraySegment<RenderTreeEdit>, referenceFrames: ArrayValues<RenderTreeFrame>): void {
|
||||
public updateComponent(batch: RenderBatch, componentId: number, edits: ArrayBuilderSegment<RenderTreeEdit>, referenceFrames: ArrayValues<RenderTreeFrame>): void {
|
||||
const element = this.childComponentLocations[componentId];
|
||||
if (!element) {
|
||||
throw new Error(`No element is currently associated with component ${componentId}`);
|
||||
|
|
@ -71,17 +71,17 @@ export class BrowserRenderer {
|
|||
this.childComponentLocations[componentId] = element;
|
||||
}
|
||||
|
||||
private applyEdits(batch: RenderBatch, componentId: number, parent: LogicalElement, childIndex: number, edits: ArraySegment<RenderTreeEdit>, referenceFrames: ArrayValues<RenderTreeFrame>) {
|
||||
private applyEdits(batch: RenderBatch, componentId: number, parent: LogicalElement, childIndex: number, edits: ArrayBuilderSegment<RenderTreeEdit>, referenceFrames: ArrayValues<RenderTreeFrame>) {
|
||||
let currentDepth = 0;
|
||||
let childIndexAtCurrentDepth = childIndex;
|
||||
let permutationList: PermutationListEntry[] | undefined;
|
||||
|
||||
const arraySegmentReader = batch.arraySegmentReader;
|
||||
const arrayBuilderSegmentReader = batch.arrayBuilderSegmentReader;
|
||||
const editReader = batch.editReader;
|
||||
const frameReader = batch.frameReader;
|
||||
const editsValues = arraySegmentReader.values(edits);
|
||||
const editsOffset = arraySegmentReader.offset(edits);
|
||||
const editsLength = arraySegmentReader.count(edits);
|
||||
const editsValues = arrayBuilderSegmentReader.values(edits);
|
||||
const editsOffset = arrayBuilderSegmentReader.offset(edits);
|
||||
const editsLength = arrayBuilderSegmentReader.count(edits);
|
||||
const maxEditIndexExcl = editsOffset + editsLength;
|
||||
|
||||
for (let editIndex = editsOffset; editIndex < maxEditIndexExcl; editIndex++) {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { RenderBatch, ArrayRange, RenderTreeDiff, ArrayValues, RenderTreeEdit, EditType, FrameType, RenderTreeFrame, RenderTreeDiffReader, RenderTreeFrameReader, RenderTreeEditReader, ArrayRangeReader, ArraySegmentReader, ArraySegment } from './RenderBatch';
|
||||
import { RenderBatch, ArrayRange, RenderTreeDiff, ArrayValues, RenderTreeEdit, EditType, FrameType, RenderTreeFrame, RenderTreeDiffReader, RenderTreeFrameReader, RenderTreeEditReader, ArrayRangeReader, ArrayBuilderSegmentReader, ArrayBuilderSegment } from './RenderBatch';
|
||||
import { decodeUtf8 } from './Utf8Decoder';
|
||||
|
||||
const updatedComponentsEntryLength = 4; // Each is a single int32 giving the location of the data
|
||||
|
|
@ -13,7 +13,7 @@ export class OutOfProcessRenderBatch implements RenderBatch {
|
|||
const stringReader = new OutOfProcessStringReader(batchData);
|
||||
|
||||
this.arrayRangeReader = new OutOfProcessArrayRangeReader(batchData);
|
||||
this.arraySegmentReader = new OutOfProcessArraySegmentReader(batchData);
|
||||
this.arrayBuilderSegmentReader = new OutOfProcessArrayBuilderSegmentReader(batchData);
|
||||
this.diffReader = new OutOfProcessRenderTreeDiffReader(batchData);
|
||||
this.editReader = new OutOfProcessRenderTreeEditReader(batchData, stringReader);
|
||||
this.frameReader = new OutOfProcessRenderTreeFrameReader(batchData, stringReader);
|
||||
|
|
@ -62,7 +62,7 @@ export class OutOfProcessRenderBatch implements RenderBatch {
|
|||
|
||||
arrayRangeReader: ArrayRangeReader;
|
||||
|
||||
arraySegmentReader: ArraySegmentReader;
|
||||
arrayBuilderSegmentReader: ArrayBuilderSegmentReader;
|
||||
}
|
||||
|
||||
class OutOfProcessRenderTreeDiffReader implements RenderTreeDiffReader {
|
||||
|
|
@ -207,24 +207,24 @@ class OutOfProcessArrayRangeReader implements ArrayRangeReader {
|
|||
}
|
||||
}
|
||||
|
||||
class OutOfProcessArraySegmentReader implements ArraySegmentReader {
|
||||
class OutOfProcessArrayBuilderSegmentReader implements ArrayBuilderSegmentReader {
|
||||
constructor(private batchDataUint8: Uint8Array) {
|
||||
}
|
||||
|
||||
offset<T>(arraySegment: ArraySegment<T>) {
|
||||
offset<T>(arrayBuilderSegment: ArrayBuilderSegment<T>) {
|
||||
// Not used by the out-of-process representation of RenderBatch data.
|
||||
// This only exists on the ArraySegmentReader for the shared-memory representation.
|
||||
// This only exists on the ArrayBuilderSegmentReader for the shared-memory representation.
|
||||
return 0;
|
||||
}
|
||||
|
||||
count<T>(arraySegment: ArraySegment<T>) {
|
||||
count<T>(arrayBuilderSegment: ArrayBuilderSegment<T>) {
|
||||
// First int is count
|
||||
return readInt32LE(this.batchDataUint8, arraySegment as any);
|
||||
return readInt32LE(this.batchDataUint8, arrayBuilderSegment as any);
|
||||
}
|
||||
|
||||
values<T>(arraySegment: ArraySegment<T>): ArrayValues<T> {
|
||||
values<T>(arrayBuilderSegment: ArrayBuilderSegment<T>): ArrayValues<T> {
|
||||
// Entries data starts after the 'count' int (i.e., after 4 bytes)
|
||||
return arraySegment as any + 4;
|
||||
return arrayBuilderSegment as any + 4;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ export interface RenderBatch {
|
|||
editReader: RenderTreeEditReader;
|
||||
frameReader: RenderTreeFrameReader;
|
||||
arrayRangeReader: ArrayRangeReader;
|
||||
arraySegmentReader: ArraySegmentReader;
|
||||
arrayBuilderSegmentReader: ArrayBuilderSegmentReader;
|
||||
}
|
||||
|
||||
export interface ArrayRangeReader {
|
||||
|
|
@ -21,15 +21,15 @@ export interface ArrayRangeReader {
|
|||
values<T>(arrayRange: ArrayRange<T>): ArrayValues<T>;
|
||||
}
|
||||
|
||||
export interface ArraySegmentReader {
|
||||
offset<T>(arraySegment: ArraySegment<T>): number;
|
||||
count<T>(arraySegment: ArraySegment<T>): number;
|
||||
values<T>(arraySegment: ArraySegment<T>): ArrayValues<T>;
|
||||
export interface ArrayBuilderSegmentReader {
|
||||
offset<T>(arrayBuilderSegment: ArrayBuilderSegment<T>): number;
|
||||
count<T>(arrayBuilderSegment: ArrayBuilderSegment<T>): number;
|
||||
values<T>(arrayBuilderSegment: ArrayBuilderSegment<T>): ArrayValues<T>;
|
||||
}
|
||||
|
||||
export interface RenderTreeDiffReader {
|
||||
componentId(diff: RenderTreeDiff): number;
|
||||
edits(diff: RenderTreeDiff): ArraySegment<RenderTreeEdit>;
|
||||
edits(diff: RenderTreeDiff): ArrayBuilderSegment<RenderTreeEdit>;
|
||||
editsEntry(values: ArrayValues<RenderTreeEdit>, index: number): RenderTreeEdit;
|
||||
}
|
||||
|
||||
|
|
@ -55,7 +55,7 @@ export interface RenderTreeFrameReader {
|
|||
}
|
||||
|
||||
export interface ArrayRange<T> { ArrayRange__DO_NOT_IMPLEMENT: any }
|
||||
export interface ArraySegment<T> { ArraySegment__DO_NOT_IMPLEMENT: any }
|
||||
export interface ArrayBuilderSegment<T> { ArrayBuilderSegment__DO_NOT_IMPLEMENT: any }
|
||||
export interface ArrayValues<T> { ArrayValues__DO_NOT_IMPLEMENT: any }
|
||||
|
||||
export interface RenderTreeDiff { RenderTreeDiff__DO_NOT_IMPLEMENT: any }
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { platform } from '../../Environment';
|
||||
import { RenderBatch, ArrayRange, ArrayRangeReader, ArraySegment, RenderTreeDiff, RenderTreeEdit, RenderTreeFrame, ArrayValues, EditType, FrameType, RenderTreeFrameReader } from './RenderBatch';
|
||||
import { Pointer, System_Array } from '../../Platform/Platform';
|
||||
import { RenderBatch, ArrayRange, ArrayRangeReader, ArrayBuilderSegment, RenderTreeDiff, RenderTreeEdit, RenderTreeFrame, ArrayValues, EditType, FrameType, RenderTreeFrameReader } from './RenderBatch';
|
||||
import { Pointer, System_Array, System_Object } from '../../Platform/Platform';
|
||||
|
||||
// Used when running on Mono WebAssembly for shared-memory interop. The code here encapsulates
|
||||
// our knowledge of the memory layout of RenderBatch and all referenced types.
|
||||
|
|
@ -49,7 +49,7 @@ export class SharedMemoryRenderBatch implements RenderBatch {
|
|||
|
||||
arrayRangeReader = arrayRangeReader;
|
||||
|
||||
arraySegmentReader = arraySegmentReader;
|
||||
arrayBuilderSegmentReader = arrayBuilderSegmentReader;
|
||||
|
||||
diffReader = diffReader;
|
||||
|
||||
|
|
@ -65,19 +65,24 @@ const arrayRangeReader = {
|
|||
count: <T>(arrayRange: ArrayRange<T>) => platform.readInt32Field(arrayRange as any, 4),
|
||||
};
|
||||
|
||||
// Keep in sync with memory layout in ArraySegment
|
||||
const arraySegmentReader = {
|
||||
// Keep in sync with memory layout in ArrayBuilderSegment
|
||||
const arrayBuilderSegmentReader = {
|
||||
structLength: 12,
|
||||
values: <T>(arraySegment: ArraySegment<T>) => platform.readObjectField<System_Array<T>>(arraySegment as any, 0) as any as ArrayValues<T>,
|
||||
offset: <T>(arraySegment: ArraySegment<T>) => platform.readInt32Field(arraySegment as any, 4),
|
||||
count: <T>(arraySegment: ArraySegment<T>) => platform.readInt32Field(arraySegment as any, 8),
|
||||
values: <T>(arrayBuilderSegment: ArrayBuilderSegment<T>) => {
|
||||
// Evaluate arrayBuilderSegment->_builder->_items, i.e., two dereferences needed
|
||||
const builder = platform.readObjectField<System_Object>(arrayBuilderSegment as any, 0);
|
||||
const builderFieldsAddress = platform.getObjectFieldsBaseAddress(builder);
|
||||
return platform.readObjectField<System_Array<T>>(builderFieldsAddress, 0) as any as ArrayValues<T>;
|
||||
},
|
||||
offset: <T>(arrayBuilderSegment: ArrayBuilderSegment<T>) => platform.readInt32Field(arrayBuilderSegment as any, 4),
|
||||
count: <T>(arrayBuilderSegment: ArrayBuilderSegment<T>) => platform.readInt32Field(arrayBuilderSegment as any, 8),
|
||||
};
|
||||
|
||||
// Keep in sync with memory layout in RenderTreeDiff.cs
|
||||
const diffReader = {
|
||||
structLength: 4 + arraySegmentReader.structLength,
|
||||
structLength: 4 + arrayBuilderSegmentReader.structLength,
|
||||
componentId: (diff: RenderTreeDiff) => platform.readInt32Field(diff as any, 0),
|
||||
edits: (diff: RenderTreeDiff) => platform.readStructField<Pointer>(diff as any, 4) as any as ArraySegment<RenderTreeEdit>,
|
||||
edits: (diff: RenderTreeDiff) => platform.readStructField<Pointer>(diff as any, 4) as any as ArrayBuilderSegment<RenderTreeEdit>,
|
||||
editsEntry: (values: ArrayValues<RenderTreeEdit>, index: number) => arrayValuesEntry(values, index, editReader.structLength),
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -101,11 +101,13 @@ namespace Ignitor
|
|||
RenderTreeEdit.UpdateMarkup(108, 109),
|
||||
RenderTreeEdit.RemoveAttribute(110, "Some removed attribute"), // To test deduplication
|
||||
};
|
||||
var editsBuilder = new ArrayBuilder<RenderTreeEdit>();
|
||||
editsBuilder.Append(edits, 0, edits.Length);
|
||||
var editsSegment = editsBuilder.ToSegment(1, edits.Length); // Skip first to show offset is respected
|
||||
var bytes = RoundTripSerialize(new RenderBatch(
|
||||
new ArrayRange<RenderTreeDiff>(new[]
|
||||
{
|
||||
new RenderTreeDiff(123, new ArraySegment<RenderTreeEdit>(
|
||||
edits, 1, edits.Length - 1)) // Skip first to show offset is respected
|
||||
new RenderTreeDiff(123, editsSegment)
|
||||
}, 1),
|
||||
default,
|
||||
default,
|
||||
|
|
|
|||
|
|
@ -77,7 +77,7 @@ namespace Ignitor
|
|||
}
|
||||
}
|
||||
|
||||
private void UpdateComponent(RenderBatch batch, int componentId, ArraySegment<RenderTreeEdit> edits)
|
||||
private void UpdateComponent(RenderBatch batch, int componentId, ArrayBuilderSegment<RenderTreeEdit> edits)
|
||||
{
|
||||
if (!Components.TryGetValue(componentId, out var component))
|
||||
{
|
||||
|
|
@ -98,7 +98,7 @@ namespace Ignitor
|
|||
|
||||
}
|
||||
|
||||
private void ApplyEdits(RenderBatch batch, ContainerNode parent, int childIndex, ArraySegment<RenderTreeEdit> edits)
|
||||
private void ApplyEdits(RenderBatch batch, ContainerNode parent, int childIndex, ArrayBuilderSegment<RenderTreeEdit> edits)
|
||||
{
|
||||
var currentDepth = 0;
|
||||
var childIndexAtCurrentDepth = childIndex;
|
||||
|
|
|
|||
|
|
@ -115,12 +115,19 @@ namespace Ignitor
|
|||
}
|
||||
}
|
||||
|
||||
result[i / 4] = new RenderTreeDiff(componentId, new ArraySegment<RenderTreeEdit>(edits));
|
||||
result[i / 4] = new RenderTreeDiff(componentId, ToArrayBuilderSegment(edits));
|
||||
}
|
||||
|
||||
return new ArrayRange<RenderTreeDiff>(result, result.Length);
|
||||
}
|
||||
|
||||
private static ArrayBuilderSegment<T> ToArrayBuilderSegment<T>(T[] entries)
|
||||
{
|
||||
var builder = new ArrayBuilder<T>();
|
||||
builder.Append(entries, 0, entries.Length);
|
||||
return builder.ToSegment(0, entries.Length);
|
||||
}
|
||||
|
||||
private static ArrayRange<RenderTreeFrame> ReadReferenceFrames(ReadOnlySpan<byte> data, string[] strings)
|
||||
{
|
||||
var result = new RenderTreeFrame[data.Length / 16];
|
||||
|
|
|
|||
Loading…
Reference in New Issue