Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using multiple inheritance to implement Entity-Component programming

I've read a few articles about the Entity-Component style of programming. One of the common problems posed is how to express dependencies between components, and how components related to the same entity communicate.

It seems to me that a simple solution to this problem is to make each dependency a virtual base class of its dependant.

That way, when a component is included in an entity (via virtual inheritance), all of the dependant components are included exactly once. Additionally, all of the functionality that a component depends upon will be available in its member functions.

class C_RigidBody : public virtual C_Transform {
    public void tick(float dt);
};

class C_Explodes : public virtual C_Transform {
    public void explode();
};

class E_Grenade : public virtual C_RigidBody, public virtual C_Explodes {
    //no members
};

Is there any reason no one does this?

(I realize that multiple inheritance is usually frowned upon due to the "diamond problem," but this problem is something components have to deal with anyway. (Imagine how many components would depend on an entity's position in the game world))

like image 912
Dan Avatar asked Nov 01 '22 05:11

Dan


1 Answers

I recently come up with the same idea with you too.

In theory I blieve that this approach is a perfect solution for Inverse of Dependency if properly applied. Otherwise you'll mess things up.

Recalling what DIP states:

  • High-level modules should not depend on low-level modules. Both should depend on abstractions.

  • Abstractions should not depend on details. Details should depend on abstractions.

See the following code example

struct IClockService
{
    virtual unsigned timestamp() = 0;
};

struct ITimingService
{
    virtual unsigned timing(std::function<void()> f) = 0;
};

struct ClockService : public virtual IClockService // public virtual means implement
{
    virtual unsigned timestamp() { return std::time(nullptr); }
};

struct TimingService : public virtual ITimingService
                     , private virtual IClockService // private virtual means dependency
{
    virtual unsigned timing(std::function<void()> f)
    {
        auto begin = timestamp();
        f();
        return timestamp() - begin;
    }
};

class Application : public ClockService
                  , public TimingService
{
};
Application a; 
unsigned runingTime = a.timing(anyFunction);

In the above example. ClockService and TiminigService are two module, they don't need to know each other but only the interface IClockService and ITimingService. And the Application composits the two modules together.

In conclusion:

  • Map depend as private virtual inheritance, otherwise breaks Liskov-Subtitution Principle
  • Map implements as public virtual inheritance.
  • Module implementation only depends on abstraction, this is what DIP required.
  • Compositing all module together via normal inheritance, if any compilation occurred, that would means there is unmet dependency or multiple-implementation for an interface.

I do only apply such idioms in my toy projects. Take it at your own risk.

like image 186
thynson Avatar answered Nov 15 '22 04:11

thynson