Răsfoiți Sursa

Make ArrayBuilder<T>.ToSegment() safe even when reusing underlying arrays (#11903)

Steve Sanderson 6 ani în urmă
părinte
comite
6f6d099113

+ 13 - 1
src/Components/Components/ref/Microsoft.AspNetCore.Components.netstandard2.0.cs

@@ -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

+ 3 - 3
src/Components/Components/src/RenderTree/ArrayBuilder.cs

@@ -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)
         {

+ 59 - 0
src/Components/Components/src/RenderTree/ArrayBuilderSegment.cs

@@ -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.
+    }
+}

+ 3 - 3
src/Components/Components/src/RenderTree/RenderTreeDiff.cs

@@ -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;

+ 55 - 0
src/Components/Components/test/Rendering/ArrayBuilderSegmentTest.cs

@@ -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));
+        }
+    }
+}

+ 1 - 1
src/Components/Server/src/Circuits/CircuitPrerenderer.cs

@@ -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);

+ 4 - 2
src/Components/Server/test/Circuits/RenderBatchWriterTest.cs

@@ -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,

+ 3 - 1
src/Components/Shared/test/CapturedBatch.cs

@@ -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);
         }

Fișier diff suprimat deoarece este prea mare
+ 0 - 0
src/Components/Web.JS/dist/Release/blazor.server.js


Fișier diff suprimat deoarece este prea mare
+ 0 - 0
src/Components/Web.JS/dist/Release/blazor.webassembly.js


+ 7 - 7
src/Components/Web.JS/src/Rendering/BrowserRenderer.ts

@@ -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++) {

+ 10 - 10
src/Components/Web.JS/src/Rendering/RenderBatch/OutOfProcessRenderBatch.ts

@@ -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;
   }
 }
 

+ 7 - 7
src/Components/Web.JS/src/Rendering/RenderBatch/RenderBatch.ts

@@ -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 }

+ 15 - 10
src/Components/Web.JS/src/Rendering/RenderBatch/SharedMemoryRenderBatch.ts

@@ -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),
 };
 

+ 4 - 2
src/Components/test/Ignitor.Test/RenderBatchReaderTest.cs

@@ -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,

+ 2 - 2
src/Components/test/testassets/Ignitor/ElementHive.cs

@@ -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;

+ 8 - 1
src/Components/test/testassets/Ignitor/RenderBatchReader.cs

@@ -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];

Unele fișiere nu au fost afișate deoarece prea multe fișiere au fost modificate în acest diff