0
\$\begingroup\$

I've been using the ECS pattern for a while now and, as the complexity of the game increased, I started having issues with the timing of the entity initialisation and destruction code.

I'll use the Physics as the example, but the same principles apply to most other aspects as well.

First a few definitions:

  • PhysicsModule - the low level physics system (usually an external library like box2d) - has no idea about ECS
  • PhysicsSystem - an ECS system - works as an iterface between the low-level module and the rest of the ECS
  • EntityWorld - the object responsible for creating and destroying entities and associating them to Systems

For example, a typical scenario goes like this:

  • 1) An Entity gets created from external data (Transform, ShapeData, CollidableData)
  • 2) The data has to be registered with the PhysicsModule (using ShapeData and CollidableData)
  • 3) Every frame, the PhysicsSystem asks the PhysicsModule for the position of the related object and updates the Transform. Repeat for a while.
  • 4) The Entity gets destroyed in another System far a way
  • 5) The PhysicsModule needs to be told to remove the associated physics object

The problematic steps are 2) and 5). I can think of two ways of handling this.


Have the PhysicsSystem handle the lifecycle.

Pros:

  • the logic is encapsulated in one System

Cons:

  • the PhysicsSystem needs to know about the init data (ShapeData, CollidableData) which is useless after the initial registration with the PhysicsModule
  • the EntityWorld has to keep track which entities were freshly added or removed from a system to notify it about them
  • the destruction of the entity has to be delayed in order for its components to still be accessible in the same frame
  • this approach often causes long chains of events (spanning a couple of frames) which are difficult to follow

In order to make this work, the main loop always ends up being a complete mess to make sure particular inits, updates and destroys are done in the correct order.


Have one place for all the init and destruction code.

Pros:

  • the init data is not used beyond the initial registration
  • the init / destroy code is in one place
  • all the cleanup can be done in one frame in the correct order

Cons:

  • this essentially becomes the God Object and knows about everything in the game
  • the whole thing probably becomes very cluttered with all the possible permutations
  • the destruction of the entity has to be delayed in order for its components to still be accessible in the same frame

At the moment my game evolved into the first solution and I'm having quite a few issues so I'm thinking about trying another approach. The second one I mentioned seems a bit better, but it does involve a God Object and from my experience that is a bad way of doing things.

Also, I took a look at various ECS implementations and noticed that some of them have methods to notify systems that entities were added/removed to them and some of them don't - so there's obviously various approaches to this.

My question is - Is there another way of handling Entity Initialisation and Destruction without resorting to either the God Object or the God Loop?

\$\endgroup\$

2 Answers 2

2
\$\begingroup\$

This is one of the common problems with ECS architecture: Intersystem communication.

There is no silver bullet solution, and research is ongoing, but common solutions are:

  1. Observer patterns. Any change to the entity triggers updates to the observer, which could be the systems in question, or a system relevant abstraction of the entity. Upon destruction, the observer is notified, and consequently removes the entity from it's list of stuff to process and observe.

  2. Callbacks. Think of this like an "on destruct" asynchronous behaviour. Requires the entity to have knowledge of the world/engine into which it has been injected. Upon destruction, it calls the callback function, passing itself and the world/engine, and calls something like engine->removeEntity(this);

  3. Direct Command Object. Any time an entity is to be destroyed, it is not done so directly. Instead, a command is filtered up to the top engine level, which processes the command and calls a remove function on all systems, to remove the entity.

Finally, another option, is to have a single factory with a static collection of entities and components, managing their lifetimes. Systems, then, do not actually store pointers to entities, but an ID, which is used to query the factory. If the ID query returns false, then the system simply removes that entry from it's collection of IDs, and processes the next in line. This completely eliminates the possibility of dangling pointers, but at the cost of being potentially non-cache friendly.

Personally, I use instantiation of direct command classes, as this is something very easily tied into a scripting language, but this has it's own drawbacks.

The best advice I can give, is to experiment with the different approaches above, or combinations of them, and pay mind to considerations such as usability, ability to be scripted, performance, and cache friendliness.

Each approach has drawbacks and benefits, and its highly educational to discover these by yourself, if you aren't on a timescale.

\$\endgroup\$
6
  • \$\begingroup\$ The issue with callbacks and observers (if done wrong) is they can lead to spaghetti pathing through code. Direct ref to superobjects are best / simplest, in my experience. Have sub-object notify the super-object of the destruction. Let it inform all other subsystems / make calls accordingly. This is the least spaghetti-codish way of doing things. Do so using direct refs to super-objects, or singleton refs. \$\endgroup\$
    – Engineer
    Feb 20, 2018 at 10:15
  • \$\begingroup\$ All totally valid points. I could say don't do callbacks and observers wrong though ;p The super object approach is solid enough, but personally I dislike the idea of globally visible data, which is necessary for singletons to work. Another viable option for a super object is simply to have all it's member variables be static. and have everything that needs access to simply instantiate the object. It's far simpler and avoids all the pitfalls of singletons. \$\endgroup\$
    – Ian Young
    Feb 20, 2018 at 11:09
  • \$\begingroup\$ Yep, globally-visible data not ideal. One could argue that that is a limitation of languages rather - should be able to specify who can see what, call what and what access they have, RWX-style. Problem with instantiation is heap allocation time and cleanup if you're under GC. \$\endgroup\$
    – Engineer
    Feb 20, 2018 at 11:12
  • \$\begingroup\$ An easy way to get around the GC issue is to instantiate the object at startup/configuration time. Allow the super objects to live throughout the programs lifecycle. \$\endgroup\$
    – Ian Young
    Feb 20, 2018 at 11:16
  • \$\begingroup\$ That's fairly standard practice for me in C. Keeps things tight regardless of GC. Must have misunderstood what you meant before, then, by "have everything that needs access to simply instantiate the object". \$\endgroup\$
    – Engineer
    Feb 20, 2018 at 11:18
0
\$\begingroup\$

Check this answer from myself in another post where I explain a messaging system for an ECS and provide a link to code implementing it.

On the other hand, I don't know what you mean by God object exactly, but a Factory pattern is perfectly fine. Besides centralizing this initialization/destruction of entities, you can avoid the object creation in cases where you create/destroy frequently. I'll explain:

You can create a pool of entities and have X instances of each type of entity already created. They are objects that the factory holds internally and nobody else has to know about them until they require a new object of that type to the factory. Then, you only need to initialise the entity with the right parameters, but you avoid the memory allocation (expensive) and object construction itself. That's factory's responsibility and users of the factory have no idea nor worries about whether its entity was newly created or reused.

Hope it helps.

\$\endgroup\$
1
  • \$\begingroup\$ By God Object I mean an object that knows about everything in the game. For example, if you were to create an Entity with Physics, Rendering and Animation the Factory would need to have references to all these subsystems to correctly register the Entity with them. It would also need to know about all the possible interactions between components - for example, If the object to be destroyed has child objects, destroy them as well unless they have a certain set of components, etc. I'd imagine this would become quite messy very quickly. \$\endgroup\$
    – loodakrawa
    Sep 25, 2017 at 4:23

You must log in to answer this question.

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