/* Copyright (c) 2001, Stanford University * All rights reserved * * See the file LICENSE.txt for information on redistributing this software. */ #include "cr_threads.h" #include "cr_hash.h" #include "cr_mem.h" #include "cr_error.h" #include #define CR_MAXUINT ((GLuint) 0xFFFFFFFF) #define CR_HASH_ID_MIN ((GLuint)1) #define CR_HASH_ID_MAX CR_MAXUINT #define CR_NUM_BUCKETS 1047 typedef struct FreeElemRec { RTLISTNODE Node; GLuint min; GLuint max; } FreeElem; typedef struct CRHashIdPoolRec { RTLISTNODE freeList; } CRHashIdPool; typedef struct CRHashNode { unsigned long key; void *data; struct CRHashNode *next; } CRHashNode; struct CRHashTable { unsigned int num_elements; CRHashNode *buckets[CR_NUM_BUCKETS]; CRHashIdPool *idPool; #ifdef CHROMIUM_THREADSAFE CRmutex mutex; #endif }; static CRHashIdPool *crAllocHashIdPool( void ) { CRHashIdPool *pool = (CRHashIdPool *) crCalloc(sizeof(CRHashIdPool)); FreeElem *elem = (FreeElem *) crCalloc(sizeof(FreeElem)); RTListInit(&pool->freeList); elem->min = CR_HASH_ID_MIN; elem->max = CR_HASH_ID_MAX; RTListAppend(&pool->freeList, &elem->Node); return pool; } static void crFreeHashIdPool( CRHashIdPool *pool ) { FreeElem *i, *next; RTListForEachSafe(&pool->freeList, i, next, FreeElem, Node) { crFree(i); } crFree(pool); } static GLboolean crHashIdPoolIsIdFree( const CRHashIdPool *pool, GLuint id ); #ifdef DEBUG_misha static void crHashIdPoolDbgCheckConsistency(CRHashIdPool *pool) { FreeElem *i; GLuint min = 0; /* null is a special case, it is always treated as allocated */ Assert(!crHashIdPoolIsIdFree(pool, 0)); /* first ensure entries have correct values */ RTListForEach(&pool->freeList, i, FreeElem, Node) { Assert(i->min >= CR_HASH_ID_MIN); Assert(i->max <= CR_HASH_ID_MAX); Assert(i->min < i->max); } /* now ensure entries do not intersect */ /* and that they are sorted */ RTListForEach(&pool->freeList, i, FreeElem, Node) { Assert(min < i->min); min = i->max; } } static void crHashIdPoolDbgCheckUsed( const CRHashIdPool *pool, GLuint start, GLuint count, GLboolean fUsed ) { GLuint i; CRASSERT(count); CRASSERT(start >= CR_HASH_ID_MIN); CRASSERT(start + count <= CR_HASH_ID_MAX); CRASSERT(start + count > start); for (i = 0; i < count; ++i) { Assert(!fUsed == !!crHashIdPoolIsIdFree( pool, start + i )); } } # define CR_HASH_IDPOOL_DBG_CHECK_USED(_p, _start, _count, _used) do { \ crHashIdPoolDbgCheckConsistency((_p)); \ crHashIdPoolDbgCheckUsed( (_p), (_start), (_count), (_used) ); \ } while (0) # define CR_HASH_IDPOOL_DBG_CHECK_CONSISTENCY(_p) do { crHashIdPoolDbgCheckConsistency((_p)); } while (0) #else # define CR_HASH_IDPOOL_DBG_CHECK_USED(_p, _start, _count, _used) do { } while (0) # define CR_HASH_IDPOOL_DBG_CHECK_CONSISTENCY(_p) do { } while (0) #endif /* * Allocate a block of IDs. Return index of first one. * Return 0 if we fail. */ static GLuint crHashIdPoolAllocBlock( CRHashIdPool *pool, GLuint count ) { FreeElem *f, *next; GLuint ret; CRASSERT(count > 0); RTListForEachSafe(&pool->freeList, f, next, FreeElem, Node) { Assert(f->max > f->min); if (f->max - f->min >= (GLuint) count) { /* found a sufficiently large enough block */ ret = f->min; f->min += count; if (f->min == f->max) { RTListNodeRemove(&f->Node); crFree(f); } CR_HASH_IDPOOL_DBG_CHECK_USED(pool, ret, count, GL_TRUE); return ret; } } /* failed to find free block */ crWarning("crHashIdPoolAllocBlock failed"); CR_HASH_IDPOOL_DBG_CHECK_CONSISTENCY(pool); return 0; } /* * Free a block of IDs starting at . */ static void crHashIdPoolFreeBlock( CRHashIdPool *pool, GLuint first, GLuint count ) { FreeElem *f; GLuint last; GLuint newMax; FreeElem *cur, *curNext; /* null is a special case, it is always treated as allocated */ if (!first) { Assert(!crHashIdPoolIsIdFree(pool, 0)); ++first; --count; if (!count) return; } last = first + count; CRASSERT(count > 0); CRASSERT(last > first); CRASSERT(first >= CR_HASH_ID_MIN); CRASSERT(last <= CR_HASH_ID_MAX); /* the id list is sorted, first find a place to insert */ RTListForEach(&pool->freeList, f, FreeElem, Node) { Assert(f->max > f->min); if (f->max < first) continue; if (f->min > last) { /* we are here because first is > than prevEntry->max * otherwise the previous loop iterations should handle that */ FreeElem *elem = (FreeElem *) crCalloc(sizeof(FreeElem)); elem->min = first; elem->max = last; RTListNodeInsertBefore(&f->Node, &elem->Node); CR_HASH_IDPOOL_DBG_CHECK_USED(pool, first, count, GL_FALSE); return; } /* now we have f->min <= last and f->max >= first, * so we have either intersection */ if (f->min > first) f->min = first; /* first is guarantied not to touch any prev regions */ newMax = last; if (f->max >= last) { CR_HASH_IDPOOL_DBG_CHECK_USED(pool, first, count, GL_FALSE); return; } for (cur = RTListNodeGetNext(&f->Node, FreeElem, Node), curNext = RT_FROM_MEMBER(cur->Node.pNext, FreeElem, Node); !RTListNodeIsDummy(&pool->freeList, cur, FreeElem, Node); cur = curNext, curNext = RT_FROM_MEMBER((cur)->Node.pNext, FreeElem, Node) ) { if (cur->min > last) break; newMax = cur->max; RTListNodeRemove(&cur->Node); crFree(cur); if (newMax >= last) break; } f->max = newMax; CR_HASH_IDPOOL_DBG_CHECK_USED(pool, first, count, GL_FALSE); return; } /* we are here because either the list is empty or because all list rande elements have smaller values */ f = (FreeElem *) crCalloc(sizeof(FreeElem)); f->min = first; f->max = last; RTListAppend(&pool->freeList, &f->Node); CR_HASH_IDPOOL_DBG_CHECK_USED(pool, first, count, GL_FALSE); } /* * Mark the given Id as being allocated. */ static GLboolean crHashIdPoolAllocId( CRHashIdPool *pool, GLuint id ) { FreeElem *f, *next; if (!id) { /* null is a special case, it is always treated as allocated */ Assert(!crHashIdPoolIsIdFree(pool, 0)); return GL_FALSE; } // Assert(id != 2); RTListForEachSafe(&pool->freeList, f, next, FreeElem, Node) { if (f->max <= id) continue; if (f->min > id) { CR_HASH_IDPOOL_DBG_CHECK_USED(pool, id, 1, GL_TRUE); return GL_FALSE; } /* f->min <= id && f->max > id */ if (id > f->min) { if (id + 1 < f->max) { FreeElem *elem = (FreeElem *) crCalloc(sizeof(FreeElem)); elem->min = id + 1; elem->max = f->max; RTListNodeInsertAfter(&f->Node, &elem->Node); } f->max = id; CR_HASH_IDPOOL_DBG_CHECK_USED(pool, id, 1, GL_TRUE); } else { Assert(id == f->min); if (id + 1 < f->max) { f->min = id + 1; CR_HASH_IDPOOL_DBG_CHECK_USED(pool, id, 1, GL_TRUE); } else { RTListNodeRemove(&f->Node); crFree(f); CR_HASH_IDPOOL_DBG_CHECK_USED(pool, id, 1, GL_TRUE); } } CR_HASH_IDPOOL_DBG_CHECK_USED(pool, id, 1, GL_TRUE); return GL_TRUE; } /* if we get here, the ID was already allocated - that's OK */ CR_HASH_IDPOOL_DBG_CHECK_USED(pool, id, 1, GL_TRUE); return GL_FALSE; } /* * Determine if the given id is free. Return GL_TRUE if so. */ static GLboolean crHashIdPoolIsIdFree( const CRHashIdPool *pool, GLuint id ) { FreeElem *f; CRASSERT(id >= 0); CRASSERT(id <= CR_HASH_ID_MAX); RTListForEach(&pool->freeList, f, FreeElem, Node) { if (f->max <= id) continue; if (f->min > id) return GL_FALSE; return GL_TRUE; } return GL_FALSE; } CRHashTable *crAllocHashtable( void ) { int i; CRHashTable *hash = (CRHashTable *) crCalloc( sizeof( CRHashTable )) ; hash->num_elements = 0; for (i = 0 ; i < CR_NUM_BUCKETS ; i++) { hash->buckets[i] = NULL; } hash->idPool = crAllocHashIdPool(); #ifdef CHROMIUM_THREADSAFE crInitMutex(&hash->mutex); #endif return hash; } void crFreeHashtable( CRHashTable *hash, CRHashtableCallback deleteFunc ) { int i; CRHashNode *entry, *next; if ( !hash) return; #ifdef CHROMIUM_THREADSAFE crLockMutex(&hash->mutex); #endif for ( i = 0; i < CR_NUM_BUCKETS; i++ ) { entry = hash->buckets[i]; while (entry) { next = entry->next; /* Clear the key in case crHashtableDelete() is called * from this callback. */ entry->key = 0; if (deleteFunc && entry->data) { (*deleteFunc)(entry->data); } crFree(entry); entry = next; } } crFreeHashIdPool( hash->idPool ); #ifdef CHROMIUM_THREADSAFE crUnlockMutex(&hash->mutex); crFreeMutex(&hash->mutex); #endif crFree( hash ); } void crHashtableLock(CRHashTable *h) { #ifdef CHROMIUM_THREADSAFE crLockMutex(&h->mutex); #endif } void crHashtableUnlock(CRHashTable *h) { #ifdef CHROMIUM_THREADSAFE crUnlockMutex(&h->mutex); #endif } void crHashtableWalk( CRHashTable *hash, CRHashtableWalkCallback walkFunc , void *dataPtr2) { int i; CRHashNode *entry, *next; if (!hash) return; #ifdef CHROMIUM_THREADSAFE crLockMutex(&hash->mutex); #endif for (i = 0; i < CR_NUM_BUCKETS; i++) { entry = hash->buckets[i]; while (entry) { /* save next ptr here, in case walkFunc deletes this entry */ next = entry->next; if (entry->data && walkFunc) { (*walkFunc)( entry->key, entry->data, dataPtr2 ); } entry = next; } } #ifdef CHROMIUM_THREADSAFE crUnlockMutex(&hash->mutex); #endif } static unsigned int crHash( unsigned long key ) { return key % CR_NUM_BUCKETS; } void crHashtableAdd( CRHashTable *h, unsigned long key, void *data ) { CRHashNode *node = (CRHashNode *) crCalloc( sizeof( CRHashNode ) ); #ifdef CHROMIUM_THREADSAFE crLockMutex(&h->mutex); #endif node->key = key; node->data = data; node->next = h->buckets[crHash( key )]; h->buckets[ crHash( key ) ] = node; h->num_elements++; crHashIdPoolAllocId (h->idPool, key); #ifdef CHROMIUM_THREADSAFE crUnlockMutex(&h->mutex); #endif } GLboolean crHashtableAllocRegisterKey( CRHashTable *h, GLuint key) { GLboolean fAllocated; #ifdef CHROMIUM_THREADSAFE crLockMutex(&h->mutex); #endif fAllocated = crHashIdPoolAllocId (h->idPool, key); #ifdef CHROMIUM_THREADSAFE crUnlockMutex(&h->mutex); #endif return fAllocated; } GLuint crHashtableAllocKeys( CRHashTable *h, GLsizei range) { GLuint res; int i; #ifdef CHROMIUM_THREADSAFE crLockMutex(&h->mutex); #endif res = crHashIdPoolAllocBlock (h->idPool, range); #ifdef DEBUG_misha Assert(res); for (i = 0; i < range; ++i) { void *search = crHashtableSearch( h, res+i ); Assert(!search); } #endif #ifdef CHROMIUM_THREADSAFE crUnlockMutex(&h->mutex); #endif return res; } void crHashtableDelete( CRHashTable *h, unsigned long key, CRHashtableCallback deleteFunc ) { unsigned int index = crHash( key ); CRHashNode *temp, *beftemp = NULL; #ifdef CHROMIUM_THREADSAFE crLockMutex(&h->mutex); #endif for ( temp = h->buckets[index]; temp; temp = temp->next ) { if ( temp->key == key ) break; beftemp = temp; } if ( !temp ) { #ifdef CHROMIUM_THREADSAFE crUnlockMutex(&h->mutex); #endif return; /* not an error */ } if ( beftemp ) beftemp->next = temp->next; else h->buckets[index] = temp->next; h->num_elements--; if (temp->data && deleteFunc) { (*deleteFunc)( temp->data ); } crFree( temp ); crHashIdPoolFreeBlock( h->idPool, key, 1 ); #ifdef CHROMIUM_THREADSAFE crUnlockMutex(&h->mutex); #endif } void crHashtableDeleteBlock( CRHashTable *h, unsigned long key, GLsizei range, CRHashtableCallback deleteFunc ) { /* XXX optimize someday */ GLuint i; for (i = 0; i < (GLuint)range; i++) { crHashtableDelete( h, key, deleteFunc ); } } void *crHashtableSearch( const CRHashTable *h, unsigned long key ) { unsigned int index = crHash( key ); CRHashNode *temp; #ifdef CHROMIUM_THREADSAFE crLockMutex((CRmutex *)&h->mutex); #endif for ( temp = h->buckets[index]; temp; temp = temp->next ) { if ( temp->key == key ) break; } #ifdef CHROMIUM_THREADSAFE crUnlockMutex((CRmutex *)&h->mutex); #endif if ( !temp ) { return NULL; } return temp->data; } void crHashtableReplace( CRHashTable *h, unsigned long key, void *data, CRHashtableCallback deleteFunc) { unsigned int index = crHash( key ); CRHashNode *temp; #ifdef CHROMIUM_THREADSAFE crLockMutex(&h->mutex); #endif for ( temp = h->buckets[index]; temp; temp = temp->next ) { if ( temp->key == key ) break; } #ifdef CHROMIUM_THREADSAFE crUnlockMutex(&h->mutex); #endif if ( !temp ) { crHashtableAdd( h, key, data ); return; } #ifdef CHROMIUM_THREADSAFE crLockMutex(&h->mutex); #endif if ( temp->data && deleteFunc ) { (*deleteFunc)( temp->data ); } temp->data = data; #ifdef CHROMIUM_THREADSAFE crUnlockMutex(&h->mutex); #endif } unsigned int crHashtableNumElements( const CRHashTable *h) { if (h) return h->num_elements; else return 0; } /* * Determine if the given key is used. Return GL_TRUE if so. */ GLboolean crHashtableIsKeyUsed( const CRHashTable *h, GLuint id ) { return (GLboolean) !crHashIdPoolIsIdFree( h->idPool, id); } GLboolean crHashtableGetDataKey(CRHashTable *pHash, void *pData, unsigned long *pKey) { int i; CRHashNode *entry; GLboolean rc = GL_FALSE; if (!pHash) return rc; #ifdef CHROMIUM_THREADSAFE crLockMutex(&pHash->mutex); #endif for (i = 0; ibuckets[i]; while (entry) { if (entry->data == pData) { if (pKey) *pKey = entry->key; rc = GL_TRUE; break; } entry = entry->next; } } #ifdef CHROMIUM_THREADSAFE crUnlockMutex(&pHash->mutex); #endif return rc; }