// Returns the new tail (old one if pool not extended) internal unsafe FastHashTableBufferNode *ExtendPoolIfFull(int typeSize) { if (size == capacity) { FastHashTableBufferNode *newPool = Allocate(typeSize); newPool->size = 0; next = newPool; return(newPool); } fixed(FastHashTableBufferNode *self = &this) return(self); }
public static unsafe void Shutdown() { if (!initialized) { return; } FastHashTableBufferNode *node = markerHashTableHead; while (node != null) { var prevNode = node; node = node->next; UnsafeUtility.Free(prevNode, Allocator.Persistent); ProfilerStats.AccumStats.memReservedProfiler.Accumulate(-k_HashChunkSize); ProfilerStats.AccumStats.memUsedProfiler.Accumulate(-k_HashChunkSize); } markerHashTableHead = null; markerHashTableTail = null; node = threadHashTableHead; while (node != null) { var prevNode = node; node = node->next; UnsafeUtility.Free(prevNode, Allocator.Persistent); ProfilerStats.AccumStats.memReservedProfiler.Accumulate(-k_HashChunkSize); ProfilerStats.AccumStats.memUsedProfiler.Accumulate(-k_HashChunkSize); } threadHashTableHead = null; threadHashTableTail = null; nextMarkerId = 0; NeedsUpdate = false; ProfilerProtocolThread.Shutdown(); PlayerConnectionProfiler.Shutdown(); initialized = false; }
// Burst/thread safe internal static unsafe void *MarkerGetOrCreate(ushort categoryId, byte *name, int nameBytes, ushort flags) { if (nameBytes <= 0) { return(null); } MarkerBucketNode *marker = null; int bucket = (((nameBytes << 5) + (nameBytes >> 2)) ^ name[0]) & 255; if (markerHashTableHead != null) { // No need for locking yet - read operations on hash table are thread safe as long as we are careful about // modification during write and only allow one thread to write at a time. if (MarkerBucketNode.SearchFor(&markerHashTableHead->MarkersBuffer[bucket], name, nameBytes, ref marker)) { return(marker); } } // The marker didn't exist in hash table. Need to lock so only one thread can modify at a time. // This path will usually only be taken during startup - after which markers should be found in the // above loop instead of needing to be created. Even this is a worse-case scenario because correct ProfilerMarker // usage will create/get them once, and they will exist as an instance which only calls MarkerBegin() and MarkerEnd() // when needed. PlayerConnectionMt_LockProfilerHashTables(); if (marker == null) { if (markerHashTableHead == null) { markerHashTableHead = FastHashTableBufferNode.Allocate(sizeof(MarkerBucketNode)); markerHashTableTail = markerHashTableHead; } marker = &markerHashTableHead->MarkersBuffer[bucket]; } // In case this bucket was added to while another thread had the lock, the end-of-bucket // pointer needs to be increased. Also, it's possible the same exact name appears now. if (MarkerBucketNode.SearchFor(marker, name, nameBytes, ref marker)) { PlayerConnectionMt_UnlockProfilerHashTables(); return(marker); } MarkerBucketNode *oldMarker = null; if (marker->nameBytes > 0) { // There is already a valid marker here at the end of the linked list - add a new one markerHashTableTail = markerHashTableTail->ExtendPoolIfFull(sizeof(MarkerBucketNode)); MarkerBucketNode *newMarker = &markerHashTableTail->MarkersBuffer[markerHashTableTail->size]; markerHashTableTail->size++; oldMarker = marker; marker = newMarker; } marker->init = false; marker->categoryId = categoryId; marker->flags = flags; marker->markerId = nextMarkerId++; marker->nameBytes = nameBytes; // Todo: When Burst printing works we should warn the user if a name is too long and will be truncated UnsafeUtility.MemCpy(marker->nameUtf8, name, Math.Min(nameBytes, k_MaxMarkerNameLength)); // Do this last so if we find the node before locking, we don't have access to it unless it is otherwise fully assigned if (oldMarker != null) { oldMarker->next = marker; } PlayerConnectionMt_UnlockProfilerHashTables(); NeedsUpdate = true; return(marker); }
// NOT Burst safe due to string usage // Thread safe internal static unsafe void ThreadSetInfo(ulong threadId, ulong sysTicksStart, bool frameIndependent, string threadGroup, string threadName) { ThreadBucketNode *thread = null; int bucket = (int)threadId & 255; if (threadHashTableHead != null) { // No need for locking yet - read operations on hash table are thread safe as long as we are careful about // modification during write and only allow one thread to write at a time. if (ThreadBucketNode.SearchFor(&threadHashTableHead->ThreadsBuffer[bucket], threadId, ref thread)) { return; } } // The thread info didn't exist in hash table. Need to lock so only one thread can modify at a time. // This path will usually only be taken during startup when creating threads - after which thread info // should be found in the above loop instead of needing to be created. PlayerConnectionMt_LockProfilerHashTables(); if (thread == null) { if (threadHashTableHead == null) { threadHashTableHead = FastHashTableBufferNode.Allocate(sizeof(ThreadBucketNode)); threadHashTableTail = threadHashTableHead; } thread = &threadHashTableHead->ThreadsBuffer[bucket]; } // In case this bucket was added to while another thread had the lock, the end-of-bucket // pointer needs to be increased. Also, it's possible the same exact name appears now. if (ThreadBucketNode.SearchFor(thread, threadId, ref thread)) { PlayerConnectionMt_UnlockProfilerHashTables(); return; } ThreadBucketNode *oldThread = null; if (thread->nameBytes > 0 || thread->groupBytes > 0) { // There is already a valid thread here at the end of the linked list - add a new one threadHashTableTail = threadHashTableTail->ExtendPoolIfFull(sizeof(ThreadBucketNode)); ThreadBucketNode *newThread = &threadHashTableTail->ThreadsBuffer[threadHashTableTail->size]; threadHashTableTail->size++; oldThread = thread; thread = newThread; } thread->init = false; Assert.IsTrue(thread->nameBytes <= k_MaxThreadNameLength); Assert.IsTrue(thread->groupBytes <= k_MaxThreadNameLength); fixed(char *c = threadGroup) thread->groupBytes = UTF8.GetBytes(c, threadGroup.Length, thread->groupUtf8, k_MaxThreadNameLength); fixed(char *c = threadName) thread->nameBytes = UTF8.GetBytes(c, threadName.Length, thread->nameUtf8, k_MaxThreadNameLength); thread->frameIndependent = frameIndependent; thread->sysTicksStart = sysTicksStart; thread->threadId = threadId; // Do this last so if we find the node before locking, we don't have access to it unless it is otherwise fully assigned if (oldThread != null) { oldThread->next = thread; } PlayerConnectionMt_UnlockProfilerHashTables(); NeedsUpdate = true; }