Example #1
0
        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
        }
Example #2
0
 static unsafe void CopyMetaDataToJobData(ref JobMetaData jobMetaData, JobMetaData *managedJobDataPtr, JobMetaData *unmanagedJobData)
 {
     jobMetaData.managedPtr   = managedJobDataPtr;
     jobMetaData.unmanagedPtr = unmanagedJobData;
     if (unmanagedJobData != null)
     {
         UnsafeUtility.CopyStructureToPtr(ref jobMetaData, unmanagedJobData);
     }
     if (managedJobDataPtr != null)
     {
         UnsafeUtility.CopyStructureToPtr(ref jobMetaData, managedJobDataPtr);
     }
 }
Example #3
0
        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);
        }
Example #4
0
            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
            }