2
\$\begingroup\$

For reference I am mostly imitating the architecture in this tutorial, the "Entity System" section: http://www.raywenderlich.com/24878/introduction-to-component-based-architecture-in-games

NOTE: the tutorial is in objective-c but I am using C++. Also, I only code the "happy case" for now because I am not sure if this is the architecture I am going with; I am simply experimenting.

Quick background: every time a system's update() function is called, the relevant system calls a helper method in EntityManager, getEntitiesWithProperty(), that returns a vector of all entities which possess a component(s) type that the system operates on.

template <class T>
std::vector<unsigned int> getEntitiesWithProperty()
{
    std::vector<unsigned int> ret;
    auto key = std::type_index(typeid(T));
    std::unordered_map<unsigned int, PProperty*> map = data[key];
    for(auto i = map.begin(); i != map.end(); i++)
    {
        ret.push_back(i->first);
    }
    return ret;
}

The system then iterates through this vector, calling yet another helper method in EntityManager per each element in the entity vector. This method returns a pointer to the component that is associated with the particular entity that is being iterated over, and the system performs its logic on the entity:

void SRender::update(EntityManager& entM, float frameTime)
{
    auto ids = entM.getEntitiesWithProperty<Image>();
    for(auto i = ids.begin(); i != ids.end(); i++)
    {
            Image* img = entM.getProperty<Image>(*i);
            draw(img);
    }
}

After I implemented this ECS I "stress-tested" it by simply drawing 300 sprites at differing x and y values, and was only able to get around 80 average fps doing absolutely nothing besides rendering.

I then altered the SRender system class and update method so that, instead of calling getEnt..() every frame, it only calls it upon system initialization and every time the EntityManager's component data structure changes size (i.e. entity deleted or created), and stores a vector of relevant components locally within the system as a member variable, like so:

   class SRender : public SSystem
   {
       private:
           ...
           std::vector<Image*> components
   }

Without update() calling the helper function every frame, rendering the same 300 sprites every frame I get an increase of 150 average fps. 1k sprites yield 100 fps; 20 fps faster the prior implementation.

My problem with this new approach is that there is no longer a separation of data from function, and that there are more pointers to deal with spread over systems instead of all being contained within the EntityManager, and it overall is just messier to deal with and more bug prone. But I don't know how else to increase performance. NOTE THAT there is nothing flawed from a performance standpoint regarding my rendering pipeline; I did the same initial test WITHOUT actually rendering anything, but simply iterating over each element returned by the helper method, and the fps was exactly the same (around 80 fps average) as it was making draw calls.

What other approaches can I take to improve performance? I have seen other ECS approaches doing the same thing as in the tutorial (returning relevant data structures from the entity manager on every update() call), so I don't understand why my implementation of it has poor performance. Maybe I am overlooking something, or misunderstanding the tutorial (I'm unfamiliar with objective-c)?

\$\endgroup\$
2
  • \$\begingroup\$ Change your nickname to a proper one, show your account is permanent. Otherwise it discourages people from answering. \$\endgroup\$
    – Den
    Aug 31, 2013 at 11:30
  • \$\begingroup\$ I used templates to make the entity manager independent from the data types. All properties are stored in a std::unordered_map<std::type_index, std::unordered_map<unsigned int, void*>> and using the getter function could look like Entities->Get<Image>(). If you like this approach, I can provide more information. \$\endgroup\$
    – danijar
    Oct 2, 2013 at 6:03

2 Answers 2

4
\$\begingroup\$

You have various implementation issues and some misunderstandings either about C++ or what ECS is supposed to do.

template <class T>
std::vector<unsigned int> getEntitiesWithProperty()
{
    std::vector<unsigned int> ret;
    auto key = std::type_index(typeid(T));
    std::unordered_map<unsigned int, PProperty*> map = data[key];
    for(auto i = map.begin(); i != map.end(); i++)
    {
        ret.push_back(i->first);
    }
    return ret;
}

This is probemsome. You're allocating a whole new vector every time you call this method as you're not reusing or caching the result vector. You're allocating a whole new unordered_map in each call, too, since you're not using a reference.

void SRender::update(EntityManager& entM, float frameTime)
{
    auto ids = entM.getEntitiesWithProperty<Image>();
    for(auto i = ids.begin(); i != ids.end(); i++)
    {
            Image* img = entM.getProperty<Image>(*i);
            draw(img);
    }
}

And here you're making yet another copy, in the line starting with auto since getEntitiesWithProperty does not return a reference. With C++11 you wouldn't be making a copy, but you're still allocating/filling a new vector every time.

Memory allocations alone can have a significant impact on performance, not to mention all the copying; these might be your problem. In general avoid copies anywhere you possibly can without compromising your ability to maintain the code.

After I implemented this ECS I "stress-tested" it by simply drawing 300 sprites at differing x and y values, and was only able to get around 80 average fps doing absolutely nothing besides rendering.

If you're doing rendering, this is a bad benchmark. You have no way to tell if your ECS is inefficient or your rendering is inefficient (80 FPS is not unheard of for a naive sprite renderer on some hardware still in common use). Do a nullary update loop (do no work in your update method) to check only your object system's efficiency.

class SRender : public SSystem { private: ... std::vector components }

This eliminates half the point of what an ECS was originally about. You have a nice contiguous vector which is just holding pointers to Images that could be strewn all over memory. You get very little performance benefit to this and might as well be using another significantly simpler component-based design (which I'd recommend anyway, but that's a different subject). This should be something like std::vector<Image> along with a free-list. Or, if you really want to use pointers/iterators, std::deque<Image>. That way all the Image instances are in contiguous memory and can be quickly iterated over and updated in a cache-friendly way.

Yes, Artemis is effectively doing the same thing you're doing in terms of memory access inefficiency (it's in Java and has no choice, since Java has no way to contiguously allocate objects last I checked), but that's a (significant) problem with Artemis, not something to emulate.

\$\endgroup\$
0
\$\begingroup\$

I just put my component cache inside of EntityManager. Systems don't need to know where the components come from. Implementation relies on a dictionary with the key being type of component - not sure if that is possible in C++.

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .