Welcome, Guest. Please login or register. Did you miss your activation email?

Author Topic: Designing a Component-based system  (Read 7744 times)

0 Members and 1 Guest are viewing this topic.

ramaskrik

  • Newbie
  • *
  • Posts: 45
    • View Profile
Designing a Component-based system
« on: February 06, 2016, 08:22:57 pm »
Hi everyone!

I am trying to implement a kind of a component based system and having trouble translating it into SFML code.

The Brief Idea
The Entity class consists of components (like RenderComponent, InputComponent, KinematicsComponent, CollisionComponent... which hold data), and systems which operate on them (RenderSystem, InputSystem, KinematicsSystem, CollisionSystem...). I have however no idea, how would I implement RenderComponent.

  • Should I make it abstract and derive SpriteComponent,  ShapeComponent... from RenderComponent? That way, I would have to synchronize the sf::Transformable data in those components with KinematicsComponent, which simply doesn't feel right, not to mention that the true Kinematics data would be held in RenderComponent in sf::Transformable in sf::Sprite which is also counter-intuitive
  • Would it work if I simply held sf::Drawable in RenderComponent and sf::Transformable in KinematicsComponent and let the system of mine operate on these? How would SFML know then where to draw the Drawable? I am confused here.

Thanks for any response.

Sorry, just had a little mind-refusing-to-work and totally forgot how virtual inheritance works. I've implemented the thing flawlessly. Please, remove this thread. :)
« Last Edit: February 07, 2016, 01:11:21 pm by ramaskrik »

Nexus

  • SFML Team
  • Hero Member
  • *****
  • Posts: 6286
  • Thor Developer
    • View Profile
    • Bromeon
Re: Designing a Component-based system
« Reply #1 on: February 07, 2016, 04:15:28 am »
Virtual inheritance is extremely rare, and generally indicates a complex inheritance design -- something you're exactly trying to avoid with component-based systems.

Maybe you're trying to achieve something specific but have the wrong technique in mind?
Zloxx II: action platformer
Thor Library: particle systems, animations, dot products, ...
SFML Game Development:

ramaskrik

  • Newbie
  • *
  • Posts: 45
    • View Profile
Re: Designing a Component-based system
« Reply #2 on: February 07, 2016, 11:09:10 am »
Speaking about the inheritance,I was talking about the sf::Sprite (and sf::Text and the others) inheriting from sf::Transformable and sf::Drawable.
What I am really doing is that I take sf::Drawable & from sf::Sprite and put it into my RenderComponent and then I take sf::Transformable & from the same sf::Sprite and put it into KinematicsComponent. That way I have a nice modular way (at least I like to think it is nice and modular:-) ) of splitting the sprites and text and shapes and vertex arrays into de-facto independent components. The resulting demo is here:

#include <SFML/Graphics.hpp>
#include "Game.hpp"

#include "RenderComponent.hpp"
#include "RenderSystem.hpp"

#include "KinematicsComponent.hpp"
#include "KinematicsSystem.hpp"

int main()
{
//     Game game;
//     game.run();
   
    sf::RenderWindow window(sf::VideoMode(800, 600, 32), "HALLO");
   
    sf::Texture texture;
    texture.loadFromFile("resources/img.png");
    texture.setSmooth(true);
    sf::CircleShape shape(150.f, 2000);
    shape.setTexture(&texture, true);
 
   
    RenderComponent render_component(shape);
    RenderSystem render_system(render_component, window);
   
    KinematicsComponent kinematics_component(shape);
    KinematicsSystem kinematics_system(kinematics_component);
   
    kinematics_component.set_position(sf::Vector2f(200.f, 200.f));
    kinematics_component.set_acceleration(sf::Vector2f(-1.f, -1.f));
   
   
    while(window.isOpen())
    {
        window.clear(sf::Color::Blue);
        render_system.update();
        kinematics_system.update(.0001f);
        window.display();
    }

    return EXIT_SUCCESS;
}
 

Or am I making a stupid beginner mistake and there is a better way of doing this?

Nexus

  • SFML Team
  • Hero Member
  • *****
  • Posts: 6286
  • Thor Developer
    • View Profile
    • Bromeon
Re: Designing a Component-based system
« Reply #3 on: February 07, 2016, 12:46:15 pm »
Speaking about the inheritance,I was talking about the sf::Sprite (and sf::Text and the others) inheriting from sf::Transformable and sf::Drawable.
Okay, then be careful with terminology. Virtual inheritance is something different ;)

Or am I making a stupid beginner mistake and there is a better way of doing this?
No, that separation sounds pretty reasonable.

You might want to think about ownership though. Currently, the components only keep a pointer/reference to the shape. This works well as long as you declare it in main() and thus keep it alive long enough, but it doesn't scale. You should think about where the shape is stored in your system, as soon as things get more complex.
Zloxx II: action platformer
Thor Library: particle systems, animations, dot products, ...
SFML Game Development:

ramaskrik

  • Newbie
  • *
  • Posts: 45
    • View Profile
Re: Designing a Component-based system
« Reply #4 on: February 07, 2016, 01:08:34 pm »
Speaking about the inheritance,I was talking about the sf::Sprite (and sf::Text and the others) inheriting from sf::Transformable and sf::Drawable.
Okay, then be careful with terminology. Virtual inheritance is something different ;)
Right. Virtual inheritance is something different than just pure, non-virtual inheriting from an ABC. Noted down. :)

You might want to think about ownership though. Currently, the components only keep a pointer/reference to the shape. This works well as long as you declare it in main() and thus keep it alive long enough, but it doesn't scale. You should think about where the shape is stored in your system, as soon as things get more complex.
Yep, that's what bothers me. I want to pass these components and systems (or derived implementations of those) into Entity class then, but it bothers me whom to give the responsibility of owning the original sf::Sprite / sf::Shape / sf::Text / sf::VertexArray :).
  • Should I implement SpriteManager, ShapeManager, TextManager, VertexArrayManager classes?
  • Should I keep it in the respective Entity? (I don't like this option too much, as this is, IMO, mixing different levels of abstractions in one class)
  • Something else?

Nexus

  • SFML Team
  • Hero Member
  • *****
  • Posts: 6286
  • Thor Developer
    • View Profile
    • Bromeon
Re: Designing a Component-based system
« Reply #5 on: February 07, 2016, 01:27:30 pm »
You use SFML to visualize your entities, so it's standing to reason to move the SFML object to the graphics component. The physics/kinetics component should be decoupled from rendering. It can still use a sf::Transformable copy if that helps simplify the implementation, but it needn't reference the same object directly. You want components to act independently; changes in one should not implicitly affect the other.

So, your RenderComponent class could store a std::unique_ptr<sf::Drawable>. If you want the component to be deep-copyable, simply use aurora::CopiedPtr<sf::Drawable> and it will take care of appropriately copying a sprite, a text, or whatever. Textures and other resources shared between entities should be stored in their respective manager class. There's no need to reinvent the wheel, I developed thor::ResourceHolder a while ago, which is quite flexible and easy to use.
Zloxx II: action platformer
Thor Library: particle systems, animations, dot products, ...
SFML Game Development:

ramaskrik

  • Newbie
  • *
  • Posts: 45
    • View Profile
Re: Designing a Component-based system
« Reply #6 on: February 07, 2016, 02:03:28 pm »
Would it be wise to have yet another type of Component, SpriteComponent (and the others, ofc) , as a kind of sf::Sprite wrapper, which would exist just for this purpose of owning the sf::Sprite? That way I could easily implement Object Pool pattern if needed (as the SpriteComponent would take care of reusing sf::Sprite, instead of recreating it). Has this approach got any significant disadvantage?

Yes, I want to definitely use smart pointers for this purpose, thank you for reminding it to me. As for the Resource Management, I have implemented a naive ResourceManager in first commits, so I am probably sticking to it, as it fulfills my needs and doesn't create more dependencies (I do know about the great Thor Library of yours, but decided to roll own my own implementation.) Thanks for the pointers (both metaphorically and literally :) ) anyway.

Nexus

  • SFML Team
  • Hero Member
  • *****
  • Posts: 6286
  • Thor Developer
    • View Profile
    • Bromeon
Re: Designing a Component-based system
« Reply #7 on: February 07, 2016, 02:09:03 pm »
Is it needed to have a separate SpriteComponent, TextComponent, ShapeComponent, VertexArrayComponent, ...? Why not a single one working with all sf::Drawables?

The Object Pool pattern can be used in either case. But I wouldn't make my life more difficult than necessary, performance pays only off for really many objects (thousands to millions). Sprites are quite lightweight objects, you don't need to "reuse" (aka share) them, especially not at the cost of additional complexity/dependencies.
Zloxx II: action platformer
Thor Library: particle systems, animations, dot products, ...
SFML Game Development:

fallahn

  • Sr. Member
  • ****
  • Posts: 492
  • Buns.
    • View Profile
    • Trederia
Re: Designing a Component-based system
« Reply #8 on: February 07, 2016, 02:10:49 pm »
Vittorio Romeo gave a very good talk on ECS design which is worth checking out. Almost the first first thing he did was point out all the flaws in my existing design ;) I wish I had seen this before I started.


ramaskrik

  • Newbie
  • *
  • Posts: 45
    • View Profile
Re: Designing a Component-based system
« Reply #9 on: February 07, 2016, 02:20:52 pm »
Is it needed to have a separate SpriteComponent, TextComponent, ShapeComponent, VertexArrayComponent, ...? Why not a single one working with all sf::Drawables?
The RenderableComponent deals with drawing. I need to store those objects somewhere (both sf::Drawable and sf::Transformable part), so they don't just go out of scope. I also want to have single-responsibility classes (which is why I don't want to store sf::Sprite right in the RenderComponent, as KinematicsComponent will also need access to sf::Transformable part of sf::Sprite). But I guess I could at least use template class (although I am not too familiar with template metaprogramming - at least I will learn something new :) ).

The Object Pool pattern can be used in either case. But I wouldn't make my life more difficult than necessary, performance pays only off for really many objects (thousands to millions). Sprites are quite lightweight objects, you don't need to "reuse" (aka share) them, especially not at the cost of additional complexity/dependencies.

I consider it a good design choice, and am more interested if there are other disadvantages besides a minor implementation time addition.

Vittorio Romeo gave a very good talk on ECS design which is worth checking out. Almost the first first thing he did was point out all the flaws in my existing design ;) I wish I had seen this before I started.

(click to show/hide)
I have seen a part of this video here on SFML forums and this is what inspired me to write a very simplified version of what he talked about, although in less DOD-style, less metaprogramming and more classic Component-style. Great talk anyway.
« Last Edit: February 07, 2016, 02:22:53 pm by ramaskrik »

Nexus

  • SFML Team
  • Hero Member
  • *****
  • Posts: 6286
  • Thor Developer
    • View Profile
    • Bromeon
Re: Designing a Component-based system
« Reply #10 on: February 07, 2016, 03:12:56 pm »
Vittorio Romeo gave a very good talk on ECS design which is worth checking out.
Thanks for the link. I've seen quite a few videos from Vittorio, he's one of the few guys who makes high-quality videos about programming (unlike some popular tutorial authors who barely know how to write a correct C++ program, but that's a different topic...) Currently watching it, seems great so far :)

The RenderableComponent deals with drawing. I need to store those objects somewhere (both sf::Drawable and sf::Transformable part), so they don't just go out of scope.
Yes, as a smart pointer in RenderComponent. A smart pointer owns its pointee.

as KinematicsComponent will also need access to sf::Transformable part of sf::Sprite).
Why would a physics component need to store a sprite? Why can't it just keep its own independent copy of the transform, either as separate position/rotation/scale, or as a sf::Transformable object? There's no reason why it must reference the same sprite used in the RenderComponent. In fact, the point of introducing components is to split data and to get rid of such implicit dependencies. Nobody cares if a few bytes are duplicated.

On the other hand, truly separating the two gives you great flexibility. Now you have an implicit dependency between RenderComponent and KinematicsComponent: The one who references the sprite cannot live with the one who stores it. That's unnecessary. You want to be able to create KinematicsComponents even if there is no corresponding RenderComponent; imagine invisible objects that still have a physical representation in the world.

Furthermore, if KinematicsComponent is supposed to live up to its name, it should at least support velocity and possibly angular velocity, as well as second derivatives (acceleration). On the other hand, I'm not sure if an origin in SFML's sense is necessary. So sf::Transform is still too limited, and you need a better representation of your physical objects anyway.

But I guess I could at least use template class (although I am not too familiar with template metaprogramming - at least I will learn something new :) ).
I have no idea how this relates to the rest of the discussion. In any case, template programming and template metaprogramming are different things.

I consider it a good design choice, and am more interested if there are other disadvantages besides a minor implementation time addition.
I use a more pragmatic decision process: I don't use anything unless I can argue what critical advantage it brings me. If I have to start arguing why a certain feature has a disadvantage and is thus not used, I won't ever get anywhere.

Besides, "minor implementation time" is not the only thing to consider. Whatever you add to your code needs to be maintained until eternity, so choose carefully. If you're not sure where to store a sprite, I doubt that an Object Pool is the one thing you need right now. Start simple, and add things when the need develops, not before.
« Last Edit: February 07, 2016, 03:14:38 pm by Nexus »
Zloxx II: action platformer
Thor Library: particle systems, animations, dot products, ...
SFML Game Development:

ramaskrik

  • Newbie
  • *
  • Posts: 45
    • View Profile
Re: Designing a Component-based system
« Reply #11 on: February 07, 2016, 03:29:55 pm »

as KinematicsComponent will also need access to sf::Transformable part of sf::Sprite).
Why would a physics component need to store a sprite? Why can't it just keep its own independent copy of the transform, either as separate position/rotation/scale, or as a sf::Transformable object?
No, it doesn't need sf::Sprite, just the sf::Transformable part.
There's no reason why it must reference the same sprite used in the RenderComponent. In fact, the point of introducing components is to split data and to get rid of such implicit dependencies. Nobody cares if a few bytes are duplicated.

On the other hand, truly separating the two gives you great flexibility. Now you have an implicit dependency between RenderComponent and KinematicsComponent: The one who references the sprite cannot live with the one who stores it. That's unnecessary. You want to be able to create KinematicsComponents even if there is no corresponding RenderComponent; imagine invisible objects that still have a physical representation in the world.
That seems like a case for SpriteComponent. No duplication (which means no need for synchronization) and everyone got its part (RenderComponent its access to sf::Drawable and KinematicsComponent to sf::Transformable). Also, no coupling between KinematicsComponent and RenderComponent. Unless I am not understanding this right, of course. (Please correct me :) )

Furthermore, if KinematicsComponent is supposed to live up to its name, it should at least support velocity and possibly angular velocity, as well as second derivatives (acceleration). On the other hand, I'm not sure if an origin in SFML's sense is necessary. So sf::Transform is still too limited, and you need a better representation of your physical objects anyway.
It does support those things. Don't worry, I got it covered :)

class KinematicsComponent: public Component
{
public:
    KinematicsComponent(sf::Transformable & transformable);
    virtual ~KinematicsComponent() = default;

    // Uninteresting setters and getters  
   
private:
   
    sf::Vector2f acceleration;
    sf::Vector2f velocity;
   
    float angular_accelaration;
    float angular_velocity;

    sf::Transformable & transformable;
};
 

But I guess I could at least use template class (although I am not too familiar with template metaprogramming - at least I will learn something new :) ).
I have no idea how this relates to the rest of the discussion. In any case, template programming and template metaprogramming are different things.
Second time today I used a word I didn't understand correctly. Shame on me!  :-[

I consider it a good design choice, and am more interested if there are other disadvantages besides a minor implementation time addition.
I use a more pragmatic decision process: I don't use anything unless I can argue what critical advantage it brings me. If I have to start arguing why a certain feature has a disadvantage and is thus not used, I won't ever get anywhere.

Besides, "minor implementation time" is not the only thing to consider. Whatever you add to your code needs to be maintained until eternity, so choose carefully. If you're not sure where to store a sprite, I doubt that an Object Pool is the one thing you need right now. Start simple, and add things when the need develops, not before.
Good approach. But I like it decoupled anyway. Let me have this little perversion of mine :)

Nexus

  • SFML Team
  • Hero Member
  • *****
  • Posts: 6286
  • Thor Developer
    • View Profile
    • Bromeon
Re: Designing a Component-based system
« Reply #12 on: February 07, 2016, 03:43:15 pm »
That seems like a case for SpriteComponent. No duplication
Yes, there's duplication as soon as you introduce TextComponent, ShapeComponent, VertexArrayComponent... Again: why no DrawableComponent?

and everyone got its part (RenderComponent its access to sf::Drawable and KinematicsComponent to sf::Transformable). Also, no coupling between KinematicsComponent and RenderComponent. Unless I am not understanding this right, of course.
I outlined the coupling problem above, what's not clear?

Or, explain me why two different components need access (via reference) to the same sprite, stored in a third component, and why a component cannot carry its own data. And tell me how those two components (Kinetics + Render) can exist without the third (Sprite).

But I like it decoupled anyway. Let me have this little perversion of mine :)
It's your code, so you may do as you wish ;)

But it definitely helps to first get the basic structure right, before thinking about optimizations. You have design problems which are much more fundamental and which you need to address first. By the way, an Object Pool is an allocator and thus primarily an optimization. It doesn't really decouple things, just replaces one dependency with another (instead of depending on the new operator, you depend on the pool).
« Last Edit: February 07, 2016, 03:45:30 pm by Nexus »
Zloxx II: action platformer
Thor Library: particle systems, animations, dot products, ...
SFML Game Development:

ramaskrik

  • Newbie
  • *
  • Posts: 45
    • View Profile
Re: Designing a Component-based system
« Reply #13 on: February 07, 2016, 05:43:08 pm »
Yes, there's duplication as soon as you introduce TextComponent, ShapeComponent, VertexArrayComponent... Again: why no DrawableComponent?
I already have a component which holds drawable: RenderComponent. You "insisting" on DrawableComponent brings a question - is it possible to have "living" sf::Drawable and sf::Transformable with the respective sf::Sprite going out of the scope?

I outlined the coupling problem above, what's not clear?

Or, explain me why two different components need access (via reference) to the same sprite, stored in a third component, and why a component cannot carry its own data. And tell me how those two components (Kinetics + Render) can exist without the third (Sprite).

Kinetics and Render Components are passed sf::Transformable and sf::Drawable respectively. They don't care if it's a Sprite, Text, VertexArray, if it comes from main function or SpriteComponent.. I see where you're coming from and I would gladly just copy sf::Transformable and sf::Drawable to the respecting components, but, the virtual function calls are not going to work then, right? And I don't want Kinetics nor Render Components to exclusively own the sprite - it does not make sense, as it belongs to both of them equally. They should be on equal level of abstraction, so they should equally hold (or not hold) their resources. And if holding their resources equally is impossible, I guess I must introduce a component which does exactly that.

But it definitely helps to first get the basic structure right, before thinking about optimizations. You have design problems which are much more fundamental and which you need to address first. By the way, an Object Pool is an allocator and thus primarily an optimization. It doesn't really decouple things, just replaces one dependency with another (instead of depending on the new operator, you depend on the pool).
Yes, I know, but this is less optimization and more a design question.

I have decided to go with a modification of Sprite/Text/Shape Component implemented via templates, so there is no duplication. Thank you guys for pointers and a nice discussion.

Nexus

  • SFML Team
  • Hero Member
  • *****
  • Posts: 6286
  • Thor Developer
    • View Profile
    • Bromeon
Re: Designing a Component-based system
« Reply #14 on: February 08, 2016, 10:22:27 am »
I'm not sure you've understood what I wanted to say. You insist on storing both the sf::Drawable and sf::Tranformable part of the sprite in two separate components, because you somehow assume both must be abstracted exactly this way. I told you why this is not a good idea, it severely limits how you can use your components.

My suggestion is:

RenderComponent stores a std::unique_ptr<sf::Drawable> and thus owns the graphics object. Like it should, because it's the only component responsible of rendering. You can put whatever drawable you like - sprites, texts, shapes, vertex arrays, your own custom class...

KineticsComponent stores some vectors and scalar values that represent the current physical state of the object. There's absolutely no reason why it should be tied to a sf::Sprite (a visual representation of a 2D object), only because the latter also happens to have a position and rotation. We're talking about a physical representation, not a graphical one, so let's keep that separate.

The third component is inherently unnecessary.
Zloxx II: action platformer
Thor Library: particle systems, animations, dot products, ...
SFML Game Development: