I am currently trying to implement an (sort-of) Entity-Component-System.
I've got the gist of it, that is, how an ECS is supposed to work. So far i have 4 classes in my design (not yet fully implemented): EntityWorld is a global container for systems, entities and their respective components. It is responsible for updating systems/stepping. EntitySystem represents the base class for a system, with a virtual update-function. Entity is a container, basically with a list of components and an id, nothing more. EntityComponent represents a component. Now, i thought about making it possible to multithread my systems, but i think i've ran into a problem here. Suppose my EntityWorld stores its entities in the simplest way possible, in a std::vector<Entity*> for example. Now, that list would either be fully passed to a system when it is updated, or the EntityWorld loops through this list and sends the entities to the systems one-by-one. In my understanding though, when using multiple threads, this would require me to lock the whole list every time a system is being updated. That would practically equal zero increase in performance, since the other threads are always waiting for the list to get free. Is there a better way to implement this? So multiple threads can be updated (and r/w to entites at the same time)?
Thanks in advance!
Related
Let's say some threads produce data and every piece of data has associated 3D coordinate. And other threads consumes these data and every consumer thread has cubic volume of interest described by center and "radius" (size of the cube). Consumer threads can update their cube of interest parameter (like move it) from time to time. Every piece of data is broadcasted - a copy of it should be received by every thread which has cube of interest which includes this coordinate.
What multi-threaded data structure can be used for this with the best performance? I am using C++, but generic algorithm pointer is fine too.
Bonus: it would be nice if an algorithm will have possibility to generalize to multiple network nodes (some nodes produce data and some consumes with the same rules as threads).
Extra information: there are more consumers than producers, there are much more data broadcasts than cube of interest changes (cube size changes are very rare, but moving is quite common event). It's okay if consumer will start receiving data from the new cube of interest after some delay after changing it (but before that it should continue receive data from the previous cube).
Your terminology is problematic. A cube by definition does not have a radius; a sphere does. A broadcast by definition is received by everyone, it is not received only by those who are interested; a multicast is.
I have encountered this problem in the development of an MMORPG. The approach taken in the development of that MMORPG was a bit wacky, but in the decade that followed my thinking has evolved so I have a much better idea of how to go about it now.
The solution is a bit involved, but it does not require any advanced notions like space partitioning, and it is reusable for all kinds of information that the consumers will inevitably need besides just 3D coordinates. Furthermore, it is reusable for entirely different projects.
We begin by building a light-weight data modelling framework which allows us to describe, instantiate, and manipulate finite, self-contained sets of inter-related observable data known as "Entities" in memory and perform various operations on them in an application-agnostic way.
Description can be done in simple object-relational terms. ("Object-relational" means relational with inheritance.)
Instantiation means that given a schema, the framework creates a container (an "EntitySpace") to hold, during runtime, instances of entities described by the schema.
Manipulation means being able to read and write properties of those entities.
Self-contained means that although an entity may contain a property which is a reference to another entity, the other entity must reside within the same EntitySpace.
Observable means that when the value of a property changes, a notification is issued by the EntitySpace, telling us which property of which entity has changed. Anyone can register for notifications from an EntitySpace, and receives all of them.
Once you have such a framework, you can build lots of useful functionality around it in an entirely application-agnostic way. For example:
Serialization: you can serialize and de-serialize an EntitySpace to and from markup.
Filtering: you can create a special kind of EntitySpace which does not contain storage, and instead acts as a view into a subset of another EntitySpace, filtering entities based on the values of certain properties.
Mirroring: You can keep an EntitySpace in sync with another, by responding to each property-changed notification from one and applying the change to the other, and vice versa.
Remoting: You can interject a transport layer between the two mirrored parts, thus keeping them mirrored while they reside on different threads or on different physical machines.
Every node in the network must have a corresponding "agent" object running inside every node that it needs data from. If you have a centralized architecture, (and I will continue under this hypothesis,) this means that within the server you will have one agent object for each client connected to that server. The agent represents the client, so the fact that the client is remote becomes irrelevant. The agent is only responsible for filtering and sending data to the client that it represents, so multi-threading becomes irrelevant, too.
An agent registers for notifications from the server's EntitySpace and filters them based on whatever criteria you choose. One such criterion for an Entity which contains a 3D-coordinate property can be whether that 3D-coordinate is within the client's area of interest. The center-of-sphere-and-radius approach will work, the center-of-cube-and-size approach will probably work even better. (No need for calculating a square.)
I created several "manager" objects of a library, each with different parameters. Every cycle a manager is fed with a data set, run calculations and writes result into a data structure. I have to run all managers on the same data set as fast as possible, so I created a threadpool to distribute data to all managers so that they can be run concurrently. Each manager have access to one result data structure, so I thought this will be thread safe.
However later I found out that the several classes in this library, which are used by managers, have static member variables which (as I believe) causes segmentation faults - segmentation errors originates from the library, not my code (checked).
My question is, is it possible to go around this? This will probably sound stupid, but is it possible to force each manager to use its own copy of the library and thus circumventing the static issue? I am processing ~20-50k data sets per second so I cannot afford overhead. Using forks would be very painful and in my case could create unwanted overheads.
Thanks for any advice!
I've been reading about Entity-Component-Systems and i think i understand the basic concept:
Entities are just IDs with their Components stored in Arrays to reduce cache misses. Systems then iterate over one or more of these Arrays and process the data contained in the Components.
But i don't quite understand how these systems are supposed to efficently and cleanly interact with one and another.
1: If my entity has a health component, how would i go about damaging it?
Just doing health -= damage wouldn't account for dying if health goes below or equal 0. But adding a damage() function to the component would defy the point of components being only data. Basically: How do systems process components which need to respond to their changes and change other components based on their changes? (Without copy-and-pasting the damage code into each system which can possibly inflict damage)
2: Components are supposed to be data-only structs with no functions. How do i best approach entity-specific behaviour like exploding on death. It seems unpractical to fill the Health component with memory-wasting data like explodesOnDeath=false when only one or two out of many entities will actually explode on death. I am not sure how to solve this elegantly.
Is there a common approach to these problems?
Ease of modification (for ex with Lua scripts) and high chances of compatibility are important to me, as i really like games with high modding potential. :)
Used Language: C++
I am also new to the field, but here are my experiences with ECS models:
How do systems process components which need to respond to their changes and change other components based on their changes?
As you correctly pointed out, the components are just containers of data, so don't give them functions. All the logic is handled by the systems and each new piece of logic is handled by a different system. So its a good choice to seperate the logic of "dealing damage" from "killing an entity". The comminication
between the DamageSystem and the DeathSystem (with other words, when should an entity be killed) can the be based on the HealthComponent.
Possible implementation:
You typically have one system (The DamageSystem) that calculates the new health of an entity. For this purpose, it can use all sorts of information (components) about the entity (maybe your entities have some shield to protect them, etc.). If the health falls below 0, the DamageSystem does not care, as its only purpose is to contain the logic of dealing damage.
Besides the DamageSystem, you also want to have some sort of DeathSystem, that checks for each entity if the health is below 0. If this is the case, some action is taken. As every entity does sth on their death (which is the reason why your explodesOnDeath=false is not a bad idea), it is usefull to have a DeathComponent that stores some kind of enum for the death animation (e.g. exploding or just vanishing), a path to a sound file (e.g. a fancy exploding sound) and other stuff you need.
With this approach, all the damage calculation is located at one place and seperated from e.g. the logic of the death of an entity.
Hope this helps!
I develop a Hand-tracking application, with C++ and OpenGL. (and QT, Eigen, OpenCV)
OpenGL is used in order to render a 3D model (for every iteration of the tracking loop).
The application runs in just 1 thread.
I'm interested in doing some very time-consuming experiments, so I was wondering if it is possible to parallelize things, in the sense of starting many instances of the same executable, and running them with different parameters.
Just by trying to do this, it seems that it works, but I'm not sure if different instances interfere with each other on the GPU. To be more descriptive, I wonder about the following:
if I do some experiments by running only one instance at each time, and then repeat the same experiments by running many instances concurrently, are the results going to be the same numerically?
Of course I'll try to verify it through experiments, I was wondering though if anybody can pinpoint me to a suitable read (I didn't find something truly relevant).
Any ideas on this matter?
Answering the first comment (#KillianDS)
The details of the experiment are really mathematical and would cause 'noise' in the topic.
The idea is that you have a tracking-algorithm that tries to find correspondences between the previous and the current frame. By using these correspondences, the algorithm takes the 3D model from the pose of the previous frame (already known), and it transforms it in such a way, so that it fits the current frame. There are some (mathematical) parameters affecting this, and the experiments are about having a lot of testing frames, and running the algorithm on these with many different parameters, so that you can find the optimal parameters (value or range of values)
During the experiments, you use OpenGL to project the 3D model, so that it fits the current frame-image. What you see is rendered as usual, the actual job though is done with the use of an offscreen buffer in GPU.
Up to now I run the experiments using multiprocessing (many instances at the same time), but when I run just one instance at the time, I couldn't reproduce the exact same number, because of a bug that I just found. (same test is now ongoing - but very time-consuming)
However I was wondering if you can really trust GPU when you run many instances at the same time, or things in GPU-memory can be messed up
Answering the second comment (#Lajos Arpad)
To redefine in short the problem, I don't want to share things, but be sure that different instances (in case of multiprocessing, that you mention) don't affect each other (that is no sharing at all)
There are two possible parallelizations:
Multithreading
Multiprocessing
You can use one of them or both.
I don't really understand your task, as you were not too specific, but in general you can share values in the following way:
Use a file which will be used by your threads/processes.
Use a service, like a database where your threads/processes will manipulate data.
Use variables which are declaratively or by definition shared (like static members for C++ classes).
Using communication between your processes/threads by a listening port and sockets.
In this moment nothing else comes to my mind. Everything else is not shared.
For more information, read this and this.
This is just for a kind of concurrency refresher...
Imagine I have a B+ tree data structure in memory - multiple items per node, only leaf nodes contain items, leaf nodes also form a linked list for easy sequential access. Inserts and deletes mostly only affect a leaf node, but can cause nodes to split or merge in a process that may propagate to the root.
I have a single-thread implementation, and the updates follow a kind of pre-planning approach. A recursion steps up the tree from leaf level as far as nodes need to change, building a linked list (linking local variable in different recursions) that describes the changes needed. When it knows what's needed, it can check whether it can allocate all needed nodes, and apply all needed changes (or not) by referencing this plan before falling out of the recursion.
This implementation also "maintains" iterators on updates, so iterators aren't invalidated by inserts/deletes unless the specific item they point to is deleted. Inserts/deletes within the same node cause the iterators pointing into that node to be updated.
Trouble is, I need to make it multithreaded - supporting potentially many readers and writers at once.
I want multiple readers to be able to read and write at the same time, so long as there is no risk of corruption as a result. So for reading, I don't want mutually exclusive access at all, even to a single node. For writing, I want to lock the minimum number of nodes needed for the change. And I want to avoid deadlock, of course.
Thankfully, it isn't something I actually need to do - but since I've neglected my concurrency skills, this just seems like a good thought experiment.
This is obviously similar to the kinds of problems that databases and filesystems have to handle, so I'm guessing I might get some references to that kind of thing, which would be great.
So - how would I handle the thread synchronisation for this? I can vaguely see a role for mutexes and/or semaphores on nodes, but what strategies would I use to work with them?
Definitely challenging task! I see that you c++ programmer, however I believe that in c++ there are similar concepts as in java and I'll try to help from java standpoint.
So for reading, I don't want mutually exclusive access at all, even to a single node
You could use ReadWriteLock. It be held simultaneously by multiple reader threads, so long as there are no writers. The write lock is exclusive. You just have to use exclusive access when doing writing. Do you have analogue in c++?
And I want to avoid deadlock, of course.
Just lock multiple nodes in order of levels (eg from top to bottom). That will guarantee you protection from deadlocks(that would be smth similar to Lamport's Bakery Algorithm).
As for databases - they resolve deadlocks by killing one process :-).
One more strategy is to implement unblocking tree structure in the similar manner how Cliff Click implemented unblocking hash map(state machine with all cases covered):
video
Cheers