Component entity system - create from template

Started by
14 comments, last by avision 10 years, 5 months ago

haegarr

I agree with you.

Most of the code is pseudo-code and is in process of evolution. As I noted, spawner, or as you call it "UID generator", obviously should not care about storing and updating components, it only should be able to construct an entity. This was the initial concept of the EntitySpawner. It was then expanded by simber who suggest to use another class that will be responsible for storing the components and third class for each system, to update the needed components.

This question is also a theoretical discussion of different approaches to achieve the same. Whether there should be one system per component, or one "mega" system to store all components? What are the pitfalls of each approach?

I'm not so experienced in ECS and I need help and suggestions of those who know better than me, those we can see the pros and cons of each method.

I would love to change the world, but they won’t give me the source code.

Advertisement

My favorite concept (because I'm about to implement it myself ;)) is the following:

An entity in the sense of a CES exists during runtime of gameplay only, i.e. it is part of the world. As such it is a UID and a couple of entries distributed over some sub-systems (named "services" in my engine). The entity instantiation process works on a resource kind of the Model class. A Model instance by itself is a composite of concrete Component instances. For each possible concrete Component sub-class there is a corresponding concrete ComponentType singleton-like instance. Such a ComponentType has a name (i.e. the one you use in JSON), is a factory for the belonging concrete Component, and can be understood as an outpost of one of the sub-systems.

Loading means to make Model instances, populate it with Component instances, and store them in the resource library. The ComponentType instances, addressed by their name, work as factories and de-serializer, so to say. The names of all used ComponentType instances are stored (indirectly) within the Model, too. There is another mechanism that may put objects into a Model instance. Components need not be specified in their entirety. Instead it is possible to express that some properties need to be defined at entity instantiation time (example: auto-generated individual name for each new instance of an orc-ish soldier).

Entity instantiation then means that the EntityServices sub-system is invoked to create a new entity by name. A parameter map is needed to be given at this moment if the Model of the requested entity has variables. The EntityServices creates a new entity UID, looks up the Model belonging to the given name in the resource library for Model instances, iterates the ComponentType instances for which a concrete Component instance is attached to the found Model instance, and invokes them to do their portion of entity creation. The entity UID, Model instance, and current Component instance is overhanded during this invocation. The ComponentType then investigates the Model instance for completeness, if necessary, does all allocation and initialization for the new entity inside the belonging sub-system, including eventually the filling of variables from the provided parameter map.

A ComponentType may return either with a Success, Failure, or Rework state. The former 2 are probably clear. However, it may happen that a ComponentType requires the set-up of another ComponentType (i.e. of another sub-system) to be done already, although those ComponentType wasn't iterated yet. So a ComponentType may tell the EntityServices that it want to be re-invoked once after the list of ComponentType is iterated first. What exactly happens within the sub-system during entity instantiation depends on the sub-system itself. It may be a cloning of Component as a prototype and linking the clone, or reserving and filling-in a place in a pre-allocated table, or whatever is suitable for the sub-system.

Notice please that the described mechanism doesn't require a 1:1 correspondence between sub-system and ComponentType; instead a 1:n correspondence is possible. Notice further that Component is just a data carrier, although it may define a behavior, but it does not perform any behavior.

Looks similar to what I want to implement, but with different names for the classes. Thanks!

I would love to change the world, but they won’t give me the source code.


A ComponentType may return either with a Success, Failure, or Rework state. The former 2 are probably clear. However, it may happen that a ComponentType requires the set-up of another ComponentType (i.e. of another sub-system) to be done already, although those ComponentType wasn't iterated yet. So a ComponentType may tell the EntityServices that it want to be re-invoked once after the list of ComponentType is iterated first. What exactly happens within the sub-system during entity instantiation depends on the sub-system itself. It may be a cloning of Component as a prototype and linking the clone, or reserving and filling-in a place in a pre-allocated table, or whatever is suitable for the sub-system.

Can you elaborate on this ("Rework")? Maybe some examples of what kinds of problems this is intended to solve? I don't have anything like this in my implementation, so I'm wondering if I'm missing something (or if I solved it another way).


Can you elaborate on this ("Rework")? ...

If the components are iterated in order C1, C2, C3, and the sub-system handling component C2 requires access to whatever C3 will cause later, then it will fail because C3 isn't handled yet. It then returns the Rework state to tell the entity services that it wants to be invoked again. The services memorizes all sub-systems that return Rework. After the initial run through all component the services iterates all memorized sub-systems and invoked them again. For the example, the total sequence then would be C1, C2, C3, C2. This is repeated (with resetting the memorized list each turn, of course) until at least one sub-system returned Failure, or none returned Rework, or the amount of Reworks was not lesser than as in the previous run (this last condition avoids endless looping).

The above is a mechanism to solve dependencies without a-priori bringing the sub-system invocations into a special order. It is perhaps not required for your implementation.

I would probably arrange things differently.. I will try to describe it with examples.

I would use Entities, Components, Nodes and Systems. Ok what are they..

Entity: Could be just a UID

Components: Things like a 3D-mesh component, visibility-component, location in 3d-space component, AI-component ..

Nodes: below..

Systems: Render System, AI-System, Pathfinding-System

When game data is loaded there are basically a set of components describing an entity. Like a mesh-component with the filename and the like and probably an ai-component. The component is only a set of values with some sort of identifier.

Then this set of components is examined. Means all systems check if they want an entity having this _combination_ of components. If the render-system sees a entity having a mesh-component and a visibility component it creats a render-node for this entity and stores this in a list, dictionary, vector or whatever..

On each frame each systems iterates over the nodes it has and does it`s work. If an entity looses a component the systems are notified about that and check if they have to destory/remove the respetive node (Or some other piece of code does this and only notifies the systems loosing a node/entity). For example if a entity looses its visibility-component the render-system may want to remove the render-node but the resources-system may want to keep its resource-node.

In pratice most system will need to share data. So the goal should be to try to group data as much as you can if the data is not shared by multiple systems, if data is shared by multiple systems, spread it in different components. The way systems will store the entites/component-sets will be different from system to system, most systems will probably use a simple list, others will use a different way cause they want to process the nodes with multithreading or have a search tree or whatever.

To identify if an entity has a set of components a component could have an integer-id and the check if a entity has a set of components could be done with bit-masking. (The check which is done when an entity is created and the systems want to know if they "want" this entity).

just my ..

avision

This topic is closed to new replies.

Advertisement