1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
|
using Unity.Mathematics;
using Unity.Burst;
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
using System.Runtime.CompilerServices;
using System.Collections.Generic;
namespace Pathfinding.Util {
/// <summary>
/// Replacement for System.Span which is compatible with earlier versions of C#.
///
/// Warning: These spans do not in any way guarantee that the memory they refer to is valid. It is up to the user to make sure
/// the memory is not deallocated before usage. It should never be used to refer to managed heap memory without pinning it, since unpinned managed memory can be moved by some runtimes.
///
/// This has several benefits over e.g. UnsafeList:
/// - It is faster to index into a span than into an UnsafeList, especially from C#. In fact, indexing into an UnsafeSpan is as fast as indexing into a native C# array.
/// - As a comparison, indexing into a NativeArray can easily be 10x slower, and indexing into an UnsafeList is at least a few times slower.
/// - You can create a UnsafeSpan from a C# array by pinning it.
/// - It can be sliced efficiently.
/// - It supports ref returns for the indexing operations.
/// </summary>
public readonly struct UnsafeSpan<T> where T : unmanaged {
[NativeDisableUnsafePtrRestriction]
internal readonly unsafe T* ptr;
internal readonly uint length;
/// <summary>Number of elements in this span</summary>
public int Length => (int)length;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public unsafe UnsafeSpan(void* ptr, int length) {
if (length < 0) throw new System.ArgumentOutOfRangeException();
if (length > 0 && ptr == null) throw new System.ArgumentNullException();
this.ptr = (T*)ptr;
this.length = (uint)length;
}
/// <summary>
/// Creates a new UnsafeSpan from a C# array.
/// The array is pinned to ensure it does not move while the span is in use.
///
/// You must unpin the pinned memory using UnsafeUtility.ReleaseGCObject when you are done with the span.
/// </summary>
public unsafe UnsafeSpan(T[] data, out ulong gcHandle) {
unsafe {
this.ptr = (T*)UnsafeUtility.PinGCArrayAndGetDataAddress(data, out gcHandle);
}
this.length = (uint)data.Length;
}
/// <summary>
/// Allocates a new UnsafeSpan with the specified length.
/// The memory is not initialized.
///
/// You are responsible for freeing the memory using the same allocator when you are done with it.
/// </summary>
public UnsafeSpan(Allocator allocator, int length) {
unsafe {
if (length < 0) throw new System.ArgumentOutOfRangeException();
if (length > 0) this.ptr = AllocatorManager.Allocate<T>(allocator, length);
else this.ptr = null;
this.length = (uint)length;
}
}
public ref T this[int index] {
// With aggressive inlining the performance of indexing is essentially the same as indexing into a native C# array
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get {
unsafe {
if ((uint)index >= length) throw new System.IndexOutOfRangeException();
Unity.Burst.CompilerServices.Hint.Assume(ptr != null);
return ref *(ptr + index);
}
}
}
public ref T this[uint index] {
// With aggressive inlining the performance of indexing is essentially the same as indexing into a native C# array
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get {
unsafe {
if (index >= length) throw new System.IndexOutOfRangeException();
Unity.Burst.CompilerServices.Hint.Assume(ptr != null);
Unity.Burst.CompilerServices.Hint.Assume(ptr + index != null);
return ref *(ptr + index);
}
}
}
/// <summary>
/// Returns a copy of this span, but with a different data-type.
/// The new data-type must have the same size as the old one.
///
/// In burst, this should effectively be a no-op, except possibly a branch.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public UnsafeSpan<U> Reinterpret<U> () where U : unmanaged {
unsafe {
if (sizeof(T) != sizeof(U)) throw new System.InvalidOperationException("Cannot reinterpret span because the size of the types do not match");
return new UnsafeSpan<U>(ptr, (int)length);
}
}
/// <summary>
/// Creates a new span which is a slice of this span.
/// The new span will start at the specified index and have the specified length.
/// </summary>
public UnsafeSpan<T> Slice (int start, int length) {
if (start < 0 || length < 0 || start + length > this.length) throw new System.ArgumentOutOfRangeException();
unsafe {
return new UnsafeSpan<T>(ptr + start, length);
}
}
/// <summary>
/// Creates a new span which is a slice of this span.
/// The new span will start at the specified index and continue to the end of this span.
/// </summary>
public UnsafeSpan<T> Slice (int start) {
return Slice(start, (int)this.length - start);
}
/// <summary>Copy the range [startIndex,startIndex+count) to [toIndex,toIndex+count)</summary>
public void Move (int startIndex, int toIndex, int count) {
unsafe {
if (count < 0) throw new System.ArgumentOutOfRangeException();
if (startIndex < 0 || startIndex + count > length) throw new System.ArgumentOutOfRangeException();
if (toIndex < 0 || toIndex + count > length) throw new System.ArgumentOutOfRangeException();
// If length is zero, the pointers may be null, which is technically undefined behavior (but in practice usually fine)
if (count == 0) return;
UnsafeUtility.MemMove(ptr + toIndex, ptr + startIndex, (long)sizeof(T) * (long)count);
}
}
/// <summary>
/// Copies the memory of this span to another span.
/// The other span must be large enough to hold the contents of this span.
///
/// Note: Assumes the other span does not alias this one.
/// </summary>
public void CopyTo (UnsafeSpan<T> other) {
if (other.length < length) throw new System.ArgumentException();
unsafe {
// If length is zero, the pointers may be null, which is technically undefined behavior (but in practice usually fine)
if (length > 0) UnsafeUtility.MemCpy(other.ptr, ptr, (long)sizeof(T) * (long)length);
}
}
/// <summary>Appends all elements in this span to the given list</summary>
public void CopyTo (List<T> buffer) {
if (buffer.Capacity < buffer.Count + Length) buffer.Capacity = buffer.Count + Length;
for (int i = 0; i < Length; i++) buffer.Add(this[i]);
}
/// <summary>
/// Creates a new copy of the span allocated using the given allocator.
///
/// You are responsible for freeing this memory using the same allocator when you are done with it.
/// </summary>
public UnsafeSpan<T> Clone (Allocator allocator) {
unsafe {
var clone = new UnsafeSpan<T>(allocator, (int)length);
CopyTo(clone);
return clone;
}
}
/// <summary>Converts the span to a managed array</summary>
public T[] ToArray () {
var arr = new T[length];
if (length > 0) {
unsafe {
fixed (T* ptr = arr) {
UnsafeUtility.MemCpy(ptr, this.ptr, (long)sizeof(T) * (long)length);
}
}
}
return arr;
}
/// <summary>
/// Frees the underlaying memory.
///
/// Warning: The span must have been allocated using the specified allocator.
///
/// Warning: You must never use this span (or any other span referencing the same memory) again after calling this method.
/// </summary>
public unsafe void Free (Allocator allocator) {
if (length > 0) AllocatorManager.Free<T>(allocator, ptr, (int)length);
}
}
public static class SpanExtensions {
public static void FillZeros<T>(this UnsafeSpan<T> span) where T : unmanaged {
unsafe {
if (span.length > 0) UnsafeUtility.MemSet(span.ptr, 0, (long)sizeof(T) * (long)span.length);
}
}
public static void Fill<T>(this UnsafeSpan<T> span, T value) where T : unmanaged {
unsafe {
// This is wayy faster than a C# for loop (easily 10x faster).
// It is also faster than a burst loop (at least as long as the span is reasonably large).
// It also generates a lot less code than a burst for loop.
if (span.length > 0) {
// If this is too big, unity seems to overflow and crash internally
if ((long)sizeof(T) * (long)span.length > (long)int.MaxValue) throw new System.ArgumentException("Span is too large to fill");
UnsafeUtility.MemCpyReplicate(span.ptr, &value, sizeof(T), (int)span.length);
}
}
}
/// <summary>
/// Copies the contents of a NativeArray to this span.
/// The span must be large enough to hold the contents of the array.
/// </summary>
public static void CopyFrom<T>(this UnsafeSpan<T> span, NativeArray<T> array) where T : unmanaged {
CopyFrom(span, array.AsUnsafeReadOnlySpan());
}
/// <summary>
/// Copies the contents of another span to this span.
/// The span must be large enough to hold the contents of the array.
/// </summary>
public static void CopyFrom<T>(this UnsafeSpan<T> span, UnsafeSpan<T> other) where T : unmanaged {
if (other.Length > span.Length) throw new System.InvalidOperationException();
if (other.Length == 0) return;
unsafe {
UnsafeUtility.MemCpy(span.ptr, other.ptr, (long)sizeof(T) * (long)other.Length);
}
}
/// <summary>
/// Copies the contents of an array to this span.
/// The span must be large enough to hold the contents of the array.
/// </summary>
public static void CopyFrom<T>(this UnsafeSpan<T> span, T[] array) where T : unmanaged {
if (array.Length > span.Length) throw new System.InvalidOperationException();
if (array.Length == 0) return;
unsafe {
var ptr = UnsafeUtility.PinGCArrayAndGetDataAddress(array, out var gcHandle);
UnsafeUtility.MemCpy(span.ptr, ptr, (long)sizeof(T) * (long)array.Length);
UnsafeUtility.ReleaseGCObject(gcHandle);
}
}
/// <summary>
/// Converts an UnsafeAppendBuffer to a span.
/// The buffer must be a multiple of the element size.
///
/// The span is a view of the buffer memory, so do not dispose the buffer while the span is in use.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static UnsafeSpan<T> AsUnsafeSpan<T>(this UnsafeAppendBuffer buffer) where T : unmanaged {
unsafe {
var items = buffer.Length / UnsafeUtility.SizeOf<T>();
if (items * UnsafeUtility.SizeOf<T>() != buffer.Length) throw new System.ArgumentException("Buffer length is not a multiple of the element size");
return new UnsafeSpan<T>(buffer.Ptr, items);
}
}
/// <summary>
/// Converts a NativeList to a span.
///
/// The span is a view of the list memory, so do not dispose the list while the span is in use.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static UnsafeSpan<T> AsUnsafeSpan<T>(this NativeList<T> list) where T : unmanaged {
unsafe {
return new UnsafeSpan<T>(list.GetUnsafePtr(), list.Length);
}
}
/// <summary>
/// Converts a NativeArray to a span.
///
/// The span is a view of the array memory, so do not dispose the array while the span is in use.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static UnsafeSpan<T> AsUnsafeSpan<T>(this NativeArray<T> arr) where T : unmanaged {
unsafe {
return new UnsafeSpan<T>(arr.GetUnsafePtr(), arr.Length);
}
}
/// <summary>
/// Converts a NativeArray to a span without performing any checks.
///
/// The span is a view of the array memory, so do not dispose the array while the span is in use.
/// This method does not perform any checks to ensure that the array is safe to write to or read from.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static UnsafeSpan<T> AsUnsafeSpanNoChecks<T>(this NativeArray<T> arr) where T : unmanaged {
unsafe {
return new UnsafeSpan<T>(NativeArrayUnsafeUtility.GetUnsafeBufferPointerWithoutChecks(arr), arr.Length);
}
}
/// <summary>
/// Converts a NativeArray to a span, assuming it will only be read.
///
/// The span is a view of the array memory, so do not dispose the array while the span is in use.
///
/// Warning: No checks are done to ensure that you only read from the array. You are responsible for ensuring that you do not write to the span.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static UnsafeSpan<T> AsUnsafeReadOnlySpan<T>(this NativeArray<T> arr) where T : unmanaged {
unsafe {
return new UnsafeSpan<T>(arr.GetUnsafeReadOnlyPtr(), arr.Length);
}
}
/// <summary>
/// Converts an UnsafeList to a span.
///
/// The span is a view of the list memory, so do not dispose the list while the span is in use.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static UnsafeSpan<T> AsUnsafeSpan<T>(this UnsafeList<T> arr) where T : unmanaged {
unsafe {
return new UnsafeSpan<T>(arr.Ptr, arr.Length);
}
}
/// <summary>
/// Converts a NativeSlice to a span.
///
/// The span is a view of the slice memory, so do not dispose the underlaying memory allocation while the span is in use.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static UnsafeSpan<T> AsUnsafeSpan<T>(this NativeSlice<T> slice) where T : unmanaged {
unsafe {
return new UnsafeSpan<T>(slice.GetUnsafePtr(), slice.Length);
}
}
/// <summary>Returns true if the value exists in the span</summary>
public static bool Contains<T>(this UnsafeSpan<T> span, T value) where T : unmanaged, System.IEquatable<T> {
return IndexOf(span, value) != -1;
}
/// <summary>
/// Returns the index of the first occurrence of a value in the span.
/// If the value is not found, -1 is returned.
/// </summary>
public static int IndexOf<T>(this UnsafeSpan<T> span, T value) where T : unmanaged, System.IEquatable<T> {
unsafe {
return System.MemoryExtensions.IndexOf(new System.ReadOnlySpan<T>(span.ptr, (int)span.length), value);
}
}
/// <summary>Sorts the span in ascending order</summary>
public static void Sort<T>(this UnsafeSpan<T> span) where T : unmanaged, System.IComparable<T> {
unsafe {
NativeSortExtension.Sort<T>(span.ptr, span.Length);
}
}
/// <summary>Sorts the span in ascending order</summary>
public static void Sort<T, U>(this UnsafeSpan<T> span, U comp) where T : unmanaged where U : System.Collections.Generic.IComparer<T> {
unsafe {
NativeSortExtension.Sort<T, U>(span.ptr, span.Length, comp);
}
}
#if !MODULE_COLLECTIONS_2_4_0_OR_NEWER
/// <summary>Shifts elements toward the end of this list, increasing its length</summary>
public static void InsertRange<T>(this NativeList<T> list, int index, int count) where T : unmanaged {
list.ResizeUninitialized(list.Length + count);
list.AsUnsafeSpan().Move(index, index + count, list.Length - (index + count));
}
#endif
#if !MODULE_COLLECTIONS_2_1_0_OR_NEWER
/// <summary>Appends value count times to the end of this list</summary>
public static void AddReplicate<T>(this NativeList<T> list, T value, int count) where T : unmanaged {
var origLength = list.Length;
list.ResizeUninitialized(origLength + count);
list.AsUnsafeSpan().Slice(origLength).Fill(value);
}
#endif
}
}
|