2019-09-13 10:06:02 +02:00
|
|
|
/*
|
|
|
|
* 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.
|
|
|
|
*/
|
|
|
|
|
2020-04-21 17:31:56 +02:00
|
|
|
#ifndef __BLI_VECTOR_SET_HH__
|
|
|
|
#define __BLI_VECTOR_SET_HH__
|
2019-09-13 21:12:26 +10:00
|
|
|
|
2019-09-13 10:06:02 +02:00
|
|
|
/** \file
|
|
|
|
* \ingroup bli
|
|
|
|
*
|
2019-09-14 12:37:58 +02:00
|
|
|
* A VectorSet is a set built on top of a vector. The elements are stored in a continuous array,
|
|
|
|
* but every element exists at most once. The insertion order is maintained, as long as there are
|
|
|
|
* no deletes. The expected time to check if a value is in the VectorSet is O(1).
|
2019-09-13 10:06:02 +02:00
|
|
|
*/
|
|
|
|
|
2020-04-21 17:31:56 +02:00
|
|
|
#include "BLI_hash.hh"
|
|
|
|
#include "BLI_open_addressing.hh"
|
|
|
|
#include "BLI_vector.hh"
|
2019-09-13 10:06:02 +02:00
|
|
|
|
|
|
|
namespace BLI {
|
|
|
|
|
|
|
|
// clang-format off
|
|
|
|
|
|
|
|
#define ITER_SLOTS_BEGIN(VALUE, ARRAY, OPTIONAL_CONST, R_SLOT) \
|
|
|
|
uint32_t hash = DefaultHash<T>{}(VALUE); \
|
|
|
|
uint32_t perturb = hash; \
|
|
|
|
while (true) { \
|
|
|
|
for (uint i = 0; i < 4; i++) {\
|
|
|
|
uint32_t slot_index = (hash + i) & ARRAY.slot_mask(); \
|
|
|
|
OPTIONAL_CONST Slot &R_SLOT = ARRAY.item(slot_index);
|
|
|
|
|
|
|
|
#define ITER_SLOTS_END \
|
|
|
|
} \
|
|
|
|
perturb >>= 5; \
|
|
|
|
hash = hash * 5 + 1 + perturb; \
|
|
|
|
} ((void)0)
|
|
|
|
|
|
|
|
// clang-format on
|
|
|
|
|
2019-09-14 12:37:58 +02:00
|
|
|
template<typename T, typename Allocator = GuardedAllocator> class VectorSet {
|
2019-09-13 10:06:02 +02:00
|
|
|
private:
|
|
|
|
static constexpr int32_t IS_EMPTY = -1;
|
|
|
|
static constexpr int32_t IS_DUMMY = -2;
|
|
|
|
|
|
|
|
class Slot {
|
|
|
|
private:
|
|
|
|
int32_t m_value = IS_EMPTY;
|
|
|
|
|
|
|
|
public:
|
|
|
|
static constexpr uint slots_per_item = 1;
|
|
|
|
|
|
|
|
bool is_set() const
|
|
|
|
{
|
|
|
|
return m_value >= 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool is_empty() const
|
|
|
|
{
|
|
|
|
return m_value == IS_EMPTY;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool is_dummy() const
|
|
|
|
{
|
|
|
|
return m_value == IS_DUMMY;
|
|
|
|
}
|
|
|
|
|
2020-04-23 11:57:58 +02:00
|
|
|
bool has_value(const T &value, const T *elements) const
|
2019-09-13 10:06:02 +02:00
|
|
|
{
|
|
|
|
return this->is_set() && elements[this->index()] == value;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool has_index(uint index) const
|
|
|
|
{
|
|
|
|
return m_value == (int32_t)index;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint index() const
|
|
|
|
{
|
|
|
|
BLI_assert(this->is_set());
|
2019-09-13 11:03:49 +02:00
|
|
|
return (uint)m_value;
|
2019-09-13 10:06:02 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
int32_t &index_ref()
|
|
|
|
{
|
|
|
|
return m_value;
|
|
|
|
}
|
|
|
|
|
|
|
|
void set_index(uint index)
|
|
|
|
{
|
|
|
|
BLI_assert(!this->is_set());
|
2019-09-13 11:03:49 +02:00
|
|
|
m_value = (int32_t)index;
|
2019-09-13 10:06:02 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void set_dummy()
|
|
|
|
{
|
|
|
|
BLI_assert(this->is_set());
|
|
|
|
m_value = IS_DUMMY;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
using ArrayType = OpenAddressingArray<Slot, 4, Allocator>;
|
|
|
|
ArrayType m_array;
|
2020-04-23 11:57:58 +02:00
|
|
|
|
|
|
|
/* The capacity of the array should always be at least m_array.slots_usable(). */
|
|
|
|
T *m_elements = nullptr;
|
2019-09-13 10:06:02 +02:00
|
|
|
|
|
|
|
public:
|
2019-09-14 14:41:19 +02:00
|
|
|
VectorSet()
|
|
|
|
{
|
2020-04-23 11:57:58 +02:00
|
|
|
m_elements = this->allocate_elements_array(m_array.slots_usable());
|
2019-09-14 14:41:19 +02:00
|
|
|
}
|
2019-09-13 10:06:02 +02:00
|
|
|
|
2019-09-14 14:41:19 +02:00
|
|
|
VectorSet(ArrayRef<T> values) : VectorSet()
|
2019-09-13 10:06:02 +02:00
|
|
|
{
|
|
|
|
this->add_multiple(values);
|
|
|
|
}
|
|
|
|
|
2019-09-14 14:41:19 +02:00
|
|
|
VectorSet(const std::initializer_list<T> &values) : VectorSet()
|
2019-09-13 10:06:02 +02:00
|
|
|
{
|
|
|
|
this->add_multiple(values);
|
|
|
|
}
|
|
|
|
|
2019-09-14 14:41:19 +02:00
|
|
|
VectorSet(const Vector<T> &values) : VectorSet()
|
2019-09-13 10:06:02 +02:00
|
|
|
{
|
|
|
|
this->add_multiple(values);
|
|
|
|
}
|
|
|
|
|
2020-04-23 11:57:58 +02:00
|
|
|
VectorSet(const VectorSet &other) : m_array(other.m_array)
|
|
|
|
{
|
|
|
|
m_elements = this->allocate_elements_array(m_array.slots_usable());
|
|
|
|
copy_n(other.m_elements, m_array.slots_set(), m_elements);
|
|
|
|
}
|
|
|
|
|
|
|
|
VectorSet(VectorSet &&other) : m_array(std::move(other.m_array)), m_elements(other.m_elements)
|
|
|
|
{
|
|
|
|
other.m_elements = other.allocate_elements_array(other.m_array.slots_usable());
|
|
|
|
}
|
|
|
|
|
|
|
|
~VectorSet()
|
|
|
|
{
|
2020-04-23 20:05:53 +02:00
|
|
|
destruct_n(m_elements, this->size());
|
2020-04-23 11:57:58 +02:00
|
|
|
this->deallocate_elements_array(m_elements);
|
|
|
|
}
|
|
|
|
|
|
|
|
VectorSet &operator=(const VectorSet &other)
|
|
|
|
{
|
|
|
|
if (this == &other) {
|
|
|
|
return *this;
|
|
|
|
}
|
|
|
|
this->~VectorSet();
|
|
|
|
new (this) VectorSet(other);
|
|
|
|
return *this;
|
|
|
|
}
|
|
|
|
|
|
|
|
VectorSet &operator=(VectorSet &&other)
|
|
|
|
{
|
|
|
|
if (this == &other) {
|
|
|
|
return *this;
|
|
|
|
}
|
|
|
|
this->~VectorSet();
|
|
|
|
new (this) VectorSet(std::move(other));
|
|
|
|
return *this;
|
|
|
|
}
|
|
|
|
|
2019-09-13 12:09:06 +02:00
|
|
|
/**
|
|
|
|
* Allocate memory such that at least min_usable_slots can be added without having to grow again.
|
|
|
|
*/
|
|
|
|
void reserve(uint min_usable_slots)
|
|
|
|
{
|
|
|
|
if (m_array.slots_usable() < min_usable_slots) {
|
|
|
|
this->grow(min_usable_slots);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-09-13 10:06:02 +02:00
|
|
|
/**
|
|
|
|
* Add a new element. The method assumes that the value did not exist before.
|
|
|
|
*/
|
|
|
|
void add_new(const T &value)
|
|
|
|
{
|
2019-09-14 12:11:14 +02:00
|
|
|
this->add_new__impl(value);
|
|
|
|
}
|
|
|
|
void add_new(T &&value)
|
|
|
|
{
|
|
|
|
this->add_new__impl(std::move(value));
|
2019-09-13 10:06:02 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Add a new element if it does not exist yet. Does not add the value again if it exists already.
|
|
|
|
*/
|
|
|
|
bool add(const T &value)
|
|
|
|
{
|
2019-09-14 12:11:14 +02:00
|
|
|
return this->add__impl(value);
|
|
|
|
}
|
|
|
|
bool add(T &&value)
|
|
|
|
{
|
|
|
|
return this->add__impl(std::move(value));
|
2019-09-13 10:06:02 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Add multiple values. Duplicates will not be inserted.
|
|
|
|
*/
|
|
|
|
void add_multiple(ArrayRef<T> values)
|
|
|
|
{
|
|
|
|
for (const T &value : values) {
|
|
|
|
this->add(value);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns true when the value is in the set-vector, otherwise false.
|
|
|
|
*/
|
|
|
|
bool contains(const T &value) const
|
|
|
|
{
|
|
|
|
ITER_SLOTS_BEGIN (value, m_array, const, slot) {
|
|
|
|
if (slot.is_empty()) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
else if (slot.has_value(value, m_elements)) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
ITER_SLOTS_END;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Remove a value from the set-vector. The method assumes that the value exists.
|
|
|
|
*/
|
|
|
|
void remove(const T &value)
|
|
|
|
{
|
|
|
|
BLI_assert(this->contains(value));
|
|
|
|
ITER_SLOTS_BEGIN (value, m_array, , slot) {
|
|
|
|
if (slot.has_value(value, m_elements)) {
|
2020-04-23 20:05:53 +02:00
|
|
|
uint old_index = this->size() - 1;
|
2019-09-13 10:06:02 +02:00
|
|
|
uint new_index = slot.index();
|
|
|
|
|
2020-04-23 11:57:58 +02:00
|
|
|
if (new_index < old_index) {
|
|
|
|
m_elements[new_index] = std::move(m_elements[old_index]);
|
|
|
|
this->update_slot_index(m_elements[new_index], old_index, new_index);
|
|
|
|
}
|
|
|
|
|
|
|
|
destruct(m_elements + old_index);
|
2019-09-13 10:06:02 +02:00
|
|
|
slot.set_dummy();
|
|
|
|
m_array.update__set_to_dummy();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
ITER_SLOTS_END;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get and remove the last element of the vector.
|
|
|
|
*/
|
|
|
|
T pop()
|
|
|
|
{
|
|
|
|
BLI_assert(this->size() > 0);
|
2020-04-23 20:05:53 +02:00
|
|
|
uint index_to_pop = this->size() - 1;
|
2020-04-23 11:57:58 +02:00
|
|
|
T value = std::move(m_elements[index_to_pop]);
|
|
|
|
destruct(m_elements + index_to_pop);
|
2019-09-13 10:06:02 +02:00
|
|
|
|
|
|
|
ITER_SLOTS_BEGIN (value, m_array, , slot) {
|
2020-04-23 11:57:58 +02:00
|
|
|
if (slot.has_index(index_to_pop)) {
|
2019-09-13 10:06:02 +02:00
|
|
|
slot.set_dummy();
|
|
|
|
m_array.update__set_to_dummy();
|
|
|
|
return value;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
ITER_SLOTS_END;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the index of the value in the vector. It is assumed that the value is in the vector.
|
|
|
|
*/
|
|
|
|
uint index(const T &value) const
|
|
|
|
{
|
|
|
|
BLI_assert(this->contains(value));
|
|
|
|
ITER_SLOTS_BEGIN (value, m_array, const, slot) {
|
|
|
|
if (slot.has_value(value, m_elements)) {
|
|
|
|
return slot.index();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
ITER_SLOTS_END;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the index of the value in the vector. If it does not exist return -1.
|
|
|
|
*/
|
|
|
|
int index_try(const T &value) const
|
|
|
|
{
|
|
|
|
ITER_SLOTS_BEGIN (value, m_array, const, slot) {
|
|
|
|
if (slot.has_value(value, m_elements)) {
|
|
|
|
return slot.index();
|
|
|
|
}
|
|
|
|
else if (slot.is_empty()) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
ITER_SLOTS_END;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the number of elements in the set-vector.
|
|
|
|
*/
|
|
|
|
uint size() const
|
|
|
|
{
|
|
|
|
return m_array.slots_set();
|
|
|
|
}
|
|
|
|
|
|
|
|
const T *begin() const
|
|
|
|
{
|
2020-04-23 11:57:58 +02:00
|
|
|
return m_elements;
|
2019-09-13 10:06:02 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
const T *end() const
|
|
|
|
{
|
2020-04-23 20:05:53 +02:00
|
|
|
return m_elements + this->size();
|
2019-09-13 10:06:02 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
const T &operator[](uint index) const
|
|
|
|
{
|
2020-04-23 20:05:53 +02:00
|
|
|
BLI_assert(index <= this->size());
|
2019-09-13 10:06:02 +02:00
|
|
|
return m_elements[index];
|
|
|
|
}
|
|
|
|
|
2020-02-10 13:54:57 +01:00
|
|
|
ArrayRef<T> as_ref() const
|
2019-09-13 10:06:02 +02:00
|
|
|
{
|
2020-02-10 13:54:57 +01:00
|
|
|
return *this;
|
2019-09-13 10:06:02 +02:00
|
|
|
}
|
|
|
|
|
2020-02-10 13:54:57 +01:00
|
|
|
operator ArrayRef<T>() const
|
2019-09-13 10:06:02 +02:00
|
|
|
{
|
2020-04-23 20:05:53 +02:00
|
|
|
return ArrayRef<T>(m_elements, this->size());
|
2019-09-13 10:06:02 +02:00
|
|
|
}
|
|
|
|
|
2019-09-14 15:03:25 +02:00
|
|
|
void print_stats() const
|
|
|
|
{
|
|
|
|
std::cout << "VectorSet at " << (void *)this << ":\n";
|
|
|
|
std::cout << " Size: " << this->size() << "\n";
|
|
|
|
std::cout << " Usable Slots: " << m_array.slots_usable() << "\n";
|
|
|
|
std::cout << " Total Slots: " << m_array.slots_total() << "\n";
|
|
|
|
std::cout << " Average Collisions: " << this->compute_average_collisions() << "\n";
|
|
|
|
}
|
|
|
|
|
2019-09-13 10:06:02 +02:00
|
|
|
private:
|
|
|
|
void update_slot_index(T &value, uint old_index, uint new_index)
|
|
|
|
{
|
|
|
|
ITER_SLOTS_BEGIN (value, m_array, , slot) {
|
|
|
|
int32_t &stored_index = slot.index_ref();
|
|
|
|
if (stored_index == old_index) {
|
|
|
|
stored_index = new_index;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
ITER_SLOTS_END;
|
|
|
|
}
|
|
|
|
|
2019-09-14 12:11:14 +02:00
|
|
|
template<typename ForwardT> void add_new_in_slot(Slot &slot, ForwardT &&value)
|
2019-09-13 10:06:02 +02:00
|
|
|
{
|
2020-04-23 20:05:53 +02:00
|
|
|
uint index = this->size();
|
2019-09-13 10:06:02 +02:00
|
|
|
slot.set_index(index);
|
2020-04-23 11:57:58 +02:00
|
|
|
new (m_elements + index) T(std::forward<ForwardT>(value));
|
2019-09-13 10:06:02 +02:00
|
|
|
m_array.update__empty_to_set();
|
|
|
|
}
|
|
|
|
|
|
|
|
void ensure_can_add()
|
|
|
|
{
|
|
|
|
if (UNLIKELY(m_array.should_grow())) {
|
|
|
|
this->grow(this->size() + 1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
BLI_NOINLINE void grow(uint min_usable_slots)
|
|
|
|
{
|
2020-04-23 11:57:58 +02:00
|
|
|
uint size = this->size();
|
|
|
|
|
2019-09-13 10:06:02 +02:00
|
|
|
ArrayType new_array = m_array.init_reserved(min_usable_slots);
|
2020-04-23 11:57:58 +02:00
|
|
|
T *new_elements = this->allocate_elements_array(new_array.slots_usable());
|
2019-09-13 10:06:02 +02:00
|
|
|
|
2020-04-23 11:57:58 +02:00
|
|
|
for (uint i : IndexRange(size)) {
|
2019-09-13 10:06:02 +02:00
|
|
|
this->add_after_grow(i, new_array);
|
|
|
|
}
|
|
|
|
|
2020-04-23 11:57:58 +02:00
|
|
|
uninitialized_relocate_n(m_elements, size, new_elements);
|
|
|
|
this->deallocate_elements_array(m_elements);
|
|
|
|
|
2019-09-13 10:06:02 +02:00
|
|
|
m_array = std::move(new_array);
|
2020-04-23 11:57:58 +02:00
|
|
|
m_elements = new_elements;
|
2019-09-13 10:06:02 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void add_after_grow(uint index, ArrayType &new_array)
|
|
|
|
{
|
|
|
|
const T &value = m_elements[index];
|
|
|
|
ITER_SLOTS_BEGIN (value, new_array, , slot) {
|
|
|
|
if (slot.is_empty()) {
|
|
|
|
slot.set_index(index);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
ITER_SLOTS_END;
|
|
|
|
}
|
2019-09-14 12:11:14 +02:00
|
|
|
|
2019-09-14 15:03:25 +02:00
|
|
|
float compute_average_collisions() const
|
|
|
|
{
|
2020-04-23 11:57:58 +02:00
|
|
|
if (this->size() == 0) {
|
2019-09-14 15:03:25 +02:00
|
|
|
return 0.0f;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint collisions_sum = 0;
|
2020-04-23 11:57:58 +02:00
|
|
|
for (const T &value : this->as_ref()) {
|
2019-09-14 15:03:25 +02:00
|
|
|
collisions_sum += this->count_collisions(value);
|
|
|
|
}
|
2020-04-23 11:57:58 +02:00
|
|
|
return (float)collisions_sum / (float)this->size();
|
2019-09-14 15:03:25 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
uint count_collisions(const T &value) const
|
|
|
|
{
|
|
|
|
uint collisions = 0;
|
|
|
|
ITER_SLOTS_BEGIN (value, m_array, const, slot) {
|
|
|
|
if (slot.is_empty() || slot.has_value(value, m_elements)) {
|
|
|
|
return collisions;
|
|
|
|
}
|
|
|
|
collisions++;
|
|
|
|
}
|
|
|
|
ITER_SLOTS_END;
|
|
|
|
}
|
|
|
|
|
2019-09-14 12:11:14 +02:00
|
|
|
template<typename ForwardT> void add_new__impl(ForwardT &&value)
|
|
|
|
{
|
|
|
|
BLI_assert(!this->contains(value));
|
|
|
|
this->ensure_can_add();
|
|
|
|
ITER_SLOTS_BEGIN (value, m_array, , slot) {
|
|
|
|
if (slot.is_empty()) {
|
|
|
|
this->add_new_in_slot(slot, std::forward<ForwardT>(value));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
ITER_SLOTS_END;
|
|
|
|
}
|
|
|
|
|
|
|
|
template<typename ForwardT> bool add__impl(ForwardT &&value)
|
|
|
|
{
|
|
|
|
this->ensure_can_add();
|
|
|
|
ITER_SLOTS_BEGIN (value, m_array, , slot) {
|
|
|
|
if (slot.is_empty()) {
|
|
|
|
this->add_new_in_slot(slot, std::forward<ForwardT>(value));
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
else if (slot.has_value(value, m_elements)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
ITER_SLOTS_END;
|
|
|
|
}
|
2020-04-23 11:57:58 +02:00
|
|
|
|
|
|
|
T *allocate_elements_array(uint size)
|
|
|
|
{
|
|
|
|
return (T *)m_array.allocator().allocate_aligned((uint)sizeof(T) * size, alignof(T), __func__);
|
|
|
|
}
|
|
|
|
|
|
|
|
void deallocate_elements_array(T *elements)
|
|
|
|
{
|
|
|
|
m_array.allocator().deallocate(elements);
|
|
|
|
}
|
2019-09-13 10:06:02 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
#undef ITER_SLOTS_BEGIN
|
|
|
|
#undef ITER_SLOTS_END
|
|
|
|
|
|
|
|
} // namespace BLI
|
2019-09-13 21:12:26 +10:00
|
|
|
|
2020-04-21 17:31:56 +02:00
|
|
|
#endif /* __BLI_VECTOR_SET_HH__ */
|