Skip to main content
gamedev.net
🔒 Locked

Wrote an Entity-Component Framework

Started by TheComet Sep 12, 2014 at 12:49 AM 8 replies 3.5k views
Original Post
TheComet
TheComet

I wasn't happy with existing frameworks so I quickly rolled my own. I'm posting this here in the hopes of someone finding it useful.
https://github.com/TheComet93/ontology

Feature highlights are:

  • Unique filtering ability. Systems can declare what components they support, making sure they only receive entities that actually have those components.
  • Compact and comprehensive method for creating components and systems.
  • Doxygen documented code.

The top-level interface can look like this:


int main()
{
    // The world holds everything together
    Ontology::World world;

    // register various systems with the world
    world.getSystemManager()
        .addSystem(new MovementSystem,
            Ontology::SupportsComponents<
                Position,
                Velocity
                >())
        .addSystem(new RenderSystem,
            Ontology::SupportsComponents<
                Position,
                Sprite
                >())
        .addSystem(new InputSystem)
        .addSystem(new MainLoop,
            Ontology::SupportsComponents<
                Ontology::None
                >())
        .initialise()
        ;

    // create an entity in the world
    world.getEntityManager().createEntity("Player")
        .addComponent(new Position(0, 0))
        .addComponent(new Velocity(0, 0))
        .addComponent(new Sprite("res/player.png"))
        ;

    // run game
    world.getSystem<MainLoop>().execute();

    return 0;
}

The syntax is very boost-esque.

"I would try to find halo source code by bungie best fps engine ever created, u see why call of duty loses speed due to its detail." -- GettingNifty
KulSeran
KulSeran

I'd say the biggest issue with this system is that you're storing components on entities without a decent wrapper for how to allocate those components.

Ideally, each component would be contiguous in ram, as opposed to disjoint as your example code will generate (via "new Position(...)"). I'd like to see a better wrapper that hides the allocation scheme of the components to allow for better memory access patterns.

It's not ideal to iterate over entities to reach components when you already know something like the RenderSystem only cares about the union of the sets for Position and Velocity that share entity ids. If the union is sparse on either component list, or sparce among all entities then you're wasting a lot of time iterating over every entity to find out if it has the matching set of components.

ProtectedMode
ProtectedMode

Passing a unique pointer instead of a raw pointer is also better if you are giving up ownership. Aside from that and what KulSeran mentioned, it looks interesting. smile.png

TheComet
TheComet

Thanks for the feedback!


I'd say the biggest issue with this system is that you're storing components on entities without a decent wrapper for how to allocate those components.
Ideally, each component would be contiguous in ram, as opposed to disjoint as your example code will generate (via "new Position(...)"). I'd like to see a better wrapper that hides the allocation scheme of the components to allow for better memory access patterns.

Very good point, I'll see if I can get that to work. Is there a common technique when trying to store varying types in contiguous memory? I can think of two immediate ways, such as maintaining n number of std::vector objects where n=number of types, or doing some allocation trickery since the types are known at compile time. [EDIT] Actually scratch the first suggestion, there's guaranteed to only ever be one instance of every component.


Passing a unique pointer instead of a raw pointer is also better if you are giving up ownership

I actually removed that because I found it makes its usage a little unwieldy and harder to read. I know that shouldn't be an argument...

"I would try to find halo source code by bungie best fps engine ever created, u see why call of duty loses speed due to its detail." -- GettingNifty
Postie
Postie




Unique filtering ability. Systems can declare what components they support, making sure they only receive entities that actually have those components.

I wouldn't really call that a "unique" feature, I implemented the same exact concept in my ECS based on seeing it in several other ECS's I studied for ideas. Also, sorry to be pedantic, but it doesn't appear as though the systems are declaring anything about what components they support, the call to addsystem() is providing that information externally, which isn't quite the same thing. You could create a system that only knows how to work on a component of type A, and call addSystem() and tell the systemmanager it works on components of type B, and it would be quite happy to sit there idle I'm guessing?

[size="2"]Currently working on an open world survival RPG - For info check out my Development blog:[size="2"] ByteWrangler
Starfarer42
Starfarer42




Very good point, I'll see if I can get that to work. Is there a common technique when trying to store varying types in contiguous memory? I can think of two immediate ways, such as maintaining n number of std::vector objects where n=number of types, or doing some allocation trickery since the types are known at compile time. [EDIT] Actually scratch the first suggestion, there's guaranteed to only ever be one instance of every component.

I think the point is that you *wouldn't* (or shouldn't) store varying types in contiguous memory. You might have one component of each type per entity, but you have many entities with components of the same type. So you store each type of component together in its own list -- all the PhysicsComponents in one list, all the GraphicsComponents in another list, all the AIComponents together, and so on. You associate each component with the entity it belongs to, but there's no need to store the components for a given entity next to each other. When you want to handle your physics simulation, for example, you iterate over all the PhysicsComponents in your game world which is fast because they're all in contiguous memory and you don't need any branches to skip over entities that don't have a PhysicsComponent.

KulSeran
KulSeran




there's no need to store the components for a given entity next to each other

Thanks Starfarer42, that's really what I was trying to get at. My issue with your implementation TheComet is that it puts too much emphasis on allocating and attaching things to an Entity. That provides flexibility, but costs performance. An ideal entity component system focuses more on the components / systems than it does on the "entity" part, because it should be rare that you ever need to check what other components an entity has. Components can be stored in a single array per-type, like Starfarer42 suggests. If you keep them sorted by entity id (or in a binary tree pattern), it's possible to quickly traverse one or more component ('attribute') lists in parallel paired off by id for the rare cases where you need multiple attributes together to perform a calculation.

TheComet
TheComet

it doesn't appear as though the systems are declaring anything about what components they support, the call to addsystem() is providing that information externally, which isn't quite the same thing.

The information eventually ends up inside the base class of System. The reason I didn't want to do it through System's constructor was for three reasons:

  • It would force the user to write a constructor every time he derives from System just to pass in the necessary information.
  • Adding future options/more arguments to addSystem would cause every derived class' constructor to be re-written -> unnecessary.
  • addSystem() is more capable of making default decisions when arguments aren't supplied because the class it belongs to has access to all systems (such as execution reordering).

You could create a system that only knows how to work on a component of type A, and call addSystem() and tell the systemmanager it works on components of type B, and it would be quite happy to sit there idle I'm guessing?

What Starfarer42 said and you'll get a runtime error if you try to fetch a component from an entity that doesn't exist.

@ Starfarer42 & KulSeran

Thanks for the explanation, now I get where you're coming from. I've added it to my todo list.

Two other things I'm working on are:

  • Each system to precompute a list of entities it supports instead of comparing every time world.update() is called.
  • A multithreaded approach at processing entities.
"I would try to find halo source code by bungie best fps engine ever created, u see why call of duty loses speed due to its detail." -- GettingNifty
Juliean
Juliean



The information eventually ends up inside the base class of System. The reason I didn't want to do it through System's constructor was for three reasons:

  • It would force the user to write a constructor every time he derives from System just to pass in the necessary information.
  • Adding future options/more arguments to addSystem would cause every derived class' constructor to be re-written -> unnecessary.
  • addSystem() is more capable of making default decisions when arguments aren't supplied because the class it belongs to has access to all systems (such as execution reordering).


You don't have to use a constructor for this, but I would also advice towards keeping this information inside the system. You can have a virtual method in the system base class:


class System
{
public:

     virtual SupportedComponents GetSupportedComponents(void) const = 0;
}

every system will have to overwrite this and supply the components it wants to use. You can all this method e.g. directly inside "addSystem", which poses almost zero overhead but keeps this information in the systems declaration.

TheComet
TheComet

@Juliean - That's exactly what I'm doing. biggrin.png

"I would try to find halo source code by bungie best fps engine ever created, u see why call of duty loses speed due to its detail." -- GettingNifty

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.