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
|
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)]
|
[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
|
||||||
public readonly partial struct ArrayRange<T>
|
public readonly partial struct ArrayRange<T>
|
||||||
{
|
{
|
||||||
|
|
@ -759,7 +771,7 @@ namespace Microsoft.AspNetCore.Components.RenderTree
|
||||||
public readonly partial struct RenderTreeDiff
|
public readonly partial struct RenderTreeDiff
|
||||||
{
|
{
|
||||||
public readonly int ComponentId;
|
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)]
|
[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Explicit)]
|
||||||
public readonly partial struct RenderTreeEdit
|
public readonly partial struct RenderTreeEdit
|
||||||
|
|
|
||||||
|
|
@ -152,13 +152,13 @@ namespace Microsoft.AspNetCore.Components.RenderTree
|
||||||
=> new ArrayRange<T>(_items, _itemsInUse);
|
=> new ArrayRange<T>(_items, _itemsInUse);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Produces an <see cref="ArraySegment{T}"/> structure describing the selected contents.
|
/// Produces an <see cref="ArrayBuilderSegment{T}"/> structure describing the selected contents.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="fromIndexInclusive">The index of the first item in the segment.</param>
|
/// <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>
|
/// <param name="toIndexExclusive">One plus the index of the last item in the segment.</param>
|
||||||
/// <returns>The <see cref="ArraySegment{T}"/>.</returns>
|
/// <returns>The <see cref="ArraySegment{T}"/>.</returns>
|
||||||
public ArraySegment<T> ToSegment(int fromIndexInclusive, int toIndexExclusive)
|
public ArrayBuilderSegment<T> ToSegment(int fromIndexInclusive, int toIndexExclusive)
|
||||||
=> new ArraySegment<T>(_items, fromIndexInclusive, toIndexExclusive - fromIndexInclusive);
|
=> new ArrayBuilderSegment<T>(this, fromIndexInclusive, toIndexExclusive - fromIndexInclusive);
|
||||||
|
|
||||||
private void SetCapacity(int desiredCapacity, bool preserveContents)
|
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.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
|
@ -18,11 +18,11 @@ namespace Microsoft.AspNetCore.Components.RenderTree
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the changes to the render tree since a previous state.
|
/// Gets the changes to the render tree since a previous state.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public readonly ArraySegment<RenderTreeEdit> Edits;
|
public readonly ArrayBuilderSegment<RenderTreeEdit> Edits;
|
||||||
|
|
||||||
internal RenderTreeDiff(
|
internal RenderTreeDiff(
|
||||||
int componentId,
|
int componentId,
|
||||||
ArraySegment<RenderTreeEdit> entries)
|
ArrayBuilderSegment<RenderTreeEdit> entries)
|
||||||
{
|
{
|
||||||
ComponentId = componentId;
|
ComponentId = componentId;
|
||||||
Edits = entries;
|
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)
|
if (cancellationStatus.Canceled)
|
||||||
{
|
{
|
||||||
// Avoid creating a circuit host if other component earlier in the pipeline already triggered
|
// 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>());
|
return new ComponentPrerenderResult(Array.Empty<string>());
|
||||||
}
|
}
|
||||||
var circuitHost = GetOrCreateCircuitHost(context, cancellationStatus);
|
var circuitHost = GetOrCreateCircuitHost(context, cancellationStatus);
|
||||||
|
|
|
||||||
|
|
@ -152,11 +152,13 @@ namespace Microsoft.AspNetCore.Components.Server
|
||||||
RenderTreeEdit.UpdateMarkup(108, 109),
|
RenderTreeEdit.UpdateMarkup(108, 109),
|
||||||
RenderTreeEdit.RemoveAttribute(110, "Some removed attribute"), // To test deduplication
|
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(
|
var bytes = Serialize(new RenderBatch(
|
||||||
new ArrayRange<RenderTreeDiff>(new[]
|
new ArrayRange<RenderTreeDiff>(new[]
|
||||||
{
|
{
|
||||||
new RenderTreeDiff(123, new ArraySegment<RenderTreeEdit>(
|
new RenderTreeDiff(123, editsSegment)
|
||||||
edits, 1, edits.Length - 1)) // Skip first to show offset is respected
|
|
||||||
}, 1),
|
}, 1),
|
||||||
default,
|
default,
|
||||||
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
|
// 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(
|
var diffClone = new RenderTreeDiff(
|
||||||
diff.ComponentId,
|
diff.ComponentId,
|
||||||
new ArraySegment<RenderTreeEdit>(diff.Edits.ToArray()));
|
cloneBuilder.ToSegment(0, diff.Edits.Count));
|
||||||
DiffsByComponentId[componentId].Add(diffClone);
|
DiffsByComponentId[componentId].Add(diffClone);
|
||||||
DiffsInOrder.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 { EventDelegator } from './EventDelegator';
|
||||||
import { EventForDotNet, UIEventArgs } from './EventForDotNet';
|
import { EventForDotNet, UIEventArgs } from './EventForDotNet';
|
||||||
import { LogicalElement, PermutationListEntry, toLogicalElement, insertLogicalChild, removeLogicalChild, getLogicalParent, getLogicalChild, createAndInsertLogicalContainer, isSvgElement, getLogicalChildrenArray, getLogicalSiblingEnd, permuteLogicalChildren, getClosestDomElement } from './LogicalElements';
|
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;
|
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];
|
const element = this.childComponentLocations[componentId];
|
||||||
if (!element) {
|
if (!element) {
|
||||||
throw new Error(`No element is currently associated with component ${componentId}`);
|
throw new Error(`No element is currently associated with component ${componentId}`);
|
||||||
|
|
@ -71,17 +71,17 @@ export class BrowserRenderer {
|
||||||
this.childComponentLocations[componentId] = element;
|
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 currentDepth = 0;
|
||||||
let childIndexAtCurrentDepth = childIndex;
|
let childIndexAtCurrentDepth = childIndex;
|
||||||
let permutationList: PermutationListEntry[] | undefined;
|
let permutationList: PermutationListEntry[] | undefined;
|
||||||
|
|
||||||
const arraySegmentReader = batch.arraySegmentReader;
|
const arrayBuilderSegmentReader = batch.arrayBuilderSegmentReader;
|
||||||
const editReader = batch.editReader;
|
const editReader = batch.editReader;
|
||||||
const frameReader = batch.frameReader;
|
const frameReader = batch.frameReader;
|
||||||
const editsValues = arraySegmentReader.values(edits);
|
const editsValues = arrayBuilderSegmentReader.values(edits);
|
||||||
const editsOffset = arraySegmentReader.offset(edits);
|
const editsOffset = arrayBuilderSegmentReader.offset(edits);
|
||||||
const editsLength = arraySegmentReader.count(edits);
|
const editsLength = arrayBuilderSegmentReader.count(edits);
|
||||||
const maxEditIndexExcl = editsOffset + editsLength;
|
const maxEditIndexExcl = editsOffset + editsLength;
|
||||||
|
|
||||||
for (let editIndex = editsOffset; editIndex < maxEditIndexExcl; editIndex++) {
|
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';
|
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
|
||||||
|
|
@ -13,7 +13,7 @@ export class OutOfProcessRenderBatch implements RenderBatch {
|
||||||
const stringReader = new OutOfProcessStringReader(batchData);
|
const stringReader = new OutOfProcessStringReader(batchData);
|
||||||
|
|
||||||
this.arrayRangeReader = new OutOfProcessArrayRangeReader(batchData);
|
this.arrayRangeReader = new OutOfProcessArrayRangeReader(batchData);
|
||||||
this.arraySegmentReader = new OutOfProcessArraySegmentReader(batchData);
|
this.arrayBuilderSegmentReader = new OutOfProcessArrayBuilderSegmentReader(batchData);
|
||||||
this.diffReader = new OutOfProcessRenderTreeDiffReader(batchData);
|
this.diffReader = new OutOfProcessRenderTreeDiffReader(batchData);
|
||||||
this.editReader = new OutOfProcessRenderTreeEditReader(batchData, stringReader);
|
this.editReader = new OutOfProcessRenderTreeEditReader(batchData, stringReader);
|
||||||
this.frameReader = new OutOfProcessRenderTreeFrameReader(batchData, stringReader);
|
this.frameReader = new OutOfProcessRenderTreeFrameReader(batchData, stringReader);
|
||||||
|
|
@ -62,7 +62,7 @@ export class OutOfProcessRenderBatch implements RenderBatch {
|
||||||
|
|
||||||
arrayRangeReader: ArrayRangeReader;
|
arrayRangeReader: ArrayRangeReader;
|
||||||
|
|
||||||
arraySegmentReader: ArraySegmentReader;
|
arrayBuilderSegmentReader: ArrayBuilderSegmentReader;
|
||||||
}
|
}
|
||||||
|
|
||||||
class OutOfProcessRenderTreeDiffReader implements RenderTreeDiffReader {
|
class OutOfProcessRenderTreeDiffReader implements RenderTreeDiffReader {
|
||||||
|
|
@ -207,24 +207,24 @@ class OutOfProcessArrayRangeReader implements ArrayRangeReader {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class OutOfProcessArraySegmentReader implements ArraySegmentReader {
|
class OutOfProcessArrayBuilderSegmentReader implements ArrayBuilderSegmentReader {
|
||||||
constructor(private batchDataUint8: Uint8Array) {
|
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.
|
// 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;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
count<T>(arraySegment: ArraySegment<T>) {
|
count<T>(arrayBuilderSegment: ArrayBuilderSegment<T>) {
|
||||||
// First int is count
|
// 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)
|
// 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;
|
editReader: RenderTreeEditReader;
|
||||||
frameReader: RenderTreeFrameReader;
|
frameReader: RenderTreeFrameReader;
|
||||||
arrayRangeReader: ArrayRangeReader;
|
arrayRangeReader: ArrayRangeReader;
|
||||||
arraySegmentReader: ArraySegmentReader;
|
arrayBuilderSegmentReader: ArrayBuilderSegmentReader;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ArrayRangeReader {
|
export interface ArrayRangeReader {
|
||||||
|
|
@ -21,15 +21,15 @@ export interface ArrayRangeReader {
|
||||||
values<T>(arrayRange: ArrayRange<T>): ArrayValues<T>;
|
values<T>(arrayRange: ArrayRange<T>): ArrayValues<T>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ArraySegmentReader {
|
export interface ArrayBuilderSegmentReader {
|
||||||
offset<T>(arraySegment: ArraySegment<T>): number;
|
offset<T>(arrayBuilderSegment: ArrayBuilderSegment<T>): number;
|
||||||
count<T>(arraySegment: ArraySegment<T>): number;
|
count<T>(arrayBuilderSegment: ArrayBuilderSegment<T>): number;
|
||||||
values<T>(arraySegment: ArraySegment<T>): ArrayValues<T>;
|
values<T>(arrayBuilderSegment: ArrayBuilderSegment<T>): ArrayValues<T>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RenderTreeDiffReader {
|
export interface RenderTreeDiffReader {
|
||||||
componentId(diff: RenderTreeDiff): number;
|
componentId(diff: RenderTreeDiff): number;
|
||||||
edits(diff: RenderTreeDiff): ArraySegment<RenderTreeEdit>;
|
edits(diff: RenderTreeDiff): ArrayBuilderSegment<RenderTreeEdit>;
|
||||||
editsEntry(values: ArrayValues<RenderTreeEdit>, index: number): 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 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 ArrayValues<T> { ArrayValues__DO_NOT_IMPLEMENT: any }
|
||||||
|
|
||||||
export interface RenderTreeDiff { RenderTreeDiff__DO_NOT_IMPLEMENT: any }
|
export interface RenderTreeDiff { RenderTreeDiff__DO_NOT_IMPLEMENT: any }
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { platform } from '../../Environment';
|
import { platform } from '../../Environment';
|
||||||
import { RenderBatch, ArrayRange, ArrayRangeReader, ArraySegment, RenderTreeDiff, RenderTreeEdit, RenderTreeFrame, ArrayValues, EditType, FrameType, RenderTreeFrameReader } from './RenderBatch';
|
import { RenderBatch, ArrayRange, ArrayRangeReader, ArrayBuilderSegment, RenderTreeDiff, RenderTreeEdit, RenderTreeFrame, ArrayValues, EditType, FrameType, RenderTreeFrameReader } from './RenderBatch';
|
||||||
import { Pointer, System_Array } from '../../Platform/Platform';
|
import { Pointer, System_Array, System_Object } from '../../Platform/Platform';
|
||||||
|
|
||||||
// Used when running on Mono WebAssembly for shared-memory interop. The code here encapsulates
|
// 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.
|
// our knowledge of the memory layout of RenderBatch and all referenced types.
|
||||||
|
|
@ -49,7 +49,7 @@ export class SharedMemoryRenderBatch implements RenderBatch {
|
||||||
|
|
||||||
arrayRangeReader = arrayRangeReader;
|
arrayRangeReader = arrayRangeReader;
|
||||||
|
|
||||||
arraySegmentReader = arraySegmentReader;
|
arrayBuilderSegmentReader = arrayBuilderSegmentReader;
|
||||||
|
|
||||||
diffReader = diffReader;
|
diffReader = diffReader;
|
||||||
|
|
||||||
|
|
@ -65,19 +65,24 @@ const arrayRangeReader = {
|
||||||
count: <T>(arrayRange: ArrayRange<T>) => platform.readInt32Field(arrayRange as any, 4),
|
count: <T>(arrayRange: ArrayRange<T>) => platform.readInt32Field(arrayRange as any, 4),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Keep in sync with memory layout in ArraySegment
|
// Keep in sync with memory layout in ArrayBuilderSegment
|
||||||
const arraySegmentReader = {
|
const arrayBuilderSegmentReader = {
|
||||||
structLength: 12,
|
structLength: 12,
|
||||||
values: <T>(arraySegment: ArraySegment<T>) => platform.readObjectField<System_Array<T>>(arraySegment as any, 0) as any as ArrayValues<T>,
|
values: <T>(arrayBuilderSegment: ArrayBuilderSegment<T>) => {
|
||||||
offset: <T>(arraySegment: ArraySegment<T>) => platform.readInt32Field(arraySegment as any, 4),
|
// Evaluate arrayBuilderSegment->_builder->_items, i.e., two dereferences needed
|
||||||
count: <T>(arraySegment: ArraySegment<T>) => platform.readInt32Field(arraySegment as any, 8),
|
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
|
// Keep in sync with memory layout in RenderTreeDiff.cs
|
||||||
const diffReader = {
|
const diffReader = {
|
||||||
structLength: 4 + arraySegmentReader.structLength,
|
structLength: 4 + arrayBuilderSegmentReader.structLength,
|
||||||
componentId: (diff: RenderTreeDiff) => platform.readInt32Field(diff as any, 0),
|
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),
|
editsEntry: (values: ArrayValues<RenderTreeEdit>, index: number) => arrayValuesEntry(values, index, editReader.structLength),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -101,11 +101,13 @@ namespace Ignitor
|
||||||
RenderTreeEdit.UpdateMarkup(108, 109),
|
RenderTreeEdit.UpdateMarkup(108, 109),
|
||||||
RenderTreeEdit.RemoveAttribute(110, "Some removed attribute"), // To test deduplication
|
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(
|
var bytes = RoundTripSerialize(new RenderBatch(
|
||||||
new ArrayRange<RenderTreeDiff>(new[]
|
new ArrayRange<RenderTreeDiff>(new[]
|
||||||
{
|
{
|
||||||
new RenderTreeDiff(123, new ArraySegment<RenderTreeEdit>(
|
new RenderTreeDiff(123, editsSegment)
|
||||||
edits, 1, edits.Length - 1)) // Skip first to show offset is respected
|
|
||||||
}, 1),
|
}, 1),
|
||||||
default,
|
default,
|
||||||
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))
|
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 currentDepth = 0;
|
||||||
var childIndexAtCurrentDepth = childIndex;
|
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);
|
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)
|
private static ArrayRange<RenderTreeFrame> ReadReferenceFrames(ReadOnlySpan<byte> data, string[] strings)
|
||||||
{
|
{
|
||||||
var result = new RenderTreeFrame[data.Length / 16];
|
var result = new RenderTreeFrame[data.Length / 16];
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue