Need help with issues I encountered while writing my ECS

Started by
16 comments, last by crancran 8 years, 4 months ago

Hi everyone,

I'm working on making an Entity Component System in C++ for my game and there are some things I'm a bit confused about.

Right now my game is structured as following:

  • Entity: Contains an unique id and a bunch of components, only accessible by using a singleton EntityManager. e.g.

    int EntityManager::createEntity()

    void EntityManager::addComponent<T>(T* t, int entity_id)

    T EntityManager::getComponent<T>(int entity_id)

  • Component: Simple 'databag' structs without any methods, just simple data. These are stored in entities using the above mentioned EntityManager as void* (and using typeid::hash_code to compare the requested types at get/hasComponent).

  • System: Derived from 'ISystem' with only two methods 'onUpdate' and 'onMessage', and stored in a SystemManager singleton. When a component gets added or removed the systems get a message containing the entity id, so they can (un)register to it.

Is this the right way of doing this? I read some things about getting a lot of cache misses if you don't store components in a contiguous array but I can't imagine how to do that without coupling back the code again. (So far I haven't noticed any performance issues, though there's not a lot running right now).

Furthermore, these two problems have been bothering me a lot:

1) It seems like a good idea to use separate rendering systems (e.g. SpriteRenderer, ShapeRenderer, TilemapRenderer) but how do I take care of things like sorting (depth, shader, texture) and batching when these systems are completely decoupled?

2) The camera, I've searched far and wide for a good answer but I can't seem to find one. Is the camera an entity? If so, does that mean the view matrix is a global or singleton? (What if I want multiple camera's with only 1 'active' like in Unity?). This is assuming that the camera transformation gets applied at the rendering process, otherwise I guess it'd be possible to have like a 'CameraSystem' which translates all objects opposite of the currently active camera position, though that seems slow to me.


Please enlighten me!

Thanks in advance my dear gamedevs!

Advertisement

First of all, don't make anything be a global or singleton (including EntityManager and SystemManager). There's absolutely no reason to do that. Just pass your instances around to whomever needs to use them.


Is this the right way of doing this? I read some things about getting a lot of cache misses if you don't store components in a contiguous array but I can't imagine how to do that without coupling back the code again.

Not sure why you think that would "couple the code back again".You can store a contiguous array for each component type in your EntityManager (The EntityManager can be agnostic of the actual component types - only the callers need to know). The systems can request the ones they need (some people store components in the SystemManagers, but I don't like this, as it's not always clear which system would "own" which component). Then somewhere you need to store a mapping of which components each entity has. There are many ways to do this, and a good design probably isn't trivial (pointers vs indices, do you store these in the entity object or in a table in EntityManager, do components move around in their array and you need to deal with that, etc...).

If you're making a small game, you probably don't need to worry about storing components in contiguous memory, but it is one of the major benefits of an entity system.


2) The camera, I've searched far and wide for a good answer but I can't seem to find one. Is the camera an entity? If so, does that mean the view matrix is a global or singleton?

Sure, it can be an entity. This really isn't very complex. The view matrix is data in a component on the camera entity. The render system (or whomever needs the camera information) just looks for the first "active" camera component and uses the information in that. Done.

Hi phil, thanks for your response, there are some things I'd like to ask about some more.


First of all, don't make anything be a global or singleton (including EntityManager and SystemManager). There's absolutely no reason to do that. Just pass your instances around to whomever needs to use them.

Would this mean I make these classes a non-singleton and just pass references/pointers to whoever is using them? (e.g. EntityManager has a pointer to SystemManager that it sends messages to on add/remove component). Even though it seems a bit cleaner, it does mean that whenever you decide to make use of what-would-be-a-singleton, you'd have to go out of your way to add it to the constructor (or even worse: add getters and setters).


Not sure why you think that would "couple the code back again".You can store a contiguous array for each component type in your EntityManager (The EntityManager can be agnostic of the actual component types - only the callers need to know).

What confuses me about this is if you store the components as say IComponent* or void*, how would that solve the issue of accessing contiguous memory? This is actually why I thought it would mean you'd have to couple back the code because when a system requests say a 'Position' component, the EntityManager would still have to iterate through the components to see which one is a 'Position' component. Unless you store all types of components in their own respective container (e.g. vector<Position> position_components <-- accessed by entity index/id). But this would couple back the code again because you'd have to create a new container for each new component that an entity could possibly have.

EDIT: Your suggestion about the camera entity sounds like a good idea! But how would I go about making sure only one camera can be active at a time (without making use of a global state)?


Even though it seems a bit cleaner, it does mean that whenever you decide to make use of what-would-be-a-singleton, you'd have to go out of your way to add it to the constructor

Yes. That's a good thing. Makes your dependencies clear.


Unless you store all types of components in their own respective container (e.g. vector position_components <-- accessed by entity index/id). But this would couple back the code again because you'd have to create a new container for each new component that an entity could possibly have.

I just coded this up quickly (I've only ever implemented one in C#, there might be better ways to do this in C++, but it should give you the idea).


// Just some common base class
struct ComponentArrayBase {};

template<typename _T>
struct ComponentArray : public ComponentArrayBase
{
    ComponentArray(size_t defaultSize) : Components(defaultSize) {}

    std::vector<_T> Components;
};

class EntityManager
{
public:
    template<typename _T>
    size_t AddComponentType(size_t defaultSize)
    {
        _componentArrays.push_back(std::make_unique<ComponentArray<_T>>(defaultSize));
        return _componentArrays.size() - 1;
    }

    template<typename _T>
    std::vector<_T> &GetComponentArray(size_t componentTypeId)
    {
        return static_cast<ComponentArray<_T>&>(*_componentArrays[componentTypeId]).Components;
    }

private:
    std::vector<std::unique_ptr<ComponentArrayBase>> _componentArrays;
};


// Some components:
struct PositionComponent
{
    float x, y, z;
};
struct PhysicsComponent
{
    float mass;
    int shapeType;
};



    EntityManager em;

    // At startup, add the component types we need:
    size_t ComponentIdPosition = em.AddComponentType<PositionComponent>(500);
    size_t PhysicsIdPosition = em.AddComponentType<PhysicsComponent>(500);

    // Them whomever needs them does this:
    std::vector<PositionComponent> &positions = em.GetComponentArray<PositionComponent>(ComponentIdPosition);
    std::vector<PhysicsComponent> &physics = em.GetComponentArray<PhysicsComponent>(PhysicsIdPosition);


... you could use hard-coded ids too (and then maybe an unordered_map instead of a vector for _componentArrays) so you don't need to pass the generated component ids around. Or you could use typeid(_T) if you have RTTI enabled.


Your suggestion about the camera entity sounds like a good idea! But how would I go about making sure only one camera can be active at a time (without making use of a global state)?

Do you need to? It's hard to offer suggestions here without knowing more about why you want multiple cameras. Is it for a split-screen view? Is it for switching between cut-scenes and gameplay? The reasons for it and your needs will dictate how you implement this.

[Disclaimer: I've never written a game with an ECS system before, though I've read a bunch about them. So below is acquired second-hand knowledge, not learned first-hand experience.

Take with a glass of water and spoonful of salt. Consume at your own risk] smile.png

Unless you store all types of components in their own respective container (e.g. vector<Position> position_components <-- accessed by entity index/id).

Yes, this is how it's often done. Though because components may move around within the vector as you're adding and removing entities (because not every entity will have every component, and because you may want to occasionally sort components), you may want to do something like:


std::unordered_map<EntityID, ComponentIndices> // or std::vector<ComponentIndices> with 'EntityID' being the index into *this* vector.
 
struct ComponentIndices
{
     int physicsComponentIndex = NoComponent;
     int scriptableComponentIndex = 17;
     etc...
};

But this would couple back the code again because you'd have to create a new container for each new component that an entity could possibly have.

Yep. You'll also have to write a new data struct and a new logic system for each Component type. It's not hard to type std::vector<RenderComponentDataStruct>.


struct SpecificComponent
{
    //...data...
};

class SpecificSystem //Why inherit?
{
public:
   void onUpdate();
   //other funcs, some system-specific.
   
private:
   std::vector<SpecificComponent> componentData;
};

In some ECS architectures the "entities" are conceptual only, without any actual class. In those flavors of ECS, an 'entity' is just an ID, with all the data in the components and all the logic ran by systems (which may include a scriptable component for more uniquish per-entity custom behavior logic).

Basically, components are the data, systems hold the logic, operate on the data, and own the components. Because some systems depend on other systems (like physics on positions), you can pass one system as a reference to the constructor of dependent systems.

In total, you really shouldn't have too many "systems". A dozen-ish maybe.

But how would I go about making sure only one camera can be active at a time (without making use of a global state)?

What if you want more than one camera active at a time? Maybe you want to support split screen, or maybe you want to, for example, show in the upper-left corner of the screen a door opening up elsewhere in the level when you step on a switch.

Instead of thinking of cameras as "active" or "inactive", I'd just give cameras IDs (if they are entities, use their entityID), and assign your screen a camera.

For example:


struct Screen
{
    Rect region; //What region of the game window to render into.
    CameraID camera; //What camera to use.
};

void World::Render(const Screen &screen);

Though I don't know if I'd actually make the camera an entity or not. Not everything needs to be forced into components.

It seems like a good idea to use separate rendering systems (e.g. SpriteRenderer, ShapeRenderer, TilemapRenderer) but how do I take care of things like sorting (depth, shader, texture) and batching when these systems are completely decoupled?


You can have those "renderers" generate the drawcalls, and then pass those drawcalls to the real renderer that culls them, sorts them, and then draws them all.
In my code (which isn't an ECS), I call these "batches" - LineBatch, QuadBatch (for sprites, tiles, etc...). Instead of a SpriteRenderer, I create a QuadBatch, fill it with sprites, and then render it.

Basically, the batches are for code-convenience/code-clarity in creating the drawcalls, so they are built on top of the regular renderer. You mentioned different "rendering systems", which is confusing because "systems" mean something specific in a ECS system. You don't want multiple different rendering systems in the ECS sense of the word, but you can certainly have convenience classes or convenience functions that help you generate data for the renderer.

In fact, your components should be at a higher level of abstraction than your renderer anyway (though you may still have a RenderingSystem depending on your design), because not everything you'll draw will come from components. You don't have to (and probably shouldn't) cram your HUD or other GUI stuff into your ECS system, for example.

ECS is a useful paradigm for parts of your architecture, but not your entire architecture. Different paradigms and patterns fit the nature of different problems, and games have multiple problems that are best tackled through the lens of different paradigms. There is no "one solution fits every problem", and because games are complex things with many disparate challenges, ECS doesn't solve every game-related problem perfectly; it solves some of the problems really well, and others poorly, so make it "a" tool in your toolbox, not "the" tool.


    EntityManager em;

    // At startup, add the component types we need:
    size_t ComponentIdPosition = em.AddComponentType<PositionComponent>(500);
    size_t PhysicsIdPosition = em.AddComponentType<PhysicsComponent>(500);

    // Them whomever needs them does this:
    std::vector<PositionComponent> &positions = em.GetComponentArray<PositionComponent>(ComponentIdPosition);
    std::vector<PhysicsComponent> &physics = em.GetComponentArray<PhysicsComponent>(PhysicsIdPosition);


... you could use hard-coded ids too (and then maybe an unordered_map instead of a vector for _componentArrays) so you don't need to pass the generated component ids around. Or you could use typeid(_T) if you have RTTI enabled.



A typical pattern here is to add one more layer of indirection and to reference the components by the EntityID itself. You don't need the entity store know the index of the component at all. When you query the Component Mapper for a particular entity, it uses a data structure (e.g. a good hash table) to map the EntityID to the ComponentIndex.

The public API for the Component Mapper can allow you to use the ComponentIndex if you happen to have one cached, but you should avoid that, because of course if you _actually_ care about contiguous memory usage then you're also recompacting the component vector every time a component is removed and so the ComponentIndex is unstable across any operation that might add or remove components. Since your EntityId to ComponentIndex cache thus can only live locally, you might as well just cache the pointer instead... unless of course you _really actually_ care about data locality and your components are stored in a Structure-of-Arrays form instead of an Array-of-Structures form.

Computers is hard.

Of course, most games don't need any of that over-complicated cruft and shouldn't use an ECS anyway and just stick to a simpler component model...

Sean Middleditch – Game Systems Engineer – Join my team!


Of course, most games don't need any of that over-complicated cruft and shouldn't use an ECS anyway and just stick to a simpler component model...

indeed.

it appears there are only three circumstances where an ECS is truly called for:

1. when non-coders must be able to define entity types with differing components - like in unity. or when entity types with differing components must be data driven for some reason. or entity definitions change frequently, and build times are long.

2. as a workaround to inheritance issues. compostion may be a possible better alternative in this case.

3. as a method of code optimization, via data oriented design - requires cache friendly SoA data oriented design. i've only seen this mentioned once in a gammasutra postmortem, when it comes to real world use. PS2 or PS3 title as i recall. they had to go cache friendly to meet their minimum publisher mandated FPS. they were pretty desperate. they had already optimized all of render, and were down to optimizing update. lucky for them, cache friendly design just put them over the top.

unless one or more of the above three case applies, ECS appears to be overkill, introducing unnecessary complexity. IE over-engineering.

Norm Barrows

Rockland Software Productions

"Building PC games since 1989"

rocklandsoftware.net

PLAY CAVEMAN NOW!

http://rocklandsoftware.net/beta.php

Thanks everyone for the great replies, this has definitely been very educational!


Of course, most games don't need any of that over-complicated cruft and shouldn't use an ECS anyway and just stick to a simpler component model...


unless one or more of the above three case applies, ECS appears to be overkill, introducing unnecessary complexity. IE over-engineering.

Could you elaborate on the alternatives? Although I'm writing this system partly to educate myself on how an ECS works (on a lower level than Unity), I am serious about developing my game and have thought before that this may be a bit overkill (I'm developing a 2D rpg with tile maps). I've tried developing games before using 'traditional' OOP, by which I basically mean just a bunch of inheritance trees, and I've seen it turn out really nasty (multiple inheritance, duplicate code, different classes with only slight differences). I never actually tried to use 'normal' composition (e.g. Player has 'IMovementBehaviour', 'IRenderBehaviour', IXBehaviour properties) but I came across this thread: http://gamedev.stackexchange.com/questions/34625/composition-heavy-oop-vs-pure-entity-component-systems

Here someone explains how even if you do take the latter mentioned approach, you still end up with empty classes that just contain a bunch of components (back to ECS).

(The only difference I see in the example in the link is that components also contain logic, which isn't part of the 'traditional' ECS approach, and I can imagine that interdependency between components can become a problem)

EDIT: grammar

In a ""normal"" OOP architecture (which BTW, ECS isn't "not OOP"), you don't need a huge inheritance tree.

It can be as simple as a single level of inheritance:

17a4120f6a.png

The key is that 'Entity' provides as much good defaults as possible, so the subclasses override as few functions as possible.

Further, you don't need different classes for different enemy types - as much as possible, you customize enemies by the parameters you passed into the constructor (appearance, stats, etc...). So you really only need separate classes for distinct behavior, and even the behavior can be adjusted by passed in parameters. Further, once you realize that entire functions (callbacks, functors, or lambdas) or classes (or scripts) can be passed in as parameters, even behavior can be customized through parameters. Or you could just make "Entity" scriptable, and just make scripts for each of the "derived" classes.

The thing is that once you assume that behaviour can be defined by parameters/properties, you basically go back to composition.

For example if you have AI behaviour in say the 'Basic Enemy' class (which most likely other enemy classes will inherit) and then later you later decide you want the player to also be controlled by an AI (e.g. 'demo mode' when the main menu has been inactive for a while). You could make a base class AIEntity and make both the basic enemy and the player inherit that (obviously very bad because you'd just end up making a blob(?) class as an easy fix) or you could (more likely) say "hey, in that case let's just give the player a property IMovementBehaviour which can be set to say PlayerBehaviour or AIBehaviour at runtime". All good, except when you repeat these steps a dozen times and come to the conclusion that a lot of behaviour should be changable in runtime. That leads to classes without any implementation and with just a bunch of behaviour interface properties which is essentially the same as a CES.

This is of course based on a lot of "what if"'s but I never developed a game before on a relatively large scale, and I'm sure a lot of things will pop up during the development, it doesn't really seem possible to know exactly how everything should behave from the beginning. Am I just overthinking it?

This topic is closed to new replies.

Advertisement