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.
3.3 KiB
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:
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-templatePdmNestedCollectionBase. Without it, generic script methods (AddFolder) would have to be re-registered per derived class. -
Typed storage and accessors —
m_subCollections : PdmChildArrayField<SelfT*>needsSelfT, which only exists as a template parameter. Hence the CRTP template layer on top of the base.
Alternative considered: non-templated base only
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 8–10 derived classes that's ~200–250 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 1–2 — 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.