763 lines
20 KiB
C++
763 lines
20 KiB
C++
/*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version 2
|
|
* of the License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software Foundation,
|
|
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*/
|
|
|
|
#ifndef __BLI_MAP_HH__
|
|
#define __BLI_MAP_HH__
|
|
|
|
/** \file
|
|
* \ingroup bli
|
|
*
|
|
* This file provides a map implementation that uses open addressing with probing.
|
|
*
|
|
* The key and value objects are stored directly in the hash table to avoid indirect memory
|
|
* lookups. Keys and values are stored in groups of four to avoid wasting memory due to padding.
|
|
*/
|
|
|
|
#include "BLI_array_ref.hh"
|
|
#include "BLI_hash.hh"
|
|
#include "BLI_open_addressing.hh"
|
|
|
|
namespace BLI {
|
|
|
|
// clang-format off
|
|
|
|
#define ITER_SLOTS_BEGIN(KEY, ARRAY, OPTIONAL_CONST, R_ITEM, R_OFFSET) \
|
|
uint32_t hash = DefaultHash<KeyT>{}(KEY); \
|
|
uint32_t perturb = hash; \
|
|
while (true) { \
|
|
uint32_t item_index = (hash & ARRAY.slot_mask()) >> OFFSET_SHIFT; \
|
|
uint8_t R_OFFSET = hash & OFFSET_MASK; \
|
|
uint8_t initial_offset = R_OFFSET; \
|
|
OPTIONAL_CONST Item &R_ITEM = ARRAY.item(item_index); \
|
|
do {
|
|
|
|
#define ITER_SLOTS_END(R_OFFSET) \
|
|
R_OFFSET = (R_OFFSET + 1u) & OFFSET_MASK; \
|
|
} while (R_OFFSET != initial_offset); \
|
|
perturb >>= 5; \
|
|
hash = hash * 5 + 1 + perturb; \
|
|
} ((void)0)
|
|
|
|
// clang-format on
|
|
|
|
template<typename KeyT,
|
|
typename ValueT,
|
|
uint32_t InlineBufferCapacity = 4,
|
|
typename Allocator = GuardedAllocator>
|
|
class Map {
|
|
private:
|
|
static constexpr uint OFFSET_MASK = 3;
|
|
static constexpr uint OFFSET_SHIFT = 2;
|
|
|
|
class Item {
|
|
private:
|
|
static constexpr uint8_t IS_EMPTY = 0;
|
|
static constexpr uint8_t IS_SET = 1;
|
|
static constexpr uint8_t IS_DUMMY = 2;
|
|
|
|
uint8_t m_status[4];
|
|
AlignedBuffer<4 * sizeof(KeyT), alignof(KeyT)> m_keys_buffer;
|
|
AlignedBuffer<4 * sizeof(ValueT), alignof(ValueT)> m_values_buffer;
|
|
|
|
public:
|
|
static constexpr uint slots_per_item = 4;
|
|
|
|
Item()
|
|
{
|
|
for (uint offset = 0; offset < 4; offset++) {
|
|
m_status[offset] = IS_EMPTY;
|
|
}
|
|
}
|
|
|
|
~Item()
|
|
{
|
|
for (uint offset = 0; offset < 4; offset++) {
|
|
if (m_status[offset] == IS_SET) {
|
|
this->key(offset)->~KeyT();
|
|
this->value(offset)->~ValueT();
|
|
}
|
|
}
|
|
}
|
|
|
|
Item(const Item &other)
|
|
{
|
|
for (uint offset = 0; offset < 4; offset++) {
|
|
uint8_t status = other.m_status[offset];
|
|
m_status[offset] = status;
|
|
if (status == IS_SET) {
|
|
new (this->key(offset)) KeyT(*other.key(offset));
|
|
new (this->value(offset)) ValueT(*other.value(offset));
|
|
}
|
|
}
|
|
}
|
|
|
|
Item(Item &&other) noexcept
|
|
{
|
|
for (uint offset = 0; offset < 4; offset++) {
|
|
uint8_t status = other.m_status[offset];
|
|
m_status[offset] = status;
|
|
if (status == IS_SET) {
|
|
new (this->key(offset)) KeyT(std::move(*other.key(offset)));
|
|
new (this->value(offset)) ValueT(std::move(*other.value(offset)));
|
|
}
|
|
}
|
|
}
|
|
|
|
bool has_key(uint offset, const KeyT &key) const
|
|
{
|
|
return m_status[offset] == IS_SET && key == *this->key(offset);
|
|
}
|
|
|
|
bool is_set(uint offset) const
|
|
{
|
|
return m_status[offset] == IS_SET;
|
|
}
|
|
|
|
bool is_empty(uint offset) const
|
|
{
|
|
return m_status[offset] == IS_EMPTY;
|
|
}
|
|
|
|
bool is_dummy(uint offset) const
|
|
{
|
|
return m_status[offset] == IS_DUMMY;
|
|
}
|
|
|
|
KeyT *key(uint offset) const
|
|
{
|
|
return (KeyT *)m_keys_buffer.ptr() + offset;
|
|
}
|
|
|
|
ValueT *value(uint offset) const
|
|
{
|
|
return (ValueT *)m_values_buffer.ptr() + offset;
|
|
}
|
|
|
|
template<typename ForwardKeyT, typename ForwardValueT>
|
|
void store(uint offset, ForwardKeyT &&key, ForwardValueT &&value)
|
|
{
|
|
this->store_without_value(offset, std::forward<ForwardKeyT>(key));
|
|
new (this->value(offset)) ValueT(std::forward<ForwardValueT>(value));
|
|
}
|
|
|
|
template<typename ForwardKeyT> void store_without_value(uint offset, ForwardKeyT &&key)
|
|
{
|
|
BLI_assert(m_status[offset] != IS_SET);
|
|
m_status[offset] = IS_SET;
|
|
new (this->key(offset)) KeyT(std::forward<ForwardKeyT>(key));
|
|
}
|
|
|
|
void set_dummy(uint offset)
|
|
{
|
|
BLI_assert(m_status[offset] == IS_SET);
|
|
m_status[offset] = IS_DUMMY;
|
|
destruct(this->key(offset));
|
|
destruct(this->value(offset));
|
|
}
|
|
};
|
|
|
|
using ArrayType = OpenAddressingArray<Item, InlineBufferCapacity, Allocator>;
|
|
ArrayType m_array;
|
|
|
|
public:
|
|
Map() = default;
|
|
|
|
/**
|
|
* Allocate memory such that at least min_usable_slots can be added before the map has to grow
|
|
* again.
|
|
*/
|
|
void reserve(uint min_usable_slots)
|
|
{
|
|
if (m_array.slots_usable() < min_usable_slots) {
|
|
this->grow(min_usable_slots);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Remove all elements from the map.
|
|
*/
|
|
void clear()
|
|
{
|
|
this->~Map();
|
|
new (this) Map();
|
|
}
|
|
|
|
/**
|
|
* Insert a new key-value-pair in the map.
|
|
* Asserts when the key existed before.
|
|
*/
|
|
void add_new(const KeyT &key, const ValueT &value)
|
|
{
|
|
this->add_new__impl(key, value);
|
|
}
|
|
void add_new(const KeyT &key, ValueT &&value)
|
|
{
|
|
this->add_new__impl(key, std::move(value));
|
|
}
|
|
void add_new(KeyT &&key, const ValueT &value)
|
|
{
|
|
this->add_new__impl(std::move(key), value);
|
|
}
|
|
void add_new(KeyT &&key, ValueT &&value)
|
|
{
|
|
this->add_new__impl(std::move(key), std::move(value));
|
|
}
|
|
|
|
/**
|
|
* Insert a new key-value-pair in the map if the key does not exist yet.
|
|
* Returns true when the pair was newly inserted, otherwise false.
|
|
*/
|
|
bool add(const KeyT &key, const ValueT &value)
|
|
{
|
|
return this->add__impl(key, value);
|
|
}
|
|
bool add(const KeyT &key, ValueT &&value)
|
|
{
|
|
return this->add__impl(key, std::move(value));
|
|
}
|
|
bool add(KeyT &&key, const ValueT &value)
|
|
{
|
|
return this->add__impl(std::move(key), value);
|
|
}
|
|
bool add(KeyT &&key, ValueT &&value)
|
|
{
|
|
return this->add__impl(std::move(key), std::move(value));
|
|
}
|
|
|
|
/**
|
|
* Remove the key from the map.
|
|
* Asserts when the key does not exist in the map.
|
|
*/
|
|
void remove(const KeyT &key)
|
|
{
|
|
BLI_assert(this->contains(key));
|
|
ITER_SLOTS_BEGIN (key, m_array, , item, offset) {
|
|
if (item.has_key(offset, key)) {
|
|
item.set_dummy(offset);
|
|
m_array.update__set_to_dummy();
|
|
return;
|
|
}
|
|
}
|
|
ITER_SLOTS_END(offset);
|
|
}
|
|
|
|
/**
|
|
* Get the value for the given key and remove it from the map.
|
|
* Asserts when the key does not exist in the map.
|
|
*/
|
|
ValueT pop(const KeyT &key)
|
|
{
|
|
BLI_assert(this->contains(key));
|
|
ITER_SLOTS_BEGIN (key, m_array, , item, offset) {
|
|
if (item.has_key(offset, key)) {
|
|
ValueT value = std::move(*item.value(offset));
|
|
item.set_dummy(offset);
|
|
m_array.update__set_to_dummy();
|
|
return value;
|
|
}
|
|
}
|
|
ITER_SLOTS_END(offset);
|
|
}
|
|
|
|
/**
|
|
* Returns true when the key exists in the map, otherwise false.
|
|
*/
|
|
bool contains(const KeyT &key) const
|
|
{
|
|
ITER_SLOTS_BEGIN (key, m_array, const, item, offset) {
|
|
if (item.is_empty(offset)) {
|
|
return false;
|
|
}
|
|
else if (item.has_key(offset, key)) {
|
|
return true;
|
|
}
|
|
}
|
|
ITER_SLOTS_END(offset);
|
|
}
|
|
|
|
/**
|
|
* First, checks if the key exists in the map.
|
|
* If it does exist, call the modify function with a pointer to the corresponding value.
|
|
* If it does not exist, call the create function with a pointer to where the value should be
|
|
* created.
|
|
*
|
|
* Returns whatever is returned from one of the callback functions. Both callbacks have to return
|
|
* the same type.
|
|
*
|
|
* CreateValueF: Takes a pointer to where the value should be created.
|
|
* ModifyValueF: Takes a pointer to the value that should be modified.
|
|
*/
|
|
template<typename CreateValueF, typename ModifyValueF>
|
|
auto add_or_modify(const KeyT &key,
|
|
const CreateValueF &create_value,
|
|
const ModifyValueF &modify_value) -> decltype(create_value(nullptr))
|
|
{
|
|
return this->add_or_modify__impl(key, create_value, modify_value);
|
|
}
|
|
template<typename CreateValueF, typename ModifyValueF>
|
|
auto add_or_modify(KeyT &&key,
|
|
const CreateValueF &create_value,
|
|
const ModifyValueF &modify_value) -> decltype(create_value(nullptr))
|
|
{
|
|
return this->add_or_modify__impl(std::move(key), create_value, modify_value);
|
|
}
|
|
|
|
/**
|
|
* Similar to add, but overrides the value for the key when it exists already.
|
|
*/
|
|
bool add_override(const KeyT &key, const ValueT &value)
|
|
{
|
|
return this->add_override__impl(key, value);
|
|
}
|
|
bool add_override(const KeyT &key, ValueT &&value)
|
|
{
|
|
return this->add_override__impl(key, std::move(value));
|
|
}
|
|
bool add_override(KeyT &&key, const ValueT &value)
|
|
{
|
|
return this->add_override__impl(std::move(key), value);
|
|
}
|
|
bool add_override(KeyT &&key, ValueT &&value)
|
|
{
|
|
return this->add_override__impl(std::move(key), std::move(value));
|
|
}
|
|
|
|
/**
|
|
* Check if the key exists in the map.
|
|
* Return a pointer to the value, when it exists.
|
|
* Otherwise return nullptr.
|
|
*/
|
|
const ValueT *lookup_ptr(const KeyT &key) const
|
|
{
|
|
ITER_SLOTS_BEGIN (key, m_array, const, item, offset) {
|
|
if (item.is_empty(offset)) {
|
|
return nullptr;
|
|
}
|
|
else if (item.has_key(offset, key)) {
|
|
return item.value(offset);
|
|
}
|
|
}
|
|
ITER_SLOTS_END(offset);
|
|
}
|
|
|
|
ValueT *lookup_ptr(const KeyT &key)
|
|
{
|
|
const Map *const_this = this;
|
|
return const_cast<ValueT *>(const_this->lookup_ptr(key));
|
|
}
|
|
|
|
/**
|
|
* Lookup the value that corresponds to the key.
|
|
* Asserts when the key does not exist.
|
|
*/
|
|
const ValueT &lookup(const KeyT &key) const
|
|
{
|
|
const ValueT *ptr = this->lookup_ptr(key);
|
|
BLI_assert(ptr != nullptr);
|
|
return *ptr;
|
|
}
|
|
|
|
ValueT &lookup(const KeyT &key)
|
|
{
|
|
const Map *const_this = this;
|
|
return const_cast<ValueT &>(const_this->lookup(key));
|
|
}
|
|
|
|
/**
|
|
* Check if the key exists in the map.
|
|
* If it does, return a copy of the value.
|
|
* Otherwise, return the default value.
|
|
*/
|
|
ValueT lookup_default(const KeyT &key, ValueT default_value) const
|
|
{
|
|
const ValueT *ptr = this->lookup_ptr(key);
|
|
if (ptr != nullptr) {
|
|
return *ptr;
|
|
}
|
|
else {
|
|
return default_value;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Return the value that corresponds to the given key.
|
|
* If it does not exist yet, create and insert it first.
|
|
*/
|
|
template<typename CreateValueF>
|
|
ValueT &lookup_or_add(const KeyT &key, const CreateValueF &create_value)
|
|
{
|
|
return this->lookup_or_add__impl(key, create_value);
|
|
}
|
|
template<typename CreateValueF>
|
|
ValueT &lookup_or_add(KeyT &&key, const CreateValueF &create_value)
|
|
{
|
|
return this->lookup_or_add__impl(std::move(key), create_value);
|
|
}
|
|
|
|
/**
|
|
* Return the value that corresponds to the given key.
|
|
* If it does not exist yet, insert a new default constructed value and return that.
|
|
*/
|
|
ValueT &lookup_or_add_default(const KeyT &key)
|
|
{
|
|
return this->lookup_or_add(key, []() { return ValueT(); });
|
|
}
|
|
ValueT &lookup_or_add_default(const KeyT &&key)
|
|
{
|
|
return this->lookup_or_add(std::move(key), []() { return ValueT(); });
|
|
}
|
|
|
|
/**
|
|
* Get the number of elements in the map.
|
|
*/
|
|
uint32_t size() const
|
|
{
|
|
return m_array.slots_set();
|
|
}
|
|
|
|
/**
|
|
* Returns true if there are no elements in the map.
|
|
*/
|
|
bool is_empty() const
|
|
{
|
|
return this->size() == 0;
|
|
}
|
|
|
|
/**
|
|
* Calls the given function for each key-value-pair.
|
|
*/
|
|
template<typename FuncT> void foreach_item(const FuncT &func) const
|
|
{
|
|
for (const Item &item : m_array) {
|
|
for (uint offset = 0; offset < 4; offset++) {
|
|
if (item.is_set(offset)) {
|
|
const KeyT &key = *item.key(offset);
|
|
const ValueT &value = *item.value(offset);
|
|
func(key, value);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void print_table() const
|
|
{
|
|
std::cout << "Hash Table:\n";
|
|
std::cout << " Size: " << m_array.slots_set() << '\n';
|
|
std::cout << " Capacity: " << m_array.slots_total() << '\n';
|
|
uint32_t item_index = 0;
|
|
for (const Item &item : m_array) {
|
|
std::cout << " Item: " << item_index++ << '\n';
|
|
for (uint offset = 0; offset < 4; offset++) {
|
|
std::cout << " " << offset << " \t";
|
|
if (item.is_empty(offset)) {
|
|
std::cout << " <empty>\n";
|
|
}
|
|
else if (item.is_set(offset)) {
|
|
const KeyT &key = *item.key(offset);
|
|
const ValueT &value = *item.value(offset);
|
|
uint32_t collisions = this->count_collisions(key);
|
|
std::cout << " " << key << " -> " << value << " \t Collisions: " << collisions
|
|
<< '\n';
|
|
}
|
|
else if (item.is_dummy(offset)) {
|
|
std::cout << " <dummy>\n";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
template<typename SubIterator> class BaseIterator {
|
|
protected:
|
|
const Map *m_map;
|
|
uint32_t m_slot;
|
|
|
|
public:
|
|
BaseIterator(const Map *map, uint32_t slot) : m_map(map), m_slot(slot)
|
|
{
|
|
}
|
|
|
|
BaseIterator &operator++()
|
|
{
|
|
m_slot = m_map->next_slot(m_slot + 1);
|
|
return *this;
|
|
}
|
|
|
|
friend bool operator==(const BaseIterator &a, const BaseIterator &b)
|
|
{
|
|
BLI_assert(a.m_map == b.m_map);
|
|
return a.m_slot == b.m_slot;
|
|
}
|
|
|
|
friend bool operator!=(const BaseIterator &a, const BaseIterator &b)
|
|
{
|
|
return !(a == b);
|
|
}
|
|
|
|
SubIterator begin() const
|
|
{
|
|
return SubIterator(m_map, m_map->next_slot(0));
|
|
}
|
|
|
|
SubIterator end() const
|
|
{
|
|
return SubIterator(m_map, m_map->m_array.slots_total());
|
|
}
|
|
};
|
|
|
|
class KeyIterator final : public BaseIterator<KeyIterator> {
|
|
public:
|
|
KeyIterator(const Map *map, uint32_t slot) : BaseIterator<KeyIterator>(map, slot)
|
|
{
|
|
}
|
|
|
|
const KeyT &operator*() const
|
|
{
|
|
uint32_t item_index = this->m_slot >> OFFSET_SHIFT;
|
|
uint offset = this->m_slot & OFFSET_MASK;
|
|
const Item &item = this->m_map->m_array.item(item_index);
|
|
BLI_assert(item.is_set(offset));
|
|
return *item.key(offset);
|
|
}
|
|
};
|
|
|
|
class ValueIterator final : public BaseIterator<ValueIterator> {
|
|
public:
|
|
ValueIterator(const Map *map, uint32_t slot) : BaseIterator<ValueIterator>(map, slot)
|
|
{
|
|
}
|
|
|
|
ValueT &operator*() const
|
|
{
|
|
uint32_t item_index = this->m_slot >> OFFSET_SHIFT;
|
|
uint offset = this->m_slot & OFFSET_MASK;
|
|
const Item &item = this->m_map->m_array.item(item_index);
|
|
BLI_assert(item.is_set(offset));
|
|
return *item.value(offset);
|
|
}
|
|
};
|
|
|
|
class ItemIterator final : public BaseIterator<ItemIterator> {
|
|
public:
|
|
ItemIterator(const Map *map, uint32_t slot) : BaseIterator<ItemIterator>(map, slot)
|
|
{
|
|
}
|
|
|
|
struct UserItem {
|
|
const KeyT &key;
|
|
ValueT &value;
|
|
|
|
friend std::ostream &operator<<(std::ostream &stream, const Item &item)
|
|
{
|
|
stream << item.key << " -> " << item.value;
|
|
return stream;
|
|
}
|
|
};
|
|
|
|
UserItem operator*() const
|
|
{
|
|
uint32_t item_index = this->m_slot >> OFFSET_SHIFT;
|
|
uint offset = this->m_slot & OFFSET_MASK;
|
|
const Item &item = this->m_map->m_array.item(item_index);
|
|
BLI_assert(item.is_set(offset));
|
|
return {*item.key(offset), *item.value(offset)};
|
|
}
|
|
};
|
|
|
|
template<typename SubIterator> friend class BaseIterator;
|
|
|
|
/**
|
|
* Iterate over all keys in the map.
|
|
*/
|
|
KeyIterator keys() const
|
|
{
|
|
return KeyIterator(this, 0);
|
|
}
|
|
|
|
/**
|
|
* Iterate over all values in the map.
|
|
*/
|
|
ValueIterator values() const
|
|
{
|
|
return ValueIterator(this, 0);
|
|
}
|
|
|
|
/**
|
|
* Iterate over all key-value-pairs in the map.
|
|
* They can be accessed with item.key and item.value.
|
|
*/
|
|
ItemIterator items() const
|
|
{
|
|
return ItemIterator(this, 0);
|
|
}
|
|
|
|
private:
|
|
uint32_t next_slot(uint32_t slot) const
|
|
{
|
|
for (; slot < m_array.slots_total(); slot++) {
|
|
uint32_t item_index = slot >> OFFSET_SHIFT;
|
|
uint offset = slot & OFFSET_MASK;
|
|
const Item &item = m_array.item(item_index);
|
|
if (item.is_set(offset)) {
|
|
return slot;
|
|
}
|
|
}
|
|
return slot;
|
|
}
|
|
|
|
uint32_t count_collisions(const KeyT &key) const
|
|
{
|
|
uint32_t collisions = 0;
|
|
ITER_SLOTS_BEGIN (key, m_array, const, item, offset) {
|
|
if (item.is_empty(offset) || item.has_key(offset, key)) {
|
|
return collisions;
|
|
}
|
|
collisions++;
|
|
}
|
|
ITER_SLOTS_END(offset);
|
|
}
|
|
|
|
void ensure_can_add()
|
|
{
|
|
if (UNLIKELY(m_array.should_grow())) {
|
|
this->grow(this->size() + 1);
|
|
}
|
|
}
|
|
|
|
BLI_NOINLINE void grow(uint32_t min_usable_slots)
|
|
{
|
|
ArrayType new_array = m_array.init_reserved(min_usable_slots);
|
|
for (Item &old_item : m_array) {
|
|
for (uint offset = 0; offset < 4; offset++) {
|
|
if (old_item.is_set(offset)) {
|
|
this->add_after_grow(*old_item.key(offset), *old_item.value(offset), new_array);
|
|
}
|
|
}
|
|
}
|
|
m_array = std::move(new_array);
|
|
}
|
|
|
|
void add_after_grow(KeyT &key, ValueT &value, ArrayType &new_array)
|
|
{
|
|
ITER_SLOTS_BEGIN (key, new_array, , item, offset) {
|
|
if (item.is_empty(offset)) {
|
|
item.store(offset, std::move(key), std::move(value));
|
|
return;
|
|
}
|
|
}
|
|
ITER_SLOTS_END(offset);
|
|
}
|
|
|
|
template<typename ForwardKeyT, typename ForwardValueT>
|
|
bool add_override__impl(ForwardKeyT &&key, ForwardValueT &&value)
|
|
{
|
|
auto create_func = [&](ValueT *dst) {
|
|
new (dst) ValueT(std::forward<ForwardValueT>(value));
|
|
return true;
|
|
};
|
|
auto modify_func = [&](ValueT *old_value) {
|
|
*old_value = std::forward<ForwardValueT>(value);
|
|
return false;
|
|
};
|
|
return this->add_or_modify(std::forward<ForwardKeyT>(key), create_func, modify_func);
|
|
}
|
|
|
|
template<typename ForwardKeyT, typename ForwardValueT>
|
|
bool add__impl(ForwardKeyT &&key, ForwardValueT &&value)
|
|
{
|
|
this->ensure_can_add();
|
|
|
|
ITER_SLOTS_BEGIN (key, m_array, , item, offset) {
|
|
if (item.is_empty(offset)) {
|
|
item.store(offset, std::forward<ForwardKeyT>(key), std::forward<ForwardValueT>(value));
|
|
m_array.update__empty_to_set();
|
|
return true;
|
|
}
|
|
else if (item.has_key(offset, key)) {
|
|
return false;
|
|
}
|
|
}
|
|
ITER_SLOTS_END(offset);
|
|
}
|
|
|
|
template<typename ForwardKeyT, typename ForwardValueT>
|
|
void add_new__impl(ForwardKeyT &&key, ForwardValueT &&value)
|
|
{
|
|
BLI_assert(!this->contains(key));
|
|
this->ensure_can_add();
|
|
|
|
ITER_SLOTS_BEGIN (key, m_array, , item, offset) {
|
|
if (item.is_empty(offset)) {
|
|
item.store(offset, std::forward<ForwardKeyT>(key), std::forward<ForwardValueT>(value));
|
|
m_array.update__empty_to_set();
|
|
return;
|
|
}
|
|
}
|
|
ITER_SLOTS_END(offset);
|
|
}
|
|
|
|
template<typename ForwardKeyT, typename CreateValueF, typename ModifyValueF>
|
|
auto add_or_modify__impl(ForwardKeyT &&key,
|
|
const CreateValueF &create_value,
|
|
const ModifyValueF &modify_value) -> decltype(create_value(nullptr))
|
|
{
|
|
using CreateReturnT = decltype(create_value(nullptr));
|
|
using ModifyReturnT = decltype(modify_value(nullptr));
|
|
BLI_STATIC_ASSERT((std::is_same<CreateReturnT, ModifyReturnT>::value),
|
|
"Both callbacks should return the same type.");
|
|
|
|
this->ensure_can_add();
|
|
|
|
ITER_SLOTS_BEGIN (key, m_array, , item, offset) {
|
|
if (item.is_empty(offset)) {
|
|
m_array.update__empty_to_set();
|
|
item.store_without_value(offset, std::forward<ForwardKeyT>(key));
|
|
ValueT *value_ptr = item.value(offset);
|
|
return create_value(value_ptr);
|
|
}
|
|
else if (item.has_key(offset, key)) {
|
|
ValueT *value_ptr = item.value(offset);
|
|
return modify_value(value_ptr);
|
|
}
|
|
}
|
|
ITER_SLOTS_END(offset);
|
|
}
|
|
|
|
template<typename ForwardKeyT, typename CreateValueF>
|
|
ValueT &lookup_or_add__impl(ForwardKeyT &&key, const CreateValueF &create_value)
|
|
{
|
|
this->ensure_can_add();
|
|
|
|
ITER_SLOTS_BEGIN (key, m_array, , item, offset) {
|
|
if (item.is_empty(offset)) {
|
|
item.store(offset, std::forward<ForwardKeyT>(key), create_value());
|
|
m_array.update__empty_to_set();
|
|
return *item.value(offset);
|
|
}
|
|
else if (item.has_key(offset, key)) {
|
|
return *item.value(offset);
|
|
}
|
|
}
|
|
ITER_SLOTS_END(offset);
|
|
}
|
|
};
|
|
|
|
#undef ITER_SLOTS_BEGIN
|
|
#undef ITER_SLOTS_END
|
|
|
|
} // namespace BLI
|
|
|
|
#endif /* __BLI_MAP_HH__ */
|