UnityGame/Library/PackageCache/com.unity.collections/Unity.Collections.Tests/Jobs/JobTests.cs

656 lines
21 KiB
C#
Raw Normal View History

2024-10-27 10:53:47 +03:00
using System;
using NUnit.Framework;
using UnityEngine.Scripting;
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
using Unity.Jobs;
using Unity.Jobs.LowLevel.Unsafe;
using Unity.Burst;
using System.Diagnostics;
using Unity.Collections.Tests;
[assembly: RegisterGenericJobType(typeof(Unity.Jobs.Tests.ManagedJobs.MyGenericJobDefer<int>))]
[assembly: RegisterGenericJobType(typeof(Unity.Jobs.Tests.ManagedJobs.MyGenericJobDefer<double>))]
[assembly: RegisterGenericJobType(typeof(Unity.Jobs.Tests.ManagedJobs.MyGenericJobDefer<float>))]
[assembly: RegisterGenericJobType(typeof(Unity.Jobs.Tests.ManagedJobs.GenericContainerJobDefer<NativeList<int>, int>))]
namespace Unity.Jobs.Tests.ManagedJobs
{
internal enum JobRunType
{
Schedule,
ScheduleByRef,
Run,
RunByRef,
}
[JobProducerType(typeof(IJobTestExtensions.JobTestProducer<>))]
internal interface IJobTest
{
void Execute();
}
internal interface IJobTestInherit : IJob
{
}
internal static class IJobTestExtensions
{
internal struct JobTestWrapper<T> where T : struct
{
internal T JobData;
[NativeDisableContainerSafetyRestriction]
internal NativeArray<byte> ProducerResourceToClean;
}
internal struct JobTestProducer<T> where T : struct, IJobTest
{
internal static readonly SharedStatic<IntPtr> s_JobReflectionData = SharedStatic<IntPtr>.GetOrCreate<JobTestProducer<T>>();
[BurstDiscard]
internal static void Initialize()
{
if (s_JobReflectionData.Data == IntPtr.Zero)
s_JobReflectionData.Data = JobsUtility.CreateJobReflectionData(typeof(JobTestWrapper<T>), typeof(T), (ExecuteJobFunction)Execute);
}
public delegate void ExecuteJobFunction(ref JobTestWrapper<T> jobWrapper, IntPtr additionalPtr, IntPtr bufferRangePatchData, ref JobRanges ranges, int jobIndex);
public unsafe static void Execute(ref JobTestWrapper<T> jobWrapper, IntPtr additionalPtr, IntPtr bufferRangePatchData, ref JobRanges ranges, int jobIndex)
{
jobWrapper.JobData.Execute();
}
}
public static void EarlyJobInit<T>()
where T : struct, IJobTest
{
JobTestProducer<T>.Initialize();
}
static IntPtr GetReflectionData<T>()
where T : struct, IJobTest
{
JobTestProducer<T>.Initialize();
var reflectionData = JobTestProducer<T>.s_JobReflectionData.Data;
CollectionHelper.CheckReflectionDataCorrect<T>(reflectionData);
return reflectionData;
}
public static unsafe JobHandle ScheduleTest<T>(this T jobData, NativeArray<byte> dataForProducer, JobHandle dependsOn = new JobHandle()) where T : struct, IJobTest
{
JobTestWrapper<T> jobTestWrapper = new JobTestWrapper<T>
{
JobData = jobData,
ProducerResourceToClean = dataForProducer
};
var scheduleParams = new JobsUtility.JobScheduleParameters(
UnsafeUtility.AddressOf(ref jobTestWrapper),
GetReflectionData<T>(),
dependsOn,
ScheduleMode.Parallel
);
return JobsUtility.Schedule(ref scheduleParams);
}
}
[JobProducerType(typeof(IJobTestInheritProducerExtensions.JobTestProducer<>))]
internal interface IJobTestInheritWithProducer : IJob
{
void Execute(bool empty);
}
internal static class IJobTestInheritProducerExtensions
{
internal struct JobTestWrapper<T> where T : struct
{
internal T JobData;
internal byte Empty;
}
internal struct JobTestProducer<T> where T : struct, IJobTestInheritWithProducer
{
internal static readonly SharedStatic<IntPtr> jobReflectionData = SharedStatic<IntPtr>.GetOrCreate<JobTestProducer<T>>();
[BurstDiscard]
internal static void Initialize()
{
if (jobReflectionData.Data == IntPtr.Zero)
jobReflectionData.Data = JobsUtility.CreateJobReflectionData(typeof(JobTestWrapper<T>), typeof(T), (ExecuteJobFunction)Execute);
}
public delegate void ExecuteJobFunction(ref JobTestWrapper<T> jobWrapper, IntPtr additionalPtr, IntPtr bufferRangePatchData, ref JobRanges ranges, int jobIndex);
public unsafe static void Execute(ref JobTestWrapper<T> jobWrapper, IntPtr additionalPtr, IntPtr bufferRangePatchData, ref JobRanges ranges, int jobIndex)
{
jobWrapper.JobData.Execute(jobWrapper.Empty != 0);
}
}
public static void EarlyJobInit<T>()
where T : struct, IJobTestInheritWithProducer
{
JobTestProducer<T>.Initialize();
}
static IntPtr GetReflectionData<T>()
where T : struct, IJobTestInheritWithProducer
{
JobTestProducer<T>.Initialize();
var reflectionData = JobTestProducer<T>.jobReflectionData.Data;
CollectionHelper.CheckReflectionDataCorrect<T>(reflectionData);
return reflectionData;
}
unsafe public static JobHandle Schedule<T>(this T jobData, bool empty, JobHandle dependsOn = new JobHandle()) where T : struct, IJobTestInheritWithProducer
{
JobTestWrapper<T> jobTestWrapper = new JobTestWrapper<T>
{
JobData = jobData,
Empty = (byte)(empty ? 1 : 0)
};
var scheduleParams = new JobsUtility.JobScheduleParameters(
UnsafeUtility.AddressOf(ref jobTestWrapper),
GetReflectionData<T>(),
dependsOn,
ScheduleMode.Parallel
);
return JobsUtility.Schedule(ref scheduleParams);
}
}
internal struct MyGenericResizeJob<T> : IJob where T : unmanaged
{
public int m_ListLength;
public NativeList<T> m_GenericList;
public void Execute()
{
m_GenericList.Resize(m_ListLength, NativeArrayOptions.UninitializedMemory);
}
}
internal struct MyGenericJobDefer<T> : IJobParallelForDefer where T: unmanaged
{
public T m_Value;
[NativeDisableParallelForRestriction]
public NativeList<T> m_GenericList;
public void Execute(int index)
{
m_GenericList[index] = m_Value;
}
}
internal struct GenericContainerResizeJob<T, U> : IJob
where T : unmanaged, INativeList<U>
where U : unmanaged
{
public int m_ListLength;
public T m_GenericList;
public void Execute()
{
m_GenericList.Length = m_ListLength;
}
}
internal struct GenericContainerJobDefer<T, U> : IJobParallelForDefer
where T : unmanaged, INativeList<U>
where U : unmanaged
{
public U m_Value;
[NativeDisableParallelForRestriction]
public T m_GenericList;
public void Execute(int index)
{
m_GenericList[index] = m_Value;
}
}
internal class JobTests : JobTestsFixture
{
public void ScheduleGenericContainerJob<T, U>(T container, U value)
where T : unmanaged, INativeList<U>
where U : unmanaged
{
var j0 = new GenericContainerResizeJob<T, U>();
var length = 5;
j0.m_ListLength = length;
j0.m_GenericList = container;
var handle0 = j0.Schedule();
var j1 = new GenericContainerJobDefer<T, U>();
j1.m_Value = value;
j1.m_GenericList = j0.m_GenericList;
INativeList<U> iList = j0.m_GenericList;
j1.Schedule((NativeList<U>)iList, 1, handle0).Complete();
Assert.AreEqual(length, j1.m_GenericList.Length);
for (int i = 0; i != j1.m_GenericList.Length; i++)
Assert.AreEqual(value, j1.m_GenericList[i]);
}
[Test]
public void ValidateContainerSafetyInGenericJob_ContainerIsGenericParameter()
{
var list = new NativeList<int>(1, RwdAllocator.ToAllocator);
ScheduleGenericContainerJob(list, 5);
}
public void GenericScheduleJobPair<T>(T value) where T : unmanaged
{
var j0 = new MyGenericResizeJob<T>();
var length = 5;
j0.m_ListLength = length;
j0.m_GenericList = new NativeList<T>(1, RwdAllocator.ToAllocator);
var handle0 = j0.Schedule();
var j1 = new MyGenericJobDefer<T>();
j1.m_Value = value;
j1.m_GenericList = j0.m_GenericList;
j1.Schedule(j0.m_GenericList, 1, handle0).Complete();
Assert.AreEqual(length, j1.m_GenericList.Length);
for (int i = 0; i != j1.m_GenericList.Length; i++)
Assert.AreEqual(value, j1.m_GenericList[i]);
}
[Test]
public void ScheduleGenericJobPairFloat()
{
GenericScheduleJobPair(10f);
}
[Test]
public void ScheduleGenericJobPairDouble()
{
GenericScheduleJobPair<double>(10.0);
}
[Test]
public void ScheduleGenericJobPairInt()
{
GenericScheduleJobPair(20);
}
#if ENABLE_UNITY_COLLECTIONS_CHECKS
[Test]
public void SchedulingGenericJobUnsafelyThrows()
{
var j0 = new MyGenericResizeJob<int>();
var length = 5;
j0.m_ListLength = length;
j0.m_GenericList = new NativeList<int>(1, RwdAllocator.ToAllocator);
var handle0 = j0.Schedule();
var j1 = new MyGenericJobDefer<int>();
j1.m_Value = 6;
j1.m_GenericList = j0.m_GenericList;
Assert.Throws<InvalidOperationException>(()=>j1.Schedule(j0.m_GenericList, 1).Complete());
handle0.Complete();
}
#endif
struct DontReferenceThisTypeOutsideOfThisTest { public int v; }
[Test]
[TestRequiresCollectionChecks]
public void SchedulingGenericJobFromGenericContextUnsafelyThrows()
{
var list = new NativeList<DontReferenceThisTypeOutsideOfThisTest>(1, RwdAllocator.ToAllocator);
ScheduleGenericJobUnsafely(list, new DontReferenceThisTypeOutsideOfThisTest { v = 5 });
}
void ScheduleGenericJobUnsafely<T, U>(T container, U value)
where T : unmanaged, INativeList<U>
where U : unmanaged
{
var j0 = new GenericContainerResizeJob<T, U>();
var length = 5;
j0.m_ListLength = length;
j0.m_GenericList = container;
var handle0 = j0.Schedule();
var j1 = new GenericContainerJobDefer<T, U>();
j1.m_Value = value;
j1.m_GenericList = j0.m_GenericList;
INativeList<U> iList = j0.m_GenericList;
Assert.Throws<InvalidOperationException>(()=>j1.Schedule((NativeList<U>)iList, 1).Complete());
handle0.Complete(); // complete this so we can dispose the nativelist
}
/*
* these two tests used to test that a job that inherited from both IJob and IJobParallelFor would work as expected
* but that's probably crazy.
*/
/*[Test]
public void Scheduling()
{
var job = data.Schedule();
job.Complete();
ExpectOutputSumOfInput0And1();
}*/
/*[Test]
public void Scheduling_With_Dependencies()
{
data.input0 = input0;
data.input1 = input1;
data.output = output2;
var job1 = data.Schedule();
// Schedule job2 with dependency against the first job
data.input0 = output2;
data.input1 = input2;
data.output = output;
var job2 = data.Schedule(job1);
// Wait for completion
job2.Complete();
ExpectOutputSumOfInput0And1And2();
}*/
[Test]
public void ForEach_Scheduling_With_Dependencies()
{
data.input0 = input0;
data.input1 = input1;
data.output = output2;
var job1 = data.Schedule(output.Length, 1);
// Schedule job2 with dependency against the first job
data.input0 = output2;
data.input1 = input2;
data.output = output;
var job2 = data.Schedule(output.Length, 1, job1);
// Wait for completion
job2.Complete();
ExpectOutputSumOfInput0And1And2();
}
struct EmptyComputeParallelForJob : IJobParallelFor
{
public void Execute(int i)
{
}
}
[Test]
public void ForEach_Scheduling_With_Zero_Size()
{
var test = new EmptyComputeParallelForJob();
var job = test.Schedule(0, 1);
job.Complete();
}
[Test]
public void Deallocate_Temp_NativeArray_From_Job()
{
TestDeallocateNativeArrayFromJob(RwdAllocator.ToAllocator);
}
[Test]
public void Deallocate_Persistent_NativeArray_From_Job()
{
TestDeallocateNativeArrayFromJob(Allocator.Persistent);
}
private void TestDeallocateNativeArrayFromJob(Allocator label)
{
var tempNativeArray = CollectionHelper.CreateNativeArray<int>(expectedInput0, label);
var copyAndDestroyJob = new CopyAndDestroyNativeArrayParallelForJob
{
input = tempNativeArray,
output = output
};
// NativeArray can safely be accessed before scheduling
Assert.AreEqual(10, tempNativeArray.Length);
tempNativeArray[0] = tempNativeArray[0];
var job = copyAndDestroyJob.Schedule(copyAndDestroyJob.input.Length, 1);
job.Complete();
// Need to dispose because the allocator may be Allocator.Persistent.
tempNativeArray.Dispose();
Assert.AreEqual(expectedInput0, copyAndDestroyJob.output.ToArray());
}
#if ENABLE_UNITY_COLLECTIONS_CHECKS
public struct NestedDeallocateStruct
{
public NativeArray<int> input;
}
public struct TestNestedDeallocate : IJob
{
public NestedDeallocateStruct nested;
public NativeArray<int> output;
public void Execute()
{
for (int i = 0; i < nested.input.Length; ++i)
output[i] = nested.input[i];
}
}
[Test]
public void TestNestedDeallocateOnJobCompletion()
{
var tempNativeArray = CollectionHelper.CreateNativeArray<int>(10, RwdAllocator.ToAllocator);
var outNativeArray = CollectionHelper.CreateNativeArray<int>(10, RwdAllocator.ToAllocator);
for (int i = 0; i < 10; i++)
tempNativeArray[i] = i;
var job = new TestNestedDeallocate
{
nested = new NestedDeallocateStruct() { input = tempNativeArray },
output = outNativeArray
};
var handle = job.Schedule();
handle.Complete();
RwdAllocator.Rewind();
#if ENABLE_UNITY_COLLECTIONS_CHECKS
// Ensure released safety handle indicating invalid buffer
Assert.Throws<ObjectDisposedException>(() => { AtomicSafetyHandle.CheckExistsAndThrow(NativeArrayUnsafeUtility.GetAtomicSafetyHandle(tempNativeArray)); });
Assert.Throws<ObjectDisposedException>(() => { AtomicSafetyHandle.CheckExistsAndThrow(NativeArrayUnsafeUtility.GetAtomicSafetyHandle(job.nested.input)); });
#endif
}
public struct TestJobProducerJob : IJobTest
{
public NativeArray<int> jobStructData;
public void Execute()
{
}
}
[Test]
public void TestJobProducerCleansUp()
{
var tempNativeArray = CollectionHelper.CreateNativeArray<int>(10, RwdAllocator.ToAllocator);
var tempNativeArray2 = CollectionHelper.CreateNativeArray<byte>(16, RwdAllocator.ToAllocator);
var job = new TestJobProducerJob
{
jobStructData = tempNativeArray,
};
var handle = job.ScheduleTest(tempNativeArray2);
handle.Complete();
RwdAllocator.Rewind();
#if ENABLE_UNITY_COLLECTIONS_CHECKS
// Check job data
Assert.Throws<ObjectDisposedException>(() => { AtomicSafetyHandle.CheckExistsAndThrow(NativeArrayUnsafeUtility.GetAtomicSafetyHandle(tempNativeArray)); });
Assert.Throws<ObjectDisposedException>(() => { AtomicSafetyHandle.CheckExistsAndThrow(NativeArrayUnsafeUtility.GetAtomicSafetyHandle(job.jobStructData)); });
// Check job producer
Assert.Throws<ObjectDisposedException>(() => { AtomicSafetyHandle.CheckExistsAndThrow(NativeArrayUnsafeUtility.GetAtomicSafetyHandle(tempNativeArray2)); });
#endif
}
public struct CopyJob : IJob
{
public NativeList<int> List1;
public NativeList<int> List2;
public void Execute()
{
List1 = List2;
}
}
[Test]
public unsafe void TestContainerCopy_EnsureSafetyHandlesCopyAndDisposeProperly()
{
var list1 = new NativeList<int>(10, RwdAllocator.ToAllocator);
var list2 = new NativeList<int>(10, RwdAllocator.ToAllocator);
list1.Add(1);
list2.Add(2);
var job = new CopyJob
{
List1 = list1,
List2 = list2
};
job.Schedule().Complete();
list1.Dispose();
list2.Dispose();
}
#endif
struct LargeJobParallelForDefer : IJobParallelForDefer
{
public FixedString4096Bytes StrA;
public FixedString4096Bytes StrB;
public FixedString4096Bytes StrC;
public FixedString4096Bytes StrD;
[NativeDisableParallelForRestriction]
public NativeArray<int> TotalLengths;
[ReadOnly]
public NativeList<float> Unused; // Schedule() from NativeList.Length requires that the list be passed into the job
public void Execute(int index)
{
TotalLengths[0] = StrA.Length + StrB.Length + StrC.Length + StrD.Length;
}
}
public enum IterationCountMode
{
List, Pointer
}
[Test]
public unsafe void IJobParallelForDefer_LargeJobStruct_ScheduleRefWorks(
[Values(IterationCountMode.List, IterationCountMode.Pointer)] IterationCountMode countMode)
{
using(var lengths = CollectionHelper.CreateNativeArray<int>(1, RwdAllocator.ToAllocator))
{
var dummyList = new NativeList<float>(RwdAllocator.ToAllocator);
dummyList.Add(5.0f);
var job = new LargeJobParallelForDefer
{
StrA = "A",
StrB = "BB",
StrC = "CCC",
StrD = "DDDD",
TotalLengths = lengths,
Unused = dummyList,
};
if (countMode == IterationCountMode.List)
{
Assert.DoesNotThrow(() => job.ScheduleByRef(dummyList, 1).Complete());
}
else if (countMode == IterationCountMode.Pointer)
{
var lengthArray = CollectionHelper.CreateNativeArray<int>(1, RwdAllocator.ToAllocator);
lengthArray[0] = 1;
Assert.DoesNotThrow(() => job.ScheduleByRef((int*)lengthArray.GetUnsafePtr(), 1).Complete());
}
}
}
[BurstCompile(CompileSynchronously = true)]
public struct InheritJob : IJobTestInherit
{
public NativeList<int> List1;
public NativeList<int> List2;
public void Execute()
{
List1[0] = List2[0];
}
}
[Test]
public void InheritInterfaceJobWorks()
{
var l1 = new NativeList<int>(4, RwdAllocator.ToAllocator);
l1.Add(3);
var l2 = new NativeList<int>(4, RwdAllocator.ToAllocator);
l2.Add(17);
var job = new InheritJob { List1 = l1, List2 = l2 };
job.Schedule().Complete();
Assert.IsTrue(l1[0] == 17);
l2.Dispose();
l1.Dispose();
}
[BurstCompile(CompileSynchronously = true)]
public struct InheritWithProducerJob : IJobTestInheritWithProducer
{
public NativeList<int> List1;
public NativeList<int> List2;
public void Execute()
{
List2[0] = List1[0];
}
public void Execute(bool empty)
{
List1[0] = List2[0];
}
}
[Test]
public void InheritInterfaceWithProducerJobWorks()
{
var l1 = new NativeList<int>(4, RwdAllocator.ToAllocator);
l1.Add(3);
var l2 = new NativeList<int>(4, RwdAllocator.ToAllocator);
l2.Add(17);
var job = new InheritWithProducerJob { List1 = l1, List2 = l2 };
job.Schedule(false).Complete();
Assert.IsTrue(l1[0] == 17);
l1[0] = 3;
job.Schedule().Complete();
Assert.IsTrue(l2[0] == 3);
l2.Dispose();
l1.Dispose();
}
}
}