Optional dependent component


#1

Hi,

Is there a way to achieve both of the following simultaneously:

  1. If component A and component B both exist within the same Entity, component A should initialise before component B.
  2. If component B exists within an Entity but component A does not, the initialisation of the scene should not fail.

Casi


#2

I believe the order of components on an Entity determines the order of initialization (correct me if I’m wrong @cklosters).
Napkin does support reordering of components at the moment, but this is in the making, so you’d have to go into the json and move it manually for now.


#3

Hey,

What you are suggesting is not possible when you declare a component to depend on another component. The dependency is enforced by NAP. This has been a design decision from the beginning and is not likely to change, why would you otherwise call it a dependency ?

Your best bet is to rely on the component processing order as discussed here: ComponentInstance processing order or change your design.

This comment is probably most valuable to you:

Did a quick debugging session on my end using the lineblend demo, which uses component ptrs and component dependencies. Looks like the JSON component declaration is always leading. This is even the case when components that link to each other are initialized in a different order.


#4

Hey Bas and Coen, thank you for your answers!

I agree that dependency is maybe not the right term for it, I was just wondering if there is some general way to ensure that components of a certain type are always initialised before components of another type, without enforcing that there has to be at least one component of this type.

Just to make sure I understand it completely, are the following two statements correct?:

  1. If you have a component A that depends on components of type B (of which there could be zero, one or more), the only real way to ensure that all components of type B are initialised before component A initialises is to create a vector<ComponentPtr> property in A in which you add every component of type B in Napkin.
  2. However, if you know there will always be at least one component of type B, you can use the getDependentComponents() method in C++.

If so, wouldn’t it make sense to be able to achieve the first situation in C++ as well? (for example by having an ‘optional’ flag when adding a component to the getDependentComponents list, which would just carry on initialisation if no component is there instead of failing)

(not necessarily advocating this, just trying to understand the logic behind the system :slight_smile: )

I guess another way to make component A aware of all components of type B is to make type B depend on type A and make components of type B register at component A during their initialisation (so after initialisation of component A). But the downside of this solution is that component A won’t be able to execute an action after all components of type B are registered, because it can never know if a component registering is the last one.

Is there another way to set this up that I’m missing?

Casi


#5

This is correct. The vector ensures all components that are referenced in that vector are initialized before initializing itself.

Also correct. The getDependentComponents() method is a shortcut to not having to use a component ptr. The component ptr concept was introduced after the getDependentComponents() method to allow the user to setup explicit links between components of different entities. It’s good to realize that the getDependentComponents method is generally used for single component dependencies. For example: a rotate component that needs to manipulate the transform.

I was contemplating the optional flag, but introducing flags to a function that is called getDependent… makes no sense to me. Maybe it’s a better idea to add a virtual called: getOptionalComponents() instead. This would allow you to initialize an optional component when found under an entity.

I have done this in the past myself but without the need to execute an action when registration completes. Right now this introduces a problem (with your current build) that we are currently resolving, ie: order of destruction of components is atm random. So you can’t count on component A to exist on destruction when de-registration is required.

Would it be useful to say that (in the future):

  • getDependentComponents() needs at least 1 component of that type to exist
  • getDependentComponents() will initialize all of the dependent component types before itself

and introduce a new virtual called:

  • getOptionalComponents() where there is no requirement for the component to exist
  • getOptionalComponents() will initialize all components of that type before initializing itself if they do exist.

#6

Thank you. Yes, this new virtual method sounds like a good idea!


#7

I just ran through the getDependentComponents code with Jelle and Ritesh. All components of type B that component A depends upon will be initialized before component A.

We are also considering making a component dependency optional by default when using getDependentComponents. Would you prefer that? This means that:

  • there is no requirement for the component to exist
  • but will initialize all component of that type if they do exist.

As a user you will have to correctly handle initialization in your init() call of your component.


#8

After some time debugging the current system I have made some adjustments: getDependentComponents isn’t a hard requirement anymore. It can be used to control the order of initialization but doesn’t require the component to be part of the same entity anymore. Turns out this was broken anyway :wink: Important: if your component depends on a component and your entity has more than 1 of those dependencies, it will initialize all of them before your current component. Updated documentation:

	/**
	 * Populates a list of components this component depends on.
	 * Every component dependency, when found, is initialized before this component.
	 * A dependency is NOT a hard requirement. Serialization will not fail if the dependent component 
	 * isn't declared in JSON. It only means that when a component is declared under the same entity,
	 * and that component is tagged as a dependency of this component, it is initialized before this component.
	 * It is your responsibility to return false on initialization if the dependency is a hard requirement 
	 * and can't be found. To ensure the right order of initialization based on a hard requirement it
	 * is advised to use a nap::ComponentPtr instead.
	 * @param components list of component types this resource depends on.
	 */
	virtual void getDependentComponents(std::vector<rtti::TypeInfo>& components) const { }