// #define DEBUG_JOBS namespace Pathfinding.Jobs { using System.Reflection; using Unity.Collections; using Unity.Jobs; using System.Collections.Generic; using Unity.Collections.LowLevel.Unsafe; using Pathfinding.Pooling; using Pathfinding.Collections; using System.Runtime.InteropServices; using System.Diagnostics; /// /// Disable the check that prevents jobs from including uninitialized native arrays open for reading. /// /// Sometimes jobs have to include a readable native array that starts out uninitialized. /// The job might for example write to it and later read from it in the same job. /// /// See: /// class DisableUninitializedReadCheckAttribute : System.Attribute { } public interface IArenaDisposable { void DisposeWith(DisposeArena arena); } /// Convenient collection of items that can be disposed together public class DisposeArena { List > buffer; List > buffer2; List > buffer3; List gcHandles; public void Add(NativeArray data) where T : unmanaged { if (buffer == null) buffer = ListPool >.Claim(); buffer.Add(data.Reinterpret(UnsafeUtility.SizeOf())); } public void Add(NativeList data) where T : unmanaged { // SAFETY: This is safe because NativeList and NativeList have the same memory layout. var byteList = Unity.Collections.LowLevel.Unsafe.UnsafeUtility.As, NativeList >(ref data); if (buffer2 == null) buffer2 = ListPool >.Claim(); buffer2.Add(byteList); } public void Add(NativeQueue data) where T : unmanaged { // SAFETY: This is safe because NativeQueue and NativeQueue have the same memory layout. var byteList = Unity.Collections.LowLevel.Unsafe.UnsafeUtility.As, NativeQueue >(ref data); if (buffer3 == null) buffer3 = ListPool >.Claim(); buffer3.Add(byteList); } public void Remove(NativeArray data) where T : unmanaged { if (buffer == null) return; unsafe { var ptr = NativeArrayUnsafeUtility.GetUnsafeBufferPointerWithoutChecks(data); for (int i = 0; i < buffer.Count; i++) { if (NativeArrayUnsafeUtility.GetUnsafeBufferPointerWithoutChecks(buffer[i]) == ptr) { buffer.RemoveAtSwapBack(i); return; } } } } public void Add(T data) where T : IArenaDisposable { data.DisposeWith(this); } public void Add (GCHandle handle) { if (gcHandles == null) gcHandles = ListPool.Claim(); gcHandles.Add(handle); } /// /// Dispose all items in the arena. /// This also clears the arena and makes it available for reuse. /// public void DisposeAll () { UnityEngine.Profiling.Profiler.BeginSample("Disposing"); if (buffer != null) { for (int i = 0; i < buffer.Count; i++) buffer[i].Dispose(); ListPool >.Release(ref buffer); } if (buffer2 != null) { for (int i = 0; i < buffer2.Count; i++) buffer2[i].Dispose(); ListPool >.Release(ref buffer2); } if (buffer3 != null) { for (int i = 0; i < buffer3.Count; i++) buffer3[i].Dispose(); ListPool >.Release(ref buffer3); } if (gcHandles != null) { for (int i = 0; i < gcHandles.Count; i++) gcHandles[i].Free(); ListPool.Release(ref gcHandles); } UnityEngine.Profiling.Profiler.EndSample(); } } // TODO: Remove or use? public struct JobHandleWithMainThreadWork where T : struct { JobDependencyTracker tracker; IEnumerator<(JobHandle, T)> coroutine; public JobHandleWithMainThreadWork (IEnumerator<(JobHandle, T)> handles, JobDependencyTracker tracker) { this.coroutine = handles; this.tracker = tracker; } public void Complete () { tracker.timeSlice = TimeSlice.Infinite; while (coroutine.MoveNext()) { coroutine.Current.Item1.Complete(); } } public System.Collections.Generic.IEnumerable CompleteTimeSliced (float maxMillisPerStep) { tracker.timeSlice = TimeSlice.MillisFromNow(maxMillisPerStep); while (true) { if (!coroutine.MoveNext()) yield break; if (maxMillisPerStep < float.PositiveInfinity) { while (!coroutine.Current.Item1.IsCompleted) { yield return null; tracker.timeSlice = TimeSlice.MillisFromNow(maxMillisPerStep); } } coroutine.Current.Item1.Complete(); yield return coroutine.Current.Item2; tracker.timeSlice = TimeSlice.MillisFromNow(maxMillisPerStep); } } } enum LinearDependencies : byte { Check, Enabled, Disabled, } /// /// Automatic dependency tracking for the Unity Job System. /// /// Uses reflection to find the [ReadOnly] and [WriteOnly] attributes on job data struct fields. /// These are used to automatically figure out dependencies between jobs. /// /// A job that reads from an array depends on the last job that wrote to that array. /// A job that writes to an array depends on the last job that wrote to the array as well as all jobs that read from the array. /// /// /// struct ExampleJob : IJob { /// public NativeArray someData; /// /// public void Execute () { /// // Do something /// } /// } /// /// void Start () { /// var tracker = new JobDependencyTracker(); /// var data = new NativeArray(100, Allocator.TempJob); /// var job1 = new ExampleJob { /// someData = data /// }.Schedule(tracker); /// /// var job2 = new ExampleJob { /// someData = data /// }.Schedule(tracker); /// /// // job2 automatically depends on job1 because they both require read/write access to the data array /// } /// /// /// See: /// public class JobDependencyTracker : IAstarPooledObject { internal List slots = ListPool.Claim(); DisposeArena arena; internal NativeArray dependenciesScratchBuffer; LinearDependencies linearDependencies; internal TimeSlice timeSlice = TimeSlice.Infinite; #if ENABLE_UNITY_COLLECTIONS_CHECKS ~JobDependencyTracker() { if (dependenciesScratchBuffer.IsCreated) { UnityEngine.Debug.LogError("JobDependencyTracker was not disposed. This will cause a memory leak. Please call Dispose on the JobDependencyTracker when you are done with it."); } } #endif public bool forceLinearDependencies { get { if (linearDependencies == LinearDependencies.Check) SetLinearDependencies(false); return linearDependencies == LinearDependencies.Enabled; } } internal struct JobInstance { public JobHandle handle; public int hash; #if DEBUG_JOBS public string name; #endif } internal struct NativeArraySlot { public long hash; public JobInstance lastWrite; public List lastReads; public bool initialized; public bool hasWrite; } // Note: burst compiling even an empty job can avoid the overhead of going from unmanaged to managed code. /* [BurstCompile] struct JobDispose : IJob where T : struct { [DeallocateOnJobCompletion] [DisableUninitializedReadCheck] public NativeArray data; public void Execute () { } }*/ struct JobRaycastCommandDummy : IJob { [ReadOnly] public NativeArray commands; [WriteOnly] public NativeArray results; public void Execute () {} } #if UNITY_2022_2_OR_NEWER struct JobOverlapCapsuleCommandDummy : IJob { [ReadOnly] public NativeArray commands; [WriteOnly] public NativeArray results; public void Execute () {} } struct JobOverlapSphereCommandDummy : IJob { [ReadOnly] public NativeArray commands; [WriteOnly] public NativeArray results; public void Execute () {} } #endif /// /// JobHandle that represents a dependency for all jobs. /// All native arrays that are written (and have been tracked by this tracker) to will have their final results in them /// when the returned job handle is complete. /// public JobHandle AllWritesDependency { get { var handles = new NativeArray(slots.Count, Allocator.Temp); for (int i = 0; i < slots.Count; i++) handles[i] = slots[i].lastWrite.handle; var dependencies = JobHandle.CombineDependencies(handles); handles.Dispose(); return dependencies; } } bool supportsMultithreading { get { #if UNITY_WEBGL return false; #else return Unity.Jobs.LowLevel.Unsafe.JobsUtility.JobWorkerCount > 0; #endif } } /// /// Disable dependency tracking and just run jobs one after the other. /// This may be faster in some cases since dependency tracking has some overhead. /// public void SetLinearDependencies (bool linearDependencies) { if (!supportsMultithreading) linearDependencies = true; if (linearDependencies) { AllWritesDependency.Complete(); } this.linearDependencies = linearDependencies ? LinearDependencies.Enabled : LinearDependencies.Disabled; } public NativeArray NewNativeArray(int length, Allocator allocator, NativeArrayOptions options = NativeArrayOptions.ClearMemory) where T : unmanaged { var res = new NativeArray(length, allocator, options); Track(res, options == NativeArrayOptions.ClearMemory); return res; } public void Track(NativeArray array, bool initialized = true) where T : unmanaged { unsafe { slots.Add(new NativeArraySlot { hash = (long)NativeArrayUnsafeUtility.GetUnsafeBufferPointerWithoutChecks(array), lastWrite = default, lastReads = ListPool.Claim(), initialized = initialized, }); } if (this.arena == null) this.arena = new DisposeArena(); arena.Add(array); } /// /// Makes the given array not be disposed when this tracker is disposed. /// This is useful if you want to keep the array around after the tracker has been disposed. /// The array will still be tracked for the purposes of automatic dependency management. /// public void Persist(NativeArray array) where T : unmanaged { if (this.arena == null) return; arena.Remove(array); } /// /// Schedules a raycast batch command. /// Like RaycastCommand.ScheduleBatch, but dependencies are tracked automatically. /// public JobHandle ScheduleBatch (NativeArray commands, NativeArray results, int minCommandsPerJob) { if (forceLinearDependencies) { UnityEngine.RaycastCommand.ScheduleBatch(commands, results, minCommandsPerJob).Complete(); return default; } // Create a dummy structure to allow the analyzer to determine how the job reads/writes data var dummy = new JobRaycastCommandDummy { commands = commands, results = results }; var dependencies = JobDependencyAnalyzer.GetDependencies(ref dummy, this); var job = UnityEngine.RaycastCommand.ScheduleBatch(commands, results, minCommandsPerJob, dependencies); JobDependencyAnalyzer.Scheduled(ref dummy, this, job); return job; } #if UNITY_2022_2_OR_NEWER /// /// Schedules an overlap capsule batch command. /// Like OverlapCapsuleCommand.ScheduleBatch, but dependencies are tracked automatically. /// public JobHandle ScheduleBatch (NativeArray commands, NativeArray results, int minCommandsPerJob) { if (forceLinearDependencies) { UnityEngine.OverlapCapsuleCommand.ScheduleBatch(commands, results, minCommandsPerJob, 1).Complete(); return default; } // Create a dummy structure to allow the analyzer to determine how the job reads/writes data var dummy = new JobOverlapCapsuleCommandDummy { commands = commands, results = results }; var dependencies = JobDependencyAnalyzer.GetDependencies(ref dummy, this); var job = UnityEngine.OverlapCapsuleCommand.ScheduleBatch(commands, results, minCommandsPerJob, 1, dependencies); JobDependencyAnalyzer.Scheduled(ref dummy, this, job); return job; } /// /// Schedules an overlap sphere batch command. /// Like OverlapSphereCommand.ScheduleBatch, but dependencies are tracked automatically. /// public JobHandle ScheduleBatch (NativeArray commands, NativeArray results, int minCommandsPerJob) { if (forceLinearDependencies) { UnityEngine.OverlapSphereCommand.ScheduleBatch(commands, results, minCommandsPerJob, 1).Complete(); return default; } // Create a dummy structure to allow the analyzer to determine how the job reads/writes data var dummy = new JobOverlapSphereCommandDummy { commands = commands, results = results }; var dependencies = JobDependencyAnalyzer.GetDependencies(ref dummy, this); var job = UnityEngine.OverlapSphereCommand.ScheduleBatch(commands, results, minCommandsPerJob, 1, dependencies); JobDependencyAnalyzer.Scheduled(ref dummy, this, job); return job; } #endif /// Frees the GCHandle when the JobDependencyTracker is disposed public void DeferFree (GCHandle handle, JobHandle dependsOn) { if (this.arena == null) this.arena = new DisposeArena(); this.arena.Add(handle); } #if DEBUG_JOBS internal void JobReadsFrom (JobHandle job, long nativeArrayHash, int jobHash, string jobName) #else internal void JobReadsFrom (JobHandle job, long nativeArrayHash, int jobHash) #endif { for (int j = 0; j < slots.Count; j++) { var slot = slots[j]; if (slot.hash == nativeArrayHash) { // If the job only reads from the array then we just add this job to the list of readers slot.lastReads.Add(new JobInstance { handle = job, hash = jobHash, #if DEBUG_JOBS name = jobName, #endif }); break; } } } #if DEBUG_JOBS internal void JobWritesTo (JobHandle job, long nativeArrayHash, int jobHash, string jobName) #else internal void JobWritesTo (JobHandle job, long nativeArrayHash, int jobHash) #endif { for (int j = 0; j < slots.Count; j++) { var slot = slots[j]; if (slot.hash == nativeArrayHash) { // If the job writes to the array then this job is now the last writer slot.lastWrite = new JobInstance { handle = job, hash = jobHash, #if DEBUG_JOBS name = jobName, #endif }; slot.lastReads.Clear(); // The array no longer contains uninitialized data. // Parts of it may still be uninitialized if the job doesn't write to everything, but that's something that this class cannot track. slot.initialized = true; slot.hasWrite = true; slots[j] = slot; break; } } } /// /// Disposes this tracker. /// This will pool all used lists which makes the GC happy. /// /// Note: It is necessary to call this method to avoid memory leaks if you are using the DeferDispose method. But it's a good thing to do otherwise as well. /// It is automatically called if you are using the ObjectPool.Release method. /// void Dispose () { #if ENABLE_UNITY_COLLECTIONS_CHECKS && UNITY_2022_2_OR_NEWER // Note: This can somehow fail in Unity 2021 and 2022.1, even when calling Complete on all jobs UnityEngine.Assertions.Assert.IsTrue(AllWritesDependency.IsCompleted); #endif for (int i = 0; i < slots.Count; i++) ListPool.Release(slots[i].lastReads); slots.Clear(); if (arena != null) arena.DisposeAll(); linearDependencies = LinearDependencies.Check; if (dependenciesScratchBuffer.IsCreated) dependenciesScratchBuffer.Dispose(); } public void ClearMemory () { AllWritesDependency.Complete(); Dispose(); } void IAstarPooledObject.OnEnterPool () { Dispose(); } } public struct TimeSlice { public long endTick; public static readonly TimeSlice Infinite = new TimeSlice { endTick = long.MaxValue }; public bool isInfinite => endTick == long.MaxValue; public bool expired => Stopwatch.GetTimestamp() > endTick; public static TimeSlice MillisFromNow (float millis) => new TimeSlice { endTick = Stopwatch.GetTimestamp() + (long)(millis * 10000) }; } public interface IJobTimeSliced : IJob { /// /// Returns true if the job completed. /// If false is returned this job may be called again until the job completes. /// bool Execute(TimeSlice timeSlice); } /// Extension methods for IJob and related interfaces public static class IJobExtensions { struct ManagedJob : IJob { public GCHandle handle; public void Execute () { ((IJob)handle.Target).Execute(); handle.Free(); } } struct ManagedActionJob : IJob { public GCHandle handle; public void Execute () { ((System.Action)handle.Target)(); handle.Free(); } } /// /// Schedule a job with automatic dependency tracking. /// You need to have "using Pathfinding.Util" in your script to be able to use this extension method. /// /// See: /// // TODO: Compare performance impact by using ref this, and ScheduleByRef public static JobHandle Schedule(this T data, JobDependencyTracker tracker) where T : struct, IJob { if (tracker.forceLinearDependencies) { data.Run(); return default; } else { var job = data.Schedule(JobDependencyAnalyzer.GetDependencies(ref data, tracker)); JobDependencyAnalyzer.Scheduled(ref data, tracker, job); return job; } } /// Schedules an job with automatic dependency tracking public static JobHandle ScheduleBatch(this T data, int arrayLength, int minIndicesPerJobCount, JobDependencyTracker tracker, JobHandle additionalDependency = default) where T : struct, IJobParallelForBatched { if (tracker.forceLinearDependencies) { additionalDependency.Complete(); //data.ScheduleBatch(arrayLength, minIndicesPerJobCount, additionalDependency).Complete(); data.RunBatch(arrayLength); return default; } else { var job = data.ScheduleBatch(arrayLength, minIndicesPerJobCount, JobDependencyAnalyzer.GetDependencies(ref data, tracker, additionalDependency)); JobDependencyAnalyzer.Scheduled(ref data, tracker, job); return job; } } /// Schedules a managed job to run in the job system public static JobHandle ScheduleManaged(this T data, JobHandle dependsOn) where T : struct, IJob { return new ManagedJob { handle = GCHandle.Alloc(data) }.Schedule(dependsOn); } /// Schedules a managed job to run in the job system public static JobHandle ScheduleManaged (this System.Action data, JobHandle dependsOn) { return new ManagedActionJob { handle = GCHandle.Alloc(data) }.Schedule(dependsOn); } public static JobHandle GetDependencies(this T data, JobDependencyTracker tracker) where T : struct, IJob { if (tracker.forceLinearDependencies) return default; else return JobDependencyAnalyzer.GetDependencies(ref data, tracker); } /// /// Executes this job in the main thread using a coroutine. /// Usage: /// - 1. Optionally schedule some other jobs before this one (using the dependency tracker) /// - 2. Call job.ExecuteMainThreadJob(tracker) /// - 3. Iterate over the enumerator until it is finished. Call handle.Complete on all yielded job handles. Usually this only yields once, but if you use the wrapper it will /// yield once for every time slice. /// - 4. Continue scheduling other jobs. /// /// You must not schedule other jobs (that may touch the same data) while executing this job. /// /// See: /// public static IEnumerator ExecuteMainThreadJob(this T data, JobDependencyTracker tracker) where T : struct, IJobTimeSliced { if (tracker.forceLinearDependencies) { UnityEngine.Profiling.Profiler.BeginSample("Main Thread Work"); data.Execute(); UnityEngine.Profiling.Profiler.EndSample(); yield break; } var dependsOn = JobDependencyAnalyzer.GetDependencies(ref data, tracker); yield return dependsOn; while (true) { UnityEngine.Profiling.Profiler.BeginSample("Main Thread Work"); var didComplete = data.Execute(tracker.timeSlice); UnityEngine.Profiling.Profiler.EndSample(); if (didComplete) yield break; else yield return new JobHandle(); } } } static class JobDependencyAnalyzerAssociated { internal static int[] tempJobDependencyHashes = new int[16]; internal static int jobCounter = 1; } struct JobDependencyAnalyzer where T : struct { static ReflectionData reflectionData; /// Offset to the m_Buffer field inside each NativeArray // Note: Due to a Unity bug we have to calculate this for NativeArray instead of NativeArray<>. NativeArray<> will return an incorrect value (-16) when using IL2CPP. static readonly int BufferOffset = UnsafeUtility.GetFieldOffset(typeof(NativeArray).GetField("m_Buffer", BindingFlags.Instance | BindingFlags.NonPublic)); static readonly int SpanPtrOffset = UnsafeUtility.GetFieldOffset(typeof(UnsafeSpan).GetField("ptr", BindingFlags.Instance | BindingFlags.NonPublic)); struct ReflectionData { public int[] fieldOffsets; public bool[] writes; public bool[] checkUninitializedRead; public string[] fieldNames; public void Build () { // Find the byte offsets within the struct to all m_Buffer fields in all the native arrays in the struct var fields = new List(); var writes = new List(); var reads = new List(); var names = new List(); Build(typeof(T), fields, writes, reads, names, 0, false, false, false); this.fieldOffsets = fields.ToArray(); this.writes = writes.ToArray(); this.fieldNames = names.ToArray(); this.checkUninitializedRead = reads.ToArray(); } void Build (System.Type type, List fields, List writes, List reads, List names, int offset, bool forceReadOnly, bool forceWriteOnly, bool forceDisableUninitializedCheck) { foreach (var field in type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)) { if (field.FieldType.IsGenericType && field.FieldType.GetGenericTypeDefinition() == typeof(NativeArray<>)) { // Handle NativeArrays fields.Add(offset + UnsafeUtility.GetFieldOffset(field) + BufferOffset); writes.Add(!forceReadOnly && field.GetCustomAttribute(typeof(ReadOnlyAttribute)) == null); reads.Add(!forceWriteOnly && !forceDisableUninitializedCheck && field.GetCustomAttribute(typeof(WriteOnlyAttribute)) == null && field.GetCustomAttribute(typeof(DisableUninitializedReadCheckAttribute)) == null); names.Add(field.Name); } else if (field.FieldType.IsGenericType && field.FieldType.GetGenericTypeDefinition() == typeof(UnsafeSpan<>)) { // Handle UnsafeSpans fields.Add(offset + UnsafeUtility.GetFieldOffset(field) + SpanPtrOffset); writes.Add(!forceReadOnly && field.GetCustomAttribute(typeof(ReadOnlyAttribute)) == null); reads.Add(!forceWriteOnly && !forceDisableUninitializedCheck && field.GetCustomAttribute(typeof(WriteOnlyAttribute)) == null && field.GetCustomAttribute(typeof(DisableUninitializedReadCheckAttribute)) == null); names.Add(field.Name); } else if (!field.FieldType.IsPrimitive && field.FieldType.IsValueType && !field.FieldType.IsEnum) { // Recurse to handle nested types bool readOnly = field.GetCustomAttribute(typeof(ReadOnlyAttribute)) != null; bool writeOnly = field.GetCustomAttribute(typeof(WriteOnlyAttribute)) != null; bool disableUninitializedCheck = field.GetCustomAttribute(typeof(DisableUninitializedReadCheckAttribute)) != null; Build(field.FieldType, fields, writes, reads, names, offset + UnsafeUtility.GetFieldOffset(field), readOnly, writeOnly, disableUninitializedCheck); } } } } static void initReflectionData () { if (reflectionData.fieldOffsets == null) { reflectionData.Build(); } } static bool HasHash (int[] hashes, int hash, int count) { for (int i = 0; i < count; i++) if (hashes[i] == hash) return true; return false; } /// Returns the dependencies for the given job. /// Job data. Must be allocated on the stack. /// The tracker to use for dependency tracking. public static JobHandle GetDependencies (ref T data, JobDependencyTracker tracker) { return GetDependencies(ref data, tracker, default, false); } public static JobHandle GetDependencies (ref T data, JobDependencyTracker tracker, JobHandle additionalDependency) { return GetDependencies(ref data, tracker, additionalDependency, true); } static JobHandle GetDependencies (ref T data, JobDependencyTracker tracker, JobHandle additionalDependency, bool useAdditionalDependency) { if (!tracker.dependenciesScratchBuffer.IsCreated) tracker.dependenciesScratchBuffer = new NativeArray(16, Allocator.Persistent, NativeArrayOptions.UninitializedMemory); var dependencies = tracker.dependenciesScratchBuffer; var slots = tracker.slots; var dependencyHashes = JobDependencyAnalyzerAssociated.tempJobDependencyHashes; int numDependencies = 0; initReflectionData(); #if DEBUG_JOBS string dependenciesDebug = ""; #endif unsafe { // Note: data is a struct. It is stored on the stack and can thus not be moved by the GC. // Therefore we do not need to pin it first. // It is guaranteed to be stored on the stack since the Schedule method takes the data parameter by value and not by reference. byte* dataPtr = (byte*)UnsafeUtility.AddressOf(ref data); var offsets = reflectionData.fieldOffsets; for (int i = 0; i < offsets.Length; i++) { // This is the internal value of the m_Buffer field of the NativeArray void* nativeArrayBufferPtr = *(void**)(dataPtr + offsets[i]); // Use the pointer as a hash to uniquely identify a NativeArray var hash = (long)nativeArrayBufferPtr; //JobDependencyAnalyzerAssociated.iteratingSlotsSampler.Begin(); for (int j = 0; j <= slots.Count; j++) { // No slot found. Add a new one if (j == slots.Count) { slots.Add(new JobDependencyTracker.NativeArraySlot { hash = hash, lastWrite = default, lastReads = ListPool.Claim(), initialized = true, // We don't know anything about the array, so assume it contains initialized data. JobDependencyTracker.NewNativeArray should be used otherwise. hasWrite = false, }); } // Check if we know about this NativeArray yet var slot = slots[j]; if (slot.hash == hash) { if (reflectionData.checkUninitializedRead[i] && !slot.initialized) { throw new System.InvalidOperationException("A job tries to read from the native array " + typeof(T).Name + "." + reflectionData.fieldNames[i] + " which contains uninitialized data"); } if (slot.hasWrite && !HasHash(dependencyHashes, slot.lastWrite.hash, numDependencies)) { // Reads/writes always depend on the last write to the native array dependencies[numDependencies] = slot.lastWrite.handle; dependencyHashes[numDependencies] = slot.lastWrite.hash; numDependencies++; if (numDependencies >= dependencies.Length) throw new System.Exception("Too many dependencies for job"); #if DEBUG_JOBS dependenciesDebug += slot.lastWrite.name + " "; #endif } // If we want to write to the array we additionally depend on all previous reads of the array if (reflectionData.writes[i]) { for (int q = 0; q < slot.lastReads.Count; q++) { if (!HasHash(dependencyHashes, slot.lastReads[q].hash, numDependencies)) { dependencies[numDependencies] = slot.lastReads[q].handle; dependencyHashes[numDependencies] = slot.lastReads[q].hash; numDependencies++; if (numDependencies >= dependencies.Length) throw new System.Exception("Too many dependencies for job"); #if DEBUG_JOBS dependenciesDebug += slot.lastReads[q].name + " "; #endif } } } break; } } } if (useAdditionalDependency) { dependencies[numDependencies] = additionalDependency; numDependencies++; #if DEBUG_JOBS dependenciesDebug += "[additional dependency]"; #endif } #if DEBUG_JOBS UnityEngine.Debug.Log(typeof(T) + " depends on " + dependenciesDebug); #endif if (numDependencies == 0) { return default; } else if (numDependencies == 1) { return dependencies[0]; } else { return JobHandle.CombineDependencies(dependencies.Slice(0, numDependencies)); } } } internal static void Scheduled (ref T data, JobDependencyTracker tracker, JobHandle job) { unsafe { int jobHash = JobDependencyAnalyzerAssociated.jobCounter++; // Note: data is a struct. It is stored on the stack and can thus not be moved by the GC. // Therefore we do not need to pin it first. // It is guaranteed to be stored on the stack since the Schedule method takes the data parameter by value and not by reference. byte* dataPtr = (byte*)UnsafeUtility.AddressOf(ref data); for (int i = 0; i < reflectionData.fieldOffsets.Length; i++) { // This is the internal value of the m_Buffer field of the NativeArray void* nativeArrayBufferPtr = *(void**)(dataPtr + reflectionData.fieldOffsets[i]); // Use the pointer as a hash to uniquely identify a NativeArray var hash = (long)nativeArrayBufferPtr; #if DEBUG_JOBS if (reflectionData.writes[i]) tracker.JobWritesTo(job, hash, jobHash, typeof(T).Name); else tracker.JobReadsFrom(job, hash, jobHash, typeof(T).Name); #else if (reflectionData.writes[i]) tracker.JobWritesTo(job, hash, jobHash); else tracker.JobReadsFrom(job, hash, jobHash); #endif } } } } }