#13987 PDM: Add caf::PdmNestedCollection framework

Introduce a shared base for tree-shaped folder containers that hold a
list of leaf items together with a list of subcontainers of the same
kind. Replaces the per-class plumbing (subcollections field, top-folder
flag, add/remove/find sub-collection, factory) that is duplicated across
RimSurfaceCollection, RimPolygonCollection and similar candidates.

- caf::PdmNestedCollectionBase: non-templated PdmObject base exposing
  collection-name, add-new-subcollection, leaf-check and topmost-folder
  marking for generic command features and script methods.
- caf::PdmNestedCollection<SelfT, ItemT>: CRTP template that owns the
  typed m_subCollections field and inherits the m_items array from
  PdmObjectCollection<ItemT>. Provides allItems(), find-sub-collection,
  and a typed factory hook.
- RimNestedMirrorCollectionInView: view-side analogue for in-view
  collections that mirror a nested source collection, with orphan-sweep
  + find-or-create + reorder sync.

Design rationale (templated vs non-templated, break-even on derived
class count) lives in docs/pdm-nested-collection-design.md.

No callers migrated in this commit.
This commit is contained in:
Magne Sjaastad
2026-05-13 14:53:00 +02:00
parent 4055954144
commit 92da6633bb
9 changed files with 934 additions and 0 deletions
+81
View File
@@ -0,0 +1,81 @@
# `caf::PdmNestedCollection` — design notes
## Context
`caf::PdmNestedCollection` is the base class for tree-shaped PDM containers — a
folder of items that can also contain folders of the same kind. The design is a
non-template concrete base + a CRTP template:
```cpp
class PdmNestedCollectionBase : public PdmObject
{
QString collectionName() const;
void setCollectionName( const QString& name );
virtual bool canAddSubCollection() const;
virtual PdmObject* addNewSubCollection();
void setAsTopmostFolder();
PdmField<QString> m_collectionName;
};
template <typename SelfT, typename ItemT>
class PdmNestedCollection : public PdmNestedCollectionBase
{
// typed accessors + storage for items and subcollections
PdmChildArrayField<SelfT*> m_subCollections;
PdmChildArrayField<ItemT*> m_items;
};
```
`PdmNestedCollectionBase` is the shared PdmObject ancestor — the anchor point
where the CAF method factory can register generic script methods (like
`AddFolder`) once and have them surface on every derived nested collection via
the inheritance walk and Python class hierarchy.
`PdmNestedCollection<SelfT, ItemT>` adds typed storage and accessors. `SelfT` is
the CRTP self type (so subcollections are typed `SelfT*`); `ItemT` is the leaf
item type. The template does not inherit `PdmObjectCollection<ItemT>` — m_items
is declared here directly, so the inheritance chain stays linear and free of
diamond shapes.
## Why both a non-template base and a CRTP template
Two needs that don't fit into a single layer:
- **CAF method registration is keyed by `classKeyword`** — only a concrete
non-template class can serve as the anchor. Hence the non-template
`PdmNestedCollectionBase`. Without it, generic script methods (`AddFolder`)
would have to be re-registered per derived class.
- **Typed storage and accessors** — `m_subCollections : PdmChildArrayField<SelfT*>`
needs `SelfT`, which only exists as a template parameter. Hence the CRTP
template layer on top of the base.
## Alternative considered: non-templated base only
```cpp
class PdmNestedCollection : public PdmObject
{
...
virtual PdmObject* createSubCollection() const = 0;
// m_subCollectionsField / m_itemsField stored as type-erased handles
};
```
Each derived class would own its typed `PdmChildArrayField<DerivedType*>
m_subCollections` and `PdmChildArrayField<ItemType*> m_items`, register them
with the base in the constructor, and override `createSubCollection()`. About
~25 lines of boilerplate per derived class beyond what the current design needs:
two field declarations, two `registerXxxField` calls, an override of
`createSubCollection`, re-declared typed accessors.
At the projected 810 derived classes that's ~200250 lines of repeated code.
The hybrid (non-template base + template) design pays the cost once in the
framework and gets typed access plus generic registration for free.
## When to revisit
- If the count of nested-collection types drops to 12 — the template tier no
longer earns its keep, and a pure non-template base wins on simplicity.
- If template instantiation cost shows up in build profiling for the PDM layer.
- If the typed accessors are rarely used in practice and most call sites
immediately cast to `PdmObject*` anyway.