private static bool TryGetNextValueAtomic( NativeHashSetState *state, ref NativeMultiHashSetIterator it) { int entryIdx = it.NextEntryIndex; it.NextEntryIndex = -1; if (entryIdx < 0 || entryIdx >= state->ItemCapacity) { return(false); } int *nextPtrs = (int *)state->Next; while (!UnsafeUtility.ReadArrayElement <T>( state->Items, entryIdx).Equals(it.Item)) { entryIdx = nextPtrs[entryIdx]; if (entryIdx < 0 || entryIdx >= state->ItemCapacity) { return(false); } } it.NextEntryIndex = nextPtrs[entryIdx]; return(true); }
/// <summary> /// Deallocate the state's contents then the state itself. /// </summary> private void Deallocate() { UnsafeUtility.Free(m_State->Items, m_Allocator); m_State->Items = null; m_State->Next = null; m_State->Buckets = null; UnsafeUtility.Free(m_State, m_Allocator); m_State = null; }
public Enumerator(NativeHashSet <T> set) { set.RequireReadAccess(); index = -1; bucket = -1; state = set.m_State; bucketArray = (int *)state->Buckets; bucketNext = (int *)state->Next; }
private static bool TryGetFirstValueAtomic( NativeHashSetState *state, T item, out NativeMultiHashSetIterator it) { it.Item = item; if (state->AllocatedIndexLength <= 0) { it.NextEntryIndex = -1; return(false); } // First find the slot based on the hash int *buckets = (int *)state->Buckets; int bucket = item.GetHashCode() & state->BucketCapacityMask; it.NextEntryIndex = buckets[bucket]; return(TryGetNextValueAtomic(state, ref it)); }
/// <summary> /// Schedule a job to release the set's unmanaged memory after the given /// dependency jobs. Do not use it after this job executes. Do /// not call <see cref="Dispose()"/> on copies of the set either. /// /// This operation requires write access. /// /// This complexity of this operation is O(1) plus the allocator's /// deallocation complexity. /// </summary> public JobHandle Dispose(JobHandle inputDeps) { RequireWriteAccess(); #if ENABLE_UNITY_COLLECTIONS_CHECKS // Clear the dispose sentinel, but don't Dispose it DisposeSentinel.Clear(ref m_DisposeSentinel); #endif // Schedule the job DisposeJob disposeJob = new DisposeJob { Set = this }; JobHandle jobHandle = disposeJob.Schedule(inputDeps); // Release the atomic safety handle now that the job is scheduled #if ENABLE_UNITY_COLLECTIONS_CHECKS AtomicSafetyHandle.Release(m_Safety); #endif m_State = null; return(jobHandle); }
/// <summary> /// Allocate an entry from the free list. The list must not be full. /// </summary> /// /// <param name="state"> /// State to allocate from. /// </param> /// /// <param name="threadIndex"> /// Index of the allocating thread. /// </param> /// /// <returns> /// The allocated free list entry index /// </returns> private static int AllocFreeListEntry( NativeHashSetState *state, int threadIndex) { int idx; int *nextPtrs = (int *)state->Next; do { idx = state->FirstFreeTLS[threadIndex * NativeHashSetState.IntsPerCacheLine]; if (idx < 0) { // Try to refill local cache Interlocked.Exchange( ref state->FirstFreeTLS[threadIndex * NativeHashSetState.IntsPerCacheLine], -2); // If it failed try to get one from the never-allocated array if (state->AllocatedIndexLength < state->ItemCapacity) { idx = Interlocked.Add(ref state->AllocatedIndexLength, 16) - 16; if (idx < state->ItemCapacity - 1) { int count = Math.Min(16, state->ItemCapacity - idx); for (int i = 1; i < count; ++i) { nextPtrs[idx + i] = idx + i + 1; } nextPtrs[idx + count - 1] = -1; nextPtrs[idx] = -1; Interlocked.Exchange( ref state->FirstFreeTLS[threadIndex * NativeHashSetState.IntsPerCacheLine], idx + 1); return(idx); } if (idx == state->ItemCapacity - 1) { Interlocked.Exchange( ref state->FirstFreeTLS[threadIndex * NativeHashSetState.IntsPerCacheLine], -1); return(idx); } } Interlocked.Exchange( ref state->FirstFreeTLS[threadIndex * NativeHashSetState.IntsPerCacheLine], -1); // Failed to get any, try to get one from another free list bool again = true; while (again) { again = false; for (int other = (threadIndex + 1) % JobsUtility.MaxJobThreadCount; other != threadIndex; other = (other + 1) % JobsUtility.MaxJobThreadCount) { do { idx = state->FirstFreeTLS[other * NativeHashSetState.IntsPerCacheLine]; if (idx < 0) { break; } } while (Interlocked.CompareExchange( ref state->FirstFreeTLS[other * NativeHashSetState.IntsPerCacheLine], nextPtrs[idx], idx) != idx); if (idx == -2) { again = true; } else if (idx >= 0) { nextPtrs[idx] = -1; return(idx); } } } throw new InvalidOperationException("HashSet is full"); } if (idx >= state->ItemCapacity) { throw new InvalidOperationException("HashSet is full"); } } while (Interlocked.CompareExchange( ref state->FirstFreeTLS[threadIndex * NativeHashSetState.IntsPerCacheLine], nextPtrs[idx], idx) != idx); nextPtrs[idx] = -1; return(idx); }
/// <summary> /// Create an empty hash set with a given capacity /// </summary> /// /// <param name="capacity"> /// Capacity of the hash set. If less than four, four is used. /// </param> /// /// <param name="allocator"> /// Allocator to allocate unmanaged memory with. Must be valid. /// </param> public NativeHashSet(int capacity, Allocator allocator) { // Require a valid allocator if (allocator <= Allocator.None) { throw new ArgumentException( "Allocator must be Temp, TempJob or Persistent", "allocator"); } RequireBlittable(); // Insist on a minimum capacity if (capacity < 4) { capacity = 4; } m_Allocator = allocator; // Allocate the state NativeHashSetState *state = (NativeHashSetState *)UnsafeUtility.Malloc( sizeof(NativeHashSetState), UnsafeUtility.AlignOf <NativeHashSetState>(), allocator); state->ItemCapacity = capacity; // To reduce collisions, use twice as many buckets int bucketLength = capacity * 2; bucketLength = NextHigherPowerOfTwo(bucketLength); state->BucketCapacityMask = bucketLength - 1; // Allocate state arrays int nextOffset; int bucketOffset; int totalSize = CalculateDataLayout( capacity, bucketLength, out nextOffset, out bucketOffset); state->Items = (byte *)UnsafeUtility.Malloc( totalSize, JobsUtility.CacheLineSize, allocator); state->Next = state->Items + nextOffset; state->Buckets = state->Items + bucketOffset; m_State = state; #if ENABLE_UNITY_COLLECTIONS_CHECKS #if UNITY_2018_3_OR_NEWER DisposeSentinel.Create( out m_Safety, out m_DisposeSentinel, 1, allocator); #else DisposeSentinel.Create( out m_Safety, out m_DisposeSentinel, 1); #endif #endif Clear(); }