A game about forced loneliness, made by TACStudios
1using System;
2using NUnit.Framework;
3using Unity.Burst;
4using Unity.Collections;
5using Unity.Collections.LowLevel.Unsafe;
6using Unity.Collections.Tests;
7using Unity.Jobs;
8using UnityEngine;
9using Assert = FastAssert;
10
11internal class NativeStreamTests : CollectionsTestFixture
12{
13 [BurstCompile(CompileSynchronously = true)]
14 struct WriteInts : IJobParallelFor
15 {
16 public NativeStream.Writer Writer;
17
18 public void Execute(int index)
19 {
20 Writer.BeginForEachIndex(index);
21 for (int i = 0; i != index; i++)
22 Writer.Write(i);
23 Writer.EndForEachIndex();
24 }
25 }
26
27 struct ReadInts : IJobParallelFor
28 {
29 public NativeStream.Reader Reader;
30
31 public void Execute(int index)
32 {
33 int count = Reader.BeginForEachIndex(index);
34 Assert.AreEqual(count, index);
35
36 for (int i = 0; i != index; i++)
37 {
38 Assert.AreEqual(index - i, Reader.RemainingItemCount);
39 var peekedValue = Reader.Peek<int>();
40 var value = Reader.Read<int>();
41 Assert.AreEqual(i, value);
42 Assert.AreEqual(i, peekedValue);
43 }
44
45 Reader.EndForEachIndex();
46 }
47 }
48
49 [Test]
50 public void NativeStream_PopulateInts([Values(1, 100, 200)] int count, [Values(1, 3, 10)] int batchSize)
51 {
52 var stream = new NativeStream(count, CommonRwdAllocator.Handle);
53 var fillInts = new WriteInts {Writer = stream.AsWriter()};
54 var jobHandle = fillInts.Schedule(count, batchSize);
55
56 var compareInts = new ReadInts {Reader = stream.AsReader()};
57 var res0 = compareInts.Schedule(count, batchSize, jobHandle);
58 var res1 = compareInts.Schedule(count, batchSize, jobHandle);
59
60 res0.Complete();
61 res1.Complete();
62
63 stream.Dispose();
64 }
65
66 static void ExpectedCount(ref NativeStream container, int expected)
67 {
68 Assert.AreEqual(expected == 0, container.IsEmpty());
69 Assert.AreEqual(expected, container.Count());
70 }
71
72 [Test]
73 public void NativeStream_CreateAndDestroy([Values(1, 100, 200)] int count)
74 {
75 var stream = new NativeStream(count, Allocator.Temp);
76
77 Assert.IsTrue(stream.IsCreated);
78 Assert.IsTrue(stream.ForEachCount == count);
79 ExpectedCount(ref stream, 0);
80
81 stream.Dispose();
82 Assert.IsFalse(stream.IsCreated);
83 }
84
85 [Test]
86 public void NativeStream_ItemCount([Values(1, 100, 200)] int count, [Values(1, 3, 10)] int batchSize)
87 {
88 var stream = new NativeStream(count, CommonRwdAllocator.Handle);
89 var fillInts = new WriteInts {Writer = stream.AsWriter()};
90 fillInts.Schedule(count, batchSize).Complete();
91
92 ExpectedCount(ref stream, count * (count - 1) / 2);
93
94 stream.Dispose();
95 }
96
97 [Test]
98 public void NativeStream_ToArray([Values(1, 100, 200)] int count, [Values(1, 3, 10)] int batchSize)
99 {
100 var stream = new NativeStream(count, CommonRwdAllocator.Handle);
101 var fillInts = new WriteInts {Writer = stream.AsWriter()};
102 fillInts.Schedule(count, batchSize).Complete();
103 ExpectedCount(ref stream, count * (count - 1) / 2);
104
105 var array = stream.ToNativeArray<int>(Allocator.Temp);
106 int itemIndex = 0;
107
108 for (int i = 0; i != count; ++i)
109 {
110 for (int j = 0; j < i; ++j)
111 {
112 Assert.AreEqual(j, array[itemIndex]);
113 itemIndex++;
114 }
115 }
116
117 array.Dispose();
118 stream.Dispose();
119 }
120
121 [Test]
122 public void NativeStream_DisposeJob()
123 {
124 var stream = new NativeStream(100, CommonRwdAllocator.Handle);
125 Assert.IsTrue(stream.IsCreated);
126
127 var fillInts = new WriteInts {Writer = stream.AsWriter()};
128 var writerJob = fillInts.Schedule(100, 16);
129
130 var disposeJob = stream.Dispose(writerJob);
131 Assert.IsFalse(stream.IsCreated);
132
133 disposeJob.Complete();
134 }
135
136#if ENABLE_UNITY_COLLECTIONS_CHECKS
137 [Test]
138 public void NativeStream_ParallelWriteThrows()
139 {
140 var stream = new NativeStream(100, CommonRwdAllocator.Handle);
141 var fillInts = new WriteInts {Writer = stream.AsWriter()};
142
143 var writerJob = fillInts.Schedule(100, 16);
144 Assert.Throws<InvalidOperationException>(() => fillInts.Schedule(100, 16));
145
146 writerJob.Complete();
147 stream.Dispose();
148 }
149
150 [Test]
151 public void NativeStream_ScheduleCreateThrows_NativeList()
152 {
153 var container = new NativeList<int>(Allocator.Persistent);
154 container.Add(2);
155
156 NativeStream stream;
157 var jobHandle = NativeStream.ScheduleConstruct(out stream, container, default, CommonRwdAllocator.Handle);
158
159 Assert.Throws<InvalidOperationException>(() => { int val = stream.ForEachCount; });
160
161 jobHandle.Complete();
162
163 Assert.AreEqual(1, stream.ForEachCount);
164
165 stream.Dispose();
166 container.Dispose();
167 }
168
169 [Test]
170 public void NativeStream_ScheduleCreateThrows_NativeArray()
171 {
172 var container = new NativeArray<int>(1, Allocator.Persistent);
173 container[0] = 1;
174
175 NativeStream stream;
176 var jobHandle = NativeStream.ScheduleConstruct(out stream, container, default, CommonRwdAllocator.Handle);
177
178 Assert.Throws<InvalidOperationException>(() => { int val = stream.ForEachCount; });
179
180 jobHandle.Complete();
181
182 Assert.AreEqual(1, stream.ForEachCount);
183
184 stream.Dispose();
185 container.Dispose();
186 }
187
188 [Test]
189 public void NativeStream_OutOfBoundsWriteThrows()
190 {
191 var stream = new NativeStream(1, Allocator.Temp);
192 var writer = stream.AsWriter();
193 Assert.Throws<ArgumentException>(() => writer.BeginForEachIndex(-1));
194 Assert.Throws<ArgumentException>(() => writer.BeginForEachIndex(2));
195
196 stream.Dispose();
197 }
198
199 [Test]
200 public void NativeStream_EndForEachIndexWithoutBeginThrows()
201 {
202 var stream = new NativeStream(1, Allocator.Temp);
203 var writer = stream.AsWriter();
204 Assert.Throws<ArgumentException>(() => writer.EndForEachIndex());
205
206 stream.Dispose();
207 }
208
209 [Test]
210 public void NativeStream_WriteWithoutBeginThrows()
211 {
212 var stream = new NativeStream(1, Allocator.Temp);
213 var writer = stream.AsWriter();
214 Assert.Throws<ArgumentException>(() => writer.Write(5));
215
216 stream.Dispose();
217 }
218
219 [Test]
220 public void NativeStream_WriteAfterEndThrows()
221 {
222 var stream = new NativeStream(1, Allocator.Temp);
223 var writer = stream.AsWriter();
224 writer.BeginForEachIndex(0);
225 writer.Write(2);
226 Assert.AreEqual(1, writer.ForEachCount);
227 writer.EndForEachIndex();
228
229 Assert.AreEqual(1, writer.ForEachCount);
230 Assert.Throws<ArgumentException>(() => writer.Write(5));
231
232 stream.Dispose();
233 }
234
235 [Test]
236 public void NativeStream_UnbalancedBeginThrows()
237 {
238 var stream = new NativeStream(2, Allocator.Temp);
239 var writer = stream.AsWriter();
240 writer.BeginForEachIndex(0);
241 // Missing EndForEachIndex();
242 Assert.Throws<ArgumentException>(() => writer.BeginForEachIndex(1));
243
244 stream.Dispose();
245 }
246
247 static void CreateBlockStream1And2Int(out NativeStream stream)
248 {
249 stream = new NativeStream(2, Allocator.Temp);
250
251 var writer = stream.AsWriter();
252 writer.BeginForEachIndex(0);
253 writer.Write(0);
254 writer.EndForEachIndex();
255
256 writer.BeginForEachIndex(1);
257 writer.Write(1);
258 writer.Write(2);
259 writer.EndForEachIndex();
260 }
261
262 [Test]
263 public void NativeStream_IncompleteReadThrows()
264 {
265 NativeStream stream;
266 CreateBlockStream1And2Int(out stream);
267
268 var reader = stream.AsReader();
269
270 reader.BeginForEachIndex(0);
271 reader.Read<byte>();
272 Assert.Throws<ArgumentException>(() => reader.EndForEachIndex());
273
274 reader.BeginForEachIndex(1);
275
276 stream.Dispose();
277 }
278
279 [Test]
280 public void NativeStream_ReadWithoutBeginThrows()
281 {
282 NativeStream stream;
283 CreateBlockStream1And2Int(out stream);
284
285 var reader = stream.AsReader();
286 Assert.Throws<ArgumentException>(() => reader.Read<int>());
287
288 stream.Dispose();
289 }
290
291 [Test]
292 public void NativeStream_TooManyReadsThrows()
293 {
294 NativeStream stream;
295 CreateBlockStream1And2Int(out stream);
296
297 var reader = stream.AsReader();
298
299 reader.BeginForEachIndex(0);
300 reader.Read<byte>();
301 Assert.Throws<ArgumentException>(() => reader.Read<byte>());
302
303 stream.Dispose();
304 }
305
306 [Test]
307 public void NativeStream_OutOfBoundsReadThrows()
308 {
309 NativeStream stream;
310 CreateBlockStream1And2Int(out stream);
311
312 var reader = stream.AsReader();
313
314 reader.BeginForEachIndex(0);
315 Assert.Throws<ArgumentException>(() => reader.Read<long>());
316
317 stream.Dispose();
318 }
319
320 [Test]
321 public void NativeStream_CopyWriterByValueThrows()
322 {
323 var stream = new NativeStream(1, Allocator.Temp);
324 var writer = stream.AsWriter();
325
326 writer.BeginForEachIndex(0);
327
328 Assert.Throws<ArgumentException>(() =>
329 {
330 var writerCopy = writer;
331 writerCopy.Write(5);
332 });
333
334 Assert.Throws<ArgumentException>(() =>
335 {
336 var writerCopy = writer;
337 writerCopy.BeginForEachIndex(1);
338 writerCopy.Write(5);
339 });
340
341 stream.Dispose();
342 }
343
344 [Test]
345 public void NativeStream_WriteSameIndexTwiceThrows()
346 {
347 var stream = new NativeStream(1, Allocator.Temp);
348 var writer = stream.AsWriter();
349
350 writer.BeginForEachIndex(0);
351 writer.Write(1);
352 writer.EndForEachIndex();
353
354 Assert.Throws<ArgumentException>(() =>
355 {
356 writer.BeginForEachIndex(0);
357 writer.Write(2);
358 });
359
360 stream.Dispose();
361 }
362
363 static void WriteNotPassedByRef(NativeStream.Writer notPassedByRef)
364 {
365 notPassedByRef.Write(10);
366 }
367
368 static void WritePassedByRef(ref NativeStream.Writer passedByRef)
369 {
370 passedByRef.Write(10);
371 }
372
373 [Test]
374 public void NativeStream_ThrowsOnIncorrectUsage()
375 {
376 var stream = new NativeStream(1, Allocator.Temp);
377 var writer = stream.AsWriter();
378
379 Assert.Throws<ArgumentException>(() => stream.AsWriter().Write(10));
380
381 writer.BeginForEachIndex(0);
382 Assert.Throws<ArgumentException>(() => WriteNotPassedByRef(writer));
383 Assert.DoesNotThrow(() => WritePassedByRef(ref writer));
384 writer.EndForEachIndex();
385
386 stream.Dispose();
387 }
388
389#endif
390
391 [Test]
392 public void NativeStream_CustomAllocatorTest()
393 {
394 AllocatorManager.Initialize();
395 var allocatorHelper = new AllocatorHelper<CustomAllocatorTests.CountingAllocator>(AllocatorManager.Persistent);
396 ref var allocator = ref allocatorHelper.Allocator;
397 allocator.Initialize();
398
399 using (var container = new NativeStream(1, allocator.Handle))
400 {
401 }
402
403 Assert.IsTrue(allocator.WasUsed);
404 allocator.Dispose();
405 allocatorHelper.Dispose();
406 AllocatorManager.Shutdown();
407 }
408
409 [BurstCompile]
410 struct BurstedCustomAllocatorJob : IJob
411 {
412 [NativeDisableUnsafePtrRestriction]
413 public unsafe CustomAllocatorTests.CountingAllocator* Allocator;
414
415 public void Execute()
416 {
417 unsafe
418 {
419 using (var container = new NativeStream(1, Allocator->Handle))
420 {
421 }
422 }
423 }
424 }
425
426 [Test]
427 public unsafe void NativeStream_BurstedCustomAllocatorTest()
428 {
429 AllocatorManager.Initialize();
430 var allocatorHelper = new AllocatorHelper<CustomAllocatorTests.CountingAllocator>(AllocatorManager.Persistent);
431 ref var allocator = ref allocatorHelper.Allocator;
432 allocator.Initialize();
433
434 var allocatorPtr = (CustomAllocatorTests.CountingAllocator*)UnsafeUtility.AddressOf<CustomAllocatorTests.CountingAllocator>(ref allocator);
435 unsafe
436 {
437 var handle = new BurstedCustomAllocatorJob {Allocator = allocatorPtr}.Schedule();
438 handle.Complete();
439 }
440
441 Assert.IsTrue(allocator.WasUsed);
442 allocator.Dispose();
443 allocatorHelper.Dispose();
444 AllocatorManager.Shutdown();
445 }
446
447 public struct NestedContainer
448 {
449 public NativeList<int> data;
450 }
451
452 [Test]
453 public void NativeStream_Nested()
454 {
455 var inner = new NativeList<int>(CommonRwdAllocator.Handle);
456 NestedContainer nestedStruct = new NestedContainer { data = inner };
457
458 var containerNestedStruct = new NativeStream(100, CommonRwdAllocator.Handle);
459 var containerNested = new NativeStream(100, CommonRwdAllocator.Handle);
460
461 var writer = containerNested.AsWriter();
462 writer.BeginForEachIndex(0);
463 writer.Write(inner);
464 writer.EndForEachIndex();
465 var writerStruct = containerNestedStruct.AsWriter();
466 writerStruct.BeginForEachIndex(0);
467 writerStruct.Write(nestedStruct);
468 writerStruct.EndForEachIndex();
469
470 containerNested.Dispose();
471 containerNestedStruct.Dispose();
472 inner.Dispose();
473 }
474
475 [Test]
476 public unsafe void NativeStream_Continue_Append()
477 {
478 AllocatorManager.Initialize();
479 var allocatorHelper = new AllocatorHelper<CustomAllocatorTests.CountingAllocator>(AllocatorManager.Persistent);
480 ref var allocator = ref allocatorHelper.Allocator;
481 allocator.Initialize();
482
483 for (var i = 0; i < 1024; i++)
484 {
485 var stream = new NativeStream(2, allocator.Handle);
486
487 var writer = stream.AsWriter();
488 writer.BeginForEachIndex(0);
489 writer.Allocate(4000);
490 writer.EndForEachIndex();
491
492 var writer2 = stream.AsWriter();
493 writer2.BeginForEachIndex(1);
494 writer2.Allocate(4000);
495 writer2.Allocate(4000);
496 writer2.EndForEachIndex();
497
498 stream.Dispose();
499
500 Assert.AreEqual(0, allocatorHelper.Allocator.Used);
501 }
502
503 allocator.Dispose();
504 allocatorHelper.Dispose();
505 AllocatorManager.Shutdown();
506 }
507
508 struct NestedContainerJob : IJob
509 {
510 public NativeStream nestedContainer;
511
512 public void Execute()
513 {
514 var writer = nestedContainer.AsWriter();
515 writer.BeginForEachIndex(0);
516 writer.Write(1);
517 writer.EndForEachIndex();
518 }
519 }
520
521 [Test]
522 [TestRequiresCollectionChecks]
523 public void NativeStream_NestedJob_Error()
524 {
525 var inner = new NativeList<int>(CommonRwdAllocator.Handle);
526 var container = new NativeStream(100, CommonRwdAllocator.Handle);
527
528 // This should mark the NativeStream as having nested containers and therefore should not be able to be scheduled
529 var writer = container.AsWriter();
530 writer.BeginForEachIndex(0);
531 writer.Write(inner);
532 writer.EndForEachIndex();
533
534 var nestedJob = new NestedContainerJob
535 {
536 nestedContainer = container
537 };
538
539 JobHandle job = default;
540 Assert.Throws<System.InvalidOperationException>(() => { job = nestedJob.Schedule(); });
541 job.Complete();
542
543 container.Dispose();
544 }
545}