您最多选择25个主题
主题必须以中文或者字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
519 行
16 KiB
519 行
16 KiB
using System;
|
|
using NUnit.Framework;
|
|
using Unity.Collections;
|
|
using Unity.Collections.LowLevel.Unsafe;
|
|
using Unity.Jobs;
|
|
using Unity.Jobs.LowLevel.Unsafe;
|
|
|
|
namespace Unity.Jobs.Tests.ManagedJobs
|
|
{
|
|
#if UNITY_DOTSRUNTIME
|
|
public class DotsRuntimeFixmeAttribute : IgnoreAttribute
|
|
{
|
|
public DotsRuntimeFixmeAttribute(string msg = null) : base(msg == null ? "Test should work in DOTS Runtime but currently doesn't. Ignoring until fixed..." : msg)
|
|
{
|
|
}
|
|
}
|
|
#else
|
|
public class DotsRuntimeFixmeAttribute : Attribute
|
|
{
|
|
public DotsRuntimeFixmeAttribute(string msg = null)
|
|
{
|
|
}
|
|
}
|
|
#endif
|
|
|
|
[JobProducerType(typeof(IJobTestExtensions.JobTestProducer<>))]
|
|
public interface IJobTest
|
|
{
|
|
void Execute();
|
|
}
|
|
|
|
public static class IJobTestExtensions
|
|
{
|
|
internal struct JobTestWrapper<T> where T : struct
|
|
{
|
|
internal T JobData;
|
|
|
|
[NativeDisableContainerSafetyRestriction]
|
|
[DeallocateOnJobCompletion]
|
|
internal NativeArray<byte> ProducerResourceToClean;
|
|
}
|
|
|
|
internal struct JobTestProducer<T> where T : struct, IJobTest
|
|
{
|
|
static IntPtr s_JobReflectionData;
|
|
|
|
public static IntPtr Initialize()
|
|
{
|
|
if (s_JobReflectionData == IntPtr.Zero)
|
|
{
|
|
#if UNITY_2020_2_OR_NEWER
|
|
s_JobReflectionData = JobsUtility.CreateJobReflectionData(typeof(JobTestWrapper<T>), typeof(T), (ExecuteJobFunction)Execute);
|
|
#else
|
|
s_JobReflectionData = JobsUtility.CreateJobReflectionData(typeof(JobTestWrapper<T>), typeof(T),
|
|
JobType.Single, (ExecuteJobFunction)Execute);
|
|
#endif
|
|
}
|
|
|
|
return s_JobReflectionData;
|
|
}
|
|
|
|
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 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),
|
|
JobTestProducer<T>.Initialize(),
|
|
dependsOn,
|
|
#if UNITY_2020_2_OR_NEWER
|
|
ScheduleMode.Parallel
|
|
#else
|
|
ScheduleMode.Batched
|
|
#endif
|
|
);
|
|
|
|
return JobsUtility.Schedule(ref scheduleParams);
|
|
}
|
|
}
|
|
|
|
public 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);
|
|
}
|
|
}
|
|
|
|
public 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;
|
|
}
|
|
}
|
|
|
|
public struct GenericContainerResizeJob<T, U> : IJob
|
|
where T : struct, INativeList<U>
|
|
where U : struct
|
|
{
|
|
public int m_ListLength;
|
|
public T m_GenericList;
|
|
public void Execute()
|
|
{
|
|
m_GenericList.Length = m_ListLength;
|
|
}
|
|
}
|
|
|
|
public struct GenericContainerJobDefer<T, U> : IJobParallelForDefer
|
|
where T : struct, INativeList<U>
|
|
where U : struct
|
|
{
|
|
public U m_Value;
|
|
[NativeDisableParallelForRestriction]
|
|
public T m_GenericList;
|
|
|
|
public void Execute(int index)
|
|
{
|
|
m_GenericList[index] = m_Value;
|
|
}
|
|
}
|
|
|
|
public class JobTests : JobTestsFixture
|
|
{
|
|
public void ScheduleGenericContainerJob<T, U>(T container, U value)
|
|
where T : struct, 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, Allocator.TempJob);
|
|
ScheduleGenericContainerJob(list, 5);
|
|
list.Dispose();
|
|
}
|
|
|
|
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, Allocator.TempJob);
|
|
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]);
|
|
j0.m_GenericList.Dispose();
|
|
}
|
|
|
|
[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, Allocator.TempJob);
|
|
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();
|
|
j0.m_GenericList.Dispose();
|
|
}
|
|
#endif
|
|
|
|
[Test, DotsRuntimeFixme("From a pure generic context, DOTS Runtime cannot determine what closed generic jobs are scheduled. See DOTSR-2347")]
|
|
public void SchedulingGenericJobFromGenericContextUnsafelyThrows()
|
|
{
|
|
var list = new NativeList<int>(1, Allocator.TempJob);
|
|
ScheduleGenericJobUnsafely(list, 5);
|
|
list.Dispose();
|
|
}
|
|
|
|
void ScheduleGenericJobUnsafely<T, U>(T container, U value)
|
|
where T : struct, 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());
|
|
// Note we now pass the correct dependency to complete the job otherwise we won't be able to dispose the list
|
|
// which will cause other tests to fail when they detect leaks. We can't just throw and then dispose since the
|
|
// safety system will see that the list was scheduled and should first have the job completed (however we
|
|
// are intentionally setting up a job that cannot complete)
|
|
j1.Schedule((NativeList<U>)iList, 1, handle0).Complete();
|
|
}
|
|
|
|
/*
|
|
* 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(Allocator.TempJob);
|
|
}
|
|
|
|
[Test]
|
|
public void Deallocate_Persistent_NativeArray_From_Job()
|
|
{
|
|
TestDeallocateNativeArrayFromJob(Allocator.Persistent);
|
|
}
|
|
|
|
private void TestDeallocateNativeArrayFromJob(Allocator label)
|
|
{
|
|
var tempNativeArray = new NativeArray<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();
|
|
|
|
Assert.AreEqual(expectedInput0, copyAndDestroyJob.output.ToArray());
|
|
}
|
|
|
|
#if ENABLE_UNITY_COLLECTIONS_CHECKS
|
|
public struct NestedDeallocateStruct
|
|
{
|
|
// This should deallocate even though it's a nested field
|
|
[DeallocateOnJobCompletion]
|
|
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 = new NativeArray<int>(10, Allocator.TempJob);
|
|
var outNativeArray = new NativeArray<int>(10, Allocator.TempJob);
|
|
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();
|
|
|
|
outNativeArray.Dispose();
|
|
|
|
// Ensure released safety handle indicating invalid buffer
|
|
Assert.Throws<InvalidOperationException>(() => { AtomicSafetyHandle.CheckExistsAndThrow(NativeArrayUnsafeUtility.GetAtomicSafetyHandle(tempNativeArray)); });
|
|
Assert.Throws<InvalidOperationException>(() => { AtomicSafetyHandle.CheckExistsAndThrow(NativeArrayUnsafeUtility.GetAtomicSafetyHandle(job.nested.input)); });
|
|
}
|
|
|
|
public struct TestJobProducerJob : IJobTest
|
|
{
|
|
[DeallocateOnJobCompletion]
|
|
public NativeArray<int> jobStructData;
|
|
|
|
public void Execute()
|
|
{
|
|
}
|
|
}
|
|
|
|
[Test]
|
|
public void TestJobProducerCleansUp()
|
|
{
|
|
var tempNativeArray = new NativeArray<int>(10, Allocator.TempJob);
|
|
var tempNativeArray2 = new NativeArray<byte>(16, Allocator.TempJob);
|
|
|
|
var job = new TestJobProducerJob
|
|
{
|
|
jobStructData = tempNativeArray,
|
|
};
|
|
|
|
var handle = job.ScheduleTest(tempNativeArray2);
|
|
handle.Complete();
|
|
|
|
// Check job data
|
|
Assert.Throws<InvalidOperationException>(() => { AtomicSafetyHandle.CheckExistsAndThrow(NativeArrayUnsafeUtility.GetAtomicSafetyHandle(tempNativeArray)); });
|
|
Assert.Throws<InvalidOperationException>(() => { AtomicSafetyHandle.CheckExistsAndThrow(NativeArrayUnsafeUtility.GetAtomicSafetyHandle(job.jobStructData)); });
|
|
// Check job producer
|
|
Assert.Throws<InvalidOperationException>(() => { AtomicSafetyHandle.CheckExistsAndThrow(NativeArrayUnsafeUtility.GetAtomicSafetyHandle(tempNativeArray2)); });
|
|
}
|
|
|
|
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, Allocator.TempJob);
|
|
var list2 = new NativeList<int>(10, Allocator.TempJob);
|
|
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 FixedString4096 StrA;
|
|
public FixedString4096 StrB;
|
|
public FixedString4096 StrC;
|
|
public FixedString4096 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 = new NativeArray<int>(1, Allocator.TempJob))
|
|
{
|
|
var dummyList = new NativeList<float>(Allocator.TempJob);
|
|
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 = new NativeArray<int>(1, Allocator.TempJob);
|
|
lengthArray[0] = 1;
|
|
Assert.DoesNotThrow(() => job.ScheduleByRef((int*)lengthArray.GetUnsafePtr(), 1).Complete());
|
|
lengthArray.Dispose();
|
|
}
|
|
dummyList.Dispose();
|
|
}
|
|
}
|
|
}
|
|
}
|