How to reduce Duck-typing phenomenon in entity-component-system?
Example
Here is a coliru demo.
There are 2 systems in my ECS :-
System_Projectile
: manage all projectile and bullet aspect.
System_Physic
: manage physic's component.
There are 2 components-type : Com_Projectile
, Physics
.
Sometimes, I find that it is nice to cache pointer to another entity inside some certain component :-
class Com_Projectile : public Component{
public:
Entity* physic;
Entity* physicSecondary; //just to show that it is possible to have >1 physic
};
If I want to change position of the Com_Projectile
, I will call manage(Com_Projectile::physic)
.
class System_Projectile{
public: static void manage(Entity* projectile){
Com_Projectile* comP = getComponent<Com_Projectile>(projectile);
//suffer duck-typing at "comP->physic"
System_Physic::setVelocity(comP->physic,Vec3(1,0,0));
}
};
Problem
The real program based on the above snippet works OK.
However, when coding, Com_Projectile::physic
suffer duck-typing.
- I get no C++ semantic clues about
physic
's type.
(except the variable name and comment) Thus, I have to be conscious about it.
Coder's misunderstanding about type will be detected only at run-time.
In practice, such mistake occurs very rarely though.I have to recall name of system (
System_Physic::
) that can do a thing I want,
then recall the name of function (System_Physic::setVelocity()
in this case).- In summary, there are a lot of indirection for my brain.
In my old days, when I use a lot of (deep) inheritance, it is much easier, like this :-
physic->setVelocity(Vec3(1,0,0));
I really miss the cute content assists that list all the functions that related to physics.
Question
How to reduce duck-typing in some certain part of ECS system?
More specifically, what is a design pattern to enable the cute content-assist again?
My current workaround
Let Com_Projectile
cache Physic* physic
instead of Entity*
:-
class Com_Projectile{
public: Physics* physic; //edited from "Entity* physic"
};
Disadvantage:-
- It will promote unwanted (?) coupling.
- I have to forward declaration
Physics
insideCom_Projectile.h
. - I will have to move complex functions (e.g.
setVelocity()
) from System (e.g.Sys_Physic::
) into component (e.g.Physics::
). - Overall, I am breaking the Entity-Component-System religious
-> I may get punish in some ways (?).
One idea would be to treat your component implementations as the conduit by which you interact with the systems. That's their intent anyway, to be a data-driven way to influence behavior.
Now in order to set the velocity, its a simple call into:
It is then the job of the physics system to take the velocity on the physics component and apply that along with any other data attributes to the internal physics simulation.
In other words, treat the input state of a system as the combination of the current component values and any other mutable state that the prior system emits.
Besides avoiding the duck-typing you mentioned, you also end up with code that is easier to follow and it flows more easily. It also opens the door to be easily mutated by scripting systems and other external influencers all by manipulating getter/setters on your components.