404 lines
9.4 KiB
C++
404 lines
9.4 KiB
C++
#include "assert.h"
|
|
|
|
namespace stingray_plugin_foundation {
|
|
|
|
template<class K, class H, class E>
|
|
HashSet<K,H,E>::HashSet(Allocator &a) : _data(), _used(0), _buckets(0),
|
|
_spill_unused(0), _spill_freelist(END_OF_FREELIST), _allocator(a)
|
|
{}
|
|
|
|
template<class K, class H, class E>
|
|
HashSet<K,H,E>::HashSet(unsigned buckets, unsigned spill, Allocator &a) : _data(),
|
|
_used(0), _buckets(buckets), _spill_unused(spill), _spill_freelist(END_OF_FREELIST)
|
|
, _allocator(a)
|
|
{
|
|
allocate_data(buckets + spill);
|
|
for (unsigned i=0; i<_data.size; ++i)
|
|
_data.marker[i] = UNUSED;
|
|
}
|
|
|
|
template<class K, class H, class E>
|
|
HashSet<K,H,E>::HashSet(const HashSet<K,H,E> &o) :
|
|
_hash(o._hash), _equal(o._equal),
|
|
_data(), _used(o._used), _buckets(o._buckets),
|
|
_spill_unused(o._spill_unused), _spill_freelist(o._spill_freelist),
|
|
_allocator(o._allocator)
|
|
{
|
|
allocate_data(o._data.size);
|
|
for (unsigned i = 0; i < _data.size; ++i) {
|
|
if (o._data.marker[i] != UNUSED && (o._data.marker[i] & 0x80000000) == 0) {
|
|
construct(_data.key[i], IS_ALLOCATOR_AWARE_TYPE(K)());
|
|
_data.key[i] = o._data.key[i];
|
|
}
|
|
_data.marker[i] = o._data.marker[i];
|
|
}
|
|
}
|
|
|
|
template<class K, class H, class E>
|
|
void HashSet<K,H,E>::operator=(const HashSet<K,H,E> &o)
|
|
{
|
|
if (this == &o)
|
|
return;
|
|
clear();
|
|
_hash = o._hash;
|
|
_equal = o._equal;
|
|
_allocator.deallocate(_data.marker);
|
|
_data.marker = nullptr;
|
|
allocate_data(o._data.size);
|
|
for (unsigned i = 0; i < _data.size; ++i) {
|
|
if (o._data.marker[i] != UNUSED && (o._data.marker[i] & 0x80000000) == 0) {
|
|
construct(_data.key[i], IS_ALLOCATOR_AWARE_TYPE(K)());
|
|
_data.key[i] = o._data.key[i];
|
|
}
|
|
_data.marker[i] = o._data.marker[i];
|
|
}
|
|
_used = o._used;
|
|
_buckets = o._buckets;
|
|
_spill_unused = o._spill_unused;
|
|
_spill_freelist = o._spill_freelist;
|
|
}
|
|
|
|
template<class K, class H, class E>
|
|
HashSet<K, H, E>::~HashSet()
|
|
{
|
|
clear();
|
|
_allocator.deallocate(_data.marker);
|
|
}
|
|
|
|
template <class K, class H, class E>
|
|
void HashSet<K,H,E>::reserve(unsigned items)
|
|
{
|
|
unsigned buckets = int(items * 1.37f);
|
|
if (buckets < int(19*1.37f))
|
|
buckets = int(19*1.37f);
|
|
rehash(buckets);
|
|
}
|
|
|
|
template<class K, class H, class E>
|
|
void HashSet<K,H,E>::swap(HashSet<K,H,E> &o)
|
|
{
|
|
XENSURE(&_allocator == &o._allocator);
|
|
std::swap(_hash, o._hash);
|
|
std::swap(_equal, o._equal);
|
|
std::swap(_data.size, o._data.size);
|
|
std::swap(_data.marker, o._data.marker);
|
|
std::swap(_data.key, o._data.key);
|
|
std::swap(_used, o._used);
|
|
std::swap(_buckets, o._buckets);
|
|
std::swap(_spill_unused, o._spill_unused);
|
|
std::swap(_spill_freelist, o._spill_freelist);
|
|
}
|
|
|
|
template<class K, class H, class E>
|
|
Allocator & HashSet<K,H,E>::allocator() const
|
|
{
|
|
return _allocator;
|
|
}
|
|
|
|
template<class K, class H, class E>
|
|
unsigned HashSet<K,H,E>::size() const
|
|
{
|
|
return _used;
|
|
}
|
|
|
|
template<class K, class H, class E>
|
|
bool HashSet<K,H,E>::empty() const
|
|
{
|
|
return _used == 0;
|
|
}
|
|
|
|
template<class K, class H, class E> template <class KEY_EQ>
|
|
unsigned HashSet<K,H,E>::find_or_fail(const KEY_EQ &k) const
|
|
{
|
|
if (empty())
|
|
return END_OF_LIST;
|
|
|
|
unsigned i = hash(k);
|
|
if (_data.marker[i] == UNUSED)
|
|
return END_OF_LIST;
|
|
while (true) {
|
|
if (i == END_OF_LIST || _equal(k, _data.key[i]))
|
|
return i;
|
|
i = _data.marker[i];
|
|
}
|
|
}
|
|
|
|
template<class K, class H, class E> template <class KEY_EQ>
|
|
unsigned HashSet<K,H,E>::find_or_make(const KEY_EQ &k)
|
|
{
|
|
unsigned i = hash(k);
|
|
if (_data.marker[i] == UNUSED) {
|
|
++_used;
|
|
construct(_data.key[i], IS_ALLOCATOR_AWARE_TYPE(K)());
|
|
_data.key[i] = k;
|
|
_data.marker[i] = END_OF_LIST;
|
|
return i;
|
|
}
|
|
while (true) {
|
|
if (_equal(k, _data.key[i]) )
|
|
return i;
|
|
|
|
if (_data.marker[i] == END_OF_LIST) {
|
|
unsigned j = allocate_spill();
|
|
_data.marker[i] = j;
|
|
i = j;
|
|
construct(_data.key[i], IS_ALLOCATOR_AWARE_TYPE(K)());
|
|
_data.key[i] = k;
|
|
_data.marker[i] = END_OF_LIST;
|
|
return j;
|
|
} else
|
|
i = _data.marker[i];
|
|
}
|
|
}
|
|
|
|
template<class K, class H, class E> template <class KEY_EQ>
|
|
void HashSet<K,H,E>::find_and_erase(const KEY_EQ &k)
|
|
{
|
|
unsigned i = hash(k);
|
|
if (_data.marker[i] == UNUSED)
|
|
return;
|
|
|
|
if (_equal(k, _data.key[i])) {
|
|
if (_data.marker[i] == END_OF_LIST) {
|
|
_data.marker[i] = UNUSED;
|
|
destruct(_data.key[i]);
|
|
--_used;
|
|
} else {
|
|
unsigned del = _data.marker[i];
|
|
_data.marker[i] = _data.marker[del];
|
|
_data.key[i] = _data.key[del];
|
|
destruct(_data.key[del]);
|
|
free_spill(del);
|
|
}
|
|
return;
|
|
}
|
|
|
|
unsigned prev = i;
|
|
while (true) {
|
|
if (_equal(k, _data.key[i]) ) {
|
|
_data.marker[prev] = _data.marker[i];
|
|
destruct(_data.key[i]);
|
|
free_spill(i);
|
|
return;
|
|
}
|
|
|
|
if (_data.marker[i] == END_OF_LIST)
|
|
return;
|
|
|
|
prev = i;
|
|
i = _data.marker[i];
|
|
}
|
|
}
|
|
|
|
template<class K, class H, class E> template <class KEY_EQ>
|
|
bool HashSet<K,H,E>::has(const KEY_EQ &k) const
|
|
{
|
|
return find_or_fail(k) != END_OF_LIST;
|
|
}
|
|
|
|
template<class K, class H, class E>
|
|
void HashSet<K,H,E>::clear()
|
|
{
|
|
_used = 0;
|
|
_spill_freelist = END_OF_FREELIST;
|
|
_spill_unused = _data.size - _buckets;
|
|
for (unsigned i = 0; i<_data.size; ++i) {
|
|
if (bucket_valid(i))
|
|
destruct(_data.key[i]);
|
|
_data.marker[i] = UNUSED;
|
|
}
|
|
}
|
|
|
|
template<class K, class H, class E> template <class KEY_EQ>
|
|
void HashSet<K,H,E>::insert(const KEY_EQ &k)
|
|
{
|
|
if (full()) {
|
|
unsigned i = find_or_fail(k);
|
|
if (i != END_OF_LIST)
|
|
return;
|
|
grow();
|
|
}
|
|
find_or_make(k);
|
|
}
|
|
|
|
template<class K, class H, class E> template <class KEY_EQ>
|
|
void HashSet<K,H,E>::erase(const KEY_EQ &k)
|
|
{
|
|
find_and_erase(k);
|
|
}
|
|
|
|
template<class K, class H, class E>
|
|
void HashSet<K,H,E>::rehash(unsigned new_buckets)
|
|
{
|
|
XENSURE(new_buckets >= _used);
|
|
clear_freelist();
|
|
unsigned spill = int(new_buckets * 0.37f + 1);
|
|
unsigned old_size = _data.size;
|
|
if (_data.size == 0) {
|
|
allocate_data(new_buckets + spill);
|
|
_buckets = new_buckets;
|
|
_spill_unused = spill;
|
|
for (unsigned i = 0; i<_data.size; ++i)
|
|
_data.marker[i] = UNUSED;
|
|
return;
|
|
}
|
|
this_type new_set(new_buckets, spill, _allocator);
|
|
for (unsigned o = 0; o<old_size; ++o) {
|
|
if (_data.marker[o] == UNUSED) {
|
|
continue;
|
|
}
|
|
// Find/allocate entry
|
|
auto &k = _data.key[o];
|
|
unsigned i = new_set.hash(k);
|
|
if (new_set._data.marker[i] == UNUSED) {
|
|
new_set._data.marker[i] = END_OF_LIST;
|
|
++new_set._used;
|
|
}
|
|
else {
|
|
// Walk the hash chain until the end and add a new entry
|
|
while (new_set._data.marker[i] != END_OF_LIST)
|
|
i = new_set._data.marker[i];
|
|
|
|
unsigned j = new_set.allocate_spill();
|
|
new_set._data.marker[i] = j;
|
|
i = j;
|
|
new_set._data.marker[i] = END_OF_LIST;
|
|
}
|
|
memmove(&new_set._data.key[i], &k, sizeof(K));
|
|
_data.marker[o] = UNUSED;
|
|
}
|
|
swap(new_set);
|
|
}
|
|
|
|
template<class K, class H, class E>
|
|
typename HashSet<K,H,E>::const_iterator HashSet<K,H,E>::begin() const
|
|
{
|
|
return const_iterator(*this, 0);
|
|
}
|
|
|
|
template<class K, class H, class E>
|
|
typename HashSet<K,H,E>::const_iterator HashSet<K,H,E>::end() const
|
|
{
|
|
return const_iterator(*this, num_buckets());
|
|
}
|
|
|
|
template<class K, class H, class E>
|
|
unsigned HashSet<K,H,E>::num_buckets() const
|
|
{
|
|
return _data.size;
|
|
}
|
|
|
|
template<class K, class H, class E>
|
|
bool HashSet<K,H,E>::bucket_valid(unsigned i) const
|
|
{
|
|
return (_data.marker[i] & 0x80000000) == 0;
|
|
}
|
|
|
|
template<class K, class H, class E>
|
|
const typename HashSet<K,H,E>::key_type &HashSet<K,H,E>::bucket_value(unsigned i) const
|
|
{
|
|
return _data.key[i];
|
|
}
|
|
|
|
template<class K, class H, class E>
|
|
typename HashSet<K,H,E>::key_type &HashSet<K,H,E>::bucket_value(unsigned i)
|
|
{
|
|
return _data.key[i];
|
|
}
|
|
|
|
template<class K, class H, class E>
|
|
bool HashSet<K,H,E>::full() const
|
|
{
|
|
return _spill_unused == 0 && _spill_freelist == END_OF_FREELIST;
|
|
}
|
|
|
|
template<class K, class H, class E>
|
|
void HashSet<K,H,E>::grow()
|
|
{
|
|
do {
|
|
unsigned new_buckets = _buckets * 2 + 1;
|
|
if (new_buckets < 19)
|
|
new_buckets = 19;
|
|
rehash(new_buckets);
|
|
} while (full());
|
|
}
|
|
|
|
template<class K, class H, class E> template <class KEY_EQ>
|
|
unsigned HashSet<K,H,E>::hash(const KEY_EQ &k) const
|
|
{
|
|
return _hash(k) % _buckets;
|
|
}
|
|
|
|
template<class K, class H, class E>
|
|
unsigned HashSet<K,H,E>::allocate_spill()
|
|
{
|
|
++_used;
|
|
unsigned i = 0;
|
|
if (_spill_freelist != END_OF_FREELIST) {
|
|
i = _spill_freelist & 0x7fffffff;
|
|
_spill_freelist = _data.marker[i];
|
|
return i;
|
|
} else {
|
|
XENSURE(_spill_unused > 0);
|
|
i = _data.size - _spill_unused;
|
|
--_spill_unused;
|
|
}
|
|
_data.marker[i] = UNUSED;
|
|
return i;
|
|
}
|
|
|
|
template<class K, class H, class E>
|
|
void HashSet<K,H,E>::free_spill(unsigned i)
|
|
{
|
|
--_used;
|
|
_data.marker[i] = _spill_freelist;
|
|
_spill_freelist = i | 0x80000000;
|
|
}
|
|
|
|
template<class K, class H, class E>
|
|
void HashSet<K,H,E>::clear_freelist()
|
|
{
|
|
while (_spill_freelist != END_OF_FREELIST) {
|
|
unsigned i = _spill_freelist & 0x7fffffff;
|
|
_spill_freelist = _data.marker[i];
|
|
_data.marker[i] = UNUSED;
|
|
}
|
|
}
|
|
|
|
template<class K, class H, class E>
|
|
void HashSet<K, H, E>::allocate_data(unsigned count)
|
|
{
|
|
XASSERT(_data.marker == nullptr, "Data must be deallocated/cleared before allocating new data");
|
|
if (count == 0) {
|
|
_data = Data();
|
|
return;
|
|
}
|
|
|
|
auto marker_size = (((sizeof(unsigned) * count) + sizeof(void*) - 1) / sizeof(void*)) * sizeof(void*);
|
|
auto key_size = sizeof(key_type) * count;
|
|
auto data_size = marker_size + key_size;
|
|
_data.marker = (unsigned*)_allocator.allocate(data_size);
|
|
_data.key = (key_type*)&(((char*)_data.marker)[marker_size]);
|
|
_data.size = count;
|
|
}
|
|
|
|
template<class K, class H, class E>
|
|
template <class STREAM> void HashSet<K,H,E>::serialize(STREAM &stream)
|
|
{
|
|
unsigned n = _used;
|
|
stream & n;
|
|
if (stream.is_output()) {
|
|
iterator it(*this, 0);
|
|
iterator end(*this, num_buckets());
|
|
for (; it != end; ++it)
|
|
stream & *it;
|
|
} else {
|
|
for (unsigned i=0; i<n; ++i) {
|
|
key_type k(_allocator);
|
|
stream & k;
|
|
insert(k);
|
|
}
|
|
}
|
|
}
|
|
|
|
} // namespace stingray_plugin_foundation
|