Problem
The behavior of components during some operations is defined through metadata that is stored in the ComponentInfo when it is registered with the application. This pattern comes up a lot (storage location, component mutability, cloning behavior, component hooks, required components, relationship details) and there's good reason why, because in effect it allows for handling trait-like polymorphism on an ECS level. The use of this pattern in the implementation of cloning (and in part relationships) specifically illustrates both why this type of behavior is necessary for those kinds of operations, and why it might be desirable for an end user to define similar behavior.
Cloning is a fundamental operation, but also an expensive one, so it's essential that what gets cloned and when is able to be controlled based on the specific use case. The current EntityCloner is pretty good in this regard as it takes an EntityClonerFilter from anything that implements CloneByFilter, but the filters are limited by only receiving the archetypes of the source and target entities and the identifier of the component type. This is fine if the cloner is only interested in including or excluding a static set of component types, but falls apart if new types implement Component and want to specify how that component should behave with that particular filter. That lack of specificity currently prevents the EntityCloner from being used in applications like rendering or serialization where partial snapshots of the world need to be made for a parallel task, but other crates may need to specify additional component types that are relevant to that task. This leads to patterns like ExtractComponentPlugin where a plugin has to be added to the application for every type that should be extracted by the renderer, or RelationshipCloneBehaviorSpecialization which once again defines how a relationship-type component should behave when cloned.
Once again, while cloning is a good example of where this used, this type of behavior is not limited to just cloning, has already been used internally to implement many recent features, and is necessary for implementing any kind of feature that defines interactions between types of components. A quick search of other feature suggestions reveals many ideas that, at a cursory glance, I imagine would also require the use of this pattern and the further extension of ComponentInfo. A non-exhaustive list includes: Mutually Exclusive Components (#23569) and similar features like kinded entities, Components as Entities (#23988) and similar features like resources and assets as entities, etc.
Solutions
The solution is to expose some kind of API that allows for custom metadata to be stored as ComponentInfo. The scope of what this would look like it what I'm still uncertain about (especially in the context of other large ECS changes I know are coming). In the absolute most basic sense this could be achieved simply through a hook that runs when a ComponentId is registered for a given component type. With that alone it would be possible for each specific use case to manage its own store of metadata however it deems necessary, but again, this pattern is common (even just within Bevy itself) and most implementations are likely to have roughly the same set of concerns when it comes to storing and accessing that data so it's probably worth offering more support than just that. Admittedly, my knowledge on some of the finer implementation details of the ECS is limited, so this may be entirely impossible, but when I imagine how this could be achieved in the most maximal sense, my mind jumps to some kind of attribute/macro based solution to define extensions of ComponentInfo and then when types implement Component allowing them also define instances of those ComponentInfo extensions to be stored in the ComponentInfo for that Component.
Problem
The behavior of components during some operations is defined through metadata that is stored in the
ComponentInfowhen it is registered with the application. This pattern comes up a lot (storage location, component mutability, cloning behavior, component hooks, required components, relationship details) and there's good reason why, because in effect it allows for handling trait-like polymorphism on an ECS level. The use of this pattern in the implementation of cloning (and in part relationships) specifically illustrates both why this type of behavior is necessary for those kinds of operations, and why it might be desirable for an end user to define similar behavior.Cloning is a fundamental operation, but also an expensive one, so it's essential that what gets cloned and when is able to be controlled based on the specific use case. The current
EntityCloneris pretty good in this regard as it takes anEntityClonerFilterfrom anything that implementsCloneByFilter, but the filters are limited by only receiving the archetypes of the source and target entities and the identifier of the component type. This is fine if the cloner is only interested in including or excluding a static set of component types, but falls apart if new types implementComponentand want to specify how that component should behave with that particular filter. That lack of specificity currently prevents theEntityClonerfrom being used in applications like rendering or serialization where partial snapshots of the world need to be made for a parallel task, but other crates may need to specify additional component types that are relevant to that task. This leads to patterns likeExtractComponentPluginwhere a plugin has to be added to the application for every type that should be extracted by the renderer, orRelationshipCloneBehaviorSpecializationwhich once again defines how a relationship-type component should behave when cloned.Once again, while cloning is a good example of where this used, this type of behavior is not limited to just cloning, has already been used internally to implement many recent features, and is necessary for implementing any kind of feature that defines interactions between types of components. A quick search of other feature suggestions reveals many ideas that, at a cursory glance, I imagine would also require the use of this pattern and the further extension of
ComponentInfo. A non-exhaustive list includes: Mutually Exclusive Components (#23569) and similar features like kinded entities, Components as Entities (#23988) and similar features like resources and assets as entities, etc.Solutions
The solution is to expose some kind of API that allows for custom metadata to be stored as
ComponentInfo. The scope of what this would look like it what I'm still uncertain about (especially in the context of other large ECS changes I know are coming). In the absolute most basic sense this could be achieved simply through a hook that runs when aComponentIdis registered for a given component type. With that alone it would be possible for each specific use case to manage its own store of metadata however it deems necessary, but again, this pattern is common (even just within Bevy itself) and most implementations are likely to have roughly the same set of concerns when it comes to storing and accessing that data so it's probably worth offering more support than just that. Admittedly, my knowledge on some of the finer implementation details of the ECS is limited, so this may be entirely impossible, but when I imagine how this could be achieved in the most maximal sense, my mind jumps to some kind of attribute/macro based solution to define extensions ofComponentInfoand then when types implementComponentallowing them also define instances of thoseComponentInfoextensions to be stored in theComponentInfofor thatComponent.