Coder Social home page Coder Social logo

Inheritance problem about entityx HOT 7 CLOSED

alecthomas avatar alecthomas commented on June 13, 2024
Inheritance problem

from entityx.

Comments (7)

alecthomas avatar alecthomas commented on June 13, 2024

Thank you, I'm glad you like EntityX!

Component inheritance is inherently (hah!) against the design of entity-component systems.

In your example you had two components PhysicsComponent and RenderingComponent that both inherit from ActivatableComponent...you actually end up with four components on each entity: two ActivatableComponents, a PhysicsComponent and a RenderingComponent. What is the component family ID of the ActivatableComponents? Which ActivatableComponent do you return when you're processing these?

In general if you think about components as being a bit like mixins, you should be okay.

For your particular example, I would instead use either:

entity.add_component<ActivatableComponent>();
entity.add_component<PhysicsComponent>();
entity.add_component<RenderingComponent>();

or you could also use the component dependency plugin:

// Once at initialisation time...
system_manager->add<entityx::deps::Dependency<PhysicsComponent, ActivatableComponent>>();
system_manager->add<entityx::deps::Dependency<RenderingComponent, ActivatableComponent>>();

// ...

entity.add_component<PhysicsComponent>(position, mass, ...); // This will also ensure that ActivatableComponent is added.
entity.add_component<RenderingComponent>(texture, rects); // This will also ensure that ActivatableComponent is added, if it is not already added.

What do you think?

from entityx.

herpec-j avatar herpec-j commented on June 13, 2024

I totally agree that inheritance is quite a nonsense in this design, but I think that it could be a cool feature.

I'm not sure to understand your proposal. As "ActivatableComponent" is an interface defining some common behaviors, we can't instantiate it. This way, we won't be able to call "addComponent" nor using the dependency plugin, as in both cases, the component has to be constructible.

I'll just explain a little bit more what I want to achieve. Let's start for a more concrete example:

// Define my common interface, not instantiable as it is abstract
struct Behavior : public Component<Behavior>
{
  virtual void initialize(void) = 0;

  virtual void update(float elapsedTime) = 0;

  virtual void finalize(void) = 0;
}

// Define some Behaviors...
struct Flee : public Behavior
{
  virtual void initialize(void) override final;

  virtual void update(float elapsedTime) override final;

  virtual void finalize(void) override final;
}

struct Seek : public Behavior
{
  virtual void initialize(void) override final;

  virtual void update(float elapsedTime) override final;

  virtual void finalize(void) override final;
}

So, now, we won't be able to call "add_component", but I still want to be able to add Seek or Flee. "get_component" would also be impossible, but still, we would be able to call "get_component", and it would return a "ComponentHandle", that could be retrieved as a "ComponentHandle" if we want.

And now, what could be awesome is a method like "get_all_components_of_type", the template parameter being an interface, returning a kind of View. Internally, it could do some magic stuff via C++11 type_traits, like a static_assert if the template parameter is not an interface (std::is_abstract). This method would return all components inheriting from C an Entity has. A slow solution could be to iterate over all valid components (like "entities_for_debugging"), and determine if they inherit from C, using std::is_base_of (well, as they are internally managed as a BaseComponent, it won't work, we would have to know the real type of the component, but that's what I want to achieve).
We could also imagine a method like "has_component_of_base".

Then, we could do that :

if (entity.has_component_of_base<Behavior>())
{
  for (auto behavior : entity.get_all_components_of_type<Behavior>())
  {
    // This will update both Seek and Flee behaviors
    behavior->update(elapsedTime);
  }
}

Now, basically, what I want is to expose the Behavior class to a scripting language so that we could define new behaviors in Python or Lua, but that's not very related.

In short, to return to the initial problem, I just need that inheritance works well in this kind of situation, so that we could achieve some cool and powerful stuff. :)

from entityx.

alecthomas avatar alecthomas commented on June 13, 2024

Ah. It seems almost like you want polymorphic components? You want to be able to say "give me any component that is of interface X"? Unfortunately, this would not work, as the internal memory management requires uniformly sized types (space for components is pre-allocated in blocks).

Ignoring the technical difficulties though, the polymorphic proposal also doesn't fit with the way EntityX approaches ECS systems. It promotes the idea that components are purely data (perhaps with some trivial data access helper methods), and all logic is in systems.

So in your example we would refactor it like so:

struct FleeingBehaviourComponent : public Component<FleeingBehaviour> {
  // Some data necessary for fleeing behaviour
  Entity fleeing_from;
};

struct FleeingBehaviour : public System<FleeingBehaviour>, public Behaviour {
  void configure(EventManager &events) override {
    // ...
  }
  void update(EntityManager &entities, EventManager &events, double dt) override {
    ComponentHandle<FleeingBehaviourComponent> fleeing;
    for (Entity entity : entities.entities_with_components(fleeing)) {
      // Move away from fleeing->fleeing_from...
    }
  }
};

struct SeekingBehaviourComponent : public Component<SeekingBehaviour> {
  // Some data necessary for seeking behaviour
};

struct SeekingBehaviour : public System<SeekingBehaviour>, public Behaviour {
  void configure(EventManager &events) override {
    // ...
  }
  void update(EntityManager &entities, EventManager &events, double dt) override {
    for (Entity entity : entities.entities_with_components<SeekingBehaviourComponent>()) {
    }
  }
};

Note that SystemManager already calls configure() and update().

This might be a too fine-grained way of applying behaviour though, and as you say, applying a scripting layer or data-driven behavioural layer might be more appropriate.

Also, regarding scripting, I wrote Python bindings for EntityX a while back, though alas it has not been updated for the 1.0.0 release. But it might give a helpful start for integrating a scripting layer.

from entityx.

herpec-j avatar herpec-j commented on June 13, 2024

Ah, yes, you're right, I forgot about the recent memory pool stuff. Maybe it could be possible in the old version of Entityx which used shared_ptr.

I wanted something like what I described above so that I could have only one system which iterates through all entities having at least one behavior, and updates all their behaviors, instead of having as many systems as behaviors types. It seemed better to me because once the system would have been implemented, I could have added new behaviors and it would have worked without having to modify the system or, in this case, adding new systems. I was thinking of something like the Unity3D API, where they have kind of polymorphic components. Anyway, I'll find a way to automate the behavior I want to achieve, thanks for your answers !

I just took a look at your Python binding system. Apart from performance reasons, are there any reasons why a component can't be created directly from python ?

from entityx.

zsebastian avatar zsebastian commented on June 13, 2024

It sounds like you want dependency injection. Basically:

class Quackable
{
public:
    virtual void quack() = 0;
    virtual ~Quackable() {};
}

class CayugaDuck: public Quackable
{
public:
    void quack() overide
    {
        std::cout <<    "A Cayuga Duck is a medium-class domesticated duck breed"
                        "that has been a popular variety in the USA since the"
                        "mid-19th" century a popular variety in the USA since" 
                        "the mid-19th century. They are used for egg and meat" 
                        "production, as well as an ornamental bird.";
    }
}

class AbacotRanger: public Quackable
{
public:
    void quack() overide
    {
        std::cout <<    "The 'Abacot Ranger' is a breed of domestic duck,"
                        "initially known as the Hooded Ranger and as Streicherente"
                        "(\'Ranger Duck\', in Germany). A utility breed, originally"
                        "developed for eggs and meat, it is popular for exhibition"
                        "and egg production today.";
    }
}

Those are not components, those are just part of a hierachy, completely seperate from the component system. Here's what the component and system that use this would look like:

struct CanQuack: public Component<CanQuack>
{
    std::unique_ptr<Quackable> quackable;

    CanQuack() = default;
    CanQuack(std::unique_ptr<Quackable> quack)
        :quackable(std::move(quack))
    {}
}

class Quacker: public System<Quacker>
{
public:
    void update(entityx::EntityManager &es, entityx::EventManager, double) override 
    {
        CanQuack::Handle quack;
        for (Entity entity : es.entities_with_components(quack)) 
        {
            quack->quackable->quack();
        }
    }       
}

Obviously to create such entites:

etityx::Entity abacot = entityx.entities.create();
abacot.assign<CanQuack>(std::make_unique<AbacotRanger>());

etityx::Entity cayuga = entityx.entities.create();
cayuga.assign<CanQuack>(std::make_unique<CayugaDuck>());

For more advanced usages of this pattern you could have lists of such CanQuack. Essentially what you have is a component system seperate from ECS, which can be quite useful. I've used this in games to create spells and items for example. To seperate these "components" from ECS by name I have called them aspects in the past.

Been a while since I wrote C++, so my code might be wack, but it should get the idea across.

from entityx.

alecthomas avatar alecthomas commented on June 13, 2024

@herpec-j yes, components can only be created from C++ because of the family IDs. Each ID is assigned during static initialisation, so there's no opportunity for Python to create a new one.

@putBoy good idea! The approach of delegating more dynamic behaviour to attributes of the components is similar to how the Python system works: each PythonComponent basically has pointers to the Python object that controls the logic for that entity, and the component just calls pyobject.update().

from entityx.

alecthomas avatar alecthomas commented on June 13, 2024

I'm going to close this issue, but feel free to keep discussing it here, or on the mailing list.

from entityx.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.