public unsafe JobScheduleParameters(void *jobData, IntPtr reflectionData, JobHandle jobDependency, ScheduleMode scheduleMode, int jobDataSize = 0, int schedule = 3) { // Default is 0; code-gen should set to a correct size. if (jobDataSize == 0) { throw new InvalidOperationException("JobScheduleParameters (size) should be set by code gen."); } // Default is 1; however, the function created by code gen will always return 2. if (schedule != 2) { throw new InvalidOperationException( "JobScheduleParamaters (rc of ScheduleJob_Gen) should be set by code gen."); } Assert.IsTrue(sizeof(JobMetaData) % 16 == 0); int headerSize = sizeof(JobMetaData); int size = headerSize + jobDataSize; void *mem = UnsafeUtility.Malloc(size, 16, Allocator.TempJob); UnsafeUtility.MemClear(mem, size); UnsafeUtility.MemCpy(((byte *)mem + headerSize), jobData, jobDataSize); UnsafeUtility.AssertHeap(mem); Dependency = jobDependency; JobDataPtr = (IntPtr)mem; ReflectionData = reflectionData; ScheduleMode = (int)scheduleMode; }
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 void Failure() { // Take assertion at "real value": an Assert should *never* fire. // If in debug w/ GUARD_HEAP (the default) AssertHeap will crash the program (with a call stack in the debugger.) // As a fallback, throw an exception. Note that so much code catches exceptions this can hide Asserts. UnsafeUtility.AssertHeap(null); throw new Exception("Exception caused by internal assert."); }
public static unsafe JobHandle Schedule(ref JobScheduleParameters parameters) { // Heap memory must be passed to schedule, so that Cleanup can free() it. 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.IsTrue(jobReflectionData.GenCleanupFunctionPtr.ToPointer() == null); void *jobMetaPtr = parameters.JobDataPtr.ToPointer(); #if UNITY_SINGLETHREADED_JOBS // In the single threaded case, this is synchronous execution. UnsafeUtility.CallFunctionPtr_pi(jobReflectionData.GenExecuteFunctionPtr.ToPointer(), jobMetaPtr, 0); // 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(ScheduleJob(jobReflectionData.GenExecuteFunctionPtr, parameters.JobDataPtr, parameters.Dependency)); #endif }
public static unsafe JobHandle Schedule(ref JobScheduleParameters parameters) { // Ensure the user has not set the schedule mode to a currently unsupported type Assert.IsTrue(parameters.ScheduleMode != ScheduleMode.Single); // Heap memory must be passed to schedule, so that Cleanup can free() it. UnsafeUtility.AssertHeap(parameters.JobDataPtr); UnsafeUtility.AssertHeap(parameters.ReflectionData); ReflectionDataProxy jobReflectionData = UnsafeUtility.AsRef <ReflectionDataProxy>(parameters.ReflectionData); Assert.IsTrue(jobReflectionData.ExecuteFunctionPtr.ToPointer() != null); Assert.IsTrue(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; Assert.IsTrue(sizeof(JobRanges) <= JobMetaData.kJobMetaDataIsParallelOffset); UnsafeUtility.CopyPtrToStructure(managedJobDataPtr, out jobMetaData); Assert.IsTrue(jobMetaData.jobDataSize > 0); // set by JobScheduleParameters jobMetaData.managedPtr = managedJobDataPtr; jobMetaData.isParallelFor = 0; UnsafeUtility.CopyStructureToPtr(ref jobMetaData, managedJobDataPtr); JobHandle jobHandle = default; #if !UNITY_SINGLETHREADED_JOBS bool runSingleThreadSynchronous = parameters.ScheduleMode == ScheduleMode.RunOnMainThread || parameters.ScheduleMode == ScheduleMode.Run || parameters.ScheduleMode == ScheduleMode.ScheduleOnMainThread; #else bool runSingleThreadSynchronous = true; #endif if (runSingleThreadSynchronous) { bool syncNow = parameters.ScheduleMode == ScheduleMode.Run || parameters.ScheduleMode == ScheduleMode.RunOnMainThread; #if UNITY_SINGLETHREADED_JOBS 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); UnsafeUtility.EnterTempScope(); try { UnsafeUtility.CallFunctionPtr_pp(jobReflectionData.MarshalToBurstFunctionPtr.ToPointer(), dst, src); // In the single threaded case, this is synchronous execution. // The cleanup *is* bursted, so pass in the unmanangedJobDataPtr CopyMetaDataToJobData(ref jobMetaData, managedJobDataPtr, unmanagedJobData); UnsafeUtility.CallFunctionPtr_pi(jobReflectionData.ExecuteFunctionPtr.ToPointer(), unmanagedJobData, k_MainThreadWorkerIndex); } 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); } 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. // This time though, we have a whole bunch of jobs that need to be processed. if (jobReflectionData.UnmanagedSize != -1) { JobMetaData *unmanagedJobData = AllocateJobHeapMemory(jobReflectionData.UnmanagedSize, 1); void *dst = (byte *)unmanagedJobData + sizeof(JobMetaData); void *src = (byte *)managedJobDataPtr + sizeof(JobMetaData); UnsafeUtility.CallFunctionPtr_pp(jobReflectionData.MarshalToBurstFunctionPtr.ToPointer(), dst, src); CopyMetaDataToJobData(ref jobMetaData, managedJobDataPtr, unmanagedJobData); jobHandle = ScheduleJob(jobReflectionData.ExecuteFunctionPtr, unmanagedJobData, parameters.Dependency); } else #endif { CopyMetaDataToJobData(ref jobMetaData, managedJobDataPtr, null); jobHandle = ScheduleJob(jobReflectionData.ExecuteFunctionPtr, parameters.JobDataPtr, parameters.Dependency); } #endif return(jobHandle); }
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); }
public unsafe JobScheduleParameters(void *jobData, ReflectionDataProxy *reflectionData, JobHandle jobDependency, ScheduleMode scheduleMode, int jobDataSize = 0, #if ENABLE_UNITY_COLLECTIONS_CHECKS int producerJobPreSchedule = 1, int userJobPreSchedule = 3, #endif int isBursted = 5) { // Synchronize with InterfaceGen.cs! #if ENABLE_UNITY_COLLECTIONS_CHECKS const int k_ProducerScheduleReturnValue = 4; const int k_UserScheduleReturnValue = 2; const int k_UserScheduleReturnValueNoParallel = -2; #endif const string k_PostFix = " Seeing this error indicates a bug in the dots compiler. We'd appreciate a bug report (About->Report a Bug...)."; // Default is 0; code-gen should set to a correct size. if (jobDataSize == 0) { throw new InvalidOperationException("JobScheduleParameters (size) should be set by code-gen." + k_PostFix); } #if ENABLE_UNITY_COLLECTIONS_CHECKS if (producerJobPreSchedule != k_ProducerScheduleReturnValue) { throw new InvalidOperationException( "JobScheduleParameter (which is the return code of ProducerScheduleFn_Gen) should be set by code-gen." + k_PostFix); } if (userJobPreSchedule != k_UserScheduleReturnValue && userJobPreSchedule != k_UserScheduleReturnValueNoParallel) { throw new InvalidOperationException( "JobScheduleParameter (which is the return code of PrepareJobAtPreScheduleTimeFn_Gen) should be set by code-gen." + k_PostFix); } #endif if (!(isBursted == 0 || isBursted == 1)) { throw new InvalidOperationException( "JobScheduleParameter (which is the return code of RunOnMainThread_Gen) should be set by code-gen." + k_PostFix); } int nWorkers = JobWorkerCount > 0 ? JobWorkerCount : 1; JobMetaData *mem = AllocateJobHeapMemory(jobDataSize, nWorkers); // A copy of the JobData is needed *for each worker thread* as it will // get mutated in unique ways (threadIndex, safety.) The jobIndex is passed // to the Execute method, so a thread can look up the correct jobData to use. // Cleanup is always called on jobIndex=0. for (int i = 0; i < nWorkers; i++) { UnsafeUtility.MemCpy(((byte *)mem + sizeof(JobMetaData) + jobDataSize * i), jobData, jobDataSize); } UnsafeUtility.AssertHeap(mem); JobMetaData jobMetaData = new JobMetaData(); jobMetaData.jobDataSize = jobDataSize; // Indicate parallel for is an error #if ENABLE_UNITY_COLLECTIONS_CHECKS if (userJobPreSchedule == k_UserScheduleReturnValueNoParallel) { jobMetaData.isParallelFor = -1; } #endif UnsafeUtility.CopyStructureToPtr(ref jobMetaData, mem); Dependency = jobDependency; JobDataPtr = mem; ReflectionData = reflectionData; #if UNITY_DOTSRUNTIME_MULTITHREAD_NOBURST // Allow debugging multithreaded code without burst when necessary ScheduleMode = scheduleMode; #else // Normally, only bursted methods run on worker threads if (isBursted != 0) { ScheduleMode = scheduleMode; } else { // Both main thread schedule modes run immediately - the difference is that scheduling returns a job handle // which must still be synced in order to maintain dependency safety, whereas run returns a default job handle // not needing completion. ScheduleMode = scheduleMode == ScheduleMode.Run ? ScheduleMode.RunOnMainThread : ScheduleMode.ScheduleOnMainThread; } #endif }