public static unsafe JobHandle ScheduleParallelFor(ref JobScheduleParameters parameters, int arrayLength, int innerloopBatchCount) { UnsafeUtility.AssertHeap(parameters.JobDataPtr.ToPointer()); UnsafeUtility.AssertHeap(parameters.ReflectionData.ToPointer()); ReflectionDataProxy jobReflectionData = UnsafeUtility.AsRef <ReflectionDataProxy>(parameters.ReflectionData.ToPointer()); Assert.IsFalse(jobReflectionData.GenExecuteFunctionPtr.ToPointer() == null); Assert.IsFalse(jobReflectionData.GenCleanupFunctionPtr.ToPointer() == null); void * jobMetaPtr = parameters.JobDataPtr.ToPointer(); JobMetaData jobMetaData = default; jobMetaData.JobRanges.ArrayLength = arrayLength; jobMetaData.JobRanges.IndicesPerPhase = GetDefaultIndicesPerPhase(arrayLength); UnsafeUtility.CopyStructureToPtr(ref jobMetaData, jobMetaPtr); #if UNITY_SINGLETHREADED_JOBS // In the single threaded case, this is synchronous execution. UnsafeUtility.CallFunctionPtr_pi(jobReflectionData.GenExecuteFunctionPtr.ToPointer(), jobMetaPtr, 0); UnsafeUtility.CallFunctionPtr_p(jobReflectionData.GenCleanupFunctionPtr.ToPointer(), jobMetaPtr); // This checks that the generated code was actually called; the last responsibility of // the generated code is to clean up the memory. Unfortunately only works in single threaded mode, Assert.IsTrue(UnsafeUtility.GetLastFreePtr() == jobMetaPtr); return(new JobHandle()); #else return(ScheduleJobParallelFor(jobReflectionData.GenExecuteFunctionPtr, jobReflectionData.GenCleanupFunctionPtr, parameters.JobDataPtr, arrayLength, innerloopBatchCount, parameters.Dependency)); #endif }
static unsafe JobHandle ScheduleParallelForInternal(ref JobScheduleParameters parameters, int arrayLength, void *deferredDataPtr, int innerloopBatchCount) { // Ensure the user has not set the schedule mode to a currently unsupported type Assert.IsTrue(parameters.ScheduleMode != ScheduleMode.Single); // May provide an arrayLength (>=0) OR a deferredDataPtr, but both is senseless. Assert.IsTrue((arrayLength >= 0 && deferredDataPtr == null) || (arrayLength < 0 && deferredDataPtr != null)); UnsafeUtility.AssertHeap(parameters.JobDataPtr); UnsafeUtility.AssertHeap(parameters.ReflectionData); ReflectionDataProxy jobReflectionData = UnsafeUtility.AsRef <ReflectionDataProxy>(parameters.ReflectionData); Assert.IsFalse(jobReflectionData.ExecuteFunctionPtr.ToPointer() == null); Assert.IsFalse(jobReflectionData.CleanupFunctionPtr.ToPointer() == null); #if ENABLE_UNITY_COLLECTIONS_CHECKS && !UNITY_DOTSRUNTIME_IL2CPP Assert.IsTrue((jobReflectionData.UnmanagedSize != -1 && jobReflectionData.MarshalToBurstFunctionPtr != IntPtr.Zero) || (jobReflectionData.UnmanagedSize == -1 && jobReflectionData.MarshalToBurstFunctionPtr == IntPtr.Zero)); #endif JobMetaData *managedJobDataPtr = parameters.JobDataPtr; JobMetaData jobMetaData; UnsafeUtility.CopyPtrToStructure(parameters.JobDataPtr, out jobMetaData); Assert.IsTrue(jobMetaData.jobDataSize > 0); // set by JobScheduleParameters Assert.IsTrue(sizeof(JobRanges) <= JobMetaData.kJobMetaDataIsParallelOffset); jobMetaData.JobRanges.ArrayLength = (arrayLength >= 0) ? arrayLength : 0; jobMetaData.JobRanges.IndicesPerPhase = (arrayLength >= 0) ? GetDefaultIndicesPerPhase(arrayLength) : 1; // TODO indicesPerPhase isn't actually used, except as a flag. // If this is set to -1 by codegen, that indicates an error if we schedule the job as parallel for because // it potentially consists of write operations which are not parallel compatible if (jobMetaData.isParallelFor == -1) { throw new InvalidOperationException("Parallel writing not supported in this job. Parallel scheduling invalid."); } jobMetaData.isParallelFor = 1; jobMetaData.deferredDataPtr = deferredDataPtr; JobHandle jobHandle = default; #if !UNITY_SINGLETHREADED_JOBS bool runSingleThreadSynchronous = parameters.ScheduleMode == ScheduleMode.RunOnMainThread || parameters.ScheduleMode == ScheduleMode.ScheduleOnMainThread; #else bool runSingleThreadSynchronous = true; #endif jobMetaData.JobRanges.runOnMainThread = runSingleThreadSynchronous ? 1 : 0; if (runSingleThreadSynchronous) { bool syncNow = parameters.ScheduleMode == ScheduleMode.Run || parameters.ScheduleMode == ScheduleMode.RunOnMainThread; #if UNITY_SINGLETHREADED_JOBS // Nativejobs needs further support in creating a JobHandle not linked to an actual job in order to support this correctly // in multithreaded builds if (!syncNow) { jobHandle.JobGroup = GetFakeJobGroupId(); #if ENABLE_UNITY_COLLECTIONS_CHECKS DebugDidScheduleJob(ref jobHandle, (JobHandle *)UnsafeUtility.AddressOf(ref parameters.Dependency), 1); #endif } #endif parameters.Dependency.Complete(); UnsafeUtility.SetInJob(1); try { // We assume there are no non-blittable fields in a bursted job (i.e. DisposeSentinel) if // collections checks are not enabled #if ENABLE_UNITY_COLLECTIONS_CHECKS && !UNITY_DOTSRUNTIME_IL2CPP // If the job was bursted, and the job structure contained non-blittable fields, the UnmanagedSize will // be something other than -1 meaning we need to marshal the managed representation before calling the ExecuteFn if (jobReflectionData.UnmanagedSize != -1) { JobMetaData *unmanagedJobData = AllocateJobHeapMemory(jobReflectionData.UnmanagedSize, 1); void *dst = (byte *)unmanagedJobData + sizeof(JobMetaData); void *src = (byte *)managedJobDataPtr + sizeof(JobMetaData); // In the single threaded case, this is synchronous execution. UnsafeUtility.EnterTempScope(); try { UnsafeUtility.CallFunctionPtr_pp(jobReflectionData.MarshalToBurstFunctionPtr.ToPointer(), dst, src); CopyMetaDataToJobData(ref jobMetaData, managedJobDataPtr, unmanagedJobData); UnsafeUtility.CallFunctionPtr_pi(jobReflectionData.ExecuteFunctionPtr.ToPointer(), unmanagedJobData, k_MainThreadWorkerIndex); UnsafeUtility.CallFunctionPtr_p(jobReflectionData.CleanupFunctionPtr.ToPointer(), unmanagedJobData); } finally { UnsafeUtility.ExitTempScope(); } } else #endif { CopyMetaDataToJobData(ref jobMetaData, managedJobDataPtr, null); // In the single threaded case, this is synchronous execution. UnsafeUtility.EnterTempScope(); try { UnsafeUtility.CallFunctionPtr_pi(jobReflectionData.ExecuteFunctionPtr.ToPointer(), managedJobDataPtr, k_MainThreadWorkerIndex); UnsafeUtility.CallFunctionPtr_p(jobReflectionData.CleanupFunctionPtr.ToPointer(), managedJobDataPtr); } finally { UnsafeUtility.ExitTempScope(); } } } finally { UnsafeUtility.SetInJob(0); } return(jobHandle); } #if !UNITY_SINGLETHREADED_JOBS #if ENABLE_UNITY_COLLECTIONS_CHECKS && !UNITY_DOTSRUNTIME_IL2CPP // If the job was bursted, and the job structure contained non-blittable fields, the UnmanagedSize will // be something other than -1 meaning we need to marshal the managed representation before calling the ExecuteFn if (jobReflectionData.UnmanagedSize != -1) { int nWorker = JobWorkerCount > 1 ? JobWorkerCount : 1; JobMetaData *unmanagedJobData = AllocateJobHeapMemory(jobReflectionData.UnmanagedSize, nWorker); for (int i = 0; i < nWorker; i++) { void *dst = (byte *)unmanagedJobData + sizeof(JobMetaData) + i * jobReflectionData.UnmanagedSize; void *src = (byte *)managedJobDataPtr + sizeof(JobMetaData) + i * jobMetaData.jobDataSize; UnsafeUtility.CallFunctionPtr_pp(jobReflectionData.MarshalToBurstFunctionPtr.ToPointer(), dst, src); } // Need to change the jobDataSize so the job will have the correct stride when finding // the correct jobData for a thread. JobMetaData unmanagedJobMetaData = jobMetaData; unmanagedJobMetaData.jobDataSize = jobReflectionData.UnmanagedSize; CopyMetaDataToJobData(ref unmanagedJobMetaData, managedJobDataPtr, unmanagedJobData); jobHandle = ScheduleJobParallelFor(jobReflectionData.ExecuteFunctionPtr, jobReflectionData.CleanupFunctionPtr, unmanagedJobData, arrayLength, innerloopBatchCount, parameters.Dependency); } else #endif { CopyMetaDataToJobData(ref jobMetaData, managedJobDataPtr, null); jobHandle = ScheduleJobParallelFor(jobReflectionData.ExecuteFunctionPtr, jobReflectionData.CleanupFunctionPtr, parameters.JobDataPtr, arrayLength, innerloopBatchCount, parameters.Dependency); } if (parameters.ScheduleMode == ScheduleMode.Run) { jobHandle.Complete(); } #endif return(jobHandle); }