// This code is in the public domain -- castanyo@yahoo.es #ifndef NV_CORE_CONTAINER_H #define NV_CORE_CONTAINER_H /* These containers are based on Thatcher Ulrich containers, donated to the Public Domain. I've also borrowed some ideas from the Qt toolkit, specially the cool foreach iterator. TODO Do not use memmove in insert & remove, use copy ctors instead. */ // nvcore #include "nvcore.h" #include "Memory.h" #include "Debug.h" #include // memmove #include // for placement new #if NV_CC_GNUC // If typeof is available: #define NV_FOREACH(i, container) \ typedef typeof(container) NV_STRING_JOIN2(cont,__LINE__); \ for(NV_STRING_JOIN2(cont,__LINE__)::PseudoIndex i((container).start()); !(container).isDone(i); (container).advance(i)) /* #define NV_FOREACH(i, container) \ for(typename typeof(container)::PseudoIndex i((container).start()); !(container).isDone(i); (container).advance(i)) */ #else // If typeof not available: struct PseudoIndexWrapper { template PseudoIndexWrapper(const T & container) { nvStaticCheck(sizeof(typename T::PseudoIndex) <= sizeof(memory)); new (memory) typename T::PseudoIndex(container.start()); } // PseudoIndex cannot have a dtor! template typename T::PseudoIndex & operator()(const T * container) { return *reinterpret_cast(memory); } template const typename T::PseudoIndex & operator()(const T * container) const { return *reinterpret_cast(memory); } uint8 memory[4]; // Increase the size if we have bigger enumerators. }; #define NV_FOREACH(i, container) \ for(PseudoIndexWrapper i(container); !(container).isDone(i(&(container))); (container).advance(i(&(container)))) #endif // Declare foreach keyword. #if !defined NV_NO_USE_KEYWORDS # define foreach NV_FOREACH #endif namespace nv { // Templates /// Return the maximum of two values. template inline const T & max(const T & a, const T & b) { //return std::max(a, b); if( a < b ) { return b; } return a; } /// Return the minimum of two values. template inline const T & min(const T & a, const T & b) { //return std::min(a, b); if( b < a ) { return b; } return a; } /// Clamp between two values. template inline const T & clamp(const T & x, const T & a, const T & b) { return min(max(x, a), b); } /// Swap two values. template inline void swap(T & a, T & b) { //return std::swap(a, b); T temp = a; a = b; b = temp; } template struct hash { inline uint sdbm_hash(const void * data_in, uint size, uint h = 5381) { const uint8 * data = (const uint8 *) data_in; uint i = 0; while (i < size) { h = (h << 16) + (h << 6) - h + (uint) data[i++]; } return h; } uint operator()(const Key & k) { return sdbm_hash(&k, sizeof(Key)); } }; template <> struct hash { uint operator()(int x) const { return x; } }; template <> struct hash { uint operator()(uint x) const { return x; } }; /// Delete all the elements of a container. template void deleteAll(T & container) { for(typename T::PseudoIndex i = container.start(); !container.isDone(i); container.advance(i)) { delete container[i]; } } /** Return the next power of two. * @see http://graphics.stanford.edu/~seander/bithacks.html * @warning Behaviour for 0 is undefined. * @note isPowerOfTwo(x) == true -> nextPowerOfTwo(x) == x * @note nextPowerOfTwo(x) = 2 << log2(x-1) */ inline uint nextPowerOfTwo( uint x ) { nvDebugCheck( x != 0 ); #if 1 // On modern CPUs this is as fast as using the bsr instruction. x--; x |= x >> 1; x |= x >> 2; x |= x >> 4; x |= x >> 8; x |= x >> 16; return x+1; #else uint p = 1; while( x > p ) { p += p; } return p; #endif } /// Return true if @a n is a power of two. inline bool isPowerOfTwo( uint n ) { return (n & (n-1)) == 0; } /// Simple iterator interface. template struct Iterator { virtual void advance(); virtual bool isDone(); virtual T current(); }; /** * Replacement for std::vector that is easier to debug and provides * some nice foreach enumerators. */ template class NVCORE_CLASS Array { public: /// Ctor. Array() : m_buffer(NULL), m_size(0), m_buffer_size(0) { } /// Copy ctor. Array( const Array & a ) : m_buffer(NULL), m_size(0), m_buffer_size(0) { copy(a.m_buffer, a.m_size); } /// Ctor that initializes the vector with the given elements. Array( const T * ptr, int num ) : m_buffer(NULL), m_size(0), m_buffer_size(0) { copy(ptr, num); } /// Allocate array. explicit Array(uint capacity) : m_buffer(NULL), m_size(0), m_buffer_size(0) { allocate(capacity); } /// Dtor. ~Array() { clear(); allocate(0); } /// Const and save vector access. const T & operator[]( uint index ) const { nvDebugCheck(index < m_size); return m_buffer[index]; } /// Safe vector access. T & operator[] ( uint index ) { nvDebugCheck(index < m_size); return m_buffer[index]; } /// Get vector size. uint size() const { return m_size; } /// Get vector size. uint count() const { return m_size; } /// Get const vector pointer. const T * buffer() const { return m_buffer; } /// Get vector pointer. T * unsecureBuffer() { return m_buffer; } /// Is vector empty. bool isEmpty() const { return m_size == 0; } /// Is a null vector. bool isNull() const { return m_buffer == NULL; } /// Push an element at the end of the vector. void push_back( const T & val ) { uint new_size = m_size + 1; if (new_size > m_buffer_size) { const T copy(val); // create a copy in case value is inside of this array. resize(new_size); m_buffer[new_size-1] = copy; } else { m_size = new_size; new(m_buffer+new_size-1) T(val); } } void pushBack( const T & val ) { push_back(val); } void append( const T & val ) { push_back(val); } /// Qt like push operator. Array & operator<< ( T & t ) { push_back(t); return *this; } /// Pop and return element at the end of the vector. void pop_back() { nvDebugCheck( m_size > 0 ); resize( m_size - 1 ); } void popBack() { pop_back(); } /// Get back element. const T & back() const { nvDebugCheck( m_size > 0 ); return m_buffer[m_size-1]; } /// Get back element. T & back() { nvDebugCheck( m_size > 0 ); return m_buffer[m_size-1]; } /// Get front element. const T & front() const { nvDebugCheck( m_size > 0 ); return m_buffer[0]; } /// Get front element. T & front() { nvDebugCheck( m_size > 0 ); return m_buffer[0]; } /// Check if the given element is contained in the array. bool contains(const T & e) const { for (uint i = 0; i < m_size; i++) { if (m_buffer[i] == e) return true; } return false; } /// Remove the element at the given index. This is an expensive operation! void removeAt( uint index ) { nvCheck(index >= 0 && index < m_size); if( m_size == 1 ) { clear(); } else { m_buffer[index].~T(); memmove( m_buffer+index, m_buffer+index+1, sizeof(T) * (m_size - 1 - index) ); m_size--; } } /// Remove the first instance of the given element. void remove(const T & element) { for(PseudoIndex i = start(); !isDone(i); advance(i)) { removeAt(i); break; } } /// Insert the given element at the given index shifting all the elements up. void insertAt( uint index, const T & val = T() ) { nvCheck( index >= 0 && index <= m_size ); resize( m_size + 1 ); if( index < m_size - 1 ) { memmove( m_buffer+index+1, m_buffer+index, sizeof(T) * (m_size - 1 - index) ); } // Copy-construct into the newly opened slot. new(m_buffer+index) T(val); } /// Append the given data to our vector. void append(const Array & other) { append(other.m_buffer, other.m_size); } /// Append the given data to our vector. void append(const T other[], uint count) { if( count > 0 ) { const uint old_size = m_size; resize(m_size + count); // Must use operator=() to copy elements, in case of side effects (e.g. ref-counting). for( uint i = 0; i < count; i++ ) { m_buffer[old_size + i] = other[i]; } } } /// Remove the given element by replacing it with the last one. void replaceWithLast(uint index) { nvDebugCheck( index < m_size ); m_buffer[index] = back(); (m_buffer+m_size-1)->~T(); m_size--; } /// Resize the vector preserving existing elements. void resize(uint new_size) { uint i; uint old_size = m_size; m_size = new_size; // Destruct old elements (if we're shrinking). for( i = new_size; i < old_size; i++ ) { (m_buffer+i)->~T(); // Explicit call to the destructor } if( m_size == 0 ) { //Allocate(0); // Don't shrink automatically. } else if( m_size <= m_buffer_size/* && m_size > m_buffer_size >> 1*/) { // don't compact yet. nvDebugCheck(m_buffer != NULL); } else { uint new_buffer_size; if( m_buffer_size == 0 ) { // first allocation new_buffer_size = m_size; } else { // growing new_buffer_size = m_size + (m_size >> 2); } allocate( new_buffer_size ); } // Call default constructors for( i = old_size; i < new_size; i++ ) { new(m_buffer+i) T; // placement new } } /// Resize the vector preserving existing elements and initializing the /// new ones with the given value. void resize( uint new_size, const T &elem ) { uint i; uint old_size = m_size; m_size = new_size; // Destruct old elements (if we're shrinking). for( i = new_size; i < old_size; i++ ) { (m_buffer+i)->~T(); // Explicit call to the destructor } if( m_size == 0 ) { //Allocate(0); // Don't shrink automatically. } else if( m_size <= m_buffer_size && m_size > m_buffer_size >> 1 ) { // don't compact yet. } else { uint new_buffer_size; if( m_buffer_size == 0 ) { // first allocation new_buffer_size = m_size; } else { // growing new_buffer_size = m_size + (m_size >> 2); } allocate( new_buffer_size ); } // Call copy constructors for( i = old_size; i < new_size; i++ ) { new(m_buffer+i) T( elem ); // placement new } } /// Tighten the memory used by the container. void tighten() { // TODO Reallocate only if worth. } /// Clear the buffer. void clear() { resize(0); } /// Shrink the allocated vector. void shrink() { if( m_size < m_buffer_size ) { allocate(m_size); } } /// Preallocate space. void reserve(uint desired_size) { if( desired_size > m_buffer_size ) { allocate( desired_size ); } } /// Copy memory to our vector. Resizes the vector if needed. void copy( const T * ptr, uint num ) { resize( num ); for(uint i = 0; i < m_size; i++) { m_buffer[i] = ptr[i]; } } /// Assignment operator. void operator=( const Array & a ) { copy( a.m_buffer, a.m_size ); } /* /// Array serialization. friend Stream & operator<< ( Stream & s, Array & p ) { if( s.isLoading() ) { uint size; s << size; p.resize( size ); } else { s << p.m_size; } for( uint i = 0; i < p.m_size; i++ ) { s << p.m_buffer[i]; } return s; } */ // Array enumerator. typedef uint PseudoIndex; PseudoIndex start() const { return 0; } bool isDone(const PseudoIndex & i) const { nvDebugCheck(i <= this->m_size); return i == this->m_size; }; void advance(PseudoIndex & i) const { nvDebugCheck(i <= this->m_size); i++; } #if NV_CC_MSVC T & operator[]( const PseudoIndexWrapper & i ) { return m_buffer[i(this)]; } const T & operator[]( const PseudoIndexWrapper & i ) const { return m_buffer[i(this)]; } #endif /// Swap the members of this vector and the given vector. friend void swap(Array & a, Array & b) { swap(a.m_buffer, b.m_buffer); swap(a.m_size, b.m_size); swap(a.m_buffer_size, b.m_buffer_size); } private: /// Change buffer size. void allocate( uint rsize ) { m_buffer_size = rsize; // free the buffer. if( m_buffer_size == 0 ) { if( m_buffer ) { free( m_buffer ); m_buffer = NULL; } } // realloc the buffer else { if( m_buffer ) m_buffer = (T *) realloc(m_buffer, sizeof(T) * m_buffer_size); else m_buffer = (T *) ::malloc(sizeof(T) * m_buffer_size); } } private: T * m_buffer; uint m_size; uint m_buffer_size; }; /** Thatcher Ulrich's hash table. * * Hash table, linear probing, internal chaining. One * interesting/nice thing about this implementation is that the table * itself is a flat chunk of memory containing no pointers, only * relative indices. If the key and value types of the hash contain * no pointers, then the hash can be serialized using raw IO. Could * come in handy. * * Never shrinks, unless you explicitly clear() it. Expands on * demand, though. For best results, if you know roughly how big your * table will be, default it to that size when you create it. */ template > class NVCORE_CLASS HashMap { NV_FORBID_COPY(HashMap) public: /// Default ctor. HashMap() : entry_count(0), size_mask(-1), table(NULL) { } /// Ctor with size hint. explicit HashMap(int size_hint) : entry_count(0), size_mask(-1), table(NULL) { setCapacity(size_hint); } /// Dtor. ~HashMap() { clear(); } /// Set a new or existing value under the key, to the value. void set(const T& key, const U& value) { int index = findIndex(key); if (index >= 0) { E(index).value = value; return; } // Entry under key doesn't exist. add(key, value); } /// Add a new value to the hash table, under the specified key. void add(const T& key, const U& value) { nvCheck(findIndex(key) == -1); checkExpand(); nvCheck(table != NULL); entry_count++; const uint hash_value = hash_functor()(key); const int index = hash_value & size_mask; Entry * natural_entry = &(E(index)); if (natural_entry->isEmpty()) { // Put the new entry in. new (natural_entry) Entry(key, value, -1, hash_value); } else { // Find a blank spot. int blank_index = index; for (;;) { blank_index = (blank_index + 1) & size_mask; if (E(blank_index).isEmpty()) break; // found it } Entry * blank_entry = &E(blank_index); if (int(natural_entry->hash_value & size_mask) == index) { // Collision. Link into this chain. // Move existing list head. new (blank_entry) Entry(*natural_entry); // placement new, copy ctor // Put the new info in the natural entry. natural_entry->key = key; natural_entry->value = value; natural_entry->next_in_chain = blank_index; natural_entry->hash_value = hash_value; } else { // Existing entry does not naturally // belong in this slot. Existing // entry must be moved. // Find natural location of collided element (i.e. root of chain) int collided_index = natural_entry->hash_value & size_mask; for (;;) { Entry * e = &E(collided_index); if (e->next_in_chain == index) { // Here's where we need to splice. new (blank_entry) Entry(*natural_entry); e->next_in_chain = blank_index; break; } collided_index = e->next_in_chain; nvCheck(collided_index >= 0 && collided_index <= size_mask); } // Put the new data in the natural entry. natural_entry->key = key; natural_entry->value = value; natural_entry->hash_value = hash_value; natural_entry->next_in_chain = -1; } } } /// Remove the first value under the specified key. bool remove(const T& key) { if (table == NULL) { return false; } int index = findIndex(key); if (index < 0) { return false; } Entry * entry = &E(index); if( entry->isEndOfChain() ) { entry->clear(); } else { // Get next entry. Entry & next_entry = E(entry->next_in_chain); // Copy next entry in this place. new (entry) Entry(next_entry); next_entry.clear(); } entry_count--; return true; } /// Remove all entries from the hash table. void clear() { if (table != NULL) { // Delete the entries. for (int i = 0, n = size_mask; i <= n; i++) { Entry * e = &E(i); if (e->isEmpty() == false) { e->clear(); } } free(table); table = NULL; entry_count = 0; size_mask = -1; } } /// Returns true if the hash is empty. bool isEmpty() const { return table == NULL || entry_count == 0; } /** Retrieve the value under the given key. * * If there's no value under the key, then return false and leave * *value alone. * * If there is a value, return true, and set *value to the entry's * value. * * If value == NULL, return true or false according to the * presence of the key, but don't touch *value. */ bool get(const T& key, U* value = NULL) const { int index = findIndex(key); if (index >= 0) { if (value) { *value = E(index).value; // take care with side-effects! } return true; } return false; } /// Determine if the given key is contained in the hash. bool contains(const T & key) const { return get(key); } /// Number of entries in the hash. int size() const { return entry_count; } /// Number of entries in the hash. int count() const { return size(); } /** * Resize the hash table to fit one more entry. Often this * doesn't involve any action. */ void checkExpand() { if (table == NULL) { // Initial creation of table. Make a minimum-sized table. setRawCapacity(16); } else if (entry_count * 3 > (size_mask + 1) * 2) { // Table is more than 2/3rds full. Expand. setRawCapacity(entry_count * 2); } } /// Hint the bucket count to >= n. void resize(int n) { // Not really sure what this means in relation to // STLport's hash_map... they say they "increase the // bucket count to at least n" -- but does that mean // their real capacity after resize(n) is more like // n*2 (since they do linked-list chaining within // buckets?). setCapacity(n); } /** * Size the hash so that it can comfortably contain the given * number of elements. If the hash already contains more * elements than new_size, then this may be a no-op. */ void setCapacity(int new_size) { int new_raw_size = (new_size * 3) / 2; if (new_raw_size < size()) { return; } setRawCapacity(new_raw_size); } /// Behaves much like std::pair. struct Entry { int next_in_chain; // internal chaining for collisions uint hash_value; // avoids recomputing. Worthwhile? T key; U value; Entry() : next_in_chain(-2) {} Entry(const Entry& e) : next_in_chain(e.next_in_chain), hash_value(e.hash_value), key(e.key), value(e.value) { } Entry(const T& k, const U& v, int next, int hash) : next_in_chain(next), hash_value(hash), key(k), value(v) { } bool isEmpty() const { return next_in_chain == -2; } bool isEndOfChain() const { return next_in_chain == -1; } void clear() { key.~T(); // placement delete value.~U(); // placement delete next_in_chain = -2; } }; // HashMap enumerator. typedef int PseudoIndex; PseudoIndex start() const { PseudoIndex i = 0; findNext(i); return i; } bool isDone(const PseudoIndex & i) const { nvDebugCheck(i <= size_mask+1); return i == size_mask+1; }; void advance(PseudoIndex & i) const { nvDebugCheck(i <= size_mask+1); i++; findNext(i); } #if NV_CC_GNUC Entry & operator[]( const PseudoIndex & i ) { return E(i); } const Entry & operator[]( const PseudoIndex & i ) const { return E(i); } #elif NV_CC_MSVC Entry & operator[]( const PseudoIndexWrapper & i ) { return E(i(this)); } const Entry & operator[]( const PseudoIndexWrapper & i ) const { return E(i(this)); } #endif private: // Find the index of the matching entry. If no match, then return -1. int findIndex(const T& key) const { if (table == NULL) return -1; uint hash_value = hash_functor()(key); int index = hash_value & size_mask; const Entry * e = &E(index); if (e->isEmpty()) return -1; if (int(e->hash_value & size_mask) != index) return -1; // occupied by a collider for (;;) { nvCheck((e->hash_value & size_mask) == (hash_value & size_mask)); if (e->hash_value == hash_value && e->key == key) { // Found it. return index; } nvDebugCheck(! (e->key == key)); // keys are equal, but hash differs! // Keep looking through the chain. index = e->next_in_chain; if (index == -1) break; // end of chain nvCheck(index >= 0 && index <= size_mask); e = &E(index); nvCheck(e->isEmpty() == false); } return -1; } // Helpers. Entry & E(int index) { nvDebugCheck(table != NULL); nvDebugCheck(index >= 0 && index <= size_mask); return table[index]; } const Entry & E(int index) const { nvDebugCheck(table != NULL); nvDebugCheck(index >= 0 && index <= size_mask); return table[index]; } /** * Resize the hash table to the given size (Rehash the * contents of the current table). The arg is the number of * hash table entries, not the number of elements we should * actually contain (which will be less than this). */ void setRawCapacity(int new_size) { if (new_size <= 0) { // Special case. clear(); return; } // Force new_size to be a power of two. new_size = nextPowerOfTwo(new_size); HashMap new_hash; new_hash.table = (Entry *) ::malloc(sizeof(Entry) * new_size); nvDebugCheck(new_hash.table != NULL); new_hash.entry_count = 0; new_hash.size_mask = new_size - 1; for (int i = 0; i < new_size; i++) { new_hash.E(i).next_in_chain = -2; // mark empty } // Copy stuff to new_hash if (table != NULL) { for (int i = 0, n = size_mask; i <= n; i++) { Entry * e = &E(i); if (e->isEmpty() == false) { // Insert old entry into new hash. new_hash.add(e->key, e->value); e->clear(); // placement delete of old element } } // Delete our old data buffer. free(table); } // Steal new_hash's data. entry_count = new_hash.entry_count; size_mask = new_hash.size_mask; table = new_hash.table; new_hash.entry_count = 0; new_hash.size_mask = -1; new_hash.table = NULL; } // Move the enumerator to the next valid element. void findNext(PseudoIndex & i) const { while (i <= size_mask && E(i).isEmpty()) { i++; } } int entry_count; int size_mask; Entry * table; }; } // nv namespace #endif // NV_CORE_CONTAINER_H