p2p/stingray_sdk/plugin_foundation/hash_map.inl
Lucas Schwiderski 2c9ce46dd2
chore: Rework project structure
There likely won't be much need for multiple separate crates.
2023-05-26 23:42:01 +02:00

518 lines
14 KiB
C++

namespace stingray_plugin_foundation {
template <class K, class D, class H, class E>
HashMap<K,D,H,E>::HashMap(Allocator &a) : _data(), _used(0), _buckets(0),
_spill_unused(0), _spill_freelist(END_OF_FREELIST), _allocator(a)
{
}
template <class K, class D, class H, class E>
HashMap<K,D,H,E>::HashMap(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 D, class H, class E>
HashMap<K,D,H,E>::HashMap(const HashMap<K,D,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.value[i]);
_data.value[i] = o._data.value[i];
}
_data.marker[i] = o._data.marker[i];
}
}
template <class K, class D, class H, class E>
void HashMap<K,D,H,E>::operator=(const HashMap<K,D,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.value[i]);
_data.value[i] = o._data.value[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 D, class H, class E>
HashMap<K, D, H, E>::~HashMap()
{
clear();
_allocator.deallocate(_data.marker);
}
template <class K, class D, class H, class E>
void HashMap<K,D,H,E>::swap(HashMap<K,D,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.value, o._data.value);
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 D, class H, class E>
typename HashMap<K,D,H,E>::iterator HashMap<K,D,H,E>::begin()
{
return iterator(*this, 0);
}
template <class K, class D, class H, class E>
typename HashMap<K,D,H,E>::iterator HashMap<K,D,H,E>::end()
{
return iterator(*this, num_buckets());
}
template <class K, class D, class H, class E>
typename HashMap<K,D,H,E>::const_iterator HashMap<K,D,H,E>::begin() const
{
return const_iterator(*this, 0);
}
template <class K, class D, class H, class E>
typename HashMap<K,D,H,E>::const_iterator HashMap<K,D,H,E>::end() const
{
return const_iterator(*this, num_buckets());
}
template <class K, class D, class H, class E> template <class KEY_EQ>
typename HashMap<K,D,H,E>::const_iterator HashMap<K,D,H,E>::find(const KEY_EQ &k) const{
auto i = find_or_fail(k);
return (i == END_OF_LIST) ? end() : const_iterator(*this, i);
}
template <class K, class D, class H, class E> template <class KEY_EQ>
typename HashMap<K,D,H,E>::iterator HashMap<K,D,H,E>::find(const KEY_EQ &k){
auto i = find_or_fail(k);
return (i == END_OF_LIST) ? end() : iterator(*this, i);
}
template <class K, class D, class H, class E>
Allocator & HashMap<K,D,H,E>::allocator() const
{
return _allocator;
}
template <class K, class D, class H, class E>
unsigned HashMap<K,D,H,E>::size() const
{
return _used;
}
template <class K, class D, class H, class E>
bool HashMap<K,D,H,E>::empty() const
{
return _used == 0;
}
template <class K, class D, class H, class E> template <class KEY_EQ>
bool HashMap<K,D,H,E>::has(const KEY_EQ &k) const
{
return find_or_fail(k) != END_OF_LIST;
}
template <class K, class D, class H, class E> template <class KEY_EQ>
typename HashMap<K,D,H,E>::data_type &HashMap<K,D,H,E>::operator[](const KEY_EQ &k)
{
if (full()) {
unsigned i = find_or_fail(k);
if (i != END_OF_LIST)
return _data.value[i].second;
grow();
}
return _data.value[find_or_make(k)].second;
}
template <class K, class D, class H, class E> template <class KEY_EQ>
typename HashMap<K, D, H, E>::value_type &HashMap<K, D, H, E>::get_pair(const KEY_EQ &k)
{
if (full()) {
unsigned i = find_or_fail(k);
if (i != END_OF_LIST)
return _data.value[i];
grow();
}
return _data.value[find_or_make(k)];
}
template <class K, class D, class H, class E> template <class KEY_EQ>
const typename HashMap<K,D,H,E>::data_type &HashMap<K,D,H,E>::operator[](const KEY_EQ &k) const
{
unsigned i = find_or_fail(k);
XASSERT(i != END_OF_LIST, "key not in map");
return _data.value[i].second;
}
template <class K, class D, class H, class E> template <class KEY_EQ>
typename HashMap<K,D,H,E>::data_type &HashMap<K,D,H,E>::get(const KEY_EQ &k, data_type &def)
{
unsigned i = find_or_fail(k);
if (i == END_OF_LIST)
return def;
else
return _data.value[i].second;
}
template <class K, class D, class H, class E> template <class KEY_EQ>
const typename HashMap<K,D,H,E>::data_type &HashMap<K,D,H,E>::get(const KEY_EQ &k, const data_type &def) const
{
unsigned i = find_or_fail(k);
if (i == END_OF_LIST)
return def;
else
return _data.value[i].second;
}
template <class K, class D, class H, class E>
void HashMap<K,D,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.value[i]);
_data.marker[i] = UNUSED;
}
}
template <class K, class D, class H, class E> template <class KEY_EQ, class DATA_EQ>
void HashMap<K,D,H,E>::insert(const KEY_EQ &k, const DATA_EQ &v)
{
(*this)[k] = v;
}
template <class K, class D, class H, class E> template <class KEY_EQ>
void HashMap<K,D,H,E>::erase(const KEY_EQ &k)
{
find_and_erase(k);
}
template <class K, class D, class H, class E>
void HashMap<K,D,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_hash(new_buckets, spill, _allocator);
for (unsigned o=0; o<old_size; ++o) {
if (_data.marker[o] == UNUSED) {
continue;
}
// Find/allocate entry
auto &k = _data.value[o].first;
auto &d = _data.value[o].second;
unsigned i = new_hash.hash(k);
if (new_hash._data.marker[i] == UNUSED) {
new_hash._data.marker[i] = END_OF_LIST;
++new_hash._used;
}
else {
// Walk the hash chain until the end and add a new entry
while (new_hash._data.marker[i] != END_OF_LIST)
i = new_hash._data.marker[i];
unsigned j = new_hash.allocate_spill();
new_hash._data.marker[i] = j;
i = j;
new_hash._data.marker[i] = END_OF_LIST;
}
memmove(&new_hash._data.value[i].first, &k, sizeof(K));
memmove(&new_hash._data.value[i].second, &d, sizeof(D));
_data.marker[o] = UNUSED;
}
swap(new_hash);
}
template <class K, class D, class H, class E>
void HashMap<K,D,H,E>::reserve(unsigned items)
{
unsigned buckets = int(items * 1.37f);
if (buckets < 26)
buckets = 26;
rehash(buckets);
}
template <class K, class D, class H, class E>
unsigned HashMap<K,D,H,E>::num_buckets() const
{
return _data.size;
}
template <class K, class D, class H, class E>
bool HashMap<K,D,H,E>::bucket_valid(unsigned i) const
{
return (_data.marker[i] & 0x80000000) == 0;
}
template <class K, class D, class H, class E>
typename HashMap<K,D,H,E>::value_type &HashMap<K,D,H,E>::bucket_value(unsigned i)
{
return _data.value[i];
}
template <class K, class D, class H, class E>
const typename HashMap<K,D,H,E>::value_type &HashMap<K,D,H,E>::bucket_value(unsigned i) const
{
return _data.value[i];
}
// Searches for `k` in the map. Returns the index of the entry if found and
// END_OF_LIST if not found.
template <class K, class D, class H, class E> template <class KEY_EQ>
unsigned HashMap<K,D,H,E>::find_or_fail(const KEY_EQ &k) const
{
// Is hash empty?
if (empty())
return END_OF_LIST;
unsigned i = hash(k);
// Is primary slot unused?
if (_data.marker[i] == UNUSED)
return END_OF_LIST;
// Walk the hash chain until key matches or end is reached.
while (true) {
if (i == END_OF_LIST || _equal(k, _data.value[i].first))
return i;
i = _data.marker[i];
}
}
// Searches for `k` in the map. Returns the index if it is found. If it is
// not found a new hash slot is create for `k` and its index is returned.
template <class K, class D, class H, class E> template <class KEY_EQ>
unsigned HashMap<K,D,H,E>::find_or_make(const KEY_EQ &k)
{
unsigned i = hash(k);
// Is primary slot unused?
if (_data.marker[i] == UNUSED) {
++_used;
construct(_data.value[i]);
_data.value[i].first = k;
_data.marker[i] = END_OF_LIST;
return i;
}
while (true) {
// Walk the hash chain until found
if (_equal(k, _data.value[i].first) )
return i;
// Or until end of hash chain, in which case we add a new entry
// to the end.
if (_data.marker[i] == END_OF_LIST) {
unsigned j = allocate_spill();
_data.marker[i] = j;
i = j;
construct(_data.value[i]);
_data.value[i].first = k;
_data.marker[i] = END_OF_LIST;
return i;
}
i = _data.marker[i];
}
}
// Searches for `k` in the map. If it is found, it is erased.
template <class K, class D, class H, class E> template <class KEY_EQ>
void HashMap<K,D,H,E>::find_and_erase(const KEY_EQ &k)
{
// early out to avoid division by zero in hash method if the map is empty.
if (_buckets == 0)
return;
unsigned i = hash(k);
// Is primary slot unused?
if (_data.marker[i] == UNUSED)
return;
// Did the primary slot match?
if (_equal(k, _data.value[i].first)) {
// If there is no chain, just return the primary value
if (_data.marker[i] == END_OF_LIST) {
_data.marker[i] = UNUSED;
destruct(_data.value[i]);
--_used;
// If there is a chain, bring in the first value from the
// chain and mark it as free in the spillover region.
} else {
unsigned del = _data.marker[i];
_data.marker[i] = _data.marker[del];
_data.value[i] = _data.value[del];
destruct(_data.value[del]);
free_spill(del);
}
return;
}
// If not, search the hash chain for a match
unsigned prev = i;
while (true) {
// If there is a match remove that value from the chain and
// mark it as free in the spillover region.
if (_equal(k, _data.value[i].first) ) {
_data.marker[prev] = _data.marker[i];
destruct(_data.value[i]);
free_spill(i);
return;
}
// If end of chain was reached without finding the key just return
if (_data.marker[i] == END_OF_LIST)
return;
prev = i;
i = _data.marker[i];
}
}
template <class K, class D, class H, class E>
bool HashMap<K,D,H,E>::full() const
{
// Hash map is full when spillover region is full
return _spill_unused == 0 && _spill_freelist == END_OF_FREELIST;
}
template <class K, class D, class H, class E>
void HashMap<K,D,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 D, class H, class E> template <class KEY_EQ>
unsigned HashMap<K,D,H,E>::hash(const KEY_EQ &k) const
{
XENSURE(_buckets > 0);
return _hash(k) % _buckets;
}
// Allocate a slot in the spillover region and return it.
template <class K, class D, class H, class E>
unsigned HashMap<K,D,H,E>::allocate_spill()
{
++_used;
// If there spillover free list is non-empty, use its
// first item.
if (_spill_freelist != END_OF_FREELIST) {
unsigned i = _spill_freelist & 0x7fffffff;
_spill_freelist = _data.marker[i];
return i;
}
// Return the first unused item from the spillover region.
XENSURE(_spill_unused > 0);
unsigned i = _data.size - _spill_unused;
--_spill_unused;
_data.marker[i] = UNUSED;
return i;
}
template <class K, class D, class H, class E>
void HashMap<K,D,H,E>::free_spill(unsigned i)
{
// Free a spillover item by inserting it in the freelist.
--_used;
_data.marker[i] = _spill_freelist;
_spill_freelist = i | 0x80000000;
}
// Clears the spillover freelist (marks all items as unused).
template <class K, class D, class H, class E>
void HashMap<K,D,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 D, class H, class E>
void HashMap<K, D, 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 value_size = sizeof(value_type) * count;
auto data_size = marker_size + value_size;
_data.marker = (unsigned*)_allocator.allocate(data_size);
_data.value = (value_type*)&(((char*)_data.marker)[marker_size]);
_data.size = count;
}
template<class K, class D, class H, class E>
template <class STREAM> void HashMap<K,D,H,E>::serialize(STREAM &stream)
{
unsigned n = size();
stream & n;
if (stream.is_output()) {
iterator it(this->begin());
iterator end(this->end());
for (; it != end; ++it)
stream & (*it);
} else {
reserve(n);
for (unsigned i=0; i<n; ++i) {
value_type v(_allocator);
stream & v;
insert(v.first, v.second);
}
}
}
} // namespace stingray_plugin_foundation