[Core] Rework the component factory#6162
Conversation
|
I would be happy to discuss this point and it is worth having a look there as there was lengthy discussion on the topic in the past. If I remember correctly, the general idea was that both canCreate and create should be removed.
To move forward the idea was thus to:
If there is a deep refactoring of ObjectFactory, I would also recommand adding the namespace features and user define aliases suggested in: And also to remove the use of GetClass which cause a code bloat. |
|
|
||
| const objectmodel::BaseClass* getClass() override | ||
| { | ||
| return RealComponent::GetClass(); |
There was a problem hiding this comment.
If we are refactoring component factory could we drop this please.
If I remenber correctly this single line is causing a compilation code bloat (just to provide the class name). When we are registering a component into the factory the class name could be passed with a std::string instead of relying a complex custom "reflection" system.
There was a problem hiding this comment.
For now, the only reason I had to keep this function was to list components deriving from another, using the method hasParent in BaseClass. If we find another mechanism for that (or if we remove this feature), we can remove this usage in the factory.
391be6c to
079e09e
Compare
|
[ci-depends-on] detected during build #13. To unlock the merge button, you must
|
|
[ci-depends-on] detected during build #14. To unlock the merge button, you must
|
Rework the component factory: introduce
ComponentFactorySummary
This PR replaces the legacy
ObjectFactorywith a new, simplerComponentFactory, and removes (or disables) the tooling that was built on top of the old factory internals (ComponentLibrary, most ofCategoryLibrary,SofaLibrary, and JSON dumping).ObjectFactoryis kept as a type alias for backward source compatibility, and a legacy registration path is preserved so existingregisterObjects(...)call sites keep compiling — but a full migration to the new API is strongly encouraged, and some behavior is intentionally not preserved (see Breaking changes below).Motivation
The previous
ObjectFactorystored components in astd::map<std::string, ClassEntry>, where eachClassEntryinternally held its own map of per-template creators (ObjectTemplateCreatorMap). This design made a few things awkward or impossible to extend cleanly:canCreate(...)method, which mixed instantiation logic with selection logic.template="..."XML attribute, which becomes unreadable once a component has more than one template parameter (e.g.template="Vec3,Hexahedron").ComponentLibrary/CategoryLibrary/SofaLibrary(used mainly by the old Modeler tooling) were tightly coupled toObjectFactoryinternals, which made the factory harder to change safely.What changed
New registry model
The registry is no longer a map of
ClassEntryobjects, each containing a nested map of per-template creators. It is now a flatstd::vector<ComponentRegistrationData::SPtr>, where each element represents exactly one (component name, template) instantiation.ComponentCreator<T>, in the newComponentCreator.h) are intentionally simplified: they always use the component's default constructor viasofa::core::objectmodel::New<T>(), instead of relying on a per-class staticcreate(...)function. This is a deliberate simplification — classes that relied on customcreate()logic will need to be checked.Module-qualified names
Components now register with a module name. This makes it possible to instantiate a component using the
moduleName.componentNamesyntax (e.g.Sofa.Component.MechanicalObject), in addition to the plain component name. This isn't used for namespacing yet in this PR, but it lays the groundwork for it (see [#2512](#2512)).Template selection: deduction rules replace
canCreateInstead of relying on each templated class's static
canCreate(context, arg)to decide whether it's a valid candidate, template selection is now driven by explicit template deduction rules (TemplateDeductionRules.h):BaseTemplateDeductionRule— the interface, checked against aBaseContextand aBaseObjectDescription.OtherComponentsInContextDeductionRule<T...>/MechanicalStateDeductionRule<DataTypes>— matches based on what other components already exist in the current context (e.g. aMechanicalState<DataTypes>sibling).CanCreateDeductionRule<T>— a compatibility shim that falls back to the oldT::canCreate(...)behavior, used by the legacy registration path.When multiple candidates remain ambiguous after filtering,
ComponentFactory::createComponent(...)resolves the ambiguity in this order of priority:template="..."keyword, matched against the full declared attribute list.If ambiguity remains after all filters, the first candidate (sorted by
instantiationPriority) is used, and a warning is emitted listing the other candidates.Named template attributes instead of the
templatekeywordInstead of
template="...", a component can declare named attributes for its template parameters and be instantiated using those names directly. For example:This scales much better than
template="..."once a component has more than one template parameter, since attribute order no longer matters and each parameter is self-documenting. The legacytemplate="..."attribute is still supported for backward compatibility.Simplified registration API
Registering a component in the factory now looks like this:
core::CreateComponent<T>(componentName)is mandatory as the entry point: it builds the creator forTand returns a builder that requires a module name (.withModule(...)) and a description (.withDescription(...)) before it can be committed — the code won't compile without them. From there,.addAlias(...),.addTemplateAttribute<T>(name)/.addTemplateAttribute(name, value),.addAuthor(...),.withLicense(...),.withDocumentationURL(...),.withDeductionRule<Rule>(), and.withInstantiationPriority(...)are all available as needed.Component registration functions were migrated to this new API, for example
registerMechanicalObject,registerRequiredPlugin,registerSparseLDLSolver, andregisterCorotationalFEMForceField.Legacy registration path
To avoid breaking every existing
registerObjects(...)call site immediately, a compatibility path is preserved:LegacyComponentRegistrationData(replacingObjectRegistrationData) supports the old.add<T>()-based, macro-style registration.ComponentFactory::registerObjects(LegacyComponentRegistrationData&)converts a legacy registration into one or moreComponentRegistrationDataentries, including deriving aCanCreateDeductionRule<T>for each one so oldcanCreate-based classes keep working.ObjectFactoryis now simplyusing ObjectFactory = ComponentFactory;, andObjectRegistrationDataisusing ObjectRegistrationData = LegacyComponentRegistrationData;, so most existing call sites should keep compiling.All of the legacy-only surface (
ClassEntry,registerObjects(LegacyComponentRegistrationData&),createObject(...),ObjectFactory::getInstance(),ObjectFactory::CreateObject(...),ObjectFactory::HasCreator(...)) is now marked deprecated via new macros inconfig.h.in(SOFA_CORE_DEPRECATED_OBJECTFACTORY_*), targeting removal betweenv26.12andv27.12/v27.06depending on the symbol. UseMainComponentFactory::getInstance(),MainComponentFactory::CreateComponent(...), andMainComponentFactory::HasCreator(...)going forward.Component creation flow (
ComponentFactory::createComponent)The new creation flow, in
ComponentFactory.cpp, adds a few improvements over the old one:sofa::helper::lifecycletables before attempting creation.sofa::helper::getClosestMatch).moduleName.componentNameform and that module/plugin isn't loaded yet, the factory attempts to load it automatically and register its objects.factoryNamedata field onBaseComponent(see below), which may differ from itsgetClassName()when created through an alias.BaseComponent::d_factoryNameBaseComponentgains a new, read-onlyData<std::string> d_factoryName("Component name registered into the factory"), set byComponentFactoryright afterparse(...). This records the exact name used to instantiate the component (which may be an alias, e.g.LegacyAliasrather thanDummyComponent), which was previously not recoverable from the object itself.Tests
Sofa/framework/Core/test/ComponentFactory_test.cpp, a comprehensive new test suite covering: basic creation, creation via alias,hasCreator, per-target entry listing, single- and two-template-attribute selection (both via named attributes and thetemplatekeyword), duplicate/ambiguous registration warnings, instantiation priority, fully-qualifiedmodule.componentcreation, typo suggestions on unknown components, and both the new and the legacy (LegacyComponentRegistrationData) registration paths, including legacy templated registration.ObjectFactory_test.cppandObjectFactoryJson_test.cppare commented out for now rather than rewritten, since they test behavior (rawClassEntryaccess, JSON dump) that this PR does not fully port over yet.Breaking changes
ComponentLibraryis now justusing ComponentLibrary = DeprecatedAndRemoved;— its implementation is entirely gone.CategoryLibrarykeeps its class shell for source compatibility, but nearly every method (addComponent,endConstruction,getName,getComponents,getComponent,getNumComponents,createComponent, and even the constructor) is now= delete. Only the staticgetCategories()helpers still work.SofaLibrary(.h/.cpp) is fully commented out. Anything built on top of it (e.g. Modeler-style component browsing) will not compile.ObjectFactoryJsondumping (ObjectFactoryJson::dump(...)) is commented out/disabled; it relied directly on the oldClassEntry/creatorMapstructure and hasn't been ported to the new registry yet. This is a known gap.SceneCheckMissingRequiredPluginandSceneCheckUsingAlias: their core logic is commented out, since both relied onObjectFactory::getEntry(...)/creatorMapandObjectFactory::setCallback(...), which no longer exist in the same form. These scene checks are effectively no-ops until ported.ObjectFactory/ComponentFactoryand relying on a customcreate(...)static method may behave differently now thatComponentCreator<T>always uses the default constructor.If your code depends on any of the above, it will need to be updated or ported as part of, or after, this PR.
Follow-ups (not in this PR)
ObjectFactoryJson::dumpto the newComponentRegistrationDatamodel.SceneCheckMissingRequiredPluginandSceneCheckUsingAliasagainstComponentFactory.SofaLibrary/category browsing for tooling that needs it, on top of the new registry.registerObjects(...)call sites to the newregisterComponent(...)API and retireLegacyComponentRegistrationData.[ci-depends-on https://github.com/sofa-framework/SofaPython3/pull/623]
By submitting this pull request, I acknowledge that
I have read, understand, and agree SOFA Developer Certificate of Origin (DCO).
Reviewers will merge this pull-request only if