Listing the "Blender Foundation" as copyright holder implied the Blender Foundation holds copyright to files which may include work from many developers. While keeping copyright on headers makes sense for isolated libraries, Blender's own code may be refactored or moved between files in a way that makes the per file copyright holders less meaningful. Copyright references to the "Blender Foundation" have been replaced with "Blender Authors", with the exception of `./extern/` since these this contains libraries which are more isolated, any changed to license headers there can be handled on a case-by-case basis. Some directories in `./intern/` have also been excluded: - `./intern/cycles/` it's own `AUTHORS` file is planned. - `./intern/opensubdiv/`. An "AUTHORS" file has been added, using the chromium projects authors file as a template. Design task: #110784 Ref !110783.
370 lines
9.1 KiB
C++
370 lines
9.1 KiB
C++
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0-or-later */
|
|
|
|
#pragma once
|
|
|
|
/** \file
|
|
* \ingroup bli
|
|
*
|
|
* This file contains different slot types that are supposed to be used with blender::Set.
|
|
*
|
|
* Every slot type has to be able to hold a value of the Key type and state information.
|
|
* A set slot has three possible states: empty, occupied and removed.
|
|
*
|
|
* Only when a slot is occupied, it stores an instance of type Key.
|
|
*
|
|
* A set slot type has to implement a couple of methods that are explained in SimpleSetSlot.
|
|
* A slot type is assumed to be trivially destructible, when it is not in occupied state. So the
|
|
* destructor might not be called in that case.
|
|
*/
|
|
|
|
#include "BLI_memory_utils.hh"
|
|
#include "BLI_string_ref.hh"
|
|
|
|
namespace blender {
|
|
|
|
/**
|
|
* The simplest possible set slot. It stores the slot state and the optional key instance in
|
|
* separate variables. Depending on the alignment requirement of the key, many bytes might be
|
|
* wasted.
|
|
*/
|
|
template<typename Key> class SimpleSetSlot {
|
|
private:
|
|
enum State : uint8_t {
|
|
Empty = 0,
|
|
Occupied = 1,
|
|
Removed = 2,
|
|
};
|
|
|
|
State state_;
|
|
TypedBuffer<Key> key_buffer_;
|
|
|
|
public:
|
|
/**
|
|
* After the default constructor has run, the slot has to be in the empty state.
|
|
*/
|
|
SimpleSetSlot()
|
|
{
|
|
state_ = Empty;
|
|
}
|
|
|
|
/**
|
|
* The destructor also has to destruct the key, if the slot is currently occupied.
|
|
*/
|
|
~SimpleSetSlot()
|
|
{
|
|
if (state_ == Occupied) {
|
|
key_buffer_.ref().~Key();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* The copy constructor has to copy the state. If the other slot was occupied, a copy of the key
|
|
* has to be made as well.
|
|
*/
|
|
SimpleSetSlot(const SimpleSetSlot &other)
|
|
{
|
|
state_ = other.state_;
|
|
if (other.state_ == Occupied) {
|
|
new (&key_buffer_) Key(*other.key_buffer_);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* The move constructor has to copy the state. If the other slot was occupied, the key from the
|
|
* other slot has to be moved as well. The other slot stays in the state it was in before. Its
|
|
* optionally stored key remains in a moved-from state.
|
|
*/
|
|
SimpleSetSlot(SimpleSetSlot &&other) noexcept(std::is_nothrow_move_constructible_v<Key>)
|
|
{
|
|
state_ = other.state_;
|
|
if (other.state_ == Occupied) {
|
|
new (&key_buffer_) Key(std::move(*other.key_buffer_));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get a non-const pointer to the position where the key is stored.
|
|
*/
|
|
Key *key()
|
|
{
|
|
return key_buffer_;
|
|
}
|
|
|
|
/**
|
|
* Get a const pointer to the position where the key is stored.
|
|
*/
|
|
const Key *key() const
|
|
{
|
|
return key_buffer_;
|
|
}
|
|
|
|
/**
|
|
* Return true if the slot currently contains a key.
|
|
*/
|
|
bool is_occupied() const
|
|
{
|
|
return state_ == Occupied;
|
|
}
|
|
|
|
/**
|
|
* Return true if the slot is empty, i.e. it does not contain a key and is not in removed state.
|
|
*/
|
|
bool is_empty() const
|
|
{
|
|
return state_ == Empty;
|
|
}
|
|
|
|
/**
|
|
* Return the hash of the currently stored key. In this simple set slot implementation, we just
|
|
* compute the hash here. Other implementations might store the hash in the slot instead.
|
|
*/
|
|
template<typename Hash> uint64_t get_hash(const Hash &hash) const
|
|
{
|
|
BLI_assert(this->is_occupied());
|
|
return hash(*key_buffer_);
|
|
}
|
|
|
|
/**
|
|
* Return true, when this slot is occupied and contains a key that compares equal to the given
|
|
* key. The hash is used by other slot implementations to determine inequality faster.
|
|
*/
|
|
template<typename ForwardKey, typename IsEqual>
|
|
bool contains(const ForwardKey &key, const IsEqual &is_equal, uint64_t /*hash*/) const
|
|
{
|
|
if (state_ == Occupied) {
|
|
return is_equal(key, *key_buffer_);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Change the state of this slot from empty/removed to occupied. The key has to be constructed
|
|
* by calling the constructor with the given key as parameter.
|
|
*/
|
|
template<typename ForwardKey> void occupy(ForwardKey &&key, uint64_t /*hash*/)
|
|
{
|
|
BLI_assert(!this->is_occupied());
|
|
new (&key_buffer_) Key(std::forward<ForwardKey>(key));
|
|
state_ = Occupied;
|
|
}
|
|
|
|
/**
|
|
* Change the state of this slot from occupied to removed. The key has to be destructed as well.
|
|
*/
|
|
void remove()
|
|
{
|
|
BLI_assert(this->is_occupied());
|
|
key_buffer_.ref().~Key();
|
|
state_ = Removed;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* This set slot implementation stores the hash of the key within the slot. This helps when
|
|
* computing the hash or an equality check is expensive.
|
|
*/
|
|
template<typename Key> class HashedSetSlot {
|
|
private:
|
|
enum State : uint8_t {
|
|
Empty = 0,
|
|
Occupied = 1,
|
|
Removed = 2,
|
|
};
|
|
|
|
uint64_t hash_;
|
|
State state_;
|
|
TypedBuffer<Key> key_buffer_;
|
|
|
|
public:
|
|
HashedSetSlot()
|
|
{
|
|
state_ = Empty;
|
|
}
|
|
|
|
~HashedSetSlot()
|
|
{
|
|
if (state_ == Occupied) {
|
|
key_buffer_.ref().~Key();
|
|
}
|
|
}
|
|
|
|
HashedSetSlot(const HashedSetSlot &other)
|
|
{
|
|
state_ = other.state_;
|
|
if (other.state_ == Occupied) {
|
|
hash_ = other.hash_;
|
|
new (&key_buffer_) Key(*other.key_buffer_);
|
|
}
|
|
}
|
|
|
|
HashedSetSlot(HashedSetSlot &&other) noexcept(std::is_nothrow_move_constructible_v<Key>)
|
|
{
|
|
state_ = other.state_;
|
|
if (other.state_ == Occupied) {
|
|
hash_ = other.hash_;
|
|
new (&key_buffer_) Key(std::move(*other.key_buffer_));
|
|
}
|
|
}
|
|
|
|
Key *key()
|
|
{
|
|
return key_buffer_;
|
|
}
|
|
|
|
const Key *key() const
|
|
{
|
|
return key_buffer_;
|
|
}
|
|
|
|
bool is_occupied() const
|
|
{
|
|
return state_ == Occupied;
|
|
}
|
|
|
|
bool is_empty() const
|
|
{
|
|
return state_ == Empty;
|
|
}
|
|
|
|
template<typename Hash> uint64_t get_hash(const Hash & /*hash*/) const
|
|
{
|
|
BLI_assert(this->is_occupied());
|
|
return hash_;
|
|
}
|
|
|
|
template<typename ForwardKey, typename IsEqual>
|
|
bool contains(const ForwardKey &key, const IsEqual &is_equal, const uint64_t hash) const
|
|
{
|
|
/* `hash_` might be uninitialized here, but that is ok. */
|
|
if (hash_ == hash) {
|
|
if (state_ == Occupied) {
|
|
return is_equal(key, *key_buffer_);
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
template<typename ForwardKey> void occupy(ForwardKey &&key, const uint64_t hash)
|
|
{
|
|
BLI_assert(!this->is_occupied());
|
|
new (&key_buffer_) Key(std::forward<ForwardKey>(key));
|
|
state_ = Occupied;
|
|
hash_ = hash;
|
|
}
|
|
|
|
void remove()
|
|
{
|
|
BLI_assert(this->is_occupied());
|
|
key_buffer_.ref().~Key();
|
|
state_ = Removed;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* An IntrusiveSetSlot uses two special values of the key to indicate whether the slot is empty or
|
|
* removed. This saves some memory in all cases and is more efficient in many cases. The KeyInfo
|
|
* type indicates which specific values are used. An example for a KeyInfo implementation is
|
|
* PointerKeyInfo.
|
|
*
|
|
* The special key values are expected to be trivially destructible.
|
|
*/
|
|
template<typename Key, typename KeyInfo> class IntrusiveSetSlot {
|
|
private:
|
|
Key key_ = KeyInfo::get_empty();
|
|
|
|
public:
|
|
IntrusiveSetSlot() = default;
|
|
~IntrusiveSetSlot() = default;
|
|
IntrusiveSetSlot(const IntrusiveSetSlot &other) = default;
|
|
IntrusiveSetSlot(IntrusiveSetSlot &&other) noexcept(std::is_nothrow_move_constructible_v<Key>) =
|
|
default;
|
|
|
|
Key *key()
|
|
{
|
|
return &key_;
|
|
}
|
|
|
|
const Key *key() const
|
|
{
|
|
return &key_;
|
|
}
|
|
|
|
bool is_occupied() const
|
|
{
|
|
return KeyInfo::is_not_empty_or_removed(key_);
|
|
}
|
|
|
|
bool is_empty() const
|
|
{
|
|
return KeyInfo::is_empty(key_);
|
|
}
|
|
|
|
template<typename Hash> uint64_t get_hash(const Hash &hash) const
|
|
{
|
|
BLI_assert(this->is_occupied());
|
|
return hash(key_);
|
|
}
|
|
|
|
template<typename ForwardKey, typename IsEqual>
|
|
bool contains(const ForwardKey &key, const IsEqual &is_equal, const uint64_t /*hash*/) const
|
|
{
|
|
BLI_assert(KeyInfo::is_not_empty_or_removed(key));
|
|
return is_equal(key_, key);
|
|
}
|
|
|
|
template<typename ForwardKey> void occupy(ForwardKey &&key, const uint64_t /*hash*/)
|
|
{
|
|
BLI_assert(!this->is_occupied());
|
|
BLI_assert(KeyInfo::is_not_empty_or_removed(key));
|
|
key_ = std::forward<ForwardKey>(key);
|
|
}
|
|
|
|
void remove()
|
|
{
|
|
BLI_assert(this->is_occupied());
|
|
KeyInfo::remove(key_);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* This exists just to make it more convenient to define which special integer values can be used
|
|
* to indicate an empty and removed value.
|
|
*/
|
|
template<typename Int, Int EmptyValue, Int RemovedValue>
|
|
using IntegerSetSlot = IntrusiveSetSlot<Int, TemplatedKeyInfo<Int, EmptyValue, RemovedValue>>;
|
|
|
|
template<typename Key> struct DefaultSetSlot;
|
|
|
|
/**
|
|
* Use SimpleSetSlot by default, because it is the smallest slot type that works for all key types.
|
|
*/
|
|
template<typename Key> struct DefaultSetSlot {
|
|
using type = SimpleSetSlot<Key>;
|
|
};
|
|
|
|
/**
|
|
* Store the hash of a string in the slot by default. Recomputing the hash or doing string
|
|
* comparisons can be relatively costly.
|
|
*/
|
|
template<> struct DefaultSetSlot<std::string> {
|
|
using type = HashedSetSlot<std::string>;
|
|
};
|
|
template<> struct DefaultSetSlot<StringRef> {
|
|
using type = HashedSetSlot<StringRef>;
|
|
};
|
|
template<> struct DefaultSetSlot<StringRefNull> {
|
|
using type = HashedSetSlot<StringRefNull>;
|
|
};
|
|
|
|
/**
|
|
* Use a special slot type for pointer keys, because we can store whether a slot is empty or
|
|
* removed with special pointer values.
|
|
*/
|
|
template<typename Key> struct DefaultSetSlot<Key *> {
|
|
using type = IntrusiveSetSlot<Key *, PointerKeyInfo<Key *>>;
|
|
};
|
|
|
|
} // namespace blender
|