feat(unsafe-collections)!: move span/array copy methods to core

CopyTo, CopyFrom, ToArray, and ToList are now implemented as instance methods on UnsafeArray<T> and UnsafeList<T>, replacing the previous extension methods. This change enables more direct and efficient copying between unsafe collections and managed spans, arrays, and lists, with overloads for ranges and resizing as needed. Extension methods for these operations have been removed. Minor cleanup and usage updates are included.

BREAKING CHANGE: Extension methods for copying and converting between unsafe collections and spans/arrays/lists have been removed. Use the new instance methods instead.
This commit is contained in:
2026-03-28 11:28:50 +09:00
parent e1c9a3781b
commit 04dd7222d9
6 changed files with 186 additions and 135 deletions

View File

@@ -1,3 +1,4 @@
using Misaki.HighPerformance.LowLevel.Utilities;
using System.Collections.Concurrent; using System.Collections.Concurrent;
namespace Misaki.HighPerformance.Jobs; namespace Misaki.HighPerformance.Jobs;
@@ -121,6 +122,5 @@ internal class WorkerThread : IDisposable
public void Dispose() public void Dispose()
{ {
_thread.Join(); _thread.Join();
_localQueue.Clear();
} }
} }

View File

@@ -1,7 +1,3 @@
#if DEBUG
#define ENABLE_DEBUG_LAYER
#endif
using Misaki.HighPerformance.Collections; using Misaki.HighPerformance.Collections;
using System.Diagnostics; using System.Diagnostics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;

View File

@@ -286,6 +286,9 @@ public unsafe struct UnsafeArray<T> : IUnsafeCollection<T>
/// <summary> /// <summary>
/// Reinterprets the underlying buffer as an array of a different unmanaged type without copying the data. /// Reinterprets the underlying buffer as an array of a different unmanaged type without copying the data.
/// </summary> /// </summary>
/// <remarks>
/// The returned UnsafeArray<U> shares the same memory as the original array, and does not own the memory.
/// </remarks>
/// <typeparam name="U">The unmanaged type to reinterpret the buffer as.</typeparam> /// <typeparam name="U">The unmanaged type to reinterpret the buffer as.</typeparam>
/// <returns>An UnsafeArray<U> that views the same memory as the original array, but as elements of type U.</returns> /// <returns>An UnsafeArray<U> that views the same memory as the original array, but as elements of type U.</returns>
/// <exception cref="InvalidOperationException">Thrown if the total size of the buffer in bytes is not a multiple of the size of type U.</exception> /// <exception cref="InvalidOperationException">Thrown if the total size of the buffer in bytes is not a multiple of the size of type U.</exception>
@@ -304,6 +307,92 @@ public unsafe struct UnsafeArray<T> : IUnsafeCollection<T>
return new UnsafeArray<U>((U*)_buffer, newCount); return new UnsafeArray<U>((U*)_buffer, newCount);
} }
/// <summary>
/// Copies elements from a source UnsafeCollection to a destination Span, ensuring both have the same size.
/// </summary>
/// <param name="destination">Represents the target span where elements are copied to.</param>
public readonly void CopyTo(Span<T> destination)
{
var size = Math.Min(destination.Length, Count);
fixed (T* pDest = destination)
{
MemCpy(pDest, _buffer, (uint)(size * sizeof(T)));
}
}
/// <summary>
/// Copies a range of elements from a source collection to a destination span, ensuring both are adequately sized.
/// </summary>
/// <param name="destination">The span where the elements will be copied to.</param>
/// <param name="sourceIndex">The starting index in the source collection for the copy operation.</param>
/// <param name="destinationIndex">The starting index in the destination span where the elements will be placed.</param>
/// <param name="length">The number of elements to copy from the source to the destination.</param>
/// <exception cref="ArgumentOutOfRangeException">Thrown when the specified range exceeds the bounds of the source collection or destination span.</exception>
public readonly void CopyTo(Span<T> destination, int sourceIndex, int destinationIndex, int length)
{
if (sourceIndex + length > _count || destinationIndex + length > destination.Length)
{
throw new ArgumentOutOfRangeException(nameof(length), "Source collection or destination span is too small for the specified range.");
}
fixed (T* pDest = destination)
{
MemCpy(pDest + destinationIndex, _buffer + sourceIndex, (nuint)(length * sizeof(T)));
}
}
/// <summary>
/// Copies elements from a source span to a destination unsafe collection, ensuring both have the same size.
/// </summary>
/// <param name="source">Represents the span containing the elements to be copied to the unsafe collection.</param>
public void CopyFrom(ReadOnlySpan<T> source)
{
if (_count < source.Length)
{
Resize(source.Length);
}
fixed (T* pSrc = source)
{
MemCpy(_buffer, pSrc, (nuint)(source.Length * sizeof(T)));
}
}
/// <summary>
/// Copies a specified range of elements from a source span to a destination collection.
/// </summary>
/// <param name="source">The span containing the elements to be copied.</param>
/// <param name="sourceIndex">The starting index in the source span from which to begin copying.</param>
/// <param name="destinationIndex">The starting index in the destination collection where the elements will be placed.</param>
/// <param name="length">The number of elements to copy from the source span to the destination collection.</param>
/// <exception cref="ArgumentOutOfRangeException">Thrown when the specified range exceeds the bounds of the source span or destination collection.</exception>
public void CopyFrom(ReadOnlySpan<T> source, int sourceIndex, int destinationIndex, int length)
{
if (sourceIndex + length > source.Length)
{
throw new ArgumentOutOfRangeException(nameof(length), "Source span or destination collection is too small for the specified range.");
}
if (destinationIndex + length > _count)
{
Resize(destinationIndex + length);
}
fixed (T* pSrc = source)
{
MemCpy(_buffer + destinationIndex, pSrc + sourceIndex, (nuint)(length * sizeof(T)));
}
}
/// <summary>
/// Creates a new array containing all elements.
/// </summary>
/// <returns>An array containing all elements.</returns>
public readonly T[] ToArray()
{
return new Span<T>(_buffer, _count).ToArray();
}
/// <inheritdoc/> /// <inheritdoc/>
public void Dispose() public void Dispose()
{ {

View File

@@ -118,7 +118,7 @@ public unsafe struct UnsafeList<T> : IUnsafeCollection<T>
/// Use <see cref="AsParallelWriter"/> to create a parallel writer for a list. /// Use <see cref="AsParallelWriter"/> to create a parallel writer for a list.
/// The list must live at least as long as the parallel writer, and the parallel writer must not be used after the list is disposed. /// The list must live at least as long as the parallel writer, and the parallel writer must not be used after the list is disposed.
/// </remarks> /// </remarks>
public readonly unsafe struct ParallelWriter public readonly struct ParallelWriter
{ {
public readonly UnsafeList<T>* listData; public readonly UnsafeList<T>* listData;
@@ -515,17 +515,102 @@ public unsafe struct UnsafeList<T> : IUnsafeCollection<T>
return _array.AsSpan(start, length); return _array.AsSpan(start, length);
} }
/// <summary>
/// Copies elements from a source UnsafeCollection to a destination Span, ensuring both have the same size.
/// </summary>
/// <param name="destination">Represents the target span where elements are copied to.</param>
public readonly void CopyTo(Span<T> destination)
{
var size = Math.Min(destination.Length, Count);
fixed (T* pDest = destination)
{
MemCpy(pDest, _array.GetUnsafePtr(), (uint)(size * sizeof(T)));
}
}
/// <summary>
/// Copies a range of elements from a source collection to a destination span, ensuring both are adequately sized.
/// </summary>
/// <param name="destination">The span where the elements will be copied to.</param>
/// <param name="sourceIndex">The starting index in the source collection for the copy operation.</param>
/// <param name="destinationIndex">The starting index in the destination span where the elements will be placed.</param>
/// <param name="length">The number of elements to copy from the source to the destination.</param>
/// <exception cref="ArgumentOutOfRangeException">Thrown when the specified range exceeds the bounds of the source collection or destination span.</exception>
public readonly void CopyTo(Span<T> destination, int sourceIndex, int destinationIndex, int length)
{
if (sourceIndex + length > _count || destinationIndex + length > destination.Length)
{
throw new ArgumentOutOfRangeException(nameof(length), "Source collection or destination span is too small for the specified range.");
}
fixed (T* pDest = destination)
{
MemCpy(pDest + destinationIndex, (byte*)_array.GetUnsafePtr() + sourceIndex * sizeof(T), (nuint)(length * sizeof(T)));
}
}
/// <summary>
/// Copies elements from a source span to a destination unsafe collection, ensuring both have the same size.
/// </summary>
/// <param name="source">Represents the span containing the elements to be copied to the unsafe collection.</param>
public void CopyFrom(ReadOnlySpan<T> source)
{
if (_count < source.Length)
{
Resize(source.Length);
}
fixed (T* pSrc = source)
{
MemCpy(_array.GetUnsafePtr(), pSrc, (nuint)(source.Length * sizeof(T)));
}
}
/// <summary>
/// Copies a specified range of elements from a source span to a destination collection.
/// </summary>
/// <param name="source">The span containing the elements to be copied.</param>
/// <param name="sourceIndex">The starting index in the source span from which to begin copying.</param>
/// <param name="destinationIndex">The starting index in the destination collection where the elements will be placed.</param>
/// <param name="length">The number of elements to copy from the source span to the destination collection.</param>
/// <exception cref="ArgumentOutOfRangeException">Thrown when the specified range exceeds the bounds of the source span or destination collection.</exception>
public void CopyFrom(ReadOnlySpan<T> source, int sourceIndex, int destinationIndex, int length)
{
if (sourceIndex + length > source.Length)
{
throw new ArgumentOutOfRangeException(nameof(length), "Source span or destination collection is too small for the specified range.");
}
if (destinationIndex + length > _count)
{
Resize(destinationIndex + length);
}
fixed (T* pSrc = source)
{
MemCpy((byte*)_array.GetUnsafePtr() + destinationIndex * sizeof(T), pSrc + sourceIndex, (nuint)(length * sizeof(T)));
}
}
/// <summary>
/// Creates a new <see cref="List{T}"/> containing the elements.
/// </summary>
/// <returns>A <see cref="List{T}"/> containing all elements.</returns>
public readonly List<T> ToList()
{
var list = new List<T>(_count);
var span = new Span<T>(_array.GetUnsafePtr(), _count);
list.AddRange(span);
return list;
}
public void Dispose() public void Dispose()
{ {
_array.Dispose(); _array.Dispose();
_count = 0; _count = 0;
} }
public static implicit operator UnsafeArray<T>(UnsafeList<T> list)
{
return list.AsUnsafeArray();
}
public static implicit operator ReadOnlyUnsafeCollection<T>(UnsafeList<T> list) public static implicit operator ReadOnlyUnsafeCollection<T>(UnsafeList<T> list)
{ {
return list.AsReadOnly(); return list.AsReadOnly();

View File

@@ -11,100 +11,6 @@ namespace Misaki.HighPerformance.LowLevel.Utilities;
/// </summary> /// </summary>
public static unsafe class UnsafeCollectionExtensions public static unsafe class UnsafeCollectionExtensions
{ {
/// <summary>
/// Copies elements from a source UnsafeCollection to a destination Span, ensuring both have the same size.
/// </summary>
/// <typeparam name="C">The type of the destination collection, which must implement <see cref="IUnsafeCollection{T}"/>.</typeparam>
/// <typeparam name="T">Specifies the type of elements being copied, which must be unmanaged.</typeparam>
/// <param name="source">Represents the source collection from which elements are copied.</param>
/// <param name="destination">Represents the target span where elements are copied to.</param>
/// <exception cref="ArgumentException">Thrown when the sizes of the source collection and destination span do not match.</exception>
public static void CopyTo<C, T>(this C source, Span<T> destination)
where C: IUnsafeCollection<T> where T : unmanaged
{
if (source.Count > destination.Length)
{
throw new ArgumentException("Source collection is larger than the destination span.");
}
fixed (T* pDest = destination)
{
MemCpy(pDest, source.GetUnsafePtr(), (uint)(source.Count * sizeof(T)));
}
}
/// <summary>
/// Copies a range of elements from a source collection to a destination span, ensuring both are adequately sized.
/// </summary>
/// <typeparam name="C">The type of the destination collection, which must implement <see cref="IUnsafeCollection{T}"/>.</typeparam>
/// <typeparam name="T">Specifies the type of elements being copied, which must be a value type.</typeparam>
/// <param name="source">The collection from which elements are copied.</param>
/// <param name="destination">The span where the elements will be copied to.</param>
/// <param name="sourceIndex">The starting index in the source collection for the copy operation.</param>
/// <param name="destinationIndex">The starting index in the destination span where the elements will be placed.</param>
/// <param name="length">The number of elements to copy from the source to the destination.</param>
/// <exception cref="ArgumentException">Thrown when the specified range exceeds the bounds of the source collection or destination span.</exception>
public static void CopyTo<C, T>(this C source, Span<T> destination, uint sourceIndex, uint destinationIndex, uint length)
where C : IUnsafeCollection<T> where T : unmanaged
{
if (sourceIndex + length > source.Count || destinationIndex + length > destination.Length)
{
throw new ArgumentException("Source collection or destination span is too small for the specified range.");
}
fixed (T* pDest = destination)
{
MemCpy(pDest + destinationIndex, (byte*)source.GetUnsafePtr() + sourceIndex * sizeof(T), (uint)(length * sizeof(T)));
}
}
/// <summary>
/// Copies elements from a source span to a destination unsafe collection, ensuring both have the same size.
/// </summary>
/// <typeparam name="C">The type of the destination collection, which must implement <see cref="IUnsafeCollection{T}"/>.</typeparam>
/// <typeparam name="T">Specifies the type of elements being copied, which must be unmanaged.</typeparam>
/// <param name="destination">Represents the unsafe collection that will receive the copied elements.</param>
/// <param name="source">Represents the span containing the elements to be copied to the unsafe collection.</param>
/// <exception cref="ArgumentException">Thrown when the source span and destination collection have different sizes.</exception>
public static void CopyFrom<C, T>(this C destination, ReadOnlySpan<T> source)
where C : IUnsafeCollection<T> where T : unmanaged
{
if (destination.Count < source.Length)
{
throw new ArgumentException("Destination collection is smaller than the source span.");
}
fixed (T* pSrc = source)
{
MemCpy(destination.GetUnsafePtr(), pSrc, (uint)(source.Length * sizeof(T)));
}
}
/// <summary>
/// Copies a specified range of elements from a source span to a destination collection.
/// </summary>
/// <typeparam name="C">The type of the destination collection, which must implement <see cref="IUnsafeCollection{T}"/>.</typeparam>
/// <typeparam name="T">Represents the type of elements being copied, which must be unmanaged.</typeparam>
/// <param name="destination">The collection where elements will be copied to.</param>
/// <param name="source">The span containing the elements to be copied.</param>
/// <param name="sourceIndex">The starting index in the source span from which to begin copying.</param>
/// <param name="destinationIndex">The starting index in the destination collection where the elements will be placed.</param>
/// <param name="length">The number of elements to copy from the source span to the destination collection.</param>
/// <exception cref="ArgumentException">Thrown when the specified range exceeds the bounds of the source span or destination collection.</exception>
public static void CopyFrom<C, T>(this C destination, ReadOnlySpan<T> source, uint sourceIndex, uint destinationIndex, uint length)
where C : IUnsafeCollection<T> where T : unmanaged
{
if (sourceIndex + length > source.Length || destinationIndex + length > destination.Count)
{
throw new ArgumentException("Source span or destination collection is too small for the specified range.");
}
fixed (T* pSrc = source)
{
MemCpy((byte*)destination.GetUnsafePtr() + destinationIndex * sizeof(T), pSrc + sourceIndex, (uint)(length * sizeof(T)));
}
}
/// <summary> /// <summary>
/// Converts a managed array to an UnsafeArray by copying its elements to unmanaged memory. /// Converts a managed array to an UnsafeArray by copying its elements to unmanaged memory.
/// </summary> /// </summary>
@@ -142,33 +48,4 @@ public static unsafe class UnsafeCollectionExtensions
return list; return list;
} }
/// <summary>
/// Creates a new array containing all elements from the specified unsafe collection.
/// </summary>
/// <typeparam name="C">The type of the source collection, which must implement <see cref="IUnsafeCollection{T}"/>.</typeparam>
/// <typeparam name="T">The type of elements contained in the collection. Must be an unmanaged type.</typeparam>
/// <param name="source">The collection whose elements will be copied to the new array. Must not be null.</param>
/// <returns>An array containing all elements from <paramref name="source"/> in their current order.</returns>
public static T[] ToArray<C, T>(this C source)
where C : IUnsafeCollection<T> where T : unmanaged
{
return new Span<T>(source.GetUnsafePtr(), source.Count).ToArray();
}
/// <summary>
/// Creates a new <see cref="List{T}"/> containing the elements of the specified unsafe collection.
/// </summary>
/// <typeparam name="C">The type of the source collection, which must implement <see cref="IUnsafeCollection{T}"/>.</typeparam>
/// <typeparam name="T">The type of elements contained in the collection. Must be an unmanaged type.</typeparam>
/// <param name="source">The unsafe collection whose elements are to be copied to the new list. Must not be null.</param>
/// <returns>A <see cref="List{T}"/> containing all elements from <paramref name="source"/> in their original order.</returns>
public static List<T> ToList<C, T>(this C source)
where C : IUnsafeCollection<T> where T : unmanaged
{
var list = new List<T>(source.Count);
var span = new Span<T>(source.GetUnsafePtr(), source.Count);
span.CopyTo(CollectionsMarshal.AsSpan(list));
return list;
}
} }

View File

@@ -1,5 +1,6 @@
using Misaki.HighPerformance.LowLevel.Buffer; using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections; using Misaki.HighPerformance.LowLevel.Collections;
using Misaki.HighPerformance.LowLevel.Utilities;
//BenchmarkRunner.Run<SPMDBenchmark>(); //BenchmarkRunner.Run<SPMDBenchmark>();
//var hashMap = new UnsafeHashMap<int, int>(10, Misaki.HighPerformance.LowLevel.Buffer.Allocator.Persistent); //var hashMap = new UnsafeHashMap<int, int>(10, Misaki.HighPerformance.LowLevel.Buffer.Allocator.Persistent);
@@ -41,5 +42,8 @@ var opts = new AllocationManagerInitOpts
AllocationManager.Initialize(opts); AllocationManager.Initialize(opts);
var arr = new UnsafeArray<int>(10, Allocator.FreeList); using var arr = new UnsafeArray<int>(10, Allocator.FreeList);
var marr = new int[10];
arr.CopyTo(marr);
AllocationManager.Dispose(); AllocationManager.Dispose();