209 lines
5.2 KiB
C++
209 lines
5.2 KiB
C++
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0-or-later */
|
|
|
|
/** \file
|
|
* \ingroup bke
|
|
*
|
|
* Tree hash for the outliner space.
|
|
*/
|
|
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
|
|
#include "BLI_mempool.h"
|
|
#include "BLI_utildefines.h"
|
|
#include "BLI_vector.hh"
|
|
|
|
#include "DNA_outliner_types.h"
|
|
|
|
#include "BKE_outliner_treehash.hh"
|
|
|
|
#include "MEM_guardedalloc.h"
|
|
|
|
namespace blender::bke::outliner::treehash {
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name #TseGroup
|
|
* \{ */
|
|
|
|
class TseGroup {
|
|
public:
|
|
blender::Vector<TreeStoreElem *> elems;
|
|
/* Index of last used #TreeStoreElem item, to speed up search for another one. */
|
|
int lastused = 0;
|
|
/* Counter used to reduce the amount of 'rests' of `lastused` index, otherwise search for unused
|
|
* item is exponential and becomes critically slow when there are a lot of items in the group. */
|
|
int lastused_reset_count = -1;
|
|
|
|
void add_element(TreeStoreElem &elem);
|
|
void remove_element(TreeStoreElem &elem);
|
|
};
|
|
|
|
/* Only allow reset of #TseGroup.lastused counter to 0 once every 1k search. */
|
|
#define TSEGROUP_LASTUSED_RESET_VALUE 10000
|
|
|
|
void TseGroup::add_element(TreeStoreElem &elem)
|
|
{
|
|
const int64_t idx = elems.append_and_get_index(&elem);
|
|
lastused = idx;
|
|
}
|
|
|
|
void TseGroup::remove_element(TreeStoreElem &elem)
|
|
{
|
|
const int64_t idx = elems.first_index_of(&elem);
|
|
elems.remove(idx);
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name #TreeStoreElemKey
|
|
* \{ */
|
|
|
|
TreeStoreElemKey::TreeStoreElemKey(const TreeStoreElem &elem)
|
|
: id(elem.id), type(elem.type), nr(elem.nr)
|
|
{
|
|
}
|
|
|
|
TreeStoreElemKey::TreeStoreElemKey(ID *id, short type, short nr) : id(id), type(type), nr(nr) {}
|
|
|
|
uint64_t TreeStoreElemKey::hash() const
|
|
{
|
|
return get_default_hash_3(id, type, nr);
|
|
}
|
|
|
|
bool operator==(const TreeStoreElemKey &a, const TreeStoreElemKey &b)
|
|
{
|
|
return (a.id == b.id) && (a.type == b.type) && (a.nr == b.nr);
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name #TreeHash
|
|
* \{ */
|
|
|
|
TreeHash::~TreeHash() = default;
|
|
|
|
std::unique_ptr<TreeHash> TreeHash::create_from_treestore(BLI_mempool &treestore)
|
|
{
|
|
/* Can't use `make_unique()` here because of private constructor. */
|
|
std::unique_ptr<TreeHash> tree_hash{new TreeHash()};
|
|
tree_hash->fill_treehash(treestore);
|
|
|
|
return tree_hash;
|
|
}
|
|
|
|
void TreeHash::fill_treehash(BLI_mempool &treestore)
|
|
{
|
|
TreeStoreElem *tselem;
|
|
BLI_mempool_iter iter;
|
|
BLI_mempool_iternew(&treestore, &iter);
|
|
|
|
while ((tselem = static_cast<TreeStoreElem *>(BLI_mempool_iterstep(&iter)))) {
|
|
add_element(*tselem);
|
|
}
|
|
}
|
|
|
|
void TreeHash::clear_used()
|
|
{
|
|
for (auto &group : elem_groups_.values()) {
|
|
group->lastused = 0;
|
|
group->lastused_reset_count = 0;
|
|
}
|
|
}
|
|
|
|
void TreeHash::rebuild_from_treestore(BLI_mempool &treestore)
|
|
{
|
|
elem_groups_.clear();
|
|
fill_treehash(treestore);
|
|
}
|
|
|
|
void TreeHash::add_element(TreeStoreElem &elem)
|
|
{
|
|
std::unique_ptr<TseGroup> &group = elem_groups_.lookup_or_add_cb(
|
|
TreeStoreElemKey(elem), []() { return std::make_unique<TseGroup>(); });
|
|
group->add_element(elem);
|
|
}
|
|
|
|
void TreeHash::remove_element(TreeStoreElem &elem)
|
|
{
|
|
TseGroup *group = lookup_group(elem);
|
|
BLI_assert(group != nullptr);
|
|
|
|
if (group->elems.size() <= 1) {
|
|
/* One element -> remove group completely. */
|
|
elem_groups_.remove(TreeStoreElemKey(elem));
|
|
}
|
|
else {
|
|
group->remove_element(elem);
|
|
}
|
|
}
|
|
|
|
TseGroup *TreeHash::lookup_group(const TreeStoreElemKey &key) const
|
|
{
|
|
const auto *group = elem_groups_.lookup_ptr(key);
|
|
if (group) {
|
|
return group->get();
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
TseGroup *TreeHash::lookup_group(const TreeStoreElem &key_elem) const
|
|
{
|
|
return lookup_group(TreeStoreElemKey(key_elem));
|
|
}
|
|
|
|
TseGroup *TreeHash::lookup_group(const short type, const short nr, ID *id) const
|
|
{
|
|
TreeStoreElemKey key(id, type, nr);
|
|
if (type == TSE_SOME_ID) {
|
|
key.nr = 0; /* we're picky! :) */
|
|
}
|
|
return lookup_group(key);
|
|
}
|
|
|
|
TreeStoreElem *TreeHash::lookup_unused(const short type, const short nr, ID *id) const
|
|
{
|
|
TseGroup *group = lookup_group(type, nr, id);
|
|
if (!group) {
|
|
return nullptr;
|
|
}
|
|
|
|
/* Find unused element, with optimization to start from previously
|
|
* found element assuming we do repeated lookups. */
|
|
const int size = group->elems.size();
|
|
int offset = group->lastused;
|
|
|
|
for (int i = 0; i < size; i++, offset++) {
|
|
/* Once at the end of the array of items, in most cases it just means that all items are
|
|
* used, so only check the whole array once every TSEGROUP_LASTUSED_RESET_VALUE times. */
|
|
if (offset >= size) {
|
|
if (LIKELY(group->lastused_reset_count <= TSEGROUP_LASTUSED_RESET_VALUE)) {
|
|
group->lastused_reset_count++;
|
|
group->lastused = group->elems.size() - 1;
|
|
break;
|
|
}
|
|
group->lastused_reset_count = 0;
|
|
offset = 0;
|
|
}
|
|
|
|
if (!group->elems[offset]->used) {
|
|
group->lastused = offset;
|
|
return group->elems[offset];
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
TreeStoreElem *TreeHash::lookup_any(const short type, const short nr, ID *id) const
|
|
{
|
|
const TseGroup *group = lookup_group(type, nr, id);
|
|
return group ? group->elems[0] : nullptr;
|
|
}
|
|
|
|
/** \} */
|
|
|
|
} // namespace blender::bke::outliner::treehash
|