reduce Duck-typing disadvantage in entity-component-system

318 views Asked by At

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.

enter image description here

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 inside Com_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 (?).
1

There are 1 answers

1
Naros On BEST ANSWER

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?

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.

class Physics : public Component<Physics> {
public:
  Vector3 GetVelocity() const;
  void SetVelocity(const Vector3& velocity);
private:
  Vector3 velocity_;
}

Now in order to set the velocity, its a simple call into:

Physics* physics = getComponent<Physics>( projectile->physic );
if ( physics ) 
  physics->SetVelocity( Vector3( 1, 0, 0 ) );

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.