Outliner: Refactor how lazy-building of children is done

Makes the lazy-building (where children are only built when the parent
isn't collapsed) more generic, so more display modes can use it. So far
this was hardcoded for the "Data API" display mode.

This will be used to work around a big performance issue with the
Library Overrides Hierachies view in a complex production file, see
following commit.
This commit is contained in:
Julian Eisel 2022-08-19 21:56:09 +02:00
parent 8115d31248
commit 231078441f
10 changed files with 40 additions and 32 deletions

View File

@ -3336,7 +3336,7 @@ static void outliner_draw_tree_element(bContext *C,
/* Scene collection in view layer can't expand/collapse. */
}
else if (te->subtree.first || ((tselem->type == TSE_SOME_ID) && (te->idcode == ID_SCE)) ||
(te->flag & TE_LAZY_CLOSED)) {
(te->flag & TE_PRETEND_HAS_CHILDREN)) {
/* Open/close icon, only when sub-levels, except for scene. */
int icon_x = startx;

View File

@ -144,14 +144,10 @@ void OUTLINER_OT_highlight_update(wmOperatorType *ot)
/** \name Toggle Open/Closed Operator
* \{ */
void outliner_item_openclose(SpaceOutliner *space_outliner,
TreeElement *te,
bool open,
bool toggle_all)
void outliner_item_openclose(TreeElement *te, bool open, bool toggle_all)
{
/* Prevent opening leaf elements in the tree unless in the Data API display mode because in that
* mode subtrees are empty unless expanded. */
if (space_outliner->outlinevis != SO_DATA_API && BLI_listbase_is_empty(&te->subtree)) {
/* Only allow opening elements with children. */
if (!(te->flag & TE_PRETEND_HAS_CHILDREN) && BLI_listbase_is_empty(&te->subtree)) {
return;
}
@ -198,7 +194,7 @@ static int outliner_item_openclose_modal(bContext *C, wmOperator *op, const wmEv
/* Only toggle openclose on the same level as the first clicked element */
if (te->xs == data->x_location) {
outliner_item_openclose(space_outliner, te, data->open, false);
outliner_item_openclose(te, data->open, false);
outliner_tag_redraw_avoid_rebuild_on_open_change(space_outliner, region);
}
@ -242,7 +238,7 @@ static int outliner_item_openclose_invoke(bContext *C, wmOperator *op, const wmE
const bool open = (tselem->flag & TSE_CLOSED) ||
(toggle_all && (outliner_flag_is_any_test(&te->subtree, TSE_CLOSED, 1)));
outliner_item_openclose(space_outliner, te, open, toggle_all);
outliner_item_openclose(te, open, toggle_all);
outliner_tag_redraw_avoid_rebuild_on_open_change(space_outliner, region);
/* Only toggle once for single click toggling */

View File

@ -157,7 +157,10 @@ enum {
/* Closed items display their children as icon within the row. TE_ICONROW is for
* these child-items that are visible but only within the row of the closed parent. */
TE_ICONROW = (1 << 1),
TE_LAZY_CLOSED = (1 << 2),
/** Treat the element as if it had children, e.g. draw an icon to un-collapse it, even if it
* doesn't. Used where children are lazy-built only if the parent isn't collapsed (see
* #AbstractTreeDisplay::is_lazy_built()). */
TE_PRETEND_HAS_CHILDREN = (1 << 2),
TE_FREE_NAME = (1 << 3),
TE_DRAGGING = (1 << 4),
TE_CHILD_NOT_IN_COLLECTION = (1 << 6),
@ -280,11 +283,6 @@ struct TreeElement *outliner_add_collection_recursive(SpaceOutliner *space_outli
bool outliner_requires_rebuild_on_select_or_active_change(
const struct SpaceOutliner *space_outliner);
/**
* Check if a display mode needs a full rebuild if the open/collapsed state changes.
* Element types in these modes don't actually add children if collapsed, so the rebuild is needed.
*/
bool outliner_requires_rebuild_on_open_change(const struct SpaceOutliner *space_outliner);
typedef struct IDsSelectedData {
struct ListBase selected_array;
@ -465,10 +463,7 @@ void outliner_set_coordinates(const struct ARegion *region,
/**
* Open or close a tree element, optionally toggling all children recursively.
*/
void outliner_item_openclose(struct SpaceOutliner *space_outliner,
TreeElement *te,
bool open,
bool toggle_all);
void outliner_item_openclose(TreeElement *te, bool open, bool toggle_all);
/* outliner_dragdrop.c */

View File

@ -1885,7 +1885,7 @@ static TreeElement *outliner_walk_left(SpaceOutliner *space_outliner,
TreeStoreElem *tselem = TREESTORE(te);
if (TSELEM_OPEN(tselem, space_outliner)) {
outliner_item_openclose(space_outliner, te, false, toggle_all);
outliner_item_openclose(te, false, toggle_all);
}
/* Only walk up a level if the element is closed and not toggling expand */
else if (!toggle_all && te->parent) {
@ -1906,7 +1906,7 @@ static TreeElement *outliner_walk_right(SpaceOutliner *space_outliner,
te = static_cast<TreeElement *>(te->subtree.first);
}
else {
outliner_item_openclose(space_outliner, te, true, toggle_all);
outliner_item_openclose(te, true, toggle_all);
}
return te;

View File

@ -217,11 +217,6 @@ bool outliner_requires_rebuild_on_select_or_active_change(const SpaceOutliner *s
return exclude_flags & (SO_FILTER_OB_STATE_SELECTED | SO_FILTER_OB_STATE_ACTIVE);
}
bool outliner_requires_rebuild_on_open_change(const SpaceOutliner *space_outliner)
{
return ELEM(space_outliner->outlinevis, SO_DATA_API);
}
/* special handling of hierarchical non-lib data */
static void outliner_add_bone(SpaceOutliner *space_outliner,
ListBase *lb,

View File

@ -27,6 +27,7 @@
#include "UI_view2d.h"
#include "outliner_intern.hh"
#include "tree/tree_display.hh"
#include "tree/tree_iterator.hh"
using namespace blender::ed::outliner;
@ -436,7 +437,7 @@ void outliner_tag_redraw_avoid_rebuild_on_open_change(const SpaceOutliner *space
ARegion *region)
{
/* Avoid rebuild if possible. */
if (outliner_requires_rebuild_on_open_change(space_outliner)) {
if (space_outliner->runtime->tree_display->is_lazy_built()) {
ED_region_tag_redraw(region);
}
else {

View File

@ -50,4 +50,9 @@ bool AbstractTreeDisplay::supportsModeColumn() const
return false;
}
bool AbstractTreeDisplay::is_lazy_built() const
{
return false;
}
} // namespace blender::ed::outliner

View File

@ -84,6 +84,15 @@ class AbstractTreeDisplay {
*/
virtual bool supportsModeColumn() const;
/**
* Some trees may want to skip building children of collapsed parents. This should be done if the
* tree type may become very complex, which could cause noticeable slowdowns.
* Problem: This doesn't address performance issues while searching, since all elements are
* constructed for that. Trees of this type have to be rebuilt for any change to the collapsed
* state of any element.
*/
virtual bool is_lazy_built() const;
protected:
/** All derived classes will need a handle to this, so storing it in the base for convenience. */
SpaceOutliner &space_outliner_;
@ -232,6 +241,8 @@ class TreeDisplayDataAPI final : public AbstractTreeDisplay {
TreeDisplayDataAPI(SpaceOutliner &space_outliner);
ListBase buildTree(const TreeSourceData &source_data) override;
bool is_lazy_built() const override;
};
} // namespace blender::ed::outliner

View File

@ -42,4 +42,9 @@ ListBase TreeDisplayDataAPI::buildTree(const TreeSourceData &source_data)
return tree;
}
bool TreeDisplayDataAPI::is_lazy_built() const
{
return true;
}
} // namespace blender::ed::outliner

View File

@ -124,7 +124,7 @@ void TreeElementRNAStruct::expand(SpaceOutliner &space_outliner) const
}
}
else if (tot) {
legacy_te_.flag |= TE_LAZY_CLOSED;
legacy_te_.flag |= TE_PRETEND_HAS_CHILDREN;
}
}
@ -172,7 +172,7 @@ void TreeElementRNAProperty::expand(SpaceOutliner &space_outliner) const
&space_outliner, &legacy_te_.subtree, &pptr, &legacy_te_, TSE_RNA_STRUCT, -1);
}
else {
legacy_te_.flag |= TE_LAZY_CLOSED;
legacy_te_.flag |= TE_PRETEND_HAS_CHILDREN;
}
}
}
@ -189,7 +189,7 @@ void TreeElementRNAProperty::expand(SpaceOutliner &space_outliner) const
}
}
else if (tot) {
legacy_te_.flag |= TE_LAZY_CLOSED;
legacy_te_.flag |= TE_PRETEND_HAS_CHILDREN;
}
}
else if (ELEM(proptype, PROP_BOOLEAN, PROP_INT, PROP_FLOAT)) {
@ -207,7 +207,7 @@ void TreeElementRNAProperty::expand(SpaceOutliner &space_outliner) const
}
}
else if (tot) {
legacy_te_.flag |= TE_LAZY_CLOSED;
legacy_te_.flag |= TE_PRETEND_HAS_CHILDREN;
}
}
}