Fix #143697: UI freezes with lots of Action slots (in the Outliner)

Only create an outliner tree element for all Action slots in specific
modes of the outliner. The "object-hierarchical" modes (Scene, View
Layer) show the Action underneath each user of that Action; in these
cases, only the assigned slot is shown now.

Previously all slots would be shown, in all modes of the Outliner. This
meant quadratic complexity, as each Action is included in the tree for
each of its users, and then each of those tree items would show all
slots. This means the number of tree items for the slots was multiplied
by the number of users.

Pull Request: https://projects.blender.org/blender/blender/pulls/144864
This commit is contained in:
Sybren A. Stüvel
2025-08-21 18:16:59 +02:00
parent eea22743c1
commit 50876d9b26
4 changed files with 73 additions and 9 deletions

View File

@@ -45,6 +45,11 @@ void TreeElementAnimData::expand(SpaceOutliner & /*space_outliner*/) const
expand_NLA_tracks();
}
animrig::slot_handle_t TreeElementAnimData::get_slot_handle() const
{
return this->anim_data_.slot_handle;
}
void TreeElementAnimData::expand_drivers() const
{
if (BLI_listbase_is_empty(&anim_data_.drivers)) {

View File

@@ -10,6 +10,8 @@
#include "tree_element.hh"
#include "BKE_action.hh"
struct AnimData;
namespace blender::ed::outliner {
@@ -22,6 +24,8 @@ class TreeElementAnimData final : public AbstractTreeElement {
void expand(SpaceOutliner &space_outliner) const override;
animrig::slot_handle_t get_slot_handle() const;
private:
void expand_drivers() const;
void expand_NLA_tracks() const;

View File

@@ -8,9 +8,11 @@
#include "DNA_action_types.h"
#include "DNA_outliner_types.h"
#include "DNA_space_types.h"
#include "../outliner_intern.hh"
#include "tree_element_anim_data.hh"
#include "tree_element_id_action.hh"
#include "ANIM_action.hh"
@@ -20,19 +22,63 @@ namespace blender::ed::outliner {
TreeElementIDAction::TreeElementIDAction(TreeElement &legacy_te, bAction &action)
: TreeElementID(legacy_te, action.id), action_(action)
{
/* Fetch the assigned slot handle from the parent node in the tree. This is done this way,
* because AbstractTreeElement::add_element() constructs the element and immediately calls its
* expand() function. That means that there is no time for the creator of this
* TreeElementIDAction to pass us the slot handle explicitly.
*
* Adding a constructor parameter for this is also not feasible, due to the generic nature of the
* code that constructs this TreeElement. */
const TreeElement *legacy_parent = legacy_te.parent;
if (!legacy_parent) {
return;
}
const TreeElementAnimData *parent_anim_te = dynamic_cast<const TreeElementAnimData *>(
legacy_parent->abstract_element.get());
if (!parent_anim_te) {
return;
}
this->slot_handle_ = parent_anim_te->get_slot_handle();
}
void TreeElementIDAction::expand(SpaceOutliner & /* space_outliner */) const
void TreeElementIDAction::expand(SpaceOutliner &space_outliner) const
{
blender::animrig::Action &action = action_.wrap();
for (blender::animrig::Slot *slot : action.slots()) {
add_element(&legacy_te_.subtree,
reinterpret_cast<ID *>(&action_),
slot,
&legacy_te_,
TSE_ACTION_SLOT,
0);
/* If the outliner is showing the Action because it's in some hierarchical data mode, only show
* the slot that is used by the parent ID tree element. Showing all slots will create quadratic
* complexity, as each user of the Action has a child tree element for the Action. This means the
* complexity is O(U x S), where U = the number of users of the Action, and S = the number of
* slots. Typically U = S. */
const bool may_show_all_slots = ELEM(
space_outliner.outlinevis, SO_SEQUENCE, SO_LIBRARIES, SO_ID_ORPHANS, SO_OVERRIDES_LIBRARY);
animrig::Action &action = action_.wrap();
if (may_show_all_slots) {
/* Show all slots of the Action. */
for (animrig::Slot *slot : action.slots()) {
add_element(&legacy_te_.subtree,
reinterpret_cast<ID *>(&action_),
slot,
&legacy_te_,
TSE_ACTION_SLOT,
0);
}
return;
}
/* Only show a single slot. */
animrig::Slot *slot = action.slot_for_handle(this->slot_handle_);
if (!slot) {
return;
}
add_element(&legacy_te_.subtree,
reinterpret_cast<ID *>(&action_),
slot,
&legacy_te_,
TSE_ACTION_SLOT,
0);
}
} // namespace blender::ed::outliner

View File

@@ -10,16 +10,25 @@
#include "tree_element_id.hh"
#include "ANIM_action.hh"
struct bAction;
namespace blender::ed::outliner {
class TreeElementIDAction final : public TreeElementID {
bAction &action_;
animrig::slot_handle_t slot_handle_ = animrig::Slot::unassigned;
public:
TreeElementIDAction(TreeElement &legacy_te, bAction &action);
/**
* When displaying this tree element in a "flat" tree view (so each Action is
* only listed once, like in the Blender File outliner mode), this expands to
* show all the Action's slots Otherwise, when using a data-hierarchical tree
* view (like Scene or View Layer), only the assigned slot is shown.
*/
void expand(SpaceOutliner &space_outliner) const override;
};