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.
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
|