Sign in to follow this  

Some problems with ECS design

This topic is 476 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Hi! Now I am developing my first Entity Component System.

My ECS must be:

1)fast (no vtables and random memory access) 

2)cache-friendly

 

Now I have this design:

1. Entity - pointer to the World + unique id 

2. Component - pure struct without logic

3. System - storage and processing logic of components with type T. All components stored in a continuous memory area (aka array or vector)

4. 1 Component = 1 System 

5. Intersystem interaction occurs via messaging

6. If I need share some components I need a new component + system. Example:

struct Position
{
  Position(float x = 0, float y = 0): x(x), y(y) {}
  float x, y;
};


struct Rotation
{
  Rotation(float angle = 0): angle(angle) {}
  float angle;
};


struct Size
{
  Size(float x = 1.0f, float y = 1.0f): x(x), y(y) {}
  float x, y;
};


struct Transform
{
  Transform(Position* position,
            Rotation* rotation,
            Size* size):
            position(position),
            rotation(rotation),
            size(size) {}
  Position* position;
  Rotation* rotation;
  Size* size;
};

Problems: 

1. How can I ensure the safety of pointers? std::vector can break my pointers while resizing.

(P.S. I don't want have array with a static size)

2. Is it okay to use std::unordered_map very often? (insert/remove/find. My key is unique uint32_t number)

Edited by Saitei

Share this post


Link to post
Share on other sites

1. One way to do it is to not store pointers to your components, but IDs and having a second vector that maps IDs to pointers (or indices into your "actual" vector). This way you can shuffle objects around in the component storage vector without caring about the outside world, as long as you update the mapping vector.

 

This kind of design does not only protect you from pointers becoming invalid on resize, but also allows you to reorder objects in your vector to avoid empty spots, which is good for locality and therefore one step further towards a cache friendly architecture.

 

By using smaller indices than 32bit (do you really need more than 64k components of one kind?) you may be able to store your mapping in only 128k bytes and store 32 of them in a single cache line.

 

2. Using an unordered_map is often faster than a "normal" std::map, but it still uses dynamic allocations, since it is probably implemented using a list for each bucket. It touches less memory than a map, but it's still far from free. If you really want fast accesses and can find a way to do it, then limit the range of your keys and use a vector or array.

Share this post


Link to post
Share on other sites
One way to do it is to not store pointers to your components, but IDs and having a second vector that maps IDs to pointers (or indices into your "actual" vector). This way you can shuffle objects around in the component storage vector without caring about the outside world, as long as you update the mapping vector.

I think this may negatively affect on beauty of components:

struct Transform
{
Transform(Handle<Position> position,
            Handle<Rotation> rotation,
            Handle<Size> size):
            position(position),
            rotation(rotation),
            size(size) {}
  Handle<Position> position;
  Handle<Rotation> rotation;
  Handle<Size> size;
};

And mapping stage seems a little overhead. In my design the system is not very often use Entity (it only used when i must remove component/destroy entity/attach another component). Most often, the system updates the components that are in its possession: iterates by it's vector<T>.

 

Are there more ways to fix bug?

 

 

 

Using an unordered_map is often faster than a "normal" std::map, but it still uses dynamic allocations, since it is probably implemented using a list for each bucket. It touches less memory than a map, but it's still far from free. If you really want fast accesses and can find a way to do it, then limit the range of your keys and use a vector or array.

 

But what if unordered_map used only if i need attach/dettach some component? It will not used very often... 

Cache-friendly HashMap exist?

Edited by Saitei

Share this post


Link to post
Share on other sites
Cashe friendly hash map : google::dense_hash_map

You can either use indices in to the vectors, or you can use direct pointers if you can gaurentee the component vectors stay below some capacity that you set at startup.

If your using vectors to store you comps, and are using the indice method, you have to make sure your comps are still valid when copied.

A dense hash map with comp type id to comp index per entity would perform just fine.

Share this post


Link to post
Share on other sites

It looks like these are all premature attempts at optimizations.  The things you are discussing are not problems in the real world.
 

1)fast (no vtables and random memory access)

 
If you need virtual dispatch then vtables are currently the fastest available way to implement that. You're going to need some way to call the function.  
 
On x86 processors since about 1995 virtual dispatch has approximately zero cost, the very first time they are accessed the CPU caches them, and assuming you touch them occasionally they'll stay on the CPU. Since you should be touching them around 60+ times per second, they CPU will happily keep them around for zero cost to you.
 
In other words, you say you don't want vtables as you think they are not fast; but vtables are the fastest known solution to the task.  Use them.

 

  
 
As for random memory access, unless you can somehow organize your world and scene graphs so data traversal of components is linear, you'll need to live with some of that.  Be smart about it so the jumping around lives in L2 cache.

 

Random memory access can be amazingly fast, or it can be tortuously slow. The only way to know is to run it on a computer, profile it with cache analysis tools, and determine how your real-world memory patterns are working.  
 

2)cache-friendly

 
 
While you have clearly minimized size in your data (good), you have decreased cache friendliness by making then non-contiguous.  You have a continuous array of structures. The cache is generally most happy with a structure of arrays; parallel instructions are best when operating on a batch of elements at once rather than operating on a single item alone.

 

There is far more to cache friendliness than size of the data. The only way to know for certain how your program interacts with the cache is to run it with cache analysis tools to determine how your real-world memory patterns are working.
 

1 Component = 1 System

I need a new component + system

 
 
You keep using the word "system" in a way I'm not familiar with.  A system is any group of connected things. Any time you take any series of actions with any object you have created a system.  The interfaces you create define how the system is used.
 
Did you create a process or class or structure and give it a name "system"?
 

Intersystem interaction occurs via messaging

 
 
This can work reliably, but the thing most developers think of with messaging tends to add performance overhead, not remove it.
 
If you need to make a function call on an object then do so, that is the fastest way.  Going through a communications messaging service adds a lot of work. 

 

There are many excellent reasons to use messaging services: allow extension by adding message listeners, resolving threading issues and non-reentrant code, and processing load balancing are a few of them.  Faster execution time is not one of those reasons. 

 

1. How can I ensure the safety of pointers? std::vector can break my pointers while resizing. (P.S. I don't want have array with a static size)

 

If you use any type of dynamic array and you add or remove items, you cannot avoid it.  If you want the addresses to remain constant you cannot use a dynamic array.  I suggest learning more about fundamental data structures.
 
Some other options are to use a different structure (perhaps a linked list style) or to store a reference to an object (such as a container of pointers).  This will break your continuous access data pattern, but give you stable addresses. You need to decide which is more important.
 
Alternatively you can design your system to not store addresses of items, to work on clusters of items at once, and to not hold references to data they don't own.

 

 

 

 

 

If you really are concerned about performance you need to start up a profiler and look at the actual performance. You need to measure and compare with what you expect, you need to find the actual performance concerns. Performance is not something you can observe by just looking at the code alone. You need to actually see how it is moving through the computer.  Generally the best performing code looks complex and is larger than you first expect; the solutions that are simple and small tend to perform poorly as data grows because they don't fit the CPU's best features.

 

The things you are mentioning are tiny performance gains by themselves, and if you do have any performance concerns on your project they're not coming from these choices mentioned in the thread.

Share this post


Link to post
Share on other sites
You keep using the word "system" in a way I'm not familiar with.  A system is any group of connected things. Any time you take any series of actions with any object you have created a system.  The interfaces you create define how the system is used.   Did you create a process or class or structure and give it a name "system"?  

 

In classic ECS System - is a logic, Component - is a data. 

struct Drawable
{
  Drawable(Transform* transform, Texture texture):
          transform(transform), texture(texture) {}
  Texture texture;
  Transform* transform;
};


struct Renderer: public System<Drawable>
{
  void update(float dt)
  {
    for_each
    (
      [](Drawable& d)
      {
        sprite.set_texture(d.texture);
        sprite.set_transform(*d.transform);
        sprite.draw();
      }
    );
  }
  /*...*/
};

In my implementation all systems has ONE type of components. So, in System<Drawable> its components can be stored like vector<Drawable>. 

All systems work only with those components for which they are responsible. It is very good, because: 

1. Logic rigidly separated from the data

2. All components stored in a continuous memory area 

3. All components are updated consistently (with minimum cache-misses) 

4. Usually processing logic can be parallelised (by thread pool or something else)

5. Almost always, the components are std::is_standard_layout, so it can be easily serialized in binary form (if inside structs no pointers)

 

 

 

Alternatively you can design your system

 

I am thinking about custom memory pool... Maybe, it can help me alot 

Edited by Saitei

Share this post


Link to post
Share on other sites

In other words, you say you don't want vtables as you think they are not fast; but vtables are the fastest known solution to the task.  Use them.  
 

vtables used only inside my System<T> (virtual void update(float) = 0;) 

Since components are pure data there is no needs to look at vtable when I want to update component state. It is little overhead, because it can be done without vtables

Share this post


Link to post
Share on other sites
My implementation of "ECS" is not really any one thing i can just describe - its pretty much an amalgamation of code ive written making past games.
It happens to use indices in to component arrays - but as frob mentioned these are arrays of structs not structs of arrays - i dont know whether ot makes a difference or not i have never had any performance issues there.

Originally i had just a bunch of comps newed in to each ent - i went through a huge ordeal switching to the comp in arrays thing - honestly kind of a waste of time. I never had enough objs in any of my games to make a noticeable difference. I was swayed by the public opinion in to doing something that i didnt profile, see if it was really necessary, and as mentioned above ended up being pointless.

The only issue I had performance wise came when I was making a hex tile game - I needed to draw about 100000 tiles at once - broken between a few different tile types. I ended up adding an array to my transform comp that had matrix for each tile of that type - and used a mapped opengl buffer and a single instanced draw call per tile. This worked nicely but had has its limitations - each tile instance must have the same mesh and material and shader properties - but this was exactly what i needed anyways.

How does that fit in to ECS? Should i have made a instance component or something? I dont know but it worked for that game and it works for some other similar things i have done.

Share this post


Link to post
Share on other sites

While I'm a huge fan of ECS systems (through reading, not through use), I'm with frob in thinking that you might be jumping the gun.

 

You're using ECS because you think it might be "better" in areas you don't need, because it sounds cool and because everyone is talking about it and it's new and trendy (to most people anyway - I've been reading about it since 2006, and it's been around (in various forms) since at least 1996).

 

My advice would be to forget about writing fast code, optimize for code that is easy to read; optimize for code that is easy to develop with; optimize for actually finishing a project.

 

Don't use ECS because you think it'll make your code faster. Use ECS only if you think it'll make your code cleaner, your development quicker, your life easier.

And by "development easier" I don't mean some complex file-driven assembling of entities in an infinite combination of pointless possibilities that you'll rarely actually need.

 

And if you are making an 'engine' - don't. Make a game instead (from scratch or otherwise). Unless you want an engine instead of a game, which is also a fun project, but one that doesn't lead to actual games getting made. What I mean is, if you are making an engine with the illusion that you'll use it to make a game, then you are shooting yourself in the foot. But if you make a game by necessity you'll also make a engine - albeit an adhoc one; and you can later refine and reuse that adhoc engine.

 

If you insist on ECS, then I have to ask these questions:

 

1. Entity - pointer to the World + unique id 

Why does it have a pointer to the world? Why does the Entity even exist?

 

Entities don't own the world (as much as they might wish :wink:), the World owns the entities.

Further, Entities shouldn't own their ID, IDs are used to locate the Entity (or rather, the components belonging to that entity).

 

2. Component - pure struct without logic

Good.

 

3. System - storage and processing logic of components with type T. All components stored in a continuous memory area (aka array or vector)

Why are all components stored in an array or vector?

I hope you have double-indirection of your IDs, or you'll be wasting alot of performance and memory trying to filter which entities you need to process.

 

4. 1 Component = 1 System 

Why? What if a System logically makes sense managing two or three related component types?

Why does the System own the components?

 

Some Systems should own some components, hidden behind-the-scenes, other sets of components should be more public and shared by many systems.

 

5. Intersystem interaction occurs via messaging

Function calls are "messaging". What kind of messaging are you talking about?

If SystemX needs to talk to SystemY, why doesn't SystemX just have a reference to SystemY - why over-complicate it?

 

6. If I need share some components I need a new component + system.

Why? If you need to share some components, why not just hand the components to all the systems that need it?

Share this post


Link to post
Share on other sites

instanced draw call per tile. This worked nicely but had has its limitations - each tile instance must have the same mesh and material and shader properties - but this was exactly what i needed anyways.
 

Oh, I see. But what about bindless textures and glMultidrawElementsIndirect? With this features you can draw your world with single draw call on CPU side (if all meshes stored in the same VBO)

 

Yes, to do exactly the game easier ... But I set a goal to create a ECS library, not a game

Share this post


Link to post
Share on other sites

In my implementation all systems has ONE type of components. So, in System<Drawable> its components can be stored like vector<Drawable>.

Every system is different, why are you forcing them into one interface, and one way to store their data?
 

All systems work only with those components for which they are responsible.

Why can't systems work with the components they need access to, even if they aren't responsible for it?
 

1. Logic rigidly separated from the data

Logic needs to operate on data. I get logically separating them for reasoning about them, but why build unnecessary walls between them?
 

2. All components stored in a continuous memory area

But, depending on the System's internal unique desires, that isn't always what's best.
 

3. All components are updated consistently (with minimum cache-misses)

You're assuming minimum cache-misses.

I'm not sure what you mean by 'updated consistently'.
Different systems or even components don't need to run at the same frequency as other systems and components.
Sometimes they should be skipped, other times they should be updated at a slower pace (i.e. not always once per frame).

 

5. Almost always, the components are std::is_standard_layout, so it can be easily serialized in binary form (if inside structs no pointers)

I do not think std::is_standard_layout means what you think that means.
 

vtables used only inside my System<T> (virtual void update(float) = 0;)

Why do systems inherit from a common base class? Why are they forced into a common Update() function call?

 

I'm shooting at you with ten thousand questions - sorry, I'm not trying to badger you, just trying to make you think about your design.

Like I said, I like ECS.

 

Are you coding it this way because someone else somewhere on some blog told you to do X because all their friends have done it, and they have such a great track record of having failed to release any game ever; or are you basing your decisions on things you actually thought through, having considered "why is X better than Y, and what are the other alternatives?"

Share this post


Link to post
Share on other sites

Oh, I see. But what about bindless textures and glMultidrawElementsIndirect? With this features you can draw your world with single draw call on CPU side (if all meshes stored in the same VBO)

 

If I needed that functionality I could do it - but drawing each tile type in its own draw call worked fine - also I would like to remain at OpenGL 4.1 to keep full support for Mac. I'd have to check if that exact function is supported as part of the limited 4.2 or not.

 

Bottom line, I didn't/don't need it. The regular elements instanced draw works fine and is plenty fast.

Share this post


Link to post
Share on other sites

[b]@[member='Servant of the Lord'][/b], yes, I making an engine. Engine for myself, it is my little toy :) 

 

The idea of Data Oriented Developing seems for me very interesting and beutiful. I have read many articles on this topic (but a lot less than you =)) and tried to use a ready-made library EntityX. Also, I read its source code and I can understand how its work. But some things I don't like, so I want to try create library by myself.

 

1. Entity - pointer to the World + unique id 
 

World is a container of its entities and components.

If the world is only one, then Entity == single unique id. 

But... What if there is multiple worlds?

With pointer to the world I can say to what world entity belongs. 

Additionaly, I can make OOP-style methods like "destroy", "attach", "detach", "get", "has", etc. Without pointer to the world it is not possible

 

Why are all components stored in an array or vector? I hope you have double-indirection of your IDs, or you'll be wasting alot of performance and memory trying to filter which entities you need to process.
 

 

I'm still not sure inside what structure they should be stored. Main idea: data-locality. The data should be located nearby, because in this case processor can read many useful information inside cache

 

Why? What if a System logically makes sense managing two or three related component types? Why does the System own the components?
 

Because if I know what data the system operates, I can easily understand how to parallelize computations.

Also, I really don't want create something like "ComponentManager".

When system must interact with other system there is two ways:

1. By changing shared components data

2. By sending messages

 

Function calls are "messaging". What kind of messaging are you talking about?
 

Something like this:

emit(PositionChanged(position));

...(some system)
void handle(PositionChanded msg)
{
/*...*/
}
...

If SystemX needs to talk to SystemY, why doesn't SystemX just have a reference to SystemY - why over-complicate it?

But what if the number of such systems is large? Each time you add new systems you must to change the source code. Yes, and it itself source code will grow strongly. 

But if you send the messages than this will not happened.

 

Why? If you need to share some components, why not just hand the components to all the systems that need it?

Because I can not think how to implement it without losing performance

 

 

P.S. You understand me? English is not my native language...

Share this post


Link to post
Share on other sites

Every system is different, why are you forcing them into one interface, and one way to store their data?
 

Because I'm developing generic ECS library. 

Storage method we can specify by the template parameter.

But first, I want to write a simple System<T> 

 

Why can't systems work with the components they need access to, even if they aren't responsible for it?

Single responsibility principle. If other systems must be notificated - just send a message 

 

Logic needs to operate on data. I get logically separating them for reasoning about them, but why build unnecessary walls between them?  

Keep a little bit of data in the system is possible. But if there are a lot of data - there is problem in Data (Components) design

 

Why do systems inherit from a common base class?

Because I developing ECS library. It is interface for custom systems. Implementation of it interface can add some more methods

I'm shooting at you with ten thousand questions - sorry, I'm not trying to badger you, just trying to make you think about your design. Like I said, I like ECS.
 

It's okay. Just do not try to dissuade me, I really want to develop a ECS library: P

Share this post


Link to post
Share on other sites

P.S. You understand me? English is not my native language...

Your English is clear enough.
 

 

1. Entity - pointer to the World + unique id

 
World is a container of its entities and components.
If the world is only one, then Entity == single unique id. 
But... What if there is multiple worlds?
With pointer to the world I can say to what world entity belongs.

 


If World contains the entities, then creating a separate world has that separate world contain separate entities.

If multiple Worlds loaded at the same time are actually required, then each world should have separate collections of components.
 

 

Why are all components stored in an array or vector? I hope you have double-indirection of your IDs, or you'll be wasting alot of performance and memory trying to filter which entities you need to process.

 
I'm still not sure inside what structure they should be stored. Main idea: data-locality. The data should be located nearby, because in this case processor can read many useful information inside cache

 


I understand the concept of data locality and the cache (both instruction and data), but if every component array is [entity count] in length, but not every entity uses every component, you're going to be wasting alot of performance doing if(entity-actually-uses-component) for each component in the array. Hence, double-indirection for direct entity access, and packed arrays (ordered for optimization) for bulk system processing.
 

 

Why? What if a System logically makes sense managing two or three related component types? Why does the System own the components?

 
Because if I know what data the system operates, I can easily understand how to parallelize computations.
Also, I really don't want create something like "ComponentManager".

 


I'm not suggesting 'ComponentManager' (and agree that a ComponentManager would be bad).

What I'm saying is, some systems might want to handle more than one collection of data.

For example, physics. A PhysicsSystem might want to process all the CollisionComponents, but also might want to produce CollisionEvents in a separate array.

A PhysicsSystem might want to also seperate internally, invisibly, for speed reasons, all the dynamic collision objects from the static collision objects - which might be separate components (DynamicCollisionComponent vs StaticCollisionComponent).
 

 

Function calls are "messaging". What kind of messaging are you talking about?

 
Something like this:
emit(PositionChanged(position));

...(some system)
void handle(PositionChanded msg)
{
/*...*/
}
...

At the system level?
 
Why not:

this->anotherSystem->positionChanced(postion, entityID);

 

If SystemX needs to talk to SystemY, why doesn't SystemX just have a reference to SystemY - why over-complicate it?

But what if the number of such systems is large?

 


SystemX should *only* have references to the one or two (AT MOST - most of the time, zero) other systems it needs access to. Adding new systems doesn't require you to change anything.
All it requires is for you to make your dependencies explicit.

And if you have more than 30-50 systems, I'd heavily question the reason.

It seems like you are breaking every entity member variable into a separate system - that's overkill, in my opinion.

A system is performing a specific task - every variable that is needed for that task should be available to that system directly.
Each variable shouldn't be in a separate component, except where there is a need for it to be.
 

Each time you add new systems you must to change the source code. Yes, and it itself source code will grow strongly. 
But if you send the messages than this will not happened.


Sending messages is hiding dependencies (the depdencies are no longer visible on the interface).
If that's the case, I'd rather pass in a reference to a struct that owns all the Systems, and make that lack-of-protection explicit.
But my preferred route is still handing in references to the one or two systems that are depdendencies. 
 

 

Why? If you need to share some components, why not just hand the components to all the systems that need it?

Because I can not think how to implement it without losing performance

 

 
There is no performance loss in this:
 

PositionComponents positionComponents;

GraphicsSystem graphicsSystem(&positionComponents);
CollisionSystem collisonSystem(&positionComponents);

There is also no performance loss in this:
 

CollisionSystem collisonSystem;
GraphicsSystem graphicsSystem(&collisonSystem);

Share this post


Link to post
Share on other sites

 

Why can't systems work with the components they need access to, even if they aren't responsible for it?

Single responsibility principle. If other systems must be notificated - just send a message

 


That's not what the Single responsibility principle means.
SRP doesn't mean "Hide data behind walls so the systems that need access to it have to jump through hoops to pretend they don't need access to it" 
 
SRP really means: "Every class (system, in this case) should be responsible for doing (primarily) one major thing". It's about task responsibility, not data access.
 
Data ownership should be clear, and data access should be clear, but by all means classes should have as direct access to the data they need as possible (which the appropriate const protections where write-access isn't needed), unless safety requires otherwise.
 

 

Why do systems inherit from a common base class?

Because I developing ECS library. It is interface for custom systems. Implementation of it interface can add some more methods

 


But by trying to make it generic, you actually criple it's usability. Meaning, anyone who wants to actually use your "generic" system needs to fight against your generic design to actually get things done.
There is balance between too abstract and too concrete, and you're learning to too abstract in this case.
 
Did you read the discussion in the [Why are they forced into a common Update() function call?] link?

Share this post


Link to post
Share on other sites

If World contains the entities, then creating a separate world has that separate world contain separate entities. If multiple Worlds loaded at the same time are actually required, then each world should have separate collections of components.
 

 

Regardless, when you are adding/removing entities or components on an entity, you need access to a World object somehow (or some context indicating the thing that owns the entities). The calling code could have it, or it could be in the Entity class as a convenience.

 

Because if I know what data the system operates, I can easily understand how to parallelize computations. Also, I really don't want create something like "ComponentManager". When system must interact with other system there is two ways: 1. By changing shared components data 2. By sending messages
 

 

Be careful with number 2. The order in which various systems are processed genrally ends up being very important. If you have some system emitting a "position changed" event, any system responding to that event will then be doing stuff on the source's "time frame". Something to be aware of, and it can risk resulting in spaghetti code if you're not careful.

 

And while messages/events can be useful if a system needs to broadcast information to other sinks it doesn't know about, if you have a system that need information from a specific system it's best to have that as an explicit dependency (e.g. a hard reference to the other system), as Servant suggested.

Share this post


Link to post
Share on other sites

Did you read the discussion in the [Why are they forced into a common Update() function call?] link?
 

or are you basing your decisions on things you actually thought through, having considered "why is X better than Y, and what are the other alternatives?"

That's right. 

I am now 20 years old and I started programming in 2009.

Previously, the OOP approach seemed to me the cure for all ills. 

I just did not know what approaches exist and what disadvantages there are in the OOP.

I also did not know how CPU works and how to use the multithreading.

 

But now... More or less I understand. 

 

==First of all, multithreading. 

Multithreading with 100500 mutex lock's... WHERE IS POWER OF MY THREADS?! Yes. In this case, the program will work very poorly. Even worse than the single-threaded program. 

Also, here is cache issues... It all depends on the architecture, but typically processor cores provide its cache. 

One core can easily invalidate cache of another core, so this core must reload cache from RAM (Yuck! Core cycles are idle).

 

So, there is cure: The smaller the number of locks - the greater the efficiency of multi-threading. Also it is necessary to intelligently share data for multi-threaded processing, so that the CPU cores did not fight each other.

 

If I know what components are processed by the system, and also know that there is practically no modification of components from other systems, it becomes possible to make multithreaded computations without blocking.

 

==Cache-friendly code && OOP 

For example, I have object "Tank" which is derived from Transform, Scriptable, Mesh, etc. 

Suppose that it is necessary to update the position of all objects.

And... Here is a issue. With transform component we also load to a cache a lot of "trash". If i need update only transforms, why I need load to the cache Mesh/Scriptable? 

 

In ECS case: all transforms in one continuous memory area. So, cache store ONLY useful information and use it VERY WELL, because update goes sequentially. Cache is very hot :) 

 

Also here is a very well reusability of components (if it designed very well). Yeah, in OOP with this all ok too. But... Here is some logical problems. 

 

==Only logic

Basically, the system contains processing logic only. It enhances the beauty of the code and allows you to concentrate only on the logic.

 

==Easy to rewrite

Because logic placed in one place. The code is not scattered on different classes.

 

==Easy to debug

(It follows from the preceding paragraph)

 

==vtable lookups 

No vtable lookups inside method update. Vtables is not so terrible... But we can don't use them

 
==OOP is evil?
No. I love basic idea. It's really easy to understand. But sometimes it is hard to develop and debug, also performance issues is possible. 
OOP - is not "MUST HAVE" concept. I think, it is better to mix OOP with many other concepts to make it stronger =)
 
P.S. I will respond to other posts a bit later. Some things difficult to translate into my native language

Share this post


Link to post
Share on other sites
Your English is clear enough.

 

Thank you, good to hear :)

 

If World contains the entities, then creating a separate world has that separate world contain separate entities. If multiple Worlds loaded at the same time are actually required, then each world should have separate collections of components.

Entity is not pure id. It has some methods. phil_t understood me correctly: 

Regardless, when you are adding/removing entities or components on an entity, you need access to a World object somehow (or some context indicating the thing that owns the entities). The calling code could have it, or it could be in the Entity class as a convenience.

 

If World contains the entities, then creating a separate world has that separate world contain separate entities. If multiple Worlds loaded at the same time are actually required, then each world should have separate collections of components.  

I understand that perfectly

 

but if every component array is [entity count] in length, but not every entity uses every component, you're going to be wasting alot of performance doing if(entity-actually-uses-component) for each component in the array. Hence, double-indirection for direct entity access, and packed arrays (ordered for optimization) for bulk system processing.

 

That's right. System<T> is not only logic. It's storage, too. 

Sp, inside System<T> I can make something like unordered_map<Entity, uint32_t> components, where uint32_t - index of component in some container (for example, array)

This dictionary is used in such cases:

1. Component creation

2. Component destroying

3. When I need get address of component for specific entity

 

My doubts:

1. unordered_map or some else HashMap is fast enough? 

2. Search by value, not key, a bad thing? I must have some ability to get component's owner. Create another map?: unordered_map<uint32_t, Entity>?

 

Typically, the system will operate at the component level. So, using hash map is not bottleneck..? 

 

I'm not suggesting 'ComponentManager' (and agree that a ComponentManager would be bad). What I'm saying is, some systems might want to handle more than one collection of data. For example, physics. A PhysicsSystem might want to process all the CollisionComponents, but also might want to produce CollisionEvents in a separate array. A PhysicsSystem might want to also seperate internally, invisibly, for speed reasons, all the dynamic collision objects from the static collision objects - which might be separate components (DynamicCollisionComponent vs StaticCollisionComponent).

Uhm, I see. But how fastly get this entities with this kind of components?

Why I can't just create another Component with pointers to the desired components inside?  That's it: 1 System = 1 Component. Another thing - if the component by a reference now is invalid (somehow deleted). But it is logical issue that must be "debuged" 

 

There is no performance loss in this:  

 

Very well. But... I am designing abstract ECS library that can be used everywhere. 

I really do not understand how can I get entities with specific components without performance issue. If I add some "glue-components" like this:

struct Position
{
  Position(float x = 0, float y = 0): x(x), y(y) {}
  float x, y;
};


struct Rotation
{
  Rotation(float angle = 0): angle(angle) {}
  float angle;
};


struct Size
{
  Size(float x = 1.0f, float y = 1.0f): x(x), y(y) {}
  float x, y;
};


struct Transform
{
  Transform(Position* position,
            Rotation* rotation,
            Size* size):
            position(position),
            rotation(rotation),
            size(size) {}
  Position* position;
  Rotation* rotation;
  Size* size;
};

It becomes very easy. 

 

There is also no performance loss in this:   CollisionSystem collisonSystem; GraphicsSystem graphicsSystem(&collisonSystem);

Thank you. This is the third way of communication between the systems! :)

1. By the data (components)

2. By the messages 

3. By the direct call (system can be passed to another system by constructor)

 

But by trying to make it generic, you actually criple it's usability. Meaning, anyone who wants to actually use your "generic" system needs to fight against your generic design to actually get things done.
There is also no performance loss in this:

It is okay. I can make function update non-pure (virtual void update(float) {}), so method update not necessary to implement.

 

Another thing - all systems must inherit from my class. But it is a necessary sacrifice, because I'm developing a library rather than a specific application.

Did you read the discussion in the [Why are they forced into a common Update() function call?] link?

 

Yes... However, I realized not too many ideas. Perhaps the problem is in the language barrier.

Method "some_system.update(dt)" called every time when I call world.update(dt). Or is the problem purely in the name of the method? 

 

If it so, how about word "process"? :)

 

messages/events

 

What is the difference between them?

 

Be careful with number 2. The order in which various systems are processed genrally ends up being very important.

Thank you for your comment. Yes, it is very important the order of message processing. Also bags is possible when I everytime send message A from function that handles messages with type A. while(true) =)

Edited by Saitei

Share this post


Link to post
Share on other sites

Personally I'm in favor of having a virtual update(ComponentStorage& components, float dt) method for every system type, where ComponentStorage holds the components in arrays organized by component type. The systems can be viewed as operators on the state of the components in the engine, where on each frame the internal/external state is updated according to the elapsed time. Relationships between systems can be implemented by giving a system pointers to the other systems it depends on during initialization (where the concrete types of the systems are known). There's no need to shoehorn everything into the update method, systems are free to have other methods that perform other actions (like being notified of added/removed components).

Share this post


Link to post
Share on other sites

Very well. But... I am designing abstract ECS library that can be used everywhere. 

 

 

I know it sounds great - but just to warn you - be prepared to waste a lot of time without ever having anything practical.

 

 

I really do not understand how can I get entities with specific components without performance issue. If I add some "glue-components" like this:

 

 

When you iterate though components of a given type in a system, you will likely be able to get the entity with

entity * ent = cur_comp->owner() or something. Then get the id to the comp you want:

uint32_t other_comp_type_id = 1234;
uint32_t comp_index = ent->get_component(other_comp_type_id);
compont_type * other_comp = &world->get_comp_array(other_comp_type_id)[comp_index];

Then in your get_component in the entity something like

uint32_t entity::get_component(uint32_t comp_type)
{
    auto fiter = m_comp_umap.find(comp_type);
    if (fiter != m_comp_umap.end())
        return fiter->second;
    return -1; // indicates no component
}

Yes a hash map would be plenty fast enough. Sorry about no code tags - my browser at work doesn't show them (even after pressing more reply options).

 

You really should try to make a game instead - even if your ultimate goal is an engine. How could you possibly write code that is to be used without writing, or at least having, the usage code? 

Edited by EarthBanana

Share this post


Link to post
Share on other sites
When you iterate though components of a given type in a system, you will likely be able to get the entity with entity * ent = cur_comp->owner() or something. Then get the id to the comp you want:   uint32_t other_comp_type_id = 1234; uint32_t comp_index = ent->get_component(other_comp_type_id); compont_type * other_comp = &world->get_comp_array(other_comp_type_id)[comp_index];   Then in your get_component in the entity something like   uint32_t entity::get_component(uint32_t comp_type) { auto fiter = m_comp_umap.find(comp_type); if (fiter != m_comp_umap.end())     return fiter->second; return -1; // indicates no component }  

I can't use it. Because there is many find calls.

Systems already can iterate by its components and answer the question "Is it used now? Can I update it" without using of find function.

Very frequent searching exactly bad for performance... 

 

System<T> can work something like this: 

vector<T> components; 
dynamic_bitset<T> alive_components; //actually there is no std::dynamic_bitset. Btw, std::vector<bool> storing bits, not bytes

void for_each(std::function<void(T&)> f)
{
   for(size_t i = 0; i < alive_components.size() && i < components.size(); ++i)
   {
      if(alive_components[i]) { f(components[i]); }
   }
}
Edited by Saitei

Share this post


Link to post
Share on other sites

This topic is 476 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this