namespace stingray_plugin_foundation { template HashMap::HashMap(Allocator &a) : _data(), _used(0), _buckets(0), _spill_unused(0), _spill_freelist(END_OF_FREELIST), _allocator(a) { } template HashMap::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 HashMap::HashMap(const HashMap &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 void HashMap::operator=(const HashMap &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 HashMap::~HashMap() { clear(); _allocator.deallocate(_data.marker); } template void HashMap::swap(HashMap &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 typename HashMap::iterator HashMap::begin() { return iterator(*this, 0); } template typename HashMap::iterator HashMap::end() { return iterator(*this, num_buckets()); } template typename HashMap::const_iterator HashMap::begin() const { return const_iterator(*this, 0); } template typename HashMap::const_iterator HashMap::end() const { return const_iterator(*this, num_buckets()); } template template typename HashMap::const_iterator HashMap::find(const KEY_EQ &k) const{ auto i = find_or_fail(k); return (i == END_OF_LIST) ? end() : const_iterator(*this, i); } template template typename HashMap::iterator HashMap::find(const KEY_EQ &k){ auto i = find_or_fail(k); return (i == END_OF_LIST) ? end() : iterator(*this, i); } template Allocator & HashMap::allocator() const { return _allocator; } template unsigned HashMap::size() const { return _used; } template bool HashMap::empty() const { return _used == 0; } template template bool HashMap::has(const KEY_EQ &k) const { return find_or_fail(k) != END_OF_LIST; } template template typename HashMap::data_type &HashMap::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 template typename HashMap::value_type &HashMap::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 template const typename HashMap::data_type &HashMap::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 template typename HashMap::data_type &HashMap::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 template const typename HashMap::data_type &HashMap::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 void HashMap::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 template void HashMap::insert(const KEY_EQ &k, const DATA_EQ &v) { (*this)[k] = v; } template template void HashMap::erase(const KEY_EQ &k) { find_and_erase(k); } template void HashMap::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 void HashMap::reserve(unsigned items) { unsigned buckets = int(items * 1.37f); if (buckets < 26) buckets = 26; rehash(buckets); } template unsigned HashMap::num_buckets() const { return _data.size; } template bool HashMap::bucket_valid(unsigned i) const { return (_data.marker[i] & 0x80000000) == 0; } template typename HashMap::value_type &HashMap::bucket_value(unsigned i) { return _data.value[i]; } template const typename HashMap::value_type &HashMap::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 template unsigned HashMap::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 template unsigned HashMap::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 template void HashMap::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 bool HashMap::full() const { // Hash map is full when spillover region is full return _spill_unused == 0 && _spill_freelist == END_OF_FREELIST; } template void HashMap::grow() { do { unsigned new_buckets = _buckets * 2 + 1; if (new_buckets < 19) new_buckets = 19; rehash(new_buckets); } while (full()); } template template unsigned HashMap::hash(const KEY_EQ &k) const { XENSURE(_buckets > 0); return _hash(k) % _buckets; } // Allocate a slot in the spillover region and return it. template unsigned HashMap::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 void HashMap::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 void HashMap::clear_freelist() { while (_spill_freelist != END_OF_FREELIST) { unsigned i = _spill_freelist & 0x7fffffff; _spill_freelist = _data.marker[i]; _data.marker[i] = UNUSED; } } template void HashMap::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 template void HashMap::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