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))] [assembly: RegisterGenericJobType(typeof(Unity.Jobs.Tests.ManagedJobs.MyGenericJobDefer))] [assembly: RegisterGenericJobType(typeof(Unity.Jobs.Tests.ManagedJobs.MyGenericJobDefer))] [assembly: RegisterGenericJobType(typeof(Unity.Jobs.Tests.ManagedJobs.GenericContainerJobDefer, 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 where T : struct { internal T JobData; [NativeDisableContainerSafetyRestriction] internal NativeArray ProducerResourceToClean; } internal struct JobTestProducer where T : struct, IJobTest { internal static readonly SharedStatic s_JobReflectionData = SharedStatic.GetOrCreate>(); [BurstDiscard] internal static void Initialize() { if (s_JobReflectionData.Data == IntPtr.Zero) s_JobReflectionData.Data = JobsUtility.CreateJobReflectionData(typeof(JobTestWrapper), typeof(T), (ExecuteJobFunction)Execute); } public delegate void ExecuteJobFunction(ref JobTestWrapper jobWrapper, IntPtr additionalPtr, IntPtr bufferRangePatchData, ref JobRanges ranges, int jobIndex); public unsafe static void Execute(ref JobTestWrapper jobWrapper, IntPtr additionalPtr, IntPtr bufferRangePatchData, ref JobRanges ranges, int jobIndex) { jobWrapper.JobData.Execute(); } } public static void EarlyJobInit() where T : struct, IJobTest { JobTestProducer.Initialize(); } static IntPtr GetReflectionData() where T : struct, IJobTest { JobTestProducer.Initialize(); var reflectionData = JobTestProducer.s_JobReflectionData.Data; CollectionHelper.CheckReflectionDataCorrect(reflectionData); return reflectionData; } public static unsafe JobHandle ScheduleTest(this T jobData, NativeArray dataForProducer, JobHandle dependsOn = new JobHandle()) where T : struct, IJobTest { JobTestWrapper jobTestWrapper = new JobTestWrapper { JobData = jobData, ProducerResourceToClean = dataForProducer }; var scheduleParams = new JobsUtility.JobScheduleParameters( UnsafeUtility.AddressOf(ref jobTestWrapper), GetReflectionData(), 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 where T : struct { internal T JobData; internal byte Empty; } internal struct JobTestProducer where T : struct, IJobTestInheritWithProducer { internal static readonly SharedStatic jobReflectionData = SharedStatic.GetOrCreate>(); [BurstDiscard] internal static void Initialize() { if (jobReflectionData.Data == IntPtr.Zero) jobReflectionData.Data = JobsUtility.CreateJobReflectionData(typeof(JobTestWrapper), typeof(T), (ExecuteJobFunction)Execute); } public delegate void ExecuteJobFunction(ref JobTestWrapper jobWrapper, IntPtr additionalPtr, IntPtr bufferRangePatchData, ref JobRanges ranges, int jobIndex); public unsafe static void Execute(ref JobTestWrapper jobWrapper, IntPtr additionalPtr, IntPtr bufferRangePatchData, ref JobRanges ranges, int jobIndex) { jobWrapper.JobData.Execute(jobWrapper.Empty != 0); } } public static void EarlyJobInit() where T : struct, IJobTestInheritWithProducer { JobTestProducer.Initialize(); } static IntPtr GetReflectionData() where T : struct, IJobTestInheritWithProducer { JobTestProducer.Initialize(); var reflectionData = JobTestProducer.jobReflectionData.Data; CollectionHelper.CheckReflectionDataCorrect(reflectionData); return reflectionData; } unsafe public static JobHandle Schedule(this T jobData, bool empty, JobHandle dependsOn = new JobHandle()) where T : struct, IJobTestInheritWithProducer { JobTestWrapper jobTestWrapper = new JobTestWrapper { JobData = jobData, Empty = (byte)(empty ? 1 : 0) }; var scheduleParams = new JobsUtility.JobScheduleParameters( UnsafeUtility.AddressOf(ref jobTestWrapper), GetReflectionData(), dependsOn, ScheduleMode.Parallel ); return JobsUtility.Schedule(ref scheduleParams); } } internal struct MyGenericResizeJob : IJob where T : unmanaged { public int m_ListLength; public NativeList m_GenericList; public void Execute() { m_GenericList.Resize(m_ListLength, NativeArrayOptions.UninitializedMemory); } } internal struct MyGenericJobDefer : IJobParallelForDefer where T: unmanaged { public T m_Value; [NativeDisableParallelForRestriction] public NativeList m_GenericList; public void Execute(int index) { m_GenericList[index] = m_Value; } } internal struct GenericContainerResizeJob : IJob where T : unmanaged, INativeList where U : unmanaged { public int m_ListLength; public T m_GenericList; public void Execute() { m_GenericList.Length = m_ListLength; } } internal struct GenericContainerJobDefer : IJobParallelForDefer where T : unmanaged, INativeList 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 container, U value) where T : unmanaged, INativeList where U : unmanaged { var j0 = new GenericContainerResizeJob(); var length = 5; j0.m_ListLength = length; j0.m_GenericList = container; var handle0 = j0.Schedule(); var j1 = new GenericContainerJobDefer(); j1.m_Value = value; j1.m_GenericList = j0.m_GenericList; INativeList iList = j0.m_GenericList; j1.Schedule((NativeList)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(1, RwdAllocator.ToAllocator); ScheduleGenericContainerJob(list, 5); } public void GenericScheduleJobPair(T value) where T : unmanaged { var j0 = new MyGenericResizeJob(); var length = 5; j0.m_ListLength = length; j0.m_GenericList = new NativeList(1, RwdAllocator.ToAllocator); var handle0 = j0.Schedule(); var j1 = new MyGenericJobDefer(); 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(10.0); } [Test] public void ScheduleGenericJobPairInt() { GenericScheduleJobPair(20); } #if ENABLE_UNITY_COLLECTIONS_CHECKS [Test] public void SchedulingGenericJobUnsafelyThrows() { var j0 = new MyGenericResizeJob(); var length = 5; j0.m_ListLength = length; j0.m_GenericList = new NativeList(1, RwdAllocator.ToAllocator); var handle0 = j0.Schedule(); var j1 = new MyGenericJobDefer(); j1.m_Value = 6; j1.m_GenericList = j0.m_GenericList; Assert.Throws(()=>j1.Schedule(j0.m_GenericList, 1).Complete()); handle0.Complete(); } #endif struct DontReferenceThisTypeOutsideOfThisTest { public int v; } [Test] [TestRequiresCollectionChecks] public void SchedulingGenericJobFromGenericContextUnsafelyThrows() { var list = new NativeList(1, RwdAllocator.ToAllocator); ScheduleGenericJobUnsafely(list, new DontReferenceThisTypeOutsideOfThisTest { v = 5 }); } void ScheduleGenericJobUnsafely(T container, U value) where T : unmanaged, INativeList where U : unmanaged { var j0 = new GenericContainerResizeJob(); var length = 5; j0.m_ListLength = length; j0.m_GenericList = container; var handle0 = j0.Schedule(); var j1 = new GenericContainerJobDefer(); j1.m_Value = value; j1.m_GenericList = j0.m_GenericList; INativeList iList = j0.m_GenericList; Assert.Throws(()=>j1.Schedule((NativeList)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(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 input; } public struct TestNestedDeallocate : IJob { public NestedDeallocateStruct nested; public NativeArray 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(10, RwdAllocator.ToAllocator); var outNativeArray = CollectionHelper.CreateNativeArray(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(() => { AtomicSafetyHandle.CheckExistsAndThrow(NativeArrayUnsafeUtility.GetAtomicSafetyHandle(tempNativeArray)); }); Assert.Throws(() => { AtomicSafetyHandle.CheckExistsAndThrow(NativeArrayUnsafeUtility.GetAtomicSafetyHandle(job.nested.input)); }); #endif } public struct TestJobProducerJob : IJobTest { public NativeArray jobStructData; public void Execute() { } } [Test] public void TestJobProducerCleansUp() { var tempNativeArray = CollectionHelper.CreateNativeArray(10, RwdAllocator.ToAllocator); var tempNativeArray2 = CollectionHelper.CreateNativeArray(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(() => { AtomicSafetyHandle.CheckExistsAndThrow(NativeArrayUnsafeUtility.GetAtomicSafetyHandle(tempNativeArray)); }); Assert.Throws(() => { AtomicSafetyHandle.CheckExistsAndThrow(NativeArrayUnsafeUtility.GetAtomicSafetyHandle(job.jobStructData)); }); // Check job producer Assert.Throws(() => { AtomicSafetyHandle.CheckExistsAndThrow(NativeArrayUnsafeUtility.GetAtomicSafetyHandle(tempNativeArray2)); }); #endif } public struct CopyJob : IJob { public NativeList List1; public NativeList List2; public void Execute() { List1 = List2; } } [Test] public unsafe void TestContainerCopy_EnsureSafetyHandlesCopyAndDisposeProperly() { var list1 = new NativeList(10, RwdAllocator.ToAllocator); var list2 = new NativeList(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 TotalLengths; [ReadOnly] public NativeList 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(1, RwdAllocator.ToAllocator)) { var dummyList = new NativeList(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(1, RwdAllocator.ToAllocator); lengthArray[0] = 1; Assert.DoesNotThrow(() => job.ScheduleByRef((int*)lengthArray.GetUnsafePtr(), 1).Complete()); } } } [BurstCompile(CompileSynchronously = true)] public struct InheritJob : IJobTestInherit { public NativeList List1; public NativeList List2; public void Execute() { List1[0] = List2[0]; } } [Test] public void InheritInterfaceJobWorks() { var l1 = new NativeList(4, RwdAllocator.ToAllocator); l1.Add(3); var l2 = new NativeList(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 List1; public NativeList 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(4, RwdAllocator.ToAllocator); l1.Add(3); var l2 = new NativeList(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(); } } }