Merge branch 'master' into lazyLoadingThreshold
This commit is contained in:
Коммит
fb40d85cc1
|
@ -24,7 +24,7 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
|
|||
/// the arrays in use are rented from the shared <see cref="ArrayPool{T}"/> instance,
|
||||
/// and that <see cref="ArrayPoolBufferWriter{T}"/> is also available on .NET Standard 2.0.
|
||||
/// </remarks>
|
||||
[DebuggerTypeProxy(typeof(ArrayPoolBufferWriterDebugView<>))]
|
||||
[DebuggerTypeProxy(typeof(MemoryDebugView<>))]
|
||||
[DebuggerDisplay("{ToString(),raw}")]
|
||||
public sealed class ArrayPoolBufferWriter<T> : IBuffer<T>, IMemoryOwner<T>
|
||||
{
|
||||
|
|
|
@ -0,0 +1,98 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using Microsoft.Toolkit.HighPerformance.Extensions;
|
||||
|
||||
namespace Microsoft.Toolkit.HighPerformance.Buffers.Internals
|
||||
{
|
||||
/// <summary>
|
||||
/// A custom <see cref="MemoryManager{T}"/> that can wrap arbitrary <see cref="object"/> instances.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of elements in the target memory area.</typeparam>
|
||||
internal sealed class RawObjectMemoryManager<T> : MemoryManager<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// The target <see cref="object"/> instance.
|
||||
/// </summary>
|
||||
private readonly object instance;
|
||||
|
||||
/// <summary>
|
||||
/// The initial offset within <see cref="instance"/>.
|
||||
/// </summary>
|
||||
private readonly IntPtr offset;
|
||||
|
||||
/// <summary>
|
||||
/// The length of the target memory area.
|
||||
/// </summary>
|
||||
private readonly int length;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RawObjectMemoryManager{T}"/> class.
|
||||
/// </summary>
|
||||
/// <param name="instance">The target <see cref="object"/> instance.</param>
|
||||
/// <param name="offset">The starting offset within <paramref name="instance"/>.</param>
|
||||
/// <param name="length">The usable length within <paramref name="instance"/>.</param>
|
||||
public RawObjectMemoryManager(object instance, IntPtr offset, int length)
|
||||
{
|
||||
this.instance = instance;
|
||||
this.offset = offset;
|
||||
this.length = length;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override Span<T> GetSpan()
|
||||
{
|
||||
ref T r0 = ref this.instance.DangerousGetObjectDataReferenceAt<T>(this.offset);
|
||||
|
||||
return MemoryMarshal.CreateSpan(ref r0, this.length);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override unsafe MemoryHandle Pin(int elementIndex = 0)
|
||||
{
|
||||
if ((uint)elementIndex >= (uint)this.length)
|
||||
{
|
||||
ThrowArgumentOutOfRangeExceptionForInvalidElementIndex();
|
||||
}
|
||||
|
||||
// Allocating a pinned handle for the array with fail and throw an exception
|
||||
// if the array contains non blittable data. This is the expected behavior and
|
||||
// the same happens when trying to pin a Memory<T> instance obtained through
|
||||
// traditional means (eg. via the implicit T[] array conversion), if T is a
|
||||
// reference type or a type containing some references.
|
||||
GCHandle handle = GCHandle.Alloc(this.instance, GCHandleType.Pinned);
|
||||
ref T r0 = ref this.instance.DangerousGetObjectDataReferenceAt<T>(this.offset);
|
||||
ref T r1 = ref Unsafe.Add(ref r0, (nint)(uint)elementIndex);
|
||||
void* p = Unsafe.AsPointer(ref r1);
|
||||
|
||||
return new MemoryHandle(p, handle);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Unpin()
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentOutOfRangeException"/> when the input index for <see cref="Pin"/> is not valid.
|
||||
/// </summary>
|
||||
private static void ThrowArgumentOutOfRangeExceptionForInvalidElementIndex()
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("elementIndex", "The input element index was not in the valid range");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
|
@ -7,6 +7,7 @@ using System.Buffers;
|
|||
using System.Diagnostics;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Microsoft.Toolkit.HighPerformance.Buffers.Views;
|
||||
|
||||
namespace Microsoft.Toolkit.HighPerformance.Buffers
|
||||
{
|
||||
|
@ -20,7 +21,7 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
|
|||
/// instances (or objects that can be converted to a <see cref="Memory{T}"/>), to ensure the data is written directly
|
||||
/// to the intended buffer, with no possibility of doing additional allocations or expanding the available capacity.
|
||||
/// </remarks>
|
||||
[DebuggerTypeProxy(typeof(MemoryBufferWriter<>))]
|
||||
[DebuggerTypeProxy(typeof(MemoryDebugView<>))]
|
||||
[DebuggerDisplay("{ToString(),raw}")]
|
||||
public sealed class MemoryBufferWriter<T> : IBuffer<T>
|
||||
{
|
||||
|
|
|
@ -16,7 +16,7 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
|
|||
/// An <see cref="IMemoryOwner{T}"/> implementation with an embedded length and a fast <see cref="Span{T}"/> accessor.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of items to store in the current instance.</typeparam>
|
||||
[DebuggerTypeProxy(typeof(MemoryOwnerDebugView<>))]
|
||||
[DebuggerTypeProxy(typeof(MemoryDebugView<>))]
|
||||
[DebuggerDisplay("{ToString(),raw}")]
|
||||
public sealed class MemoryOwner<T> : IMemoryOwner<T>
|
||||
{
|
||||
|
|
|
@ -31,7 +31,7 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
|
|||
/// Not doing so will cause the underlying buffer not to be returned to the shared pool.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of items to store in the current instance.</typeparam>
|
||||
[DebuggerTypeProxy(typeof(SpanOwnerDebugView<>))]
|
||||
[DebuggerTypeProxy(typeof(MemoryDebugView<>))]
|
||||
[DebuggerDisplay("{ToString(),raw}")]
|
||||
public readonly ref struct SpanOwner<T>
|
||||
{
|
||||
|
|
|
@ -536,7 +536,7 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
|
|||
(uint)i < (uint)length;
|
||||
i = entry.NextIndex)
|
||||
{
|
||||
entry = ref Unsafe.Add(ref mapEntriesRef, (IntPtr)(void*)(uint)i);
|
||||
entry = ref Unsafe.Add(ref mapEntriesRef, (nint)(uint)i);
|
||||
|
||||
if (entry.HashCode == hashcode &&
|
||||
entry.Value!.AsSpan().SequenceEqual(span))
|
||||
|
@ -556,7 +556,7 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
|
|||
/// <param name="value">The new <see cref="string"/> instance to store.</param>
|
||||
/// <param name="hashcode">The precomputed hashcode for <paramref name="value"/>.</param>
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private unsafe void Insert(string value, int hashcode)
|
||||
private void Insert(string value, int hashcode)
|
||||
{
|
||||
ref int bucketsRef = ref this.buckets.DangerousGetReference();
|
||||
ref MapEntry mapEntriesRef = ref this.mapEntries.DangerousGetReference();
|
||||
|
@ -571,7 +571,7 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
|
|||
entryIndex = heapEntriesRef.MapIndex;
|
||||
heapIndex = 0;
|
||||
|
||||
ref MapEntry removedEntry = ref Unsafe.Add(ref mapEntriesRef, (IntPtr)(void*)(uint)entryIndex);
|
||||
ref MapEntry removedEntry = ref Unsafe.Add(ref mapEntriesRef, (nint)(uint)entryIndex);
|
||||
|
||||
// The removal logic can be extremely optimized in this case, as we
|
||||
// can retrieve the precomputed hashcode for the target entry by doing
|
||||
|
@ -588,9 +588,9 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
|
|||
}
|
||||
|
||||
int bucketIndex = hashcode & (this.buckets.Length - 1);
|
||||
ref int targetBucket = ref Unsafe.Add(ref bucketsRef, (IntPtr)(void*)(uint)bucketIndex);
|
||||
ref MapEntry targetMapEntry = ref Unsafe.Add(ref mapEntriesRef, (IntPtr)(void*)(uint)entryIndex);
|
||||
ref HeapEntry targetHeapEntry = ref Unsafe.Add(ref heapEntriesRef, (IntPtr)(void*)(uint)heapIndex);
|
||||
ref int targetBucket = ref Unsafe.Add(ref bucketsRef, (nint)(uint)bucketIndex);
|
||||
ref MapEntry targetMapEntry = ref Unsafe.Add(ref mapEntriesRef, (nint)(uint)entryIndex);
|
||||
ref HeapEntry targetHeapEntry = ref Unsafe.Add(ref heapEntriesRef, (nint)(uint)heapIndex);
|
||||
|
||||
// Assign the values in the new map entry
|
||||
targetMapEntry.HashCode = hashcode;
|
||||
|
@ -616,7 +616,7 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
|
|||
/// <param name="mapIndex">The index of the target map node to remove.</param>
|
||||
/// <remarks>The input <see cref="string"/> instance needs to already exist in the map.</remarks>
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private unsafe void Remove(int hashcode, int mapIndex)
|
||||
private void Remove(int hashcode, int mapIndex)
|
||||
{
|
||||
ref MapEntry mapEntriesRef = ref this.mapEntries.DangerousGetReference();
|
||||
int
|
||||
|
@ -628,7 +628,7 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
|
|||
// value we're looking for is guaranteed to be present
|
||||
while (true)
|
||||
{
|
||||
ref MapEntry candidate = ref Unsafe.Add(ref mapEntriesRef, (IntPtr)(void*)(uint)entryIndex);
|
||||
ref MapEntry candidate = ref Unsafe.Add(ref mapEntriesRef, (nint)(uint)entryIndex);
|
||||
|
||||
// Check the current value for a match
|
||||
if (entryIndex == mapIndex)
|
||||
|
@ -636,7 +636,7 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
|
|||
// If this was not the first list node, update the parent as well
|
||||
if (lastIndex != EndOfList)
|
||||
{
|
||||
ref MapEntry lastEntry = ref Unsafe.Add(ref mapEntriesRef, (IntPtr)(void*)(uint)lastIndex);
|
||||
ref MapEntry lastEntry = ref Unsafe.Add(ref mapEntriesRef, (nint)(uint)lastIndex);
|
||||
|
||||
lastEntry.NextIndex = candidate.NextIndex;
|
||||
}
|
||||
|
@ -662,14 +662,14 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
|
|||
/// </summary>
|
||||
/// <param name="heapIndex">The index of the target heap node to update.</param>
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private unsafe void UpdateTimestamp(ref int heapIndex)
|
||||
private void UpdateTimestamp(ref int heapIndex)
|
||||
{
|
||||
int
|
||||
currentIndex = heapIndex,
|
||||
count = this.count;
|
||||
ref MapEntry mapEntriesRef = ref this.mapEntries.DangerousGetReference();
|
||||
ref HeapEntry heapEntriesRef = ref this.heapEntries.DangerousGetReference();
|
||||
ref HeapEntry root = ref Unsafe.Add(ref heapEntriesRef, (IntPtr)(void*)(uint)currentIndex);
|
||||
ref HeapEntry root = ref Unsafe.Add(ref heapEntriesRef, (nint)(uint)currentIndex);
|
||||
uint timestamp = this.timestamp;
|
||||
|
||||
// Check if incrementing the current timestamp for the heap node to update
|
||||
|
@ -721,7 +721,7 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
|
|||
// Check and update the left child, if necessary
|
||||
if (left < count)
|
||||
{
|
||||
ref HeapEntry child = ref Unsafe.Add(ref heapEntriesRef, (IntPtr)(void*)(uint)left);
|
||||
ref HeapEntry child = ref Unsafe.Add(ref heapEntriesRef, (nint)(uint)left);
|
||||
|
||||
if (child.Timestamp < minimum.Timestamp)
|
||||
{
|
||||
|
@ -733,7 +733,7 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
|
|||
// Same check as above for the right child
|
||||
if (right < count)
|
||||
{
|
||||
ref HeapEntry child = ref Unsafe.Add(ref heapEntriesRef, (IntPtr)(void*)(uint)right);
|
||||
ref HeapEntry child = ref Unsafe.Add(ref heapEntriesRef, (nint)(uint)right);
|
||||
|
||||
if (child.Timestamp < minimum.Timestamp)
|
||||
{
|
||||
|
@ -752,8 +752,8 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
|
|||
}
|
||||
|
||||
// Update the indices in the respective map entries (accounting for the swap)
|
||||
Unsafe.Add(ref mapEntriesRef, (IntPtr)(void*)(uint)root.MapIndex).HeapIndex = targetIndex;
|
||||
Unsafe.Add(ref mapEntriesRef, (IntPtr)(void*)(uint)minimum.MapIndex).HeapIndex = currentIndex;
|
||||
Unsafe.Add(ref mapEntriesRef, (nint)(uint)root.MapIndex).HeapIndex = targetIndex;
|
||||
Unsafe.Add(ref mapEntriesRef, (nint)(uint)minimum.MapIndex).HeapIndex = currentIndex;
|
||||
|
||||
currentIndex = targetIndex;
|
||||
|
||||
|
@ -764,7 +764,7 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
|
|||
minimum = temp;
|
||||
|
||||
// Update the reference to the root node
|
||||
root = ref Unsafe.Add(ref heapEntriesRef, (IntPtr)(void*)(uint)currentIndex);
|
||||
root = ref Unsafe.Add(ref heapEntriesRef, (nint)(uint)currentIndex);
|
||||
}
|
||||
|
||||
Fallback:
|
||||
|
@ -787,14 +787,14 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
|
|||
/// a given number of nodes, those are all contiguous from the start of the array.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private unsafe void UpdateAllTimestamps()
|
||||
private void UpdateAllTimestamps()
|
||||
{
|
||||
int count = this.count;
|
||||
ref HeapEntry heapEntriesRef = ref this.heapEntries.DangerousGetReference();
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
Unsafe.Add(ref heapEntriesRef, (IntPtr)(void*)(uint)i).Timestamp = (uint)i;
|
||||
Unsafe.Add(ref heapEntriesRef, (nint)(uint)i).Timestamp = (uint)i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,30 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Microsoft.Toolkit.HighPerformance.Buffers.Views
|
||||
{
|
||||
/// <summary>
|
||||
/// A debug proxy used to display items for the <see cref="ArrayPoolBufferWriter{T}"/> type.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of items stored in the input <see cref="ArrayPoolBufferWriter{T}"/> instances.</typeparam>
|
||||
internal sealed class ArrayPoolBufferWriterDebugView<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ArrayPoolBufferWriterDebugView{T}"/> class with the specified parameters.
|
||||
/// </summary>
|
||||
/// <param name="arrayPoolBufferWriter">The input <see cref="ArrayPoolBufferWriter{T}"/> instance with the items to display.</param>
|
||||
public ArrayPoolBufferWriterDebugView(ArrayPoolBufferWriter<T>? arrayPoolBufferWriter)
|
||||
{
|
||||
this.Items = arrayPoolBufferWriter?.WrittenSpan.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the items to display for the current instance
|
||||
/// </summary>
|
||||
[DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
|
||||
public T[]? Items { get; }
|
||||
}
|
||||
}
|
|
@ -1,30 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Microsoft.Toolkit.HighPerformance.Buffers.Views
|
||||
{
|
||||
/// <summary>
|
||||
/// A debug proxy used to display items for the <see cref="MemoryBufferWriter{T}"/> type.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of items stored in the input <see cref="ArrayPoolBufferWriter{T}"/> instances.</typeparam>
|
||||
internal sealed class MemoryBufferWriterDebugView<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MemoryBufferWriterDebugView{T}"/> class with the specified parameters.
|
||||
/// </summary>
|
||||
/// <param name="memoryBufferWriter">The input <see cref="MemoryBufferWriter{T}"/> instance with the items to display.</param>
|
||||
public MemoryBufferWriterDebugView(MemoryBufferWriter<T>? memoryBufferWriter)
|
||||
{
|
||||
this.Items = memoryBufferWriter?.WrittenSpan.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the items to display for the current instance
|
||||
/// </summary>
|
||||
[DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
|
||||
public T[]? Items { get; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Microsoft.Toolkit.HighPerformance.Buffers.Views
|
||||
{
|
||||
/// <summary>
|
||||
/// A debug proxy used to display items in a 1D layout.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of items to display.</typeparam>
|
||||
internal sealed class MemoryDebugView<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MemoryDebugView{T}"/> class with the specified parameters.
|
||||
/// </summary>
|
||||
/// <param name="arrayPoolBufferWriter">The input <see cref="ArrayPoolBufferWriter{T}"/> instance with the items to display.</param>
|
||||
public MemoryDebugView(ArrayPoolBufferWriter<T>? arrayPoolBufferWriter)
|
||||
{
|
||||
this.Items = arrayPoolBufferWriter?.WrittenSpan.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MemoryDebugView{T}"/> class with the specified parameters.
|
||||
/// </summary>
|
||||
/// <param name="memoryBufferWriter">The input <see cref="MemoryBufferWriter{T}"/> instance with the items to display.</param>
|
||||
public MemoryDebugView(MemoryBufferWriter<T>? memoryBufferWriter)
|
||||
{
|
||||
this.Items = memoryBufferWriter?.WrittenSpan.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MemoryDebugView{T}"/> class with the specified parameters.
|
||||
/// </summary>
|
||||
/// <param name="memoryOwner">The input <see cref="MemoryOwner{T}"/> instance with the items to display.</param>
|
||||
public MemoryDebugView(MemoryOwner<T>? memoryOwner)
|
||||
{
|
||||
this.Items = memoryOwner?.Span.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MemoryDebugView{T}"/> class with the specified parameters.
|
||||
/// </summary>
|
||||
/// <param name="spanOwner">The input <see cref="SpanOwner{T}"/> instance with the items to display.</param>
|
||||
public MemoryDebugView(SpanOwner<T> spanOwner)
|
||||
{
|
||||
this.Items = spanOwner.Span.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the items to display for the current instance
|
||||
/// </summary>
|
||||
[DebuggerBrowsable(DebuggerBrowsableState.Collapsed)]
|
||||
public T[]? Items { get; }
|
||||
}
|
||||
}
|
|
@ -1,30 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Microsoft.Toolkit.HighPerformance.Buffers.Views
|
||||
{
|
||||
/// <summary>
|
||||
/// A debug proxy used to display items for the <see cref="MemoryOwner{T}"/> type.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of items stored in the input <see cref="MemoryOwner{T}"/> instances.</typeparam>
|
||||
internal sealed class MemoryOwnerDebugView<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MemoryOwnerDebugView{T}"/> class with the specified parameters.
|
||||
/// </summary>
|
||||
/// <param name="memoryOwner">The input <see cref="MemoryOwner{T}"/> instance with the items to display.</param>
|
||||
public MemoryOwnerDebugView(MemoryOwner<T>? memoryOwner)
|
||||
{
|
||||
this.Items = memoryOwner?.Span.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the items to display for the current instance
|
||||
/// </summary>
|
||||
[DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
|
||||
public T[]? Items { get; }
|
||||
}
|
||||
}
|
|
@ -1,30 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Microsoft.Toolkit.HighPerformance.Buffers.Views
|
||||
{
|
||||
/// <summary>
|
||||
/// A debug proxy used to display items for the <see cref="SpanOwner{T}"/> type.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of items stored in the input <see cref="SpanOwner{T}"/> instances.</typeparam>
|
||||
internal sealed class SpanOwnerDebugView<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SpanOwnerDebugView{T}"/> class with the specified parameters.
|
||||
/// </summary>
|
||||
/// <param name="spanOwner">The input <see cref="SpanOwner{T}"/> instance with the items to display.</param>
|
||||
public SpanOwnerDebugView(SpanOwner<T> spanOwner)
|
||||
{
|
||||
this.Items = spanOwner.Span.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the items to display for the current instance
|
||||
/// </summary>
|
||||
[DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
|
||||
public T[]? Items { get; }
|
||||
}
|
||||
}
|
|
@ -1,213 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Microsoft.Toolkit.HighPerformance.Extensions;
|
||||
|
||||
namespace Microsoft.Toolkit.HighPerformance.Enumerables
|
||||
{
|
||||
/// <summary>
|
||||
/// A <see langword="ref"/> <see langword="struct"/> that iterates a column in a given 2D <typeparamref name="T"/> array instance.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of items to enumerate.</typeparam>
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public readonly ref struct Array2DColumnEnumerable<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// The source 2D <typeparamref name="T"/> array instance.
|
||||
/// </summary>
|
||||
private readonly T[,] array;
|
||||
|
||||
/// <summary>
|
||||
/// The target column to iterate within <see cref="array"/>.
|
||||
/// </summary>
|
||||
private readonly int column;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Array2DColumnEnumerable{T}"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="array">The source 2D <typeparamref name="T"/> array instance.</param>
|
||||
/// <param name="column">The target column to iterate within <paramref name="array"/>.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Array2DColumnEnumerable(T[,] array, int column)
|
||||
{
|
||||
this.array = array;
|
||||
this.column = column;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implements the duck-typed <see cref="IEnumerable{T}.GetEnumerator"/> method.
|
||||
/// </summary>
|
||||
/// <returns>An <see cref="Enumerator"/> instance targeting the current 2D <typeparamref name="T"/> array instance.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Enumerator GetEnumerator() => new Enumerator(this.array, this.column);
|
||||
|
||||
/// <summary>
|
||||
/// Returns a <typeparamref name="T"/> array with the values in the target column.
|
||||
/// </summary>
|
||||
/// <returns>A <typeparamref name="T"/> array with the values in the target column.</returns>
|
||||
/// <remarks>
|
||||
/// This method will allocate a new <typeparamref name="T"/> array, so only
|
||||
/// use it if you really need to copy the target items in a new memory location.
|
||||
/// </remarks>
|
||||
[Pure]
|
||||
public T[] ToArray()
|
||||
{
|
||||
if ((uint)column >= (uint)this.array.GetLength(1))
|
||||
{
|
||||
ThrowArgumentOutOfRangeExceptionForInvalidColumn();
|
||||
}
|
||||
|
||||
int height = this.array.GetLength(0);
|
||||
|
||||
T[] array = new T[height];
|
||||
|
||||
ref T r0 = ref array.DangerousGetReference();
|
||||
int i = 0;
|
||||
|
||||
// Leverage the enumerator to traverse the column
|
||||
foreach (T item in this)
|
||||
{
|
||||
Unsafe.Add(ref r0, i++) = item;
|
||||
}
|
||||
|
||||
return array;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An enumerator for a source 2D array instance.
|
||||
/// </summary>
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public ref struct Enumerator
|
||||
{
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
/// <summary>
|
||||
/// The <see cref="Span{T}"/> instance mapping the target 2D array.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// In runtimes where we have support for the <see cref="Span{T}"/> type, we can
|
||||
/// create one from the input 2D array and use that to traverse the target column.
|
||||
/// This reduces the number of operations to perform for the offsetting to the right
|
||||
/// column element (we simply need to add <see cref="width"/> to the offset at each
|
||||
/// iteration to move down by one row), and allows us to use the fast <see cref="Span{T}"/>
|
||||
/// accessor instead of the slower indexer for 2D arrays, as we can then access each
|
||||
/// individual item linearly, since we know the absolute offset from the base location.
|
||||
/// </remarks>
|
||||
private readonly Span<T> span;
|
||||
|
||||
/// <summary>
|
||||
/// The width of the target 2D array.
|
||||
/// </summary>
|
||||
private readonly int width;
|
||||
|
||||
/// <summary>
|
||||
/// The current absolute offset within <see cref="span"/>.
|
||||
/// </summary>
|
||||
private int offset;
|
||||
#else
|
||||
/// <summary>
|
||||
/// The source 2D array instance.
|
||||
/// </summary>
|
||||
private readonly T[,] array;
|
||||
|
||||
/// <summary>
|
||||
/// The target column to iterate within <see cref="array"/>.
|
||||
/// </summary>
|
||||
private readonly int column;
|
||||
|
||||
/// <summary>
|
||||
/// The height of a column in <see cref="array"/>.
|
||||
/// </summary>
|
||||
private readonly int height;
|
||||
|
||||
/// <summary>
|
||||
/// The current row.
|
||||
/// </summary>
|
||||
private int row;
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Enumerator"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="array">The source 2D array instance.</param>
|
||||
/// <param name="column">The target column to iterate within <paramref name="array"/>.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Enumerator(T[,] array, int column)
|
||||
{
|
||||
if ((uint)column >= (uint)array.GetLength(1))
|
||||
{
|
||||
ThrowArgumentOutOfRangeExceptionForInvalidColumn();
|
||||
}
|
||||
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
this.span = array.AsSpan();
|
||||
this.width = array.GetLength(1);
|
||||
this.offset = column - this.width;
|
||||
#else
|
||||
this.array = array;
|
||||
this.column = column;
|
||||
this.height = array.GetLength(0);
|
||||
this.row = -1;
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implements the duck-typed <see cref="System.Collections.IEnumerator.MoveNext"/> method.
|
||||
/// </summary>
|
||||
/// <returns><see langword="true"/> whether a new element is available, <see langword="false"/> otherwise</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool MoveNext()
|
||||
{
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
int offset = this.offset + this.width;
|
||||
|
||||
if ((uint)offset < (uint)this.span.Length)
|
||||
{
|
||||
this.offset = offset;
|
||||
|
||||
return true;
|
||||
}
|
||||
#else
|
||||
int row = this.row + 1;
|
||||
|
||||
if (row < this.height)
|
||||
{
|
||||
this.row = row;
|
||||
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the duck-typed <see cref="IEnumerator{T}.Current"/> property.
|
||||
/// </summary>
|
||||
public ref T Current
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get
|
||||
{
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
return ref this.span.DangerousGetReferenceAt(this.offset);
|
||||
#else
|
||||
return ref this.array[this.row, this.column];
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentOutOfRangeException"/> when the <see cref="column"/> is invalid.
|
||||
/// </summary>
|
||||
private static void ThrowArgumentOutOfRangeExceptionForInvalidColumn()
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(column), "The target column parameter was not valid");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,170 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
#if !SPAN_RUNTIME_SUPPORT
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Microsoft.Toolkit.HighPerformance.Extensions;
|
||||
|
||||
namespace Microsoft.Toolkit.HighPerformance.Enumerables
|
||||
{
|
||||
/// <summary>
|
||||
/// A <see langword="ref"/> <see langword="struct"/> that iterates a row in a given 2D <typeparamref name="T"/> array instance.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of items to enumerate.</typeparam>
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public readonly ref struct Array2DRowEnumerable<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// The source 2D <typeparamref name="T"/> array instance.
|
||||
/// </summary>
|
||||
private readonly T[,] array;
|
||||
|
||||
/// <summary>
|
||||
/// The target row to iterate within <see cref="array"/>.
|
||||
/// </summary>
|
||||
private readonly int row;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Array2DRowEnumerable{T}"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="array">The source 2D <typeparamref name="T"/> array instance.</param>
|
||||
/// <param name="row">The target row to iterate within <paramref name="array"/>.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Array2DRowEnumerable(T[,] array, int row)
|
||||
{
|
||||
this.array = array;
|
||||
this.row = row;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implements the duck-typed <see cref="IEnumerable{T}.GetEnumerator"/> method.
|
||||
/// </summary>
|
||||
/// <returns>An <see cref="Enumerator"/> instance targeting the current 2D <typeparamref name="T"/> array instance.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Enumerator GetEnumerator() => new Enumerator(this.array, this.row);
|
||||
|
||||
/// <summary>
|
||||
/// Returns a <typeparamref name="T"/> array with the values in the target row.
|
||||
/// </summary>
|
||||
/// <returns>A <typeparamref name="T"/> array with the values in the target row.</returns>
|
||||
/// <remarks>
|
||||
/// This method will allocate a new <typeparamref name="T"/> array, so only
|
||||
/// use it if you really need to copy the target items in a new memory location.
|
||||
/// </remarks>
|
||||
[Pure]
|
||||
public T[] ToArray()
|
||||
{
|
||||
if ((uint)row >= (uint)this.array.GetLength(0))
|
||||
{
|
||||
ThrowArgumentOutOfRangeExceptionForInvalidRow();
|
||||
}
|
||||
|
||||
int width = this.array.GetLength(1);
|
||||
|
||||
T[] array = new T[width];
|
||||
|
||||
for (int i = 0; i < width; i++)
|
||||
{
|
||||
array.DangerousGetReferenceAt(i) = this.array.DangerousGetReferenceAt(this.row, i);
|
||||
}
|
||||
|
||||
return array;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An enumerator for a source 2D array instance.
|
||||
/// </summary>
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public ref struct Enumerator
|
||||
{
|
||||
/// <summary>
|
||||
/// The source 2D array instance.
|
||||
/// </summary>
|
||||
private readonly T[,] array;
|
||||
|
||||
/// <summary>
|
||||
/// The target row to iterate within <see cref="array"/>.
|
||||
/// </summary>
|
||||
private readonly int row;
|
||||
|
||||
/// <summary>
|
||||
/// The width of a row in <see cref="array"/>.
|
||||
/// </summary>
|
||||
private readonly int width;
|
||||
|
||||
/// <summary>
|
||||
/// The current column.
|
||||
/// </summary>
|
||||
private int column;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Enumerator"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="array">The source 2D array instance.</param>
|
||||
/// <param name="row">The target row to iterate within <paramref name="array"/>.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Enumerator(T[,] array, int row)
|
||||
{
|
||||
if ((uint)row >= (uint)array.GetLength(0))
|
||||
{
|
||||
ThrowArgumentOutOfRangeExceptionForInvalidRow();
|
||||
}
|
||||
|
||||
this.array = array;
|
||||
this.row = row;
|
||||
this.width = array.GetLength(1);
|
||||
this.column = -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implements the duck-typed <see cref="System.Collections.IEnumerator.MoveNext"/> method.
|
||||
/// </summary>
|
||||
/// <returns><see langword="true"/> whether a new element is available, <see langword="false"/> otherwise</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool MoveNext()
|
||||
{
|
||||
int column = this.column + 1;
|
||||
|
||||
if (column < this.width)
|
||||
{
|
||||
this.column = column;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the duck-typed <see cref="IEnumerator{T}.Current"/> property.
|
||||
/// </summary>
|
||||
public ref T Current
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get
|
||||
{
|
||||
// This type is never used on .NET Core runtimes, where
|
||||
// the fast indexer is available. Therefore, we can just
|
||||
// use the built-in indexer for 2D arrays to access the value.
|
||||
return ref this.array[this.row, this.column];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentOutOfRangeException"/> when the <see cref="row"/> is invalid.
|
||||
/// </summary>
|
||||
private static void ThrowArgumentOutOfRangeExceptionForInvalidRow()
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(row), "The target row parameter was not valid");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
|
@ -0,0 +1,371 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Runtime.CompilerServices;
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
using System.Runtime.InteropServices;
|
||||
#endif
|
||||
using Microsoft.Toolkit.HighPerformance.Extensions;
|
||||
using Microsoft.Toolkit.HighPerformance.Helpers.Internals;
|
||||
#if !SPAN_RUNTIME_SUPPORT
|
||||
using RuntimeHelpers = Microsoft.Toolkit.HighPerformance.Helpers.Internals.RuntimeHelpers;
|
||||
#endif
|
||||
|
||||
namespace Microsoft.Toolkit.HighPerformance.Enumerables
|
||||
{
|
||||
/// <summary>
|
||||
/// A <see langword="ref"/> <see langword="struct"/> that iterates readonly items from arbitrary memory locations.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of items to enumerate.</typeparam>
|
||||
public readonly ref struct ReadOnlyRefEnumerable<T>
|
||||
{
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
/// <summary>
|
||||
/// The <see cref="ReadOnlySpan{T}"/> instance pointing to the first item in the target memory area.
|
||||
/// </summary>
|
||||
/// <remarks>The <see cref="ReadOnlySpan{T}.Length"/> field maps to the total available length.</remarks>
|
||||
private readonly ReadOnlySpan<T> span;
|
||||
#else
|
||||
/// <summary>
|
||||
/// The target <see cref="object"/> instance, if present.
|
||||
/// </summary>
|
||||
private readonly object? instance;
|
||||
|
||||
/// <summary>
|
||||
/// The initial offset within <see cref="instance"/>.
|
||||
/// </summary>
|
||||
private readonly IntPtr offset;
|
||||
|
||||
/// <summary>
|
||||
/// The total available length for the sequence.
|
||||
/// </summary>
|
||||
private readonly int length;
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// The distance between items in the sequence to enumerate.
|
||||
/// </summary>
|
||||
/// <remarks>The distance refers to <typeparamref name="T"/> items, not byte offset.</remarks>
|
||||
private readonly int step;
|
||||
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ReadOnlyRefEnumerable{T}"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="span">The <see cref="ReadOnlySpan{T}"/> instance pointing to the first item in the target memory area.</param>
|
||||
/// <param name="step">The distance between items in the sequence to enumerate.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private ReadOnlyRefEnumerable(ReadOnlySpan<T> span, int step)
|
||||
{
|
||||
this.span = span;
|
||||
this.step = step;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ReadOnlyRefEnumerable{T}"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="reference">A reference to the first item of the sequence.</param>
|
||||
/// <param name="length">The number of items in the sequence.</param>
|
||||
/// <param name="step">The distance between items in the sequence to enumerate.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal ReadOnlyRefEnumerable(in T reference, int length, int step)
|
||||
{
|
||||
this.span = MemoryMarshal.CreateReadOnlySpan(ref Unsafe.AsRef(reference), length);
|
||||
this.step = step;
|
||||
}
|
||||
#else
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ReadOnlyRefEnumerable{T}"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="instance">The target <see cref="object"/> instance.</param>
|
||||
/// <param name="offset">The initial offset within <see paramref="instance"/>.</param>
|
||||
/// <param name="length">The number of items in the sequence.</param>
|
||||
/// <param name="step">The distance between items in the sequence to enumerate.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal ReadOnlyRefEnumerable(object? instance, IntPtr offset, int length, int step)
|
||||
{
|
||||
this.instance = instance;
|
||||
this.offset = offset;
|
||||
this.length = length;
|
||||
this.step = step;
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <inheritdoc cref="System.Collections.IEnumerable.GetEnumerator"/>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Enumerator GetEnumerator()
|
||||
{
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
return new Enumerator(this.span, this.step);
|
||||
#else
|
||||
return new Enumerator(this.instance, this.offset, this.length, this.step);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies the contents of this <see cref="ReadOnlyRefEnumerable{T}"/> into a destination <see cref="RefEnumerable{T}"/> instance.
|
||||
/// </summary>
|
||||
/// <param name="destination">The destination <see cref="RefEnumerable{T}"/> instance.</param>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// Thrown when <paramref name="destination"/> is shorter than the source <see cref="ReadOnlyRefEnumerable{T}"/> instance.
|
||||
/// </exception>
|
||||
public void CopyTo(RefEnumerable<T> destination)
|
||||
{
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
if (this.step == 1)
|
||||
{
|
||||
destination.CopyFrom(this.span);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (destination.Step == 1)
|
||||
{
|
||||
CopyTo(destination.Span);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
ref T sourceRef = ref this.span.DangerousGetReference();
|
||||
ref T destinationRef = ref destination.Span.DangerousGetReference();
|
||||
int
|
||||
sourceLength = this.span.Length,
|
||||
destinationLength = destination.Span.Length;
|
||||
#else
|
||||
ref T sourceRef = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference<T>(this.instance, this.offset);
|
||||
ref T destinationRef = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference<T>(destination.Instance, destination.Offset);
|
||||
int
|
||||
sourceLength = this.length,
|
||||
destinationLength = destination.Length;
|
||||
#endif
|
||||
|
||||
if ((uint)destinationLength < (uint)sourceLength)
|
||||
{
|
||||
ThrowArgumentExceptionForDestinationTooShort();
|
||||
}
|
||||
|
||||
RefEnumerableHelper.CopyTo(ref sourceRef, ref destinationRef, (nint)(uint)sourceLength, (nint)(uint)this.step, (nint)(uint)destination.Step);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to copy the current <see cref="ReadOnlyRefEnumerable{T}"/> instance to a destination <see cref="RefEnumerable{T}"/>.
|
||||
/// </summary>
|
||||
/// <param name="destination">The target <see cref="RefEnumerable{T}"/> of the copy operation.</param>
|
||||
/// <returns>Whether or not the operation was successful.</returns>
|
||||
public bool TryCopyTo(RefEnumerable<T> destination)
|
||||
{
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
int
|
||||
sourceLength = this.span.Length,
|
||||
destinationLength = destination.Span.Length;
|
||||
#else
|
||||
int
|
||||
sourceLength = this.length,
|
||||
destinationLength = destination.Length;
|
||||
#endif
|
||||
|
||||
if (destinationLength >= sourceLength)
|
||||
{
|
||||
CopyTo(destination);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies the contents of this <see cref="RefEnumerable{T}"/> into a destination <see cref="Span{T}"/> instance.
|
||||
/// </summary>
|
||||
/// <param name="destination">The destination <see cref="Span{T}"/> instance.</param>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// Thrown when <paramref name="destination"/> is shorter than the source <see cref="RefEnumerable{T}"/> instance.
|
||||
/// </exception>
|
||||
public void CopyTo(Span<T> destination)
|
||||
{
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
if (this.step == 1)
|
||||
{
|
||||
this.span.CopyTo(destination);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
ref T sourceRef = ref this.span.DangerousGetReference();
|
||||
int length = this.span.Length;
|
||||
#else
|
||||
ref T sourceRef = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference<T>(this.instance, this.offset);
|
||||
int length = this.length;
|
||||
#endif
|
||||
if ((uint)destination.Length < (uint)length)
|
||||
{
|
||||
ThrowArgumentExceptionForDestinationTooShort();
|
||||
}
|
||||
|
||||
ref T destinationRef = ref destination.DangerousGetReference();
|
||||
|
||||
RefEnumerableHelper.CopyTo(ref sourceRef, ref destinationRef, (nint)(uint)length, (nint)(uint)this.step);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to copy the current <see cref="RefEnumerable{T}"/> instance to a destination <see cref="Span{T}"/>.
|
||||
/// </summary>
|
||||
/// <param name="destination">The target <see cref="Span{T}"/> of the copy operation.</param>
|
||||
/// <returns>Whether or not the operation was successful.</returns>
|
||||
public bool TryCopyTo(Span<T> destination)
|
||||
{
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
int length = this.span.Length;
|
||||
#else
|
||||
int length = this.length;
|
||||
#endif
|
||||
|
||||
if (destination.Length >= length)
|
||||
{
|
||||
CopyTo(destination);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="RefEnumerable{T}.ToArray"/>
|
||||
[Pure]
|
||||
public T[] ToArray()
|
||||
{
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
int length = this.span.Length;
|
||||
#else
|
||||
int length = this.length;
|
||||
#endif
|
||||
|
||||
// Empty array if no data is mapped
|
||||
if (length == 0)
|
||||
{
|
||||
return Array.Empty<T>();
|
||||
}
|
||||
|
||||
T[] array = new T[length];
|
||||
|
||||
CopyTo(array);
|
||||
|
||||
return array;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implicitly converts a <see cref="RefEnumerable{T}"/> instance into a <see cref="ReadOnlyRefEnumerable{T}"/> one.
|
||||
/// </summary>
|
||||
/// <param name="enumerable">The input <see cref="RefEnumerable{T}"/> instance.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static implicit operator ReadOnlyRefEnumerable<T>(RefEnumerable<T> enumerable)
|
||||
{
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
return new ReadOnlyRefEnumerable<T>(enumerable.Span, enumerable.Step);
|
||||
#else
|
||||
return new ReadOnlyRefEnumerable<T>(enumerable.Instance, enumerable.Offset, enumerable.Length, enumerable.Step);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A custom enumerator type to traverse items within a <see cref="ReadOnlyRefEnumerable{T}"/> instance.
|
||||
/// </summary>
|
||||
public ref struct Enumerator
|
||||
{
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
/// <inheritdoc cref="ReadOnlyRefEnumerable{T}.span"/>
|
||||
private readonly ReadOnlySpan<T> span;
|
||||
#else
|
||||
/// <inheritdoc cref="ReadOnlyRefEnumerable{T}.instance"/>
|
||||
private readonly object? instance;
|
||||
|
||||
/// <inheritdoc cref="ReadOnlyRefEnumerable{T}.offset"/>
|
||||
private readonly IntPtr offset;
|
||||
|
||||
/// <inheritdoc cref="ReadOnlyRefEnumerable{T}.length"/>
|
||||
private readonly int length;
|
||||
#endif
|
||||
|
||||
/// <inheritdoc cref="ReadOnlyRefEnumerable{T}.step"/>
|
||||
private readonly int step;
|
||||
|
||||
/// <summary>
|
||||
/// The current position in the sequence.
|
||||
/// </summary>
|
||||
private int position;
|
||||
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Enumerator"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="span">The <see cref="ReadOnlySpan{T}"/> instance with the info on the items to traverse.</param>
|
||||
/// <param name="step">The distance between items in the sequence to enumerate.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal Enumerator(ReadOnlySpan<T> span, int step)
|
||||
{
|
||||
this.span = span;
|
||||
this.step = step;
|
||||
this.position = -1;
|
||||
}
|
||||
#else
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Enumerator"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="instance">The target <see cref="object"/> instance.</param>
|
||||
/// <param name="offset">The initial offset within <see paramref="instance"/>.</param>
|
||||
/// <param name="length">The number of items in the sequence.</param>
|
||||
/// <param name="step">The distance between items in the sequence to enumerate.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal Enumerator(object? instance, IntPtr offset, int length, int step)
|
||||
{
|
||||
this.instance = instance;
|
||||
this.offset = offset;
|
||||
this.length = length;
|
||||
this.step = step;
|
||||
this.position = -1;
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <inheritdoc cref="System.Collections.IEnumerator.MoveNext"/>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool MoveNext()
|
||||
{
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
return ++this.position < this.span.Length;
|
||||
#else
|
||||
return ++this.position < this.length;
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="System.Collections.Generic.IEnumerator{T}.Current"/>
|
||||
public readonly ref readonly T Current
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get
|
||||
{
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
ref T r0 = ref this.span.DangerousGetReference();
|
||||
#else
|
||||
ref T r0 = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference<T>(this.instance, this.offset);
|
||||
#endif
|
||||
nint offset = (nint)(uint)this.position * (nint)(uint)this.step;
|
||||
ref T ri = ref Unsafe.Add(ref r0, offset);
|
||||
|
||||
return ref ri;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when the target span is too short.
|
||||
/// </summary>
|
||||
private static void ThrowArgumentExceptionForDestinationTooShort()
|
||||
{
|
||||
throw new ArgumentException("The target span is too short to copy all the current items to");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -54,16 +54,7 @@ namespace Microsoft.Toolkit.HighPerformance.Enumerables
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool MoveNext()
|
||||
{
|
||||
int newIndex = this.index + 1;
|
||||
|
||||
if (newIndex < this.span.Length)
|
||||
{
|
||||
this.index = newIndex;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
return ++this.index < this.span.Length;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -76,7 +67,7 @@ namespace Microsoft.Toolkit.HighPerformance.Enumerables
|
|||
{
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
ref T r0 = ref MemoryMarshal.GetReference(this.span);
|
||||
ref T ri = ref Unsafe.Add(ref r0, this.index);
|
||||
ref T ri = ref Unsafe.Add(ref r0, (nint)(uint)this.index);
|
||||
|
||||
// See comment in SpanEnumerable<T> about this
|
||||
return new Item(ref ri, this.index);
|
||||
|
@ -139,7 +130,7 @@ namespace Microsoft.Toolkit.HighPerformance.Enumerables
|
|||
return ref MemoryMarshal.GetReference(this.span);
|
||||
#else
|
||||
ref T r0 = ref MemoryMarshal.GetReference(this.span);
|
||||
ref T ri = ref Unsafe.Add(ref r0, this.index);
|
||||
ref T ri = ref Unsafe.Add(ref r0, (nint)(uint)this.index);
|
||||
|
||||
return ref ri;
|
||||
#endif
|
||||
|
|
|
@ -43,6 +43,7 @@ namespace Microsoft.Toolkit.HighPerformance.Enumerables
|
|||
/// </summary>
|
||||
/// <param name="span">The source <see cref="ReadOnlySpan{T}"/> instance.</param>
|
||||
/// <param name="separator">The separator item to use.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public ReadOnlySpanTokenizer(ReadOnlySpan<T> span, T separator)
|
||||
{
|
||||
this.span = span;
|
||||
|
|
|
@ -0,0 +1,464 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Runtime.CompilerServices;
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
using System.Runtime.InteropServices;
|
||||
#endif
|
||||
using Microsoft.Toolkit.HighPerformance.Extensions;
|
||||
using Microsoft.Toolkit.HighPerformance.Helpers.Internals;
|
||||
#if !SPAN_RUNTIME_SUPPORT
|
||||
using RuntimeHelpers = Microsoft.Toolkit.HighPerformance.Helpers.Internals.RuntimeHelpers;
|
||||
#endif
|
||||
|
||||
namespace Microsoft.Toolkit.HighPerformance.Enumerables
|
||||
{
|
||||
/// <summary>
|
||||
/// A <see langword="ref"/> <see langword="struct"/> that iterates items from arbitrary memory locations.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of items to enumerate.</typeparam>
|
||||
public readonly ref struct RefEnumerable<T>
|
||||
{
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
/// <summary>
|
||||
/// The <see cref="Span{T}"/> instance pointing to the first item in the target memory area.
|
||||
/// </summary>
|
||||
/// <remarks>The <see cref="Span{T}.Length"/> field maps to the total available length.</remarks>
|
||||
internal readonly Span<T> Span;
|
||||
#else
|
||||
/// <summary>
|
||||
/// The target <see cref="object"/> instance, if present.
|
||||
/// </summary>
|
||||
internal readonly object? Instance;
|
||||
|
||||
/// <summary>
|
||||
/// The initial offset within <see cref="Instance"/>.
|
||||
/// </summary>
|
||||
internal readonly IntPtr Offset;
|
||||
|
||||
/// <summary>
|
||||
/// The total available length for the sequence.
|
||||
/// </summary>
|
||||
internal readonly int Length;
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// The distance between items in the sequence to enumerate.
|
||||
/// </summary>
|
||||
/// <remarks>The distance refers to <typeparamref name="T"/> items, not byte offset.</remarks>
|
||||
internal readonly int Step;
|
||||
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RefEnumerable{T}"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="reference">A reference to the first item of the sequence.</param>
|
||||
/// <param name="length">The number of items in the sequence.</param>
|
||||
/// <param name="step">The distance between items in the sequence to enumerate.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal RefEnumerable(ref T reference, int length, int step)
|
||||
{
|
||||
Span = MemoryMarshal.CreateSpan(ref reference, length);
|
||||
Step = step;
|
||||
}
|
||||
#else
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RefEnumerable{T}"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="instance">The target <see cref="object"/> instance.</param>
|
||||
/// <param name="offset">The initial offset within <see paramref="instance"/>.</param>
|
||||
/// <param name="length">The number of items in the sequence.</param>
|
||||
/// <param name="step">The distance between items in the sequence to enumerate.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal RefEnumerable(object? instance, IntPtr offset, int length, int step)
|
||||
{
|
||||
Instance = instance;
|
||||
Offset = offset;
|
||||
Length = length;
|
||||
Step = step;
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <inheritdoc cref="System.Collections.IEnumerable.GetEnumerator"/>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Enumerator GetEnumerator()
|
||||
{
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
return new Enumerator(this.Span, this.Step);
|
||||
#else
|
||||
return new Enumerator(this.Instance, this.Offset, this.Length, this.Step);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears the contents of the current <see cref="RefEnumerable{T}"/> instance.
|
||||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
// Fast path for contiguous items
|
||||
if (this.Step == 1)
|
||||
{
|
||||
this.Span.Clear();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
ref T r0 = ref this.Span.DangerousGetReference();
|
||||
int length = this.Span.Length;
|
||||
#else
|
||||
ref T r0 = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference<T>(this.Instance, this.Offset);
|
||||
int length = this.Length;
|
||||
#endif
|
||||
|
||||
RefEnumerableHelper.Clear(ref r0, (nint)(uint)length, (nint)(uint)this.Step);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies the contents of this <see cref="RefEnumerable{T}"/> into a destination <see cref="RefEnumerable{T}"/> instance.
|
||||
/// </summary>
|
||||
/// <param name="destination">The destination <see cref="RefEnumerable{T}"/> instance.</param>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// Thrown when <paramref name="destination"/> is shorter than the source <see cref="RefEnumerable{T}"/> instance.
|
||||
/// </exception>
|
||||
public void CopyTo(RefEnumerable<T> destination)
|
||||
{
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
if (this.Step == 1)
|
||||
{
|
||||
destination.CopyFrom(this.Span);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (destination.Step == 1)
|
||||
{
|
||||
CopyTo(destination.Span);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
ref T sourceRef = ref this.Span.DangerousGetReference();
|
||||
ref T destinationRef = ref destination.Span.DangerousGetReference();
|
||||
int
|
||||
sourceLength = this.Span.Length,
|
||||
destinationLength = destination.Span.Length;
|
||||
#else
|
||||
ref T sourceRef = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference<T>(this.Instance, this.Offset);
|
||||
ref T destinationRef = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference<T>(destination.Instance, destination.Offset);
|
||||
int
|
||||
sourceLength = this.Length,
|
||||
destinationLength = destination.Length;
|
||||
#endif
|
||||
|
||||
if ((uint)destinationLength < (uint)sourceLength)
|
||||
{
|
||||
ThrowArgumentExceptionForDestinationTooShort();
|
||||
}
|
||||
|
||||
RefEnumerableHelper.CopyTo(ref sourceRef, ref destinationRef, (nint)(uint)sourceLength, (nint)(uint)this.Step, (nint)(uint)destination.Step);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to copy the current <see cref="RefEnumerable{T}"/> instance to a destination <see cref="RefEnumerable{T}"/>.
|
||||
/// </summary>
|
||||
/// <param name="destination">The target <see cref="RefEnumerable{T}"/> of the copy operation.</param>
|
||||
/// <returns>Whether or not the operation was successful.</returns>
|
||||
public bool TryCopyTo(RefEnumerable<T> destination)
|
||||
{
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
int
|
||||
sourceLength = this.Span.Length,
|
||||
destinationLength = destination.Span.Length;
|
||||
#else
|
||||
int
|
||||
sourceLength = this.Length,
|
||||
destinationLength = destination.Length;
|
||||
#endif
|
||||
|
||||
if (destinationLength >= sourceLength)
|
||||
{
|
||||
CopyTo(destination);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies the contents of this <see cref="RefEnumerable{T}"/> into a destination <see cref="Span{T}"/> instance.
|
||||
/// </summary>
|
||||
/// <param name="destination">The destination <see cref="Span{T}"/> instance.</param>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// Thrown when <paramref name="destination"/> is shorter than the source <see cref="RefEnumerable{T}"/> instance.
|
||||
/// </exception>
|
||||
public void CopyTo(Span<T> destination)
|
||||
{
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
if (this.Step == 1)
|
||||
{
|
||||
this.Span.CopyTo(destination);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
ref T sourceRef = ref this.Span.DangerousGetReference();
|
||||
int length = this.Span.Length;
|
||||
#else
|
||||
ref T sourceRef = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference<T>(this.Instance, this.Offset);
|
||||
int length = this.Length;
|
||||
#endif
|
||||
if ((uint)destination.Length < (uint)length)
|
||||
{
|
||||
ThrowArgumentExceptionForDestinationTooShort();
|
||||
}
|
||||
|
||||
ref T destinationRef = ref destination.DangerousGetReference();
|
||||
|
||||
RefEnumerableHelper.CopyTo(ref sourceRef, ref destinationRef, (nint)(uint)length, (nint)(uint)this.Step);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to copy the current <see cref="RefEnumerable{T}"/> instance to a destination <see cref="Span{T}"/>.
|
||||
/// </summary>
|
||||
/// <param name="destination">The target <see cref="Span{T}"/> of the copy operation.</param>
|
||||
/// <returns>Whether or not the operation was successful.</returns>
|
||||
public bool TryCopyTo(Span<T> destination)
|
||||
{
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
int length = this.Span.Length;
|
||||
#else
|
||||
int length = this.Length;
|
||||
#endif
|
||||
|
||||
if (destination.Length >= length)
|
||||
{
|
||||
CopyTo(destination);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies the contents of a source <see cref="ReadOnlySpan{T}"/> into the current <see cref="RefEnumerable{T}"/> instance.
|
||||
/// </summary>
|
||||
/// <param name="source">The source <see cref="ReadOnlySpan{T}"/> instance.</param>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// Thrown when the current <see cref="RefEnumerable{T}"/> is shorter than the source <see cref="ReadOnlySpan{T}"/> instance.
|
||||
/// </exception>
|
||||
internal void CopyFrom(ReadOnlySpan<T> source)
|
||||
{
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
if (this.Step == 1)
|
||||
{
|
||||
source.CopyTo(this.Span);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
ref T destinationRef = ref this.Span.DangerousGetReference();
|
||||
int destinationLength = this.Span.Length;
|
||||
#else
|
||||
ref T destinationRef = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference<T>(this.Instance, this.Offset);
|
||||
int destinationLength = this.Length;
|
||||
#endif
|
||||
ref T sourceRef = ref source.DangerousGetReference();
|
||||
int sourceLength = source.Length;
|
||||
|
||||
if ((uint)destinationLength < (uint)sourceLength)
|
||||
{
|
||||
ThrowArgumentExceptionForDestinationTooShort();
|
||||
}
|
||||
|
||||
RefEnumerableHelper.CopyFrom(ref sourceRef, ref destinationRef, (nint)(uint)sourceLength, (nint)(uint)this.Step);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to copy the source <see cref="ReadOnlySpan{T}"/> into the current <see cref="RefEnumerable{T}"/> instance.
|
||||
/// </summary>
|
||||
/// <param name="source">The source <see cref="ReadOnlySpan{T}"/> instance.</param>
|
||||
/// <returns>Whether or not the operation was successful.</returns>
|
||||
public bool TryCopyFrom(ReadOnlySpan<T> source)
|
||||
{
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
int length = this.Span.Length;
|
||||
#else
|
||||
int length = this.Length;
|
||||
#endif
|
||||
|
||||
if (length >= source.Length)
|
||||
{
|
||||
CopyFrom(source);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fills the elements of this <see cref="RefEnumerable{T}"/> with a specified value.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to assign to each element of the <see cref="RefEnumerable{T}"/> instance.</param>
|
||||
public void Fill(T value)
|
||||
{
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
if (this.Step == 1)
|
||||
{
|
||||
this.Span.Fill(value);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
ref T r0 = ref this.Span.DangerousGetReference();
|
||||
int length = this.Span.Length;
|
||||
#else
|
||||
ref T r0 = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference<T>(this.Instance, this.Offset);
|
||||
int length = this.Length;
|
||||
#endif
|
||||
|
||||
RefEnumerableHelper.Fill(ref r0, (nint)(uint)length, (nint)(uint)this.Step, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a <typeparamref name="T"/> array with the values in the target row.
|
||||
/// </summary>
|
||||
/// <returns>A <typeparamref name="T"/> array with the values in the target row.</returns>
|
||||
/// <remarks>
|
||||
/// This method will allocate a new <typeparamref name="T"/> array, so only
|
||||
/// use it if you really need to copy the target items in a new memory location.
|
||||
/// </remarks>
|
||||
[Pure]
|
||||
public T[] ToArray()
|
||||
{
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
int length = this.Span.Length;
|
||||
#else
|
||||
int length = this.Length;
|
||||
#endif
|
||||
|
||||
// Empty array if no data is mapped
|
||||
if (length == 0)
|
||||
{
|
||||
return Array.Empty<T>();
|
||||
}
|
||||
|
||||
T[] array = new T[length];
|
||||
|
||||
CopyTo(array);
|
||||
|
||||
return array;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A custom enumerator type to traverse items within a <see cref="RefEnumerable{T}"/> instance.
|
||||
/// </summary>
|
||||
public ref struct Enumerator
|
||||
{
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
/// <inheritdoc cref="RefEnumerable{T}.Span"/>
|
||||
private readonly Span<T> span;
|
||||
#else
|
||||
/// <inheritdoc cref="RefEnumerable{T}.Instance"/>
|
||||
private readonly object? instance;
|
||||
|
||||
/// <inheritdoc cref="RefEnumerable{T}.Offset"/>
|
||||
private readonly IntPtr offset;
|
||||
|
||||
/// <inheritdoc cref="RefEnumerable{T}.Length"/>
|
||||
private readonly int length;
|
||||
#endif
|
||||
|
||||
/// <inheritdoc cref="RefEnumerable{T}.Step"/>
|
||||
private readonly int step;
|
||||
|
||||
/// <summary>
|
||||
/// The current position in the sequence.
|
||||
/// </summary>
|
||||
private int position;
|
||||
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Enumerator"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="span">The <see cref="Span{T}"/> instance with the info on the items to traverse.</param>
|
||||
/// <param name="step">The distance between items in the sequence to enumerate.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal Enumerator(Span<T> span, int step)
|
||||
{
|
||||
this.span = span;
|
||||
this.step = step;
|
||||
this.position = -1;
|
||||
}
|
||||
#else
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Enumerator"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="instance">The target <see cref="object"/> instance.</param>
|
||||
/// <param name="offset">The initial offset within <see paramref="instance"/>.</param>
|
||||
/// <param name="length">The number of items in the sequence.</param>
|
||||
/// <param name="step">The distance between items in the sequence to enumerate.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal Enumerator(object? instance, IntPtr offset, int length, int step)
|
||||
{
|
||||
this.instance = instance;
|
||||
this.offset = offset;
|
||||
this.length = length;
|
||||
this.step = step;
|
||||
this.position = -1;
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <inheritdoc cref="System.Collections.IEnumerator.MoveNext"/>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool MoveNext()
|
||||
{
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
return ++this.position < this.span.Length;
|
||||
#else
|
||||
return ++this.position < this.length;
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="System.Collections.Generic.IEnumerator{T}.Current"/>
|
||||
public readonly ref T Current
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get
|
||||
{
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
ref T r0 = ref this.span.DangerousGetReference();
|
||||
#else
|
||||
ref T r0 = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference<T>(this.instance, this.offset);
|
||||
#endif
|
||||
|
||||
// Here we just offset by shifting down as if we were traversing a 2D array with a
|
||||
// a single column, with the width of each row represented by the step, the height
|
||||
// represented by the current position, and with only the first element of each row
|
||||
// being inspected. We can perform all the indexing operations in this type as nint,
|
||||
// as the maximum offset is guaranteed never to exceed the maximum value, since on
|
||||
// 32 bit architectures it's not possible to allocate that much memory anyway.
|
||||
nint offset = (nint)(uint)this.position * (nint)(uint)this.step;
|
||||
ref T ri = ref Unsafe.Add(ref r0, offset);
|
||||
|
||||
return ref ri;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when the target span is too short.
|
||||
/// </summary>
|
||||
private static void ThrowArgumentExceptionForDestinationTooShort()
|
||||
{
|
||||
throw new ArgumentException("The target span is too short to copy all the current items to");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -54,16 +54,7 @@ namespace Microsoft.Toolkit.HighPerformance.Enumerables
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool MoveNext()
|
||||
{
|
||||
int newIndex = this.index + 1;
|
||||
|
||||
if (newIndex < this.span.Length)
|
||||
{
|
||||
this.index = newIndex;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
return ++this.index < this.span.Length;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -76,7 +67,7 @@ namespace Microsoft.Toolkit.HighPerformance.Enumerables
|
|||
{
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
ref T r0 = ref MemoryMarshal.GetReference(this.span);
|
||||
ref T ri = ref Unsafe.Add(ref r0, this.index);
|
||||
ref T ri = ref Unsafe.Add(ref r0, (nint)(uint)this.index);
|
||||
|
||||
// On .NET Standard 2.1 and .NET Core (or on any target that offers runtime
|
||||
// support for the Span<T> types), we can save 4 bytes by piggybacking the
|
||||
|
@ -144,7 +135,7 @@ namespace Microsoft.Toolkit.HighPerformance.Enumerables
|
|||
return ref MemoryMarshal.GetReference(this.span);
|
||||
#else
|
||||
ref T r0 = ref MemoryMarshal.GetReference(this.span);
|
||||
ref T ri = ref Unsafe.Add(ref r0, this.index);
|
||||
ref T ri = ref Unsafe.Add(ref r0, (nint)(uint)this.index);
|
||||
|
||||
return ref ri;
|
||||
#endif
|
||||
|
|
|
@ -43,6 +43,7 @@ namespace Microsoft.Toolkit.HighPerformance.Enumerables
|
|||
/// </summary>
|
||||
/// <param name="span">The source <see cref="Span{T}"/> instance.</param>
|
||||
/// <param name="separator">The separator item to use.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public SpanTokenizer(Span<T> span, T separator)
|
||||
{
|
||||
this.span = span;
|
||||
|
|
|
@ -10,6 +10,7 @@ using System.Runtime.InteropServices;
|
|||
#endif
|
||||
using Microsoft.Toolkit.HighPerformance.Enumerables;
|
||||
using Microsoft.Toolkit.HighPerformance.Helpers.Internals;
|
||||
using RuntimeHelpers = Microsoft.Toolkit.HighPerformance.Helpers.Internals.RuntimeHelpers;
|
||||
|
||||
namespace Microsoft.Toolkit.HighPerformance.Extensions
|
||||
{
|
||||
|
@ -35,20 +36,9 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
|
|||
|
||||
return ref r0;
|
||||
#else
|
||||
#pragma warning disable SA1131 // Inverted comparison to remove JIT bounds check
|
||||
// Checking the length of the array like so allows the JIT
|
||||
// to skip its own bounds check, which results in the element
|
||||
// access below to be executed without branches.
|
||||
if (0u < (uint)array.Length)
|
||||
{
|
||||
return ref array[0];
|
||||
}
|
||||
IntPtr offset = RuntimeHelpers.GetArrayDataByteOffset<T>();
|
||||
|
||||
unsafe
|
||||
{
|
||||
return ref Unsafe.AsRef<T>(null);
|
||||
}
|
||||
#pragma warning restore SA1131
|
||||
return ref array.DangerousGetObjectDataReferenceAt<T>(offset);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
@ -62,21 +52,20 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
|
|||
/// <remarks>This method doesn't do any bounds checks, therefore it is responsibility of the caller to ensure the <paramref name="i"/> parameter is valid.</remarks>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static unsafe ref T DangerousGetReferenceAt<T>(this T[] array, int i)
|
||||
public static ref T DangerousGetReferenceAt<T>(this T[] array, int i)
|
||||
{
|
||||
#if NETCORE_RUNTIME
|
||||
var arrayData = Unsafe.As<RawArrayData>(array);
|
||||
ref T r0 = ref Unsafe.As<byte, T>(ref arrayData.Data);
|
||||
ref T ri = ref Unsafe.Add(ref r0, (IntPtr)(void*)(uint)i);
|
||||
ref T ri = ref Unsafe.Add(ref r0, (nint)(uint)i);
|
||||
|
||||
return ref ri;
|
||||
#else
|
||||
if ((uint)i < (uint)array.Length)
|
||||
{
|
||||
return ref array[i];
|
||||
}
|
||||
IntPtr offset = RuntimeHelpers.GetArrayDataByteOffset<T>();
|
||||
ref T r0 = ref array.DangerousGetObjectDataReferenceAt<T>(offset);
|
||||
ref T ri = ref Unsafe.Add(ref r0, (nint)(uint)i);
|
||||
|
||||
return ref Unsafe.AsRef<T>(null);
|
||||
return ref ri;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
@ -111,13 +100,20 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
|
|||
/// <returns>The number of occurrences of <paramref name="value"/> in <paramref name="array"/>.</returns>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static unsafe int Count<T>(this T[] array, T value)
|
||||
public static int Count<T>(this T[] array, T value)
|
||||
where T : IEquatable<T>
|
||||
{
|
||||
ref T r0 = ref array.DangerousGetReference();
|
||||
IntPtr length = (IntPtr)(void*)(uint)array.Length;
|
||||
nint
|
||||
length = RuntimeHelpers.GetArrayNativeLength(array),
|
||||
count = SpanHelper.Count(ref r0, length, value);
|
||||
|
||||
return SpanHelper.Count(ref r0, length, value);
|
||||
if ((nuint)count > int.MaxValue)
|
||||
{
|
||||
ThrowOverflowException();
|
||||
}
|
||||
|
||||
return (int)count;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -182,13 +178,34 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
|
|||
/// <remarks>The Djb2 hash is fully deterministic and with no random components.</remarks>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static unsafe int GetDjb2HashCode<T>(this T[] array)
|
||||
public static int GetDjb2HashCode<T>(this T[] array)
|
||||
where T : notnull
|
||||
{
|
||||
ref T r0 = ref array.DangerousGetReference();
|
||||
IntPtr length = (IntPtr)(void*)(uint)array.Length;
|
||||
nint length = RuntimeHelpers.GetArrayNativeLength(array);
|
||||
|
||||
return SpanHelper.GetDjb2HashCode(ref r0, length);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether or not a given <typeparamref name="T"/> array is covariant.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of items in the input <typeparamref name="T"/> array instance.</typeparam>
|
||||
/// <param name="array">The input <typeparamref name="T"/> array instance.</param>
|
||||
/// <returns>Whether or not <paramref name="array"/> is covariant.</returns>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool IsCovariant<T>(this T[] array)
|
||||
{
|
||||
return default(T) is null && array.GetType() != typeof(T[]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="OverflowException"/> when the "column" parameter is invalid.
|
||||
/// </summary>
|
||||
public static void ThrowOverflowException()
|
||||
{
|
||||
throw new OverflowException();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -4,13 +4,15 @@
|
|||
|
||||
using System;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Drawing;
|
||||
using System.Runtime.CompilerServices;
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
using System.Runtime.InteropServices;
|
||||
using Microsoft.Toolkit.HighPerformance.Buffers.Internals;
|
||||
#endif
|
||||
using Microsoft.Toolkit.HighPerformance.Enumerables;
|
||||
using Microsoft.Toolkit.HighPerformance.Helpers.Internals;
|
||||
using Microsoft.Toolkit.HighPerformance.Memory;
|
||||
using RuntimeHelpers = Microsoft.Toolkit.HighPerformance.Helpers.Internals.RuntimeHelpers;
|
||||
|
||||
namespace Microsoft.Toolkit.HighPerformance.Extensions
|
||||
{
|
||||
|
@ -36,17 +38,9 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
|
|||
|
||||
return ref r0;
|
||||
#else
|
||||
#pragma warning disable SA1131 // Inverted comparison to remove JIT bounds check
|
||||
if (0u < (uint)array.Length)
|
||||
{
|
||||
return ref array[0, 0];
|
||||
}
|
||||
IntPtr offset = RuntimeHelpers.GetArray2DDataByteOffset<T>();
|
||||
|
||||
unsafe
|
||||
{
|
||||
return ref Unsafe.AsRef<T>(null);
|
||||
}
|
||||
#pragma warning restore SA1131
|
||||
return ref array.DangerousGetObjectDataReferenceAt<T>(offset);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
@ -66,23 +60,23 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
|
|||
/// </remarks>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static unsafe ref T DangerousGetReferenceAt<T>(this T[,] array, int i, int j)
|
||||
public static ref T DangerousGetReferenceAt<T>(this T[,] array, int i, int j)
|
||||
{
|
||||
#if NETCORE_RUNTIME
|
||||
var arrayData = Unsafe.As<RawArray2DData>(array);
|
||||
int offset = (i * arrayData.Width) + j;
|
||||
nint offset = ((nint)(uint)i * (nint)(uint)arrayData.Width) + (nint)(uint)j;
|
||||
ref T r0 = ref Unsafe.As<byte, T>(ref arrayData.Data);
|
||||
ref T ri = ref Unsafe.Add(ref r0, (IntPtr)(void*)(uint)offset);
|
||||
ref T ri = ref Unsafe.Add(ref r0, offset);
|
||||
|
||||
return ref ri;
|
||||
#else
|
||||
if ((uint)i < (uint)array.GetLength(0) &&
|
||||
(uint)j < (uint)array.GetLength(1))
|
||||
{
|
||||
return ref array[i, j];
|
||||
}
|
||||
int width = array.GetLength(1);
|
||||
nint index = ((nint)(uint)i * (nint)(uint)width) + (nint)(uint)j;
|
||||
IntPtr offset = RuntimeHelpers.GetArray2DDataByteOffset<T>();
|
||||
ref T r0 = ref array.DangerousGetObjectDataReferenceAt<T>(offset);
|
||||
ref T ri = ref Unsafe.Add(ref r0, index);
|
||||
|
||||
return ref Unsafe.AsRef<T>(null);
|
||||
return ref ri;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
@ -112,95 +106,46 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
|
|||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Fills an area in a given 2D <typeparamref name="T"/> array instance with a specified value.
|
||||
/// This API will try to fill as many items as possible, ignoring positions outside the bounds of the array.
|
||||
/// If invalid coordinates are given, they will simply be ignored and no exception will be thrown.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of elements in the input 2D <typeparamref name="T"/> array instance.</typeparam>
|
||||
/// <param name="array">The input <typeparamref name="T"/> array instance.</param>
|
||||
/// <param name="value">The <typeparamref name="T"/> value to fill the target area with.</param>
|
||||
/// <param name="row">The row to start on (inclusive, 0-based index).</param>
|
||||
/// <param name="column">The column to start on (inclusive, 0-based index).</param>
|
||||
/// <param name="width">The positive width of area to fill.</param>
|
||||
/// <param name="height">The positive height of area to fill.</param>
|
||||
public static void Fill<T>(this T[,] array, T value, int row, int column, int width, int height)
|
||||
{
|
||||
Rectangle bounds = new Rectangle(0, 0, array.GetLength(1), array.GetLength(0));
|
||||
|
||||
// Precompute bounds to skip branching in main loop
|
||||
bounds.Intersect(new Rectangle(column, row, width, height));
|
||||
|
||||
for (int i = bounds.Top; i < bounds.Bottom; i++)
|
||||
{
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
#if NETCORE_RUNTIME
|
||||
ref T r0 = ref array.DangerousGetReferenceAt(i, bounds.Left);
|
||||
#else
|
||||
ref T r0 = ref array[i, bounds.Left];
|
||||
#endif
|
||||
|
||||
// Span<T>.Fill will use vectorized instructions when possible
|
||||
MemoryMarshal.CreateSpan(ref r0, bounds.Width).Fill(value);
|
||||
#else
|
||||
ref T r0 = ref array[i, bounds.Left];
|
||||
|
||||
for (int j = 0; j < bounds.Width; j++)
|
||||
{
|
||||
// Storing the initial reference and only incrementing
|
||||
// that one in each iteration saves one additional indirect
|
||||
// dereference for every loop iteration compared to using
|
||||
// the DangerousGetReferenceAt<T> extension on the array.
|
||||
Unsafe.Add(ref r0, j) = value;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a <see cref="Span{T}"/> over a row in a given 2D <typeparamref name="T"/> array instance.
|
||||
/// Returns a <see cref="RefEnumerable{T}"/> over a row in a given 2D <typeparamref name="T"/> array instance.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of elements in the input 2D <typeparamref name="T"/> array instance.</typeparam>
|
||||
/// <param name="array">The input <typeparamref name="T"/> array instance.</param>
|
||||
/// <param name="row">The target row to retrieve (0-based index).</param>
|
||||
/// <returns>A <see cref="Span{T}"/> with the items from the target row within <paramref name="array"/>.</returns>
|
||||
/// <returns>A <see cref="RefEnumerable{T}"/> with the items from the target row within <paramref name="array"/>.</returns>
|
||||
/// <remarks>The returned <see cref="RefEnumerable{T}"/> value shouldn't be used directly: use this extension in a <see langword="foreach"/> loop.</remarks>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when one of the input parameters is out of range.</exception>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
Span<T>
|
||||
#else
|
||||
// .NET Standard 2.0 lacks MemoryMarshal.CreateSpan<T>(ref T, int),
|
||||
// which is necessary to create arbitrary Span<T>-s over a 2D array.
|
||||
// To work around this, we use a custom ref struct enumerator,
|
||||
// which makes the lack of that API completely transparent to the user.
|
||||
// If a user then moves from .NET Standard 2.0 to 2.1, all the previous
|
||||
// features will be perfectly supported, and in addition to that it will
|
||||
// also gain the ability to use the Span<T> value elsewhere.
|
||||
// The only case where this would be a breaking change for a user upgrading
|
||||
// the target framework is when the returned enumerator type is used directly,
|
||||
// but since that's specifically discouraged from the docs, we don't
|
||||
// need to worry about that scenario in particular, as users doing that
|
||||
// would be willingly go against the recommended usage of this API.
|
||||
Array2DRowEnumerable<T>
|
||||
#endif
|
||||
GetRow<T>(this T[,] array, int row)
|
||||
public static RefEnumerable<T> GetRow<T>(this T[,] array, int row)
|
||||
{
|
||||
if ((uint)row >= (uint)array.GetLength(0))
|
||||
if (array.IsCovariant())
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(row));
|
||||
ThrowArrayTypeMismatchException();
|
||||
}
|
||||
|
||||
int height = array.GetLength(0);
|
||||
|
||||
if ((uint)row >= (uint)height)
|
||||
{
|
||||
ThrowArgumentOutOfRangeExceptionForRow();
|
||||
}
|
||||
|
||||
int width = array.GetLength(1);
|
||||
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
ref T r0 = ref array.DangerousGetReferenceAt(row, 0);
|
||||
|
||||
return MemoryMarshal.CreateSpan(ref r0, array.GetLength(1));
|
||||
return new RefEnumerable<T>(ref r0, width, 1);
|
||||
#else
|
||||
return new Array2DRowEnumerable<T>(array, row);
|
||||
ref T r0 = ref array.DangerousGetReferenceAt(row, 0);
|
||||
IntPtr offset = array.DangerousGetObjectDataByteOffset(ref r0);
|
||||
|
||||
return new RefEnumerable<T>(array, offset, width, 1);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an enumerable that returns the items from a given column in a given 2D <typeparamref name="T"/> array instance.
|
||||
/// Returns a <see cref="RefEnumerable{T}"/> that returns the items from a given column in a given 2D <typeparamref name="T"/> array instance.
|
||||
/// This extension should be used directly within a <see langword="foreach"/> loop:
|
||||
/// <code>
|
||||
/// int[,] matrix =
|
||||
|
@ -221,15 +166,196 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
|
|||
/// <param name="array">The input <typeparamref name="T"/> array instance.</param>
|
||||
/// <param name="column">The target column to retrieve (0-based index).</param>
|
||||
/// <returns>A wrapper type that will handle the column enumeration for <paramref name="array"/>.</returns>
|
||||
/// <remarks>The returned <see cref="Array2DColumnEnumerable{T}"/> value shouldn't be used directly: use this extension in a <see langword="foreach"/> loop.</remarks>
|
||||
/// <remarks>The returned <see cref="RefEnumerable{T}"/> value shouldn't be used directly: use this extension in a <see langword="foreach"/> loop.</remarks>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when one of the input parameters is out of range.</exception>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Array2DColumnEnumerable<T> GetColumn<T>(this T[,] array, int column)
|
||||
public static RefEnumerable<T> GetColumn<T>(this T[,] array, int column)
|
||||
{
|
||||
return new Array2DColumnEnumerable<T>(array, column);
|
||||
if (array.IsCovariant())
|
||||
{
|
||||
ThrowArrayTypeMismatchException();
|
||||
}
|
||||
|
||||
int width = array.GetLength(1);
|
||||
|
||||
if ((uint)column >= (uint)width)
|
||||
{
|
||||
ThrowArgumentOutOfRangeExceptionForColumn();
|
||||
}
|
||||
|
||||
int height = array.GetLength(0);
|
||||
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
ref T r0 = ref array.DangerousGetReferenceAt(0, column);
|
||||
|
||||
return new RefEnumerable<T>(ref r0, height, width);
|
||||
#else
|
||||
ref T r0 = ref array.DangerousGetReferenceAt(0, column);
|
||||
IntPtr offset = array.DangerousGetObjectDataByteOffset(ref r0);
|
||||
|
||||
return new RefEnumerable<T>(array, offset, height, width);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="Span2D{T}"/> over an input 2D <typeparamref name="T"/> array.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of elements in the input 2D <typeparamref name="T"/> array instance.</typeparam>
|
||||
/// <param name="array">The input 2D <typeparamref name="T"/> array instance.</param>
|
||||
/// <returns>A <see cref="Span2D{T}"/> instance with the values of <paramref name="array"/>.</returns>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Span2D<T> AsSpan2D<T>(this T[,]? array)
|
||||
{
|
||||
return new Span2D<T>(array);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="Span2D{T}"/> over an input 2D <typeparamref name="T"/> array.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of elements in the input 2D <typeparamref name="T"/> array instance.</typeparam>
|
||||
/// <param name="array">The input 2D <typeparamref name="T"/> array instance.</param>
|
||||
/// <param name="row">The target row to map within <paramref name="array"/>.</param>
|
||||
/// <param name="column">The target column to map within <paramref name="array"/>.</param>
|
||||
/// <param name="height">The height to map within <paramref name="array"/>.</param>
|
||||
/// <param name="width">The width to map within <paramref name="array"/>.</param>
|
||||
/// <exception cref="ArrayTypeMismatchException">
|
||||
/// Thrown when <paramref name="array"/> doesn't match <typeparamref name="T"/>.
|
||||
/// </exception>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// Thrown when either <paramref name="height"/>, <paramref name="width"/> or <paramref name="height"/>
|
||||
/// are negative or not within the bounds that are valid for <paramref name="array"/>.
|
||||
/// </exception>
|
||||
/// <returns>A <see cref="Span2D{T}"/> instance with the values of <paramref name="array"/>.</returns>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Span2D<T> AsSpan2D<T>(this T[,]? array, int row, int column, int height, int width)
|
||||
{
|
||||
return new Span2D<T>(array, row, column, height, width);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="Memory2D{T}"/> over an input 2D <typeparamref name="T"/> array.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of elements in the input 2D <typeparamref name="T"/> array instance.</typeparam>
|
||||
/// <param name="array">The input 2D <typeparamref name="T"/> array instance.</param>
|
||||
/// <returns>A <see cref="Memory2D{T}"/> instance with the values of <paramref name="array"/>.</returns>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Memory2D<T> AsMemory2D<T>(this T[,]? array)
|
||||
{
|
||||
return new Memory2D<T>(array);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="Memory2D{T}"/> over an input 2D <typeparamref name="T"/> array.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of elements in the input 2D <typeparamref name="T"/> array instance.</typeparam>
|
||||
/// <param name="array">The input 2D <typeparamref name="T"/> array instance.</param>
|
||||
/// <param name="row">The target row to map within <paramref name="array"/>.</param>
|
||||
/// <param name="column">The target column to map within <paramref name="array"/>.</param>
|
||||
/// <param name="height">The height to map within <paramref name="array"/>.</param>
|
||||
/// <param name="width">The width to map within <paramref name="array"/>.</param>
|
||||
/// <exception cref="ArrayTypeMismatchException">
|
||||
/// Thrown when <paramref name="array"/> doesn't match <typeparamref name="T"/>.
|
||||
/// </exception>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// Thrown when either <paramref name="height"/>, <paramref name="width"/> or <paramref name="height"/>
|
||||
/// are negative or not within the bounds that are valid for <paramref name="array"/>.
|
||||
/// </exception>
|
||||
/// <returns>A <see cref="Memory2D{T}"/> instance with the values of <paramref name="array"/>.</returns>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Memory2D<T> AsMemory2D<T>(this T[,]? array, int row, int column, int height, int width)
|
||||
{
|
||||
return new Memory2D<T>(array, row, column, height, width);
|
||||
}
|
||||
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
/// <summary>
|
||||
/// Returns a <see cref="Span{T}"/> over a row in a given 2D <typeparamref name="T"/> array instance.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of elements in the input 2D <typeparamref name="T"/> array instance.</typeparam>
|
||||
/// <param name="array">The input <typeparamref name="T"/> array instance.</param>
|
||||
/// <param name="row">The target row to retrieve (0-based index).</param>
|
||||
/// <returns>A <see cref="Span{T}"/> with the items from the target row within <paramref name="array"/>.</returns>
|
||||
/// <exception cref="ArrayTypeMismatchException">Thrown when <paramref name="array"/> doesn't match <typeparamref name="T"/>.</exception>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when <paramref name="row"/> is invalid.</exception>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Span<T> GetRowSpan<T>(this T[,] array, int row)
|
||||
{
|
||||
if (array.IsCovariant())
|
||||
{
|
||||
ThrowArrayTypeMismatchException();
|
||||
}
|
||||
|
||||
if ((uint)row >= (uint)array.GetLength(0))
|
||||
{
|
||||
ThrowArgumentOutOfRangeExceptionForRow();
|
||||
}
|
||||
|
||||
ref T r0 = ref array.DangerousGetReferenceAt(row, 0);
|
||||
|
||||
return MemoryMarshal.CreateSpan(ref r0, array.GetLength(1));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a <see cref="Memory{T}"/> over a row in a given 2D <typeparamref name="T"/> array instance.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of elements in the input 2D <typeparamref name="T"/> array instance.</typeparam>
|
||||
/// <param name="array">The input <typeparamref name="T"/> array instance.</param>
|
||||
/// <param name="row">The target row to retrieve (0-based index).</param>
|
||||
/// <returns>A <see cref="Memory{T}"/> with the items from the target row within <paramref name="array"/>.</returns>
|
||||
/// <exception cref="ArrayTypeMismatchException">Thrown when <paramref name="array"/> doesn't match <typeparamref name="T"/>.</exception>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when <paramref name="row"/> is invalid.</exception>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Memory<T> GetRowMemory<T>(this T[,] array, int row)
|
||||
{
|
||||
if (array.IsCovariant())
|
||||
{
|
||||
ThrowArrayTypeMismatchException();
|
||||
}
|
||||
|
||||
if ((uint)row >= (uint)array.GetLength(0))
|
||||
{
|
||||
ThrowArgumentOutOfRangeExceptionForRow();
|
||||
}
|
||||
|
||||
ref T r0 = ref array.DangerousGetReferenceAt(row, 0);
|
||||
IntPtr offset = array.DangerousGetObjectDataByteOffset(ref r0);
|
||||
|
||||
return new RawObjectMemoryManager<T>(array, offset, array.GetLength(1)).Memory;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="Memory{T}"/> over an input 2D <typeparamref name="T"/> array.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of elements in the input 2D <typeparamref name="T"/> array instance.</typeparam>
|
||||
/// <param name="array">The input 2D <typeparamref name="T"/> array instance.</param>
|
||||
/// <returns>A <see cref="Memory{T}"/> instance with the values of <paramref name="array"/>.</returns>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Memory<T> AsMemory<T>(this T[,]? array)
|
||||
{
|
||||
if (array is null)
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
if (array.IsCovariant())
|
||||
{
|
||||
ThrowArrayTypeMismatchException();
|
||||
}
|
||||
|
||||
IntPtr offset = RuntimeHelpers.GetArray2DDataByteOffset<T>();
|
||||
int length = array.Length;
|
||||
|
||||
return new RawObjectMemoryManager<T>(array, offset, length).Memory;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="Span{T}"/> over an input 2D <typeparamref name="T"/> array.
|
||||
/// </summary>
|
||||
|
@ -238,26 +364,21 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
|
|||
/// <returns>A <see cref="Span{T}"/> instance with the values of <paramref name="array"/>.</returns>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Span<T> AsSpan<T>(this T[,] array)
|
||||
public static Span<T> AsSpan<T>(this T[,]? array)
|
||||
{
|
||||
#if NETCORE_RUNTIME
|
||||
var arrayData = Unsafe.As<RawArray2DData>(array);
|
||||
if (array is null)
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
// On x64, the length is padded to x64, but it is represented in memory
|
||||
// as two sequential uint fields (one of which is padding).
|
||||
// So we can just reinterpret a reference to the IntPtr as one of type
|
||||
// uint, to access the first 4 bytes of that field, regardless of whether
|
||||
// we're running in a 32 or 64 bit process. This will work when on little
|
||||
// endian systems as well, as the memory layout for fields is the same,
|
||||
// the only difference is the order of bytes within each field of a given type.
|
||||
// We use checked here to follow suit with the CoreCLR source, where an
|
||||
// invalid value here should fail to perform the cast and throw an exception.
|
||||
int length = checked((int)Unsafe.As<IntPtr, uint>(ref arrayData.Length));
|
||||
ref T r0 = ref Unsafe.As<byte, T>(ref arrayData.Data);
|
||||
#else
|
||||
if (array.IsCovariant())
|
||||
{
|
||||
ThrowArrayTypeMismatchException();
|
||||
}
|
||||
|
||||
ref T r0 = ref array.DangerousGetReference();
|
||||
int length = array.Length;
|
||||
ref T r0 = ref array[0, 0];
|
||||
#endif
|
||||
|
||||
return MemoryMarshal.CreateSpan(ref r0, length);
|
||||
}
|
||||
#endif
|
||||
|
@ -275,9 +396,16 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
|
|||
where T : IEquatable<T>
|
||||
{
|
||||
ref T r0 = ref array.DangerousGetReference();
|
||||
IntPtr length = (IntPtr)(void*)(uint)array.Length;
|
||||
nint
|
||||
length = RuntimeHelpers.GetArrayNativeLength(array),
|
||||
count = SpanHelper.Count(ref r0, length, value);
|
||||
|
||||
return SpanHelper.Count(ref r0, length, value);
|
||||
if ((nuint)count > int.MaxValue)
|
||||
{
|
||||
ThrowOverflowException();
|
||||
}
|
||||
|
||||
return (int)count;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -294,9 +422,46 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
|
|||
where T : notnull
|
||||
{
|
||||
ref T r0 = ref array.DangerousGetReference();
|
||||
IntPtr length = (IntPtr)(void*)(uint)array.Length;
|
||||
nint length = RuntimeHelpers.GetArrayNativeLength(array);
|
||||
|
||||
return SpanHelper.GetDjb2HashCode(ref r0, length);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether or not a given <typeparamref name="T"/> array is covariant.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of items in the input <typeparamref name="T"/> array instance.</typeparam>
|
||||
/// <param name="array">The input <typeparamref name="T"/> array instance.</param>
|
||||
/// <returns>Whether or not <paramref name="array"/> is covariant.</returns>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool IsCovariant<T>(this T[,] array)
|
||||
{
|
||||
return default(T) is null && array.GetType() != typeof(T[,]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArrayTypeMismatchException"/> when using an array of an invalid type.
|
||||
/// </summary>
|
||||
private static void ThrowArrayTypeMismatchException()
|
||||
{
|
||||
throw new ArrayTypeMismatchException("The given array doesn't match the specified type T");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentOutOfRangeException"/> when the "row" parameter is invalid.
|
||||
/// </summary>
|
||||
public static void ThrowArgumentOutOfRangeExceptionForRow()
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("row");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentOutOfRangeException"/> when the "column" parameter is invalid.
|
||||
/// </summary>
|
||||
public static void ThrowArgumentOutOfRangeExceptionForColumn()
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("column");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,324 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Runtime.CompilerServices;
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
using System.Runtime.InteropServices;
|
||||
using Microsoft.Toolkit.HighPerformance.Buffers.Internals;
|
||||
#endif
|
||||
using Microsoft.Toolkit.HighPerformance.Helpers.Internals;
|
||||
using Microsoft.Toolkit.HighPerformance.Memory;
|
||||
using RuntimeHelpers = Microsoft.Toolkit.HighPerformance.Helpers.Internals.RuntimeHelpers;
|
||||
|
||||
namespace Microsoft.Toolkit.HighPerformance.Extensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Helpers for working with the <see cref="Array"/> type.
|
||||
/// </summary>
|
||||
public static partial class ArrayExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns a reference to the first element within a given 3D <typeparamref name="T"/> array, with no bounds checks.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of elements in the input 3D <typeparamref name="T"/> array instance.</typeparam>
|
||||
/// <param name="array">The input <typeparamref name="T"/> array instance.</param>
|
||||
/// <returns>A reference to the first element within <paramref name="array"/>, or the location it would have used, if <paramref name="array"/> is empty.</returns>
|
||||
/// <remarks>This method doesn't do any bounds checks, therefore it is responsibility of the caller to perform checks in case the returned value is dereferenced.</remarks>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ref T DangerousGetReference<T>(this T[,,] array)
|
||||
{
|
||||
#if NETCORE_RUNTIME
|
||||
var arrayData = Unsafe.As<RawArray3DData>(array);
|
||||
ref T r0 = ref Unsafe.As<byte, T>(ref arrayData.Data);
|
||||
|
||||
return ref r0;
|
||||
#else
|
||||
IntPtr offset = RuntimeHelpers.GetArray3DDataByteOffset<T>();
|
||||
|
||||
return ref array.DangerousGetObjectDataReferenceAt<T>(offset);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a reference to an element at a specified coordinate within a given 3D <typeparamref name="T"/> array, with no bounds checks.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of elements in the input 3D <typeparamref name="T"/> array instance.</typeparam>
|
||||
/// <param name="array">The input 2D <typeparamref name="T"/> array instance.</param>
|
||||
/// <param name="i">The depth index of the element to retrieve within <paramref name="array"/>.</param>
|
||||
/// <param name="j">The vertical index of the element to retrieve within <paramref name="array"/>.</param>
|
||||
/// <param name="k">The horizontal index of the element to retrieve within <paramref name="array"/>.</param>
|
||||
/// <returns>A reference to the element within <paramref name="array"/> at the coordinate specified by <paramref name="i"/> and <paramref name="j"/>.</returns>
|
||||
/// <remarks>
|
||||
/// This method doesn't do any bounds checks, therefore it is responsibility of the caller to ensure the <paramref name="i"/>
|
||||
/// and <paramref name="j"/> parameters are valid. Furthermore, this extension will ignore the lower bounds for the input
|
||||
/// array, and will just assume that the input index is 0-based. It is responsability of the caller to adjust the input
|
||||
/// indices to account for the actual lower bounds, if the input array has either axis not starting at 0.
|
||||
/// </remarks>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ref T DangerousGetReferenceAt<T>(this T[,,] array, int i, int j, int k)
|
||||
{
|
||||
#if NETCORE_RUNTIME
|
||||
var arrayData = Unsafe.As<RawArray3DData>(array);
|
||||
nint offset =
|
||||
((nint)(uint)i * (nint)(uint)arrayData.Height * (nint)(uint)arrayData.Width) +
|
||||
((nint)(uint)j * (nint)(uint)arrayData.Width) + (nint)(uint)k;
|
||||
ref T r0 = ref Unsafe.As<byte, T>(ref arrayData.Data);
|
||||
ref T ri = ref Unsafe.Add(ref r0, offset);
|
||||
|
||||
return ref ri;
|
||||
#else
|
||||
int
|
||||
height = array.GetLength(1),
|
||||
width = array.GetLength(2);
|
||||
nint index =
|
||||
((nint)(uint)i * (nint)(uint)height * (nint)(uint)width) +
|
||||
((nint)(uint)j * (nint)(uint)width) + (nint)(uint)k;
|
||||
IntPtr offset = RuntimeHelpers.GetArray3DDataByteOffset<T>();
|
||||
ref T r0 = ref array.DangerousGetObjectDataReferenceAt<T>(offset);
|
||||
ref T ri = ref Unsafe.Add(ref r0, index);
|
||||
|
||||
return ref ri;
|
||||
#endif
|
||||
}
|
||||
|
||||
#if NETCORE_RUNTIME
|
||||
// See description for this in the 2D partial file.
|
||||
// Using the CHW naming scheme here (like with RGB images).
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private sealed class RawArray3DData
|
||||
{
|
||||
#pragma warning disable CS0649 // Unassigned fields
|
||||
#pragma warning disable SA1401 // Fields should be private
|
||||
public IntPtr Length;
|
||||
public int Channel;
|
||||
public int Height;
|
||||
public int Width;
|
||||
public int ChannelLowerBound;
|
||||
public int HeightLowerBound;
|
||||
public int WidthLowerBound;
|
||||
public byte Data;
|
||||
#pragma warning restore CS0649
|
||||
#pragma warning restore SA1401
|
||||
}
|
||||
#endif
|
||||
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="Memory{T}"/> over an input 3D <typeparamref name="T"/> array.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of elements in the input 3D <typeparamref name="T"/> array instance.</typeparam>
|
||||
/// <param name="array">The input 3D <typeparamref name="T"/> array instance.</param>
|
||||
/// <returns>A <see cref="Memory{T}"/> instance with the values of <paramref name="array"/>.</returns>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Memory<T> AsMemory<T>(this T[,,]? array)
|
||||
{
|
||||
if (array is null)
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
if (array.IsCovariant())
|
||||
{
|
||||
ThrowArrayTypeMismatchException();
|
||||
}
|
||||
|
||||
IntPtr offset = RuntimeHelpers.GetArray3DDataByteOffset<T>();
|
||||
int length = array.Length;
|
||||
|
||||
return new RawObjectMemoryManager<T>(array, offset, length).Memory;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="Span{T}"/> over an input 3D <typeparamref name="T"/> array.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of elements in the input 3D <typeparamref name="T"/> array instance.</typeparam>
|
||||
/// <param name="array">The input 3D <typeparamref name="T"/> array instance.</param>
|
||||
/// <returns>A <see cref="Span{T}"/> instance with the values of <paramref name="array"/>.</returns>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Span<T> AsSpan<T>(this T[,,]? array)
|
||||
{
|
||||
if (array is null)
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
if (array.IsCovariant())
|
||||
{
|
||||
ThrowArrayTypeMismatchException();
|
||||
}
|
||||
|
||||
ref T r0 = ref array.DangerousGetReference();
|
||||
int length = array.Length;
|
||||
|
||||
return MemoryMarshal.CreateSpan(ref r0, length);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="Span{T}"/> struct wrapping a layer in a 3D array.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of elements in the input 3D <typeparamref name="T"/> array instance.</typeparam>
|
||||
/// <param name="array">The given 3D array to wrap.</param>
|
||||
/// <param name="depth">The target layer to map within <paramref name="array"/>.</param>
|
||||
/// <exception cref="ArrayTypeMismatchException">Thrown when <paramref name="array"/> doesn't match <typeparamref name="T"/>.</exception>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when <paramref name="depth"/> is invalid.</exception>
|
||||
/// <returns>A <see cref="Span{T}"/> instance wrapping the target layer within <paramref name="array"/>.</returns>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Span<T> AsSpan<T>(this T[,,] array, int depth)
|
||||
{
|
||||
if (array.IsCovariant())
|
||||
{
|
||||
ThrowArrayTypeMismatchException();
|
||||
}
|
||||
|
||||
if ((uint)depth >= (uint)array.GetLength(0))
|
||||
{
|
||||
ThrowArgumentOutOfRangeExceptionForDepth();
|
||||
}
|
||||
|
||||
ref T r0 = ref array.DangerousGetReferenceAt(depth, 0, 0);
|
||||
int length = checked(array.GetLength(1) * array.GetLength(2));
|
||||
|
||||
return MemoryMarshal.CreateSpan(ref r0, length);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="Memory{T}"/> struct wrapping a layer in a 3D array.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of elements in the input 3D <typeparamref name="T"/> array instance.</typeparam>
|
||||
/// <param name="array">The given 3D array to wrap.</param>
|
||||
/// <param name="depth">The target layer to map within <paramref name="array"/>.</param>
|
||||
/// <exception cref="ArrayTypeMismatchException">Thrown when <paramref name="array"/> doesn't match <typeparamref name="T"/>.</exception>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when <paramref name="depth"/> is invalid.</exception>
|
||||
/// <returns>A <see cref="Memory{T}"/> instance wrapping the target layer within <paramref name="array"/>.</returns>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Memory<T> AsMemory<T>(this T[,,] array, int depth)
|
||||
{
|
||||
if (array.IsCovariant())
|
||||
{
|
||||
ThrowArrayTypeMismatchException();
|
||||
}
|
||||
|
||||
if ((uint)depth >= (uint)array.GetLength(0))
|
||||
{
|
||||
ThrowArgumentOutOfRangeExceptionForDepth();
|
||||
}
|
||||
|
||||
ref T r0 = ref array.DangerousGetReferenceAt(depth, 0, 0);
|
||||
IntPtr offset = array.DangerousGetObjectDataByteOffset(ref r0);
|
||||
int length = checked(array.GetLength(1) * array.GetLength(2));
|
||||
|
||||
return new RawObjectMemoryManager<T>(array, offset, length).Memory;
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="Span2D{T}"/> struct wrapping a layer in a 3D array.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of elements in the input 3D <typeparamref name="T"/> array instance.</typeparam>
|
||||
/// <param name="array">The given 3D array to wrap.</param>
|
||||
/// <param name="depth">The target layer to map within <paramref name="array"/>.</param>
|
||||
/// <exception cref="ArrayTypeMismatchException">
|
||||
/// Thrown when <paramref name="array"/> doesn't match <typeparamref name="T"/>.
|
||||
/// </exception>
|
||||
/// <exception cref="ArgumentException">Thrown when either <paramref name="depth"/> is invalid.</exception>
|
||||
/// <returns>A <see cref="Span2D{T}"/> instance wrapping the target layer within <paramref name="array"/>.</returns>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Span2D<T> AsSpan2D<T>(this T[,,] array, int depth)
|
||||
{
|
||||
return new Span2D<T>(array, depth);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="Memory2D{T}"/> struct wrapping a layer in a 3D array.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of elements in the input 3D <typeparamref name="T"/> array instance.</typeparam>
|
||||
/// <param name="array">The given 3D array to wrap.</param>
|
||||
/// <param name="depth">The target layer to map within <paramref name="array"/>.</param>
|
||||
/// <exception cref="ArrayTypeMismatchException">
|
||||
/// Thrown when <paramref name="array"/> doesn't match <typeparamref name="T"/>.
|
||||
/// </exception>
|
||||
/// <exception cref="ArgumentException">Thrown when either <paramref name="depth"/> is invalid.</exception>
|
||||
/// <returns>A <see cref="Memory2D{T}"/> instance wrapping the target layer within <paramref name="array"/>.</returns>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Memory2D<T> AsMemory2D<T>(this T[,,] array, int depth)
|
||||
{
|
||||
return new Memory2D<T>(array, depth);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Counts the number of occurrences of a given value into a target 3D <typeparamref name="T"/> array instance.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of items in the input 3D <typeparamref name="T"/> array instance.</typeparam>
|
||||
/// <param name="array">The input 3D <typeparamref name="T"/> array instance.</param>
|
||||
/// <param name="value">The <typeparamref name="T"/> value to look for.</param>
|
||||
/// <returns>The number of occurrences of <paramref name="value"/> in <paramref name="array"/>.</returns>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static int Count<T>(this T[,,] array, T value)
|
||||
where T : IEquatable<T>
|
||||
{
|
||||
ref T r0 = ref array.DangerousGetReference();
|
||||
nint
|
||||
length = RuntimeHelpers.GetArrayNativeLength(array),
|
||||
count = SpanHelper.Count(ref r0, length, value);
|
||||
|
||||
if ((nuint)count > int.MaxValue)
|
||||
{
|
||||
ThrowOverflowException();
|
||||
}
|
||||
|
||||
return (int)count;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a content hash from the input 3D <typeparamref name="T"/> array instance using the Djb2 algorithm.
|
||||
/// For more info, see the documentation for <see cref="ReadOnlySpanExtensions.GetDjb2HashCode{T}"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of items in the input 3D <typeparamref name="T"/> array instance.</typeparam>
|
||||
/// <param name="array">The input 3D <typeparamref name="T"/> array instance.</param>
|
||||
/// <returns>The Djb2 value for the input 3D <typeparamref name="T"/> array instance.</returns>
|
||||
/// <remarks>The Djb2 hash is fully deterministic and with no random components.</remarks>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static int GetDjb2HashCode<T>(this T[,,] array)
|
||||
where T : notnull
|
||||
{
|
||||
ref T r0 = ref array.DangerousGetReference();
|
||||
nint length = RuntimeHelpers.GetArrayNativeLength(array);
|
||||
|
||||
return SpanHelper.GetDjb2HashCode(ref r0, length);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether or not a given <typeparamref name="T"/> array is covariant.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of items in the input <typeparamref name="T"/> array instance.</typeparam>
|
||||
/// <param name="array">The input <typeparamref name="T"/> array instance.</param>
|
||||
/// <returns>Whether or not <paramref name="array"/> is covariant.</returns>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool IsCovariant<T>(this T[,,] array)
|
||||
{
|
||||
return default(T) is null && array.GetType() != typeof(T[,,]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentOutOfRangeException"/> when the "depth" parameter is invalid.
|
||||
/// </summary>
|
||||
public static void ThrowArgumentOutOfRangeExceptionForDepth()
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("depth");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,6 +2,7 @@
|
|||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
|
@ -12,6 +13,19 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
|
|||
/// </summary>
|
||||
public static class BoolExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Converts the given <see cref="bool"/> value into a <see cref="byte"/>.
|
||||
/// </summary>
|
||||
/// <param name="flag">The input value to convert.</param>
|
||||
/// <returns>1 if <paramref name="flag"/> is <see langword="true"/>, 0 otherwise.</returns>
|
||||
/// <remarks>This method does not contain branching instructions.</remarks>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static byte ToByte(this bool flag)
|
||||
{
|
||||
return Unsafe.As<bool, byte>(ref flag);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts the given <see cref="bool"/> value into an <see cref="int"/>.
|
||||
/// </summary>
|
||||
|
@ -20,6 +34,7 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
|
|||
/// <remarks>This method does not contain branching instructions.</remarks>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
[Obsolete("Use ToByte instead.")]
|
||||
public static int ToInt(this bool flag)
|
||||
{
|
||||
return Unsafe.As<bool, byte>(ref flag);
|
||||
|
|
|
@ -23,12 +23,7 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
|
|||
/// <param name="span">The input <see cref="ReadOnlySpan{T}"/> instance.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Add<T>(ref this HashCode hashCode, ReadOnlySpan<T> span)
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
where T : notnull
|
||||
#else
|
||||
// Same type constraints as HashCode<T>, see comments there
|
||||
where T : unmanaged
|
||||
#endif
|
||||
{
|
||||
int hash = HashCode<T>.CombineValues(span);
|
||||
|
||||
|
|
|
@ -6,6 +6,9 @@ using System;
|
|||
using System.Diagnostics.Contracts;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
using Microsoft.Toolkit.HighPerformance.Memory;
|
||||
#endif
|
||||
using MemoryStream = Microsoft.Toolkit.HighPerformance.Streams.MemoryStream;
|
||||
|
||||
namespace Microsoft.Toolkit.HighPerformance.Extensions
|
||||
|
@ -15,6 +18,52 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
|
|||
/// </summary>
|
||||
public static class MemoryExtensions
|
||||
{
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
/// <summary>
|
||||
/// Returns a <see cref="Memory2D{T}"/> instance wrapping the underlying data for the given <see cref="Memory{T}"/> instance.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of items in the input <see cref="Memory{T}"/> instance.</typeparam>
|
||||
/// <param name="memory">The input <see cref="Memory{T}"/> instance.</param>
|
||||
/// <param name="height">The height of the resulting 2D area.</param>
|
||||
/// <param name="width">The width of each row in the resulting 2D area.</param>
|
||||
/// <returns>The resulting <see cref="Memory2D{T}"/> instance.</returns>
|
||||
/// <exception cref="ArgumentOutOfRangeException">
|
||||
/// Thrown when one of the input parameters is out of range.
|
||||
/// </exception>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// Thrown when the requested area is outside of bounds for <paramref name="memory"/>.
|
||||
/// </exception>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Memory2D<T> AsMemory2D<T>(this Memory<T> memory, int height, int width)
|
||||
{
|
||||
return new Memory2D<T>(memory, height, width);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a <see cref="Memory2D{T}"/> instance wrapping the underlying data for the given <see cref="Memory{T}"/> instance.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of items in the input <see cref="Memory{T}"/> instance.</typeparam>
|
||||
/// <param name="memory">The input <see cref="Memory{T}"/> instance.</param>
|
||||
/// <param name="offset">The initial offset within <paramref name="memory"/>.</param>
|
||||
/// <param name="height">The height of the resulting 2D area.</param>
|
||||
/// <param name="width">The width of each row in the resulting 2D area.</param>
|
||||
/// <param name="pitch">The pitch in the resulting 2D area.</param>
|
||||
/// <returns>The resulting <see cref="Memory2D{T}"/> instance.</returns>
|
||||
/// <exception cref="ArgumentOutOfRangeException">
|
||||
/// Thrown when one of the input parameters is out of range.
|
||||
/// </exception>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// Thrown when the requested area is outside of bounds for <paramref name="memory"/>.
|
||||
/// </exception>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Memory2D<T> AsMemory2D<T>(this Memory<T> memory, int offset, int height, int width, int pitch)
|
||||
{
|
||||
return new Memory2D<T>(memory, offset, height, width, pitch);
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Returns a <see cref="Stream"/> wrapping the contents of the given <see cref="Memory{T}"/> of <see cref="byte"/> instance.
|
||||
/// </summary>
|
||||
|
|
|
@ -5,6 +5,10 @@
|
|||
using System;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
using Microsoft.Toolkit.HighPerformance.Memory;
|
||||
#endif
|
||||
using MemoryStream = Microsoft.Toolkit.HighPerformance.Streams.MemoryStream;
|
||||
|
||||
namespace Microsoft.Toolkit.HighPerformance.Extensions
|
||||
|
@ -14,6 +18,52 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
|
|||
/// </summary>
|
||||
public static class ReadOnlyMemoryExtensions
|
||||
{
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
/// <summary>
|
||||
/// Returns a <see cref="ReadOnlyMemory2D{T}"/> instance wrapping the underlying data for the given <see cref="ReadOnlyMemory{T}"/> instance.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of items in the input <see cref="ReadOnlyMemory{T}"/> instance.</typeparam>
|
||||
/// <param name="memory">The input <see cref="ReadOnlyMemory{T}"/> instance.</param>
|
||||
/// <param name="height">The height of the resulting 2D area.</param>
|
||||
/// <param name="width">The width of each row in the resulting 2D area.</param>
|
||||
/// <returns>The resulting <see cref="ReadOnlyMemory2D{T}"/> instance.</returns>
|
||||
/// <exception cref="ArgumentOutOfRangeException">
|
||||
/// Thrown when one of the input parameters is out of range.
|
||||
/// </exception>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// Thrown when the requested area is outside of bounds for <paramref name="memory"/>.
|
||||
/// </exception>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ReadOnlyMemory2D<T> AsMemory2D<T>(this ReadOnlyMemory<T> memory, int height, int width)
|
||||
{
|
||||
return new ReadOnlyMemory2D<T>(memory, height, width);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a <see cref="ReadOnlyMemory2D{T}"/> instance wrapping the underlying data for the given <see cref="ReadOnlyMemory{T}"/> instance.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of items in the input <see cref="ReadOnlyMemory{T}"/> instance.</typeparam>
|
||||
/// <param name="memory">The input <see cref="ReadOnlyMemory{T}"/> instance.</param>
|
||||
/// <param name="offset">The initial offset within <paramref name="memory"/>.</param>
|
||||
/// <param name="height">The height of the resulting 2D area.</param>
|
||||
/// <param name="width">The width of each row in the resulting 2D area.</param>
|
||||
/// <param name="pitch">The pitch in the resulting 2D area.</param>
|
||||
/// <returns>The resulting <see cref="ReadOnlyMemory2D{T}"/> instance.</returns>
|
||||
/// <exception cref="ArgumentOutOfRangeException">
|
||||
/// Thrown when one of the input parameters is out of range.
|
||||
/// </exception>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// Thrown when the requested area is outside of bounds for <paramref name="memory"/>.
|
||||
/// </exception>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ReadOnlyMemory2D<T> AsMemory2D<T>(this ReadOnlyMemory<T> memory, int offset, int height, int width, int pitch)
|
||||
{
|
||||
return new ReadOnlyMemory2D<T>(memory, offset, height, width, pitch);
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Returns a <see cref="Stream"/> wrapping the contents of the given <see cref="ReadOnlyMemory{T}"/> of <see cref="byte"/> instance.
|
||||
/// </summary>
|
||||
|
@ -27,6 +77,7 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
|
|||
/// </remarks>
|
||||
/// <exception cref="ArgumentException">Thrown when <paramref name="memory"/> has an invalid data store.</exception>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Stream AsStream(this ReadOnlyMemory<byte> memory)
|
||||
{
|
||||
return MemoryStream.Create(memory, true);
|
||||
|
|
|
@ -8,6 +8,9 @@ using System.Runtime.CompilerServices;
|
|||
using System.Runtime.InteropServices;
|
||||
using Microsoft.Toolkit.HighPerformance.Enumerables;
|
||||
using Microsoft.Toolkit.HighPerformance.Helpers.Internals;
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
using Microsoft.Toolkit.HighPerformance.Memory;
|
||||
#endif
|
||||
|
||||
namespace Microsoft.Toolkit.HighPerformance.Extensions
|
||||
{
|
||||
|
@ -40,10 +43,10 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
|
|||
/// <remarks>This method doesn't do any bounds checks, therefore it is responsibility of the caller to ensure the <paramref name="i"/> parameter is valid.</remarks>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static unsafe ref T DangerousGetReferenceAt<T>(this ReadOnlySpan<T> span, int i)
|
||||
public static ref T DangerousGetReferenceAt<T>(this ReadOnlySpan<T> span, int i)
|
||||
{
|
||||
// Here we assume the input index will never be negative, so we do an unsafe cast to
|
||||
// force the JIT to skip the sign extension when going from int to native int.
|
||||
// Here we assume the input index will never be negative, so we do a (nint)(uint) cast
|
||||
// to force the JIT to skip the sign extension when going from int to native int.
|
||||
// On .NET Core 3.1, if we only use Unsafe.Add(ref r0, i), we get the following:
|
||||
// =============================
|
||||
// L0000: mov rax, [rcx]
|
||||
|
@ -54,7 +57,7 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
|
|||
// Note the movsxd (move with sign extension) to expand the index passed in edx to
|
||||
// the whole rdx register. This is unnecessary and more expensive than just a mov,
|
||||
// which when done to a large register size automatically zeroes the upper bits.
|
||||
// With the (IntPtr)(void*)(uint) cast, we get the following codegen instead:
|
||||
// With the (nint)(uint) cast, we get the following codegen instead:
|
||||
// =============================
|
||||
// L0000: mov rax, [rcx]
|
||||
// L0003: mov edx, edx
|
||||
|
@ -67,13 +70,31 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
|
|||
// bit architectures, producing optimal code in both cases (they are either completely
|
||||
// elided on 32 bit systems, or result in the correct register expansion when on 64 bit).
|
||||
// We first do an unchecked conversion to uint (which is just a reinterpret-cast). We
|
||||
// then cast to void*, which lets the following IntPtr cast avoid the range check on 32 bit
|
||||
// (since uint could be out of range there if the original index was negative). The final
|
||||
// result is a clean mov as shown above. This will eventually be natively supported by the
|
||||
// JIT compiler (see https://github.com/dotnet/runtime/issues/38794), but doing this here
|
||||
// then cast to nint, so that we can obtain an IntPtr value without the range check (since
|
||||
// uint could be out of range there if the original index was negative). The final result
|
||||
// is a clean mov as shown above. This will eventually be natively supported by the JIT
|
||||
// compiler (see https://github.com/dotnet/runtime/issues/38794), but doing this here
|
||||
// still ensures the optimal codegen even on existing runtimes (eg. .NET Core 2.1 and 3.1).
|
||||
ref T r0 = ref MemoryMarshal.GetReference(span);
|
||||
ref T ri = ref Unsafe.Add(ref r0, (IntPtr)(void*)(uint)i);
|
||||
ref T ri = ref Unsafe.Add(ref r0, (nint)(uint)i);
|
||||
|
||||
return ref ri;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a reference to an element at a specified index within a given <see cref="ReadOnlySpan{T}"/>, with no bounds checks.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of elements in the input <see cref="ReadOnlySpan{T}"/> instance.</typeparam>
|
||||
/// <param name="span">The input <see cref="ReadOnlySpan{T}"/> instance.</param>
|
||||
/// <param name="i">The index of the element to retrieve within <paramref name="span"/>.</param>
|
||||
/// <returns>A reference to the element within <paramref name="span"/> at the index specified by <paramref name="i"/>.</returns>
|
||||
/// <remarks>This method doesn't do any bounds checks, therefore it is responsibility of the caller to ensure the <paramref name="i"/> parameter is valid.</remarks>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ref T DangerousGetReferenceAt<T>(this ReadOnlySpan<T> span, nint i)
|
||||
{
|
||||
ref T r0 = ref MemoryMarshal.GetReference(span);
|
||||
ref T ri = ref Unsafe.Add(ref r0, i);
|
||||
|
||||
return ref ri;
|
||||
}
|
||||
|
@ -117,7 +138,7 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
|
|||
/// </returns>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static unsafe ref readonly T DangerousGetLookupReferenceAt<T>(this ReadOnlySpan<T> span, int i)
|
||||
public static ref readonly T DangerousGetLookupReferenceAt<T>(this ReadOnlySpan<T> span, int i)
|
||||
{
|
||||
// Check whether the input is in range by first casting both
|
||||
// operands to uint and then comparing them, as this allows
|
||||
|
@ -141,11 +162,57 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
|
|||
mask = ~negativeFlag,
|
||||
offset = (uint)i & mask;
|
||||
ref T r0 = ref MemoryMarshal.GetReference(span);
|
||||
ref T r1 = ref Unsafe.Add(ref r0, (IntPtr)(void*)offset);
|
||||
ref T r1 = ref Unsafe.Add(ref r0, (nint)offset);
|
||||
|
||||
return ref r1;
|
||||
}
|
||||
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
/// <summary>
|
||||
/// Returns a <see cref="ReadOnlySpan2D{T}"/> instance wrapping the underlying data for the given <see cref="ReadOnlySpan{T}"/> instance.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of items in the input <see cref="ReadOnlySpan{T}"/> instance.</typeparam>
|
||||
/// <param name="span">The input <see cref="ReadOnlySpan{T}"/> instance.</param>
|
||||
/// <param name="height">The height of the resulting 2D area.</param>
|
||||
/// <param name="width">The width of each row in the resulting 2D area.</param>
|
||||
/// <returns>The resulting <see cref="ReadOnlySpan2D{T}"/> instance.</returns>
|
||||
/// <exception cref="ArgumentOutOfRangeException">
|
||||
/// Thrown when one of the input parameters is out of range.
|
||||
/// </exception>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// Thrown when the requested area is outside of bounds for <paramref name="span"/>.
|
||||
/// </exception>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ReadOnlySpan2D<T> AsSpan2D<T>(this ReadOnlySpan<T> span, int height, int width)
|
||||
{
|
||||
return new ReadOnlySpan2D<T>(span, height, width);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a <see cref="ReadOnlySpan2D{T}"/> instance wrapping the underlying data for the given <see cref="ReadOnlySpan{T}"/> instance.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of items in the input <see cref="ReadOnlySpan{T}"/> instance.</typeparam>
|
||||
/// <param name="span">The input <see cref="ReadOnlySpan{T}"/> instance.</param>
|
||||
/// <param name="offset">The initial offset within <paramref name="span"/>.</param>
|
||||
/// <param name="height">The height of the resulting 2D area.</param>
|
||||
/// <param name="width">The width of each row in the resulting 2D area.</param>
|
||||
/// <param name="pitch">The pitch in the resulting 2D area.</param>
|
||||
/// <returns>The resulting <see cref="ReadOnlySpan2D{T}"/> instance.</returns>
|
||||
/// <exception cref="ArgumentOutOfRangeException">
|
||||
/// Thrown when one of the input parameters is out of range.
|
||||
/// </exception>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// Thrown when the requested area is outside of bounds for <paramref name="span"/>.
|
||||
/// </exception>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ReadOnlySpan2D<T> AsSpan2D<T>(this ReadOnlySpan<T> span, int offset, int height, int width, int pitch)
|
||||
{
|
||||
return new ReadOnlySpan2D<T>(span, offset, height, width, pitch);
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Gets the index of an element of a given <see cref="ReadOnlySpan{T}"/> from its reference.
|
||||
/// </summary>
|
||||
|
@ -156,34 +223,20 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
|
|||
/// <exception cref="ArgumentOutOfRangeException">Thrown if <paramref name="value"/> does not belong to <paramref name="span"/>.</exception>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static unsafe int IndexOf<T>(this ReadOnlySpan<T> span, in T value)
|
||||
public static int IndexOf<T>(this ReadOnlySpan<T> span, in T value)
|
||||
{
|
||||
ref T r0 = ref MemoryMarshal.GetReference(span);
|
||||
ref T r1 = ref Unsafe.AsRef(value);
|
||||
IntPtr byteOffset = Unsafe.ByteOffset(ref r0, ref r1);
|
||||
|
||||
if (sizeof(IntPtr) == sizeof(long))
|
||||
nint elementOffset = byteOffset / (nint)(uint)Unsafe.SizeOf<T>();
|
||||
|
||||
if ((nuint)elementOffset >= (uint)span.Length)
|
||||
{
|
||||
long elementOffset = (long)byteOffset / Unsafe.SizeOf<T>();
|
||||
|
||||
if ((ulong)elementOffset >= (ulong)span.Length)
|
||||
{
|
||||
SpanExtensions.ThrowArgumentOutOfRangeExceptionForInvalidReference();
|
||||
}
|
||||
|
||||
return unchecked((int)elementOffset);
|
||||
SpanExtensions.ThrowArgumentOutOfRangeExceptionForInvalidReference();
|
||||
}
|
||||
else
|
||||
{
|
||||
int elementOffset = (int)byteOffset / Unsafe.SizeOf<T>();
|
||||
|
||||
if ((uint)elementOffset >= (uint)span.Length)
|
||||
{
|
||||
SpanExtensions.ThrowArgumentOutOfRangeExceptionForInvalidReference();
|
||||
}
|
||||
|
||||
return elementOffset;
|
||||
}
|
||||
return (int)elementOffset;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -195,13 +248,13 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
|
|||
/// <returns>The number of occurrences of <paramref name="value"/> in <paramref name="span"/>.</returns>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static unsafe int Count<T>(this ReadOnlySpan<T> span, T value)
|
||||
public static int Count<T>(this ReadOnlySpan<T> span, T value)
|
||||
where T : IEquatable<T>
|
||||
{
|
||||
ref T r0 = ref MemoryMarshal.GetReference(span);
|
||||
IntPtr length = (IntPtr)(void*)(uint)span.Length;
|
||||
nint length = (nint)(uint)span.Length;
|
||||
|
||||
return SpanHelper.Count(ref r0, length, value);
|
||||
return (int)SpanHelper.Count(ref r0, length, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -321,13 +374,41 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
|
|||
/// <remarks>The Djb2 hash is fully deterministic and with no random components.</remarks>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static unsafe int GetDjb2HashCode<T>(this ReadOnlySpan<T> span)
|
||||
public static int GetDjb2HashCode<T>(this ReadOnlySpan<T> span)
|
||||
where T : notnull
|
||||
{
|
||||
ref T r0 = ref MemoryMarshal.GetReference(span);
|
||||
IntPtr length = (IntPtr)(void*)(uint)span.Length;
|
||||
nint length = (nint)(uint)span.Length;
|
||||
|
||||
return SpanHelper.GetDjb2HashCode(ref r0, length);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies the contents of a given <see cref="ReadOnlySpan{T}"/> into destination <see cref="RefEnumerable{T}"/> instance.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of items in the input <see cref="ReadOnlySpan{T}"/> instance.</typeparam>
|
||||
/// <param name="span">The input <see cref="ReadOnlySpan{T}"/> instance.</param>
|
||||
/// <param name="destination">The <see cref="RefEnumerable{T}"/> instance to copy items into.</param>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// Thrown when the destination <see cref="RefEnumerable{T}"/> is shorter than the source <see cref="ReadOnlySpan{T}"/>.
|
||||
/// </exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void CopyTo<T>(this ReadOnlySpan<T> span, RefEnumerable<T> destination)
|
||||
{
|
||||
destination.CopyFrom(span);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to copy the contents of a given <see cref="ReadOnlySpan{T}"/> into destination <see cref="RefEnumerable{T}"/> instance.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of items in the input <see cref="ReadOnlySpan{T}"/> instance.</typeparam>
|
||||
/// <param name="span">The input <see cref="ReadOnlySpan{T}"/> instance.</param>
|
||||
/// <param name="destination">The <see cref="RefEnumerable{T}"/> instance to copy items into.</param>
|
||||
/// <returns>Whether or not the operation was successful.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool TryCopyTo<T>(this ReadOnlySpan<T> span, RefEnumerable<T> destination)
|
||||
{
|
||||
return destination.TryCopyFrom(span);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,9 @@ using System.Runtime.CompilerServices;
|
|||
using System.Runtime.InteropServices;
|
||||
using Microsoft.Toolkit.HighPerformance.Enumerables;
|
||||
using Microsoft.Toolkit.HighPerformance.Helpers.Internals;
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
using Microsoft.Toolkit.HighPerformance.Memory;
|
||||
#endif
|
||||
|
||||
namespace Microsoft.Toolkit.HighPerformance.Extensions
|
||||
{
|
||||
|
@ -40,14 +43,78 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
|
|||
/// <remarks>This method doesn't do any bounds checks, therefore it is responsibility of the caller to ensure the <paramref name="i"/> parameter is valid.</remarks>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static unsafe ref T DangerousGetReferenceAt<T>(this Span<T> span, int i)
|
||||
public static ref T DangerousGetReferenceAt<T>(this Span<T> span, int i)
|
||||
{
|
||||
ref T r0 = ref MemoryMarshal.GetReference(span);
|
||||
ref T ri = ref Unsafe.Add(ref r0, (IntPtr)(void*)(uint)i);
|
||||
ref T ri = ref Unsafe.Add(ref r0, (nint)(uint)i);
|
||||
|
||||
return ref ri;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a reference to an element at a specified index within a given <see cref="Span{T}"/>, with no bounds checks.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of elements in the input <see cref="Span{T}"/> instance.</typeparam>
|
||||
/// <param name="span">The input <see cref="Span{T}"/> instance.</param>
|
||||
/// <param name="i">The index of the element to retrieve within <paramref name="span"/>.</param>
|
||||
/// <returns>A reference to the element within <paramref name="span"/> at the index specified by <paramref name="i"/>.</returns>
|
||||
/// <remarks>This method doesn't do any bounds checks, therefore it is responsibility of the caller to ensure the <paramref name="i"/> parameter is valid.</remarks>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ref T DangerousGetReferenceAt<T>(this Span<T> span, nint i)
|
||||
{
|
||||
ref T r0 = ref MemoryMarshal.GetReference(span);
|
||||
ref T ri = ref Unsafe.Add(ref r0, i);
|
||||
|
||||
return ref ri;
|
||||
}
|
||||
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
/// <summary>
|
||||
/// Returns a <see cref="Span2D{T}"/> instance wrapping the underlying data for the given <see cref="Span{T}"/> instance.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of items in the input <see cref="Span{T}"/> instance.</typeparam>
|
||||
/// <param name="span">The input <see cref="Span{T}"/> instance.</param>
|
||||
/// <param name="height">The height of the resulting 2D area.</param>
|
||||
/// <param name="width">The width of each row in the resulting 2D area.</param>
|
||||
/// <returns>The resulting <see cref="Span2D{T}"/> instance.</returns>
|
||||
/// <exception cref="ArgumentOutOfRangeException">
|
||||
/// Thrown when one of the input parameters is out of range.
|
||||
/// </exception>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// Thrown when the requested area is outside of bounds for <paramref name="span"/>.
|
||||
/// </exception>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Span2D<T> AsSpan2D<T>(this Span<T> span, int height, int width)
|
||||
{
|
||||
return new Span2D<T>(span, height, width);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a <see cref="Span2D{T}"/> instance wrapping the underlying data for the given <see cref="Span{T}"/> instance.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of items in the input <see cref="Span{T}"/> instance.</typeparam>
|
||||
/// <param name="span">The input <see cref="Span{T}"/> instance.</param>
|
||||
/// <param name="offset">The initial offset within <paramref name="span"/>.</param>
|
||||
/// <param name="height">The height of the resulting 2D area.</param>
|
||||
/// <param name="width">The width of each row in the resulting 2D area.</param>
|
||||
/// <param name="pitch">The pitch in the resulting 2D area.</param>
|
||||
/// <returns>The resulting <see cref="Span2D{T}"/> instance.</returns>
|
||||
/// <exception cref="ArgumentOutOfRangeException">
|
||||
/// Thrown when one of the input parameters is out of range.
|
||||
/// </exception>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// Thrown when the requested area is outside of bounds for <paramref name="span"/>.
|
||||
/// </exception>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Span2D<T> AsSpan2D<T>(this Span<T> span, int offset, int height, int width, int pitch)
|
||||
{
|
||||
return new Span2D<T>(span, offset, height, width, pitch);
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Casts a <see cref="Span{T}"/> of one primitive type <typeparamref name="T"/> to <see cref="Span{T}"/> of bytes.
|
||||
/// That type may not contain pointers or references. This is checked at runtime in order to preserve type safety.
|
||||
|
@ -102,33 +169,19 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
|
|||
/// <exception cref="ArgumentOutOfRangeException">Thrown if <paramref name="value"/> does not belong to <paramref name="span"/>.</exception>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static unsafe int IndexOf<T>(this Span<T> span, ref T value)
|
||||
public static int IndexOf<T>(this Span<T> span, ref T value)
|
||||
{
|
||||
ref T r0 = ref MemoryMarshal.GetReference(span);
|
||||
IntPtr byteOffset = Unsafe.ByteOffset(ref r0, ref value);
|
||||
|
||||
if (sizeof(IntPtr) == sizeof(long))
|
||||
nint elementOffset = byteOffset / (nint)(uint)Unsafe.SizeOf<T>();
|
||||
|
||||
if ((nuint)elementOffset >= (uint)span.Length)
|
||||
{
|
||||
long elementOffset = (long)byteOffset / Unsafe.SizeOf<T>();
|
||||
|
||||
if ((ulong)elementOffset >= (ulong)span.Length)
|
||||
{
|
||||
ThrowArgumentOutOfRangeExceptionForInvalidReference();
|
||||
}
|
||||
|
||||
return unchecked((int)elementOffset);
|
||||
ThrowArgumentOutOfRangeExceptionForInvalidReference();
|
||||
}
|
||||
else
|
||||
{
|
||||
int elementOffset = (int)byteOffset / Unsafe.SizeOf<T>();
|
||||
|
||||
if ((uint)elementOffset >= (uint)span.Length)
|
||||
{
|
||||
ThrowArgumentOutOfRangeExceptionForInvalidReference();
|
||||
}
|
||||
|
||||
return elementOffset;
|
||||
}
|
||||
return (int)elementOffset;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -140,13 +193,13 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
|
|||
/// <returns>The number of occurrences of <paramref name="value"/> in <paramref name="span"/>.</returns>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static unsafe int Count<T>(this Span<T> span, T value)
|
||||
public static int Count<T>(this Span<T> span, T value)
|
||||
where T : IEquatable<T>
|
||||
{
|
||||
ref T r0 = ref MemoryMarshal.GetReference(span);
|
||||
IntPtr length = (IntPtr)(void*)(uint)span.Length;
|
||||
nint length = (nint)(uint)span.Length;
|
||||
|
||||
return SpanHelper.Count(ref r0, length, value);
|
||||
return (int)SpanHelper.Count(ref r0, length, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -211,15 +264,43 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
|
|||
/// <remarks>The Djb2 hash is fully deterministic and with no random components.</remarks>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static unsafe int GetDjb2HashCode<T>(this Span<T> span)
|
||||
public static int GetDjb2HashCode<T>(this Span<T> span)
|
||||
where T : notnull
|
||||
{
|
||||
ref T r0 = ref MemoryMarshal.GetReference(span);
|
||||
IntPtr length = (IntPtr)(void*)(uint)span.Length;
|
||||
nint length = (nint)(uint)span.Length;
|
||||
|
||||
return SpanHelper.GetDjb2HashCode(ref r0, length);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies the contents of a given <see cref="Span{T}"/> into destination <see cref="RefEnumerable{T}"/> instance.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of items in the input <see cref="Span{T}"/> instance.</typeparam>
|
||||
/// <param name="span">The input <see cref="Span{T}"/> instance.</param>
|
||||
/// <param name="destination">The <see cref="RefEnumerable{T}"/> instance to copy items into.</param>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// Thrown when the destination <see cref="RefEnumerable{T}"/> is shorter than the source <see cref="Span{T}"/>.
|
||||
/// </exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void CopyTo<T>(this Span<T> span, RefEnumerable<T> destination)
|
||||
{
|
||||
destination.CopyFrom(span);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to copy the contents of a given <see cref="Span{T}"/> into destination <see cref="RefEnumerable{T}"/> instance.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of items in the input <see cref="Span{T}"/> instance.</typeparam>
|
||||
/// <param name="span">The input <see cref="Span{T}"/> instance.</param>
|
||||
/// <param name="destination">The <see cref="RefEnumerable{T}"/> instance to copy items into.</param>
|
||||
/// <returns>Whether or not the operation was successful.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool TryCopyTo<T>(this Span<T> span, RefEnumerable<T> destination)
|
||||
{
|
||||
return destination.TryCopyFrom(span);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentOutOfRangeException"/> when the given reference is out of range.
|
||||
/// </summary>
|
||||
|
|
|
@ -48,7 +48,7 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
|
|||
/// <remarks>This method doesn't do any bounds checks, therefore it is responsibility of the caller to ensure the <paramref name="i"/> parameter is valid.</remarks>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static unsafe ref char DangerousGetReferenceAt(this string text, int i)
|
||||
public static ref char DangerousGetReferenceAt(this string text, int i)
|
||||
{
|
||||
#if NETCOREAPP3_1
|
||||
ref char r0 = ref Unsafe.AsRef(text.GetPinnableReference());
|
||||
|
@ -57,7 +57,7 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
|
|||
#else
|
||||
ref char r0 = ref MemoryMarshal.GetReference(text.AsSpan());
|
||||
#endif
|
||||
ref char ri = ref Unsafe.Add(ref r0, (IntPtr)(void*)(uint)i);
|
||||
ref char ri = ref Unsafe.Add(ref r0, (nint)(uint)i);
|
||||
|
||||
return ref ri;
|
||||
}
|
||||
|
@ -91,12 +91,12 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
|
|||
/// <returns>The number of occurrences of <paramref name="c"/> in <paramref name="text"/>.</returns>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static unsafe int Count(this string text, char c)
|
||||
public static int Count(this string text, char c)
|
||||
{
|
||||
ref char r0 = ref text.DangerousGetReference();
|
||||
IntPtr length = (IntPtr)(void*)(uint)text.Length;
|
||||
nint length = (nint)(uint)text.Length;
|
||||
|
||||
return SpanHelper.Count(ref r0, length, c);
|
||||
return (int)SpanHelper.Count(ref r0, length, c);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -160,7 +160,7 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
|
|||
public static unsafe int GetDjb2HashCode(this string text)
|
||||
{
|
||||
ref char r0 = ref text.DangerousGetReference();
|
||||
IntPtr length = (IntPtr)(void*)(uint)text.Length;
|
||||
nint length = (nint)(uint)text.Length;
|
||||
|
||||
return SpanHelper.GetDjb2HashCode(ref r0, length);
|
||||
}
|
||||
|
|
|
@ -9,6 +9,11 @@ using System.Diagnostics.Contracts;
|
|||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using Microsoft.Toolkit.HighPerformance.Helpers.Internals;
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
using RuntimeHelpers = System.Runtime.CompilerServices.RuntimeHelpers;
|
||||
#else
|
||||
using RuntimeHelpers = Microsoft.Toolkit.HighPerformance.Helpers.Internals.RuntimeHelpers;
|
||||
#endif
|
||||
|
||||
namespace Microsoft.Toolkit.HighPerformance.Helpers
|
||||
{
|
||||
|
@ -25,14 +30,7 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers
|
|||
/// For more info, see <see href="https://docs.microsoft.com/en-us/dotnet/api/system.object.gethashcode#remarks"/>.
|
||||
/// </remarks>
|
||||
public struct HashCode<T>
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
where T : notnull
|
||||
#else
|
||||
// If we lack the RuntimeHelpers.IsReferenceOrContainsReferences<T> API,
|
||||
// we need to constraint the generic type parameter to unmanaged, as we
|
||||
// wouldn't otherwise be able to properly validate it at runtime.
|
||||
where T : unmanaged
|
||||
#endif
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a content hash from the input <see cref="ReadOnlySpan{T}"/> instance using the xxHash32 algorithm.
|
||||
|
@ -57,19 +55,17 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers
|
|||
/// <remarks>The returned hash code is not processed through <see cref="HashCode"/> APIs.</remarks>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal static unsafe int CombineValues(ReadOnlySpan<T> span)
|
||||
internal static int CombineValues(ReadOnlySpan<T> span)
|
||||
{
|
||||
ref T r0 = ref MemoryMarshal.GetReference(span);
|
||||
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
// If typeof(T) is not unmanaged, iterate over all the items one by one.
|
||||
// This check is always known in advance either by the JITter or by the AOT
|
||||
// compiler, so this branch will never actually be executed by the code.
|
||||
if (RuntimeHelpers.IsReferenceOrContainsReferences<T>())
|
||||
{
|
||||
return SpanHelper.GetDjb2HashCode(ref r0, (IntPtr)(void*)(uint)span.Length);
|
||||
return SpanHelper.GetDjb2HashCode(ref r0, (nint)(uint)span.Length);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Get the info for the target memory area to process.
|
||||
// The line below is computing the total byte size for the span,
|
||||
|
@ -79,7 +75,7 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers
|
|||
// process. In that case it will just compute the byte size as a 32 bit
|
||||
// multiplication with overflow, which is guaranteed never to happen anyway.
|
||||
ref byte rb = ref Unsafe.As<T, byte>(ref r0);
|
||||
IntPtr length = (IntPtr)(void*)((uint)span.Length * (uint)Unsafe.SizeOf<T>());
|
||||
nint length = (nint)((uint)span.Length * (uint)Unsafe.SizeOf<T>());
|
||||
|
||||
return SpanHelper.GetDjb2LikeByteHash(ref rb, length);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,267 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Microsoft.Toolkit.HighPerformance.Helpers.Internals
|
||||
{
|
||||
/// <summary>
|
||||
/// Helpers to process sequences of values by reference with a given step.
|
||||
/// </summary>
|
||||
internal static class RefEnumerableHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Clears a target memory area.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of values to clear.</typeparam>
|
||||
/// <param name="r0">A <typeparamref name="T"/> reference to the start of the memory area.</param>
|
||||
/// <param name="length">The number of items in the memory area.</param>
|
||||
/// <param name="step">The number of items between each consecutive target value.</param>
|
||||
public static void Clear<T>(ref T r0, nint length, nint step)
|
||||
{
|
||||
nint offset = 0;
|
||||
|
||||
// Main loop with 8 unrolled iterations
|
||||
while (length >= 8)
|
||||
{
|
||||
Unsafe.Add(ref r0, offset) = default!;
|
||||
Unsafe.Add(ref r0, offset += step) = default!;
|
||||
Unsafe.Add(ref r0, offset += step) = default!;
|
||||
Unsafe.Add(ref r0, offset += step) = default!;
|
||||
Unsafe.Add(ref r0, offset += step) = default!;
|
||||
Unsafe.Add(ref r0, offset += step) = default!;
|
||||
Unsafe.Add(ref r0, offset += step) = default!;
|
||||
Unsafe.Add(ref r0, offset += step) = default!;
|
||||
|
||||
length -= 8;
|
||||
offset += step;
|
||||
}
|
||||
|
||||
if (length >= 4)
|
||||
{
|
||||
Unsafe.Add(ref r0, offset) = default!;
|
||||
Unsafe.Add(ref r0, offset += step) = default!;
|
||||
Unsafe.Add(ref r0, offset += step) = default!;
|
||||
Unsafe.Add(ref r0, offset += step) = default!;
|
||||
|
||||
length -= 4;
|
||||
offset += step;
|
||||
}
|
||||
|
||||
// Clear the remaining values
|
||||
while (length > 0)
|
||||
{
|
||||
Unsafe.Add(ref r0, offset) = default!;
|
||||
|
||||
length -= 1;
|
||||
offset += step;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies a sequence of discontiguous items from one memory area to another.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of items to copy.</typeparam>
|
||||
/// <param name="sourceRef">The source reference to copy from.</param>
|
||||
/// <param name="destinationRef">The target reference to copy to.</param>
|
||||
/// <param name="length">The total number of items to copy.</param>
|
||||
/// <param name="sourceStep">The step between consecutive items in the memory area pointed to by <paramref name="sourceRef"/>.</param>
|
||||
public static void CopyTo<T>(ref T sourceRef, ref T destinationRef, nint length, nint sourceStep)
|
||||
{
|
||||
nint
|
||||
sourceOffset = 0,
|
||||
destinationOffset = 0;
|
||||
|
||||
while (length >= 8)
|
||||
{
|
||||
Unsafe.Add(ref destinationRef, destinationOffset + 0) = Unsafe.Add(ref sourceRef, sourceOffset);
|
||||
Unsafe.Add(ref destinationRef, destinationOffset + 1) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep);
|
||||
Unsafe.Add(ref destinationRef, destinationOffset + 2) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep);
|
||||
Unsafe.Add(ref destinationRef, destinationOffset + 3) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep);
|
||||
Unsafe.Add(ref destinationRef, destinationOffset + 4) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep);
|
||||
Unsafe.Add(ref destinationRef, destinationOffset + 5) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep);
|
||||
Unsafe.Add(ref destinationRef, destinationOffset + 6) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep);
|
||||
Unsafe.Add(ref destinationRef, destinationOffset + 7) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep);
|
||||
|
||||
length -= 8;
|
||||
sourceOffset += sourceStep;
|
||||
destinationOffset += 8;
|
||||
}
|
||||
|
||||
if (length >= 4)
|
||||
{
|
||||
Unsafe.Add(ref destinationRef, destinationOffset + 0) = Unsafe.Add(ref sourceRef, sourceOffset);
|
||||
Unsafe.Add(ref destinationRef, destinationOffset + 1) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep);
|
||||
Unsafe.Add(ref destinationRef, destinationOffset + 2) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep);
|
||||
Unsafe.Add(ref destinationRef, destinationOffset + 3) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep);
|
||||
|
||||
length -= 4;
|
||||
sourceOffset += sourceStep;
|
||||
destinationOffset += 4;
|
||||
}
|
||||
|
||||
while (length > 0)
|
||||
{
|
||||
Unsafe.Add(ref destinationRef, destinationOffset) = Unsafe.Add(ref sourceRef, sourceOffset);
|
||||
|
||||
length -= 1;
|
||||
sourceOffset += sourceStep;
|
||||
destinationOffset += 1;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies a sequence of discontiguous items from one memory area to another.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of items to copy.</typeparam>
|
||||
/// <param name="sourceRef">The source reference to copy from.</param>
|
||||
/// <param name="destinationRef">The target reference to copy to.</param>
|
||||
/// <param name="length">The total number of items to copy.</param>
|
||||
/// <param name="sourceStep">The step between consecutive items in the memory area pointed to by <paramref name="sourceRef"/>.</param>
|
||||
/// <param name="destinationStep">The step between consecutive items in the memory area pointed to by <paramref name="destinationRef"/>.</param>
|
||||
public static void CopyTo<T>(ref T sourceRef, ref T destinationRef, nint length, nint sourceStep, nint destinationStep)
|
||||
{
|
||||
nint
|
||||
sourceOffset = 0,
|
||||
destinationOffset = 0;
|
||||
|
||||
while (length >= 8)
|
||||
{
|
||||
Unsafe.Add(ref destinationRef, destinationOffset) = Unsafe.Add(ref sourceRef, sourceOffset);
|
||||
Unsafe.Add(ref destinationRef, destinationOffset += destinationStep) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep);
|
||||
Unsafe.Add(ref destinationRef, destinationOffset += destinationStep) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep);
|
||||
Unsafe.Add(ref destinationRef, destinationOffset += destinationStep) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep);
|
||||
Unsafe.Add(ref destinationRef, destinationOffset += destinationStep) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep);
|
||||
Unsafe.Add(ref destinationRef, destinationOffset += destinationStep) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep);
|
||||
Unsafe.Add(ref destinationRef, destinationOffset += destinationStep) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep);
|
||||
Unsafe.Add(ref destinationRef, destinationOffset += destinationStep) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep);
|
||||
|
||||
length -= 8;
|
||||
sourceOffset += sourceStep;
|
||||
destinationOffset += destinationStep;
|
||||
}
|
||||
|
||||
if (length >= 4)
|
||||
{
|
||||
Unsafe.Add(ref destinationRef, destinationOffset) = Unsafe.Add(ref sourceRef, sourceOffset);
|
||||
Unsafe.Add(ref destinationRef, destinationOffset += destinationStep) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep);
|
||||
Unsafe.Add(ref destinationRef, destinationOffset += destinationStep) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep);
|
||||
Unsafe.Add(ref destinationRef, destinationOffset += destinationStep) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep);
|
||||
|
||||
length -= 4;
|
||||
sourceOffset += sourceStep;
|
||||
destinationOffset += destinationStep;
|
||||
}
|
||||
|
||||
while (length > 0)
|
||||
{
|
||||
Unsafe.Add(ref destinationRef, destinationOffset) = Unsafe.Add(ref sourceRef, sourceOffset);
|
||||
|
||||
length -= 1;
|
||||
sourceOffset += sourceStep;
|
||||
destinationOffset += destinationStep;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies a sequence of discontiguous items from one memory area to another. This mirrors
|
||||
/// <see cref="CopyTo{T}(ref T,ref T,nint,nint)"/>, but <paramref name="sourceStep"/> refers to <paramref name="destinationRef"/> instead.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of items to copy.</typeparam>
|
||||
/// <param name="sourceRef">The source reference to copy from.</param>
|
||||
/// <param name="destinationRef">The target reference to copy to.</param>
|
||||
/// <param name="length">The total number of items to copy.</param>
|
||||
/// <param name="sourceStep">The step between consecutive items in the memory area pointed to by <paramref name="sourceRef"/>.</param>
|
||||
public static void CopyFrom<T>(ref T sourceRef, ref T destinationRef, nint length, nint sourceStep)
|
||||
{
|
||||
nint
|
||||
sourceOffset = 0,
|
||||
destinationOffset = 0;
|
||||
|
||||
while (length >= 8)
|
||||
{
|
||||
Unsafe.Add(ref destinationRef, destinationOffset) = Unsafe.Add(ref sourceRef, sourceOffset);
|
||||
Unsafe.Add(ref destinationRef, destinationOffset += sourceStep) = Unsafe.Add(ref sourceRef, sourceOffset + 1);
|
||||
Unsafe.Add(ref destinationRef, destinationOffset += sourceStep) = Unsafe.Add(ref sourceRef, sourceOffset + 2);
|
||||
Unsafe.Add(ref destinationRef, destinationOffset += sourceStep) = Unsafe.Add(ref sourceRef, sourceOffset + 3);
|
||||
Unsafe.Add(ref destinationRef, destinationOffset += sourceStep) = Unsafe.Add(ref sourceRef, sourceOffset + 4);
|
||||
Unsafe.Add(ref destinationRef, destinationOffset += sourceStep) = Unsafe.Add(ref sourceRef, sourceOffset + 5);
|
||||
Unsafe.Add(ref destinationRef, destinationOffset += sourceStep) = Unsafe.Add(ref sourceRef, sourceOffset + 6);
|
||||
Unsafe.Add(ref destinationRef, destinationOffset += sourceStep) = Unsafe.Add(ref sourceRef, sourceOffset + 7);
|
||||
|
||||
length -= 8;
|
||||
sourceOffset += 8;
|
||||
destinationOffset += sourceStep;
|
||||
}
|
||||
|
||||
if (length >= 4)
|
||||
{
|
||||
Unsafe.Add(ref destinationRef, destinationOffset) = Unsafe.Add(ref sourceRef, sourceOffset);
|
||||
Unsafe.Add(ref destinationRef, destinationOffset += sourceStep) = Unsafe.Add(ref sourceRef, sourceOffset + 1);
|
||||
Unsafe.Add(ref destinationRef, destinationOffset += sourceStep) = Unsafe.Add(ref sourceRef, sourceOffset + 2);
|
||||
Unsafe.Add(ref destinationRef, destinationOffset += sourceStep) = Unsafe.Add(ref sourceRef, sourceOffset + 3);
|
||||
|
||||
length -= 4;
|
||||
sourceOffset += 4;
|
||||
destinationOffset += sourceStep;
|
||||
}
|
||||
|
||||
while (length > 0)
|
||||
{
|
||||
Unsafe.Add(ref destinationRef, destinationOffset) = Unsafe.Add(ref sourceRef, sourceOffset);
|
||||
|
||||
length -= 1;
|
||||
sourceOffset += 1;
|
||||
destinationOffset += sourceStep;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fills a target memory area.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of values to fill.</typeparam>
|
||||
/// <param name="r0">A <typeparamref name="T"/> reference to the start of the memory area.</param>
|
||||
/// <param name="length">The number of items in the memory area.</param>
|
||||
/// <param name="step">The number of items between each consecutive target value.</param>
|
||||
/// <param name="value">The value to assign to every item in the target memory area.</param>
|
||||
public static void Fill<T>(ref T r0, nint length, nint step, T value)
|
||||
{
|
||||
nint offset = 0;
|
||||
|
||||
while (length >= 8)
|
||||
{
|
||||
Unsafe.Add(ref r0, offset) = value;
|
||||
Unsafe.Add(ref r0, offset += step) = value;
|
||||
Unsafe.Add(ref r0, offset += step) = value;
|
||||
Unsafe.Add(ref r0, offset += step) = value;
|
||||
Unsafe.Add(ref r0, offset += step) = value;
|
||||
Unsafe.Add(ref r0, offset += step) = value;
|
||||
Unsafe.Add(ref r0, offset += step) = value;
|
||||
Unsafe.Add(ref r0, offset += step) = value;
|
||||
|
||||
length -= 8;
|
||||
offset += step;
|
||||
}
|
||||
|
||||
if (length >= 4)
|
||||
{
|
||||
Unsafe.Add(ref r0, offset) = value;
|
||||
Unsafe.Add(ref r0, offset += step) = value;
|
||||
Unsafe.Add(ref r0, offset += step) = value;
|
||||
Unsafe.Add(ref r0, offset += step) = value;
|
||||
|
||||
length -= 4;
|
||||
offset += step;
|
||||
}
|
||||
|
||||
while (length > 0)
|
||||
{
|
||||
Unsafe.Add(ref r0, offset) = value;
|
||||
|
||||
length -= 1;
|
||||
offset += step;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,275 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
#pragma warning disable SA1512
|
||||
|
||||
// The portable implementation in this type is originally from CoreFX.
|
||||
// See https://github.com/dotnet/corefx/blob/release/2.1/src/System.Memory/src/System/SpanHelpers.cs.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics.Contracts;
|
||||
#if !SPAN_RUNTIME_SUPPORT
|
||||
using System.Reflection;
|
||||
#endif
|
||||
using System.Runtime.CompilerServices;
|
||||
using Microsoft.Toolkit.HighPerformance.Extensions;
|
||||
|
||||
namespace Microsoft.Toolkit.HighPerformance.Helpers.Internals
|
||||
{
|
||||
/// <summary>
|
||||
/// A helper class that act as polyfill for .NET Standard 2.0 and below.
|
||||
/// </summary>
|
||||
internal static class RuntimeHelpers
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the length of a given array as a native integer.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of values in the array.</typeparam>
|
||||
/// <param name="array">The input <see cref="Array"/> instance.</param>
|
||||
/// <returns>The total length of <paramref name="array"/> as a native integer.</returns>
|
||||
/// <remarks>
|
||||
/// This method is needed because this expression is not inlined correctly if the target array
|
||||
/// is only visible as a non-generic <see cref="Array"/> instance, because the C# compiler will
|
||||
/// not be able to emit the <see langword="ldlen"/> opcode instead of calling the right method.
|
||||
/// </remarks>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static nint GetArrayNativeLength<T>(T[] array)
|
||||
{
|
||||
#if NETSTANDARD1_4
|
||||
// .NET Standard 1.4 doesn't include the API to get the long length, so
|
||||
// we just cast the length and throw in case the array is larger than
|
||||
// int.MaxValue. There's not much we can do in this specific case.
|
||||
return (nint)(uint)array.Length;
|
||||
#else
|
||||
return (nint)array.LongLength;
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the length of a given array as a native integer.
|
||||
/// </summary>
|
||||
/// <param name="array">The input <see cref="Array"/> instance.</param>
|
||||
/// <returns>The total length of <paramref name="array"/> as a native integer.</returns>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static nint GetArrayNativeLength(Array array)
|
||||
{
|
||||
#if NETSTANDARD1_4
|
||||
return (nint)(uint)array.Length;
|
||||
#else
|
||||
return (nint)array.LongLength;
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the byte offset to the first <typeparamref name="T"/> element in a SZ array.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of values in the array.</typeparam>
|
||||
/// <returns>The byte offset to the first <typeparamref name="T"/> element in a SZ array.</returns>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static IntPtr GetArrayDataByteOffset<T>()
|
||||
{
|
||||
return TypeInfo<T>.ArrayDataByteOffset;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the byte offset to the first <typeparamref name="T"/> element in a 2D array.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of values in the array.</typeparam>
|
||||
/// <returns>The byte offset to the first <typeparamref name="T"/> element in a 2D array.</returns>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static IntPtr GetArray2DDataByteOffset<T>()
|
||||
{
|
||||
return TypeInfo<T>.Array2DDataByteOffset;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the byte offset to the first <typeparamref name="T"/> element in a 3D array.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of values in the array.</typeparam>
|
||||
/// <returns>The byte offset to the first <typeparamref name="T"/> element in a 3D array.</returns>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static IntPtr GetArray3DDataByteOffset<T>()
|
||||
{
|
||||
return TypeInfo<T>.Array3DDataByteOffset;
|
||||
}
|
||||
|
||||
#if !SPAN_RUNTIME_SUPPORT
|
||||
/// <summary>
|
||||
/// Gets a byte offset describing a portable pinnable reference. This can either be an
|
||||
/// interior pointer into some object data (described with a valid <see cref="object"/> reference
|
||||
/// and a reference to some of its data), or a raw pointer (described with a <see langword="null"/>
|
||||
/// reference to an <see cref="object"/>, and a reference that is assumed to refer to pinned data).
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of field being referenced.</typeparam>
|
||||
/// <param name="obj">The input <see cref="object"/> hosting the target field.</param>
|
||||
/// <param name="data">A reference to a target field of type <typeparamref name="T"/> within <paramref name="obj"/>.</param>
|
||||
/// <returns>
|
||||
/// The <see cref="IntPtr"/> value representing the offset to the target field from the start of the object data
|
||||
/// for the parameter <paramref name="obj"/>, or the value of the raw pointer passed as a tracked reference.
|
||||
/// </returns>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static unsafe IntPtr GetObjectDataOrReferenceByteOffset<T>(object? obj, ref T data)
|
||||
{
|
||||
if (obj is null)
|
||||
{
|
||||
return (IntPtr)Unsafe.AsPointer(ref data);
|
||||
}
|
||||
|
||||
return obj.DangerousGetObjectDataByteOffset(ref data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a reference from data describing a portable pinnable reference. This can either be an
|
||||
/// interior pointer into some object data (described with a valid <see cref="object"/> reference
|
||||
/// and a byte offset into its data), or a raw pointer (described with a <see langword="null"/>
|
||||
/// reference to an <see cref="object"/>, and a byte offset representing the value of the raw pointer).
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of reference to retrieve.</typeparam>
|
||||
/// <param name="obj">The input <see cref="object"/> hosting the target field.</param>
|
||||
/// <param name="offset">The input byte offset for the <typeparamref name="T"/> reference to retrieve.</param>
|
||||
/// <returns>A <typeparamref name="T"/> reference matching the given parameters.</returns>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static unsafe ref T GetObjectDataAtOffsetOrPointerReference<T>(object? obj, IntPtr offset)
|
||||
{
|
||||
if (obj is null)
|
||||
{
|
||||
return ref Unsafe.AsRef<T>((void*)offset);
|
||||
}
|
||||
|
||||
return ref obj.DangerousGetObjectDataReferenceAt<T>(offset);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether or not a given type is a reference type or contains references.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type to check.</typeparam>
|
||||
/// <returns>Whether or not <typeparamref name="T"/> respects the <see langword="unmanaged"/> constraint.</returns>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool IsReferenceOrContainsReferences<T>()
|
||||
{
|
||||
return TypeInfo<T>.IsReferenceOrContainsReferences;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implements the logic for <see cref="IsReferenceOrContainsReferences{T}"/>.
|
||||
/// </summary>
|
||||
/// <param name="type">The current type to check.</param>
|
||||
/// <returns>Whether or not <paramref name="type"/> is a reference type or contains references.</returns>
|
||||
[Pure]
|
||||
private static bool IsReferenceOrContainsReferences(Type type)
|
||||
{
|
||||
// Common case, for primitive types
|
||||
if (type.GetTypeInfo().IsPrimitive)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!type.GetTypeInfo().IsValueType)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check if the type is Nullable<T>
|
||||
if (Nullable.GetUnderlyingType(type) is Type nullableType)
|
||||
{
|
||||
type = nullableType;
|
||||
}
|
||||
|
||||
if (type.GetTypeInfo().IsEnum)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Complex struct, recursively inspect all fields
|
||||
foreach (FieldInfo field in type.GetTypeInfo().DeclaredFields)
|
||||
{
|
||||
if (field.IsStatic)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (IsReferenceOrContainsReferences(field.FieldType))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// A private generic class to preload type info for arbitrary runtime types.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type to load info for.</typeparam>
|
||||
private static class TypeInfo<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// The byte offset to the first <typeparamref name="T"/> element in a SZ array.
|
||||
/// </summary>
|
||||
public static readonly IntPtr ArrayDataByteOffset = MeasureArrayDataByteOffset();
|
||||
|
||||
/// <summary>
|
||||
/// The byte offset to the first <typeparamref name="T"/> element in a 2D array.
|
||||
/// </summary>
|
||||
public static readonly IntPtr Array2DDataByteOffset = MeasureArray2DDataByteOffset();
|
||||
|
||||
/// <summary>
|
||||
/// The byte offset to the first <typeparamref name="T"/> element in a 3D array.
|
||||
/// </summary>
|
||||
public static readonly IntPtr Array3DDataByteOffset = MeasureArray3DDataByteOffset();
|
||||
|
||||
#if !SPAN_RUNTIME_SUPPORT
|
||||
/// <summary>
|
||||
/// Indicates whether <typeparamref name="T"/> does not respect the <see langword="unmanaged"/> constraint.
|
||||
/// </summary>
|
||||
public static readonly bool IsReferenceOrContainsReferences = IsReferenceOrContainsReferences(typeof(T));
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Computes the value for <see cref="ArrayDataByteOffset"/>.
|
||||
/// </summary>
|
||||
/// <returns>The value of <see cref="ArrayDataByteOffset"/> for the current runtime.</returns>
|
||||
[Pure]
|
||||
private static IntPtr MeasureArrayDataByteOffset()
|
||||
{
|
||||
var array = new T[1];
|
||||
|
||||
return array.DangerousGetObjectDataByteOffset(ref array[0]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Computes the value for <see cref="Array2DDataByteOffset"/>.
|
||||
/// </summary>
|
||||
/// <returns>The value of <see cref="Array2DDataByteOffset"/> for the current runtime.</returns>
|
||||
[Pure]
|
||||
private static IntPtr MeasureArray2DDataByteOffset()
|
||||
{
|
||||
var array = new T[1, 1];
|
||||
|
||||
return array.DangerousGetObjectDataByteOffset(ref array[0, 0]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Computes the value for <see cref="Array3DDataByteOffset"/>.
|
||||
/// </summary>
|
||||
/// <returns>The value of <see cref="Array3DDataByteOffset"/> for the current runtime.</returns>
|
||||
[Pure]
|
||||
private static IntPtr MeasureArray3DDataByteOffset()
|
||||
{
|
||||
var array = new T[1, 1, 1];
|
||||
|
||||
return array.DangerousGetObjectDataByteOffset(ref array[0, 0, 0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -25,7 +25,7 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers.Internals
|
|||
/// <returns>The number of occurrences of <paramref name="value"/> in the search space</returns>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static int Count<T>(ref T r0, IntPtr length, T value)
|
||||
public static nint Count<T>(ref T r0, nint length, T value)
|
||||
where T : IEquatable<T>
|
||||
{
|
||||
if (!Vector.IsHardwareAccelerated)
|
||||
|
@ -41,7 +41,7 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers.Internals
|
|||
ref sbyte r1 = ref Unsafe.As<T, sbyte>(ref r0);
|
||||
sbyte target = Unsafe.As<T, sbyte>(ref value);
|
||||
|
||||
return CountSimd(ref r1, length, target, (IntPtr)sbyte.MaxValue);
|
||||
return CountSimd(ref r1, length, target);
|
||||
}
|
||||
|
||||
if (typeof(T) == typeof(char) ||
|
||||
|
@ -51,7 +51,7 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers.Internals
|
|||
ref short r1 = ref Unsafe.As<T, short>(ref r0);
|
||||
short target = Unsafe.As<T, short>(ref value);
|
||||
|
||||
return CountSimd(ref r1, length, target, (IntPtr)short.MaxValue);
|
||||
return CountSimd(ref r1, length, target);
|
||||
}
|
||||
|
||||
if (typeof(T) == typeof(int) ||
|
||||
|
@ -60,7 +60,7 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers.Internals
|
|||
ref int r1 = ref Unsafe.As<T, int>(ref r0);
|
||||
int target = Unsafe.As<T, int>(ref value);
|
||||
|
||||
return CountSimd(ref r1, length, target, (IntPtr)int.MaxValue);
|
||||
return CountSimd(ref r1, length, target);
|
||||
}
|
||||
|
||||
if (typeof(T) == typeof(long) ||
|
||||
|
@ -69,7 +69,7 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers.Internals
|
|||
ref long r1 = ref Unsafe.As<T, long>(ref r0);
|
||||
long target = Unsafe.As<T, long>(ref value);
|
||||
|
||||
return CountSimd(ref r1, length, target, (IntPtr)int.MaxValue);
|
||||
return CountSimd(ref r1, length, target);
|
||||
}
|
||||
|
||||
return CountSequential(ref r0, length, value);
|
||||
|
@ -82,44 +82,44 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers.Internals
|
|||
#if NETCOREAPP3_1
|
||||
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
|
||||
#endif
|
||||
private static unsafe int CountSequential<T>(ref T r0, IntPtr length, T value)
|
||||
private static nint CountSequential<T>(ref T r0, nint length, T value)
|
||||
where T : IEquatable<T>
|
||||
{
|
||||
int result = 0;
|
||||
|
||||
IntPtr offset = default;
|
||||
nint
|
||||
result = 0,
|
||||
offset = 0;
|
||||
|
||||
// Main loop with 8 unrolled iterations
|
||||
while ((byte*)length >= (byte*)8)
|
||||
while (length >= 8)
|
||||
{
|
||||
result += Unsafe.Add(ref r0, offset + 0).Equals(value).ToInt();
|
||||
result += Unsafe.Add(ref r0, offset + 1).Equals(value).ToInt();
|
||||
result += Unsafe.Add(ref r0, offset + 2).Equals(value).ToInt();
|
||||
result += Unsafe.Add(ref r0, offset + 3).Equals(value).ToInt();
|
||||
result += Unsafe.Add(ref r0, offset + 4).Equals(value).ToInt();
|
||||
result += Unsafe.Add(ref r0, offset + 5).Equals(value).ToInt();
|
||||
result += Unsafe.Add(ref r0, offset + 6).Equals(value).ToInt();
|
||||
result += Unsafe.Add(ref r0, offset + 7).Equals(value).ToInt();
|
||||
result += Unsafe.Add(ref r0, offset + 0).Equals(value).ToByte();
|
||||
result += Unsafe.Add(ref r0, offset + 1).Equals(value).ToByte();
|
||||
result += Unsafe.Add(ref r0, offset + 2).Equals(value).ToByte();
|
||||
result += Unsafe.Add(ref r0, offset + 3).Equals(value).ToByte();
|
||||
result += Unsafe.Add(ref r0, offset + 4).Equals(value).ToByte();
|
||||
result += Unsafe.Add(ref r0, offset + 5).Equals(value).ToByte();
|
||||
result += Unsafe.Add(ref r0, offset + 6).Equals(value).ToByte();
|
||||
result += Unsafe.Add(ref r0, offset + 7).Equals(value).ToByte();
|
||||
|
||||
length -= 8;
|
||||
offset += 8;
|
||||
}
|
||||
|
||||
if ((byte*)length >= (byte*)4)
|
||||
if (length >= 4)
|
||||
{
|
||||
result += Unsafe.Add(ref r0, offset + 0).Equals(value).ToInt();
|
||||
result += Unsafe.Add(ref r0, offset + 1).Equals(value).ToInt();
|
||||
result += Unsafe.Add(ref r0, offset + 2).Equals(value).ToInt();
|
||||
result += Unsafe.Add(ref r0, offset + 3).Equals(value).ToInt();
|
||||
result += Unsafe.Add(ref r0, offset + 0).Equals(value).ToByte();
|
||||
result += Unsafe.Add(ref r0, offset + 1).Equals(value).ToByte();
|
||||
result += Unsafe.Add(ref r0, offset + 2).Equals(value).ToByte();
|
||||
result += Unsafe.Add(ref r0, offset + 3).Equals(value).ToByte();
|
||||
|
||||
length -= 4;
|
||||
offset += 4;
|
||||
}
|
||||
|
||||
// Iterate over the remaining values and count those that match
|
||||
while ((byte*)length > (byte*)0)
|
||||
while (length > 0)
|
||||
{
|
||||
result += Unsafe.Add(ref r0, offset).Equals(value).ToInt();
|
||||
result += Unsafe.Add(ref r0, offset).Equals(value).ToByte();
|
||||
|
||||
length -= 1;
|
||||
offset += 1;
|
||||
|
@ -135,15 +135,15 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers.Internals
|
|||
#if NETCOREAPP3_1
|
||||
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
|
||||
#endif
|
||||
private static unsafe int CountSimd<T>(ref T r0, IntPtr length, T value, IntPtr max)
|
||||
private static nint CountSimd<T>(ref T r0, nint length, T value)
|
||||
where T : unmanaged, IEquatable<T>
|
||||
{
|
||||
int result = 0;
|
||||
|
||||
IntPtr offset = default;
|
||||
nint
|
||||
result = 0,
|
||||
offset = 0;
|
||||
|
||||
// Skip the initialization overhead if there are not enough items
|
||||
if ((byte*)length >= (byte*)Vector<T>.Count)
|
||||
if (length >= Vector<T>.Count)
|
||||
{
|
||||
var vc = new Vector<T>(value);
|
||||
|
||||
|
@ -154,13 +154,14 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers.Internals
|
|||
// to sum the partial results. We also backup the current offset to
|
||||
// be able to track how many items have been processed, which lets
|
||||
// us avoid updating a third counter (length) in the loop body.
|
||||
IntPtr
|
||||
chunkLength = Min(length, max),
|
||||
nint
|
||||
max = GetUpperBound<T>(),
|
||||
chunkLength = length <= max ? length : max,
|
||||
initialOffset = offset;
|
||||
|
||||
var partials = Vector<T>.Zero;
|
||||
|
||||
while ((byte*)chunkLength >= (byte*)Vector<T>.Count)
|
||||
while (chunkLength >= Vector<T>.Count)
|
||||
{
|
||||
ref T ri = ref Unsafe.Add(ref r0, offset);
|
||||
|
||||
|
@ -181,27 +182,26 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers.Internals
|
|||
offset += Vector<T>.Count;
|
||||
}
|
||||
|
||||
result += CastToInt(Vector.Dot(partials, Vector<T>.One));
|
||||
|
||||
length = Subtract(length, Subtract(offset, initialOffset));
|
||||
result += CastToNativeInt(Vector.Dot(partials, Vector<T>.One));
|
||||
length -= offset - initialOffset;
|
||||
}
|
||||
while ((byte*)length >= (byte*)Vector<T>.Count);
|
||||
while (length >= Vector<T>.Count);
|
||||
}
|
||||
|
||||
// Optional 8 unrolled iterations. This is only done when a single SIMD
|
||||
// register can contain over 8 values of the current type, as otherwise
|
||||
// there could never be enough items left after the vectorized path
|
||||
if (Vector<T>.Count > 8 &&
|
||||
(byte*)length >= (byte*)8)
|
||||
length >= 8)
|
||||
{
|
||||
result += Unsafe.Add(ref r0, offset + 0).Equals(value).ToInt();
|
||||
result += Unsafe.Add(ref r0, offset + 1).Equals(value).ToInt();
|
||||
result += Unsafe.Add(ref r0, offset + 2).Equals(value).ToInt();
|
||||
result += Unsafe.Add(ref r0, offset + 3).Equals(value).ToInt();
|
||||
result += Unsafe.Add(ref r0, offset + 4).Equals(value).ToInt();
|
||||
result += Unsafe.Add(ref r0, offset + 5).Equals(value).ToInt();
|
||||
result += Unsafe.Add(ref r0, offset + 6).Equals(value).ToInt();
|
||||
result += Unsafe.Add(ref r0, offset + 7).Equals(value).ToInt();
|
||||
result += Unsafe.Add(ref r0, offset + 0).Equals(value).ToByte();
|
||||
result += Unsafe.Add(ref r0, offset + 1).Equals(value).ToByte();
|
||||
result += Unsafe.Add(ref r0, offset + 2).Equals(value).ToByte();
|
||||
result += Unsafe.Add(ref r0, offset + 3).Equals(value).ToByte();
|
||||
result += Unsafe.Add(ref r0, offset + 4).Equals(value).ToByte();
|
||||
result += Unsafe.Add(ref r0, offset + 5).Equals(value).ToByte();
|
||||
result += Unsafe.Add(ref r0, offset + 6).Equals(value).ToByte();
|
||||
result += Unsafe.Add(ref r0, offset + 7).Equals(value).ToByte();
|
||||
|
||||
length -= 8;
|
||||
offset += 8;
|
||||
|
@ -209,21 +209,21 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers.Internals
|
|||
|
||||
// Optional 4 unrolled iterations
|
||||
if (Vector<T>.Count > 4 &&
|
||||
(byte*)length >= (byte*)4)
|
||||
length >= 4)
|
||||
{
|
||||
result += Unsafe.Add(ref r0, offset + 0).Equals(value).ToInt();
|
||||
result += Unsafe.Add(ref r0, offset + 1).Equals(value).ToInt();
|
||||
result += Unsafe.Add(ref r0, offset + 2).Equals(value).ToInt();
|
||||
result += Unsafe.Add(ref r0, offset + 3).Equals(value).ToInt();
|
||||
result += Unsafe.Add(ref r0, offset + 0).Equals(value).ToByte();
|
||||
result += Unsafe.Add(ref r0, offset + 1).Equals(value).ToByte();
|
||||
result += Unsafe.Add(ref r0, offset + 2).Equals(value).ToByte();
|
||||
result += Unsafe.Add(ref r0, offset + 3).Equals(value).ToByte();
|
||||
|
||||
length -= 4;
|
||||
offset += 4;
|
||||
}
|
||||
|
||||
// Iterate over the remaining values and count those that match
|
||||
while ((byte*)length > (byte*)0)
|
||||
while (length > 0)
|
||||
{
|
||||
result += Unsafe.Add(ref r0, offset).Equals(value).ToInt();
|
||||
result += Unsafe.Add(ref r0, offset).Equals(value).ToByte();
|
||||
|
||||
length -= 1;
|
||||
offset += 1;
|
||||
|
@ -233,73 +233,88 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers.Internals
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the minimum between two <see cref="IntPtr"/> values.
|
||||
/// Gets the upper bound for partial sums with a given <typeparamref name="T"/> parameter.
|
||||
/// </summary>
|
||||
/// <param name="a">The first <see cref="IntPtr"/> value.</param>
|
||||
/// <param name="b">The second <see cref="IntPtr"/> value</param>
|
||||
/// <returns>The minimum between <paramref name="a"/> and <paramref name="b"/>.</returns>
|
||||
/// <typeparam name="T">The type argument currently in use.</typeparam>
|
||||
/// <returns>The native <see cref="int"/> value representing the upper bound.</returns>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static unsafe IntPtr Min(IntPtr a, IntPtr b)
|
||||
private static unsafe nint GetUpperBound<T>()
|
||||
where T : unmanaged
|
||||
{
|
||||
if (sizeof(IntPtr) == 4)
|
||||
if (typeof(T) == typeof(byte) ||
|
||||
typeof(T) == typeof(sbyte) ||
|
||||
typeof(T) == typeof(bool))
|
||||
{
|
||||
return (IntPtr)Math.Min((int)a, (int)b);
|
||||
return sbyte.MaxValue;
|
||||
}
|
||||
|
||||
return (IntPtr)Math.Min((long)a, (long)b);
|
||||
if (typeof(T) == typeof(char) ||
|
||||
typeof(T) == typeof(ushort) ||
|
||||
typeof(T) == typeof(short))
|
||||
{
|
||||
return short.MaxValue;
|
||||
}
|
||||
|
||||
if (typeof(T) == typeof(int) ||
|
||||
typeof(T) == typeof(uint))
|
||||
{
|
||||
return int.MaxValue;
|
||||
}
|
||||
|
||||
if (typeof(T) == typeof(long) ||
|
||||
typeof(T) == typeof(ulong))
|
||||
{
|
||||
if (sizeof(nint) == sizeof(int))
|
||||
{
|
||||
return int.MaxValue;
|
||||
}
|
||||
|
||||
// If we are on a 64 bit architecture and we are counting with a SIMD vector of 64
|
||||
// bit values, we can use long.MaxValue as the upper bound, as a native integer will
|
||||
// be able to contain such a value with no overflows. This will allow the count tight
|
||||
// loop to process all the items in the target area in a single pass (except the mod).
|
||||
// The (void*) cast is necessary to ensure the right constant is produced on runtimes
|
||||
// before .NET 5 that don't natively support C# 9. For instance, removing that (void*)
|
||||
// cast results in the value 0xFFFFFFFFFFFFFFFF (-1) instead of 0x7FFFFFFFFFFFFFFFF.
|
||||
return (nint)(void*)long.MaxValue;
|
||||
}
|
||||
|
||||
throw null!;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the difference between two <see cref="IntPtr"/> values.
|
||||
/// </summary>
|
||||
/// <param name="a">The first <see cref="IntPtr"/> value.</param>
|
||||
/// <param name="b">The second <see cref="IntPtr"/> value</param>
|
||||
/// <returns>The difference between <paramref name="a"/> and <paramref name="b"/>.</returns>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static unsafe IntPtr Subtract(IntPtr a, IntPtr b)
|
||||
{
|
||||
if (sizeof(IntPtr) == 4)
|
||||
{
|
||||
return (IntPtr)((int)a - (int)b);
|
||||
}
|
||||
|
||||
return (IntPtr)((long)a - (long)b);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Casts a value of a given type to <see cref="int"/>.
|
||||
/// Casts a value of a given type to a native <see cref="int"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The input type to cast.</typeparam>
|
||||
/// <param name="value">The input <typeparamref name="T"/> value to cast to <see cref="int"/>.</param>
|
||||
/// <returns>The <see cref="int"/> cast of <paramref name="value"/>.</returns>
|
||||
/// <param name="value">The input <typeparamref name="T"/> value to cast to native <see cref="int"/>.</param>
|
||||
/// <returns>The native <see cref="int"/> cast of <paramref name="value"/>.</returns>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static int CastToInt<T>(T value)
|
||||
private static nint CastToNativeInt<T>(T value)
|
||||
where T : unmanaged
|
||||
{
|
||||
if (typeof(T) == typeof(sbyte))
|
||||
{
|
||||
return Unsafe.As<T, sbyte>(ref value);
|
||||
return (byte)(sbyte)(object)value;
|
||||
}
|
||||
|
||||
if (typeof(T) == typeof(short))
|
||||
{
|
||||
return Unsafe.As<T, short>(ref value);
|
||||
return (ushort)(short)(object)value;
|
||||
}
|
||||
|
||||
if (typeof(T) == typeof(int))
|
||||
{
|
||||
return Unsafe.As<T, int>(ref value);
|
||||
return (nint)(uint)(int)(object)value;
|
||||
}
|
||||
|
||||
if (typeof(T) == typeof(long))
|
||||
{
|
||||
return (int)Unsafe.As<T, long>(ref value);
|
||||
return (nint)(ulong)(long)(object)value;
|
||||
}
|
||||
|
||||
throw new NotSupportedException($"Invalid input type {typeof(T)}");
|
||||
throw null!;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
@ -25,14 +24,13 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers.Internals
|
|||
#if NETCOREAPP3_1
|
||||
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
|
||||
#endif
|
||||
public static unsafe int GetDjb2HashCode<T>(ref T r0, IntPtr length)
|
||||
public static int GetDjb2HashCode<T>(ref T r0, nint length)
|
||||
where T : notnull
|
||||
{
|
||||
int hash = 5381;
|
||||
nint offset = 0;
|
||||
|
||||
IntPtr offset = default;
|
||||
|
||||
while ((byte*)length >= (byte*)8)
|
||||
while (length >= 8)
|
||||
{
|
||||
// Doing a left shift by 5 and adding is equivalent to multiplying by 33.
|
||||
// This is preferred for performance reasons, as when working with integer
|
||||
|
@ -52,7 +50,7 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers.Internals
|
|||
offset += 8;
|
||||
}
|
||||
|
||||
if ((byte*)length >= (byte*)4)
|
||||
if (length >= 4)
|
||||
{
|
||||
hash = unchecked(((hash << 5) + hash) ^ Unsafe.Add(ref r0, offset + 0).GetHashCode());
|
||||
hash = unchecked(((hash << 5) + hash) ^ Unsafe.Add(ref r0, offset + 1).GetHashCode());
|
||||
|
@ -63,7 +61,7 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers.Internals
|
|||
offset += 4;
|
||||
}
|
||||
|
||||
while ((byte*)length > (byte*)0)
|
||||
while (length > 0)
|
||||
{
|
||||
hash = unchecked(((hash << 5) + hash) ^ Unsafe.Add(ref r0, offset).GetHashCode());
|
||||
|
||||
|
@ -92,11 +90,10 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers.Internals
|
|||
#if NETCOREAPP3_1
|
||||
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
|
||||
#endif
|
||||
public static unsafe int GetDjb2LikeByteHash(ref byte r0, IntPtr length)
|
||||
public static unsafe int GetDjb2LikeByteHash(ref byte r0, nint length)
|
||||
{
|
||||
int hash = 5381;
|
||||
|
||||
IntPtr offset = default;
|
||||
nint offset = 0;
|
||||
|
||||
// Check whether SIMD instructions are supported, and also check
|
||||
// whether we have enough data to perform at least one unrolled
|
||||
|
@ -107,7 +104,7 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers.Internals
|
|||
// any preprocessing to try to get memory aligned, as that would cause
|
||||
// the hash codes to potentially be different for the same data.
|
||||
if (Vector.IsHardwareAccelerated &&
|
||||
(byte*)length >= (byte*)(Vector<byte>.Count << 3))
|
||||
length >= (Vector<byte>.Count << 3))
|
||||
{
|
||||
var vh = new Vector<int>(5381);
|
||||
var v33 = new Vector<int>(33);
|
||||
|
@ -115,7 +112,7 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers.Internals
|
|||
// First vectorized loop, with 8 unrolled iterations.
|
||||
// Assuming 256-bit registers (AVX2), a total of 256 bytes are processed
|
||||
// per iteration, with the partial hashes being accumulated for later use.
|
||||
while ((byte*)length >= (byte*)(Vector<byte>.Count << 3))
|
||||
while (length >= (Vector<byte>.Count << 3))
|
||||
{
|
||||
ref byte ri0 = ref Unsafe.Add(ref r0, offset + (Vector<byte>.Count * 0));
|
||||
var vi0 = Unsafe.ReadUnaligned<Vector<int>>(ref ri0);
|
||||
|
@ -163,7 +160,7 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers.Internals
|
|||
|
||||
// When this loop is reached, there are up to 255 bytes left (on AVX2).
|
||||
// Each iteration processed an additional 32 bytes and accumulates the results.
|
||||
while ((byte*)length >= (byte*)Vector<byte>.Count)
|
||||
while (length >= Vector<byte>.Count)
|
||||
{
|
||||
ref byte ri = ref Unsafe.Add(ref r0, offset);
|
||||
var vi = Unsafe.ReadUnaligned<Vector<int>>(ref ri);
|
||||
|
@ -186,9 +183,9 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers.Internals
|
|||
// Only use the loop working with 64-bit values if we are on a
|
||||
// 64-bit processor, otherwise the result would be much slower.
|
||||
// Each unrolled iteration processes 64 bytes.
|
||||
if (sizeof(IntPtr) == sizeof(ulong))
|
||||
if (sizeof(nint) == sizeof(ulong))
|
||||
{
|
||||
while ((byte*)length >= (byte*)(sizeof(ulong) << 3))
|
||||
while (length >= (sizeof(ulong) << 3))
|
||||
{
|
||||
ref byte ri0 = ref Unsafe.Add(ref r0, offset + (sizeof(ulong) * 0));
|
||||
var value0 = Unsafe.ReadUnaligned<ulong>(ref ri0);
|
||||
|
@ -228,7 +225,7 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers.Internals
|
|||
}
|
||||
|
||||
// Each unrolled iteration processes 32 bytes
|
||||
while ((byte*)length >= (byte*)(sizeof(uint) << 3))
|
||||
while (length >= (sizeof(uint) << 3))
|
||||
{
|
||||
ref byte ri0 = ref Unsafe.Add(ref r0, offset + (sizeof(uint) * 0));
|
||||
var value0 = Unsafe.ReadUnaligned<uint>(ref ri0);
|
||||
|
@ -271,7 +268,7 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers.Internals
|
|||
// left, both for the vectorized and non vectorized paths.
|
||||
// That number would go up to 63 on AVX512 systems, in which case it is
|
||||
// still useful to perform this last loop unrolling.
|
||||
if ((byte*)length >= (byte*)(sizeof(ushort) << 3))
|
||||
if (length >= (sizeof(ushort) << 3))
|
||||
{
|
||||
ref byte ri0 = ref Unsafe.Add(ref r0, offset + (sizeof(ushort) * 0));
|
||||
var value0 = Unsafe.ReadUnaligned<ushort>(ref ri0);
|
||||
|
@ -310,7 +307,7 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers.Internals
|
|||
}
|
||||
|
||||
// Handle the leftover items
|
||||
while ((byte*)length > (byte*)0)
|
||||
while (length > 0)
|
||||
{
|
||||
hash = unchecked(((hash << 5) + hash) ^ Unsafe.Add(ref r0, offset));
|
||||
|
||||
|
|
|
@ -218,7 +218,6 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers
|
|||
/// Processes the batch of actions at a specified index
|
||||
/// </summary>
|
||||
/// <param name="i">The index of the batch to process</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Invoke(int i)
|
||||
{
|
||||
int
|
||||
|
|
|
@ -312,7 +312,6 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers
|
|||
/// Processes the batch of actions at a specified index
|
||||
/// </summary>
|
||||
/// <param name="i">The index of the batch to process</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Invoke(int i)
|
||||
{
|
||||
int
|
||||
|
|
|
@ -135,8 +135,7 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers
|
|||
/// Processes the batch of actions at a specified index
|
||||
/// </summary>
|
||||
/// <param name="i">The index of the batch to process</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public unsafe void Invoke(int i)
|
||||
public void Invoke(int i)
|
||||
{
|
||||
int
|
||||
low = i * this.batchSize,
|
||||
|
@ -147,7 +146,7 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers
|
|||
|
||||
for (int j = low; j < end; j++)
|
||||
{
|
||||
ref TItem rj = ref Unsafe.Add(ref r0, (IntPtr)(void*)(uint)j);
|
||||
ref TItem rj = ref Unsafe.Add(ref r0, (nint)(uint)j);
|
||||
|
||||
Unsafe.AsRef(this.action).Invoke(rj);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,160 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Toolkit.HighPerformance.Memory;
|
||||
|
||||
namespace Microsoft.Toolkit.HighPerformance.Helpers
|
||||
{
|
||||
/// <summary>
|
||||
/// Helpers to work with parallel code in a highly optimized manner.
|
||||
/// </summary>
|
||||
public static partial class ParallelHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Executes a specified action in an optimized parallel loop over the input data.
|
||||
/// </summary>
|
||||
/// <typeparam name="TItem">The type of items to iterate over.</typeparam>
|
||||
/// <typeparam name="TAction">The type of action (implementing <see cref="IInAction{T}"/> of <typeparamref name="TItem"/>) to invoke over each item.</typeparam>
|
||||
/// <param name="memory">The input <see cref="ReadOnlyMemory2D{T}"/> representing the data to process.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void ForEach<TItem, TAction>(ReadOnlyMemory2D<TItem> memory)
|
||||
where TAction : struct, IInAction<TItem>
|
||||
{
|
||||
ForEach(memory, default(TAction), 1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes a specified action in an optimized parallel loop over the input data.
|
||||
/// </summary>
|
||||
/// <typeparam name="TItem">The type of items to iterate over.</typeparam>
|
||||
/// <typeparam name="TAction">The type of action (implementing <see cref="IInAction{T}"/> of <typeparamref name="TItem"/>) to invoke over each item.</typeparam>
|
||||
/// <param name="memory">The input <see cref="ReadOnlyMemory2D{T}"/> representing the data to process.</param>
|
||||
/// <param name="minimumActionsPerThread">
|
||||
/// The minimum number of actions to run per individual thread. Set to 1 if all invocations
|
||||
/// should be parallelized, or to a greater number if each individual invocation is fast
|
||||
/// enough that it is more efficient to set a lower bound per each running thread.
|
||||
/// </param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void ForEach<TItem, TAction>(ReadOnlyMemory2D<TItem> memory, int minimumActionsPerThread)
|
||||
where TAction : struct, IInAction<TItem>
|
||||
{
|
||||
ForEach(memory, default(TAction), minimumActionsPerThread);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes a specified action in an optimized parallel loop over the input data.
|
||||
/// </summary>
|
||||
/// <typeparam name="TItem">The type of items to iterate over.</typeparam>
|
||||
/// <typeparam name="TAction">The type of action (implementing <see cref="IInAction{T}"/> of <typeparamref name="TItem"/>) to invoke over each item.</typeparam>
|
||||
/// <param name="memory">The input <see cref="ReadOnlyMemory2D{T}"/> representing the data to process.</param>
|
||||
/// <param name="action">The <typeparamref name="TAction"/> instance representing the action to invoke.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void ForEach<TItem, TAction>(ReadOnlyMemory2D<TItem> memory, in TAction action)
|
||||
where TAction : struct, IInAction<TItem>
|
||||
{
|
||||
ForEach(memory, action, 1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes a specified action in an optimized parallel loop over the input data.
|
||||
/// </summary>
|
||||
/// <typeparam name="TItem">The type of items to iterate over.</typeparam>
|
||||
/// <typeparam name="TAction">The type of action (implementing <see cref="IInAction{T}"/> of <typeparamref name="TItem"/>) to invoke over each item.</typeparam>
|
||||
/// <param name="memory">The input <see cref="ReadOnlyMemory2D{T}"/> representing the data to process.</param>
|
||||
/// <param name="action">The <typeparamref name="TAction"/> instance representing the action to invoke.</param>
|
||||
/// <param name="minimumActionsPerThread">
|
||||
/// The minimum number of actions to run per individual thread. Set to 1 if all invocations
|
||||
/// should be parallelized, or to a greater number if each individual invocation is fast
|
||||
/// enough that it is more efficient to set a lower bound per each running thread.
|
||||
/// </param>
|
||||
public static void ForEach<TItem, TAction>(ReadOnlyMemory2D<TItem> memory, in TAction action, int minimumActionsPerThread)
|
||||
where TAction : struct, IInAction<TItem>
|
||||
{
|
||||
if (minimumActionsPerThread <= 0)
|
||||
{
|
||||
ThrowArgumentOutOfRangeExceptionForInvalidMinimumActionsPerThread();
|
||||
}
|
||||
|
||||
if (memory.IsEmpty)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
nint
|
||||
maxBatches = 1 + ((memory.Length - 1) / minimumActionsPerThread),
|
||||
clipBatches = maxBatches <= memory.Height ? maxBatches : memory.Height;
|
||||
int
|
||||
cores = Environment.ProcessorCount,
|
||||
numBatches = (int)(clipBatches <= cores ? clipBatches : cores),
|
||||
batchHeight = 1 + ((memory.Height - 1) / numBatches);
|
||||
|
||||
var actionInvoker = new InActionInvokerWithReadOnlyMemory2D<TItem, TAction>(batchHeight, memory, action);
|
||||
|
||||
// Skip the parallel invocation when possible
|
||||
if (numBatches == 1)
|
||||
{
|
||||
actionInvoker.Invoke(0);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Run the batched operations in parallel
|
||||
Parallel.For(
|
||||
0,
|
||||
numBatches,
|
||||
new ParallelOptions { MaxDegreeOfParallelism = numBatches },
|
||||
actionInvoker.Invoke);
|
||||
}
|
||||
|
||||
// Wrapping struct acting as explicit closure to execute the processing batches
|
||||
private readonly struct InActionInvokerWithReadOnlyMemory2D<TItem, TAction>
|
||||
where TAction : struct, IInAction<TItem>
|
||||
{
|
||||
private readonly int batchHeight;
|
||||
private readonly ReadOnlyMemory2D<TItem> memory;
|
||||
private readonly TAction action;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public InActionInvokerWithReadOnlyMemory2D(
|
||||
int batchHeight,
|
||||
ReadOnlyMemory2D<TItem> memory,
|
||||
in TAction action)
|
||||
{
|
||||
this.batchHeight = batchHeight;
|
||||
this.memory = memory;
|
||||
this.action = action;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processes the batch of actions at a specified index
|
||||
/// </summary>
|
||||
/// <param name="i">The index of the batch to process</param>
|
||||
public void Invoke(int i)
|
||||
{
|
||||
int lowY = i * this.batchHeight;
|
||||
nint highY = lowY + this.batchHeight;
|
||||
int
|
||||
stopY = (int)(highY <= this.memory.Height ? highY : this.memory.Height),
|
||||
width = this.memory.Width;
|
||||
|
||||
ReadOnlySpan2D<TItem> span = this.memory.Span;
|
||||
|
||||
for (int y = lowY; y < stopY; y++)
|
||||
{
|
||||
ref TItem r0 = ref span.DangerousGetReferenceAt(y, 0);
|
||||
|
||||
for (int x = 0; x < width; x++)
|
||||
{
|
||||
ref TItem ryx = ref Unsafe.Add(ref r0, (nint)(uint)x);
|
||||
|
||||
Unsafe.AsRef(this.action).Invoke(ryx);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -135,8 +135,7 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers
|
|||
/// Processes the batch of actions at a specified index
|
||||
/// </summary>
|
||||
/// <param name="i">The index of the batch to process</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public unsafe void Invoke(int i)
|
||||
public void Invoke(int i)
|
||||
{
|
||||
int
|
||||
low = i * this.batchSize,
|
||||
|
@ -147,7 +146,7 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers
|
|||
|
||||
for (int j = low; j < end; j++)
|
||||
{
|
||||
ref TItem rj = ref Unsafe.Add(ref r0, (IntPtr)(void*)(uint)j);
|
||||
ref TItem rj = ref Unsafe.Add(ref r0, (nint)(uint)j);
|
||||
|
||||
Unsafe.AsRef(this.action).Invoke(ref rj);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,167 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Toolkit.HighPerformance.Memory;
|
||||
|
||||
namespace Microsoft.Toolkit.HighPerformance.Helpers
|
||||
{
|
||||
/// <summary>
|
||||
/// Helpers to work with parallel code in a highly optimized manner.
|
||||
/// </summary>
|
||||
public static partial class ParallelHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Executes a specified action in an optimized parallel loop over the input data.
|
||||
/// </summary>
|
||||
/// <typeparam name="TItem">The type of items to iterate over.</typeparam>
|
||||
/// <typeparam name="TAction">The type of action (implementing <see cref="IRefAction{T}"/> of <typeparamref name="TItem"/>) to invoke over each item.</typeparam>
|
||||
/// <param name="memory">The input <see cref="Memory2D{T}"/> representing the data to process.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void ForEach<TItem, TAction>(Memory2D<TItem> memory)
|
||||
where TAction : struct, IRefAction<TItem>
|
||||
{
|
||||
ForEach(memory, default(TAction), 1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes a specified action in an optimized parallel loop over the input data.
|
||||
/// </summary>
|
||||
/// <typeparam name="TItem">The type of items to iterate over.</typeparam>
|
||||
/// <typeparam name="TAction">The type of action (implementing <see cref="IRefAction{T}"/> of <typeparamref name="TItem"/>) to invoke over each item.</typeparam>
|
||||
/// <param name="memory">The input <see cref="Memory2D{T}"/> representing the data to process.</param>
|
||||
/// <param name="minimumActionsPerThread">
|
||||
/// The minimum number of actions to run per individual thread. Set to 1 if all invocations
|
||||
/// should be parallelized, or to a greater number if each individual invocation is fast
|
||||
/// enough that it is more efficient to set a lower bound per each running thread.
|
||||
/// </param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void ForEach<TItem, TAction>(Memory2D<TItem> memory, int minimumActionsPerThread)
|
||||
where TAction : struct, IRefAction<TItem>
|
||||
{
|
||||
ForEach(memory, default(TAction), minimumActionsPerThread);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes a specified action in an optimized parallel loop over the input data.
|
||||
/// </summary>
|
||||
/// <typeparam name="TItem">The type of items to iterate over.</typeparam>
|
||||
/// <typeparam name="TAction">The type of action (implementing <see cref="IRefAction{T}"/> of <typeparamref name="TItem"/>) to invoke over each item.</typeparam>
|
||||
/// <param name="memory">The input <see cref="Memory2D{T}"/> representing the data to process.</param>
|
||||
/// <param name="action">The <typeparamref name="TAction"/> instance representing the action to invoke.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void ForEach<TItem, TAction>(Memory2D<TItem> memory, in TAction action)
|
||||
where TAction : struct, IRefAction<TItem>
|
||||
{
|
||||
ForEach(memory, action, 1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes a specified action in an optimized parallel loop over the input data.
|
||||
/// </summary>
|
||||
/// <typeparam name="TItem">The type of items to iterate over.</typeparam>
|
||||
/// <typeparam name="TAction">The type of action (implementing <see cref="IRefAction{T}"/> of <typeparamref name="TItem"/>) to invoke over each item.</typeparam>
|
||||
/// <param name="memory">The input <see cref="Memory2D{T}"/> representing the data to process.</param>
|
||||
/// <param name="action">The <typeparamref name="TAction"/> instance representing the action to invoke.</param>
|
||||
/// <param name="minimumActionsPerThread">
|
||||
/// The minimum number of actions to run per individual thread. Set to 1 if all invocations
|
||||
/// should be parallelized, or to a greater number if each individual invocation is fast
|
||||
/// enough that it is more efficient to set a lower bound per each running thread.
|
||||
/// </param>
|
||||
public static void ForEach<TItem, TAction>(Memory2D<TItem> memory, in TAction action, int minimumActionsPerThread)
|
||||
where TAction : struct, IRefAction<TItem>
|
||||
{
|
||||
if (minimumActionsPerThread <= 0)
|
||||
{
|
||||
ThrowArgumentOutOfRangeExceptionForInvalidMinimumActionsPerThread();
|
||||
}
|
||||
|
||||
if (memory.IsEmpty)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// The underlying data for a Memory2D<T> instance is bound to int.MaxValue in both
|
||||
// axes, but its total size can exceed this value. Because of this, we calculate
|
||||
// the target chunks as nint to avoid overflows, and switch back to int values
|
||||
// for the rest of the setup, since the number of batches is bound to the number
|
||||
// of CPU cores (which is an int), and the height of each batch is necessarily
|
||||
// smaller than or equal than int.MaxValue, as it can't be greater than the
|
||||
// number of total batches, which again is capped at the number of CPU cores.
|
||||
nint
|
||||
maxBatches = 1 + ((memory.Length - 1) / minimumActionsPerThread),
|
||||
clipBatches = maxBatches <= memory.Height ? maxBatches : memory.Height;
|
||||
int
|
||||
cores = Environment.ProcessorCount,
|
||||
numBatches = (int)(clipBatches <= cores ? clipBatches : cores),
|
||||
batchHeight = 1 + ((memory.Height - 1) / numBatches);
|
||||
|
||||
var actionInvoker = new RefActionInvokerWithReadOnlyMemory2D<TItem, TAction>(batchHeight, memory, action);
|
||||
|
||||
// Skip the parallel invocation when possible
|
||||
if (numBatches == 1)
|
||||
{
|
||||
actionInvoker.Invoke(0);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Run the batched operations in parallel
|
||||
Parallel.For(
|
||||
0,
|
||||
numBatches,
|
||||
new ParallelOptions { MaxDegreeOfParallelism = numBatches },
|
||||
actionInvoker.Invoke);
|
||||
}
|
||||
|
||||
// Wrapping struct acting as explicit closure to execute the processing batches
|
||||
private readonly struct RefActionInvokerWithReadOnlyMemory2D<TItem, TAction>
|
||||
where TAction : struct, IRefAction<TItem>
|
||||
{
|
||||
private readonly int batchHeight;
|
||||
private readonly Memory2D<TItem> memory;
|
||||
private readonly TAction action;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public RefActionInvokerWithReadOnlyMemory2D(
|
||||
int batchHeight,
|
||||
Memory2D<TItem> memory,
|
||||
in TAction action)
|
||||
{
|
||||
this.batchHeight = batchHeight;
|
||||
this.memory = memory;
|
||||
this.action = action;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processes the batch of actions at a specified index
|
||||
/// </summary>
|
||||
/// <param name="i">The index of the batch to process</param>
|
||||
public void Invoke(int i)
|
||||
{
|
||||
int lowY = i * this.batchHeight;
|
||||
nint highY = lowY + this.batchHeight;
|
||||
int
|
||||
stopY = (int)(highY <= this.memory.Height ? highY : this.memory.Height),
|
||||
width = this.memory.Width;
|
||||
|
||||
ReadOnlySpan2D<TItem> span = this.memory.Span;
|
||||
|
||||
for (int y = lowY; y < stopY; y++)
|
||||
{
|
||||
ref TItem r0 = ref span.DangerousGetReferenceAt(y, 0);
|
||||
|
||||
for (int x = 0; x < width; x++)
|
||||
{
|
||||
ref TItem ryx = ref Unsafe.Add(ref r0, (nint)(uint)x);
|
||||
|
||||
Unsafe.AsRef(this.action).Invoke(ref ryx);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Runtime.CompilerServices;
|
||||
using static System.Math;
|
||||
|
||||
namespace Microsoft.Toolkit.HighPerformance.Memory.Internals
|
||||
{
|
||||
/// <summary>
|
||||
/// A helper to validate arithmetic operations for <see cref="Memory2D{T}"/> and <see cref="Span2D{T}"/>.
|
||||
/// </summary>
|
||||
internal static class OverflowHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Ensures that the input parameters will not exceed the maximum native int value when indexing.
|
||||
/// </summary>
|
||||
/// <param name="height">The height of the 2D memory area to map.</param>
|
||||
/// <param name="width">The width of the 2D memory area to map.</param>
|
||||
/// <param name="pitch">The pitch of the 2D memory area to map (the distance between each row).</param>
|
||||
/// <exception cref="OverflowException">Throw when the inputs don't fit in the expected range.</exception>
|
||||
/// <remarks>The input parameters are assumed to always be positive.</remarks>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void EnsureIsInNativeIntRange(int height, int width, int pitch)
|
||||
{
|
||||
// As per the layout used in the Memory2D<T> and Span2D<T> types, we have the
|
||||
// following memory representation with respect to height, width and pitch:
|
||||
//
|
||||
// _________width_________ ________...
|
||||
// / \/
|
||||
// | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- |_
|
||||
// | -- | -- | XX | XX | XX | XX | XX | XX | -- | -- | |
|
||||
// | -- | -- | XX | XX | XX | XX | XX | XX | -- | -- | |
|
||||
// | -- | -- | XX | XX | XX | XX | XX | XX | -- | -- | |_height
|
||||
// | -- | -- | XX | XX | XX | XX | XX | XX | -- | -- |_|
|
||||
// | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- |
|
||||
// | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- |
|
||||
// ...__pitch__/
|
||||
//
|
||||
// The indexing logic works on nint values in unchecked mode, with no overflow checks,
|
||||
// which means it relies on the maximum element index to always be within <= nint.MaxValue.
|
||||
// To ensure no overflows will ever occur there, we need to ensure that no instance can be
|
||||
// created with parameters that could cause an overflow in case any item was accessed, so we
|
||||
// need to ensure no overflows occurs when calculating the index of the last item in each view.
|
||||
// The logic below calculates that index with overflow checks, throwing if one is detected.
|
||||
// Note that we're subtracting 1 to the height as we don't want to include the trailing pitch
|
||||
// for the 2D memory area, and also 1 to the width as the index is 0-based, as usual.
|
||||
// Additionally, we're also ensuring that the stride is never greater than int.MaxValue, for
|
||||
// consistency with how ND arrays work (int.MaxValue as upper bound for each axis), and to
|
||||
// allow for faster iteration in the RefEnumerable<T> type, when traversing columns.
|
||||
_ = checked(((nint)(width + pitch) * Max(unchecked(height - 1), 0)) + Max(unchecked(width - 1), 0));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensures that the input parameters will not exceed <see cref="int.MaxValue"/> when indexing.
|
||||
/// </summary>
|
||||
/// <param name="height">The height of the 2D memory area to map.</param>
|
||||
/// <param name="width">The width of the 2D memory area to map.</param>
|
||||
/// <param name="pitch">The pitch of the 2D memory area to map (the distance between each row).</param>
|
||||
/// <returns>The area resulting from the given parameters.</returns>
|
||||
/// <exception cref="OverflowException">Throw when the inputs don't fit in the expected range.</exception>
|
||||
/// <remarks>The input parameters are assumed to always be positive.</remarks>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static int ComputeInt32Area(int height, int width, int pitch)
|
||||
{
|
||||
return checked(((width + pitch) * Max(unchecked(height - 1), 0)) + width);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,122 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.Toolkit.HighPerformance.Memory.Internals
|
||||
{
|
||||
/// <summary>
|
||||
/// A helper class to throw exceptions for memory types.
|
||||
/// </summary>
|
||||
internal static class ThrowHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when using the <see langword="void"/>* constructor with a managed type.
|
||||
/// </summary>
|
||||
public static void ThrowArgumentExceptionForManagedType()
|
||||
{
|
||||
throw new ArgumentException("Can't use a void* constructor when T is a managed type");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when the target span is too short.
|
||||
/// </summary>
|
||||
public static void ThrowArgumentExceptionForDestinationTooShort()
|
||||
{
|
||||
throw new ArgumentException("The target span is too short to copy all the current items to");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArrayTypeMismatchException"/> when using an array of an invalid type.
|
||||
/// </summary>
|
||||
public static void ThrowArrayTypeMismatchException()
|
||||
{
|
||||
throw new ArrayTypeMismatchException("The given array doesn't match the specified type T");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when using an array of an invalid type.
|
||||
/// </summary>
|
||||
public static void ThrowArgumentExceptionForUnsupportedType()
|
||||
{
|
||||
throw new ArgumentException("The specified object type is not supported");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="IndexOutOfRangeException"/> when the a given coordinate is invalid.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Throwing <see cref="IndexOutOfRangeException"/> is technically discouraged in the docs, but
|
||||
/// we're doing that here for consistency with the official <see cref="Span{T}"/> type(s) from the BCL.
|
||||
/// </remarks>
|
||||
public static void ThrowIndexOutOfRangeException()
|
||||
{
|
||||
throw new IndexOutOfRangeException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentOutOfRangeException"/> when more than one parameter are invalid.
|
||||
/// </summary>
|
||||
public static void ThrowArgumentException()
|
||||
{
|
||||
throw new ArgumentException("One or more input parameters were invalid");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentOutOfRangeException"/> when the "depth" parameter is invalid.
|
||||
/// </summary>
|
||||
public static void ThrowArgumentOutOfRangeExceptionForDepth()
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("depth");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentOutOfRangeException"/> when the "row" parameter is invalid.
|
||||
/// </summary>
|
||||
public static void ThrowArgumentOutOfRangeExceptionForRow()
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("row");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentOutOfRangeException"/> when the "column" parameter is invalid.
|
||||
/// </summary>
|
||||
public static void ThrowArgumentOutOfRangeExceptionForColumn()
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("column");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentOutOfRangeException"/> when the "offset" parameter is invalid.
|
||||
/// </summary>
|
||||
public static void ThrowArgumentOutOfRangeExceptionForOffset()
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("offset");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentOutOfRangeException"/> when the "height" parameter is invalid.
|
||||
/// </summary>
|
||||
public static void ThrowArgumentOutOfRangeExceptionForHeight()
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("height");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentOutOfRangeException"/> when the "width" parameter is invalid.
|
||||
/// </summary>
|
||||
public static void ThrowArgumentOutOfRangeExceptionForWidth()
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("width");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentOutOfRangeException"/> when the "pitch" parameter is invalid.
|
||||
/// </summary>
|
||||
public static void ThrowArgumentOutOfRangeExceptionForPitch()
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("pitch");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,906 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
using Microsoft.Toolkit.HighPerformance.Buffers.Internals;
|
||||
#endif
|
||||
using Microsoft.Toolkit.HighPerformance.Extensions;
|
||||
using Microsoft.Toolkit.HighPerformance.Memory.Internals;
|
||||
using Microsoft.Toolkit.HighPerformance.Memory.Views;
|
||||
using static Microsoft.Toolkit.HighPerformance.Helpers.Internals.RuntimeHelpers;
|
||||
|
||||
namespace Microsoft.Toolkit.HighPerformance.Memory
|
||||
{
|
||||
/// <summary>
|
||||
/// <see cref="Memory2D{T}"/> represents a 2D region of arbitrary memory. It is to <see cref="Span2D{T}"/>
|
||||
/// what <see cref="Memory{T}"/> is to <see cref="Span{T}"/>. For further details on how the internal layout
|
||||
/// is structured, see the docs for <see cref="Span2D{T}"/>. The <see cref="Memory2D{T}"/> type can wrap arrays
|
||||
/// of any rank, provided that a valid series of parameters for the target memory area(s) are specified.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of items in the current <see cref="Memory2D{T}"/> instance.</typeparam>
|
||||
[DebuggerTypeProxy(typeof(MemoryDebugView2D<>))]
|
||||
[DebuggerDisplay("{ToString(),raw}")]
|
||||
public readonly struct Memory2D<T> : IEquatable<Memory2D<T>>
|
||||
{
|
||||
/// <summary>
|
||||
/// The target <see cref="object"/> instance, if present.
|
||||
/// </summary>
|
||||
private readonly object? instance;
|
||||
|
||||
/// <summary>
|
||||
/// The initial offset within <see cref="instance"/>.
|
||||
/// </summary>
|
||||
private readonly IntPtr offset;
|
||||
|
||||
/// <summary>
|
||||
/// The height of the specified 2D region.
|
||||
/// </summary>
|
||||
private readonly int height;
|
||||
|
||||
/// <summary>
|
||||
/// The width of the specified 2D region.
|
||||
/// </summary>
|
||||
private readonly int width;
|
||||
|
||||
/// <summary>
|
||||
/// The pitch of the specified 2D region.
|
||||
/// </summary>
|
||||
private readonly int pitch;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Memory2D{T}"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="array">The target array to wrap.</param>
|
||||
/// <param name="height">The height of the resulting 2D area.</param>
|
||||
/// <param name="width">The width of each row in the resulting 2D area.</param>
|
||||
/// <exception cref="ArrayTypeMismatchException">
|
||||
/// Thrown when <paramref name="array"/> doesn't match <typeparamref name="T"/>.
|
||||
/// </exception>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// Thrown when either <paramref name="height"/> or <paramref name="width"/> are invalid.
|
||||
/// </exception>
|
||||
/// <remarks>The total area must match the length of <paramref name="array"/>.</remarks>
|
||||
public Memory2D(T[] array, int height, int width)
|
||||
: this(array, 0, height, width, 0)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Memory2D{T}"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="array">The target array to wrap.</param>
|
||||
/// <param name="offset">The initial offset within <paramref name="array"/>.</param>
|
||||
/// <param name="height">The height of the resulting 2D area.</param>
|
||||
/// <param name="width">The width of each row in the resulting 2D area.</param>
|
||||
/// <param name="pitch">The pitch in the resulting 2D area.</param>
|
||||
/// <exception cref="ArrayTypeMismatchException">
|
||||
/// Thrown when <paramref name="array"/> doesn't match <typeparamref name="T"/>.
|
||||
/// </exception>
|
||||
/// <exception cref="ArgumentOutOfRangeException">
|
||||
/// Thrown when one of the input parameters is out of range.
|
||||
/// </exception>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// Thrown when the requested area is outside of bounds for <paramref name="array"/>.
|
||||
/// </exception>
|
||||
public Memory2D(T[] array, int offset, int height, int width, int pitch)
|
||||
{
|
||||
if (array.IsCovariant())
|
||||
{
|
||||
ThrowHelper.ThrowArrayTypeMismatchException();
|
||||
}
|
||||
|
||||
if ((uint)offset > (uint)array.Length)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForOffset();
|
||||
}
|
||||
|
||||
if (height < 0)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight();
|
||||
}
|
||||
|
||||
if (width < 0)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth();
|
||||
}
|
||||
|
||||
if (pitch < 0)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForPitch();
|
||||
}
|
||||
|
||||
int
|
||||
area = OverflowHelper.ComputeInt32Area(height, width, pitch),
|
||||
remaining = array.Length - offset;
|
||||
|
||||
if (area > remaining)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentException();
|
||||
}
|
||||
|
||||
this.instance = array;
|
||||
this.offset = array.DangerousGetObjectDataByteOffset(ref array.DangerousGetReferenceAt(offset));
|
||||
this.height = height;
|
||||
this.width = width;
|
||||
this.pitch = pitch;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Memory2D{T}"/> struct wrapping a 2D array.
|
||||
/// </summary>
|
||||
/// <param name="array">The given 2D array to wrap.</param>
|
||||
/// <exception cref="ArrayTypeMismatchException">
|
||||
/// Thrown when <paramref name="array"/> doesn't match <typeparamref name="T"/>.
|
||||
/// </exception>
|
||||
public Memory2D(T[,]? array)
|
||||
{
|
||||
if (array is null)
|
||||
{
|
||||
this = default;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (array.IsCovariant())
|
||||
{
|
||||
ThrowHelper.ThrowArrayTypeMismatchException();
|
||||
}
|
||||
|
||||
this.instance = array;
|
||||
this.offset = GetArray2DDataByteOffset<T>();
|
||||
this.height = array.GetLength(0);
|
||||
this.width = array.GetLength(1);
|
||||
this.pitch = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Memory2D{T}"/> struct wrapping a 2D array.
|
||||
/// </summary>
|
||||
/// <param name="array">The given 2D array to wrap.</param>
|
||||
/// <param name="row">The target row to map within <paramref name="array"/>.</param>
|
||||
/// <param name="column">The target column to map within <paramref name="array"/>.</param>
|
||||
/// <param name="height">The height to map within <paramref name="array"/>.</param>
|
||||
/// <param name="width">The width to map within <paramref name="array"/>.</param>
|
||||
/// <exception cref="ArrayTypeMismatchException">
|
||||
/// Thrown when <paramref name="array"/> doesn't match <typeparamref name="T"/>.
|
||||
/// </exception>
|
||||
/// <exception cref="ArgumentOutOfRangeException">
|
||||
/// Thrown when either <paramref name="height"/>, <paramref name="width"/> or <paramref name="height"/>
|
||||
/// are negative or not within the bounds that are valid for <paramref name="array"/>.
|
||||
/// </exception>
|
||||
public Memory2D(T[,]? array, int row, int column, int height, int width)
|
||||
{
|
||||
if (array is null)
|
||||
{
|
||||
if (row != 0 || column != 0 || height != 0 || width != 0)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentException();
|
||||
}
|
||||
|
||||
this = default;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (array.IsCovariant())
|
||||
{
|
||||
ThrowHelper.ThrowArrayTypeMismatchException();
|
||||
}
|
||||
|
||||
int
|
||||
rows = array.GetLength(0),
|
||||
columns = array.GetLength(1);
|
||||
|
||||
if ((uint)row >= (uint)rows)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForRow();
|
||||
}
|
||||
|
||||
if ((uint)column >= (uint)columns)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForColumn();
|
||||
}
|
||||
|
||||
if ((uint)height > (uint)(rows - row))
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight();
|
||||
}
|
||||
|
||||
if ((uint)width > (uint)(columns - column))
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth();
|
||||
}
|
||||
|
||||
this.instance = array;
|
||||
this.offset = array.DangerousGetObjectDataByteOffset(ref array.DangerousGetReferenceAt(row, column));
|
||||
this.height = height;
|
||||
this.width = width;
|
||||
this.pitch = columns - width;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Memory2D{T}"/> struct wrapping a layer in a 3D array.
|
||||
/// </summary>
|
||||
/// <param name="array">The given 3D array to wrap.</param>
|
||||
/// <param name="depth">The target layer to map within <paramref name="array"/>.</param>
|
||||
/// <exception cref="ArrayTypeMismatchException">
|
||||
/// Thrown when <paramref name="array"/> doesn't match <typeparamref name="T"/>.
|
||||
/// </exception>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when a parameter is invalid.</exception>
|
||||
public Memory2D(T[,,] array, int depth)
|
||||
{
|
||||
if (array.IsCovariant())
|
||||
{
|
||||
ThrowHelper.ThrowArrayTypeMismatchException();
|
||||
}
|
||||
|
||||
if ((uint)depth >= (uint)array.GetLength(0))
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForDepth();
|
||||
}
|
||||
|
||||
this.instance = array;
|
||||
this.offset = array.DangerousGetObjectDataByteOffset(ref array.DangerousGetReferenceAt(depth, 0, 0));
|
||||
this.height = array.GetLength(1);
|
||||
this.width = array.GetLength(2);
|
||||
this.pitch = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Memory2D{T}"/> struct wrapping a layer in a 3D array.
|
||||
/// </summary>
|
||||
/// <param name="array">The given 3D array to wrap.</param>
|
||||
/// <param name="depth">The target layer to map within <paramref name="array"/>.</param>
|
||||
/// <param name="row">The target row to map within <paramref name="array"/>.</param>
|
||||
/// <param name="column">The target column to map within <paramref name="array"/>.</param>
|
||||
/// <param name="height">The height to map within <paramref name="array"/>.</param>
|
||||
/// <param name="width">The width to map within <paramref name="array"/>.</param>
|
||||
/// <exception cref="ArrayTypeMismatchException">
|
||||
/// Thrown when <paramref name="array"/> doesn't match <typeparamref name="T"/>.
|
||||
/// </exception>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when a parameter is invalid.</exception>
|
||||
public Memory2D(T[,,] array, int depth, int row, int column, int height, int width)
|
||||
{
|
||||
if (array.IsCovariant())
|
||||
{
|
||||
ThrowHelper.ThrowArrayTypeMismatchException();
|
||||
}
|
||||
|
||||
if ((uint)depth >= (uint)array.GetLength(0))
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForDepth();
|
||||
}
|
||||
|
||||
int
|
||||
rows = array.GetLength(1),
|
||||
columns = array.GetLength(2);
|
||||
|
||||
if ((uint)row >= (uint)rows)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForRow();
|
||||
}
|
||||
|
||||
if ((uint)column >= (uint)columns)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForColumn();
|
||||
}
|
||||
|
||||
if ((uint)height > (uint)(rows - row))
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight();
|
||||
}
|
||||
|
||||
if ((uint)width > (uint)(columns - column))
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth();
|
||||
}
|
||||
|
||||
this.instance = array;
|
||||
this.offset = array.DangerousGetObjectDataByteOffset(ref array.DangerousGetReferenceAt(depth, row, column));
|
||||
this.height = height;
|
||||
this.width = width;
|
||||
this.pitch = columns - width;
|
||||
}
|
||||
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Memory2D{T}"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="memoryManager">The target <see cref="MemoryManager{T}"/> to wrap.</param>
|
||||
/// <param name="height">The height of the resulting 2D area.</param>
|
||||
/// <param name="width">The width of each row in the resulting 2D area.</param>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// Thrown when either <paramref name="height"/> or <paramref name="width"/> are invalid.
|
||||
/// </exception>
|
||||
/// <remarks>The total area must match the length of <paramref name="memoryManager"/>.</remarks>
|
||||
public Memory2D(MemoryManager<T> memoryManager, int height, int width)
|
||||
: this(memoryManager, 0, height, width, 0)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Memory2D{T}"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="memoryManager">The target <see cref="MemoryManager{T}"/> to wrap.</param>
|
||||
/// <param name="offset">The initial offset within <paramref name="memoryManager"/>.</param>
|
||||
/// <param name="height">The height of the resulting 2D area.</param>
|
||||
/// <param name="width">The width of each row in the resulting 2D area.</param>
|
||||
/// <param name="pitch">The pitch in the resulting 2D area.</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">
|
||||
/// Thrown when one of the input parameters is out of range.
|
||||
/// </exception>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// Thrown when the requested area is outside of bounds for <paramref name="memoryManager"/>.
|
||||
/// </exception>
|
||||
public Memory2D(MemoryManager<T> memoryManager, int offset, int height, int width, int pitch)
|
||||
{
|
||||
int length = memoryManager.GetSpan().Length;
|
||||
|
||||
if ((uint)offset > (uint)length)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForOffset();
|
||||
}
|
||||
|
||||
if (height < 0)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight();
|
||||
}
|
||||
|
||||
if (width < 0)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth();
|
||||
}
|
||||
|
||||
if (pitch < 0)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForPitch();
|
||||
}
|
||||
|
||||
if (width == 0 || height == 0)
|
||||
{
|
||||
this = default;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
int
|
||||
area = OverflowHelper.ComputeInt32Area(height, width, pitch),
|
||||
remaining = length - offset;
|
||||
|
||||
if (area > remaining)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentException();
|
||||
}
|
||||
|
||||
this.instance = memoryManager;
|
||||
this.offset = (nint)(uint)offset;
|
||||
this.height = height;
|
||||
this.width = width;
|
||||
this.pitch = pitch;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Memory2D{T}"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="memory">The target <see cref="Memory{T}"/> to wrap.</param>
|
||||
/// <param name="height">The height of the resulting 2D area.</param>
|
||||
/// <param name="width">The width of each row in the resulting 2D area.</param>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// Thrown when either <paramref name="height"/> or <paramref name="width"/> are invalid.
|
||||
/// </exception>
|
||||
/// <remarks>The total area must match the length of <paramref name="memory"/>.</remarks>
|
||||
internal Memory2D(Memory<T> memory, int height, int width)
|
||||
: this(memory, 0, height, width, 0)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Memory2D{T}"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="memory">The target <see cref="Memory{T}"/> to wrap.</param>
|
||||
/// <param name="offset">The initial offset within <paramref name="memory"/>.</param>
|
||||
/// <param name="height">The height of the resulting 2D area.</param>
|
||||
/// <param name="width">The width of each row in the resulting 2D area.</param>
|
||||
/// <param name="pitch">The pitch in the resulting 2D area.</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">
|
||||
/// Thrown when one of the input parameters is out of range.
|
||||
/// </exception>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// Thrown when the requested area is outside of bounds for <paramref name="memory"/>.
|
||||
/// </exception>
|
||||
internal Memory2D(Memory<T> memory, int offset, int height, int width, int pitch)
|
||||
{
|
||||
if ((uint)offset > (uint)memory.Length)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForOffset();
|
||||
}
|
||||
|
||||
if (height < 0)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight();
|
||||
}
|
||||
|
||||
if (width < 0)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth();
|
||||
}
|
||||
|
||||
if (pitch < 0)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForPitch();
|
||||
}
|
||||
|
||||
if (width == 0 || height == 0)
|
||||
{
|
||||
this = default;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
int
|
||||
area = OverflowHelper.ComputeInt32Area(height, width, pitch),
|
||||
remaining = memory.Length - offset;
|
||||
|
||||
if (area > remaining)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentException();
|
||||
}
|
||||
|
||||
// Check if the Memory<T> instance wraps a string. This is possible in case
|
||||
// consumers do an unsafe cast for the entire Memory<T> object, and while not
|
||||
// really safe it is still supported in CoreCLR too, so we're following suit here.
|
||||
if (typeof(T) == typeof(char) &&
|
||||
MemoryMarshal.TryGetString(Unsafe.As<Memory<T>, Memory<char>>(ref memory), out string? text, out int textStart, out _))
|
||||
{
|
||||
ref char r0 = ref text.DangerousGetReferenceAt(textStart + offset);
|
||||
|
||||
this.instance = text;
|
||||
this.offset = text.DangerousGetObjectDataByteOffset(ref r0);
|
||||
}
|
||||
else if (MemoryMarshal.TryGetArray(memory, out ArraySegment<T> segment))
|
||||
{
|
||||
// Check if the input Memory<T> instance wraps an array we can access.
|
||||
// This is fine, since Memory<T> on its own doesn't control the lifetime
|
||||
// of the underlying array anyway, and this Memory2D<T> type would do the same.
|
||||
// Using the array directly makes retrieving a Span2D<T> faster down the line,
|
||||
// as we no longer have to jump through the boxed Memory<T> first anymore.
|
||||
T[] array = segment.Array!;
|
||||
|
||||
this.instance = array;
|
||||
this.offset = array.DangerousGetObjectDataByteOffset(ref array.DangerousGetReferenceAt(segment.Offset + offset));
|
||||
}
|
||||
else if (MemoryMarshal.TryGetMemoryManager<T, MemoryManager<T>>(memory, out var memoryManager, out int memoryManagerStart, out _))
|
||||
{
|
||||
this.instance = memoryManager;
|
||||
this.offset = (nint)(uint)(memoryManagerStart + offset);
|
||||
}
|
||||
else
|
||||
{
|
||||
ThrowHelper.ThrowArgumentExceptionForUnsupportedType();
|
||||
|
||||
this.instance = null;
|
||||
this.offset = default;
|
||||
}
|
||||
|
||||
this.height = height;
|
||||
this.width = width;
|
||||
this.pitch = pitch;
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Memory2D{T}"/> struct with the specified parameters.
|
||||
/// </summary>
|
||||
/// <param name="instance">The target <see cref="object"/> instance.</param>
|
||||
/// <param name="offset">The initial offset within <see cref="instance"/>.</param>
|
||||
/// <param name="height">The height of the 2D memory area to map.</param>
|
||||
/// <param name="width">The width of the 2D memory area to map.</param>
|
||||
/// <param name="pitch">The pitch of the 2D memory area to map.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private Memory2D(object instance, IntPtr offset, int height, int width, int pitch)
|
||||
{
|
||||
this.instance = instance;
|
||||
this.offset = offset;
|
||||
this.height = height;
|
||||
this.width = width;
|
||||
this.pitch = pitch;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="Memory2D{T}"/> instance from an arbitrary object.
|
||||
/// </summary>
|
||||
/// <param name="instance">The <see cref="object"/> instance holding the data to map.</param>
|
||||
/// <param name="value">The target reference to point to (it must be within <paramref name="instance"/>).</param>
|
||||
/// <param name="height">The height of the 2D memory area to map.</param>
|
||||
/// <param name="width">The width of the 2D memory area to map.</param>
|
||||
/// <param name="pitch">The pitch of the 2D memory area to map.</param>
|
||||
/// <returns>A <see cref="Memory2D{T}"/> instance with the specified parameters.</returns>
|
||||
/// <remarks>The <paramref name="value"/> parameter is not validated, and it's responsability of the caller to ensure it's valid.</remarks>
|
||||
/// <exception cref="ArgumentOutOfRangeException">
|
||||
/// Thrown when one of the input parameters is out of range.
|
||||
/// </exception>
|
||||
[Pure]
|
||||
public static Memory2D<T> DangerousCreate(object instance, ref T value, int height, int width, int pitch)
|
||||
{
|
||||
if (height < 0)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight();
|
||||
}
|
||||
|
||||
if (width < 0)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth();
|
||||
}
|
||||
|
||||
if (pitch < 0)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForPitch();
|
||||
}
|
||||
|
||||
OverflowHelper.EnsureIsInNativeIntRange(height, width, pitch);
|
||||
|
||||
IntPtr offset = instance.DangerousGetObjectDataByteOffset(ref value);
|
||||
|
||||
return new Memory2D<T>(instance, offset, height, width, pitch);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an empty <see cref="Memory2D{T}"/> instance.
|
||||
/// </summary>
|
||||
public static Memory2D<T> Empty => default;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the current <see cref="Memory2D{T}"/> instance is empty.
|
||||
/// </summary>
|
||||
public bool IsEmpty
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => this.height == 0 || this.width == 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the length of the current <see cref="Memory2D{T}"/> instance.
|
||||
/// </summary>
|
||||
public nint Length
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => (nint)(uint)this.height * (nint)(uint)this.width;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the height of the underlying 2D memory area.
|
||||
/// </summary>
|
||||
public int Height
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => this.height;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the width of the underlying 2D memory area.
|
||||
/// </summary>
|
||||
public int Width
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => this.width;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a <see cref="Span2D{T}"/> instance from the current memory.
|
||||
/// </summary>
|
||||
public Span2D<T> Span
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get
|
||||
{
|
||||
if (!(this.instance is null))
|
||||
{
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
if (this.instance is MemoryManager<T> memoryManager)
|
||||
{
|
||||
ref T r0 = ref memoryManager.GetSpan().DangerousGetReference();
|
||||
ref T r1 = ref Unsafe.Add(ref r0, this.offset);
|
||||
|
||||
return new Span2D<T>(ref r1, this.height, this.width, this.pitch);
|
||||
}
|
||||
else
|
||||
{
|
||||
ref T r0 = ref this.instance.DangerousGetObjectDataReferenceAt<T>(this.offset);
|
||||
|
||||
return new Span2D<T>(ref r0, this.height, this.width, this.pitch);
|
||||
}
|
||||
#else
|
||||
return new Span2D<T>(this.instance, this.offset, this.height, this.width, this.pitch);
|
||||
#endif
|
||||
}
|
||||
|
||||
return default;
|
||||
}
|
||||
}
|
||||
|
||||
#if NETSTANDARD2_1_OR_GREATER
|
||||
/// <summary>
|
||||
/// Slices the current instance with the specified parameters.
|
||||
/// </summary>
|
||||
/// <param name="rows">The target range of rows to select.</param>
|
||||
/// <param name="columns">The target range of columns to select.</param>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// Thrown when either <paramref name="rows"/> or <paramref name="columns"/> are invalid.
|
||||
/// </exception>
|
||||
/// <returns>A new <see cref="Memory2D{T}"/> instance representing a slice of the current one.</returns>
|
||||
public Memory2D<T> this[Range rows, Range columns]
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get
|
||||
{
|
||||
var (row, height) = rows.GetOffsetAndLength(this.height);
|
||||
var (column, width) = columns.GetOffsetAndLength(this.width);
|
||||
|
||||
return Slice(row, column, height, width);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Slices the current instance with the specified parameters.
|
||||
/// </summary>
|
||||
/// <param name="row">The target row to map within the current instance.</param>
|
||||
/// <param name="column">The target column to map within the current instance.</param>
|
||||
/// <param name="height">The height to map within the current instance.</param>
|
||||
/// <param name="width">The width to map within the current instance.</param>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// Thrown when either <paramref name="height"/>, <paramref name="width"/> or <paramref name="height"/>
|
||||
/// are negative or not within the bounds that are valid for the current instance.
|
||||
/// </exception>
|
||||
/// <returns>A new <see cref="Memory2D{T}"/> instance representing a slice of the current one.</returns>
|
||||
[Pure]
|
||||
public Memory2D<T> Slice(int row, int column, int height, int width)
|
||||
{
|
||||
if ((uint)row >= Height)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForRow();
|
||||
}
|
||||
|
||||
if ((uint)column >= this.width)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForColumn();
|
||||
}
|
||||
|
||||
if ((uint)height > (Height - row))
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight();
|
||||
}
|
||||
|
||||
if ((uint)width > (this.width - column))
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth();
|
||||
}
|
||||
|
||||
int
|
||||
shift = ((this.width + this.pitch) * row) + column,
|
||||
pitch = this.pitch + (this.width - width);
|
||||
|
||||
IntPtr offset = this.offset + (shift * Unsafe.SizeOf<T>());
|
||||
|
||||
return new Memory2D<T>(this.instance!, offset, height, width, pitch);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies the contents of this <see cref="Memory2D{T}"/> into a destination <see cref="Memory{T}"/> instance.
|
||||
/// </summary>
|
||||
/// <param name="destination">The destination <see cref="Memory{T}"/> instance.</param>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// Thrown when <paramref name="destination" /> is shorter than the source <see cref="Memory2D{T}"/> instance.
|
||||
/// </exception>
|
||||
public void CopyTo(Memory<T> destination) => Span.CopyTo(destination.Span);
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to copy the current <see cref="Memory2D{T}"/> instance to a destination <see cref="Memory{T}"/>.
|
||||
/// </summary>
|
||||
/// <param name="destination">The target <see cref="Memory{T}"/> of the copy operation.</param>
|
||||
/// <returns>Whether or not the operation was successful.</returns>
|
||||
public bool TryCopyTo(Memory<T> destination) => Span.TryCopyTo(destination.Span);
|
||||
|
||||
/// <summary>
|
||||
/// Copies the contents of this <see cref="Memory2D{T}"/> into a destination <see cref="Memory2D{T}"/> instance.
|
||||
/// For this API to succeed, the target <see cref="Memory2D{T}"/> has to have the same shape as the current one.
|
||||
/// </summary>
|
||||
/// <param name="destination">The destination <see cref="Memory2D{T}"/> instance.</param>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// Thrown when <paramref name="destination" /> is shorter than the source <see cref="Memory2D{T}"/> instance.
|
||||
/// </exception>
|
||||
public void CopyTo(Memory2D<T> destination) => Span.CopyTo(destination.Span);
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to copy the current <see cref="Memory2D{T}"/> instance to a destination <see cref="Memory2D{T}"/>.
|
||||
/// For this API to succeed, the target <see cref="Memory2D{T}"/> has to have the same shape as the current one.
|
||||
/// </summary>
|
||||
/// <param name="destination">The target <see cref="Memory2D{T}"/> of the copy operation.</param>
|
||||
/// <returns>Whether or not the operation was successful.</returns>
|
||||
public bool TryCopyTo(Memory2D<T> destination) => Span.TryCopyTo(destination.Span);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a handle for the memory.
|
||||
/// The GC will not move the memory until the returned <see cref="MemoryHandle"/>
|
||||
/// is disposed, enabling taking and using the memory's address.
|
||||
/// </summary>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// An instance with nonprimitive (non-blittable) members cannot be pinned.
|
||||
/// </exception>
|
||||
/// <returns>A <see cref="MemoryHandle"/> instance wrapping the pinned handle.</returns>
|
||||
public unsafe MemoryHandle Pin()
|
||||
{
|
||||
if (!(this.instance is null))
|
||||
{
|
||||
if (this.instance is MemoryManager<T> memoryManager)
|
||||
{
|
||||
return memoryManager.Pin();
|
||||
}
|
||||
|
||||
GCHandle handle = GCHandle.Alloc(this.instance, GCHandleType.Pinned);
|
||||
|
||||
void* pointer = Unsafe.AsPointer(ref this.instance.DangerousGetObjectDataReferenceAt<T>(this.offset));
|
||||
|
||||
return new MemoryHandle(pointer, handle);
|
||||
}
|
||||
|
||||
return default;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to get a <see cref="Memory{T}"/> instance, if the underlying buffer is contiguous and small enough.
|
||||
/// </summary>
|
||||
/// <param name="memory">The resulting <see cref="Memory{T}"/>, in case of success.</param>
|
||||
/// <returns>Whether or not <paramref name="memory"/> was correctly assigned.</returns>
|
||||
public bool TryGetMemory(out Memory<T> memory)
|
||||
{
|
||||
if (this.pitch == 0 &&
|
||||
Length <= int.MaxValue)
|
||||
{
|
||||
// Empty Memory2D<T> instance
|
||||
if (this.instance is null)
|
||||
{
|
||||
memory = default;
|
||||
}
|
||||
else if (typeof(T) == typeof(char) && this.instance.GetType() == typeof(string))
|
||||
{
|
||||
string text = Unsafe.As<string>(this.instance);
|
||||
int index = text.AsSpan().IndexOf(in text.DangerousGetObjectDataReferenceAt<char>(this.offset));
|
||||
ReadOnlyMemory<char> temp = text.AsMemory(index, (int)Length);
|
||||
|
||||
// The string type could still be present if a user ends up creating a
|
||||
// Memory2D<T> instance from a string using DangerousCreate. Similarly to
|
||||
// how CoreCLR handles the equivalent case in Memory<T>, here we just do
|
||||
// the necessary steps to still retrieve a Memory<T> instance correctly
|
||||
// wrapping the target string. In this case, it is up to the caller
|
||||
// to make sure not to ever actually write to the resulting Memory<T>.
|
||||
memory = MemoryMarshal.AsMemory<T>(Unsafe.As<ReadOnlyMemory<char>, Memory<T>>(ref temp));
|
||||
}
|
||||
else if (this.instance is MemoryManager<T> memoryManager)
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
// If the object is a MemoryManager<T>, just slice it as needed
|
||||
memory = memoryManager.Memory.Slice((int)(void*)this.offset, this.height * this.width);
|
||||
}
|
||||
}
|
||||
else if (this.instance.GetType() == typeof(T[]))
|
||||
{
|
||||
// If it's a T[] array, also handle the initial offset
|
||||
T[] array = Unsafe.As<T[]>(this.instance);
|
||||
int index = array.AsSpan().IndexOf(ref array.DangerousGetObjectDataReferenceAt<T>(this.offset));
|
||||
|
||||
memory = array.AsMemory(index, this.height * this.width);
|
||||
}
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
else if (this.instance.GetType() == typeof(T[,]) ||
|
||||
this.instance.GetType() == typeof(T[,,]))
|
||||
{
|
||||
// If the object is a 2D or 3D array, we can create a Memory<T> from the RawObjectMemoryManager<T> type.
|
||||
// We just need to use the precomputed offset pointing to the first item in the current instance,
|
||||
// and the current usable length. We don't need to retrieve the current index, as the manager just offsets.
|
||||
memory = new RawObjectMemoryManager<T>(this.instance, this.offset, this.height * this.width).Memory;
|
||||
}
|
||||
#endif
|
||||
else
|
||||
{
|
||||
// Reuse a single failure path to reduce
|
||||
// the number of returns in the method
|
||||
goto Failure;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Failure:
|
||||
|
||||
memory = default;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies the contents of the current <see cref="Memory2D{T}"/> instance into a new 2D array.
|
||||
/// </summary>
|
||||
/// <returns>A 2D array containing the data in the current <see cref="Memory2D{T}"/> instance.</returns>
|
||||
[Pure]
|
||||
public T[,] ToArray() => Span.ToArray();
|
||||
|
||||
/// <inheritdoc/>
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
if (obj is Memory2D<T> memory)
|
||||
{
|
||||
return Equals(memory);
|
||||
}
|
||||
|
||||
if (obj is ReadOnlyMemory2D<T> readOnlyMemory)
|
||||
{
|
||||
return readOnlyMemory.Equals(this);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Equals(Memory2D<T> other)
|
||||
{
|
||||
return
|
||||
this.instance == other.instance &&
|
||||
this.offset == other.offset &&
|
||||
this.height == other.height &&
|
||||
this.width == other.width &&
|
||||
this.pitch == other.pitch;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public override int GetHashCode()
|
||||
{
|
||||
if (!(this.instance is null))
|
||||
{
|
||||
#if !NETSTANDARD1_4
|
||||
return HashCode.Combine(
|
||||
RuntimeHelpers.GetHashCode(this.instance),
|
||||
this.offset,
|
||||
this.height,
|
||||
this.width,
|
||||
this.pitch);
|
||||
#else
|
||||
Span<int> values = stackalloc int[]
|
||||
{
|
||||
RuntimeHelpers.GetHashCode(this.instance),
|
||||
this.offset.GetHashCode(),
|
||||
this.height,
|
||||
this.width,
|
||||
this.pitch
|
||||
};
|
||||
|
||||
return values.GetDjb2HashCode();
|
||||
#endif
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ToString()
|
||||
{
|
||||
return $"Microsoft.Toolkit.HighPerformance.Memory.Memory2D<{typeof(T)}>[{this.height}, {this.width}]";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines an implicit conversion of an array to a <see cref="Memory2D{T}"/>
|
||||
/// </summary>
|
||||
public static implicit operator Memory2D<T>(T[,]? array) => new Memory2D<T>(array);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,924 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
using Microsoft.Toolkit.HighPerformance.Buffers.Internals;
|
||||
#endif
|
||||
using Microsoft.Toolkit.HighPerformance.Extensions;
|
||||
using Microsoft.Toolkit.HighPerformance.Memory.Internals;
|
||||
using Microsoft.Toolkit.HighPerformance.Memory.Views;
|
||||
using static Microsoft.Toolkit.HighPerformance.Helpers.Internals.RuntimeHelpers;
|
||||
|
||||
namespace Microsoft.Toolkit.HighPerformance.Memory
|
||||
{
|
||||
/// <summary>
|
||||
/// A readonly version of <see cref="Memory2D{T}"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of items in the current <see cref="ReadOnlyMemory2D{T}"/> instance.</typeparam>
|
||||
[DebuggerTypeProxy(typeof(MemoryDebugView2D<>))]
|
||||
[DebuggerDisplay("{ToString(),raw}")]
|
||||
public readonly struct ReadOnlyMemory2D<T> : IEquatable<ReadOnlyMemory2D<T>>
|
||||
{
|
||||
/// <summary>
|
||||
/// The target <see cref="object"/> instance, if present.
|
||||
/// </summary>
|
||||
private readonly object? instance;
|
||||
|
||||
/// <summary>
|
||||
/// The initial offset within <see cref="instance"/>.
|
||||
/// </summary>
|
||||
private readonly IntPtr offset;
|
||||
|
||||
/// <summary>
|
||||
/// The height of the specified 2D region.
|
||||
/// </summary>
|
||||
private readonly int height;
|
||||
|
||||
/// <summary>
|
||||
/// The width of the specified 2D region.
|
||||
/// </summary>
|
||||
private readonly int width;
|
||||
|
||||
/// <summary>
|
||||
/// The pitch of the specified 2D region.
|
||||
/// </summary>
|
||||
private readonly int pitch;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ReadOnlyMemory2D{T}"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="text">The target <see cref="string"/> to wrap.</param>
|
||||
/// <param name="height">The height of the resulting 2D area.</param>
|
||||
/// <param name="width">The width of each row in the resulting 2D area.</param>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// Thrown when either <paramref name="height"/> or <paramref name="width"/> are invalid.
|
||||
/// </exception>
|
||||
/// <remarks>The total area must match the length of <paramref name="text"/>.</remarks>
|
||||
public ReadOnlyMemory2D(string text, int height, int width)
|
||||
: this(text, 0, height, width, 0)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ReadOnlyMemory2D{T}"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="text">The target <see cref="string"/> to wrap.</param>
|
||||
/// <param name="offset">The initial offset within <paramref name="text"/>.</param>
|
||||
/// <param name="height">The height of the resulting 2D area.</param>
|
||||
/// <param name="width">The width of each row in the resulting 2D area.</param>
|
||||
/// <param name="pitch">The pitch in the resulting 2D area.</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">
|
||||
/// Thrown when one of the input parameters is out of range.
|
||||
/// </exception>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// Thrown when the requested area is outside of bounds for <paramref name="text"/>.
|
||||
/// </exception>
|
||||
public ReadOnlyMemory2D(string text, int offset, int height, int width, int pitch)
|
||||
{
|
||||
if ((uint)offset > (uint)text.Length)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForOffset();
|
||||
}
|
||||
|
||||
if (height < 0)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight();
|
||||
}
|
||||
|
||||
if (width < 0)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth();
|
||||
}
|
||||
|
||||
if (pitch < 0)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForPitch();
|
||||
}
|
||||
|
||||
int
|
||||
area = OverflowHelper.ComputeInt32Area(height, width, pitch),
|
||||
remaining = text.Length - offset;
|
||||
|
||||
if (area > remaining)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentException();
|
||||
}
|
||||
|
||||
this.instance = text;
|
||||
this.offset = text.DangerousGetObjectDataByteOffset(ref text.DangerousGetReferenceAt(offset));
|
||||
this.height = height;
|
||||
this.width = width;
|
||||
this.pitch = pitch;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ReadOnlyMemory2D{T}"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="array">The target array to wrap.</param>
|
||||
/// <param name="height">The height of the resulting 2D area.</param>
|
||||
/// <param name="width">The width of each row in the resulting 2D area.</param>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// Thrown when either <paramref name="height"/> or <paramref name="width"/> are invalid.
|
||||
/// </exception>
|
||||
/// <remarks>The total area must match the length of <paramref name="array"/>.</remarks>
|
||||
public ReadOnlyMemory2D(T[] array, int height, int width)
|
||||
: this(array, 0, height, width, 0)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ReadOnlyMemory2D{T}"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="array">The target array to wrap.</param>
|
||||
/// <param name="offset">The initial offset within <paramref name="array"/>.</param>
|
||||
/// <param name="height">The height of the resulting 2D area.</param>
|
||||
/// <param name="width">The width of each row in the resulting 2D area.</param>
|
||||
/// <param name="pitch">The pitch in the resulting 2D area.</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">
|
||||
/// Thrown when one of the input parameters is out of range.
|
||||
/// </exception>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// Thrown when the requested area is outside of bounds for <paramref name="array"/>.
|
||||
/// </exception>
|
||||
public ReadOnlyMemory2D(T[] array, int offset, int height, int width, int pitch)
|
||||
{
|
||||
if ((uint)offset > (uint)array.Length)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForOffset();
|
||||
}
|
||||
|
||||
if (height < 0)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight();
|
||||
}
|
||||
|
||||
if (width < 0)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth();
|
||||
}
|
||||
|
||||
if (pitch < 0)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForPitch();
|
||||
}
|
||||
|
||||
int
|
||||
area = OverflowHelper.ComputeInt32Area(height, width, pitch),
|
||||
remaining = array.Length - offset;
|
||||
|
||||
if (area > remaining)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentException();
|
||||
}
|
||||
|
||||
this.instance = array;
|
||||
this.offset = array.DangerousGetObjectDataByteOffset(ref array.DangerousGetReferenceAt(offset));
|
||||
this.height = height;
|
||||
this.width = width;
|
||||
this.pitch = pitch;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ReadOnlyMemory2D{T}"/> struct wrapping a 2D array.
|
||||
/// </summary>
|
||||
/// <param name="array">The given 2D array to wrap.</param>
|
||||
public ReadOnlyMemory2D(T[,]? array)
|
||||
{
|
||||
if (array is null)
|
||||
{
|
||||
this = default;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.instance = array;
|
||||
this.offset = GetArray2DDataByteOffset<T>();
|
||||
this.height = array.GetLength(0);
|
||||
this.width = array.GetLength(1);
|
||||
this.pitch = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ReadOnlyMemory2D{T}"/> struct wrapping a 2D array.
|
||||
/// </summary>
|
||||
/// <param name="array">The given 2D array to wrap.</param>
|
||||
/// <param name="row">The target row to map within <paramref name="array"/>.</param>
|
||||
/// <param name="column">The target column to map within <paramref name="array"/>.</param>
|
||||
/// <param name="height">The height to map within <paramref name="array"/>.</param>
|
||||
/// <param name="width">The width to map within <paramref name="array"/>.</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">
|
||||
/// Thrown when either <paramref name="height"/>, <paramref name="width"/> or <paramref name="height"/>
|
||||
/// are negative or not within the bounds that are valid for <paramref name="array"/>.
|
||||
/// </exception>
|
||||
public ReadOnlyMemory2D(T[,]? array, int row, int column, int height, int width)
|
||||
{
|
||||
if (array is null)
|
||||
{
|
||||
if (row != 0 || column != 0 || height != 0 || width != 0)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentException();
|
||||
}
|
||||
|
||||
this = default;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
int
|
||||
rows = array.GetLength(0),
|
||||
columns = array.GetLength(1);
|
||||
|
||||
if ((uint)row >= (uint)rows)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForRow();
|
||||
}
|
||||
|
||||
if ((uint)column >= (uint)columns)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForColumn();
|
||||
}
|
||||
|
||||
if ((uint)height > (uint)(rows - row))
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight();
|
||||
}
|
||||
|
||||
if ((uint)width > (uint)(columns - column))
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth();
|
||||
}
|
||||
|
||||
this.instance = array;
|
||||
this.offset = array.DangerousGetObjectDataByteOffset(ref array.DangerousGetReferenceAt(row, column));
|
||||
this.height = height;
|
||||
this.width = width;
|
||||
this.pitch = columns - width;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ReadOnlyMemory2D{T}"/> struct wrapping a layer in a 3D array.
|
||||
/// </summary>
|
||||
/// <param name="array">The given 3D array to wrap.</param>
|
||||
/// <param name="depth">The target layer to map within <paramref name="array"/>.</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when a parameter is invalid.</exception>
|
||||
public ReadOnlyMemory2D(T[,,] array, int depth)
|
||||
{
|
||||
if ((uint)depth >= (uint)array.GetLength(0))
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForDepth();
|
||||
}
|
||||
|
||||
this.instance = array;
|
||||
this.offset = array.DangerousGetObjectDataByteOffset(ref array.DangerousGetReferenceAt(depth, 0, 0));
|
||||
this.height = array.GetLength(1);
|
||||
this.width = array.GetLength(2);
|
||||
this.pitch = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ReadOnlyMemory2D{T}"/> struct wrapping a layer in a 3D array.
|
||||
/// </summary>
|
||||
/// <param name="array">The given 3D array to wrap.</param>
|
||||
/// <param name="depth">The target layer to map within <paramref name="array"/>.</param>
|
||||
/// <param name="row">The target row to map within <paramref name="array"/>.</param>
|
||||
/// <param name="column">The target column to map within <paramref name="array"/>.</param>
|
||||
/// <param name="height">The height to map within <paramref name="array"/>.</param>
|
||||
/// <param name="width">The width to map within <paramref name="array"/>.</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when a parameter is invalid.</exception>
|
||||
public ReadOnlyMemory2D(T[,,] array, int depth, int row, int column, int height, int width)
|
||||
{
|
||||
if ((uint)depth >= (uint)array.GetLength(0))
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForDepth();
|
||||
}
|
||||
|
||||
int
|
||||
rows = array.GetLength(1),
|
||||
columns = array.GetLength(2);
|
||||
|
||||
if ((uint)row >= (uint)rows)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForRow();
|
||||
}
|
||||
|
||||
if ((uint)column >= (uint)columns)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForColumn();
|
||||
}
|
||||
|
||||
if ((uint)height > (uint)(rows - row))
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight();
|
||||
}
|
||||
|
||||
if ((uint)width > (uint)(columns - column))
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth();
|
||||
}
|
||||
|
||||
this.instance = array;
|
||||
this.offset = array.DangerousGetObjectDataByteOffset(ref array.DangerousGetReferenceAt(depth, row, column));
|
||||
this.height = height;
|
||||
this.width = width;
|
||||
this.pitch = columns - width;
|
||||
}
|
||||
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ReadOnlyMemory2D{T}"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="memoryManager">The target <see cref="MemoryManager{T}"/> to wrap.</param>
|
||||
/// <param name="height">The height of the resulting 2D area.</param>
|
||||
/// <param name="width">The width of each row in the resulting 2D area.</param>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// Thrown when either <paramref name="height"/> or <paramref name="width"/> are invalid.
|
||||
/// </exception>
|
||||
/// <remarks>The total area must match the length of <paramref name="memoryManager"/>.</remarks>
|
||||
public ReadOnlyMemory2D(MemoryManager<T> memoryManager, int height, int width)
|
||||
: this(memoryManager, 0, height, width, 0)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ReadOnlyMemory2D{T}"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="memoryManager">The target <see cref="MemoryManager{T}"/> to wrap.</param>
|
||||
/// <param name="offset">The initial offset within <paramref name="memoryManager"/>.</param>
|
||||
/// <param name="height">The height of the resulting 2D area.</param>
|
||||
/// <param name="width">The width of each row in the resulting 2D area.</param>
|
||||
/// <param name="pitch">The pitch in the resulting 2D area.</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">
|
||||
/// Thrown when one of the input parameters is out of range.
|
||||
/// </exception>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// Thrown when the requested area is outside of bounds for <paramref name="memoryManager"/>.
|
||||
/// </exception>
|
||||
public ReadOnlyMemory2D(MemoryManager<T> memoryManager, int offset, int height, int width, int pitch)
|
||||
{
|
||||
int length = memoryManager.GetSpan().Length;
|
||||
|
||||
if ((uint)offset > (uint)length)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForOffset();
|
||||
}
|
||||
|
||||
if (height < 0)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight();
|
||||
}
|
||||
|
||||
if (width < 0)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth();
|
||||
}
|
||||
|
||||
if (pitch < 0)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForPitch();
|
||||
}
|
||||
|
||||
if (width == 0 || height == 0)
|
||||
{
|
||||
this = default;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
int
|
||||
area = OverflowHelper.ComputeInt32Area(height, width, pitch),
|
||||
remaining = length - offset;
|
||||
|
||||
if (area > remaining)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentException();
|
||||
}
|
||||
|
||||
this.instance = memoryManager;
|
||||
this.offset = (nint)(uint)offset;
|
||||
this.height = height;
|
||||
this.width = width;
|
||||
this.pitch = pitch;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ReadOnlyMemory2D{T}"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="memory">The target <see cref="Memory{T}"/> to wrap.</param>
|
||||
/// <param name="height">The height of the resulting 2D area.</param>
|
||||
/// <param name="width">The width of each row in the resulting 2D area.</param>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// Thrown when either <paramref name="height"/> or <paramref name="width"/> are invalid.
|
||||
/// </exception>
|
||||
/// <remarks>The total area must match the length of <paramref name="memory"/>.</remarks>
|
||||
internal ReadOnlyMemory2D(ReadOnlyMemory<T> memory, int height, int width)
|
||||
: this(memory, 0, height, width, 0)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ReadOnlyMemory2D{T}"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="memory">The target <see cref="ReadOnlyMemory{T}"/> to wrap.</param>
|
||||
/// <param name="offset">The initial offset within <paramref name="memory"/>.</param>
|
||||
/// <param name="height">The height of the resulting 2D area.</param>
|
||||
/// <param name="width">The width of each row in the resulting 2D area.</param>
|
||||
/// <param name="pitch">The pitch in the resulting 2D area.</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">
|
||||
/// Thrown when one of the input parameters is out of range.
|
||||
/// </exception>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// Thrown when the requested area is outside of bounds for <paramref name="memory"/>.
|
||||
/// </exception>
|
||||
internal ReadOnlyMemory2D(ReadOnlyMemory<T> memory, int offset, int height, int width, int pitch)
|
||||
{
|
||||
if ((uint)offset > (uint)memory.Length)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForOffset();
|
||||
}
|
||||
|
||||
if (height < 0)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight();
|
||||
}
|
||||
|
||||
if (width < 0)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth();
|
||||
}
|
||||
|
||||
if (pitch < 0)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForPitch();
|
||||
}
|
||||
|
||||
if (width == 0 || height == 0)
|
||||
{
|
||||
this = default;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
int
|
||||
area = OverflowHelper.ComputeInt32Area(height, width, pitch),
|
||||
remaining = memory.Length - offset;
|
||||
|
||||
if (area > remaining)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentException();
|
||||
}
|
||||
|
||||
// Check the supported underlying objects, like in Memory2D<T>
|
||||
if (typeof(T) == typeof(char) &&
|
||||
MemoryMarshal.TryGetString(Unsafe.As<ReadOnlyMemory<T>, ReadOnlyMemory<char>>(ref memory), out string? text, out int textStart, out _))
|
||||
{
|
||||
ref char r0 = ref text.DangerousGetReferenceAt(textStart + offset);
|
||||
|
||||
this.instance = text;
|
||||
this.offset = text.DangerousGetObjectDataByteOffset(ref r0);
|
||||
}
|
||||
else if (MemoryMarshal.TryGetArray(memory, out ArraySegment<T> segment))
|
||||
{
|
||||
T[] array = segment.Array!;
|
||||
|
||||
this.instance = array;
|
||||
this.offset = array.DangerousGetObjectDataByteOffset(ref array.DangerousGetReferenceAt(segment.Offset + offset));
|
||||
}
|
||||
else if (MemoryMarshal.TryGetMemoryManager<T, MemoryManager<T>>(memory, out var memoryManager, out int memoryManagerStart, out _))
|
||||
{
|
||||
this.instance = memoryManager;
|
||||
this.offset = (nint)(uint)(memoryManagerStart + offset);
|
||||
}
|
||||
else
|
||||
{
|
||||
ThrowHelper.ThrowArgumentExceptionForUnsupportedType();
|
||||
|
||||
this.instance = null;
|
||||
this.offset = default;
|
||||
}
|
||||
|
||||
this.height = height;
|
||||
this.width = width;
|
||||
this.pitch = pitch;
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ReadOnlyMemory2D{T}"/> struct with the specified parameters.
|
||||
/// </summary>
|
||||
/// <param name="instance">The target <see cref="object"/> instance.</param>
|
||||
/// <param name="offset">The initial offset within <see cref="instance"/>.</param>
|
||||
/// <param name="height">The height of the 2D memory area to map.</param>
|
||||
/// <param name="width">The width of the 2D memory area to map.</param>
|
||||
/// <param name="pitch">The pitch of the 2D memory area to map.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private ReadOnlyMemory2D(object instance, IntPtr offset, int height, int width, int pitch)
|
||||
{
|
||||
this.instance = instance;
|
||||
this.offset = offset;
|
||||
this.height = height;
|
||||
this.width = width;
|
||||
this.pitch = pitch;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="ReadOnlyMemory2D{T}"/> instance from an arbitrary object.
|
||||
/// </summary>
|
||||
/// <param name="instance">The <see cref="object"/> instance holding the data to map.</param>
|
||||
/// <param name="value">The target reference to point to (it must be within <paramref name="instance"/>).</param>
|
||||
/// <param name="height">The height of the 2D memory area to map.</param>
|
||||
/// <param name="width">The width of the 2D memory area to map.</param>
|
||||
/// <param name="pitch">The pitch of the 2D memory area to map.</param>
|
||||
/// <returns>A <see cref="ReadOnlyMemory2D{T}"/> instance with the specified parameters.</returns>
|
||||
/// <remarks>The <paramref name="value"/> parameter is not validated, and it's responsability of the caller to ensure it's valid.</remarks>
|
||||
/// <exception cref="ArgumentOutOfRangeException">
|
||||
/// Thrown when one of the input parameters is out of range.
|
||||
/// </exception>
|
||||
[Pure]
|
||||
public static ReadOnlyMemory2D<T> DangerousCreate(object instance, ref T value, int height, int width, int pitch)
|
||||
{
|
||||
if (height < 0)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight();
|
||||
}
|
||||
|
||||
if (width < 0)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth();
|
||||
}
|
||||
|
||||
if (pitch < 0)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForPitch();
|
||||
}
|
||||
|
||||
OverflowHelper.EnsureIsInNativeIntRange(height, width, pitch);
|
||||
|
||||
IntPtr offset = instance.DangerousGetObjectDataByteOffset(ref value);
|
||||
|
||||
return new ReadOnlyMemory2D<T>(instance, offset, height, width, pitch);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an empty <see cref="ReadOnlyMemory2D{T}"/> instance.
|
||||
/// </summary>
|
||||
public static ReadOnlyMemory2D<T> Empty => default;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the current <see cref="ReadOnlyMemory2D{T}"/> instance is empty.
|
||||
/// </summary>
|
||||
public bool IsEmpty
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => this.height == 0 || this.width == 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the length of the current <see cref="ReadOnlyMemory2D{T}"/> instance.
|
||||
/// </summary>
|
||||
public nint Length
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => (nint)(uint)this.height * (nint)(uint)this.width;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the height of the underlying 2D memory area.
|
||||
/// </summary>
|
||||
public int Height
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => this.height;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the width of the underlying 2D memory area.
|
||||
/// </summary>
|
||||
public int Width
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => this.width;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a <see cref="ReadOnlySpan2D{T}"/> instance from the current memory.
|
||||
/// </summary>
|
||||
public ReadOnlySpan2D<T> Span
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get
|
||||
{
|
||||
if (!(this.instance is null))
|
||||
{
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
if (this.instance is MemoryManager<T> memoryManager)
|
||||
{
|
||||
ref T r0 = ref memoryManager.GetSpan().DangerousGetReference();
|
||||
ref T r1 = ref Unsafe.Add(ref r0, this.offset);
|
||||
|
||||
return new ReadOnlySpan2D<T>(r1, this.height, this.width, this.pitch);
|
||||
}
|
||||
else
|
||||
{
|
||||
// This handles both arrays and strings
|
||||
ref T r0 = ref this.instance.DangerousGetObjectDataReferenceAt<T>(this.offset);
|
||||
|
||||
return new ReadOnlySpan2D<T>(r0, this.height, this.width, this.pitch);
|
||||
}
|
||||
#else
|
||||
return new ReadOnlySpan2D<T>(this.instance, this.offset, this.height, this.width, this.pitch);
|
||||
#endif
|
||||
}
|
||||
|
||||
return default;
|
||||
}
|
||||
}
|
||||
|
||||
#if NETSTANDARD2_1_OR_GREATER
|
||||
/// <summary>
|
||||
/// Slices the current instance with the specified parameters.
|
||||
/// </summary>
|
||||
/// <param name="rows">The target range of rows to select.</param>
|
||||
/// <param name="columns">The target range of columns to select.</param>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// Thrown when either <paramref name="rows"/> or <paramref name="columns"/> are invalid.
|
||||
/// </exception>
|
||||
/// <returns>A new <see cref="ReadOnlyMemory2D{T}"/> instance representing a slice of the current one.</returns>
|
||||
public ReadOnlyMemory2D<T> this[Range rows, Range columns]
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get
|
||||
{
|
||||
var (row, height) = rows.GetOffsetAndLength(this.height);
|
||||
var (column, width) = columns.GetOffsetAndLength(this.width);
|
||||
|
||||
return Slice(row, column, height, width);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Slices the current instance with the specified parameters.
|
||||
/// </summary>
|
||||
/// <param name="row">The target row to map within the current instance.</param>
|
||||
/// <param name="column">The target column to map within the current instance.</param>
|
||||
/// <param name="height">The height to map within the current instance.</param>
|
||||
/// <param name="width">The width to map within the current instance.</param>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// Thrown when either <paramref name="height"/>, <paramref name="width"/> or <paramref name="height"/>
|
||||
/// are negative or not within the bounds that are valid for the current instance.
|
||||
/// </exception>
|
||||
/// <returns>A new <see cref="ReadOnlyMemory2D{T}"/> instance representing a slice of the current one.</returns>
|
||||
[Pure]
|
||||
public ReadOnlyMemory2D<T> Slice(int row, int column, int height, int width)
|
||||
{
|
||||
if ((uint)row >= Height)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForRow();
|
||||
}
|
||||
|
||||
if ((uint)column >= this.width)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForColumn();
|
||||
}
|
||||
|
||||
if ((uint)height > (Height - row))
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight();
|
||||
}
|
||||
|
||||
if ((uint)width > (this.width - column))
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth();
|
||||
}
|
||||
|
||||
int
|
||||
shift = ((this.width + this.pitch) * row) + column,
|
||||
pitch = this.pitch + (this.width - width);
|
||||
|
||||
IntPtr offset = this.offset + (shift * Unsafe.SizeOf<T>());
|
||||
|
||||
return new ReadOnlyMemory2D<T>(this.instance!, offset, height, width, pitch);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies the contents of this <see cref="ReadOnlyMemory2D{T}"/> into a destination <see cref="Memory{T}"/> instance.
|
||||
/// </summary>
|
||||
/// <param name="destination">The destination <see cref="Memory{T}"/> instance.</param>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// Thrown when <paramref name="destination" /> is shorter than the source <see cref="ReadOnlyMemory2D{T}"/> instance.
|
||||
/// </exception>
|
||||
public void CopyTo(Memory<T> destination) => Span.CopyTo(destination.Span);
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to copy the current <see cref="ReadOnlyMemory2D{T}"/> instance to a destination <see cref="Memory{T}"/>.
|
||||
/// </summary>
|
||||
/// <param name="destination">The target <see cref="Memory{T}"/> of the copy operation.</param>
|
||||
/// <returns>Whether or not the operation was successful.</returns>
|
||||
public bool TryCopyTo(Memory<T> destination) => Span.TryCopyTo(destination.Span);
|
||||
|
||||
/// <summary>
|
||||
/// Copies the contents of this <see cref="ReadOnlyMemory2D{T}"/> into a destination <see cref="Memory2D{T}"/> instance.
|
||||
/// For this API to succeed, the target <see cref="Memory2D{T}"/> has to have the same shape as the current one.
|
||||
/// </summary>
|
||||
/// <param name="destination">The destination <see cref="Memory2D{T}"/> instance.</param>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// Thrown when <paramref name="destination" /> is shorter than the source <see cref="ReadOnlyMemory2D{T}"/> instance.
|
||||
/// </exception>
|
||||
public void CopyTo(Memory2D<T> destination) => Span.CopyTo(destination.Span);
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to copy the current <see cref="ReadOnlyMemory2D{T}"/> instance to a destination <see cref="Memory2D{T}"/>.
|
||||
/// For this API to succeed, the target <see cref="Memory2D{T}"/> has to have the same shape as the current one.
|
||||
/// </summary>
|
||||
/// <param name="destination">The target <see cref="Memory2D{T}"/> of the copy operation.</param>
|
||||
/// <returns>Whether or not the operation was successful.</returns>
|
||||
public bool TryCopyTo(Memory2D<T> destination) => Span.TryCopyTo(destination.Span);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a handle for the memory.
|
||||
/// The GC will not move the memory until the returned <see cref="MemoryHandle"/>
|
||||
/// is disposed, enabling taking and using the memory's address.
|
||||
/// </summary>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// An instance with nonprimitive (non-blittable) members cannot be pinned.
|
||||
/// </exception>
|
||||
/// <returns>A <see cref="MemoryHandle"/> instance wrapping the pinned handle.</returns>
|
||||
public unsafe MemoryHandle Pin()
|
||||
{
|
||||
if (!(this.instance is null))
|
||||
{
|
||||
if (this.instance is MemoryManager<T> memoryManager)
|
||||
{
|
||||
return memoryManager.Pin();
|
||||
}
|
||||
|
||||
GCHandle handle = GCHandle.Alloc(this.instance, GCHandleType.Pinned);
|
||||
|
||||
void* pointer = Unsafe.AsPointer(ref this.instance.DangerousGetObjectDataReferenceAt<T>(this.offset));
|
||||
|
||||
return new MemoryHandle(pointer, handle);
|
||||
}
|
||||
|
||||
return default;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to get a <see cref="ReadOnlyMemory{T}"/> instance, if the underlying buffer is contiguous and small enough.
|
||||
/// </summary>
|
||||
/// <param name="memory">The resulting <see cref="ReadOnlyMemory{T}"/>, in case of success.</param>
|
||||
/// <returns>Whether or not <paramref name="memory"/> was correctly assigned.</returns>
|
||||
public bool TryGetMemory(out ReadOnlyMemory<T> memory)
|
||||
{
|
||||
if (this.pitch == 0 &&
|
||||
Length <= int.MaxValue)
|
||||
{
|
||||
// Empty Memory2D<T> instance
|
||||
if (this.instance is null)
|
||||
{
|
||||
memory = default;
|
||||
}
|
||||
else if (typeof(T) == typeof(char) && this.instance.GetType() == typeof(string))
|
||||
{
|
||||
// Here we need to create a Memory<char> from the wrapped string, and to do so we need to do an inverse
|
||||
// lookup to find the initial index of the string with respect to the byte offset we're currently using,
|
||||
// which refers to the raw string object data. This can include variable padding or other additional
|
||||
// fields on different runtimes. The lookup operation is still O(1) and just computes the byte offset
|
||||
// difference between the start of the Span<char> (which directly wraps just the actual character data
|
||||
// within the string), and the input reference, which we can get from the byte offset in use. The result
|
||||
// is the character index which we can use to create the final Memory<char> instance.
|
||||
string text = Unsafe.As<string>(this.instance);
|
||||
int index = text.AsSpan().IndexOf(in text.DangerousGetObjectDataReferenceAt<char>(this.offset));
|
||||
ReadOnlyMemory<char> temp = text.AsMemory(index, (int)Length);
|
||||
|
||||
memory = Unsafe.As<ReadOnlyMemory<char>, ReadOnlyMemory<T>>(ref temp);
|
||||
}
|
||||
else if (this.instance is MemoryManager<T> memoryManager)
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
// If the object is a MemoryManager<T>, just slice it as needed
|
||||
memory = memoryManager.Memory.Slice((int)(void*)this.offset, this.height * this.width);
|
||||
}
|
||||
}
|
||||
else if (this.instance.GetType() == typeof(T[]))
|
||||
{
|
||||
// If it's a T[] array, also handle the initial offset
|
||||
T[] array = Unsafe.As<T[]>(this.instance);
|
||||
int index = array.AsSpan().IndexOf(ref array.DangerousGetObjectDataReferenceAt<T>(this.offset));
|
||||
|
||||
memory = array.AsMemory(index, this.height * this.width);
|
||||
}
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
else if (this.instance.GetType() == typeof(T[,]) ||
|
||||
this.instance.GetType() == typeof(T[,,]))
|
||||
{
|
||||
memory = new RawObjectMemoryManager<T>(this.instance, this.offset, this.height * this.width).Memory;
|
||||
}
|
||||
#endif
|
||||
else
|
||||
{
|
||||
// Reuse a single failure path to reduce
|
||||
// the number of returns in the method
|
||||
goto Failure;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Failure:
|
||||
|
||||
memory = default;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies the contents of the current <see cref="ReadOnlyMemory2D{T}"/> instance into a new 2D array.
|
||||
/// </summary>
|
||||
/// <returns>A 2D array containing the data in the current <see cref="ReadOnlyMemory2D{T}"/> instance.</returns>
|
||||
[Pure]
|
||||
public T[,] ToArray() => Span.ToArray();
|
||||
|
||||
/// <inheritdoc/>
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
if (obj is ReadOnlyMemory2D<T> readOnlyMemory)
|
||||
{
|
||||
return Equals(readOnlyMemory);
|
||||
}
|
||||
|
||||
if (obj is Memory2D<T> memory)
|
||||
{
|
||||
return Equals(memory);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Equals(ReadOnlyMemory2D<T> other)
|
||||
{
|
||||
return
|
||||
this.instance == other.instance &&
|
||||
this.offset == other.offset &&
|
||||
this.height == other.height &&
|
||||
this.width == other.width &&
|
||||
this.pitch == other.pitch;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public override int GetHashCode()
|
||||
{
|
||||
if (!(this.instance is null))
|
||||
{
|
||||
#if !NETSTANDARD1_4
|
||||
return HashCode.Combine(
|
||||
RuntimeHelpers.GetHashCode(this.instance),
|
||||
this.offset,
|
||||
this.height,
|
||||
this.width,
|
||||
this.pitch);
|
||||
#else
|
||||
Span<int> values = stackalloc int[]
|
||||
{
|
||||
RuntimeHelpers.GetHashCode(this.instance),
|
||||
this.offset.GetHashCode(),
|
||||
this.height,
|
||||
this.width,
|
||||
this.pitch
|
||||
};
|
||||
|
||||
return values.GetDjb2HashCode();
|
||||
#endif
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ToString()
|
||||
{
|
||||
return $"Microsoft.Toolkit.HighPerformance.Memory.ReadOnlyMemory2D<{typeof(T)}>[{this.height}, {this.width}]";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines an implicit conversion of an array to a <see cref="ReadOnlyMemory2D{T}"/>
|
||||
/// </summary>
|
||||
public static implicit operator ReadOnlyMemory2D<T>(T[,]? array) => new ReadOnlyMemory2D<T>(array);
|
||||
|
||||
/// <summary>
|
||||
/// Defines an implicit conversion of a <see cref="Memory2D{T}"/> to a <see cref="ReadOnlyMemory2D{T}"/>
|
||||
/// </summary>
|
||||
public static implicit operator ReadOnlyMemory2D<T>(Memory2D<T> memory) => Unsafe.As<Memory2D<T>, ReadOnlyMemory2D<T>>(ref memory);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,201 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Microsoft.Toolkit.HighPerformance.Enumerables;
|
||||
using Microsoft.Toolkit.HighPerformance.Memory.Internals;
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
using System.Runtime.InteropServices;
|
||||
#else
|
||||
using RuntimeHelpers = Microsoft.Toolkit.HighPerformance.Helpers.Internals.RuntimeHelpers;
|
||||
#endif
|
||||
|
||||
namespace Microsoft.Toolkit.HighPerformance.Memory
|
||||
{
|
||||
/// <inheritdoc cref="ReadOnlySpan2D{T}"/>
|
||||
public readonly ref partial struct ReadOnlySpan2D<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets an enumerable that traverses items in a specified row.
|
||||
/// </summary>
|
||||
/// <param name="row">The target row to enumerate within the current <see cref="ReadOnlySpan2D{T}"/> instance.</param>
|
||||
/// <returns>A <see cref="ReadOnlyRefEnumerable{T}"/> with target items to enumerate.</returns>
|
||||
/// <remarks>The returned <see cref="ReadOnlyRefEnumerable{T}"/> value shouldn't be used directly: use this extension in a <see langword="foreach"/> loop.</remarks>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public ReadOnlyRefEnumerable<T> GetRow(int row)
|
||||
{
|
||||
if ((uint)row >= Height)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForRow();
|
||||
}
|
||||
|
||||
nint startIndex = (nint)(uint)this.stride * (nint)(uint)row;
|
||||
ref T r0 = ref DangerousGetReference();
|
||||
ref T r1 = ref Unsafe.Add(ref r0, startIndex);
|
||||
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
return new ReadOnlyRefEnumerable<T>(r1, Width, 1);
|
||||
#else
|
||||
IntPtr offset = RuntimeHelpers.GetObjectDataOrReferenceByteOffset(this.instance, ref r1);
|
||||
|
||||
return new ReadOnlyRefEnumerable<T>(this.instance!, offset, this.width, 1);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an enumerable that traverses items in a specified column.
|
||||
/// </summary>
|
||||
/// <param name="column">The target column to enumerate within the current <see cref="ReadOnlySpan2D{T}"/> instance.</param>
|
||||
/// <returns>A <see cref="ReadOnlyRefEnumerable{T}"/> with target items to enumerate.</returns>
|
||||
/// <remarks>The returned <see cref="ReadOnlyRefEnumerable{T}"/> value shouldn't be used directly: use this extension in a <see langword="foreach"/> loop.</remarks>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public ReadOnlyRefEnumerable<T> GetColumn(int column)
|
||||
{
|
||||
if ((uint)column >= Width)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForColumn();
|
||||
}
|
||||
|
||||
ref T r0 = ref DangerousGetReference();
|
||||
ref T r1 = ref Unsafe.Add(ref r0, (nint)(uint)column);
|
||||
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
return new ReadOnlyRefEnumerable<T>(r1, Height, this.stride);
|
||||
#else
|
||||
IntPtr offset = RuntimeHelpers.GetObjectDataOrReferenceByteOffset(this.instance, ref r1);
|
||||
|
||||
return new ReadOnlyRefEnumerable<T>(this.instance!, offset, Height, this.stride);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an enumerator for the current <see cref="ReadOnlySpan2D{T}"/> instance.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// An enumerator that can be used to traverse the items in the current <see cref="ReadOnlySpan2D{T}"/> instance
|
||||
/// </returns>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Enumerator GetEnumerator() => new Enumerator(this);
|
||||
|
||||
/// <summary>
|
||||
/// Provides an enumerator for the elements of a <see cref="ReadOnlySpan2D{T}"/> instance.
|
||||
/// </summary>
|
||||
public ref struct Enumerator
|
||||
{
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
/// <summary>
|
||||
/// The <see cref="ReadOnlySpan{T}"/> instance pointing to the first item in the target memory area.
|
||||
/// </summary>
|
||||
/// <remarks>Just like in <see cref="ReadOnlySpan2D{T}"/>, the length is the height of the 2D region.</remarks>
|
||||
private readonly ReadOnlySpan<T> span;
|
||||
#else
|
||||
/// <summary>
|
||||
/// The target <see cref="object"/> instance, if present.
|
||||
/// </summary>
|
||||
private readonly object? instance;
|
||||
|
||||
/// <summary>
|
||||
/// The initial offset within <see cref="instance"/>.
|
||||
/// </summary>
|
||||
private readonly IntPtr offset;
|
||||
|
||||
/// <summary>
|
||||
/// The height of the specified 2D region.
|
||||
/// </summary>
|
||||
private readonly int height;
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// The width of the specified 2D region.
|
||||
/// </summary>
|
||||
private readonly int width;
|
||||
|
||||
/// <summary>
|
||||
/// The stride of the specified 2D region.
|
||||
/// </summary>
|
||||
private readonly int stride;
|
||||
|
||||
/// <summary>
|
||||
/// The current horizontal offset.
|
||||
/// </summary>
|
||||
private int x;
|
||||
|
||||
/// <summary>
|
||||
/// The current vertical offset.
|
||||
/// </summary>
|
||||
private int y;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Enumerator"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="span">The target <see cref="ReadOnlySpan2D{T}"/> instance to enumerate.</param>
|
||||
internal Enumerator(ReadOnlySpan2D<T> span)
|
||||
{
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
this.span = span.span;
|
||||
#else
|
||||
this.instance = span.instance;
|
||||
this.offset = span.offset;
|
||||
this.height = span.height;
|
||||
#endif
|
||||
this.width = span.width;
|
||||
this.stride = span.stride;
|
||||
this.x = -1;
|
||||
this.y = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implements the duck-typed <see cref="System.Collections.IEnumerator.MoveNext"/> method.
|
||||
/// </summary>
|
||||
/// <returns><see langword="true"/> whether a new element is available, <see langword="false"/> otherwise</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool MoveNext()
|
||||
{
|
||||
int x = this.x + 1;
|
||||
|
||||
// Horizontal move, within range
|
||||
if (x < this.width)
|
||||
{
|
||||
this.x = x;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// We reached the end of a row and there is at least
|
||||
// another row available: wrap to a new line and continue.
|
||||
this.x = 0;
|
||||
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
return ++this.y < this.span.Length;
|
||||
#else
|
||||
return ++this.y < this.height;
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the duck-typed <see cref="System.Collections.Generic.IEnumerator{T}.Current"/> property.
|
||||
/// </summary>
|
||||
public readonly ref readonly T Current
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get
|
||||
{
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
ref T r0 = ref MemoryMarshal.GetReference(this.span);
|
||||
#else
|
||||
ref T r0 = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference<T>(this.instance, this.offset);
|
||||
#endif
|
||||
nint index = ((nint)(uint)this.y * (nint)(uint)this.stride) + (nint)(uint)this.x;
|
||||
|
||||
return ref Unsafe.Add(ref r0, index);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -0,0 +1,201 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Microsoft.Toolkit.HighPerformance.Enumerables;
|
||||
using Microsoft.Toolkit.HighPerformance.Memory.Internals;
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
using System.Runtime.InteropServices;
|
||||
#else
|
||||
using RuntimeHelpers = Microsoft.Toolkit.HighPerformance.Helpers.Internals.RuntimeHelpers;
|
||||
#endif
|
||||
|
||||
namespace Microsoft.Toolkit.HighPerformance.Memory
|
||||
{
|
||||
/// <inheritdoc cref="Span2D{T}"/>
|
||||
public readonly ref partial struct Span2D<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets an enumerable that traverses items in a specified row.
|
||||
/// </summary>
|
||||
/// <param name="row">The target row to enumerate within the current <see cref="Span2D{T}"/> instance.</param>
|
||||
/// <returns>A <see cref="RefEnumerable{T}"/> with target items to enumerate.</returns>
|
||||
/// <remarks>The returned <see cref="RefEnumerable{T}"/> value shouldn't be used directly: use this extension in a <see langword="foreach"/> loop.</remarks>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public RefEnumerable<T> GetRow(int row)
|
||||
{
|
||||
if ((uint)row >= Height)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForRow();
|
||||
}
|
||||
|
||||
nint startIndex = (nint)(uint)this.Stride * (nint)(uint)row;
|
||||
ref T r0 = ref DangerousGetReference();
|
||||
ref T r1 = ref Unsafe.Add(ref r0, startIndex);
|
||||
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
return new RefEnumerable<T>(ref r1, Width, 1);
|
||||
#else
|
||||
IntPtr offset = RuntimeHelpers.GetObjectDataOrReferenceByteOffset(this.Instance, ref r1);
|
||||
|
||||
return new RefEnumerable<T>(this.Instance, offset, this.width, 1);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an enumerable that traverses items in a specified column.
|
||||
/// </summary>
|
||||
/// <param name="column">The target column to enumerate within the current <see cref="Span2D{T}"/> instance.</param>
|
||||
/// <returns>A <see cref="RefEnumerable{T}"/> with target items to enumerate.</returns>
|
||||
/// <remarks>The returned <see cref="RefEnumerable{T}"/> value shouldn't be used directly: use this extension in a <see langword="foreach"/> loop.</remarks>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public RefEnumerable<T> GetColumn(int column)
|
||||
{
|
||||
if ((uint)column >= Width)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForColumn();
|
||||
}
|
||||
|
||||
ref T r0 = ref DangerousGetReference();
|
||||
ref T r1 = ref Unsafe.Add(ref r0, (nint)(uint)column);
|
||||
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
return new RefEnumerable<T>(ref r1, Height, this.Stride);
|
||||
#else
|
||||
IntPtr offset = RuntimeHelpers.GetObjectDataOrReferenceByteOffset(this.Instance, ref r1);
|
||||
|
||||
return new RefEnumerable<T>(this.Instance, offset, Height, this.Stride);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an enumerator for the current <see cref="Span2D{T}"/> instance.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// An enumerator that can be used to traverse the items in the current <see cref="Span2D{T}"/> instance
|
||||
/// </returns>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Enumerator GetEnumerator() => new Enumerator(this);
|
||||
|
||||
/// <summary>
|
||||
/// Provides an enumerator for the elements of a <see cref="Span2D{T}"/> instance.
|
||||
/// </summary>
|
||||
public ref struct Enumerator
|
||||
{
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
/// <summary>
|
||||
/// The <see cref="Span{T}"/> instance pointing to the first item in the target memory area.
|
||||
/// </summary>
|
||||
/// <remarks>Just like in <see cref="Span2D{T}"/>, the length is the height of the 2D region.</remarks>
|
||||
private readonly Span<T> span;
|
||||
#else
|
||||
/// <summary>
|
||||
/// The target <see cref="object"/> instance, if present.
|
||||
/// </summary>
|
||||
private readonly object? instance;
|
||||
|
||||
/// <summary>
|
||||
/// The initial offset within <see cref="instance"/>.
|
||||
/// </summary>
|
||||
private readonly IntPtr offset;
|
||||
|
||||
/// <summary>
|
||||
/// The height of the specified 2D region.
|
||||
/// </summary>
|
||||
private readonly int height;
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// The width of the specified 2D region.
|
||||
/// </summary>
|
||||
private readonly int width;
|
||||
|
||||
/// <summary>
|
||||
/// The stride of the specified 2D region.
|
||||
/// </summary>
|
||||
private readonly int stride;
|
||||
|
||||
/// <summary>
|
||||
/// The current horizontal offset.
|
||||
/// </summary>
|
||||
private int x;
|
||||
|
||||
/// <summary>
|
||||
/// The current vertical offset.
|
||||
/// </summary>
|
||||
private int y;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Enumerator"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="span">The target <see cref="Span2D{T}"/> instance to enumerate.</param>
|
||||
internal Enumerator(Span2D<T> span)
|
||||
{
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
this.span = span.span;
|
||||
#else
|
||||
this.instance = span.Instance;
|
||||
this.offset = span.Offset;
|
||||
this.height = span.height;
|
||||
#endif
|
||||
this.width = span.width;
|
||||
this.stride = span.Stride;
|
||||
this.x = -1;
|
||||
this.y = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implements the duck-typed <see cref="System.Collections.IEnumerator.MoveNext"/> method.
|
||||
/// </summary>
|
||||
/// <returns><see langword="true"/> whether a new element is available, <see langword="false"/> otherwise</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool MoveNext()
|
||||
{
|
||||
int x = this.x + 1;
|
||||
|
||||
// Horizontal move, within range
|
||||
if (x < this.width)
|
||||
{
|
||||
this.x = x;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// We reached the end of a row and there is at least
|
||||
// another row available: wrap to a new line and continue.
|
||||
this.x = 0;
|
||||
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
return ++this.y < this.span.Length;
|
||||
#else
|
||||
return ++this.y < this.height;
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the duck-typed <see cref="System.Collections.Generic.IEnumerator{T}.Current"/> property.
|
||||
/// </summary>
|
||||
public readonly ref T Current
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get
|
||||
{
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
ref T r0 = ref MemoryMarshal.GetReference(this.span);
|
||||
#else
|
||||
ref T r0 = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference<T>(this.instance, this.offset);
|
||||
#endif
|
||||
nint index = ((nint)(uint)this.y * (nint)(uint)this.stride) + (nint)(uint)this.x;
|
||||
|
||||
return ref Unsafe.Add(ref r0, index);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -0,0 +1,57 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Microsoft.Toolkit.HighPerformance.Memory.Views
|
||||
{
|
||||
/// <summary>
|
||||
/// A debug proxy used to display items in a 2D layout.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of items to display.</typeparam>
|
||||
internal sealed class MemoryDebugView2D<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MemoryDebugView2D{T}"/> class with the specified parameters.
|
||||
/// </summary>
|
||||
/// <param name="memory">The input <see cref="Memory2D{T}"/> instance with the items to display.</param>
|
||||
public MemoryDebugView2D(Memory2D<T> memory)
|
||||
{
|
||||
this.Items = memory.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MemoryDebugView2D{T}"/> class with the specified parameters.
|
||||
/// </summary>
|
||||
/// <param name="memory">The input <see cref="ReadOnlyMemory2D{T}"/> instance with the items to display.</param>
|
||||
public MemoryDebugView2D(ReadOnlyMemory2D<T> memory)
|
||||
{
|
||||
this.Items = memory.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MemoryDebugView2D{T}"/> class with the specified parameters.
|
||||
/// </summary>
|
||||
/// <param name="span">The input <see cref="Span2D{T}"/> instance with the items to display.</param>
|
||||
public MemoryDebugView2D(Span2D<T> span)
|
||||
{
|
||||
this.Items = span.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MemoryDebugView2D{T}"/> class with the specified parameters.
|
||||
/// </summary>
|
||||
/// <param name="span">The input <see cref="ReadOnlySpan2D{T}"/> instance with the items to display.</param>
|
||||
public MemoryDebugView2D(ReadOnlySpan2D<T> span)
|
||||
{
|
||||
this.Items = span.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the items to display for the current instance
|
||||
/// </summary>
|
||||
[DebuggerBrowsable(DebuggerBrowsableState.Collapsed)]
|
||||
public T[,]? Items { get; }
|
||||
}
|
||||
}
|
|
@ -2,18 +2,19 @@
|
|||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netstandard1.4;netstandard2.0;netstandard2.1;netcoreapp2.1;netcoreapp3.1</TargetFrameworks>
|
||||
<LangVersion>8.0</LangVersion>
|
||||
<LangVersion>9.0</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<Title>Windows Community Toolkit High Performance .NET Standard</Title>
|
||||
<Description>
|
||||
This package includes high performance .NET Standard helpers such as:
|
||||
- Memory2D<T> and Span2D<T>: two types providing fast and allocation-free abstraction over 2D memory areas.
|
||||
- ArrayPoolBufferWriter<T>: an IBufferWriter<T> implementation using pooled arrays, which also supports IMemoryOwner<T>.
|
||||
- MemoryBufferWriter<T>: an IBufferWriter<T>: implementation that can wrap external Memory<T>: instances.
|
||||
- MemoryOwner<T>: an IMemoryOwner<T> implementation with an embedded length and a fast Span<T> accessor.
|
||||
- SpanOwner<T>: a stack-only type with the ability to rent a buffer of a specified length and getting a Span<T> from it.
|
||||
- StringPool: a configurable pool for string instances that be used to minimize allocations when creating multiple strings from char buffers.
|
||||
- String, array, Span<T>, Memory<T> extensions and more, all focused on high performance.
|
||||
- String, array, Memory<T>, Span<T> extensions and more, all focused on high performance.
|
||||
- HashCode<T>: a SIMD-enabled extension of HashCode to quickly process sequences of values.
|
||||
- BitHelper: a class with helper methods to perform bit operations on numeric types.
|
||||
- ParallelHelper: helpers to work with parallel code in a highly optimized manner.
|
||||
|
|
|
@ -36,6 +36,16 @@ namespace Microsoft.Toolkit.HighPerformance
|
|||
Span = MemoryMarshal.CreateReadOnlySpan(ref r0, 1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ReadOnlyRef{T}"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="pointer">The pointer to the target value.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public unsafe ReadOnlyRef(void* pointer)
|
||||
: this(Unsafe.AsRef<T>(pointer))
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the readonly <typeparamref name="T"/> reference represented by the current <see cref="Ref{T}"/> instance.
|
||||
/// </summary>
|
||||
|
|
|
@ -34,6 +34,16 @@ namespace Microsoft.Toolkit.HighPerformance
|
|||
Span = MemoryMarshal.CreateSpan(ref value, 1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Ref{T}"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="pointer">The pointer to the target value.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public unsafe Ref(void* pointer)
|
||||
: this(ref Unsafe.AsRef<T>(pointer))
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <typeparamref name="T"/> reference represented by the current <see cref="Ref{T}"/> instance.
|
||||
/// </summary>
|
||||
|
|
|
@ -13,14 +13,14 @@ namespace Microsoft.Toolkit.HighPerformance.Streams
|
|||
internal static partial class MemoryStream
|
||||
{
|
||||
/// <summary>
|
||||
/// Validates the <see cref="Stream.Position"/> argument.
|
||||
/// Validates the <see cref="Stream.Position"/> argument (it needs to be in the [0, length]) range.
|
||||
/// </summary>
|
||||
/// <param name="position">The new <see cref="Stream.Position"/> value being set.</param>
|
||||
/// <param name="length">The maximum length of the target <see cref="Stream"/>.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void ValidatePosition(long position, int length)
|
||||
{
|
||||
if ((ulong)position >= (ulong)length)
|
||||
if ((ulong)position > (ulong)length)
|
||||
{
|
||||
ThrowArgumentOutOfRangeExceptionForPosition();
|
||||
}
|
||||
|
|
|
@ -49,6 +49,16 @@
|
|||
HorizontalSpacing="@[HorizontalSpacing:Slider:5:0-200]@" />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ListView.ItemContainerStyle>
|
||||
<Style TargetType="ListViewItem">
|
||||
<!-- Change those values to change the WrapPanel's children alignment -->
|
||||
<Setter Property="VerticalContentAlignment" Value="Center" />
|
||||
<Setter Property="HorizontalContentAlignment" Value="Center" />
|
||||
<Setter Property="Padding" Value="0" />
|
||||
<Setter Property="MinWidth" Value="0" />
|
||||
<Setter Property="MinHeight" Value="0" />
|
||||
</Style>
|
||||
</ListView.ItemContainerStyle>
|
||||
</ListView>
|
||||
</Grid>
|
||||
</Page>
|
|
@ -64,8 +64,8 @@ namespace Microsoft.Toolkit.Uwp.SampleApp.SamplePages
|
|||
{
|
||||
Category = "Remove",
|
||||
Thumbnail = "ms-appx:///Assets/Photos/BigFourSummerHeat.jpg",
|
||||
Width = Rand.Next(120, 180),
|
||||
Height = Rand.Next(80, 130)
|
||||
Width = Rand.Next(60, 180),
|
||||
Height = Rand.Next(40, 140)
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
# How to add new samples
|
||||
For the latest info, [visit the wiki article here](https://github.com/windows-toolkit/WindowsCommunityToolkit/wiki/Sample-Development).
|
||||
|
||||
# How to add new samples
|
||||
|
||||
This document describes how to add a new sample page for a new control you want to add to the toolkit.
|
||||
|
||||
|
|
|
@ -3,8 +3,10 @@
|
|||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using Microsoft.Toolkit.Uwp.UI.Automation.Peers;
|
||||
using Windows.System;
|
||||
using Windows.UI.Xaml;
|
||||
using Windows.UI.Xaml.Automation.Peers;
|
||||
using Windows.UI.Xaml.Controls;
|
||||
using Windows.UI.Xaml.Controls.Primitives;
|
||||
using Windows.UI.Xaml.Input;
|
||||
|
@ -76,6 +78,15 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls
|
|||
Collapsed?.Invoke(this, args);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates AutomationPeer (<see cref="UIElement.OnCreateAutomationPeer"/>)
|
||||
/// </summary>
|
||||
/// <returns>An automation peer for this <see cref="Expander"/>.</returns>
|
||||
protected override AutomationPeer OnCreateAutomationPeer()
|
||||
{
|
||||
return new ExpanderAutomationPeer(this);
|
||||
}
|
||||
|
||||
private void ExpanderToggleButtonPart_KeyDown(object sender, KeyRoutedEventArgs e)
|
||||
{
|
||||
if (e.Key != VirtualKey.Enter)
|
||||
|
|
|
@ -0,0 +1,116 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using Microsoft.Toolkit.Uwp.UI.Controls;
|
||||
using Windows.UI.Xaml.Automation;
|
||||
using Windows.UI.Xaml.Automation.Peers;
|
||||
using Windows.UI.Xaml.Automation.Provider;
|
||||
|
||||
namespace Microsoft.Toolkit.Uwp.UI.Automation.Peers
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines a framework element automation peer for the <see cref="Expander"/> control.
|
||||
/// </summary>
|
||||
public class ExpanderAutomationPeer : FrameworkElementAutomationPeer, IToggleProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ExpanderAutomationPeer"/> class.
|
||||
/// </summary>
|
||||
/// <param name="owner">
|
||||
/// The <see cref="Expander" /> that is associated with this <see cref="T:Windows.UI.Xaml.Automation.Peers.ExpanderAutomationPeer" />.
|
||||
/// </param>
|
||||
public ExpanderAutomationPeer(Expander owner)
|
||||
: base(owner)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>Gets the toggle state of the control.</summary>
|
||||
/// <returns>The toggle state of the control, as a value of the enumeration.</returns>
|
||||
public ToggleState ToggleState => OwningExpander.IsExpanded ? ToggleState.On : ToggleState.Off;
|
||||
|
||||
private Expander OwningExpander
|
||||
{
|
||||
get
|
||||
{
|
||||
return Owner as Expander;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Cycles through the toggle states of a control.</summary>
|
||||
public void Toggle()
|
||||
{
|
||||
if (!IsEnabled())
|
||||
{
|
||||
throw new ElementNotEnabledException();
|
||||
}
|
||||
|
||||
OwningExpander.IsExpanded = !OwningExpander.IsExpanded;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the control type for the element that is associated with the UI Automation peer.
|
||||
/// </summary>
|
||||
/// <returns>The control type.</returns>
|
||||
protected override AutomationControlType GetAutomationControlTypeCore()
|
||||
{
|
||||
return AutomationControlType.Custom;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called by GetClassName that gets a human readable name that, in addition to AutomationControlType,
|
||||
/// differentiates the control represented by this AutomationPeer.
|
||||
/// </summary>
|
||||
/// <returns>The string that contains the name.</returns>
|
||||
protected override string GetClassNameCore()
|
||||
{
|
||||
return Owner.GetType().Name;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called by GetName.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// Returns the first of these that is not null or empty:
|
||||
/// - Value returned by the base implementation
|
||||
/// - Name of the owning Expander
|
||||
/// - Expander class name
|
||||
/// </returns>
|
||||
protected override string GetNameCore()
|
||||
{
|
||||
string name = base.GetNameCore();
|
||||
if (!string.IsNullOrEmpty(name))
|
||||
{
|
||||
return name;
|
||||
}
|
||||
|
||||
if (this.OwningExpander != null)
|
||||
{
|
||||
name = this.OwningExpander.Name;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(name))
|
||||
{
|
||||
name = this.GetClassName();
|
||||
}
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the control pattern that is associated with the specified Windows.UI.Xaml.Automation.Peers.PatternInterface.
|
||||
/// </summary>
|
||||
/// <param name="patternInterface">A value from the Windows.UI.Xaml.Automation.Peers.PatternInterface enumeration.</param>
|
||||
/// <returns>The object that supports the specified pattern, or null if unsupported.</returns>
|
||||
protected override object GetPatternCore(PatternInterface patternInterface)
|
||||
{
|
||||
switch (patternInterface)
|
||||
{
|
||||
case PatternInterface.Toggle:
|
||||
return this;
|
||||
}
|
||||
|
||||
return base.GetPatternCore(patternInterface);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -469,6 +469,11 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls
|
|||
{
|
||||
OnValueChanged(this);
|
||||
base.OnValueChanged(oldValue, newValue);
|
||||
if (AutomationPeer.ListenerExists(AutomationEvents.LiveRegionChanged))
|
||||
{
|
||||
var peer = FrameworkElementAutomationPeer.FromElement(this) as RadialGaugeAutomationPeer;
|
||||
peer?.RaiseValueChangedEvent(oldValue, newValue);
|
||||
}
|
||||
}
|
||||
|
||||
private static void OnValueChanged(DependencyObject d)
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using Windows.Foundation;
|
||||
using Windows.UI.Xaml.Automation;
|
||||
using Windows.UI.Xaml.Automation.Peers;
|
||||
using Windows.UI.Xaml.Automation.Provider;
|
||||
|
||||
|
@ -12,7 +14,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls
|
|||
/// Exposes <see cref="RadialGauge"/> to Microsoft UI Automation.
|
||||
/// </summary>
|
||||
public class RadialGaugeAutomationPeer :
|
||||
FrameworkElementAutomationPeer,
|
||||
RangeBaseAutomationPeer,
|
||||
IRangeValueProvider
|
||||
{
|
||||
/// <summary>
|
||||
|
@ -25,25 +27,25 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls
|
|||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsReadOnly => !((RadialGauge)Owner).IsInteractive;
|
||||
public new bool IsReadOnly => !((RadialGauge)Owner).IsInteractive;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public double LargeChange => ((RadialGauge)Owner).StepSize;
|
||||
public new double LargeChange => ((RadialGauge)Owner).StepSize;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public double Maximum => ((RadialGauge)Owner).Maximum;
|
||||
public new double Maximum => ((RadialGauge)Owner).Maximum;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public double Minimum => ((RadialGauge)Owner).Minimum;
|
||||
public new double Minimum => ((RadialGauge)Owner).Minimum;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public double SmallChange => ((RadialGauge)Owner).StepSize;
|
||||
public new double SmallChange => ((RadialGauge)Owner).StepSize;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public double Value => ((RadialGauge)Owner).Value;
|
||||
public new double Value => ((RadialGauge)Owner).Value;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void SetValue(double value)
|
||||
public new void SetValue(double value)
|
||||
{
|
||||
((RadialGauge)Owner).Value = value;
|
||||
}
|
||||
|
@ -58,7 +60,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls
|
|||
protected override string GetNameCore()
|
||||
{
|
||||
var gauge = (RadialGauge)Owner;
|
||||
return "radial gauge. " + (string.IsNullOrWhiteSpace(gauge.Unit) ? "no unit specified. " : "unit " + gauge.Unit + ". ");
|
||||
return "radial gauge. " + (string.IsNullOrWhiteSpace(gauge.Unit) ? "no unit specified, " : "unit " + gauge.Unit + ", ") + Value;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
@ -78,5 +80,15 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls
|
|||
{
|
||||
return AutomationControlType.Custom;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Raises the property changed event for this AutomationPeer for the provided identifier.
|
||||
/// </summary>
|
||||
/// <param name="oldValue">Old value</param>
|
||||
/// <param name="newValue">New value</param>
|
||||
public void RaiseValueChangedEvent(double oldValue, double newValue)
|
||||
{
|
||||
RaisePropertyChangedEvent(RangeValuePatternIdentifiers.ValueProperty, PropertyValue.CreateDouble(oldValue), PropertyValue.CreateDouble(newValue));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,11 +2,9 @@
|
|||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Collections;
|
||||
using System.Drawing;
|
||||
using Microsoft.Toolkit.Diagnostics;
|
||||
|
||||
namespace Microsoft.Toolkit.Uwp.UI.Controls
|
||||
{
|
||||
|
@ -16,23 +14,81 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls
|
|||
/// <see cref="UniformGrid.GetFreeSpot"/> iterator.
|
||||
/// This is used so we can better isolate our logic and make it easier to test.
|
||||
/// </summary>
|
||||
internal class TakenSpotsReferenceHolder
|
||||
internal sealed class TakenSpotsReferenceHolder
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the array to hold taken spots.
|
||||
/// True value indicates an item in the layout is fixed to that position.
|
||||
/// False values indicate free openings where an item can be placed.
|
||||
/// The <see cref="BitArray"/> instance used to efficiently track empty spots.
|
||||
/// </summary>
|
||||
public bool[,] SpotsTaken { get; set; }
|
||||
private readonly BitArray spotsTaken;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TakenSpotsReferenceHolder"/> class.
|
||||
/// </summary>
|
||||
/// <param name="rows">The number of rows to track.</param>
|
||||
/// <param name="columns">The number of columns to track.</param>
|
||||
public TakenSpotsReferenceHolder(int rows, int columns)
|
||||
{
|
||||
SpotsTaken = new bool[rows, columns];
|
||||
Guard.IsGreaterThanOrEqualTo(rows, 0, nameof(rows));
|
||||
Guard.IsGreaterThanOrEqualTo(columns, 0, nameof(columns));
|
||||
|
||||
Height = rows;
|
||||
Width = columns;
|
||||
|
||||
this.spotsTaken = new BitArray(rows * columns);
|
||||
}
|
||||
|
||||
public TakenSpotsReferenceHolder(bool[,] array)
|
||||
/// <summary>
|
||||
/// Gets the height of the grid to monitor.
|
||||
/// </summary>
|
||||
public int Height { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the width of the grid to monitor.
|
||||
/// </summary>
|
||||
public int Width { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the value of a specified grid cell.
|
||||
/// </summary>
|
||||
/// <param name="i">The vertical offset.</param>
|
||||
/// <param name="j">The horizontal offset.</param>
|
||||
public bool this[int i, int j]
|
||||
{
|
||||
SpotsTaken = array;
|
||||
get => this.spotsTaken[(i * Width) + j];
|
||||
set => this.spotsTaken[(i * Width) + j] = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fills the specified area in the current grid with a given value.
|
||||
/// If invalid coordinates are given, they will simply be ignored and no exception will be thrown.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to fill the target area with.</param>
|
||||
/// <param name="row">The row to start on (inclusive, 0-based index).</param>
|
||||
/// <param name="column">The column to start on (inclusive, 0-based index).</param>
|
||||
/// <param name="width">The positive width of area to fill.</param>
|
||||
/// <param name="height">The positive height of area to fill.</param>
|
||||
public void Fill(bool value, int row, int column, int width, int height)
|
||||
{
|
||||
Rectangle bounds = new Rectangle(0, 0, Width, Height);
|
||||
|
||||
// Precompute bounds to skip branching in main loop
|
||||
bounds.Intersect(new Rectangle(column, row, width, height));
|
||||
|
||||
for (int i = bounds.Top; i < bounds.Bottom; i++)
|
||||
{
|
||||
for (int j = bounds.Left; j < bounds.Right; j++)
|
||||
{
|
||||
this[i, j] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets the current reference holder.
|
||||
/// </summary>
|
||||
public void Reset()
|
||||
{
|
||||
this.spotsTaken.SetAll(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,16 +22,16 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls
|
|||
{
|
||||
if (topdown)
|
||||
{
|
||||
var rows = arrayref.SpotsTaken.GetLength(0);
|
||||
var rows = arrayref.Height;
|
||||
|
||||
// Layout spots from Top-Bottom, Left-Right (right-left handled automatically by Grid with Flow-Direction).
|
||||
// Effectively transpose the Grid Layout.
|
||||
for (int c = 0; c < arrayref.SpotsTaken.GetLength(1); c++)
|
||||
for (int c = 0; c < arrayref.Width; c++)
|
||||
{
|
||||
int start = (c == 0 && firstcolumn > 0 && firstcolumn < rows) ? firstcolumn : 0;
|
||||
for (int r = start; r < rows; r++)
|
||||
{
|
||||
if (!arrayref.SpotsTaken[r, c])
|
||||
if (!arrayref[r, c])
|
||||
{
|
||||
yield return (r, c);
|
||||
}
|
||||
|
@ -40,17 +40,17 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls
|
|||
}
|
||||
else
|
||||
{
|
||||
var columns = arrayref.SpotsTaken.GetLength(1);
|
||||
var columns = arrayref.Width;
|
||||
|
||||
// Layout spots as normal from Left-Right.
|
||||
// (right-left handled automatically by Grid with Flow-Direction
|
||||
// during its layout, internal model is always left-right).
|
||||
for (int r = 0; r < arrayref.SpotsTaken.GetLength(0); r++)
|
||||
for (int r = 0; r < arrayref.Height; r++)
|
||||
{
|
||||
int start = (r == 0 && firstcolumn > 0 && firstcolumn < columns) ? firstcolumn : 0;
|
||||
for (int c = start; c < columns; c++)
|
||||
{
|
||||
if (!arrayref.SpotsTaken[r, c])
|
||||
if (!arrayref[r, c])
|
||||
{
|
||||
yield return (r, c);
|
||||
}
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.Toolkit.Extensions;
|
||||
using Windows.Foundation;
|
||||
using Windows.UI.Xaml;
|
||||
using Windows.UI.Xaml.Controls;
|
||||
|
@ -20,6 +19,11 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls
|
|||
// Internal list we use to keep track of items that we don't have space to layout.
|
||||
private List<UIElement> _overflow = new List<UIElement>();
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="TakenSpotsReferenceHolder"/> instance in use, if any.
|
||||
/// </summary>
|
||||
private TakenSpotsReferenceHolder _spotref;
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override Size MeasureOverride(Size availableSize)
|
||||
{
|
||||
|
@ -36,7 +40,20 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls
|
|||
SetupRowDefinitions(rows);
|
||||
SetupColumnDefinitions(columns);
|
||||
|
||||
var spotref = new TakenSpotsReferenceHolder(rows, columns);
|
||||
TakenSpotsReferenceHolder spotref;
|
||||
|
||||
// If the last spot holder matches the size currently in use, just reset
|
||||
// that instance and reuse it to avoid allocating a new bit array.
|
||||
if (_spotref != null && _spotref.Height == rows && _spotref.Width == columns)
|
||||
{
|
||||
spotref = _spotref;
|
||||
|
||||
spotref.Reset();
|
||||
}
|
||||
else
|
||||
{
|
||||
spotref = _spotref = new TakenSpotsReferenceHolder(rows, columns);
|
||||
}
|
||||
|
||||
// Figure out which children we should automatically layout and where available openings are.
|
||||
foreach (var child in visible)
|
||||
|
@ -56,7 +73,8 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls
|
|||
else
|
||||
{
|
||||
SetAutoLayout(child, false);
|
||||
spotref.SpotsTaken.Fill(true, row, col, colspan, rowspan); // row, col, width, height
|
||||
|
||||
spotref.Fill(true, row, col, colspan, rowspan);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -100,7 +118,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls
|
|||
if (rowspan > 1 || colspan > 1)
|
||||
{
|
||||
// TODO: Need to tie this into iterator
|
||||
spotref.SpotsTaken.Fill(true, row, column, GetColumnSpan(child), GetRowSpan(child)); // row, col, width, height
|
||||
spotref.Fill(true, row, column, colspan, rowspan);
|
||||
}
|
||||
}
|
||||
else
|
||||
|
@ -135,7 +153,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls
|
|||
}
|
||||
|
||||
// Return our desired size based on the largest child we found, our dimensions, and spacing.
|
||||
var desiredSize = new Size((maxWidth * (double)columns) + columnSpacingSize, (maxHeight * (double)rows) + rowSpacingSize);
|
||||
var desiredSize = new Size((maxWidth * columns) + columnSpacingSize, (maxHeight * rows) + rowSpacingSize);
|
||||
|
||||
// Required to perform regular grid measurement, but ignore result.
|
||||
base.MeasureOverride(desiredSize);
|
||||
|
|
|
@ -2,6 +2,10 @@
|
|||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.Toolkit.Diagnostics;
|
||||
using Windows.Foundation;
|
||||
using Windows.UI.Xaml.Controls;
|
||||
|
||||
namespace Microsoft.Toolkit.Uwp.UI.Controls
|
||||
|
@ -14,12 +18,17 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls
|
|||
[System.Diagnostics.DebuggerDisplay("U = {U} V = {V}")]
|
||||
private struct UvMeasure
|
||||
{
|
||||
internal static readonly UvMeasure Zero = default(UvMeasure);
|
||||
internal static UvMeasure Zero => default;
|
||||
|
||||
internal double U { get; set; }
|
||||
|
||||
internal double V { get; set; }
|
||||
|
||||
public UvMeasure(Orientation orientation, Size size)
|
||||
: this(orientation, size.Width, size.Height)
|
||||
{
|
||||
}
|
||||
|
||||
public UvMeasure(Orientation orientation, double width, double height)
|
||||
{
|
||||
if (orientation == Orientation.Horizontal)
|
||||
|
@ -33,6 +42,56 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls
|
|||
V = width;
|
||||
}
|
||||
}
|
||||
|
||||
public UvMeasure Add(double u, double v)
|
||||
=> new UvMeasure { U = U + u, V = V + v };
|
||||
|
||||
public UvMeasure Add(UvMeasure measure)
|
||||
=> Add(measure.U, measure.V);
|
||||
|
||||
public Size ToSize(Orientation orientation)
|
||||
=> orientation == Orientation.Horizontal ? new Size(U, V) : new Size(V, U);
|
||||
}
|
||||
|
||||
private struct UvRect
|
||||
{
|
||||
public UvMeasure Position { get; set; }
|
||||
|
||||
public UvMeasure Size { get; set; }
|
||||
|
||||
public Rect ToRect(Orientation orientation) => orientation switch
|
||||
{
|
||||
Orientation.Vertical => new Rect(Position.V, Position.U, Size.V, Size.U),
|
||||
Orientation.Horizontal => new Rect(Position.U, Position.V, Size.U, Size.V),
|
||||
_ => ThrowHelper.ThrowNotSupportedException<Rect>("unsupported orientation"),
|
||||
};
|
||||
}
|
||||
|
||||
private struct Row
|
||||
{
|
||||
public Row(List<UvRect> childrenRects, UvMeasure size)
|
||||
{
|
||||
ChildrenRects = childrenRects;
|
||||
Size = size;
|
||||
}
|
||||
|
||||
public List<UvRect> ChildrenRects { get; }
|
||||
|
||||
public UvMeasure Size { get; set; }
|
||||
|
||||
public UvRect Rect => ChildrenRects.Count > 0 ?
|
||||
new UvRect { Position = ChildrenRects[0].Position, Size = Size } :
|
||||
new UvRect { Position = UvMeasure.Zero, Size = Size };
|
||||
|
||||
public void Add(UvMeasure position, UvMeasure size)
|
||||
{
|
||||
ChildrenRects.Add(new UvRect { Position = position, Size = size });
|
||||
Size = new UvMeasure
|
||||
{
|
||||
U = position.U + size.U,
|
||||
V = Math.Max(Size.V, size.V),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Windows.Foundation;
|
||||
using Windows.UI.Xaml;
|
||||
using Windows.UI.Xaml.Controls;
|
||||
|
@ -128,133 +130,130 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls
|
|||
}
|
||||
}
|
||||
|
||||
private readonly List<Row> _rows = new List<Row>();
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override Size MeasureOverride(Size availableSize)
|
||||
{
|
||||
availableSize.Width = availableSize.Width - Padding.Left - Padding.Right;
|
||||
availableSize.Height = availableSize.Height - Padding.Top - Padding.Bottom;
|
||||
var totalMeasure = UvMeasure.Zero;
|
||||
var parentMeasure = new UvMeasure(Orientation, availableSize.Width, availableSize.Height);
|
||||
var spacingMeasure = new UvMeasure(Orientation, HorizontalSpacing, VerticalSpacing);
|
||||
var lineMeasure = UvMeasure.Zero;
|
||||
|
||||
var childAvailableSize = new Size(
|
||||
availableSize.Width - Padding.Left - Padding.Right,
|
||||
availableSize.Height - Padding.Top - Padding.Bottom);
|
||||
foreach (var child in Children)
|
||||
{
|
||||
child.Measure(availableSize);
|
||||
var currentMeasure = new UvMeasure(Orientation, child.DesiredSize.Width, child.DesiredSize.Height);
|
||||
if (currentMeasure.U == 0)
|
||||
{
|
||||
continue; // ignore collapsed items
|
||||
}
|
||||
|
||||
// if this is the first item, do not add spacing. Spacing is added to the "left"
|
||||
double uChange = lineMeasure.U == 0
|
||||
? currentMeasure.U
|
||||
: currentMeasure.U + spacingMeasure.U;
|
||||
if (parentMeasure.U >= uChange + lineMeasure.U)
|
||||
{
|
||||
lineMeasure.U += uChange;
|
||||
lineMeasure.V = Math.Max(lineMeasure.V, currentMeasure.V);
|
||||
}
|
||||
else
|
||||
{
|
||||
// new line should be added
|
||||
// to get the max U to provide it correctly to ui width ex: ---| or -----|
|
||||
totalMeasure.U = Math.Max(lineMeasure.U, totalMeasure.U);
|
||||
totalMeasure.V += lineMeasure.V + spacingMeasure.V;
|
||||
|
||||
// if the next new row still can handle more controls
|
||||
if (parentMeasure.U > currentMeasure.U)
|
||||
{
|
||||
// set lineMeasure initial values to the currentMeasure to be calculated later on the new loop
|
||||
lineMeasure = currentMeasure;
|
||||
}
|
||||
|
||||
// the control will take one row alone
|
||||
else
|
||||
{
|
||||
// validate the new control measures
|
||||
totalMeasure.U = Math.Max(currentMeasure.U, totalMeasure.U);
|
||||
totalMeasure.V += currentMeasure.V;
|
||||
|
||||
// add new empty line
|
||||
lineMeasure = UvMeasure.Zero;
|
||||
}
|
||||
}
|
||||
child.Measure(childAvailableSize);
|
||||
}
|
||||
|
||||
// update value with the last line
|
||||
// if the last loop is (parentMeasure.U > currentMeasure.U + lineMeasure.U) the total isn't calculated then calculate it
|
||||
// if the last loop is (parentMeasure.U > currentMeasure.U) the currentMeasure isn't added to the total so add it here
|
||||
// for the last condition it is zeros so adding it will make no difference
|
||||
// this way is faster than an if condition in every loop for checking the last item
|
||||
totalMeasure.U = Math.Max(lineMeasure.U, totalMeasure.U);
|
||||
totalMeasure.V += lineMeasure.V;
|
||||
|
||||
totalMeasure.U = Math.Ceiling(totalMeasure.U);
|
||||
|
||||
return Orientation == Orientation.Horizontal ? new Size(totalMeasure.U, totalMeasure.V) : new Size(totalMeasure.V, totalMeasure.U);
|
||||
var requiredSize = UpdateRows(availableSize);
|
||||
return requiredSize;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override Size ArrangeOverride(Size finalSize)
|
||||
{
|
||||
if (Children.Count > 0)
|
||||
if ((Orientation == Orientation.Horizontal && finalSize.Width < DesiredSize.Width) ||
|
||||
(Orientation == Orientation.Vertical && finalSize.Height < DesiredSize.Height))
|
||||
{
|
||||
var parentMeasure = new UvMeasure(Orientation, finalSize.Width, finalSize.Height);
|
||||
var spacingMeasure = new UvMeasure(Orientation, HorizontalSpacing, VerticalSpacing);
|
||||
var paddingStart = new UvMeasure(Orientation, Padding.Left, Padding.Top);
|
||||
var paddingEnd = new UvMeasure(Orientation, Padding.Right, Padding.Bottom);
|
||||
var position = new UvMeasure(Orientation, Padding.Left, Padding.Top);
|
||||
// We haven't received our desired size. We need to refresh the rows.
|
||||
UpdateRows(finalSize);
|
||||
}
|
||||
|
||||
double currentV = 0;
|
||||
void Arrange(UIElement child, bool isLast = false)
|
||||
if (_rows.Count > 0)
|
||||
{
|
||||
// Now that we have all the data, we do the actual arrange pass
|
||||
var childIndex = 0;
|
||||
foreach (var row in _rows)
|
||||
{
|
||||
var desiredMeasure = new UvMeasure(Orientation, child.DesiredSize.Width, child.DesiredSize.Height);
|
||||
if (desiredMeasure.U == 0)
|
||||
foreach (var rect in row.ChildrenRects)
|
||||
{
|
||||
return; // if an item is collapsed, avoid adding the spacing
|
||||
}
|
||||
var child = Children[childIndex++];
|
||||
var arrangeRect = new UvRect
|
||||
{
|
||||
Position = rect.Position,
|
||||
Size = new UvMeasure { U = rect.Size.U, V = row.Size.V },
|
||||
};
|
||||
|
||||
if ((desiredMeasure.U + position.U + paddingEnd.U) > parentMeasure.U)
|
||||
{
|
||||
// next row!
|
||||
position.U = paddingStart.U;
|
||||
position.V += currentV + spacingMeasure.V;
|
||||
currentV = 0;
|
||||
var finalRect = arrangeRect.ToRect(Orientation);
|
||||
child.Arrange(finalRect);
|
||||
}
|
||||
|
||||
// Stretch the last item to fill the available space
|
||||
if (isLast)
|
||||
{
|
||||
desiredMeasure.U = parentMeasure.U - position.U;
|
||||
}
|
||||
|
||||
// place the item
|
||||
if (Orientation == Orientation.Horizontal)
|
||||
{
|
||||
child.Arrange(new Rect(position.U, position.V, desiredMeasure.U, desiredMeasure.V));
|
||||
}
|
||||
else
|
||||
{
|
||||
child.Arrange(new Rect(position.V, position.U, desiredMeasure.V, desiredMeasure.U));
|
||||
}
|
||||
|
||||
// adjust the location for the next items
|
||||
position.U += desiredMeasure.U + spacingMeasure.U;
|
||||
currentV = Math.Max(desiredMeasure.V, currentV);
|
||||
}
|
||||
|
||||
var lastIndex = Children.Count - 1;
|
||||
for (var i = 0; i < lastIndex; i++)
|
||||
{
|
||||
Arrange(Children[i]);
|
||||
}
|
||||
|
||||
Arrange(Children[lastIndex], StretchChild == StretchChild.Last);
|
||||
}
|
||||
|
||||
return finalSize;
|
||||
}
|
||||
|
||||
private Size UpdateRows(Size availableSize)
|
||||
{
|
||||
_rows.Clear();
|
||||
|
||||
var paddingStart = new UvMeasure(Orientation, Padding.Left, Padding.Top);
|
||||
var paddingEnd = new UvMeasure(Orientation, Padding.Right, Padding.Bottom);
|
||||
|
||||
if (Children.Count == 0)
|
||||
{
|
||||
var emptySize = paddingStart.Add(paddingEnd).ToSize(Orientation);
|
||||
return emptySize;
|
||||
}
|
||||
|
||||
var parentMeasure = new UvMeasure(Orientation, availableSize.Width, availableSize.Height);
|
||||
var spacingMeasure = new UvMeasure(Orientation, HorizontalSpacing, VerticalSpacing);
|
||||
var position = new UvMeasure(Orientation, Padding.Left, Padding.Top);
|
||||
|
||||
var currentRow = new Row(new List<UvRect>(), default);
|
||||
var finalMeasure = new UvMeasure(Orientation, width: 0.0, height: 0.0);
|
||||
void Arrange(UIElement child, bool isLast = false)
|
||||
{
|
||||
var desiredMeasure = new UvMeasure(Orientation, child.DesiredSize);
|
||||
if (desiredMeasure.U == 0)
|
||||
{
|
||||
return; // if an item is collapsed, avoid adding the spacing
|
||||
}
|
||||
|
||||
if ((desiredMeasure.U + position.U + paddingEnd.U) > parentMeasure.U)
|
||||
{
|
||||
// next row!
|
||||
position.U = paddingStart.U;
|
||||
position.V += currentRow.Size.V + spacingMeasure.V;
|
||||
|
||||
_rows.Add(currentRow);
|
||||
currentRow = new Row(new List<UvRect>(), default);
|
||||
}
|
||||
|
||||
// Stretch the last item to fill the available space
|
||||
if (isLast)
|
||||
{
|
||||
desiredMeasure.U = parentMeasure.U - position.U;
|
||||
}
|
||||
|
||||
currentRow.Add(position, desiredMeasure);
|
||||
|
||||
// adjust the location for the next items
|
||||
position.U += desiredMeasure.U + spacingMeasure.U;
|
||||
finalMeasure.U = Math.Max(finalMeasure.U, position.U);
|
||||
}
|
||||
|
||||
var lastIndex = Children.Count - 1;
|
||||
for (var i = 0; i < lastIndex; i++)
|
||||
{
|
||||
Arrange(Children[i]);
|
||||
}
|
||||
|
||||
Arrange(Children[lastIndex], StretchChild == StretchChild.Last);
|
||||
if (currentRow.ChildrenRects.Count > 0)
|
||||
{
|
||||
_rows.Add(currentRow);
|
||||
}
|
||||
|
||||
if (_rows.Count == 0)
|
||||
{
|
||||
var emptySize = paddingStart.Add(paddingEnd).ToSize(Orientation);
|
||||
return emptySize;
|
||||
}
|
||||
|
||||
// Get max V here before computing final rect
|
||||
var lastRowRect = _rows.Last().Rect;
|
||||
finalMeasure.V = lastRowRect.Position.V + lastRowRect.Size.V;
|
||||
var finalRect = finalMeasure.Add(paddingEnd).ToSize(Orientation);
|
||||
return finalRect;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Microsoft.Toolkit.Extensions
|
||||
{
|
||||
|
@ -13,71 +14,6 @@ namespace Microsoft.Toolkit.Extensions
|
|||
/// </summary>
|
||||
public static class ArrayExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Fills elements of a rectangular array at the given position and size to a specific value.
|
||||
/// Ranges given will fill in as many elements as possible, ignoring positions outside the bounds of the array.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The element type of the array.</typeparam>
|
||||
/// <param name="array">The source array.</param>
|
||||
/// <param name="value">Value to fill with.</param>
|
||||
/// <param name="row">Row to start on (inclusive, zero-index).</param>
|
||||
/// <param name="col">Column to start on (inclusive, zero-index).</param>
|
||||
/// <param name="width">Positive width of area to fill.</param>
|
||||
/// <param name="height">Positive height of area to fill.</param>
|
||||
public static void Fill<T>(this T[,] array, T value, int row, int col, int width, int height)
|
||||
{
|
||||
for (int r = row; r < row + height; r++)
|
||||
{
|
||||
for (int c = col; c < col + width; c++)
|
||||
{
|
||||
if (r >= 0 && c >= 0 && r < array.GetLength(0) && c < array.GetLength(1))
|
||||
{
|
||||
array[r, c] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Yields a row from a rectangular array.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The element type of the array.</typeparam>
|
||||
/// <param name="rectarray">The source array.</param>
|
||||
/// <param name="row">Row record to retrieve, 0-based index.</param>
|
||||
/// <returns>Yielded row.</returns>
|
||||
public static IEnumerable<T> GetRow<T>(this T[,] rectarray, int row)
|
||||
{
|
||||
if (row < 0 || row >= rectarray.GetLength(0))
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(row));
|
||||
}
|
||||
|
||||
for (int c = 0; c < rectarray.GetLength(1); c++)
|
||||
{
|
||||
yield return rectarray[row, c];
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Yields a column from a rectangular array.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The element type of the array.</typeparam>
|
||||
/// <param name="rectarray">The source array.</param>
|
||||
/// <param name="column">Column record to retrieve, 0-based index.</param>
|
||||
/// <returns>Yielded column.</returns>
|
||||
public static IEnumerable<T> GetColumn<T>(this T[,] rectarray, int column)
|
||||
{
|
||||
if (column < 0 || column >= rectarray.GetLength(1))
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(column));
|
||||
}
|
||||
|
||||
for (int r = 0; r < rectarray.GetLength(0); r++)
|
||||
{
|
||||
yield return rectarray[r, column];
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Yields a column from a jagged array.
|
||||
/// An exception will be thrown if the column is out of bounds, and return default in places where there are no elements from inner arrays.
|
||||
|
@ -98,7 +34,7 @@ namespace Microsoft.Toolkit.Extensions
|
|||
{
|
||||
if (column >= rectarray[r].Length)
|
||||
{
|
||||
yield return default(T);
|
||||
yield return default;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
@ -112,10 +48,28 @@ namespace Microsoft.Toolkit.Extensions
|
|||
/// </summary>
|
||||
/// <typeparam name="T">The element type of the array.</typeparam>
|
||||
/// <param name="array">The source array.</param>
|
||||
/// <returns>String representation of the array.</returns>
|
||||
/// <returns>The <see cref="string"/> representation of the array.</returns>
|
||||
public static string ToArrayString<T>(this T[] array)
|
||||
{
|
||||
return "[" + string.Join(",\t", array) + "]";
|
||||
// The returned string will be in the following format:
|
||||
// [1, 2, 3]
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
builder.Append('[');
|
||||
|
||||
for (int i = 0; i < array.Length; i++)
|
||||
{
|
||||
if (i != 0)
|
||||
{
|
||||
builder.Append(",\t");
|
||||
}
|
||||
|
||||
builder.Append(array[i].ToString());
|
||||
}
|
||||
|
||||
builder.Append(']');
|
||||
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -126,32 +80,89 @@ namespace Microsoft.Toolkit.Extensions
|
|||
/// <returns>String representation of the array.</returns>
|
||||
public static string ToArrayString<T>(this T[][] mdarray)
|
||||
{
|
||||
string[] inner = new string[mdarray.GetLength(0)];
|
||||
// The returned string uses the same format as the overload for 2D arrays
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
for (int r = 0; r < mdarray.GetLength(0); r++)
|
||||
builder.Append('[');
|
||||
|
||||
for (int i = 0; i < mdarray.Length; i++)
|
||||
{
|
||||
inner[r] = string.Join(",\t", mdarray[r]);
|
||||
if (i != 0)
|
||||
{
|
||||
builder.Append(',');
|
||||
builder.Append(Environment.NewLine);
|
||||
builder.Append(' ');
|
||||
}
|
||||
|
||||
builder.Append('[');
|
||||
|
||||
T[] row = mdarray[i];
|
||||
|
||||
for (int j = 0; j < row.Length; j++)
|
||||
{
|
||||
if (j != 0)
|
||||
{
|
||||
builder.Append(",\t");
|
||||
}
|
||||
|
||||
builder.Append(row[j].ToString());
|
||||
}
|
||||
|
||||
builder.Append(']');
|
||||
}
|
||||
|
||||
return "[[" + string.Join("]," + Environment.NewLine + " [", inner) + "]]";
|
||||
builder.Append(']');
|
||||
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a simple string representation of a rectangular array.
|
||||
/// Returns a simple string representation of a 2D array.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The element type of the array.</typeparam>
|
||||
/// <param name="rectarray">The source array.</param>
|
||||
/// <returns>String representation of the array.</returns>
|
||||
public static string ToArrayString<T>(this T[,] rectarray)
|
||||
/// <param name="array">The source array.</param>
|
||||
/// <returns>The <see cref="string"/> representation of the array.</returns>
|
||||
public static string ToArrayString<T>(this T[,] array)
|
||||
{
|
||||
string[] inner = new string[rectarray.GetLength(0)];
|
||||
// The returned string will be in the following format:
|
||||
// [[1, 2, 3],
|
||||
// [4, 5, 6],
|
||||
// [7, 8, 9]]
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
for (int r = 0; r < rectarray.GetLength(0); r++)
|
||||
builder.Append('[');
|
||||
|
||||
int
|
||||
height = array.GetLength(0),
|
||||
width = array.GetLength(1);
|
||||
|
||||
for (int i = 0; i < height; i++)
|
||||
{
|
||||
inner[r] = string.Join(",\t", rectarray.GetRow(r));
|
||||
if (i != 0)
|
||||
{
|
||||
builder.Append(',');
|
||||
builder.Append(Environment.NewLine);
|
||||
builder.Append(' ');
|
||||
}
|
||||
|
||||
builder.Append('[');
|
||||
|
||||
for (int j = 0; j < width; j++)
|
||||
{
|
||||
if (j != 0)
|
||||
{
|
||||
builder.Append(",\t");
|
||||
}
|
||||
|
||||
builder.Append(array[i, j].ToString());
|
||||
}
|
||||
|
||||
builder.Append(']');
|
||||
}
|
||||
|
||||
return "[[" + string.Join("]," + Environment.NewLine + " [", inner) + "]]";
|
||||
builder.Append(']');
|
||||
|
||||
return builder.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -38,13 +38,13 @@ namespace UnitTests.HighPerformance.Buffers
|
|||
|
||||
Assert.AreEqual(size, pool.Size);
|
||||
|
||||
Array maps = (Array)typeof(StringPool).GetField("maps", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(pool);
|
||||
Array maps = (Array)typeof(StringPool).GetField("maps", BindingFlags.Instance | BindingFlags.NonPublic)!.GetValue(pool)!;
|
||||
|
||||
Assert.AreEqual(x, maps.Length);
|
||||
|
||||
Type bucketType = Type.GetType("Microsoft.Toolkit.HighPerformance.Buffers.StringPool+FixedSizePriorityMap, Microsoft.Toolkit.HighPerformance");
|
||||
Type bucketType = Type.GetType("Microsoft.Toolkit.HighPerformance.Buffers.StringPool+FixedSizePriorityMap, Microsoft.Toolkit.HighPerformance")!;
|
||||
|
||||
int[] buckets = (int[])bucketType.GetField("buckets", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(maps.GetValue(0));
|
||||
int[] buckets = (int[])bucketType.GetField("buckets", BindingFlags.Instance | BindingFlags.NonPublic)!.GetValue(maps.GetValue(0))!;
|
||||
|
||||
Assert.AreEqual(y, buckets.Length);
|
||||
}
|
||||
|
@ -64,7 +64,7 @@ namespace UnitTests.HighPerformance.Buffers
|
|||
}
|
||||
catch (ArgumentOutOfRangeException e)
|
||||
{
|
||||
var cctor = typeof(StringPool).GetConstructor(new[] { typeof(int) });
|
||||
var cctor = typeof(StringPool).GetConstructor(new[] { typeof(int) })!;
|
||||
|
||||
Assert.AreEqual(cctor.GetParameters()[0].Name, e.ParamName);
|
||||
}
|
||||
|
@ -158,7 +158,7 @@ namespace UnitTests.HighPerformance.Buffers
|
|||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private static string ToStringNoInlining(object obj)
|
||||
{
|
||||
return obj.ToString();
|
||||
return obj.ToString()!;
|
||||
}
|
||||
|
||||
[TestCategory("StringPool")]
|
||||
|
@ -285,15 +285,15 @@ namespace UnitTests.HighPerformance.Buffers
|
|||
}
|
||||
|
||||
// Get the buckets
|
||||
Array maps = (Array)typeof(StringPool).GetField("maps", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(pool);
|
||||
Array maps = (Array)typeof(StringPool).GetField("maps", BindingFlags.Instance | BindingFlags.NonPublic)!.GetValue(pool)!;
|
||||
|
||||
Type bucketType = Type.GetType("Microsoft.Toolkit.HighPerformance.Buffers.StringPool+FixedSizePriorityMap, Microsoft.Toolkit.HighPerformance");
|
||||
FieldInfo timestampInfo = bucketType.GetField("timestamp", BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
Type bucketType = Type.GetType("Microsoft.Toolkit.HighPerformance.Buffers.StringPool+FixedSizePriorityMap, Microsoft.Toolkit.HighPerformance")!;
|
||||
FieldInfo timestampInfo = bucketType.GetField("timestamp", BindingFlags.Instance | BindingFlags.NonPublic)!;
|
||||
|
||||
// Force the timestamp to be the maximum value, or the test would take too long
|
||||
for (int i = 0; i < maps.LongLength; i++)
|
||||
{
|
||||
object map = maps.GetValue(i);
|
||||
object map = maps.GetValue(i)!;
|
||||
|
||||
timestampInfo.SetValue(map, uint.MaxValue);
|
||||
|
||||
|
@ -305,16 +305,16 @@ namespace UnitTests.HighPerformance.Buffers
|
|||
|
||||
_ = pool.GetOrAdd(text);
|
||||
|
||||
Type heapEntryType = Type.GetType("Microsoft.Toolkit.HighPerformance.Buffers.StringPool+FixedSizePriorityMap+HeapEntry, Microsoft.Toolkit.HighPerformance");
|
||||
Type heapEntryType = Type.GetType("Microsoft.Toolkit.HighPerformance.Buffers.StringPool+FixedSizePriorityMap+HeapEntry, Microsoft.Toolkit.HighPerformance")!;
|
||||
|
||||
foreach (var map in maps)
|
||||
{
|
||||
// Get the heap for each bucket
|
||||
Array heapEntries = (Array)bucketType.GetField("heapEntries", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(map);
|
||||
FieldInfo fieldInfo = heapEntryType.GetField("Timestamp");
|
||||
Array heapEntries = (Array)bucketType.GetField("heapEntries", BindingFlags.Instance | BindingFlags.NonPublic)!.GetValue(map)!;
|
||||
FieldInfo fieldInfo = heapEntryType.GetField("Timestamp")!;
|
||||
|
||||
// Extract the array with the timestamps in the heap nodes
|
||||
uint[] array = heapEntries.Cast<object>().Select(entry => (uint)fieldInfo.GetValue(entry)).ToArray();
|
||||
uint[] array = heapEntries.Cast<object>().Select(entry => (uint)fieldInfo.GetValue(entry)!).ToArray();
|
||||
|
||||
static bool IsMinHeap(uint[] array)
|
||||
{
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Microsoft.Toolkit.HighPerformance.Extensions;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
@ -9,6 +10,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting;
|
|||
namespace UnitTests.HighPerformance.Extensions
|
||||
{
|
||||
[TestClass]
|
||||
[SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1601", Justification = "Partial test class")]
|
||||
public partial class Test_ArrayExtensions
|
||||
{
|
||||
[TestCategory("ArrayExtensions")]
|
||||
|
@ -17,6 +19,12 @@ namespace UnitTests.HighPerformance.Extensions
|
|||
{
|
||||
string[] tokens = "aa,bb,cc,dd,ee,ff,gg,hh,ii".Split(',');
|
||||
|
||||
// In all these "DangerousGetReference" tests, we need to ensure that a reference to a given
|
||||
// item within an array is effectively the one corresponding to the one whe expect, which is
|
||||
// either a reference to the first item if we use "DangerousGetReference", or one to the n-th
|
||||
// item if we use "DangerousGetReferenceAt". So all these tests just invoke the API and then
|
||||
// compare the returned reference against an existing baseline (like the built-in array indexer)
|
||||
// to ensure that the two are the same. These are all managed references, so no need for pinning.
|
||||
ref string r0 = ref Unsafe.AsRef(tokens.DangerousGetReference());
|
||||
ref string r1 = ref Unsafe.AsRef(tokens[0]);
|
||||
|
|
@ -3,9 +3,10 @@
|
|||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Microsoft.Toolkit.HighPerformance.Enumerables;
|
||||
using Microsoft.Toolkit.HighPerformance.Extensions;
|
||||
using Microsoft.Toolkit.HighPerformance.Memory;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace UnitTests.HighPerformance.Extensions
|
||||
|
@ -23,6 +24,7 @@ namespace UnitTests.HighPerformance.Extensions
|
|||
{ 9, 10, 11, 12 }
|
||||
};
|
||||
|
||||
// See comments in Test_ArrayExtensions.1D for how these tests work
|
||||
ref int r0 = ref array.DangerousGetReference();
|
||||
ref int r1 = ref array[0, 0];
|
||||
|
||||
|
@ -81,11 +83,16 @@ namespace UnitTests.HighPerformance.Extensions
|
|||
|
||||
[TestCategory("ArrayExtensions")]
|
||||
[TestMethod]
|
||||
public void Test_ArrayExtensions_2D_FillArrayMid()
|
||||
public void Test_ArrayExtensions_2D_AsSpan2DAndFillArrayMid()
|
||||
{
|
||||
bool[,] test = new bool[4, 5];
|
||||
|
||||
test.Fill(true, 1, 1, 3, 2);
|
||||
// To fill an array we now go through the Span2D<T> type, which includes all
|
||||
// the necessary logic to perform the operation. In these tests we just create
|
||||
// one through the extension, slice it and then fill it. For instance in this
|
||||
// one, we're creating a Span2D<bool> from coordinates (1, 1), with a height of
|
||||
// 2 and a width of 2, and then filling it. Then we just compare the results.
|
||||
test.AsSpan2D(1, 1, 2, 3).Fill(true);
|
||||
|
||||
var expected = new[,]
|
||||
{
|
||||
|
@ -100,12 +107,12 @@ namespace UnitTests.HighPerformance.Extensions
|
|||
|
||||
[TestCategory("ArrayExtensions")]
|
||||
[TestMethod]
|
||||
public void Test_ArrayExtensions_2D_FillArrayTwice()
|
||||
public void Test_ArrayExtensions_2D_AsSpan2DAndFillArrayTwice()
|
||||
{
|
||||
bool[,] test = new bool[4, 5];
|
||||
|
||||
test.Fill(true, 0, 0, 1, 2);
|
||||
test.Fill(true, 1, 3, 2, 2);
|
||||
test.AsSpan2D(0, 0, 2, 1).Fill(true);
|
||||
test.AsSpan2D(1, 3, 2, 2).Fill(true);
|
||||
|
||||
var expected = new[,]
|
||||
{
|
||||
|
@ -120,30 +127,11 @@ namespace UnitTests.HighPerformance.Extensions
|
|||
|
||||
[TestCategory("ArrayExtensions")]
|
||||
[TestMethod]
|
||||
public void Test_ArrayExtensions_2D_FillArrayNegativeSize()
|
||||
public void Test_ArrayExtensions_2D_AsSpan2DAndFillArrayBottomEdgeBoundary()
|
||||
{
|
||||
bool[,] test = new bool[4, 5];
|
||||
|
||||
test.Fill(true, 3, 4, -3, -2);
|
||||
|
||||
var expected = new[,]
|
||||
{
|
||||
{ false, false, false, false, false },
|
||||
{ false, false, false, false, false },
|
||||
{ false, false, false, false, false },
|
||||
{ false, false, false, false, false },
|
||||
};
|
||||
|
||||
CollectionAssert.AreEqual(expected, test);
|
||||
}
|
||||
|
||||
[TestCategory("ArrayExtensions")]
|
||||
[TestMethod]
|
||||
public void Test_ArrayExtensions_2D_FillArrayBottomEdgeBoundary()
|
||||
{
|
||||
bool[,] test = new bool[4, 5];
|
||||
|
||||
test.Fill(true, 1, 2, 2, 4);
|
||||
test.AsSpan2D(1, 2, 3, 2).Fill(true);
|
||||
|
||||
var expected = new[,]
|
||||
{
|
||||
|
@ -158,30 +146,11 @@ namespace UnitTests.HighPerformance.Extensions
|
|||
|
||||
[TestCategory("ArrayExtensions")]
|
||||
[TestMethod]
|
||||
public void Test_ArrayExtensions_2D_FillArrayTopLeftCornerNegativeBoundary()
|
||||
{
|
||||
bool[,] test = new bool[4, 5];
|
||||
|
||||
test.Fill(true, -1, -1, 3, 3);
|
||||
|
||||
var expected = new[,]
|
||||
{
|
||||
{ true, true, false, false, false },
|
||||
{ true, true, false, false, false },
|
||||
{ false, false, false, false, false },
|
||||
{ false, false, false, false, false },
|
||||
};
|
||||
|
||||
CollectionAssert.AreEqual(expected, test);
|
||||
}
|
||||
|
||||
[TestCategory("ArrayExtensions")]
|
||||
[TestMethod]
|
||||
public void Test_ArrayExtensions_2D_FillArrayBottomRightCornerBoundary()
|
||||
public void Test_ArrayExtensions_2D_AsSpan2DAndFillArrayBottomRightCornerBoundary()
|
||||
{
|
||||
bool[,] test = new bool[5, 4];
|
||||
|
||||
test.Fill(true, 3, 2, 3, 3);
|
||||
test.AsSpan2D(3, 2, 2, 2).Fill(true);
|
||||
|
||||
var expected = new[,]
|
||||
{
|
||||
|
@ -197,8 +166,6 @@ namespace UnitTests.HighPerformance.Extensions
|
|||
|
||||
[TestCategory("ArrayExtensions")]
|
||||
[TestMethod]
|
||||
[SuppressMessage("StyleCop.CSharp.NamingRules", "SA1312", Justification = "Dummy loop variable")]
|
||||
[SuppressMessage("StyleCop.CSharp.LayoutRules", "SA1501", Justification = "Empty test loop")]
|
||||
public void Test_ArrayExtensions_2D_GetRow_Rectangle()
|
||||
{
|
||||
int[,] array =
|
||||
|
@ -208,38 +175,29 @@ namespace UnitTests.HighPerformance.Extensions
|
|||
{ 9, 10, 11, 12 }
|
||||
};
|
||||
|
||||
// Here we use the enumerator on the RefEnumerator<T> type to traverse items in a row
|
||||
// by reference. For each one, we check that the reference does in fact point to the
|
||||
// item we expect in the underlying array (in this case, items on row 1).
|
||||
int j = 0;
|
||||
foreach (ref int value in array.GetRow(1))
|
||||
{
|
||||
Assert.IsTrue(Unsafe.AreSame(ref value, ref array[1, j++]));
|
||||
}
|
||||
|
||||
// Check that RefEnumerable<T>.ToArray() works correctly
|
||||
CollectionAssert.AreEqual(array.GetRow(1).ToArray(), new[] { 5, 6, 7, 8 });
|
||||
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() =>
|
||||
{
|
||||
foreach (var _ in array.GetRow(-1)) { }
|
||||
});
|
||||
// Test an empty array
|
||||
Assert.AreSame(new int[1, 0].GetRow(0).ToArray(), Array.Empty<int>());
|
||||
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() =>
|
||||
{
|
||||
foreach (var _ in array.GetRow(20)) { }
|
||||
});
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => array.GetRow(-1));
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => array.GetRow(3));
|
||||
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => array.GetRow(20));
|
||||
}
|
||||
|
||||
[TestCategory("ArrayExtensions")]
|
||||
[TestMethod]
|
||||
public void Test_ArrayExtensions_2D_GetRow_Empty()
|
||||
{
|
||||
int[,] array = new int[0, 0];
|
||||
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => array.GetRow(0).ToArray());
|
||||
}
|
||||
|
||||
[TestCategory("ArrayExtensions")]
|
||||
[TestMethod]
|
||||
[SuppressMessage("StyleCop.CSharp.NamingRules", "SA1312", Justification = "Dummy loop variable")]
|
||||
[SuppressMessage("StyleCop.CSharp.LayoutRules", "SA1501", Justification = "Empty test loop")]
|
||||
public void Test_ArrayExtensions_2D_GetColumn_Rectangle()
|
||||
{
|
||||
int[,] array =
|
||||
|
@ -249,6 +207,7 @@ namespace UnitTests.HighPerformance.Extensions
|
|||
{ 9, 10, 11, 12 }
|
||||
};
|
||||
|
||||
// Same as above, but this time we iterate a column instead (so non contiguous items)
|
||||
int i = 0;
|
||||
foreach (ref int value in array.GetColumn(1))
|
||||
{
|
||||
|
@ -257,15 +216,215 @@ namespace UnitTests.HighPerformance.Extensions
|
|||
|
||||
CollectionAssert.AreEqual(array.GetColumn(1).ToArray(), new[] { 2, 6, 10 });
|
||||
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() =>
|
||||
{
|
||||
foreach (var _ in array.GetColumn(-1)) { }
|
||||
});
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => array.GetColumn(-1));
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => array.GetColumn(4));
|
||||
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() =>
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => array.GetColumn(20));
|
||||
}
|
||||
|
||||
[TestCategory("ArrayExtensions")]
|
||||
[TestMethod]
|
||||
public void Test_ArrayExtensions_2D_GetRow_Empty()
|
||||
{
|
||||
int[,] array = new int[0, 0];
|
||||
|
||||
// Try to get a row from an empty array (the row index isn't in range)
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => array.GetRow(0).ToArray());
|
||||
}
|
||||
|
||||
[TestCategory("ArrayExtensions")]
|
||||
[TestMethod]
|
||||
public void Test_ArrayExtensions_2D_GetRowOrColumn_Helpers()
|
||||
{
|
||||
int[,] array =
|
||||
{
|
||||
foreach (var _ in array.GetColumn(20)) { }
|
||||
});
|
||||
{ 1, 2, 3, 4 },
|
||||
{ 5, 6, 7, 8 },
|
||||
{ 9, 10, 11, 12 },
|
||||
{ 13, 14, 15, 16 }
|
||||
};
|
||||
|
||||
// Get a row and test the Clear method. Note that the Span2D<T> here is sliced
|
||||
// starting from the second column, so this method should clear the row from index 1.
|
||||
array.AsSpan2D(1, 1, 3, 3).GetRow(0).Clear();
|
||||
|
||||
int[,] expected =
|
||||
{
|
||||
{ 1, 2, 3, 4 },
|
||||
{ 5, 0, 0, 0 },
|
||||
{ 9, 10, 11, 12 },
|
||||
{ 13, 14, 15, 16 }
|
||||
};
|
||||
|
||||
CollectionAssert.AreEqual(array, expected);
|
||||
|
||||
// Same as before, but this time we fill a column with a value
|
||||
array.GetColumn(2).Fill(42);
|
||||
|
||||
expected = new[,]
|
||||
{
|
||||
{ 1, 2, 42, 4 },
|
||||
{ 5, 0, 42, 0 },
|
||||
{ 9, 10, 42, 12 },
|
||||
{ 13, 14, 42, 16 }
|
||||
};
|
||||
|
||||
CollectionAssert.AreEqual(array, expected);
|
||||
|
||||
int[] copy = new int[4];
|
||||
|
||||
// Get a row and copy items to a target span (in this case, wrapping an array)
|
||||
array.GetRow(2).CopyTo(copy);
|
||||
|
||||
int[] result = { 9, 10, 42, 12 };
|
||||
|
||||
CollectionAssert.AreEqual(copy, result);
|
||||
|
||||
// Same as above, but copying from a column (so we test non contiguous sequences too)
|
||||
array.GetColumn(1).CopyTo(copy);
|
||||
|
||||
result = new[] { 2, 0, 10, 14 };
|
||||
|
||||
CollectionAssert.AreEqual(copy, result);
|
||||
|
||||
// Some invalid attempts to copy to an empty span or sequence
|
||||
Assert.ThrowsException<ArgumentException>(() => array.GetRow(0).CopyTo(default(RefEnumerable<int>)));
|
||||
Assert.ThrowsException<ArgumentException>(() => array.GetRow(0).CopyTo(default(Span<int>)));
|
||||
|
||||
Assert.ThrowsException<ArgumentException>(() => array.GetColumn(0).CopyTo(default(RefEnumerable<int>)));
|
||||
Assert.ThrowsException<ArgumentException>(() => array.GetColumn(0).CopyTo(default(Span<int>)));
|
||||
|
||||
// Same as CopyTo, but this will fail gracefully with an invalid target
|
||||
Assert.IsTrue(array.GetRow(2).TryCopyTo(copy));
|
||||
Assert.IsFalse(array.GetRow(0).TryCopyTo(default(Span<int>)));
|
||||
|
||||
result = new[] { 9, 10, 42, 12 };
|
||||
|
||||
CollectionAssert.AreEqual(copy, result);
|
||||
|
||||
// Also fill a row and then further down clear a column (trying out all possible combinations)
|
||||
array.GetRow(2).Fill(99);
|
||||
|
||||
expected = new[,]
|
||||
{
|
||||
{ 1, 2, 42, 4 },
|
||||
{ 5, 0, 42, 0 },
|
||||
{ 99, 99, 99, 99 },
|
||||
{ 13, 14, 42, 16 }
|
||||
};
|
||||
|
||||
CollectionAssert.AreEqual(array, expected);
|
||||
|
||||
array.GetColumn(2).Clear();
|
||||
|
||||
expected = new[,]
|
||||
{
|
||||
{ 1, 2, 0, 4 },
|
||||
{ 5, 0, 0, 0 },
|
||||
{ 99, 99, 0, 99 },
|
||||
{ 13, 14, 0, 16 }
|
||||
};
|
||||
|
||||
CollectionAssert.AreEqual(array, expected);
|
||||
}
|
||||
|
||||
[TestCategory("ArrayExtensions")]
|
||||
[TestMethod]
|
||||
public void Test_ArrayExtensions_2D_ReadOnlyGetRowOrColumn_Helpers()
|
||||
{
|
||||
int[,] array =
|
||||
{
|
||||
{ 1, 2, 3, 4 },
|
||||
{ 5, 6, 7, 8 },
|
||||
{ 9, 10, 11, 12 },
|
||||
{ 13, 14, 15, 16 }
|
||||
};
|
||||
|
||||
// This test pretty much does the same things as the method above, but this time
|
||||
// using a source ReadOnlySpan2D<T>, so that the sequence type being tested is
|
||||
// ReadOnlyRefEnumerable<T> instead (which shares most features but is separate).
|
||||
ReadOnlySpan2D<int> span2D = array;
|
||||
|
||||
int[] copy = new int[4];
|
||||
|
||||
span2D.GetRow(2).CopyTo(copy);
|
||||
|
||||
int[] result = { 9, 10, 11, 12 };
|
||||
|
||||
CollectionAssert.AreEqual(copy, result);
|
||||
|
||||
span2D.GetColumn(1).CopyTo(copy);
|
||||
|
||||
result = new[] { 2, 6, 10, 14 };
|
||||
|
||||
CollectionAssert.AreEqual(copy, result);
|
||||
|
||||
Assert.ThrowsException<ArgumentException>(() => ((ReadOnlySpan2D<int>)array).GetRow(0).CopyTo(default(RefEnumerable<int>)));
|
||||
Assert.ThrowsException<ArgumentException>(() => ((ReadOnlySpan2D<int>)array).GetRow(0).CopyTo(default(Span<int>)));
|
||||
|
||||
Assert.ThrowsException<ArgumentException>(() => ((ReadOnlySpan2D<int>)array).GetColumn(0).CopyTo(default(RefEnumerable<int>)));
|
||||
Assert.ThrowsException<ArgumentException>(() => ((ReadOnlySpan2D<int>)array).GetColumn(0).CopyTo(default(Span<int>)));
|
||||
|
||||
Assert.IsTrue(span2D.GetRow(2).TryCopyTo(copy));
|
||||
Assert.IsFalse(span2D.GetRow(2).TryCopyTo(default(Span<int>)));
|
||||
|
||||
result = new[] { 9, 10, 11, 12 };
|
||||
|
||||
CollectionAssert.AreEqual(copy, result);
|
||||
}
|
||||
|
||||
[TestCategory("ArrayExtensions")]
|
||||
[TestMethod]
|
||||
public void Test_ArrayExtensions_2D_RefEnumerable_Misc()
|
||||
{
|
||||
int[,] array1 =
|
||||
{
|
||||
{ 1, 2, 3, 4 },
|
||||
{ 5, 6, 7, 8 },
|
||||
{ 9, 10, 11, 12 },
|
||||
{ 13, 14, 15, 16 }
|
||||
};
|
||||
|
||||
int[,] array2 = new int[4, 4];
|
||||
|
||||
// Copy to enumerable with source step == 1, destination step == 1
|
||||
array1.GetRow(0).CopyTo(array2.GetRow(0));
|
||||
|
||||
// Copy enumerable with source step == 1, destination step != 1
|
||||
array1.GetRow(1).CopyTo(array2.GetColumn(1));
|
||||
|
||||
// Copy enumerable with source step != 1, destination step == 1
|
||||
array1.GetColumn(2).CopyTo(array2.GetRow(2));
|
||||
|
||||
// Copy enumerable with source step != 1, destination step != 1
|
||||
array1.GetColumn(3).CopyTo(array2.GetColumn(3));
|
||||
|
||||
int[,] result =
|
||||
{
|
||||
{ 1, 5, 3, 4 },
|
||||
{ 0, 6, 0, 8 },
|
||||
{ 3, 7, 11, 12 },
|
||||
{ 0, 8, 0, 16 }
|
||||
};
|
||||
|
||||
CollectionAssert.AreEqual(array2, result);
|
||||
|
||||
// Test a valid and an invalid TryCopyTo call with the RefEnumerable<T> overload
|
||||
bool shouldBeTrue = array1.GetRow(0).TryCopyTo(array2.GetColumn(0));
|
||||
bool shouldBeFalse = array1.GetRow(0).TryCopyTo(default(RefEnumerable<int>));
|
||||
|
||||
result = new[,]
|
||||
{
|
||||
{ 1, 5, 3, 4 },
|
||||
{ 2, 6, 0, 8 },
|
||||
{ 3, 7, 11, 12 },
|
||||
{ 4, 8, 0, 16 }
|
||||
};
|
||||
|
||||
CollectionAssert.AreEqual(array2, result);
|
||||
|
||||
Assert.IsTrue(shouldBeTrue);
|
||||
Assert.IsFalse(shouldBeFalse);
|
||||
}
|
||||
|
||||
[TestCategory("ArrayExtensions")]
|
||||
|
@ -286,6 +445,7 @@ namespace UnitTests.HighPerformance.Extensions
|
|||
|
||||
Span<int> span = array.AsSpan();
|
||||
|
||||
// Check that the empty array was loaded properly
|
||||
Assert.AreEqual(span.Length, array.Length);
|
||||
Assert.IsTrue(span.IsEmpty);
|
||||
}
|
||||
|
@ -303,11 +463,14 @@ namespace UnitTests.HighPerformance.Extensions
|
|||
|
||||
Span<int> span = array.AsSpan();
|
||||
|
||||
// Test the total length of the span
|
||||
Assert.AreEqual(span.Length, array.Length);
|
||||
|
||||
ref int r0 = ref array[0, 0];
|
||||
ref int r1 = ref span[0];
|
||||
|
||||
// Similarly to the top methods, here we compare a given reference to
|
||||
// ensure they point to the right element back in the original array.
|
||||
Assert.IsTrue(Unsafe.AreSame(ref r0, ref r1));
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Runtime.CompilerServices;
|
||||
using Microsoft.Toolkit.HighPerformance.Extensions;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace UnitTests.HighPerformance.Extensions
|
||||
{
|
||||
public partial class Test_ArrayExtensions
|
||||
{
|
||||
[TestCategory("ArrayExtensions")]
|
||||
[TestMethod]
|
||||
public void Test_ArrayExtensions_3D_DangerousGetReference_Int()
|
||||
{
|
||||
int[,,] array = new int[10, 20, 12];
|
||||
|
||||
// See comments in Test_ArrayExtensions.1D for how these tests work
|
||||
ref int r0 = ref array.DangerousGetReference();
|
||||
ref int r1 = ref array[0, 0, 0];
|
||||
|
||||
Assert.IsTrue(Unsafe.AreSame(ref r0, ref r1));
|
||||
}
|
||||
|
||||
[TestCategory("ArrayExtensions")]
|
||||
[TestMethod]
|
||||
public void Test_ArrayExtensions_3D_DangerousGetReference_String()
|
||||
{
|
||||
string[,,] array = new string[10, 20, 12];
|
||||
|
||||
ref string r0 = ref array.DangerousGetReference();
|
||||
ref string r1 = ref array[0, 0, 0];
|
||||
|
||||
Assert.IsTrue(Unsafe.AreSame(ref r0, ref r1));
|
||||
}
|
||||
|
||||
[TestCategory("ArrayExtensions")]
|
||||
[TestMethod]
|
||||
public void Test_ArrayExtensions_3D_DangerousGetReferenceAt_Zero()
|
||||
{
|
||||
int[,,] array = new int[10, 20, 12];
|
||||
|
||||
ref int r0 = ref array.DangerousGetReferenceAt(0, 0, 0);
|
||||
ref int r1 = ref array[0, 0, 0];
|
||||
|
||||
Assert.IsTrue(Unsafe.AreSame(ref r0, ref r1));
|
||||
}
|
||||
|
||||
[TestCategory("ArrayExtensions")]
|
||||
[TestMethod]
|
||||
public void Test_ArrayExtensions_3D_DangerousGetReferenceAt_Index()
|
||||
{
|
||||
int[,,] array = new int[10, 20, 12];
|
||||
|
||||
ref int r0 = ref array.DangerousGetReferenceAt(5, 3, 4);
|
||||
ref int r1 = ref array[5, 3, 4];
|
||||
|
||||
Assert.IsTrue(Unsafe.AreSame(ref r0, ref r1));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -15,16 +15,18 @@ namespace UnitTests.HighPerformance.Extensions
|
|||
[TestMethod]
|
||||
public void Test_BoolExtensions_True()
|
||||
{
|
||||
Assert.AreEqual(1, true.ToInt(), nameof(Test_BoolExtensions_True));
|
||||
Assert.AreEqual(1, (DateTime.Now.Year > 0).ToInt(), nameof(Test_BoolExtensions_True));
|
||||
// There tests all just run a couple of boolean expressions and validate that the extension
|
||||
// correctly produces either 1 or 0 depending on whether the expression was true or false.
|
||||
Assert.AreEqual(1, true.ToByte(), nameof(Test_BoolExtensions_True));
|
||||
Assert.AreEqual(1, (DateTime.Now.Year > 0).ToByte(), nameof(Test_BoolExtensions_True));
|
||||
}
|
||||
|
||||
[TestCategory("BoolExtensions")]
|
||||
[TestMethod]
|
||||
public void Test_BoolExtensions_False()
|
||||
{
|
||||
Assert.AreEqual(0, false.ToInt(), nameof(Test_BoolExtensions_False));
|
||||
Assert.AreEqual(0, (DateTime.Now.Year > 3000).ToInt(), nameof(Test_BoolExtensions_False));
|
||||
Assert.AreEqual(0, false.ToByte(), nameof(Test_BoolExtensions_False));
|
||||
Assert.AreEqual(0, (DateTime.Now.Year > 3000).ToByte(), nameof(Test_BoolExtensions_False));
|
||||
}
|
||||
|
||||
[TestCategory("BoolExtensions")]
|
||||
|
|
|
@ -103,7 +103,7 @@ namespace UnitTests.HighPerformance.Extensions
|
|||
|
||||
public Int(int value) => Value = value;
|
||||
|
||||
public bool Equals(Int other)
|
||||
public bool Equals(Int? other)
|
||||
{
|
||||
if (other is null)
|
||||
{
|
||||
|
@ -118,7 +118,7 @@ namespace UnitTests.HighPerformance.Extensions
|
|||
return this.Value == other.Value;
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
return ReferenceEquals(this, obj) || (obj is Int other && Equals(other));
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using Microsoft.Toolkit.HighPerformance.Extensions;
|
||||
|
@ -12,6 +13,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting;
|
|||
namespace UnitTests.HighPerformance.Extensions
|
||||
{
|
||||
[TestClass]
|
||||
[SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1601", Justification = "Partial test class")]
|
||||
public partial class Test_ReadOnlySpanExtensions
|
||||
{
|
||||
[TestCategory("ReadOnlySpanExtensions")]
|
||||
|
@ -267,5 +269,47 @@ namespace UnitTests.HighPerformance.Extensions
|
|||
|
||||
CollectionAssert.AreEqual(result, tokens);
|
||||
}
|
||||
|
||||
[TestCategory("ReadOnlySpanExtensions")]
|
||||
[TestMethod]
|
||||
public void Test_ReadOnlySpanExtensions_CopyTo_RefEnumerable()
|
||||
{
|
||||
int[,] array = new int[4, 5];
|
||||
|
||||
ReadOnlySpan<int>
|
||||
values1 = new[] { 10, 20, 30, 40, 50 },
|
||||
values2 = new[] { 11, 22, 33, 44, 55 };
|
||||
|
||||
// Copy a span to a target row and column with valid lengths
|
||||
values1.CopyTo(array.GetRow(0));
|
||||
values2.Slice(0, 4).CopyTo(array.GetColumn(1));
|
||||
|
||||
int[,] result =
|
||||
{
|
||||
{ 10, 11, 30, 40, 50 },
|
||||
{ 0, 22, 0, 0, 0 },
|
||||
{ 0, 33, 0, 0, 0 },
|
||||
{ 0, 44, 0, 0, 0 }
|
||||
};
|
||||
|
||||
CollectionAssert.AreEqual(array, result);
|
||||
|
||||
// Try to copy to a valid row and an invalid column (too short for the source span)
|
||||
bool shouldBeTrue = values1.TryCopyTo(array.GetRow(2));
|
||||
bool shouldBeFalse = values2.TryCopyTo(array.GetColumn(3));
|
||||
|
||||
Assert.IsTrue(shouldBeTrue);
|
||||
Assert.IsFalse(shouldBeFalse);
|
||||
|
||||
result = new[,]
|
||||
{
|
||||
{ 10, 11, 30, 40, 50 },
|
||||
{ 0, 22, 0, 0, 0 },
|
||||
{ 10, 20, 30, 40, 50 },
|
||||
{ 0, 44, 0, 0, 0 }
|
||||
};
|
||||
|
||||
CollectionAssert.AreEqual(array, result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -168,5 +168,47 @@ namespace UnitTests.HighPerformance.Extensions
|
|||
Assert.Fail("Empty source sequence");
|
||||
}
|
||||
}
|
||||
|
||||
[TestCategory("SpanExtensions")]
|
||||
[TestMethod]
|
||||
public void Test_SpanExtensions_CopyTo_RefEnumerable()
|
||||
{
|
||||
int[,] array = new int[4, 5];
|
||||
|
||||
int[]
|
||||
values1 = { 10, 20, 30, 40, 50 },
|
||||
values2 = { 11, 22, 33, 44, 55 };
|
||||
|
||||
// Copy a span to a target row and column with valid lengths
|
||||
values1.AsSpan().CopyTo(array.GetRow(0));
|
||||
values2.AsSpan(0, 4).CopyTo(array.GetColumn(1));
|
||||
|
||||
int[,] result =
|
||||
{
|
||||
{ 10, 11, 30, 40, 50 },
|
||||
{ 0, 22, 0, 0, 0 },
|
||||
{ 0, 33, 0, 0, 0 },
|
||||
{ 0, 44, 0, 0, 0 }
|
||||
};
|
||||
|
||||
CollectionAssert.AreEqual(array, result);
|
||||
|
||||
// Try to copy to a valid row and an invalid column (too short for the source span)
|
||||
bool shouldBeTrue = values1.AsSpan().TryCopyTo(array.GetRow(2));
|
||||
bool shouldBeFalse = values2.AsSpan().TryCopyTo(array.GetColumn(3));
|
||||
|
||||
Assert.IsTrue(shouldBeTrue);
|
||||
Assert.IsFalse(shouldBeFalse);
|
||||
|
||||
result = new[,]
|
||||
{
|
||||
{ 10, 11, 30, 40, 50 },
|
||||
{ 0, 22, 0, 0, 0 },
|
||||
{ 10, 20, 30, 40, 50 },
|
||||
{ 0, 44, 0, 0, 0 }
|
||||
};
|
||||
|
||||
CollectionAssert.AreEqual(array, result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -63,7 +63,6 @@ namespace UnitTests.HighPerformance.Helpers
|
|||
TestForType<char>();
|
||||
}
|
||||
|
||||
#if NETCOREAPP3_1
|
||||
[TestCategory("HashCodeOfT")]
|
||||
[TestMethod]
|
||||
public void Test_HashCodeOfT_ManagedType_TestRepeat()
|
||||
|
@ -89,7 +88,6 @@ namespace UnitTests.HighPerformance.Helpers
|
|||
Assert.AreEqual(hash1, hash2, $"Failed {typeof(string)} test with count {count}: got {hash1} and then {hash2}");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Performs a test for a specified type.
|
||||
|
|
|
@ -0,0 +1,83 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
using Microsoft.Toolkit.HighPerformance.Extensions;
|
||||
using Microsoft.Toolkit.HighPerformance.Helpers;
|
||||
using Microsoft.Toolkit.HighPerformance.Memory;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace UnitTests.HighPerformance.Helpers
|
||||
{
|
||||
public partial class Test_ParallelHelper
|
||||
{
|
||||
[TestCategory("ParallelHelper")]
|
||||
[TestMethod]
|
||||
[DataRow(1, 1, 0, 0, 1, 1)]
|
||||
[DataRow(1, 2, 0, 0, 1, 2)]
|
||||
[DataRow(2, 3, 0, 0, 2, 3)]
|
||||
[DataRow(2, 3, 0, 1, 2, 2)]
|
||||
[DataRow(3, 3, 1, 1, 2, 2)]
|
||||
[DataRow(12, 12, 2, 4, 3, 3)]
|
||||
[DataRow(64, 64, 0, 0, 32, 32)]
|
||||
[DataRow(64, 64, 13, 14, 23, 22)]
|
||||
public unsafe void Test_ParallelHelper_ForEach_In2D(
|
||||
int sizeY,
|
||||
int sizeX,
|
||||
int row,
|
||||
int column,
|
||||
int height,
|
||||
int width)
|
||||
{
|
||||
int[,] data = CreateRandomData2D(sizeY, sizeX);
|
||||
|
||||
// Create a memory wrapping the random array with the given parameters
|
||||
ReadOnlyMemory2D<int> memory = data.AsMemory2D(row, column, height, width);
|
||||
|
||||
Assert.AreEqual(memory.Length, height * width);
|
||||
Assert.AreEqual(memory.Height, height);
|
||||
Assert.AreEqual(memory.Width, width);
|
||||
|
||||
int sum = 0;
|
||||
|
||||
// Sum all the items in parallel. The Summer type takes a pointer to a target value
|
||||
// and adds values to it in a thread-safe manner (with an interlocked add).
|
||||
ParallelHelper.ForEach(memory, new Summer(&sum));
|
||||
|
||||
int expected = 0;
|
||||
|
||||
// Calculate the sum iteratively as a baseline for comparison
|
||||
foreach (int n in memory.Span)
|
||||
{
|
||||
expected += n;
|
||||
}
|
||||
|
||||
Assert.AreEqual(sum, expected, $"The sum doesn't match, was {sum} instead of {expected}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a random 2D <see cref="int"/> array filled with random numbers.
|
||||
/// </summary>
|
||||
/// <param name="height">The height of the array to create.</param>
|
||||
/// <param name="width">The width of the array to create.</param>
|
||||
/// <returns>An array of random <see cref="int"/> elements.</returns>
|
||||
[Pure]
|
||||
private static int[,] CreateRandomData2D(int height, int width)
|
||||
{
|
||||
var random = new Random((height * 33) + width);
|
||||
|
||||
int[,] data = new int[height, width];
|
||||
|
||||
foreach (ref int n in data.AsSpan2D())
|
||||
{
|
||||
n = random.Next(0, byte.MaxValue);
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using Microsoft.Toolkit.HighPerformance.Extensions;
|
||||
using Microsoft.Toolkit.HighPerformance.Helpers;
|
||||
using Microsoft.Toolkit.HighPerformance.Memory;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace UnitTests.HighPerformance.Helpers
|
||||
{
|
||||
public partial class Test_ParallelHelper
|
||||
{
|
||||
[TestCategory("ParallelHelper")]
|
||||
[TestMethod]
|
||||
[DataRow(1, 1, 0, 0, 1, 1)]
|
||||
[DataRow(1, 2, 0, 0, 1, 2)]
|
||||
[DataRow(2, 3, 0, 0, 2, 3)]
|
||||
[DataRow(2, 3, 0, 1, 2, 2)]
|
||||
[DataRow(3, 3, 1, 1, 2, 2)]
|
||||
[DataRow(12, 12, 2, 4, 3, 3)]
|
||||
[DataRow(64, 64, 0, 0, 32, 32)]
|
||||
[DataRow(64, 64, 13, 14, 23, 22)]
|
||||
public void Test_ParallelHelper_ForEach_Ref2D(
|
||||
int sizeY,
|
||||
int sizeX,
|
||||
int row,
|
||||
int column,
|
||||
int height,
|
||||
int width)
|
||||
{
|
||||
int[,]
|
||||
data = CreateRandomData2D(sizeY, sizeX),
|
||||
copy = (int[,])data.Clone();
|
||||
|
||||
// Prepare the target data iteratively
|
||||
foreach (ref int n in copy.AsSpan2D(row, column, height, width))
|
||||
{
|
||||
n = unchecked(n * 397);
|
||||
}
|
||||
|
||||
Memory2D<int> memory = data.AsMemory2D(row, column, height, width);
|
||||
|
||||
Assert.AreEqual(memory.Length, height * width);
|
||||
Assert.AreEqual(memory.Height, height);
|
||||
Assert.AreEqual(memory.Width, width);
|
||||
|
||||
// Do the same computation in paralellel, then compare the two arrays
|
||||
ParallelHelper.ForEach(memory, new Multiplier(397));
|
||||
|
||||
CollectionAssert.AreEqual(data, copy);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,544 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
#if !WINDOWS_UWP
|
||||
using System.Runtime.CompilerServices;
|
||||
using Microsoft.Toolkit.HighPerformance.Extensions;
|
||||
#endif
|
||||
using Microsoft.Toolkit.HighPerformance.Memory;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace UnitTests.HighPerformance.Memory
|
||||
{
|
||||
[TestClass]
|
||||
[SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1649", Justification = "Test class for generic type")]
|
||||
public class Test_Memory2DT
|
||||
{
|
||||
[TestCategory("Memory2DT")]
|
||||
[TestMethod]
|
||||
public void Test_Memory2DT_Empty()
|
||||
{
|
||||
// Create a few empty Memory2D<T> instances in different ways and
|
||||
// check to ensure the right parameters were used to initialize them.
|
||||
Memory2D<int> empty1 = default;
|
||||
|
||||
Assert.IsTrue(empty1.IsEmpty);
|
||||
Assert.AreEqual(empty1.Length, 0);
|
||||
Assert.AreEqual(empty1.Width, 0);
|
||||
Assert.AreEqual(empty1.Height, 0);
|
||||
|
||||
Memory2D<string> empty2 = Memory2D<string>.Empty;
|
||||
|
||||
Assert.IsTrue(empty2.IsEmpty);
|
||||
Assert.AreEqual(empty2.Length, 0);
|
||||
Assert.AreEqual(empty2.Width, 0);
|
||||
Assert.AreEqual(empty2.Height, 0);
|
||||
|
||||
Memory2D<int> empty3 = new int[4, 0];
|
||||
|
||||
Assert.IsTrue(empty3.IsEmpty);
|
||||
Assert.AreEqual(empty3.Length, 0);
|
||||
Assert.AreEqual(empty3.Width, 0);
|
||||
Assert.AreEqual(empty3.Height, 4);
|
||||
|
||||
Memory2D<int> empty4 = new int[0, 7];
|
||||
|
||||
Assert.IsTrue(empty4.IsEmpty);
|
||||
Assert.AreEqual(empty4.Length, 0);
|
||||
Assert.AreEqual(empty4.Width, 7);
|
||||
Assert.AreEqual(empty4.Height, 0);
|
||||
}
|
||||
|
||||
[TestCategory("Memory2DT")]
|
||||
[TestMethod]
|
||||
public void Test_Memory2DT_Array1DConstructor()
|
||||
{
|
||||
int[] array =
|
||||
{
|
||||
1, 2, 3, 4, 5, 6
|
||||
};
|
||||
|
||||
// Create a memory over a 1D array with 2D data in row-major order. This tests
|
||||
// the T[] array constructor for Memory2D<T> with custom size and pitch.
|
||||
Memory2D<int> memory2d = new Memory2D<int>(array, 1, 2, 2, 1);
|
||||
|
||||
Assert.IsFalse(memory2d.IsEmpty);
|
||||
Assert.AreEqual(memory2d.Length, 4);
|
||||
Assert.AreEqual(memory2d.Width, 2);
|
||||
Assert.AreEqual(memory2d.Height, 2);
|
||||
Assert.AreEqual(memory2d.Span[0, 0], 2);
|
||||
Assert.AreEqual(memory2d.Span[1, 1], 6);
|
||||
|
||||
// Also ensure the right exceptions are thrown with invalid parameters, such as
|
||||
// negative indices, indices out of range, values that are too big, etc.
|
||||
Assert.ThrowsException<ArrayTypeMismatchException>(() => new Memory2D<object>(new string[1], 1, 1));
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new Memory2D<int>(array, -99, 1, 1, 1));
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new Memory2D<int>(array, 0, -10, 1, 1));
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new Memory2D<int>(array, 0, 1, 1, -1));
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new Memory2D<int>(array, 0, 1, -100, 1));
|
||||
Assert.ThrowsException<ArgumentException>(() => new Memory2D<int>(array, 0, 2, 4, 0));
|
||||
Assert.ThrowsException<ArgumentException>(() => new Memory2D<int>(array, 0, 3, 3, 0));
|
||||
Assert.ThrowsException<ArgumentException>(() => new Memory2D<int>(array, 1, 2, 3, 0));
|
||||
Assert.ThrowsException<ArgumentException>(() => new Memory2D<int>(array, 0, 10, 1, 120));
|
||||
}
|
||||
|
||||
[TestCategory("Memory2DT")]
|
||||
[TestMethod]
|
||||
public void Test_Memory2DT_Array2DConstructor_1()
|
||||
{
|
||||
int[,] array =
|
||||
{
|
||||
{ 1, 2, 3 },
|
||||
{ 4, 5, 6 }
|
||||
};
|
||||
|
||||
// Test the constructor taking a T[,] array that is mapped directly (no slicing)
|
||||
Memory2D<int> memory2d = new Memory2D<int>(array);
|
||||
|
||||
Assert.IsFalse(memory2d.IsEmpty);
|
||||
Assert.AreEqual(memory2d.Length, 6);
|
||||
Assert.AreEqual(memory2d.Width, 3);
|
||||
Assert.AreEqual(memory2d.Height, 2);
|
||||
Assert.AreEqual(memory2d.Span[0, 1], 2);
|
||||
Assert.AreEqual(memory2d.Span[1, 2], 6);
|
||||
|
||||
// Here we test the check for covariance: we can't create a Memory2D<T> from a U[,] array
|
||||
// where U is assignable to T (as in, U : T). This would cause a type safety violation on write.
|
||||
Assert.ThrowsException<ArrayTypeMismatchException>(() => new Memory2D<object>(new string[1, 2]));
|
||||
}
|
||||
|
||||
[TestCategory("Memory2DT")]
|
||||
[TestMethod]
|
||||
public void Test_Memory2DT_Array2DConstructor_2()
|
||||
{
|
||||
int[,] array =
|
||||
{
|
||||
{ 1, 2, 3 },
|
||||
{ 4, 5, 6 }
|
||||
};
|
||||
|
||||
// Same as above, but this time we also slice the memory to test the other constructor
|
||||
Memory2D<int> memory2d = new Memory2D<int>(array, 0, 1, 2, 2);
|
||||
|
||||
Assert.IsFalse(memory2d.IsEmpty);
|
||||
Assert.AreEqual(memory2d.Length, 4);
|
||||
Assert.AreEqual(memory2d.Width, 2);
|
||||
Assert.AreEqual(memory2d.Height, 2);
|
||||
Assert.AreEqual(memory2d.Span[0, 0], 2);
|
||||
Assert.AreEqual(memory2d.Span[1, 1], 6);
|
||||
|
||||
Assert.ThrowsException<ArrayTypeMismatchException>(() => new Memory2D<object>(new string[1, 2], 0, 0, 2, 2));
|
||||
}
|
||||
|
||||
[TestCategory("Memory2DT")]
|
||||
[TestMethod]
|
||||
public void Test_Memory2DT_Array3DConstructor_1()
|
||||
{
|
||||
int[,,] array =
|
||||
{
|
||||
{
|
||||
{ 1, 2, 3 },
|
||||
{ 4, 5, 6 }
|
||||
},
|
||||
{
|
||||
{ 10, 20, 30 },
|
||||
{ 40, 50, 60 }
|
||||
}
|
||||
};
|
||||
|
||||
// Same as above, but we test the constructor taking a layer within a 3D array
|
||||
Memory2D<int> memory2d = new Memory2D<int>(array, 1);
|
||||
|
||||
Assert.IsFalse(memory2d.IsEmpty);
|
||||
Assert.AreEqual(memory2d.Length, 6);
|
||||
Assert.AreEqual(memory2d.Width, 3);
|
||||
Assert.AreEqual(memory2d.Height, 2);
|
||||
Assert.AreEqual(memory2d.Span[0, 1], 20);
|
||||
Assert.AreEqual(memory2d.Span[1, 2], 60);
|
||||
|
||||
// A couple of tests for invalid parameters, ie. layers out of range
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new Memory2D<int>(array, -1));
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new Memory2D<int>(array, 2));
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new Memory2D<int>(array, 20));
|
||||
}
|
||||
|
||||
[TestCategory("Memory2DT")]
|
||||
[TestMethod]
|
||||
public void Test_Memory2DT_Array3DConstructor_2()
|
||||
{
|
||||
int[,,] array =
|
||||
{
|
||||
{
|
||||
{ 1, 2, 3 },
|
||||
{ 4, 5, 6 }
|
||||
},
|
||||
{
|
||||
{ 10, 20, 30 },
|
||||
{ 40, 50, 60 }
|
||||
}
|
||||
};
|
||||
|
||||
// Same as above, but we also slice the target layer in the 3D array. In this case we're creating
|
||||
// a Memory<int> instance from a slice in the layer at depth 1 in our 3D array, and with an area
|
||||
// starting at coorsinates (0, 1), with a height of 2 and width of 2. So we want to wrap the
|
||||
// square with items [20, 30, 50, 60] in the second layer of the 3D array above.
|
||||
Memory2D<int> memory2d = new Memory2D<int>(array, 1, 0, 1, 2, 2);
|
||||
|
||||
Assert.IsFalse(memory2d.IsEmpty);
|
||||
Assert.AreEqual(memory2d.Length, 4);
|
||||
Assert.AreEqual(memory2d.Width, 2);
|
||||
Assert.AreEqual(memory2d.Height, 2);
|
||||
Assert.AreEqual(memory2d.Span[0, 0], 20);
|
||||
Assert.AreEqual(memory2d.Span[1, 1], 60);
|
||||
|
||||
// Same as above, testing a few cases with invalid parameters
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new Memory2D<int>(array, -1, 1, 1, 1, 1));
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new Memory2D<int>(array, 1, -1, 1, 1, 1));
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new Memory2D<int>(array, 1, 1, -1, 1, 1));
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new Memory2D<int>(array, 1, 1, 1, -1, 1));
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new Memory2D<int>(array, 1, 1, 1, 1, -1));
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new Memory2D<int>(array, 2, 0, 0, 2, 3));
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new Memory2D<int>(array, 0, 0, 1, 2, 3));
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new Memory2D<int>(array, 0, 0, 0, 2, 4));
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new Memory2D<int>(array, 0, 0, 0, 3, 3));
|
||||
}
|
||||
|
||||
#if !WINDOWS_UWP
|
||||
[TestCategory("Memory2DT")]
|
||||
[TestMethod]
|
||||
public void Test_Memory2DT_MemoryConstructor()
|
||||
{
|
||||
Memory<int> memory = new[]
|
||||
{
|
||||
1, 2, 3, 4, 5, 6
|
||||
};
|
||||
|
||||
// We also test the constructor that takes an input Memory<T> instance.
|
||||
// This is only available on runtimes with fast Span<T> support, as otherwise
|
||||
// the implementation would be too complex and slow to work in this case.
|
||||
// Conceptually, this works the same as when wrapping a 1D array with row-major items.
|
||||
Memory2D<int> memory2d = memory.AsMemory2D(1, 2, 2, 1);
|
||||
|
||||
Assert.IsFalse(memory2d.IsEmpty);
|
||||
Assert.AreEqual(memory2d.Length, 4);
|
||||
Assert.AreEqual(memory2d.Width, 2);
|
||||
Assert.AreEqual(memory2d.Height, 2);
|
||||
Assert.AreEqual(memory2d.Span[0, 0], 2);
|
||||
Assert.AreEqual(memory2d.Span[1, 1], 6);
|
||||
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => memory.AsMemory2D(-99, 1, 1, 1));
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => memory.AsMemory2D(0, -10, 1, 1));
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => memory.AsMemory2D(0, 1, 1, -1));
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => memory.AsMemory2D(0, 1, -100, 1));
|
||||
Assert.ThrowsException<ArgumentException>(() => memory.AsMemory2D(0, 2, 4, 0));
|
||||
Assert.ThrowsException<ArgumentException>(() => memory.AsMemory2D(0, 3, 3, 0));
|
||||
Assert.ThrowsException<ArgumentException>(() => memory.AsMemory2D(1, 2, 3, 0));
|
||||
Assert.ThrowsException<ArgumentException>(() => memory.AsMemory2D(0, 10, 1, 120));
|
||||
}
|
||||
#endif
|
||||
|
||||
[TestCategory("Memory2DT")]
|
||||
[TestMethod]
|
||||
public void Test_Memory2DT_Slice_1()
|
||||
{
|
||||
int[,] array =
|
||||
{
|
||||
{ 1, 2, 3 },
|
||||
{ 4, 5, 6 }
|
||||
};
|
||||
|
||||
Memory2D<int> memory2d = new Memory2D<int>(array);
|
||||
|
||||
// Test a slice from a Memory2D<T> with valid parameters
|
||||
Memory2D<int> slice1 = memory2d.Slice(1, 1, 1, 2);
|
||||
|
||||
Assert.AreEqual(slice1.Length, 2);
|
||||
Assert.AreEqual(slice1.Height, 1);
|
||||
Assert.AreEqual(slice1.Width, 2);
|
||||
Assert.AreEqual(slice1.Span[0, 0], 5);
|
||||
Assert.AreEqual(slice1.Span[0, 1], 6);
|
||||
|
||||
// Same above, but we test slicing a pre-sliced instance as well. This
|
||||
// is done to verify that the internal offsets are properly tracked
|
||||
// across multiple slicing operations, instead of just in the first.
|
||||
Memory2D<int> slice2 = memory2d.Slice(0, 1, 2, 2);
|
||||
|
||||
Assert.AreEqual(slice2.Length, 4);
|
||||
Assert.AreEqual(slice2.Height, 2);
|
||||
Assert.AreEqual(slice2.Width, 2);
|
||||
Assert.AreEqual(slice2.Span[0, 0], 2);
|
||||
Assert.AreEqual(slice2.Span[1, 0], 5);
|
||||
Assert.AreEqual(slice2.Span[1, 1], 6);
|
||||
|
||||
// A few invalid slicing operations, with out of range parameters
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new Memory2D<int>(array).Slice(-1, 1, 1, 1));
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new Memory2D<int>(array).Slice(1, -1, 1, 1));
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new Memory2D<int>(array).Slice(1, 1, 1, -1));
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new Memory2D<int>(array).Slice(1, 1, -1, 1));
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new Memory2D<int>(array).Slice(10, 1, 1, 1));
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new Memory2D<int>(array).Slice(1, 12, 1, 12));
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new Memory2D<int>(array).Slice(1, 1, 55, 1));
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new Memory2D<int>(array).Slice(0, 0, 2, 4));
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new Memory2D<int>(array).Slice(0, 0, 3, 3));
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new Memory2D<int>(array).Slice(0, 1, 2, 3));
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new Memory2D<int>(array).Slice(1, 0, 2, 3));
|
||||
}
|
||||
|
||||
[TestCategory("Memory2DT")]
|
||||
[TestMethod]
|
||||
public void Test_Memory2DT_Slice_2()
|
||||
{
|
||||
int[,] array =
|
||||
{
|
||||
{ 1, 2, 3 },
|
||||
{ 4, 5, 6 }
|
||||
};
|
||||
|
||||
Memory2D<int> memory2d = new Memory2D<int>(array);
|
||||
|
||||
// Mostly the same test as above, just with different parameters
|
||||
Memory2D<int> slice1 = memory2d.Slice(0, 0, 2, 2);
|
||||
|
||||
Assert.AreEqual(slice1.Length, 4);
|
||||
Assert.AreEqual(slice1.Height, 2);
|
||||
Assert.AreEqual(slice1.Width, 2);
|
||||
Assert.AreEqual(slice1.Span[0, 0], 1);
|
||||
Assert.AreEqual(slice1.Span[1, 1], 5);
|
||||
|
||||
Memory2D<int> slice2 = slice1.Slice(1, 0, 1, 2);
|
||||
|
||||
Assert.AreEqual(slice2.Length, 2);
|
||||
Assert.AreEqual(slice2.Height, 1);
|
||||
Assert.AreEqual(slice2.Width, 2);
|
||||
Assert.AreEqual(slice2.Span[0, 0], 4);
|
||||
Assert.AreEqual(slice2.Span[0, 1], 5);
|
||||
|
||||
Memory2D<int> slice3 = slice2.Slice(0, 1, 1, 1);
|
||||
|
||||
Assert.AreEqual(slice3.Length, 1);
|
||||
Assert.AreEqual(slice3.Height, 1);
|
||||
Assert.AreEqual(slice3.Width, 1);
|
||||
Assert.AreEqual(slice3.Span[0, 0], 5);
|
||||
}
|
||||
|
||||
[TestCategory("Memory2DT")]
|
||||
[TestMethod]
|
||||
public void Test_Memory2DT_TryGetMemory_1()
|
||||
{
|
||||
int[,] array =
|
||||
{
|
||||
{ 1, 2, 3 },
|
||||
{ 4, 5, 6 }
|
||||
};
|
||||
|
||||
Memory2D<int> memory2d = new Memory2D<int>(array);
|
||||
|
||||
// Here we test that we can get a Memory<T> from a 2D one when the underlying
|
||||
// data is contiguous. Note that in this case this can only work on runtimes
|
||||
// with fast Span<T> support, because otherwise it's not possible to get a
|
||||
// Memory<T> (or a Span<T> too, for that matter) from a 2D array.
|
||||
bool success = memory2d.TryGetMemory(out Memory<int> memory);
|
||||
|
||||
#if WINDOWS_UWP
|
||||
Assert.IsFalse(success);
|
||||
Assert.IsTrue(memory.IsEmpty);
|
||||
#else
|
||||
Assert.IsTrue(success);
|
||||
Assert.AreEqual(memory.Length, array.Length);
|
||||
Assert.IsTrue(Unsafe.AreSame(ref array[0, 0], ref memory.Span[0]));
|
||||
#endif
|
||||
}
|
||||
|
||||
[TestCategory("Memory2DT")]
|
||||
[TestMethod]
|
||||
public void Test_Memory2DT_TryGetMemory_2()
|
||||
{
|
||||
int[] array = { 1, 2, 3, 4 };
|
||||
|
||||
Memory2D<int> memory2d = new Memory2D<int>(array, 2, 2);
|
||||
|
||||
// Same test as above, but this will always succeed on all runtimes,
|
||||
// as creating a Memory<T> from a 1D array is always supported.
|
||||
bool success = memory2d.TryGetMemory(out Memory<int> memory);
|
||||
|
||||
Assert.IsTrue(success);
|
||||
Assert.AreEqual(memory.Length, array.Length);
|
||||
Assert.AreEqual(memory.Span[2], 3);
|
||||
}
|
||||
|
||||
#if !WINDOWS_UWP
|
||||
[TestCategory("Memory2DT")]
|
||||
[TestMethod]
|
||||
public void Test_Memory2DT_TryGetMemory_3()
|
||||
{
|
||||
Memory<int> data = new[] { 1, 2, 3, 4 };
|
||||
|
||||
Memory2D<int> memory2d = data.AsMemory2D(2, 2);
|
||||
|
||||
// Same as above, just with the extra Memory<T> indirection. Same as above,
|
||||
// this test is only supported on runtimes with fast Span<T> support.
|
||||
// On others, we just don't expose the Memory<T>.AsMemory2D extension.
|
||||
bool success = memory2d.TryGetMemory(out Memory<int> memory);
|
||||
|
||||
Assert.IsTrue(success);
|
||||
Assert.AreEqual(memory.Length, data.Length);
|
||||
Assert.AreEqual(memory.Span[2], 3);
|
||||
}
|
||||
#endif
|
||||
|
||||
[TestCategory("Memory2DT")]
|
||||
[TestMethod]
|
||||
public unsafe void Test_Memory2DT_Pin_1()
|
||||
{
|
||||
int[] array = { 1, 2, 3, 4 };
|
||||
|
||||
// We create a Memory2D<T> from an array and verify that pinning this
|
||||
// instance correctly returns a pointer to the right array element.
|
||||
Memory2D<int> memory2d = new Memory2D<int>(array, 2, 2);
|
||||
|
||||
using var pin = memory2d.Pin();
|
||||
|
||||
Assert.AreEqual(((int*)pin.Pointer)[0], 1);
|
||||
Assert.AreEqual(((int*)pin.Pointer)[3], 4);
|
||||
}
|
||||
|
||||
[TestCategory("Memory2DT")]
|
||||
[TestMethod]
|
||||
public unsafe void Test_Memory2DT_Pin_2()
|
||||
{
|
||||
int[] array = { 1, 2, 3, 4 };
|
||||
|
||||
// Same as above, but we test with a sliced Memory2D<T> instance
|
||||
Memory2D<int> memory2d = new Memory2D<int>(array, 2, 2);
|
||||
|
||||
using var pin = memory2d.Pin();
|
||||
|
||||
Assert.AreEqual(((int*)pin.Pointer)[0], 1);
|
||||
Assert.AreEqual(((int*)pin.Pointer)[3], 4);
|
||||
}
|
||||
|
||||
[TestCategory("Memory2DT")]
|
||||
[TestMethod]
|
||||
public void Test_Memory2DT_ToArray_1()
|
||||
{
|
||||
int[,] array =
|
||||
{
|
||||
{ 1, 2, 3 },
|
||||
{ 4, 5, 6 }
|
||||
};
|
||||
|
||||
// Here we create a Memory2D<T> instance from a 2D array and then verify that
|
||||
// calling ToArray() creates an array that matches the contents of the first.
|
||||
Memory2D<int> memory2d = new Memory2D<int>(array);
|
||||
|
||||
int[,] copy = memory2d.ToArray();
|
||||
|
||||
Assert.AreEqual(copy.GetLength(0), array.GetLength(0));
|
||||
Assert.AreEqual(copy.GetLength(1), array.GetLength(1));
|
||||
|
||||
CollectionAssert.AreEqual(array, copy);
|
||||
}
|
||||
|
||||
[TestCategory("Memory2DT")]
|
||||
[TestMethod]
|
||||
public void Test_Memory2DT_ToArray_2()
|
||||
{
|
||||
int[,] array =
|
||||
{
|
||||
{ 1, 2, 3 },
|
||||
{ 4, 5, 6 }
|
||||
};
|
||||
|
||||
// Same as above, but with a sliced Memory2D<T> instance
|
||||
Memory2D<int> memory2d = new Memory2D<int>(array, 0, 0, 2, 2);
|
||||
|
||||
int[,] copy = memory2d.ToArray();
|
||||
|
||||
Assert.AreEqual(copy.GetLength(0), 2);
|
||||
Assert.AreEqual(copy.GetLength(1), 2);
|
||||
|
||||
int[,] expected =
|
||||
{
|
||||
{ 1, 2 },
|
||||
{ 4, 5 }
|
||||
};
|
||||
|
||||
CollectionAssert.AreEqual(expected, copy);
|
||||
}
|
||||
|
||||
[TestCategory("Memory2DT")]
|
||||
[TestMethod]
|
||||
public void Test_Memory2DT_Equals()
|
||||
{
|
||||
int[,] array =
|
||||
{
|
||||
{ 1, 2, 3 },
|
||||
{ 4, 5, 6 }
|
||||
};
|
||||
|
||||
// Here we want to verify that the Memory2D<T>.Equals method works correctly. This is true
|
||||
// when the wrapped instance is the same, and the various internal offsets and sizes match.
|
||||
Memory2D<int> memory2d = new Memory2D<int>(array);
|
||||
|
||||
Assert.IsFalse(memory2d.Equals(null));
|
||||
Assert.IsFalse(memory2d.Equals(new Memory2D<int>(array, 0, 1, 2, 2)));
|
||||
Assert.IsTrue(memory2d.Equals(new Memory2D<int>(array)));
|
||||
Assert.IsTrue(memory2d.Equals(memory2d));
|
||||
|
||||
// This should work also when casting to a ReadOnlyMemory2D<T> instance
|
||||
ReadOnlyMemory2D<int> readOnlyMemory2d = memory2d;
|
||||
|
||||
Assert.IsTrue(memory2d.Equals(readOnlyMemory2d));
|
||||
Assert.IsFalse(memory2d.Equals(readOnlyMemory2d.Slice(0, 1, 2, 2)));
|
||||
}
|
||||
|
||||
[TestCategory("Memory2DT")]
|
||||
[TestMethod]
|
||||
public void Test_Memory2DT_GetHashCode()
|
||||
{
|
||||
// An emoty Memory2D<T> has just 0 as the hashcode
|
||||
Assert.AreEqual(Memory2D<int>.Empty.GetHashCode(), 0);
|
||||
|
||||
int[,] array =
|
||||
{
|
||||
{ 1, 2, 3 },
|
||||
{ 4, 5, 6 }
|
||||
};
|
||||
|
||||
Memory2D<int> memory2d = new Memory2D<int>(array);
|
||||
|
||||
// Ensure that the GetHashCode method is repeatable
|
||||
int a = memory2d.GetHashCode(), b = memory2d.GetHashCode();
|
||||
|
||||
Assert.AreEqual(a, b);
|
||||
|
||||
// The hashcode shouldn't match when the size is different
|
||||
int c = new Memory2D<int>(array, 0, 1, 2, 2).GetHashCode();
|
||||
|
||||
Assert.AreNotEqual(a, c);
|
||||
}
|
||||
|
||||
[TestCategory("Memory2DT")]
|
||||
[TestMethod]
|
||||
public void Test_Memory2DT_ToString()
|
||||
{
|
||||
int[,] array =
|
||||
{
|
||||
{ 1, 2, 3 },
|
||||
{ 4, 5, 6 }
|
||||
};
|
||||
|
||||
Memory2D<int> memory2d = new Memory2D<int>(array);
|
||||
|
||||
// Here we just want to verify that the type is nicely printed as expected, along with the size
|
||||
string text = memory2d.ToString();
|
||||
|
||||
const string expected = "Microsoft.Toolkit.HighPerformance.Memory.Memory2D<System.Int32>[2, 3]";
|
||||
|
||||
Assert.AreEqual(text, expected);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,492 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
#if !WINDOWS_UWP
|
||||
using System.Runtime.CompilerServices;
|
||||
using Microsoft.Toolkit.HighPerformance.Extensions;
|
||||
#endif
|
||||
using Microsoft.Toolkit.HighPerformance.Memory;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace UnitTests.HighPerformance.Memory
|
||||
{
|
||||
/* ====================================================================
|
||||
* NOTE
|
||||
* ====================================================================
|
||||
* All the tests here mirror the ones for Memory2D<T>, as the two types
|
||||
* are basically the same except for some small differences in return types
|
||||
* or some checks being done upon construction. See comments in the test
|
||||
* file for Memory2D<T> for more info on these tests. */
|
||||
[TestClass]
|
||||
[SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1649", Justification = "Test class for generic type")]
|
||||
public class Test_ReadOnlyMemory2DT
|
||||
{
|
||||
[TestCategory("ReadOnlyMemory2DT")]
|
||||
[TestMethod]
|
||||
public void Test_ReadOnlyMemory2DT_Empty()
|
||||
{
|
||||
ReadOnlyMemory2D<int> empty1 = default;
|
||||
|
||||
Assert.IsTrue(empty1.IsEmpty);
|
||||
Assert.AreEqual(empty1.Length, 0);
|
||||
Assert.AreEqual(empty1.Width, 0);
|
||||
Assert.AreEqual(empty1.Height, 0);
|
||||
|
||||
ReadOnlyMemory2D<string> empty2 = ReadOnlyMemory2D<string>.Empty;
|
||||
|
||||
Assert.IsTrue(empty2.IsEmpty);
|
||||
Assert.AreEqual(empty2.Length, 0);
|
||||
Assert.AreEqual(empty2.Width, 0);
|
||||
Assert.AreEqual(empty2.Height, 0);
|
||||
}
|
||||
|
||||
[TestCategory("ReadOnlyMemory2DT")]
|
||||
[TestMethod]
|
||||
public void Test_ReadOnlyMemory2DT_Array1DConstructor()
|
||||
{
|
||||
int[] array =
|
||||
{
|
||||
1, 2, 3, 4, 5, 6
|
||||
};
|
||||
|
||||
ReadOnlyMemory2D<int> memory2d = new ReadOnlyMemory2D<int>(array, 1, 2, 2, 1);
|
||||
|
||||
Assert.IsFalse(memory2d.IsEmpty);
|
||||
Assert.AreEqual(memory2d.Length, 4);
|
||||
Assert.AreEqual(memory2d.Width, 2);
|
||||
Assert.AreEqual(memory2d.Height, 2);
|
||||
Assert.AreEqual(memory2d.Span[0, 0], 2);
|
||||
Assert.AreEqual(memory2d.Span[1, 1], 6);
|
||||
|
||||
// Here we check to ensure a covariant array conversion is allowed for ReadOnlyMemory2D<T>
|
||||
_ = new ReadOnlyMemory2D<object>(new string[1], 1, 1);
|
||||
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new ReadOnlyMemory2D<int>(array, -99, 1, 1, 1));
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new ReadOnlyMemory2D<int>(array, 0, -10, 1, 1));
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new ReadOnlyMemory2D<int>(array, 0, 1, 1, -1));
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new ReadOnlyMemory2D<int>(array, 0, 1, -100, 1));
|
||||
Assert.ThrowsException<ArgumentException>(() => new ReadOnlyMemory2D<int>(array, 0, 2, 4, 0));
|
||||
Assert.ThrowsException<ArgumentException>(() => new ReadOnlyMemory2D<int>(array, 0, 3, 3, 0));
|
||||
Assert.ThrowsException<ArgumentException>(() => new ReadOnlyMemory2D<int>(array, 1, 2, 3, 0));
|
||||
Assert.ThrowsException<ArgumentException>(() => new ReadOnlyMemory2D<int>(array, 0, 10, 1, 120));
|
||||
}
|
||||
|
||||
[TestCategory("ReadOnlyMemory2DT")]
|
||||
[TestMethod]
|
||||
public void Test_ReadOnlyMemory2DT_Array2DConstructor_1()
|
||||
{
|
||||
int[,] array =
|
||||
{
|
||||
{ 1, 2, 3 },
|
||||
{ 4, 5, 6 }
|
||||
};
|
||||
|
||||
ReadOnlyMemory2D<int> memory2d = new ReadOnlyMemory2D<int>(array);
|
||||
|
||||
Assert.IsFalse(memory2d.IsEmpty);
|
||||
Assert.AreEqual(memory2d.Length, 6);
|
||||
Assert.AreEqual(memory2d.Width, 3);
|
||||
Assert.AreEqual(memory2d.Height, 2);
|
||||
Assert.AreEqual(memory2d.Span[0, 1], 2);
|
||||
Assert.AreEqual(memory2d.Span[1, 2], 6);
|
||||
|
||||
_ = new ReadOnlyMemory2D<object>(new string[1, 2]);
|
||||
}
|
||||
|
||||
[TestCategory("ReadOnlyMemory2DT")]
|
||||
[TestMethod]
|
||||
public void Test_ReadOnlyMemory2DT_Array2DConstructor_2()
|
||||
{
|
||||
int[,] array =
|
||||
{
|
||||
{ 1, 2, 3 },
|
||||
{ 4, 5, 6 }
|
||||
};
|
||||
|
||||
ReadOnlyMemory2D<int> memory2d = new ReadOnlyMemory2D<int>(array, 0, 1, 2, 2);
|
||||
|
||||
Assert.IsFalse(memory2d.IsEmpty);
|
||||
Assert.AreEqual(memory2d.Length, 4);
|
||||
Assert.AreEqual(memory2d.Width, 2);
|
||||
Assert.AreEqual(memory2d.Height, 2);
|
||||
Assert.AreEqual(memory2d.Span[0, 0], 2);
|
||||
Assert.AreEqual(memory2d.Span[1, 1], 6);
|
||||
|
||||
_ = new ReadOnlyMemory2D<object>(new string[1, 2]);
|
||||
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new ReadOnlyMemory2D<object>(new string[1, 2], 0, 0, 2, 2));
|
||||
}
|
||||
|
||||
[TestCategory("ReadOnlyMemory2DT")]
|
||||
[TestMethod]
|
||||
public void Test_ReadOnlyMemory2DT_Array3DConstructor_1()
|
||||
{
|
||||
int[,,] array =
|
||||
{
|
||||
{
|
||||
{ 1, 2, 3 },
|
||||
{ 4, 5, 6 }
|
||||
},
|
||||
{
|
||||
{ 10, 20, 30 },
|
||||
{ 40, 50, 60 }
|
||||
}
|
||||
};
|
||||
|
||||
ReadOnlyMemory2D<int> memory2d = new ReadOnlyMemory2D<int>(array, 1);
|
||||
|
||||
Assert.IsFalse(memory2d.IsEmpty);
|
||||
Assert.AreEqual(memory2d.Length, 6);
|
||||
Assert.AreEqual(memory2d.Width, 3);
|
||||
Assert.AreEqual(memory2d.Height, 2);
|
||||
Assert.AreEqual(memory2d.Span[0, 1], 20);
|
||||
Assert.AreEqual(memory2d.Span[1, 2], 60);
|
||||
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new ReadOnlyMemory2D<int>(array, -1));
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new ReadOnlyMemory2D<int>(array, 20));
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new ReadOnlyMemory2D<int>(array, 2));
|
||||
}
|
||||
|
||||
[TestCategory("ReadOnlyMemory2DT")]
|
||||
[TestMethod]
|
||||
public void Test_ReadOnlyMemory2DT_Array3DConstructor_2()
|
||||
{
|
||||
int[,,] array =
|
||||
{
|
||||
{
|
||||
{ 1, 2, 3 },
|
||||
{ 4, 5, 6 }
|
||||
},
|
||||
{
|
||||
{ 10, 20, 30 },
|
||||
{ 40, 50, 60 }
|
||||
}
|
||||
};
|
||||
|
||||
ReadOnlyMemory2D<int> memory2d = new ReadOnlyMemory2D<int>(array, 1, 0, 1, 2, 2);
|
||||
|
||||
Assert.IsFalse(memory2d.IsEmpty);
|
||||
Assert.AreEqual(memory2d.Length, 4);
|
||||
Assert.AreEqual(memory2d.Width, 2);
|
||||
Assert.AreEqual(memory2d.Height, 2);
|
||||
Assert.AreEqual(memory2d.Span[0, 0], 20);
|
||||
Assert.AreEqual(memory2d.Span[1, 1], 60);
|
||||
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new ReadOnlyMemory2D<int>(array, -1, 1, 1, 1, 1));
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new ReadOnlyMemory2D<int>(array, 1, -1, 1, 1, 1));
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new ReadOnlyMemory2D<int>(array, 1, 1, -1, 1, 1));
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new ReadOnlyMemory2D<int>(array, 1, 1, 1, -1, 1));
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new ReadOnlyMemory2D<int>(array, 1, 1, 1, 1, -1));
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new ReadOnlyMemory2D<int>(array, 2, 0, 0, 2, 3));
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new ReadOnlyMemory2D<int>(array, 0, 0, 1, 2, 3));
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new ReadOnlyMemory2D<int>(array, 0, 0, 0, 2, 4));
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new ReadOnlyMemory2D<int>(array, 0, 0, 0, 3, 3));
|
||||
}
|
||||
|
||||
#if !WINDOWS_UWP
|
||||
[TestCategory("ReadOnlyMemory2DT")]
|
||||
[TestMethod]
|
||||
public void Test_ReadOnlyMemory2DT_ReadOnlyMemoryConstructor()
|
||||
{
|
||||
ReadOnlyMemory<int> memory = new[]
|
||||
{
|
||||
1, 2, 3, 4, 5, 6
|
||||
};
|
||||
|
||||
ReadOnlyMemory2D<int> memory2d = memory.AsMemory2D(1, 2, 2, 1);
|
||||
|
||||
Assert.IsFalse(memory2d.IsEmpty);
|
||||
Assert.AreEqual(memory2d.Length, 4);
|
||||
Assert.AreEqual(memory2d.Width, 2);
|
||||
Assert.AreEqual(memory2d.Height, 2);
|
||||
Assert.AreEqual(memory2d.Span[0, 0], 2);
|
||||
Assert.AreEqual(memory2d.Span[1, 1], 6);
|
||||
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => memory.AsMemory2D(-99, 1, 1, 1));
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => memory.AsMemory2D(0, -10, 1, 1));
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => memory.AsMemory2D(0, 1, 1, -1));
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => memory.AsMemory2D(0, 1, -100, 1));
|
||||
Assert.ThrowsException<ArgumentException>(() => memory.AsMemory2D(0, 2, 4, 0));
|
||||
Assert.ThrowsException<ArgumentException>(() => memory.AsMemory2D(0, 3, 3, 0));
|
||||
Assert.ThrowsException<ArgumentException>(() => memory.AsMemory2D(1, 2, 3, 0));
|
||||
Assert.ThrowsException<ArgumentException>(() => memory.AsMemory2D(0, 10, 1, 120));
|
||||
}
|
||||
#endif
|
||||
|
||||
[TestCategory("ReadOnlyMemory2DT")]
|
||||
[TestMethod]
|
||||
public void Test_ReadOnlyMemory2DT_Slice_1()
|
||||
{
|
||||
int[,] array =
|
||||
{
|
||||
{ 1, 2, 3 },
|
||||
{ 4, 5, 6 }
|
||||
};
|
||||
|
||||
ReadOnlyMemory2D<int> memory2d = new ReadOnlyMemory2D<int>(array);
|
||||
|
||||
ReadOnlyMemory2D<int> slice1 = memory2d.Slice(1, 1, 1, 2);
|
||||
|
||||
Assert.AreEqual(slice1.Length, 2);
|
||||
Assert.AreEqual(slice1.Height, 1);
|
||||
Assert.AreEqual(slice1.Width, 2);
|
||||
Assert.AreEqual(slice1.Span[0, 0], 5);
|
||||
Assert.AreEqual(slice1.Span[0, 1], 6);
|
||||
|
||||
ReadOnlyMemory2D<int> slice2 = memory2d.Slice(0, 1, 2, 2);
|
||||
|
||||
Assert.AreEqual(slice2.Length, 4);
|
||||
Assert.AreEqual(slice2.Height, 2);
|
||||
Assert.AreEqual(slice2.Width, 2);
|
||||
Assert.AreEqual(slice2.Span[0, 0], 2);
|
||||
Assert.AreEqual(slice2.Span[1, 0], 5);
|
||||
Assert.AreEqual(slice2.Span[1, 1], 6);
|
||||
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new ReadOnlyMemory2D<int>(array).Slice(-1, 1, 1, 1));
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new ReadOnlyMemory2D<int>(array).Slice(1, -1, 1, 1));
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new ReadOnlyMemory2D<int>(array).Slice(1, 1, 1, -1));
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new ReadOnlyMemory2D<int>(array).Slice(1, 1, -1, 1));
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new ReadOnlyMemory2D<int>(array).Slice(10, 1, 1, 1));
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new ReadOnlyMemory2D<int>(array).Slice(1, 12, 1, 12));
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new ReadOnlyMemory2D<int>(array).Slice(1, 1, 55, 1));
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new ReadOnlyMemory2D<int>(array).Slice(0, 0, 2, 4));
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new ReadOnlyMemory2D<int>(array).Slice(0, 0, 3, 3));
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new ReadOnlyMemory2D<int>(array).Slice(0, 1, 2, 3));
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new ReadOnlyMemory2D<int>(array).Slice(1, 0, 2, 3));
|
||||
}
|
||||
|
||||
[TestCategory("ReadOnlyMemory2DT")]
|
||||
[TestMethod]
|
||||
public void Test_ReadOnlyMemory2DT_Slice_2()
|
||||
{
|
||||
int[,] array =
|
||||
{
|
||||
{ 1, 2, 3 },
|
||||
{ 4, 5, 6 }
|
||||
};
|
||||
|
||||
ReadOnlyMemory2D<int> memory2d = new ReadOnlyMemory2D<int>(array);
|
||||
|
||||
ReadOnlyMemory2D<int> slice1 = memory2d.Slice(0, 0, 2, 2);
|
||||
|
||||
Assert.AreEqual(slice1.Length, 4);
|
||||
Assert.AreEqual(slice1.Height, 2);
|
||||
Assert.AreEqual(slice1.Width, 2);
|
||||
Assert.AreEqual(slice1.Span[0, 0], 1);
|
||||
Assert.AreEqual(slice1.Span[1, 1], 5);
|
||||
|
||||
ReadOnlyMemory2D<int> slice2 = slice1.Slice(1, 0, 1, 2);
|
||||
|
||||
Assert.AreEqual(slice2.Length, 2);
|
||||
Assert.AreEqual(slice2.Height, 1);
|
||||
Assert.AreEqual(slice2.Width, 2);
|
||||
Assert.AreEqual(slice2.Span[0, 0], 4);
|
||||
Assert.AreEqual(slice2.Span[0, 1], 5);
|
||||
|
||||
ReadOnlyMemory2D<int> slice3 = slice2.Slice(0, 1, 1, 1);
|
||||
|
||||
Assert.AreEqual(slice3.Length, 1);
|
||||
Assert.AreEqual(slice3.Height, 1);
|
||||
Assert.AreEqual(slice3.Width, 1);
|
||||
Assert.AreEqual(slice3.Span[0, 0], 5);
|
||||
}
|
||||
|
||||
[TestCategory("ReadOnlyMemory2DT")]
|
||||
[TestMethod]
|
||||
public void Test_ReadOnlyMemory2DT_TryGetReadOnlyMemory_1()
|
||||
{
|
||||
int[,] array =
|
||||
{
|
||||
{ 1, 2, 3 },
|
||||
{ 4, 5, 6 }
|
||||
};
|
||||
|
||||
ReadOnlyMemory2D<int> memory2d = new ReadOnlyMemory2D<int>(array);
|
||||
|
||||
bool success = memory2d.TryGetMemory(out ReadOnlyMemory<int> memory);
|
||||
|
||||
#if WINDOWS_UWP
|
||||
Assert.IsFalse(success);
|
||||
Assert.IsTrue(memory.IsEmpty);
|
||||
#else
|
||||
Assert.IsTrue(success);
|
||||
Assert.AreEqual(memory.Length, array.Length);
|
||||
Assert.IsTrue(Unsafe.AreSame(ref array[0, 0], ref Unsafe.AsRef(memory.Span[0])));
|
||||
#endif
|
||||
}
|
||||
|
||||
[TestCategory("ReadOnlyMemory2DT")]
|
||||
[TestMethod]
|
||||
public void Test_ReadOnlyMemory2DT_TryGetReadOnlyMemory_2()
|
||||
{
|
||||
int[] array = { 1, 2, 3, 4 };
|
||||
|
||||
ReadOnlyMemory2D<int> memory2d = new ReadOnlyMemory2D<int>(array, 2, 2);
|
||||
|
||||
bool success = memory2d.TryGetMemory(out ReadOnlyMemory<int> memory);
|
||||
|
||||
Assert.IsTrue(success);
|
||||
Assert.AreEqual(memory.Length, array.Length);
|
||||
Assert.AreEqual(memory.Span[2], 3);
|
||||
}
|
||||
|
||||
#if !WINDOWS_UWP
|
||||
[TestCategory("ReadOnlyMemory2DT")]
|
||||
[TestMethod]
|
||||
public void Test_ReadOnlyMemory2DT_TryGetReadOnlyMemory_3()
|
||||
{
|
||||
ReadOnlyMemory<int> data = new[] { 1, 2, 3, 4 };
|
||||
|
||||
ReadOnlyMemory2D<int> memory2d = data.AsMemory2D(2, 2);
|
||||
|
||||
bool success = memory2d.TryGetMemory(out ReadOnlyMemory<int> memory);
|
||||
|
||||
Assert.IsTrue(success);
|
||||
Assert.AreEqual(memory.Length, data.Length);
|
||||
Assert.AreEqual(memory.Span[2], 3);
|
||||
}
|
||||
#endif
|
||||
|
||||
[TestCategory("ReadOnlyMemory2DT")]
|
||||
[TestMethod]
|
||||
public unsafe void Test_ReadOnlyMemory2DT_Pin_1()
|
||||
{
|
||||
int[] array = { 1, 2, 3, 4 };
|
||||
|
||||
ReadOnlyMemory2D<int> memory2d = new ReadOnlyMemory2D<int>(array, 2, 2);
|
||||
|
||||
using var pin = memory2d.Pin();
|
||||
|
||||
Assert.AreEqual(((int*)pin.Pointer)[0], 1);
|
||||
Assert.AreEqual(((int*)pin.Pointer)[3], 4);
|
||||
}
|
||||
|
||||
[TestCategory("ReadOnlyMemory2DT")]
|
||||
[TestMethod]
|
||||
public unsafe void Test_ReadOnlyMemory2DT_Pin_2()
|
||||
{
|
||||
int[] array = { 1, 2, 3, 4 };
|
||||
|
||||
ReadOnlyMemory2D<int> memory2d = new ReadOnlyMemory2D<int>(array, 2, 2);
|
||||
|
||||
using var pin = memory2d.Pin();
|
||||
|
||||
Assert.AreEqual(((int*)pin.Pointer)[0], 1);
|
||||
Assert.AreEqual(((int*)pin.Pointer)[3], 4);
|
||||
}
|
||||
|
||||
[TestCategory("ReadOnlyMemory2DT")]
|
||||
[TestMethod]
|
||||
public void Test_ReadOnlyMemory2DT_ToArray_1()
|
||||
{
|
||||
int[,] array =
|
||||
{
|
||||
{ 1, 2, 3 },
|
||||
{ 4, 5, 6 }
|
||||
};
|
||||
|
||||
ReadOnlyMemory2D<int> memory2d = new ReadOnlyMemory2D<int>(array);
|
||||
|
||||
int[,] copy = memory2d.ToArray();
|
||||
|
||||
Assert.AreEqual(copy.GetLength(0), array.GetLength(0));
|
||||
Assert.AreEqual(copy.GetLength(1), array.GetLength(1));
|
||||
|
||||
CollectionAssert.AreEqual(array, copy);
|
||||
}
|
||||
|
||||
[TestCategory("ReadOnlyMemory2DT")]
|
||||
[TestMethod]
|
||||
public void Test_ReadOnlyMemory2DT_ToArray_2()
|
||||
{
|
||||
int[,] array =
|
||||
{
|
||||
{ 1, 2, 3 },
|
||||
{ 4, 5, 6 }
|
||||
};
|
||||
|
||||
ReadOnlyMemory2D<int> memory2d = new ReadOnlyMemory2D<int>(array, 0, 0, 2, 2);
|
||||
|
||||
int[,] copy = memory2d.ToArray();
|
||||
|
||||
Assert.AreEqual(copy.GetLength(0), 2);
|
||||
Assert.AreEqual(copy.GetLength(1), 2);
|
||||
|
||||
int[,] expected =
|
||||
{
|
||||
{ 1, 2 },
|
||||
{ 4, 5 }
|
||||
};
|
||||
|
||||
CollectionAssert.AreEqual(expected, copy);
|
||||
}
|
||||
|
||||
[TestCategory("ReadOnlyMemory2DT")]
|
||||
[TestMethod]
|
||||
public void Test_ReadOnlyMemory2DT_Equals()
|
||||
{
|
||||
int[,] array =
|
||||
{
|
||||
{ 1, 2, 3 },
|
||||
{ 4, 5, 6 }
|
||||
};
|
||||
|
||||
ReadOnlyMemory2D<int> readOnlyMemory2D = new ReadOnlyMemory2D<int>(array);
|
||||
|
||||
Assert.IsFalse(readOnlyMemory2D.Equals(null));
|
||||
Assert.IsFalse(readOnlyMemory2D.Equals(new ReadOnlyMemory2D<int>(array, 0, 1, 2, 2)));
|
||||
Assert.IsTrue(readOnlyMemory2D.Equals(new ReadOnlyMemory2D<int>(array)));
|
||||
Assert.IsTrue(readOnlyMemory2D.Equals(readOnlyMemory2D));
|
||||
|
||||
Memory2D<int> memory2d = array;
|
||||
|
||||
Assert.IsTrue(readOnlyMemory2D.Equals((object)memory2d));
|
||||
Assert.IsFalse(readOnlyMemory2D.Equals((object)memory2d.Slice(0, 1, 2, 2)));
|
||||
}
|
||||
|
||||
[TestCategory("ReadOnlyMemory2DT")]
|
||||
[TestMethod]
|
||||
public void Test_ReadOnlyMemory2DT_GetHashCode()
|
||||
{
|
||||
Assert.AreEqual(ReadOnlyMemory2D<int>.Empty.GetHashCode(), 0);
|
||||
|
||||
int[,] array =
|
||||
{
|
||||
{ 1, 2, 3 },
|
||||
{ 4, 5, 6 }
|
||||
};
|
||||
|
||||
ReadOnlyMemory2D<int> memory2d = new ReadOnlyMemory2D<int>(array);
|
||||
|
||||
int a = memory2d.GetHashCode(), b = memory2d.GetHashCode();
|
||||
|
||||
Assert.AreEqual(a, b);
|
||||
|
||||
int c = new ReadOnlyMemory2D<int>(array, 0, 1, 2, 2).GetHashCode();
|
||||
|
||||
Assert.AreNotEqual(a, c);
|
||||
}
|
||||
|
||||
[TestCategory("ReadOnlyMemory2DT")]
|
||||
[TestMethod]
|
||||
public void Test_ReadOnlyMemory2DT_ToString()
|
||||
{
|
||||
int[,] array =
|
||||
{
|
||||
{ 1, 2, 3 },
|
||||
{ 4, 5, 6 }
|
||||
};
|
||||
|
||||
ReadOnlyMemory2D<int> memory2d = new ReadOnlyMemory2D<int>(array);
|
||||
|
||||
string text = memory2d.ToString();
|
||||
|
||||
const string expected = "Microsoft.Toolkit.HighPerformance.Memory.ReadOnlyMemory2D<System.Int32>[2, 3]";
|
||||
|
||||
Assert.AreEqual(text, expected);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,924 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Microsoft.Toolkit.HighPerformance.Enumerables;
|
||||
using Microsoft.Toolkit.HighPerformance.Extensions;
|
||||
using Microsoft.Toolkit.HighPerformance.Memory;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace UnitTests.HighPerformance.Memory
|
||||
{
|
||||
/* ====================================================================
|
||||
* NOTE
|
||||
* ====================================================================
|
||||
* All the tests here mirror the ones for ReadOnlySpan2D<T>. See comments
|
||||
* in the test file for Span2D<T> for more info on these tests. */
|
||||
[TestClass]
|
||||
[SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1649", Justification = "Test class for generic type")]
|
||||
public class Test_ReadOnlySpan2DT
|
||||
{
|
||||
[TestCategory("ReadOnlySpan2DT")]
|
||||
[TestMethod]
|
||||
public void Test_ReadOnlySpan2DT_Empty()
|
||||
{
|
||||
ReadOnlySpan2D<int> empty1 = default;
|
||||
|
||||
Assert.IsTrue(empty1.IsEmpty);
|
||||
Assert.AreEqual(empty1.Length, 0);
|
||||
Assert.AreEqual(empty1.Width, 0);
|
||||
Assert.AreEqual(empty1.Height, 0);
|
||||
|
||||
ReadOnlySpan2D<string> empty2 = ReadOnlySpan2D<string>.Empty;
|
||||
|
||||
Assert.IsTrue(empty2.IsEmpty);
|
||||
Assert.AreEqual(empty2.Length, 0);
|
||||
Assert.AreEqual(empty2.Width, 0);
|
||||
Assert.AreEqual(empty2.Height, 0);
|
||||
}
|
||||
|
||||
#if !WINDOWS_UWP
|
||||
[TestCategory("ReadOnlySpan2DT")]
|
||||
[TestMethod]
|
||||
public unsafe void Test_ReadOnlySpan2DT_RefConstructor()
|
||||
{
|
||||
ReadOnlySpan<int> span = stackalloc[]
|
||||
{
|
||||
1, 2, 3, 4, 5, 6
|
||||
};
|
||||
|
||||
ReadOnlySpan2D<int> span2d = ReadOnlySpan2D<int>.DangerousCreate(span[0], 2, 3, 0);
|
||||
|
||||
Assert.IsFalse(span2d.IsEmpty);
|
||||
Assert.AreEqual(span2d.Length, 6);
|
||||
Assert.AreEqual(span2d.Width, 3);
|
||||
Assert.AreEqual(span2d.Height, 2);
|
||||
Assert.AreEqual(span2d[0, 0], 1);
|
||||
Assert.AreEqual(span2d[1, 2], 6);
|
||||
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => ReadOnlySpan2D<int>.DangerousCreate(Unsafe.AsRef<int>(null), -1, 0, 0));
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => ReadOnlySpan2D<int>.DangerousCreate(Unsafe.AsRef<int>(null), 1, -2, 0));
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => ReadOnlySpan2D<int>.DangerousCreate(Unsafe.AsRef<int>(null), 1, 0, -5));
|
||||
}
|
||||
#endif
|
||||
|
||||
[TestCategory("ReadOnlySpan2DT")]
|
||||
[TestMethod]
|
||||
public unsafe void Test_ReadOnlySpan2DT_PtrConstructor()
|
||||
{
|
||||
int* ptr = stackalloc[]
|
||||
{
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6
|
||||
};
|
||||
|
||||
ReadOnlySpan2D<int> span2d = new ReadOnlySpan2D<int>(ptr, 2, 3, 0);
|
||||
|
||||
Assert.IsFalse(span2d.IsEmpty);
|
||||
Assert.AreEqual(span2d.Length, 6);
|
||||
Assert.AreEqual(span2d.Width, 3);
|
||||
Assert.AreEqual(span2d.Height, 2);
|
||||
Assert.AreEqual(span2d[0, 0], 1);
|
||||
Assert.AreEqual(span2d[1, 2], 6);
|
||||
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new ReadOnlySpan2D<int>((void*)0, -1, 0, 0));
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new ReadOnlySpan2D<int>((void*)0, 1, -2, 0));
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new ReadOnlySpan2D<int>((void*)0, 1, 0, -5));
|
||||
Assert.ThrowsException<ArgumentException>(() => new ReadOnlySpan2D<string>((void*)0, 2, 2, 0));
|
||||
}
|
||||
|
||||
[TestCategory("ReadOnlySpan2DT")]
|
||||
[TestMethod]
|
||||
public void Test_ReadOnlySpan2DT_Array1DConstructor()
|
||||
{
|
||||
int[] array =
|
||||
{
|
||||
1, 2, 3, 4, 5, 6
|
||||
};
|
||||
|
||||
ReadOnlySpan2D<int> span2d = new ReadOnlySpan2D<int>(array, 1, 2, 2, 1);
|
||||
|
||||
Assert.IsFalse(span2d.IsEmpty);
|
||||
Assert.AreEqual(span2d.Length, 4);
|
||||
Assert.AreEqual(span2d.Width, 2);
|
||||
Assert.AreEqual(span2d.Height, 2);
|
||||
Assert.AreEqual(span2d[0, 0], 2);
|
||||
Assert.AreEqual(span2d[1, 1], 6);
|
||||
|
||||
// Same for ReadOnlyMemory2D<T>, we need to check that covariant array conversions are allowed
|
||||
_ = new ReadOnlySpan2D<object>(new string[1], 1, 1);
|
||||
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new ReadOnlySpan2D<int>(array, -99, 1, 1, 1));
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new ReadOnlySpan2D<int>(array, 0, -10, 1, 1));
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new ReadOnlySpan2D<int>(array, 0, 1, 1, -1));
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new ReadOnlySpan2D<int>(array, 0, 1, -100, 1));
|
||||
Assert.ThrowsException<ArgumentException>(() => new ReadOnlySpan2D<int>(array, 0, 10, 1, 120));
|
||||
}
|
||||
|
||||
[TestCategory("ReadOnlySpan2DT")]
|
||||
[TestMethod]
|
||||
public void Test_ReadOnlySpan2DT_Array2DConstructor_1()
|
||||
{
|
||||
int[,] array =
|
||||
{
|
||||
{ 1, 2, 3 },
|
||||
{ 4, 5, 6 }
|
||||
};
|
||||
|
||||
ReadOnlySpan2D<int> span2d = new ReadOnlySpan2D<int>(array);
|
||||
|
||||
Assert.IsFalse(span2d.IsEmpty);
|
||||
Assert.AreEqual(span2d.Length, 6);
|
||||
Assert.AreEqual(span2d.Width, 3);
|
||||
Assert.AreEqual(span2d.Height, 2);
|
||||
Assert.AreEqual(span2d[0, 1], 2);
|
||||
Assert.AreEqual(span2d[1, 2], 6);
|
||||
|
||||
_ = new ReadOnlySpan2D<object>(new string[1, 2]);
|
||||
}
|
||||
|
||||
[TestCategory("ReadOnlySpan2DT")]
|
||||
[TestMethod]
|
||||
public void Test_ReadOnlySpan2DT_Array2DConstructor_2()
|
||||
{
|
||||
int[,] array =
|
||||
{
|
||||
{ 1, 2, 3 },
|
||||
{ 4, 5, 6 }
|
||||
};
|
||||
|
||||
ReadOnlySpan2D<int> span2d = new ReadOnlySpan2D<int>(array, 0, 1, 2, 2);
|
||||
|
||||
Assert.IsFalse(span2d.IsEmpty);
|
||||
Assert.AreEqual(span2d.Length, 4);
|
||||
Assert.AreEqual(span2d.Width, 2);
|
||||
Assert.AreEqual(span2d.Height, 2);
|
||||
Assert.AreEqual(span2d[0, 0], 2);
|
||||
Assert.AreEqual(span2d[1, 1], 6);
|
||||
|
||||
_ = new ReadOnlySpan2D<object>(new string[1, 2]);
|
||||
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new ReadOnlySpan2D<object>(new string[1, 2], 0, 0, 2, 2));
|
||||
}
|
||||
|
||||
[TestCategory("ReadOnlySpan2DT")]
|
||||
[TestMethod]
|
||||
public void Test_ReadOnlySpan2DT_Array3DConstructor_1()
|
||||
{
|
||||
int[,,] array =
|
||||
{
|
||||
{
|
||||
{ 1, 2, 3 },
|
||||
{ 4, 5, 6 }
|
||||
},
|
||||
{
|
||||
{ 10, 20, 30 },
|
||||
{ 40, 50, 60 }
|
||||
}
|
||||
};
|
||||
|
||||
ReadOnlySpan2D<int> span2d = new ReadOnlySpan2D<int>(array, 1);
|
||||
|
||||
Assert.IsFalse(span2d.IsEmpty);
|
||||
Assert.AreEqual(span2d.Length, 6);
|
||||
Assert.AreEqual(span2d.Width, 3);
|
||||
Assert.AreEqual(span2d.Height, 2);
|
||||
Assert.AreEqual(span2d[0, 0], 10);
|
||||
Assert.AreEqual(span2d[0, 1], 20);
|
||||
Assert.AreEqual(span2d[1, 2], 60);
|
||||
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new ReadOnlySpan2D<int>(array, -1));
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new ReadOnlySpan2D<int>(array, 20));
|
||||
}
|
||||
|
||||
[TestCategory("ReadOnlySpan2DT")]
|
||||
[TestMethod]
|
||||
public void Test_ReadOnlySpan2DT_Array3DConstructor_2()
|
||||
{
|
||||
int[,,] array =
|
||||
{
|
||||
{
|
||||
{ 1, 2, 3 },
|
||||
{ 4, 5, 6 }
|
||||
},
|
||||
{
|
||||
{ 10, 20, 30 },
|
||||
{ 40, 50, 60 }
|
||||
}
|
||||
};
|
||||
|
||||
ReadOnlySpan2D<int> span2d = new ReadOnlySpan2D<int>(array, 1, 0, 1, 2, 2);
|
||||
|
||||
Assert.IsFalse(span2d.IsEmpty);
|
||||
Assert.AreEqual(span2d.Length, 4);
|
||||
Assert.AreEqual(span2d.Width, 2);
|
||||
Assert.AreEqual(span2d.Height, 2);
|
||||
Assert.AreEqual(span2d[0, 0], 20);
|
||||
Assert.AreEqual(span2d[0, 1], 30);
|
||||
Assert.AreEqual(span2d[1, 1], 60);
|
||||
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new ReadOnlySpan2D<int>(array, -1, 1, 1, 1, 1));
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new ReadOnlySpan2D<int>(array, 1, -1, 1, 1, 1));
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new ReadOnlySpan2D<int>(array, 1, 1, -1, 1, 1));
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new ReadOnlySpan2D<int>(array, 1, 1, 1, -1, 1));
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new ReadOnlySpan2D<int>(array, 1, 1, 1, 1, -1));
|
||||
}
|
||||
|
||||
[TestCategory("ReadOnlySpan2DT")]
|
||||
[TestMethod]
|
||||
public void Test_ReadOnlySpan2DT_CopyTo_1()
|
||||
{
|
||||
int[,] array =
|
||||
{
|
||||
{ 1, 2, 3 },
|
||||
{ 4, 5, 6 }
|
||||
};
|
||||
|
||||
ReadOnlySpan2D<int> span2d = new ReadOnlySpan2D<int>(array);
|
||||
|
||||
int[] target = new int[array.Length];
|
||||
|
||||
span2d.CopyTo(target);
|
||||
|
||||
CollectionAssert.AreEqual(array, target);
|
||||
|
||||
Assert.ThrowsException<ArgumentException>(() => new ReadOnlySpan2D<int>(array).CopyTo(Span<int>.Empty));
|
||||
}
|
||||
|
||||
[TestCategory("ReadOnlySpan2DT")]
|
||||
[TestMethod]
|
||||
public void Test_ReadOnlySpan2DT_CopyTo_2()
|
||||
{
|
||||
int[,] array =
|
||||
{
|
||||
{ 1, 2, 3 },
|
||||
{ 4, 5, 6 }
|
||||
};
|
||||
|
||||
ReadOnlySpan2D<int> span2d = new ReadOnlySpan2D<int>(array, 0, 1, 2, 2);
|
||||
|
||||
int[] target = new int[4];
|
||||
|
||||
span2d.CopyTo(target);
|
||||
|
||||
int[] expected = { 2, 3, 5, 6 };
|
||||
|
||||
CollectionAssert.AreEqual(target, expected);
|
||||
|
||||
Assert.ThrowsException<ArgumentException>(() => new ReadOnlySpan2D<int>(array).CopyTo(Span<int>.Empty));
|
||||
}
|
||||
|
||||
[TestCategory("ReadOnlySpan2DT")]
|
||||
[TestMethod]
|
||||
public void Test_ReadOnlySpan2DT_CopyTo2D_1()
|
||||
{
|
||||
int[,] array =
|
||||
{
|
||||
{ 1, 2, 3 },
|
||||
{ 4, 5, 6 }
|
||||
};
|
||||
|
||||
ReadOnlySpan2D<int> span2d = new ReadOnlySpan2D<int>(array);
|
||||
|
||||
int[,] target = new int[2, 3];
|
||||
|
||||
span2d.CopyTo(target);
|
||||
|
||||
CollectionAssert.AreEqual(array, target);
|
||||
|
||||
Assert.ThrowsException<ArgumentException>(() => new ReadOnlySpan2D<int>(array).CopyTo(Span2D<int>.Empty));
|
||||
}
|
||||
|
||||
[TestCategory("ReadOnlySpan2DT")]
|
||||
[TestMethod]
|
||||
public void Test_ReadOnlySpan2DT_CopyTo2D_2()
|
||||
{
|
||||
int[,] array =
|
||||
{
|
||||
{ 1, 2, 3 },
|
||||
{ 4, 5, 6 }
|
||||
};
|
||||
|
||||
ReadOnlySpan2D<int> span2d = new ReadOnlySpan2D<int>(array, 0, 1, 2, 2);
|
||||
|
||||
int[,] target = new int[2, 2];
|
||||
|
||||
span2d.CopyTo(target);
|
||||
|
||||
int[,] expected =
|
||||
{
|
||||
{ 2, 3 },
|
||||
{ 5, 6 }
|
||||
};
|
||||
|
||||
CollectionAssert.AreEqual(target, expected);
|
||||
|
||||
Assert.ThrowsException<ArgumentException>(() => new ReadOnlySpan2D<int>(array).CopyTo(new Span2D<int>(target)));
|
||||
}
|
||||
|
||||
[TestCategory("ReadOnlySpan2DT")]
|
||||
[TestMethod]
|
||||
public void Test_ReadOnlySpan2DT_TryCopyTo()
|
||||
{
|
||||
int[,] array =
|
||||
{
|
||||
{ 1, 2, 3 },
|
||||
{ 4, 5, 6 }
|
||||
};
|
||||
|
||||
ReadOnlySpan2D<int> span2d = new ReadOnlySpan2D<int>(array);
|
||||
|
||||
int[] target = new int[array.Length];
|
||||
|
||||
Assert.IsTrue(span2d.TryCopyTo(target));
|
||||
Assert.IsFalse(span2d.TryCopyTo(Span<int>.Empty));
|
||||
}
|
||||
|
||||
[TestCategory("ReadOnlySpan2DT")]
|
||||
[TestMethod]
|
||||
public void Test_ReadOnlySpan2DT_TryCopyTo2D()
|
||||
{
|
||||
int[,] array =
|
||||
{
|
||||
{ 1, 2, 3 },
|
||||
{ 4, 5, 6 }
|
||||
};
|
||||
|
||||
ReadOnlySpan2D<int> span2d = new ReadOnlySpan2D<int>(array);
|
||||
|
||||
int[,] target = new int[2, 3];
|
||||
|
||||
Assert.IsTrue(span2d.TryCopyTo(target));
|
||||
Assert.IsFalse(span2d.TryCopyTo(Span2D<int>.Empty));
|
||||
}
|
||||
|
||||
[TestCategory("ReadOnlySpan2DT")]
|
||||
[TestMethod]
|
||||
public unsafe void Test_ReadOnlySpan2DT_GetPinnableReference()
|
||||
{
|
||||
Assert.IsTrue(Unsafe.AreSame(
|
||||
ref Unsafe.AsRef<int>(null),
|
||||
ref ReadOnlySpan2D<int>.Empty.GetPinnableReference()));
|
||||
|
||||
int[,] array =
|
||||
{
|
||||
{ 1, 2, 3 },
|
||||
{ 4, 5, 6 }
|
||||
};
|
||||
|
||||
ReadOnlySpan2D<int> span2d = new ReadOnlySpan2D<int>(array);
|
||||
|
||||
ref int r0 = ref span2d.GetPinnableReference();
|
||||
|
||||
Assert.IsTrue(Unsafe.AreSame(ref r0, ref array[0, 0]));
|
||||
}
|
||||
|
||||
[TestCategory("ReadOnlySpan2DT")]
|
||||
[TestMethod]
|
||||
public unsafe void Test_ReadOnlySpan2DT_DangerousGetReference()
|
||||
{
|
||||
Assert.IsTrue(Unsafe.AreSame(
|
||||
ref Unsafe.AsRef<int>(null),
|
||||
ref ReadOnlySpan2D<int>.Empty.DangerousGetReference()));
|
||||
|
||||
int[,] array =
|
||||
{
|
||||
{ 1, 2, 3 },
|
||||
{ 4, 5, 6 }
|
||||
};
|
||||
|
||||
ReadOnlySpan2D<int> span2d = new ReadOnlySpan2D<int>(array);
|
||||
|
||||
ref int r0 = ref span2d.DangerousGetReference();
|
||||
|
||||
Assert.IsTrue(Unsafe.AreSame(ref r0, ref array[0, 0]));
|
||||
}
|
||||
|
||||
[TestCategory("ReadOnlySpan2DT")]
|
||||
[TestMethod]
|
||||
public void Test_ReadOnlySpan2DT_Slice_1()
|
||||
{
|
||||
int[,] array =
|
||||
{
|
||||
{ 1, 2, 3 },
|
||||
{ 4, 5, 6 }
|
||||
};
|
||||
|
||||
ReadOnlySpan2D<int> span2d = new ReadOnlySpan2D<int>(array);
|
||||
|
||||
ReadOnlySpan2D<int> slice1 = span2d.Slice(1, 1, 2, 1);
|
||||
|
||||
Assert.AreEqual(slice1.Length, 2);
|
||||
Assert.AreEqual(slice1.Height, 1);
|
||||
Assert.AreEqual(slice1.Width, 2);
|
||||
Assert.AreEqual(slice1[0, 0], 5);
|
||||
Assert.AreEqual(slice1[0, 1], 6);
|
||||
|
||||
ReadOnlySpan2D<int> slice2 = span2d.Slice(0, 1, 2, 2);
|
||||
|
||||
Assert.AreEqual(slice2.Length, 4);
|
||||
Assert.AreEqual(slice2.Height, 2);
|
||||
Assert.AreEqual(slice2.Width, 2);
|
||||
Assert.AreEqual(slice2[0, 0], 2);
|
||||
Assert.AreEqual(slice2[1, 0], 5);
|
||||
Assert.AreEqual(slice2[1, 1], 6);
|
||||
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new ReadOnlySpan2D<int>(array).Slice(-1, 1, 1, 1));
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new ReadOnlySpan2D<int>(array).Slice(1, -1, 1, 1));
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new ReadOnlySpan2D<int>(array).Slice(1, 1, -1, 1));
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new ReadOnlySpan2D<int>(array).Slice(1, 1, 1, -1));
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new ReadOnlySpan2D<int>(array).Slice(10, 1, 1, 1));
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new ReadOnlySpan2D<int>(array).Slice(1, 12, 12, 1));
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new ReadOnlySpan2D<int>(array).Slice(1, 1, 1, 55));
|
||||
}
|
||||
|
||||
[TestCategory("ReadOnlySpan2DT")]
|
||||
[TestMethod]
|
||||
public void Test_ReadOnlySpan2DT_Slice_2()
|
||||
{
|
||||
int[,] array =
|
||||
{
|
||||
{ 1, 2, 3 },
|
||||
{ 4, 5, 6 }
|
||||
};
|
||||
|
||||
ReadOnlySpan2D<int> span2d = new ReadOnlySpan2D<int>(array);
|
||||
|
||||
ReadOnlySpan2D<int> slice1 = span2d.Slice(0, 0, 2, 2);
|
||||
|
||||
Assert.AreEqual(slice1.Length, 4);
|
||||
Assert.AreEqual(slice1.Height, 2);
|
||||
Assert.AreEqual(slice1.Width, 2);
|
||||
Assert.AreEqual(slice1[0, 0], 1);
|
||||
Assert.AreEqual(slice1[1, 1], 5);
|
||||
|
||||
ReadOnlySpan2D<int> slice2 = slice1.Slice(1, 0, 2, 1);
|
||||
|
||||
Assert.AreEqual(slice2.Length, 2);
|
||||
Assert.AreEqual(slice2.Height, 1);
|
||||
Assert.AreEqual(slice2.Width, 2);
|
||||
Assert.AreEqual(slice2[0, 0], 4);
|
||||
Assert.AreEqual(slice2[0, 1], 5);
|
||||
|
||||
ReadOnlySpan2D<int> slice3 = slice2.Slice(0, 1, 1, 1);
|
||||
|
||||
Assert.AreEqual(slice3.Length, 1);
|
||||
Assert.AreEqual(slice3.Height, 1);
|
||||
Assert.AreEqual(slice3.Width, 1);
|
||||
Assert.AreEqual(slice3[0, 0], 5);
|
||||
}
|
||||
|
||||
#if !WINDOWS_UWP
|
||||
[TestCategory("ReadOnlySpan2DT")]
|
||||
[TestMethod]
|
||||
public void Test_ReadOnlySpan2DT_GetRowReadOnlySpan()
|
||||
{
|
||||
int[,] array =
|
||||
{
|
||||
{ 1, 2, 3 },
|
||||
{ 4, 5, 6 }
|
||||
};
|
||||
|
||||
ReadOnlySpan2D<int> span2d = new ReadOnlySpan2D<int>(array);
|
||||
|
||||
ReadOnlySpan<int> span = span2d.GetRowSpan(1);
|
||||
|
||||
Assert.IsTrue(Unsafe.AreSame(
|
||||
ref Unsafe.AsRef(span[0]),
|
||||
ref array[1, 0]));
|
||||
Assert.IsTrue(Unsafe.AreSame(
|
||||
ref Unsafe.AsRef(span[2]),
|
||||
ref array[1, 2]));
|
||||
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new ReadOnlySpan2D<int>(array).GetRowSpan(-1));
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new ReadOnlySpan2D<int>(array).GetRowSpan(5));
|
||||
}
|
||||
#endif
|
||||
|
||||
[TestCategory("ReadOnlySpan2DT")]
|
||||
[TestMethod]
|
||||
public void Test_ReadOnlySpan2DT_TryGetReadOnlySpan_1()
|
||||
{
|
||||
int[,] array =
|
||||
{
|
||||
{ 1, 2, 3 },
|
||||
{ 4, 5, 6 }
|
||||
};
|
||||
|
||||
ReadOnlySpan2D<int> span2d = new ReadOnlySpan2D<int>(array);
|
||||
|
||||
bool success = span2d.TryGetSpan(out ReadOnlySpan<int> span);
|
||||
|
||||
#if WINDOWS_UWP
|
||||
// Can't get a ReadOnlySpan<T> over a T[,] array on UWP
|
||||
Assert.IsFalse(success);
|
||||
Assert.AreEqual(span.Length, 0);
|
||||
#else
|
||||
Assert.IsTrue(success);
|
||||
Assert.AreEqual(span.Length, span2d.Length);
|
||||
#endif
|
||||
}
|
||||
|
||||
[TestCategory("ReadOnlySpan2DT")]
|
||||
[TestMethod]
|
||||
public void Test_ReadOnlySpan2DT_TryGetReadOnlySpan_2()
|
||||
{
|
||||
int[,] array =
|
||||
{
|
||||
{ 1, 2, 3 },
|
||||
{ 4, 5, 6 }
|
||||
};
|
||||
|
||||
ReadOnlySpan2D<int> span2d = new ReadOnlySpan2D<int>(array, 0, 0, 2, 2);
|
||||
|
||||
bool success = span2d.TryGetSpan(out ReadOnlySpan<int> span);
|
||||
|
||||
Assert.IsFalse(success);
|
||||
Assert.IsTrue(span.IsEmpty);
|
||||
}
|
||||
|
||||
[TestCategory("ReadOnlySpan2DT")]
|
||||
[TestMethod]
|
||||
public void Test_ReadOnlySpan2DT_ToArray_1()
|
||||
{
|
||||
int[,] array =
|
||||
{
|
||||
{ 1, 2, 3 },
|
||||
{ 4, 5, 6 }
|
||||
};
|
||||
|
||||
ReadOnlySpan2D<int> span2d = new ReadOnlySpan2D<int>(array);
|
||||
|
||||
int[,] copy = span2d.ToArray();
|
||||
|
||||
Assert.AreEqual(copy.GetLength(0), array.GetLength(0));
|
||||
Assert.AreEqual(copy.GetLength(1), array.GetLength(1));
|
||||
|
||||
CollectionAssert.AreEqual(array, copy);
|
||||
}
|
||||
|
||||
[TestCategory("ReadOnlySpan2DT")]
|
||||
[TestMethod]
|
||||
public void Test_ReadOnlySpan2DT_ToArray_2()
|
||||
{
|
||||
int[,] array =
|
||||
{
|
||||
{ 1, 2, 3 },
|
||||
{ 4, 5, 6 }
|
||||
};
|
||||
|
||||
ReadOnlySpan2D<int> span2d = new ReadOnlySpan2D<int>(array, 0, 0, 2, 2);
|
||||
|
||||
int[,] copy = span2d.ToArray();
|
||||
|
||||
Assert.AreEqual(copy.GetLength(0), 2);
|
||||
Assert.AreEqual(copy.GetLength(1), 2);
|
||||
|
||||
int[,] expected =
|
||||
{
|
||||
{ 1, 2 },
|
||||
{ 4, 5 }
|
||||
};
|
||||
|
||||
CollectionAssert.AreEqual(expected, copy);
|
||||
}
|
||||
|
||||
[TestCategory("ReadOnlySpan2DT")]
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(NotSupportedException))]
|
||||
public void Test_ReadOnlySpan2DT_Equals()
|
||||
{
|
||||
int[,] array =
|
||||
{
|
||||
{ 1, 2, 3 },
|
||||
{ 4, 5, 6 }
|
||||
};
|
||||
|
||||
ReadOnlySpan2D<int> span2d = new ReadOnlySpan2D<int>(array);
|
||||
|
||||
_ = span2d.Equals(null);
|
||||
}
|
||||
|
||||
[TestCategory("ReadOnlySpan2DT")]
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(NotSupportedException))]
|
||||
public void Test_ReadOnlySpan2DT_GetHashCode()
|
||||
{
|
||||
int[,] array =
|
||||
{
|
||||
{ 1, 2, 3 },
|
||||
{ 4, 5, 6 }
|
||||
};
|
||||
|
||||
ReadOnlySpan2D<int> span2d = new ReadOnlySpan2D<int>(array);
|
||||
|
||||
_ = span2d.GetHashCode();
|
||||
}
|
||||
|
||||
[TestCategory("ReadOnlySpan2DT")]
|
||||
[TestMethod]
|
||||
public void Test_ReadOnlySpan2DT_ToString()
|
||||
{
|
||||
int[,] array =
|
||||
{
|
||||
{ 1, 2, 3 },
|
||||
{ 4, 5, 6 }
|
||||
};
|
||||
|
||||
ReadOnlySpan2D<int> span2d = new ReadOnlySpan2D<int>(array);
|
||||
|
||||
string text = span2d.ToString();
|
||||
|
||||
const string expected = "Microsoft.Toolkit.HighPerformance.Memory.ReadOnlySpan2D<System.Int32>[2, 3]";
|
||||
|
||||
Assert.AreEqual(text, expected);
|
||||
}
|
||||
|
||||
[TestCategory("ReadOnlySpan2DT")]
|
||||
[TestMethod]
|
||||
public void Test_ReadOnlySpan2DT_opEquals()
|
||||
{
|
||||
int[,] array =
|
||||
{
|
||||
{ 1, 2, 3 },
|
||||
{ 4, 5, 6 }
|
||||
};
|
||||
|
||||
ReadOnlySpan2D<int> span2d_1 = new ReadOnlySpan2D<int>(array);
|
||||
ReadOnlySpan2D<int> span2d_2 = new ReadOnlySpan2D<int>(array);
|
||||
|
||||
Assert.IsTrue(span2d_1 == span2d_2);
|
||||
Assert.IsFalse(span2d_1 == ReadOnlySpan2D<int>.Empty);
|
||||
Assert.IsTrue(ReadOnlySpan2D<int>.Empty == ReadOnlySpan2D<int>.Empty);
|
||||
|
||||
ReadOnlySpan2D<int> span2d_3 = new ReadOnlySpan2D<int>(array, 0, 0, 2, 2);
|
||||
|
||||
Assert.IsFalse(span2d_1 == span2d_3);
|
||||
Assert.IsFalse(span2d_3 == ReadOnlySpan2D<int>.Empty);
|
||||
}
|
||||
|
||||
[TestCategory("ReadOnlySpan2DT")]
|
||||
[TestMethod]
|
||||
public void Test_ReadOnlySpan2DT_ImplicitCast()
|
||||
{
|
||||
int[,] array =
|
||||
{
|
||||
{ 1, 2, 3 },
|
||||
{ 4, 5, 6 }
|
||||
};
|
||||
|
||||
ReadOnlySpan2D<int> span2d_1 = array;
|
||||
ReadOnlySpan2D<int> span2d_2 = new ReadOnlySpan2D<int>(array);
|
||||
|
||||
Assert.IsTrue(span2d_1 == span2d_2);
|
||||
}
|
||||
|
||||
[TestCategory("ReadOnlySpan2DT")]
|
||||
[TestMethod]
|
||||
public void Test_ReadOnlySpan2DT_GetRow()
|
||||
{
|
||||
int[,] array =
|
||||
{
|
||||
{ 1, 2, 3 },
|
||||
{ 4, 5, 6 }
|
||||
};
|
||||
|
||||
int i = 0;
|
||||
foreach (ref readonly int value in new ReadOnlySpan2D<int>(array).GetRow(1))
|
||||
{
|
||||
Assert.IsTrue(Unsafe.AreSame(ref Unsafe.AsRef(value), ref array[1, i++]));
|
||||
}
|
||||
|
||||
ReadOnlyRefEnumerable<int> enumerable = new ReadOnlySpan2D<int>(array).GetRow(1);
|
||||
|
||||
int[] expected = { 4, 5, 6 };
|
||||
|
||||
CollectionAssert.AreEqual(enumerable.ToArray(), expected);
|
||||
|
||||
Assert.AreSame(default(ReadOnlyRefEnumerable<int>).ToArray(), Array.Empty<int>());
|
||||
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new ReadOnlySpan2D<int>(array).GetRow(-1));
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new ReadOnlySpan2D<int>(array).GetRow(2));
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new ReadOnlySpan2D<int>(array).GetRow(1000));
|
||||
}
|
||||
|
||||
[TestCategory("ReadOnlySpan2DT")]
|
||||
[TestMethod]
|
||||
public unsafe void Test_ReadOnlySpan2DT_Pointer_GetRow()
|
||||
{
|
||||
int* array = stackalloc[]
|
||||
{
|
||||
1, 2, 3,
|
||||
4, 5, 6
|
||||
};
|
||||
|
||||
int i = 0;
|
||||
foreach (ref readonly int value in new ReadOnlySpan2D<int>(array, 2, 3, 0).GetRow(1))
|
||||
{
|
||||
Assert.IsTrue(Unsafe.AreSame(ref Unsafe.AsRef(value), ref array[3 + i++]));
|
||||
}
|
||||
|
||||
ReadOnlyRefEnumerable<int> enumerable = new ReadOnlySpan2D<int>(array, 2, 3, 0).GetRow(1);
|
||||
|
||||
int[] expected = { 4, 5, 6 };
|
||||
|
||||
CollectionAssert.AreEqual(enumerable.ToArray(), expected);
|
||||
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new ReadOnlySpan2D<int>(array, 2, 3, 0).GetRow(-1));
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new ReadOnlySpan2D<int>(array, 2, 3, 0).GetRow(2));
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new ReadOnlySpan2D<int>(array, 2, 3, 0).GetRow(1000));
|
||||
}
|
||||
|
||||
[TestCategory("ReadOnlySpan2DT")]
|
||||
[TestMethod]
|
||||
public void Test_ReadOnlySpan2DT_GetColumn()
|
||||
{
|
||||
int[,] array =
|
||||
{
|
||||
{ 1, 2, 3 },
|
||||
{ 4, 5, 6 }
|
||||
};
|
||||
|
||||
int i = 0;
|
||||
foreach (ref readonly int value in new ReadOnlySpan2D<int>(array).GetColumn(1))
|
||||
{
|
||||
Assert.IsTrue(Unsafe.AreSame(ref Unsafe.AsRef(value), ref array[i++, 1]));
|
||||
}
|
||||
|
||||
ReadOnlyRefEnumerable<int> enumerable = new ReadOnlySpan2D<int>(array).GetColumn(2);
|
||||
|
||||
int[] expected = { 3, 6 };
|
||||
|
||||
CollectionAssert.AreEqual(enumerable.ToArray(), expected);
|
||||
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new ReadOnlySpan2D<int>(array).GetColumn(-1));
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new ReadOnlySpan2D<int>(array).GetColumn(3));
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new ReadOnlySpan2D<int>(array).GetColumn(1000));
|
||||
}
|
||||
|
||||
[TestCategory("ReadOnlySpan2DT")]
|
||||
[TestMethod]
|
||||
public unsafe void Test_ReadOnlySpan2DT_Pointer_GetColumn()
|
||||
{
|
||||
int* array = stackalloc[]
|
||||
{
|
||||
1, 2, 3,
|
||||
4, 5, 6
|
||||
};
|
||||
|
||||
int i = 0;
|
||||
foreach (ref readonly int value in new ReadOnlySpan2D<int>(array, 2, 3, 0).GetColumn(1))
|
||||
{
|
||||
Assert.IsTrue(Unsafe.AreSame(ref Unsafe.AsRef(value), ref array[(i++ * 3) + 1]));
|
||||
}
|
||||
|
||||
ReadOnlyRefEnumerable<int> enumerable = new ReadOnlySpan2D<int>(array, 2, 3, 0).GetColumn(2);
|
||||
|
||||
int[] expected = { 3, 6 };
|
||||
|
||||
CollectionAssert.AreEqual(enumerable.ToArray(), expected);
|
||||
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new ReadOnlySpan2D<int>(array, 2, 3, 0).GetColumn(-1));
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new ReadOnlySpan2D<int>(array, 2, 3, 0).GetColumn(3));
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new ReadOnlySpan2D<int>(array, 2, 3, 0).GetColumn(1000));
|
||||
}
|
||||
|
||||
[TestCategory("ReadOnlySpan2DT")]
|
||||
[TestMethod]
|
||||
public void Test_ReadOnlySpan2DT_GetEnumerator()
|
||||
{
|
||||
int[,] array =
|
||||
{
|
||||
{ 1, 2, 3 },
|
||||
{ 4, 5, 6 }
|
||||
};
|
||||
|
||||
int[] result = new int[4];
|
||||
int i = 0;
|
||||
|
||||
foreach (var item in new ReadOnlySpan2D<int>(array, 0, 1, 2, 2))
|
||||
{
|
||||
result[i++] = item;
|
||||
}
|
||||
|
||||
int[] expected = { 2, 3, 5, 6 };
|
||||
|
||||
CollectionAssert.AreEqual(result, expected);
|
||||
}
|
||||
|
||||
[TestCategory("ReadOnlySpan2DT")]
|
||||
[TestMethod]
|
||||
public unsafe void Test_ReadOnlySpan2DT_Pointer_GetEnumerator()
|
||||
{
|
||||
int* array = stackalloc[]
|
||||
{
|
||||
1, 2, 3,
|
||||
4, 5, 6
|
||||
};
|
||||
|
||||
int[] result = new int[4];
|
||||
int i = 0;
|
||||
|
||||
foreach (var item in new ReadOnlySpan2D<int>(array + 1, 2, 2, 1))
|
||||
{
|
||||
result[i++] = item;
|
||||
}
|
||||
|
||||
int[] expected = { 2, 3, 5, 6 };
|
||||
|
||||
CollectionAssert.AreEqual(result, expected);
|
||||
}
|
||||
|
||||
[TestCategory("ReadOnlySpan2DT")]
|
||||
[TestMethod]
|
||||
public void Test_ReadOnlySpan2DT_GetEnumerator_Empty()
|
||||
{
|
||||
var enumerator = ReadOnlySpan2D<int>.Empty.GetEnumerator();
|
||||
|
||||
Assert.IsFalse(enumerator.MoveNext());
|
||||
}
|
||||
|
||||
[TestCategory("ReadOnlySpan2DT")]
|
||||
[TestMethod]
|
||||
public void Test_ReadOnlySpan2DT_ReadOnlyRefEnumerable_Misc()
|
||||
{
|
||||
int[,] array1 =
|
||||
{
|
||||
{ 1, 2, 3, 4 },
|
||||
{ 5, 6, 7, 8 },
|
||||
{ 9, 10, 11, 12 },
|
||||
{ 13, 14, 15, 16 }
|
||||
};
|
||||
|
||||
ReadOnlySpan2D<int> span1 = array1;
|
||||
|
||||
int[,] array2 = new int[4, 4];
|
||||
|
||||
// Copy to enumerable with source step == 1, destination step == 1
|
||||
span1.GetRow(0).CopyTo(array2.GetRow(0));
|
||||
|
||||
// Copy enumerable with source step == 1, destination step != 1
|
||||
span1.GetRow(1).CopyTo(array2.GetColumn(1));
|
||||
|
||||
// Copy enumerable with source step != 1, destination step == 1
|
||||
span1.GetColumn(2).CopyTo(array2.GetRow(2));
|
||||
|
||||
// Copy enumerable with source step != 1, destination step != 1
|
||||
span1.GetColumn(3).CopyTo(array2.GetColumn(3));
|
||||
|
||||
int[,] result =
|
||||
{
|
||||
{ 1, 5, 3, 4 },
|
||||
{ 0, 6, 0, 8 },
|
||||
{ 3, 7, 11, 12 },
|
||||
{ 0, 8, 0, 16 }
|
||||
};
|
||||
|
||||
CollectionAssert.AreEqual(array2, result);
|
||||
|
||||
// Test a valid and an invalid TryCopyTo call with the RefEnumerable<T> overload
|
||||
bool shouldBeTrue = span1.GetRow(0).TryCopyTo(array2.GetColumn(0));
|
||||
bool shouldBeFalse = span1.GetRow(0).TryCopyTo(default(RefEnumerable<int>));
|
||||
|
||||
result = new[,]
|
||||
{
|
||||
{ 1, 5, 3, 4 },
|
||||
{ 2, 6, 0, 8 },
|
||||
{ 3, 7, 11, 12 },
|
||||
{ 4, 8, 0, 16 }
|
||||
};
|
||||
|
||||
CollectionAssert.AreEqual(array2, result);
|
||||
|
||||
Assert.IsTrue(shouldBeTrue);
|
||||
Assert.IsFalse(shouldBeFalse);
|
||||
}
|
||||
|
||||
[TestCategory("ReadOnlySpan2DT")]
|
||||
[TestMethod]
|
||||
public void Test_ReadOnlySpan2DT_ReadOnlyRefEnumerable_Cast()
|
||||
{
|
||||
int[,] array1 =
|
||||
{
|
||||
{ 1, 2, 3, 4 },
|
||||
{ 5, 6, 7, 8 },
|
||||
{ 9, 10, 11, 12 },
|
||||
{ 13, 14, 15, 16 }
|
||||
};
|
||||
|
||||
int[] result = { 5, 6, 7, 8 };
|
||||
|
||||
// Cast a RefEnumerable<T> to a readonly one and verify the contents
|
||||
int[] row = ((ReadOnlyRefEnumerable<int>)array1.GetRow(1)).ToArray();
|
||||
|
||||
CollectionAssert.AreEqual(result, row);
|
||||
}
|
||||
}
|
||||
}
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -78,6 +78,42 @@ namespace UnitTests.HighPerformance.Streams
|
|||
Assert.AreEqual(stream.Position, 32);
|
||||
}
|
||||
|
||||
// See https://github.com/windows-toolkit/WindowsCommunityToolkit/issues/3536
|
||||
[TestCategory("MemoryStream")]
|
||||
[TestMethod]
|
||||
public void Test_MemoryStream_WriteToEndAndRefreshPosition()
|
||||
{
|
||||
byte[]
|
||||
array = new byte[10],
|
||||
temp = new byte[1];
|
||||
ReadOnlyMemory<byte> memory = array;
|
||||
|
||||
using var stream = memory.AsStream();
|
||||
|
||||
for (int i = 0; i < array.Length; i++)
|
||||
{
|
||||
int read = stream.Read(temp, 0, 1);
|
||||
|
||||
Assert.AreEqual(read, 1);
|
||||
Assert.AreEqual(stream.Position, i + 1);
|
||||
}
|
||||
|
||||
Assert.AreEqual(stream.Position, array.Length);
|
||||
|
||||
// These should not throw, seeking to the end is valid
|
||||
stream.Position = stream.Position;
|
||||
Assert.AreEqual(stream.Position, array.Length);
|
||||
|
||||
stream.Seek(array.Length, SeekOrigin.Begin);
|
||||
Assert.AreEqual(stream.Position, array.Length);
|
||||
|
||||
stream.Seek(0, SeekOrigin.Current);
|
||||
Assert.AreEqual(stream.Position, array.Length);
|
||||
|
||||
stream.Seek(0, SeekOrigin.End);
|
||||
Assert.AreEqual(stream.Position, array.Length);
|
||||
}
|
||||
|
||||
[TestCategory("MemoryStream")]
|
||||
[TestMethod]
|
||||
public void Test_MemoryStream_ReadWrite_Array()
|
||||
|
|
|
@ -16,8 +16,9 @@
|
|||
<Compile Include="$(MSBuildThisFileDirectory)Buffers\Test_StringPool.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Buffers\Test_SpanOwner{T}.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Buffers\TrackingArrayPool{T}.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Extensions\Test_ArrayExtensions.3D.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Extensions\Test_ArrayExtensions.2D.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Extensions\Test_ArrayExtensions.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Extensions\Test_ArrayExtensions.1D.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Extensions\Test_ArrayPoolExtensions.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Extensions\Test_IMemoryOwnerExtensions.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Extensions\Test_ReadOnlyMemoryExtensions.cs" />
|
||||
|
@ -33,12 +34,18 @@
|
|||
<Compile Include="$(MSBuildThisFileDirectory)Extensions\Test_SpinLockExtensions.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Extensions\Test_StringExtensions.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Helpers\Test_HashCode{T}.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Helpers\Test_ParallelHelper.ForEach.Ref2D.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Helpers\Test_ParallelHelper.ForEach.In2D.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Helpers\Test_ParallelHelper.ThrowExceptions.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Helpers\Test_ParallelHelper.For.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Helpers\Test_ParallelHelper.For2D.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Helpers\Test_ParallelHelper.ForEach.In.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Helpers\Test_ParallelHelper.ForEach.Ref.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Helpers\Test_BitHelper.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Memory\Test_ReadOnlyMemory2D{T}.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Memory\Test_ReadOnlySpan2D{T}.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Memory\Test_Memory2D{T}.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Memory\Test_Span2D{T}.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Streams\Test_IMemoryOwnerStream.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Streams\Test_MemoryStream.ThrowExceptions.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Streams\Test_MemoryStream.cs" />
|
||||
|
|
|
@ -13,161 +13,6 @@ namespace UnitTests.Extensions
|
|||
[TestClass]
|
||||
public class Test_ArrayExtensions
|
||||
{
|
||||
[TestCategory("ArrayExtensions")]
|
||||
[TestMethod]
|
||||
public void Test_ArrayExtensions_FillArrayMid()
|
||||
{
|
||||
bool[,] test = new bool[4, 5];
|
||||
|
||||
test.Fill(true, 1, 1, 3, 2);
|
||||
|
||||
var expected = new bool[,]
|
||||
{
|
||||
{ false, false, false, false, false },
|
||||
{ false, true, true, true, false },
|
||||
{ false, true, true, true, false },
|
||||
{ false, false, false, false, false },
|
||||
};
|
||||
|
||||
CollectionAssert.AreEqual(
|
||||
expected,
|
||||
test,
|
||||
"Fill failed. Expected:\n{0}.\nActual:\n{1}",
|
||||
expected.ToArrayString(),
|
||||
test.ToArrayString());
|
||||
}
|
||||
|
||||
[TestCategory("ArrayExtensions")]
|
||||
[TestMethod]
|
||||
public void Test_ArrayExtensions_FillArrayTwice()
|
||||
{
|
||||
bool[,] test = new bool[4, 5];
|
||||
|
||||
test.Fill(true, 0, 0, 1, 2);
|
||||
test.Fill(true, 1, 3, 2, 2);
|
||||
|
||||
var expected = new bool[,]
|
||||
{
|
||||
{ true, false, false, false, false },
|
||||
{ true, false, false, true, true },
|
||||
{ false, false, false, true, true },
|
||||
{ false, false, false, false, false },
|
||||
};
|
||||
|
||||
CollectionAssert.AreEqual(
|
||||
expected,
|
||||
test,
|
||||
"Fill failed. Expected:\n{0}.\nActual:\n{1}",
|
||||
expected.ToArrayString(),
|
||||
test.ToArrayString());
|
||||
}
|
||||
|
||||
[TestCategory("ArrayExtensions")]
|
||||
[TestMethod]
|
||||
public void Test_ArrayExtensions_FillArrayNegativeSize()
|
||||
{
|
||||
bool[,] test = new bool[4, 5];
|
||||
|
||||
test.Fill(true, 3, 4, -3, -2);
|
||||
|
||||
// TODO: We may want to think about this pattern in the future:
|
||||
/*var expected = new bool[,]
|
||||
{
|
||||
{ false, false, false, false, false },
|
||||
{ false, false, false, false, false },
|
||||
{ false, false, true, true, true },
|
||||
{ false, false, true, true, true },
|
||||
};*/
|
||||
|
||||
var expected = new bool[,]
|
||||
{
|
||||
{ false, false, false, false, false },
|
||||
{ false, false, false, false, false },
|
||||
{ false, false, false, false, false },
|
||||
{ false, false, false, false, false },
|
||||
};
|
||||
|
||||
CollectionAssert.AreEqual(
|
||||
expected,
|
||||
test,
|
||||
"Fill failed. Expected:\n{0}.\nActual:\n{1}",
|
||||
expected.ToArrayString(),
|
||||
test.ToArrayString());
|
||||
}
|
||||
|
||||
[TestCategory("ArrayExtensions")]
|
||||
[TestMethod]
|
||||
public void Test_ArrayExtensions_FillArrayBottomEdgeBoundary()
|
||||
{
|
||||
bool[,] test = new bool[4, 5];
|
||||
|
||||
test.Fill(true, 1, 2, 2, 4);
|
||||
|
||||
var expected = new bool[,]
|
||||
{
|
||||
{ false, false, false, false, false },
|
||||
{ false, false, true, true, false },
|
||||
{ false, false, true, true, false },
|
||||
{ false, false, true, true, false },
|
||||
};
|
||||
|
||||
CollectionAssert.AreEqual(
|
||||
expected,
|
||||
test,
|
||||
"Fill failed. Expected:\n{0}.\nActual:\n{1}",
|
||||
expected.ToArrayString(),
|
||||
test.ToArrayString());
|
||||
}
|
||||
|
||||
[TestCategory("ArrayExtensions")]
|
||||
[TestMethod]
|
||||
public void Test_ArrayExtensions_FillArrayTopLeftCornerNegativeBoundary()
|
||||
{
|
||||
bool[,] test = new bool[4, 5];
|
||||
|
||||
test.Fill(true, -1, -1, 3, 3);
|
||||
|
||||
var expected = new bool[,]
|
||||
{
|
||||
{ true, true, false, false, false },
|
||||
{ true, true, false, false, false },
|
||||
{ false, false, false, false, false },
|
||||
{ false, false, false, false, false },
|
||||
};
|
||||
|
||||
CollectionAssert.AreEqual(
|
||||
expected,
|
||||
test,
|
||||
"Fill failed. Expected:\n{0}.\nActual:\n{1}",
|
||||
expected.ToArrayString(),
|
||||
test.ToArrayString());
|
||||
}
|
||||
|
||||
[TestCategory("ArrayExtensions")]
|
||||
[TestMethod]
|
||||
public void Test_ArrayExtensions_FillArrayBottomRightCornerBoundary()
|
||||
{
|
||||
bool[,] test = new bool[5, 4];
|
||||
|
||||
test.Fill(true, 3, 2, 3, 3);
|
||||
|
||||
var expected = new bool[,]
|
||||
{
|
||||
{ false, false, false, false },
|
||||
{ false, false, false, false },
|
||||
{ false, false, false, false },
|
||||
{ false, false, true, true },
|
||||
{ false, false, true, true },
|
||||
};
|
||||
|
||||
CollectionAssert.AreEqual(
|
||||
expected,
|
||||
test,
|
||||
"Fill failed. Expected:\n{0}.\nActual:\n{1}",
|
||||
expected.ToArrayString(),
|
||||
test.ToArrayString());
|
||||
}
|
||||
|
||||
[TestCategory("ArrayExtensions")]
|
||||
[TestMethod]
|
||||
public void Test_ArrayExtensions_Jagged_GetColumn()
|
||||
|
@ -206,82 +51,6 @@ namespace UnitTests.Extensions
|
|||
});
|
||||
}
|
||||
|
||||
[TestCategory("ArrayExtensions")]
|
||||
[TestMethod]
|
||||
public void Test_ArrayExtensions_Rectangular_GetColumn()
|
||||
{
|
||||
int[,] array =
|
||||
{
|
||||
{ 5, 2, 4 },
|
||||
{ 6, 3, 9 },
|
||||
{ 7, -1, 0 }
|
||||
};
|
||||
|
||||
var col = array.GetColumn(1).ToArray();
|
||||
|
||||
CollectionAssert.AreEquivalent(new int[] { 2, 3, -1 }, col);
|
||||
}
|
||||
|
||||
[TestCategory("ArrayExtensions")]
|
||||
[TestMethod]
|
||||
public void Test_ArrayExtensions_Rectangular_GetColumn_Exception()
|
||||
{
|
||||
int[,] array =
|
||||
{
|
||||
{ 5, 2, 4 },
|
||||
{ 6, 3, 0 },
|
||||
{ 7, 0, 0 }
|
||||
};
|
||||
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() =>
|
||||
{
|
||||
array.GetColumn(-1).ToArray();
|
||||
});
|
||||
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() =>
|
||||
{
|
||||
array.GetColumn(3).ToArray();
|
||||
});
|
||||
}
|
||||
|
||||
[TestCategory("ArrayExtensions")]
|
||||
[TestMethod]
|
||||
public void Test_ArrayExtensions_Rectangular_GetRow()
|
||||
{
|
||||
int[,] array =
|
||||
{
|
||||
{ 5, 2, 4 },
|
||||
{ 6, 3, 9 },
|
||||
{ 7, -1, 0 }
|
||||
};
|
||||
|
||||
var col = array.GetRow(1).ToArray();
|
||||
|
||||
CollectionAssert.AreEquivalent(new int[] { 6, 3, 9 }, col);
|
||||
}
|
||||
|
||||
[TestCategory("ArrayExtensions")]
|
||||
[TestMethod]
|
||||
public void Test_ArrayExtensions_Rectangular_GetRow_Exception()
|
||||
{
|
||||
int[,] array =
|
||||
{
|
||||
{ 5, 2, 4 },
|
||||
{ 6, 3, 0 },
|
||||
{ 7, 0, 0 }
|
||||
};
|
||||
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() =>
|
||||
{
|
||||
array.GetRow(-1).ToArray();
|
||||
});
|
||||
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() =>
|
||||
{
|
||||
array.GetRow(3).ToArray();
|
||||
});
|
||||
}
|
||||
|
||||
[TestCategory("ArrayExtensions")]
|
||||
[TestMethod]
|
||||
public void Test_ArrayExtensions_Rectangular_ToString()
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using Microsoft.Toolkit.Uwp.UI.Controls;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting.AppContainer;
|
||||
using Windows.UI.Xaml.Automation.Peers;
|
||||
|
||||
namespace UnitTests.UWP.UI.Controls
|
||||
{
|
||||
[TestClass]
|
||||
public class Test_RadialGauge
|
||||
{
|
||||
/// <summary>
|
||||
/// Verifies that the UIA name is valid and makes sense
|
||||
/// </summary>
|
||||
[TestCategory("Test_TextToolbar_Localization")]
|
||||
[UITestMethod]
|
||||
public void VerifyUIAName()
|
||||
{
|
||||
var gauge = new RadialGauge()
|
||||
{
|
||||
Minimum = 0,
|
||||
Maximum = 100,
|
||||
Value = 20
|
||||
};
|
||||
|
||||
var gaugePeer = FrameworkElementAutomationPeer.CreatePeerForElement(gauge);
|
||||
|
||||
Assert.IsTrue(gaugePeer.GetName().Contains(gauge.Value.ToString()), "Verify that the UIA name contains the value of the RadialGauge.");
|
||||
Assert.IsTrue(gaugePeer.GetName().Contains("no unit"), "The UIA name should indicate that unit was not specified.");
|
||||
|
||||
gauge.Unit = "KM/H";
|
||||
Assert.IsTrue(gaugePeer.GetName().Contains(gauge.Unit), "The UIA name should report the unit of the RadialGauge.");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -13,11 +13,31 @@ namespace UnitTests.UI.Controls
|
|||
[TestClass]
|
||||
public class Test_UniformGrid_FreeSpots
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a <see cref="TakenSpotsReferenceHolder"/> instance with the specified values.
|
||||
/// </summary>
|
||||
/// <param name="array">The source array to populate the instance to return.</param>
|
||||
/// <returns>A <see cref="TakenSpotsReferenceHolder"/> with the given values.</returns>
|
||||
private static TakenSpotsReferenceHolder CreateTakenSpotsReferenceHolder(bool[,] array)
|
||||
{
|
||||
var refHolder = new TakenSpotsReferenceHolder(array.GetLength(0), array.GetLength(1));
|
||||
|
||||
for (int i = 0; i < array.GetLength(0); i++)
|
||||
{
|
||||
for (int j = 0; j < array.GetLength(1); j++)
|
||||
{
|
||||
refHolder[i, j] = array[i, j];
|
||||
}
|
||||
}
|
||||
|
||||
return refHolder;
|
||||
}
|
||||
|
||||
[TestCategory("UniformGrid")]
|
||||
[UITestMethod]
|
||||
public void Test_UniformGrid_GetFreeSpots_Basic()
|
||||
{
|
||||
var testRef = new TakenSpotsReferenceHolder(new bool[4, 5]
|
||||
var testRef = CreateTakenSpotsReferenceHolder(new bool[4, 5]
|
||||
{
|
||||
{ false, true, false, true, false },
|
||||
{ false, true, true, true, false },
|
||||
|
@ -47,7 +67,7 @@ namespace UnitTests.UI.Controls
|
|||
[UITestMethod]
|
||||
public void Test_UniformGrid_GetFreeSpots_FirstColumn()
|
||||
{
|
||||
var testRef = new TakenSpotsReferenceHolder(new bool[4, 5]
|
||||
var testRef = CreateTakenSpotsReferenceHolder(new bool[4, 5]
|
||||
{
|
||||
{ true, false, false, true, false },
|
||||
{ false, true, true, true, false },
|
||||
|
@ -77,7 +97,7 @@ namespace UnitTests.UI.Controls
|
|||
[UITestMethod]
|
||||
public void Test_UniformGrid_GetFreeSpots_FirstColumnEndBoundMinusOne()
|
||||
{
|
||||
var testRef = new TakenSpotsReferenceHolder(new bool[3, 3]
|
||||
var testRef = CreateTakenSpotsReferenceHolder(new bool[3, 3]
|
||||
{
|
||||
{ false, false, false },
|
||||
{ false, false, false },
|
||||
|
@ -105,7 +125,7 @@ namespace UnitTests.UI.Controls
|
|||
[UITestMethod]
|
||||
public void Test_UniformGrid_GetFreeSpots_FirstColumnEndBound()
|
||||
{
|
||||
var testRef = new TakenSpotsReferenceHolder(new bool[3, 3]
|
||||
var testRef = CreateTakenSpotsReferenceHolder(new bool[3, 3]
|
||||
{
|
||||
{ false, false, false },
|
||||
{ false, false, false },
|
||||
|
@ -133,7 +153,7 @@ namespace UnitTests.UI.Controls
|
|||
[UITestMethod]
|
||||
public void Test_UniformGrid_GetFreeSpots_FirstColumnEndBound_TopDown()
|
||||
{
|
||||
var testRef = new TakenSpotsReferenceHolder(new bool[3, 3]
|
||||
var testRef = CreateTakenSpotsReferenceHolder(new bool[3, 3]
|
||||
{
|
||||
{ false, false, false },
|
||||
{ false, false, false },
|
||||
|
@ -161,7 +181,7 @@ namespace UnitTests.UI.Controls
|
|||
[UITestMethod]
|
||||
public void Test_UniformGrid_GetFreeSpots_VerticalOrientation()
|
||||
{
|
||||
var testRef = new TakenSpotsReferenceHolder(new bool[4, 5]
|
||||
var testRef = CreateTakenSpotsReferenceHolder(new bool[4, 5]
|
||||
{
|
||||
{ false, false, false, true, false },
|
||||
{ false, true, true, false, false },
|
||||
|
|
|
@ -179,6 +179,7 @@
|
|||
<Compile Include="PrivateType.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="Helpers\Test_WeakEventListener.cs" />
|
||||
<Compile Include="UI\Controls\Test_RadialGauge.cs" />
|
||||
<Compile Include="UI\Controls\Test_TextToolbar_Localization.cs" />
|
||||
<Compile Include="UI\Controls\Test_InfiniteCanvas_Regression.cs" />
|
||||
<Compile Include="UI\Controls\Test_TokenizingTextBox_General.cs" />
|
||||
|
@ -498,4 +499,4 @@
|
|||
<Target Name="AfterBuild">
|
||||
</Target>
|
||||
-->
|
||||
</Project>
|
||||
</Project>
|
Загрузка…
Ссылка в новой задаче