Init
This commit is contained in:
706
DependentExtensions/cat/net/SphynxCollexion.hpp
Normal file
706
DependentExtensions/cat/net/SphynxCollexion.hpp
Normal file
@ -0,0 +1,706 @@
|
||||
/*
|
||||
Copyright (c) 2009-2010 Christopher A. Taylor. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
* Neither the name of LibCat nor the names of its contributors may be used
|
||||
to endorse or promote products derived from this software without
|
||||
specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef CAT_SPHYNX_COLLEXION_HPP
|
||||
#define CAT_SPHYNX_COLLEXION_HPP
|
||||
|
||||
#include <cat/threads/Mutex.hpp>
|
||||
#include <cat/threads/RegionAllocator.hpp>
|
||||
#include <cat/net/SphynxTransport.hpp>
|
||||
|
||||
namespace cat {
|
||||
|
||||
|
||||
namespace sphynx {
|
||||
|
||||
|
||||
/*
|
||||
The purpose of sphynx::Collexion and sphynx::CollexionIterator is to
|
||||
store lists of Connexion object references and iterate through them.
|
||||
|
||||
Since the number of clients may be in the thousands, I feel it is
|
||||
important to scale effectively. So in the Collexion data structure,
|
||||
insertion and removal are O(1) operations. Also locks should be held
|
||||
for the smallest amount of time possible, so I have taken care to make
|
||||
locks short and reduce the amount of locking. For example, the iterator
|
||||
caches large blocks of data instead of locking for each iteration.
|
||||
|
||||
The design is optimized for cache usage, re-using common code to benefit
|
||||
from code cache and allocating and accessing table entries on cache line
|
||||
boundaries to double memory performance over a naive approach.
|
||||
*/
|
||||
|
||||
|
||||
//// sphynx::Collexion
|
||||
|
||||
template<class T>
|
||||
class CollexionIterator;
|
||||
|
||||
template<class T>
|
||||
struct CollexionElement
|
||||
{
|
||||
// Number of active enumerators using this element
|
||||
// If references are held it cannot be deleted
|
||||
// so the KILL flag is set on the 'next' member and
|
||||
// the final enumerator to reduce the reference count
|
||||
// to zero is responsible for removal.
|
||||
u32 refcnt;
|
||||
|
||||
// Bitfield:
|
||||
// 1 bit: COLLISION FLAG
|
||||
// 1 bit: KILL FLAG
|
||||
// 30 bits: Table index to next element in list + 1
|
||||
u32 next;
|
||||
|
||||
// Data at this table element
|
||||
T *conn;
|
||||
};
|
||||
|
||||
struct CollexionElement2
|
||||
{
|
||||
// Previous table element in list + 1
|
||||
u32 last;
|
||||
|
||||
// Hash of data pointer from main entry (so it doesn't need to be recalculated during growth)
|
||||
u32 hash;
|
||||
};
|
||||
|
||||
template<class T>
|
||||
class Collexion
|
||||
{
|
||||
static const u32 COLLIDE_MASK = 0x80000000;
|
||||
static const u32 KILL_MASK = 0x40000000;
|
||||
static const u32 NEXT_MASK = 0x3fffffff;
|
||||
static const u32 MIN_ALLOCATED = 32;
|
||||
|
||||
private:
|
||||
// Number of used table elements
|
||||
u32 _used;
|
||||
|
||||
// Number of allocated table elements
|
||||
u32 _allocated;
|
||||
|
||||
// First table index in list of active elements
|
||||
u32 _first;
|
||||
|
||||
// Primary table
|
||||
CollexionElement<T> *_table;
|
||||
|
||||
// Secondary table, split off so that primary table elements will
|
||||
// fit on a cache line. Contains data that is only accessed rarely.
|
||||
CollexionElement2 *_table2;
|
||||
|
||||
// Table lock
|
||||
Mutex _lock;
|
||||
|
||||
protected:
|
||||
// Attempt to double size of hash table (does not hold lock)
|
||||
bool DoubleTable();
|
||||
|
||||
// Hash a pointer to a 32-bit table key
|
||||
static CAT_INLINE u32 HashPtr(T *ptr)
|
||||
{
|
||||
u64 key = 0xBADdecafDEADbeef;
|
||||
|
||||
#if defined(CAT_WORD_64)
|
||||
key ^= *(u64*)&ptr;
|
||||
#else
|
||||
key ^= *(u32*)&ptr;
|
||||
#endif
|
||||
|
||||
key = (~key) + (key << 18);
|
||||
key = key ^ (key >> 31);
|
||||
key = key * 21;
|
||||
key = key ^ (key >> 11);
|
||||
key = key + (key << 6);
|
||||
key = key ^ (key >> 22);
|
||||
return (u32)key;
|
||||
}
|
||||
|
||||
// Common functions shared by interface for good code cache usage:
|
||||
|
||||
// Unlink a table key
|
||||
void Unlink(u32 key);
|
||||
|
||||
// Fill an iterator with the next set of data
|
||||
// Returns false if no data remains to fill
|
||||
void Fill(CollexionIterator<T> &iter, u32 first);
|
||||
|
||||
// Find a hash table key based on data
|
||||
u32 Find(T *conn);
|
||||
|
||||
public:
|
||||
// Ctor zeros everything
|
||||
Collexion()
|
||||
{
|
||||
_first = 0;
|
||||
_used = 0;
|
||||
_allocated = 0;
|
||||
_table = 0;
|
||||
_table2 = 0;
|
||||
}
|
||||
|
||||
// Dtor releases dangling memory
|
||||
~Collexion();
|
||||
|
||||
// Returns true if table is empty
|
||||
CAT_INLINE bool IsEmpty() { return _used == 0; }
|
||||
|
||||
// Insert Connexion object, return false if already present or out of memory
|
||||
bool Insert(T *conn);
|
||||
|
||||
// Remove Connexion object from list if it exists
|
||||
bool Remove(T *conn);
|
||||
|
||||
// Begin iterating through list
|
||||
void Begin(CollexionIterator<T> &iter);
|
||||
|
||||
// Iterate
|
||||
void Next(CollexionIterator<T> &iter, bool refill = true);
|
||||
};
|
||||
|
||||
|
||||
//// sphynx::CollexionIterator
|
||||
|
||||
template<class T>
|
||||
class CollexionIterator
|
||||
{
|
||||
friend class Collexion<T>;
|
||||
|
||||
static const u32 MAX_CACHE = 256;
|
||||
|
||||
// Parent Collexion object
|
||||
Collexion<T> *_parent;
|
||||
|
||||
// First and last hash table indices in parent
|
||||
u32 _first, _last;
|
||||
|
||||
// Stores the size of the parent when snapshot was taken,
|
||||
// will invalidate _first and _last if table changed size
|
||||
u32 _prev_allocated;
|
||||
|
||||
// Connexion object cache, to avoid locking and unlocking a lot
|
||||
T *_cache[MAX_CACHE];
|
||||
|
||||
// Offset into cache and total elements in cache
|
||||
// Will grab another cache once offset reaches total
|
||||
u32 _offset, _total;
|
||||
|
||||
public:
|
||||
// Smart pointer -style accessors
|
||||
CAT_INLINE T *Get() throw() { return _cache[_offset]; }
|
||||
CAT_INLINE T *operator->() throw() { return _cache[_offset]; }
|
||||
CAT_INLINE T &operator*() throw() { return *_cache[_offset]; }
|
||||
CAT_INLINE operator T*() { return _cache[_offset]; }
|
||||
|
||||
public:
|
||||
// Ctor: Grabs first cache of Connexions
|
||||
CollexionIterator(Collexion<T> &begin);
|
||||
|
||||
// Dtor: Calls Release()
|
||||
~CollexionIterator();
|
||||
|
||||
// Iterate to next Connexion object in list
|
||||
CollexionIterator &operator++();
|
||||
|
||||
// Releases reference to any outstanding Connexions
|
||||
void Release();
|
||||
};
|
||||
|
||||
|
||||
//// sphynx::Collexion
|
||||
|
||||
template<class T>
|
||||
bool Collexion<T>::DoubleTable()
|
||||
{
|
||||
u32 new_allocated = _allocated << 1;
|
||||
if (new_allocated < MIN_ALLOCATED) new_allocated = MIN_ALLOCATED;
|
||||
|
||||
// Allocate secondary table
|
||||
u32 new_bytes2 = sizeof(CollexionElement2) * new_allocated;
|
||||
CollexionElement2 *new_table2 = reinterpret_cast<CollexionElement2*>(
|
||||
RegionAllocator::ii->Acquire(new_bytes2) );
|
||||
|
||||
if (!new_table2) return false;
|
||||
|
||||
// Allocate primary table
|
||||
u32 new_bytes = sizeof(CollexionElement<T>) * new_allocated;
|
||||
CollexionElement<T> *new_table = reinterpret_cast<CollexionElement<T> *>(
|
||||
RegionAllocator::ii->Acquire(new_bytes) );
|
||||
|
||||
if (!new_table)
|
||||
{
|
||||
RegionAllocator::ii->Release(new_table2);
|
||||
return false;
|
||||
}
|
||||
|
||||
CAT_CLR(new_table, new_bytes);
|
||||
|
||||
u32 new_first = 0;
|
||||
|
||||
if (_table && _table2)
|
||||
{
|
||||
// For each entry in the old table,
|
||||
u32 ii = _first, mask = _allocated - 1;
|
||||
|
||||
while (ii)
|
||||
{
|
||||
--ii;
|
||||
CollexionElement<T> *oe = &_table[ii];
|
||||
u32 hash = _table2[ii].hash;
|
||||
u32 key = hash & mask;
|
||||
|
||||
// While collisions occur,
|
||||
while (new_table[key].conn)
|
||||
{
|
||||
// Mark collision
|
||||
new_table[key].next |= COLLIDE_MASK;
|
||||
|
||||
// Walk collision list
|
||||
key = (key * COLLISION_MULTIPLIER + COLLISION_INCREMENTER) & mask;
|
||||
}
|
||||
|
||||
// Fill new table element
|
||||
new_table[key].conn = oe->conn;
|
||||
new_table2[key].hash = hash;
|
||||
// new_table[key].refcnt is already zero
|
||||
|
||||
// Link new element to new list
|
||||
if (new_first)
|
||||
{
|
||||
new_table[key].next |= new_first;
|
||||
new_table2[new_first - 1].last = key;
|
||||
}
|
||||
// new_table[key].next is already zero so no need to zero it here
|
||||
new_first = key + 1;
|
||||
|
||||
// Get next old table entry
|
||||
ii = oe->next & NEXT_MASK;
|
||||
}
|
||||
|
||||
// Zero head->last
|
||||
new_table2[new_first - 1].last = 0;
|
||||
}
|
||||
|
||||
// Resulting linked list starting with _first-1 will extend until e->next == 0
|
||||
|
||||
if (_table2) RegionAllocator::ii->Release(_table2);
|
||||
if (_table) RegionAllocator::ii->Release(_table);
|
||||
|
||||
_table = new_table;
|
||||
_table2 = new_table2;
|
||||
_allocated = new_allocated;
|
||||
_first = new_first;
|
||||
return true;
|
||||
}
|
||||
|
||||
template<class T>
|
||||
Collexion<T>::~Collexion()
|
||||
{
|
||||
if (_table2)
|
||||
{
|
||||
RegionAllocator::ii->Release(_table2);
|
||||
}
|
||||
|
||||
// If table doesn't exist, return
|
||||
if (!_table) return;
|
||||
|
||||
// For each allocated table entry,
|
||||
for (u32 ii = 0; ii < _allocated; ++ii)
|
||||
{
|
||||
// Get Connexion object
|
||||
T *conn = _table[ii].conn;
|
||||
|
||||
// If object is valid, release it
|
||||
if (conn) conn->ReleaseRef();
|
||||
}
|
||||
|
||||
// Release table memory
|
||||
RegionAllocator::ii->Release(_table);
|
||||
}
|
||||
|
||||
template<class T>
|
||||
bool Collexion<T>::Insert(T *conn)
|
||||
{
|
||||
u32 hash = HashPtr(conn);
|
||||
conn->AddRef();
|
||||
|
||||
AutoMutex lock(_lock);
|
||||
|
||||
// If more than half of the table will be used,
|
||||
if (_used >= (_allocated >> 1))
|
||||
{
|
||||
// Double the size of the table (O(1) allocation pattern)
|
||||
// Growing pains are softened by careful design
|
||||
if (!DoubleTable())
|
||||
{
|
||||
// On growth failure, return false
|
||||
lock.Release();
|
||||
|
||||
conn->ReleaseRef();
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Mask off high bits to make table key from hash
|
||||
u32 mask = _allocated - 1;
|
||||
u32 key = hash & mask;
|
||||
|
||||
// While empty table entry not found,
|
||||
while (_table[key].conn)
|
||||
{
|
||||
// If Connexion object is already in the table,
|
||||
if (_table[key].conn == conn)
|
||||
{
|
||||
// Return false on duplicate
|
||||
lock.Release();
|
||||
|
||||
conn->ReleaseRef();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Mark as a collision
|
||||
_table[key].next |= COLLIDE_MASK;
|
||||
|
||||
// Walk collision list
|
||||
key = (key * COLLISION_MULTIPLIER + COLLISION_INCREMENTER) & mask;
|
||||
}
|
||||
|
||||
// Fill new element
|
||||
_table[key].conn = conn;
|
||||
_table[key].refcnt = 0;
|
||||
_table2[key].hash = hash;
|
||||
_table2[key].last = 0;
|
||||
|
||||
// Link new element to front of list
|
||||
if (_first) _table2[_first - 1].last = key + 1;
|
||||
_table[key].next = (_table[key].next & COLLIDE_MASK) | _first;
|
||||
_first = key + 1;
|
||||
|
||||
++_used;
|
||||
return true;
|
||||
}
|
||||
|
||||
template<class T>
|
||||
void Collexion<T>::Unlink(u32 key)
|
||||
{
|
||||
// Clear reference
|
||||
_table[key].conn = 0;
|
||||
|
||||
// Unlink from active list
|
||||
u32 next = _table[key].next & NEXT_MASK;
|
||||
u32 last = _table2[key].last;
|
||||
|
||||
if (last) _table[last-1].next = (_table[last-1].next & ~NEXT_MASK) | next;
|
||||
else _first = next;
|
||||
if (next) _table2[next-1].last = last;
|
||||
|
||||
// If this key was a leaf on a collision wind,
|
||||
if (!(_table[key].next & COLLIDE_MASK))
|
||||
{
|
||||
u32 mask = _allocated - 1;
|
||||
|
||||
do
|
||||
{
|
||||
// Go backwards through the collision list one step
|
||||
key = ((key + COLLISION_INCRINVERSE) * COLLISION_MULTINVERSE) & mask;
|
||||
|
||||
// Stop where collision list stops
|
||||
if (!(_table[key].next & COLLIDE_MASK))
|
||||
break;
|
||||
|
||||
// Turn off collision key for previous entry
|
||||
_table[key].next &= ~COLLIDE_MASK;
|
||||
|
||||
} while (!_table[key].conn);
|
||||
}
|
||||
|
||||
// Update number of used elements
|
||||
--_used;
|
||||
}
|
||||
|
||||
template<class T>
|
||||
bool Collexion<T>::Remove(T *conn)
|
||||
{
|
||||
u32 hash = HashPtr(conn);
|
||||
|
||||
AutoMutex lock(_lock);
|
||||
|
||||
// If table doesn't exist,
|
||||
if (!_allocated) return false;
|
||||
|
||||
// Mask off high bits to make table key from hash
|
||||
u32 mask = _allocated - 1;
|
||||
u32 key = hash & mask;
|
||||
|
||||
// While target table entry not found,
|
||||
for (;;)
|
||||
{
|
||||
// If target was found,
|
||||
if (_table[key].conn == conn)
|
||||
{
|
||||
if (_table[key].refcnt)
|
||||
{
|
||||
// Mark it killed so iterator can clean it up when it's finished
|
||||
_table[key].next |= KILL_MASK;
|
||||
}
|
||||
else
|
||||
{
|
||||
Unlink(key);
|
||||
|
||||
lock.Release();
|
||||
|
||||
conn->ReleaseRef();
|
||||
}
|
||||
|
||||
// Return success
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!(_table[key].next & COLLIDE_MASK))
|
||||
{
|
||||
break; // End of collision list
|
||||
}
|
||||
|
||||
// Walk collision list
|
||||
key = (key * COLLISION_MULTIPLIER + COLLISION_INCREMENTER) & mask;
|
||||
}
|
||||
|
||||
// Return failure: not found
|
||||
return false;
|
||||
}
|
||||
|
||||
template<class T>
|
||||
void Collexion<T>::Fill(CollexionIterator<T> &iter, u32 first)
|
||||
{
|
||||
u32 key = first;
|
||||
|
||||
// Find first list element that does not want to die
|
||||
while (key && (_table[key-1].next & KILL_MASK))
|
||||
{
|
||||
// Go to next
|
||||
key = _table[key-1].next & NEXT_MASK;
|
||||
}
|
||||
|
||||
iter._offset = 0;
|
||||
|
||||
// If no elements in table,
|
||||
if (!key)
|
||||
{
|
||||
// Return empty set
|
||||
iter._cache[0] = 0;
|
||||
iter._total = 0;
|
||||
iter._first = 0;
|
||||
iter._last = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
// Remember size of hash table in case it grows before iterator is done
|
||||
iter._prev_allocated = _allocated;
|
||||
|
||||
// Remember first key for next iteration
|
||||
iter._first = key;
|
||||
|
||||
// For each of the first MAX_CACHE elements in the table, copy the data pointer to cache
|
||||
u32 ii = 0, final = 0;
|
||||
|
||||
do
|
||||
{
|
||||
// If element does not want to die,
|
||||
if (!(_table[key-1].next & KILL_MASK))
|
||||
{
|
||||
// Copy data pointer
|
||||
iter._cache[ii] = _table[key-1].conn;
|
||||
|
||||
// Increment reference count for element
|
||||
_table[key-1].refcnt++;
|
||||
|
||||
// Remember key as the next iteration starting point
|
||||
final = key;
|
||||
|
||||
// Check if copy is complete
|
||||
if (++ii >= CollexionIterator<T>::MAX_CACHE) break;
|
||||
}
|
||||
|
||||
// Go to next key
|
||||
key = _table[key-1].next & NEXT_MASK;
|
||||
|
||||
} while (key);
|
||||
|
||||
// Record number of elements written
|
||||
iter._total = ii;
|
||||
|
||||
// Remember next key for next iteration
|
||||
iter._last = final;
|
||||
}
|
||||
|
||||
template<class T>
|
||||
void Collexion<T>::Begin(CollexionIterator<T> &iter)
|
||||
{
|
||||
iter._parent = this;
|
||||
|
||||
AutoMutex lock(_lock);
|
||||
|
||||
Fill(iter, _first);
|
||||
}
|
||||
|
||||
// Find a hash table key based on data
|
||||
template<class T>
|
||||
u32 Collexion<T>::Find(T *conn)
|
||||
{
|
||||
u32 mask = _allocated - 1;
|
||||
u32 key = HashPtr(conn) & mask;
|
||||
|
||||
// Find the object in the collision list starting at the expected location
|
||||
while (_table[key].conn != conn)
|
||||
{
|
||||
// If at the end of the collision list,
|
||||
if (!(_table[key].next & COLLIDE_MASK))
|
||||
break; // Should never happen
|
||||
|
||||
// Walk collision list
|
||||
key = (key * COLLISION_MULTIPLIER + COLLISION_INCREMENTER) & mask;
|
||||
}
|
||||
|
||||
return key;
|
||||
}
|
||||
|
||||
template<class T>
|
||||
void Collexion<T>::Next(CollexionIterator<T> &iter, bool refill)
|
||||
{
|
||||
T *release_list[CollexionIterator<T>::MAX_CACHE];
|
||||
u32 release_ii = 0;
|
||||
|
||||
u32 key = iter._first;
|
||||
u32 last = iter._last;
|
||||
|
||||
// If iteration is done,
|
||||
if (!key) return;
|
||||
|
||||
AutoMutex lock(_lock);
|
||||
|
||||
// If hash table changed size (rare),
|
||||
if (iter._prev_allocated != _allocated)
|
||||
{
|
||||
// iter._first and iter._last are invalid
|
||||
// ...but we can find them again based on the cached pointers!
|
||||
key = Find(iter._cache[0]) + 1;
|
||||
last = Find(iter._cache[iter._total - 1]) + 1;
|
||||
}
|
||||
|
||||
last = _table[last-1].next & NEXT_MASK;
|
||||
|
||||
// Release any table elements that want to die now
|
||||
do
|
||||
{
|
||||
u32 flags = _table[key-1].next;
|
||||
|
||||
// If reference count is now zero for this element,
|
||||
if (0 == --_table[key-1].refcnt)
|
||||
{
|
||||
// If element wants to die,
|
||||
if (flags & KILL_MASK)
|
||||
{
|
||||
// Prepare to release data after lock is released
|
||||
release_list[release_ii++] = _table[key-1].conn;
|
||||
|
||||
Unlink(key-1);
|
||||
}
|
||||
}
|
||||
|
||||
key = flags & NEXT_MASK;
|
||||
|
||||
} while (key != last);
|
||||
|
||||
// Fill iterator starting with next key
|
||||
if (refill) Fill(iter, key);
|
||||
|
||||
lock.Release();
|
||||
|
||||
if (!refill)
|
||||
{
|
||||
// Return empty set
|
||||
iter._cache[0] = 0;
|
||||
iter._total = 0;
|
||||
iter._offset = 0;
|
||||
iter._first = 0;
|
||||
iter._last = 0;
|
||||
}
|
||||
|
||||
// Release data awaiting destruction
|
||||
for (u32 ii = 0; ii < release_ii; ++ii)
|
||||
{
|
||||
release_list[ii]->ReleaseRef();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//// sphynx::CollexionIterator
|
||||
|
||||
template<class T>
|
||||
CollexionIterator<T>::CollexionIterator(Collexion<T> &begin)
|
||||
{
|
||||
begin.Begin(*this);
|
||||
}
|
||||
|
||||
template<class T>
|
||||
CollexionIterator<T>::~CollexionIterator()
|
||||
{
|
||||
Release();
|
||||
}
|
||||
|
||||
template<class T>
|
||||
CollexionIterator<T> &CollexionIterator<T>::operator++()
|
||||
{
|
||||
if (++_offset >= _total)
|
||||
{
|
||||
_parent->Next(*this, true);
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<class T>
|
||||
void CollexionIterator<T>::Release()
|
||||
{
|
||||
if (_parent)
|
||||
{
|
||||
_parent->Next(*this, false);
|
||||
_parent = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} // namespace sphynx
|
||||
|
||||
|
||||
} // namespace cat
|
||||
|
||||
#endif // CAT_SPHYNX_COLLEXION_HPP
|
||||
Reference in New Issue
Block a user