Architecture advice
Hi!
I am building a game engine in C++. The architectural style I am using is OOP with composition, also known as a "bag-style" ECS. The main difference from a classic ECS is that entities are not just IDs but rather own their components and can operate on them via Add, Remove, Get, and so on. The components themselves consist of data and behavior, and the systems are interested in specific entities that have specific components. For example, the Render system needs entities with Transform and Sprite components.
The current architecture is designed around the fact that this is C++ and there is no garbage collector. Additionally, it is not a good idea to destroy entities in the middle of the main loop. Because of this, I created an EntityManager that can create entities, mark entities for removal, flush pending entities, check if an entity is alive, and return a raw pointer to an entity so you can access it. The whole thing works because the manager uses EntityIds, meaning you cannot have dangling pointers. The manager also manages the entities' lifetimes so it always flushes them at the end of the frame.
For now everything is good, but after adding the BaseComponent and all the logic into the entity, I reached a point where adding or removing a component changes the entity's signature. A signature is a bitset where every index represents a different component, like Transform, Collider, or Sprite. If the bit is 0, the entity does not have it; if it is 1, it does. When this changes, every system needs to check the entity so it can add it if it now suits the criteria, remove it if it does not, or simply ignore it.
To achieve this, I either need something like a RegisterManager and make it a singleton, or I need to make every entity hold a pointer or reference to this RegisterManager, which feels wasteful. At the same time, singletons or making the pointer or reference static inside the entities are considered bad practices. I also want my components to have unique, recyclable IDs just like my entities. To do that, I need to make something like a ComponentFactory. When a component is destroyed, it should notify this factory to recycle the ID, which means the factory either has to be a singleton too or the entities will have to hold yet another extra pointer. One fix is to create an event bus to handle cases like this, but then again, either the event bus is a singleton or everything that uses it must hold a pointer or reference to it.
So my question is, is there a pattern or hierarchy I can follow to avoid this repetition of every entity holding a pointer to the same system, or should I just stop listening to the posts online that say singletons are bad because of multithreading and unit testing, and just make a few?
Thanks in advance!