/** * @file lllocalidhashmap.h * @brief Map specialized for dealing with local ids * * $LicenseInfo:firstyear=2003&license=viewergpl$ * * Copyright (c) 2003-2008, Linden Research, Inc. * * Second Life Viewer Source Code * The source code in this file ("Source Code") is provided by Linden Lab * to you under the terms of the GNU General Public License, version 2.0 * ("GPL"), unless you have obtained a separate licensing agreement * ("Other License"), formally executed by you and Linden Lab. Terms of * the GPL can be found in doc/GPL-license.txt in this distribution, or * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 * * There are special exceptions to the terms and conditions of the GPL as * it is applied to this Source Code. View the full text of the exception * in the file doc/FLOSS-exception.txt in this software distribution, or * online at http://secondlifegrid.net/programs/open_source/licensing/flossexception * * By copying, modifying or distributing this software, you acknowledge * that you have read and understood your obligations described above, * and agree to abide by those obligations. * * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, * COMPLETENESS OR PERFORMANCE. * $/LicenseInfo$ */ #ifndef LL_LLLOCALIDHASHMAP_H #define LL_LLLOCALIDHASHMAP_H #include "stdtypes.h" #include "llerror.h" const S32 MAX_ITERS = 4; // LocalID hash map // // LLLocalIDHashNode // template class LLLocalIDHashNode { public: LLLocalIDHashNode(); public: S32 mCount; U32 mKey[SIZE]; DATA mData[SIZE]; LLLocalIDHashNode *mNextNodep; }; // // LLLocalIDHashNode implementation // template LLLocalIDHashNode::LLLocalIDHashNode() { mCount = 0; mNextNodep = NULL; } // // LLLocalIDHashMapIter // template class LLLocalIDHashMap; template class LLLocalIDHashMapIter { public: LLLocalIDHashMapIter(LLLocalIDHashMap *hash_mapp); ~LLLocalIDHashMapIter(); void setMap(LLLocalIDHashMap *hash_mapp); inline void first(); inline void next(); inline DATA_TYPE& current(); // *NOTE: Deprecate? Phoenix 2005-04-15 inline BOOL done() const; inline S32 currentBin() const; inline void setBin(S32 bin); DATA_TYPE& operator*() const { return mCurHashNodep->mData[mCurHashNodeKey]; } DATA_TYPE* operator->() const { return &(operator*()); } LLLocalIDHashMap *mHashMapp; LLLocalIDHashNode *mCurHashNodep; S32 mCurHashMapNodeNum; S32 mCurHashNodeKey; DATA_TYPE mNull; S32 mIterID; }; template class LLLocalIDHashMap { public: friend class LLLocalIDHashMapIter; LLLocalIDHashMap(); // DO NOT use this unless you explicitly setNull, or the data type constructs a "null" // object by default // basic constructor including sorter LLLocalIDHashMap(const DATA_TYPE &null_data); // Hack, this should really be a const ref, but I'm not doing it that way because the sim // usually uses pointers. ~LLLocalIDHashMap(); inline DATA_TYPE &get(const U32 local_id); inline BOOL check(const U32 local_id) const; inline DATA_TYPE &set(const U32 local_id, const DATA_TYPE data); inline BOOL remove(const U32 local_id); void removeAll(); void setNull(const DATA_TYPE data) { mNull = data; } inline S32 getLength() const; // Warning, NOT O(1!) void dumpIter(); void dumpBin(U32 bin); protected: // Only used by the iterator. void addIter(LLLocalIDHashMapIter *iter); void removeIter(LLLocalIDHashMapIter *iter); // Remove the item and shift all items afterward down the list, // fixing up iterators as we go. BOOL removeWithShift(const U32 local_id); protected: LLLocalIDHashNode mNodes[256]; S32 mIterCount; LLLocalIDHashMapIter *mIters[MAX_ITERS]; DATA_TYPE mNull; }; // // LLLocalIDHashMap implementation // template LLLocalIDHashMap::LLLocalIDHashMap() : mIterCount(0), mNull() { S32 i; for (i = 0; i < MAX_ITERS; i++) { mIters[i] = NULL; } } template LLLocalIDHashMap::LLLocalIDHashMap(const DATA_TYPE &null_data) : mIterCount(0), mNull(null_data) { S32 i; for (i = 0; i < MAX_ITERS; i++) { mIters[i] = NULL; } } template LLLocalIDHashMap::~LLLocalIDHashMap() { S32 i; for (i = 0; i < MAX_ITERS; i++) { if (mIters[i]) { mIters[i]->mHashMapp = NULL; mIterCount--; } } removeAll(); } template void LLLocalIDHashMap::removeAll() { S32 bin; for (bin = 0; bin < 256; bin++) { LLLocalIDHashNode* nodep = &mNodes[bin]; BOOL first = TRUE; do // First node guaranteed to be there { S32 i; const S32 count = nodep->mCount; // Iterate through all members of this node for (i = 0; i < count; i++) { nodep->mData[i] = mNull; } nodep->mCount = 0; // Done with all objects in this node, go to the next. LLLocalIDHashNode* curp = nodep; nodep = nodep->mNextNodep; // Delete the node if it's not the first node if (first) { first = FALSE; curp->mNextNodep = NULL; } else { delete curp; } } while (nodep); } } template void LLLocalIDHashMap::dumpIter() { std::cout << "Hash map with " << mIterCount << " iterators" << std::endl; std::cout << "Hash Map Iterators:" << std::endl; S32 i; for (i = 0; i < MAX_ITERS; i++) { if (mIters[i]) { llinfos << i << " " << mIters[i]->mCurHashNodep << " " << mIters[i]->mCurHashNodeKey << llendl; } else { llinfos << i << "null" << llendl; } } } template void LLLocalIDHashMap::dumpBin(U32 bin) { std::cout << "Dump bin " << bin << std::endl; LLLocalIDHashNode* nodep = &mNodes[bin]; S32 node = 0; do // First node guaranteed to be there. { std::cout << "Bin " << bin << " node " << node << " count " << nodep->mCount << " contains " << std::flush; S32 i; for (i = 0; i < nodep->mCount; i++) { std::cout << nodep->mData[i] << " " << std::flush; } std::cout << std::endl; nodep = nodep->mNextNodep; node++; } while (nodep); } template inline S32 LLLocalIDHashMap::getLength() const { S32 count = 0; S32 bin; for (bin = 0; bin < 256; bin++) { const LLLocalIDHashNode* nodep = &mNodes[bin]; while (nodep) { count += nodep->mCount; nodep = nodep->mNextNodep; } } return count; } template inline DATA_TYPE &LLLocalIDHashMap::get(const U32 local_id) { LLLocalIDHashNode* nodep = &mNodes[local_id & 0xff]; do // First node guaranteed to be there { S32 i; const S32 count = nodep->mCount; // Iterate through all members of this node for (i = 0; i < count; i++) { if (nodep->mKey[i] == local_id) { // We found it. return nodep->mData[i]; } } // Done with all objects in this node, go to the next. nodep = nodep->mNextNodep; } while (nodep); return mNull; } template inline BOOL LLLocalIDHashMap::check(const U32 local_id) const { const LLLocalIDHashNode* nodep = &mNodes[local_id & 0xff]; do // First node guaranteed to be there { S32 i; const S32 count = nodep->mCount; // Iterate through all members of this node for (i = 0; i < count; i++) { if (nodep->mKey[i] == local_id) { // We found it. return TRUE; } } // Done with all objects in this node, go to the next. nodep = nodep->mNextNodep; } while (nodep); // Didn't find anything return FALSE; } template inline DATA_TYPE &LLLocalIDHashMap::set(const U32 local_id, const DATA_TYPE data) { // Set is just like a normal find, except that if we find a match // we replace it with the input value. // If we don't find a match, we append to the end of the list. LLLocalIDHashNode* nodep = &mNodes[local_id & 0xff]; while (1) { const S32 count = nodep->mCount; S32 i; for (i = 0; i < count; i++) { if (nodep->mKey[i] == local_id) { // We found a match for this key, replace the data with // the incoming data. nodep->mData[i] = data; return nodep->mData[i]; } } if (!nodep->mNextNodep) { // We've iterated through all of the keys without finding a match if (i < SIZE) { // There's still some space on this node, append // the key and data to it. nodep->mKey[i] = local_id; nodep->mData[i] = data; nodep->mCount++; return nodep->mData[i]; } else { // This node is full, append a new node to the end. nodep->mNextNodep = new LLLocalIDHashNode; nodep->mNextNodep->mKey[0] = local_id; nodep->mNextNodep->mData[0] = data; nodep->mNextNodep->mCount = 1; return nodep->mNextNodep->mData[0]; } } // No match on this node, go to the next nodep = nodep->mNextNodep; } } template inline BOOL LLLocalIDHashMap::remove(const U32 local_id) { // Remove is the trickiest operation. // What we want to do is swap the last element of the last // node if we find the one that we want to remove, but we have // to deal with deleting the node from the tail if it's empty, but // NOT if it's the only node left. const S32 node_index = local_id & 0xff; LLLocalIDHashNode* nodep = &mNodes[node_index]; // A modification of the standard search algorithm. do // First node guaranteed to be there { const S32 count = nodep->mCount; S32 i; for (i = 0; i < count; i++) { if (nodep->mKey[i] == local_id) { // If we're removing the item currently pointed to by one // or more iterators, we can just swap in the last item // and back the iterator(s) up by one. // Otherwise, we need to do a slow and safe shift of all // items back to one position to fill the hole and fix up // all iterators we find. BOOL need_shift = FALSE; S32 cur_iter; if (mIterCount) { for (cur_iter = 0; cur_iter < MAX_ITERS; cur_iter++) { if (mIters[cur_iter]) { // We only care if the hash map node is on the one // that we're working on. If it's before, we've already // traversed it, if it's after, changing the order doesn't // matter. if (mIters[cur_iter]->mCurHashMapNodeNum == node_index) { if ((mIters[cur_iter]->mCurHashNodep == nodep) && (mIters[cur_iter]->mCurHashNodeKey == i)) { // it's on the one we're deleting, we'll // fix the iterator quickly below. } else { // We're trying to remove an item on this // iterator's chain that this // iterator doesn't point to! We need to do // the slow remove-and-shift-down case. need_shift = TRUE; } } } } } // Removing an item that isn't pointed to by all iterators if (need_shift) { return removeWithShift(local_id); } // Fix the iterators that point to this node/i pair, the // one we're deleting for (cur_iter = 0; cur_iter < MAX_ITERS; cur_iter++) { if (mIters[cur_iter]) { // We only care if the hash map node is on the one // that we're working on. If it's before, we've already // traversed it, if it's after, changing the order doesn't // matter. if (mIters[cur_iter]->mCurHashMapNodeNum == node_index) { if ((mIters[cur_iter]->mCurHashNodep == nodep) && (mIters[cur_iter]->mCurHashNodeKey == i)) { // We can handle the case where we're deleting // the element we're on trivially (sort of). if (nodep->mCount > 1) { // If we're not going to delete this node, // it's OK. mIters[cur_iter]->mCurHashNodeKey--; } else { // We're going to delete this node, because this // is the last element on it. // Find the next node, and then back up one. mIters[cur_iter]->next(); mIters[cur_iter]->mCurHashNodeKey--; } } } } } // We found the node that we want to remove. // Find the last (and next-to-last) node, and the index of the last // element. We could conceviably start from the node we're on, // but that makes it more complicated, this is easier. LLLocalIDHashNode *prevp = &mNodes[node_index]; LLLocalIDHashNode *lastp = prevp; // Find the last and next-to-last while (lastp->mNextNodep) { prevp = lastp; lastp = lastp->mNextNodep; } // First, swap in the last to the current location. nodep->mKey[i] = lastp->mKey[lastp->mCount - 1]; nodep->mData[i] = lastp->mData[lastp->mCount - 1]; // Now, we delete the entry lastp->mCount--; lastp->mData[lastp->mCount] = mNull; if (!lastp->mCount) { // We deleted the last element! if (lastp != &mNodes[local_id & 0xff]) { // Only blitz the node if it's not the head // Set the previous node to point to NULL, then // blitz the empty last node prevp->mNextNodep = NULL; delete lastp; } } return TRUE; } } // Iterate to the next node, we've scanned all the entries in this one. nodep = nodep->mNextNodep; } while (nodep); return FALSE; } template BOOL LLLocalIDHashMap::removeWithShift(const U32 local_id) { const S32 node_index = local_id & 0xFF; LLLocalIDHashNode* nodep = &mNodes[node_index]; LLLocalIDHashNode* prevp = NULL; BOOL found = FALSE; do // First node guaranteed to be there { const S32 count = nodep->mCount; S32 i; for (i = 0; i < count; i++) { if (nodep->mKey[i] == local_id) { // Found the item. Start shifting items from later // in the list over this item. found = TRUE; } if (found) { // If there is an iterator on this node, we need to // back it up. S32 cur_iter; for (cur_iter = 0; cur_iter * iter; iter = mIters[cur_iter]; // If an iterator is on this node,i pair, then back it up. if (iter && iter->mCurHashMapNodeNum == node_index && iter->mCurHashNodep == nodep && iter->mCurHashNodeKey == i) { if (i > 0) { // Don't need to move iterator nodep, since // we're in the same node. iter->mCurHashNodeKey--; } else if (prevp) { // need to go the previous node, last item iter->mCurHashNodep = prevp; iter->mCurHashNodeKey = prevp->mCount - 1; } else { // we're on the first item in the list, but // need to go back anyhow. // BUG: If this deletion empties the list, // iter->done() will be wrong until // iter->next() is called. iter->mCurHashNodeKey = -1; } } } // Copy data from the next position into this position. if (i < count-1) { // we're not on the last item in the node, // so we can copy within the node nodep->mKey[i] = nodep->mKey[i+1]; nodep->mData[i] = nodep->mData[i+1]; } else if (nodep->mNextNodep) { // we're on the last item in the node, // but there's a next node we can copy from nodep->mKey[i] = nodep->mNextNodep->mKey[0]; nodep->mData[i] = nodep->mNextNodep->mData[0]; } else { // We're on the last position in the list. // No one to copy from. Replace with nothing. nodep->mKey[i] = 0; nodep->mData[i] = mNull; } } } // Last node in chain, so delete the last node if (found && !nodep->mNextNodep) { // delete the last item off the last node nodep->mCount--; if (nodep->mCount == 0) { // We deleted the last element! if (nodep != &mNodes[node_index]) { // Always have a prevp if we're not the head. llassert(prevp); // Only blitz the node if it's not the head // Set the previous node to point to NULL, then // blitz the empty last node prevp->mNextNodep = NULL; delete nodep; nodep = NULL; } } // Deleted last item in chain, so we're done. return found; } prevp = nodep; nodep = nodep->mNextNodep; } while (nodep); return found; } template void LLLocalIDHashMap::removeIter(LLLocalIDHashMapIter *iter) { S32 i; for (i = 0; i < MAX_ITERS; i++) { if (mIters[i] == iter) { mIters[i] = NULL; mIterCount--; return; } } llerrs << "Iterator " << iter << " not found for removal in hash map!" << llendl; } template void LLLocalIDHashMap::addIter(LLLocalIDHashMapIter *iter) { S32 i; for (i = 0; i < MAX_ITERS; i++) { if (mIters[i] == NULL) { mIters[i] = iter; mIterCount++; return; } } llerrs << "More than " << MAX_ITERS << " iterating over a map simultaneously!" << llendl; } // // LLLocalIDHashMapIter Implementation // template LLLocalIDHashMapIter::LLLocalIDHashMapIter(LLLocalIDHashMap *hash_mapp) { mHashMapp = NULL; setMap(hash_mapp); } template LLLocalIDHashMapIter::~LLLocalIDHashMapIter() { if (mHashMapp) { mHashMapp->removeIter(this); } } template void LLLocalIDHashMapIter::setMap(LLLocalIDHashMap *hash_mapp) { if (mHashMapp) { mHashMapp->removeIter(this); } mHashMapp = hash_mapp; if (mHashMapp) { mHashMapp->addIter(this); } mCurHashNodep = NULL; mCurHashMapNodeNum = -1; mCurHashNodeKey = 0; } template inline void LLLocalIDHashMapIter::first() { // Iterate through until we find the first non-empty node; S32 i; for (i = 0; i < 256; i++) { if (mHashMapp->mNodes[i].mCount) { mCurHashNodep = &mHashMapp->mNodes[i]; mCurHashMapNodeNum = i; mCurHashNodeKey = 0; //return mCurHashNodep->mData[0]; return; } } // Completely empty! mCurHashNodep = NULL; //return mNull; return; } template inline BOOL LLLocalIDHashMapIter::done() const { return mCurHashNodep ? FALSE : TRUE; } template inline S32 LLLocalIDHashMapIter::currentBin() const { if ( (mCurHashMapNodeNum > 255) ||(mCurHashMapNodeNum < 0)) { return 0; } else { return mCurHashMapNodeNum; } } template inline void LLLocalIDHashMapIter::setBin(S32 bin) { // Iterate through until we find the first non-empty node; S32 i; bin = llclamp(bin, 0, 255); for (i = bin; i < 256; i++) { if (mHashMapp->mNodes[i].mCount) { mCurHashNodep = &mHashMapp->mNodes[i]; mCurHashMapNodeNum = i; mCurHashNodeKey = 0; return; } } for (i = 0; i < bin; i++) { if (mHashMapp->mNodes[i].mCount) { mCurHashNodep = &mHashMapp->mNodes[i]; mCurHashMapNodeNum = i; mCurHashNodeKey = 0; return; } } // Completely empty! mCurHashNodep = NULL; } template inline DATA_TYPE &LLLocalIDHashMapIter::current() { if (!mCurHashNodep) { return mNull; } return mCurHashNodep->mData[mCurHashNodeKey]; } template inline void LLLocalIDHashMapIter::next() { // No current entry, this iterator is done if (!mCurHashNodep) { //return mNull; return; } // Go to the next element mCurHashNodeKey++; if (mCurHashNodeKey < mCurHashNodep->mCount) { // We're not done with this node, return the current element //return mCurHashNodep->mData[mCurHashNodeKey]; return; } // Done with this node, move to the next mCurHashNodep = mCurHashNodep->mNextNodep; if (mCurHashNodep) { // Return the first element mCurHashNodeKey = 0; //return mCurHashNodep->mData[0]; return; } // Find the next non-empty node (keyed on the first byte) mCurHashMapNodeNum++; S32 i; for (i = mCurHashMapNodeNum; i < 256; i++) { if (mHashMapp->mNodes[i].mCount) { // We found one that wasn't empty mCurHashNodep = &mHashMapp->mNodes[i]; mCurHashMapNodeNum = i; mCurHashNodeKey = 0; //return mCurHashNodep->mData[0]; return; } } // OK, we're done, nothing else to iterate mCurHashNodep = NULL; mHashMapp->mIterCount--; // Decrement since we're safe to do removes now //return mNull; return; } #endif // LL_LLLOCALIDHASHMAP_H