Im not 100% sure you really understand ECS.
Your renderer shouldn't have all the entities, as other manager systems might also work with the entities (Physics Manager moving entities, influencing your bone transformation, the position in general, etc.), while other aspects of your entities are only used by your renderer (e.g. textures) and others only by other managers (sounds, inventory...).
Now, your entites are only containers or identifier / handler for a group of components. These components are what are interesting for your renderer and managers.
For example you have a shaderComponent, saving your shader informations, a modelComponent, for 3d models, a textureComponent, saving your texture information, a positionComponent, a soundComponent, a inventoryComponent and some other components.
Your entites are handler for all of those compoents and has the option to at least have one those components, maybe even 3 or 4 for some components.
Now, your renderer interates on the entities, but only if it has the components the renderer works with: shaderComponent, textureComponent, positionComponent...
Your renderer than does its thing: if an entity has a shaderComponent, load this shader. If an entity has an animationComponent, apply the bone transformation on the model. Then if the entity has a model, render the model with the textures it may have, use the position from the positionComponent and so on.
Your renderer needs to know, what components to use and what to do with it. If an entities doesn't have a component, it doesn't try to work with those.
If some components need other components to work, your renderer either needs to know this, or your components have functions on their own to deal with that.
In the case, what informations to send to the UpdateXXXConstantBuffer, i would suggest to have a worldViewMatrixComponent, when your shader needs it for an entity, and other components for other cases.
So first, the renderer iterates over every entity and asks what components it has. Then it calls those components in its wished order. Now, what it does depends on the next thing.
There are basically two different approaches to components in an ECS.
- Components are function carrier
- Components are Value-Only
The first approach has the components be called in the corresponding manager. In your example, the shaderComponent loads the shader into the GPU to render the next object, then the renderComponent is called to render the object, using the loaded shader.
As you can see, the shaderComponent doesnt care, what component comes next, as the manager has to know, when to call what component type of an entity.
For example, an entity has a corresponding renderingComponent, but no shaderComponent, the rendering manager would first try to look for the shaderComonent, then, if it cant find it, just use the standard shader instead. If there is no rendererComponent either, the entity will not be drawn. An Entity with only a shader could be something like water effects or other shader effects, that are not directly drawn. Im not that deep in graphics programming, so i may lack better examples
- components are way more versatile, as you can implement what you want
- Components act in a void, they dont need other components to work (basically atoms), except maybe input parameters for the update function (rendererComponent need the current position and the current deformation matrix and so on.
- the renderer doesnt need to know anything other than the order and what informations to keep
- As components know nothing about other components (could be dealt with, but is bad practice), the renderer needs to do the order. If something changes in a component, it might influence the order of component calls (no loose coupling between components and manager)
- Components NEED to have update(): Even if your component does nothing but save something, it does need to have an update function. This update() can be empty and your renderer may decide to call it, if it knows what type of component it is (like a positionComponent only containing a position), but that would mean stronger coupling of components and renderer.
The other method is to just save data in your components. I prefer this type, as it comes with some benefits to it. You can save shaders, textures and models by reference, others by value.
- Loose coupling: Only the renderer knows the values of the components. The components themselves dont care, what the informations are. So if you change something in the renderer, the components couldn't care less.
- Loading from files: If you create a new type of enemy, just load the file with his texture, his model... no need to save functions in a scripting language (good for modding aswell)
- Informations from one component can easily be used in another. No need to call an update() with different parameters, just to give position and texture and so on.
- less flexible: Your renderer needs a one-fits-all update-function. If the Enttiy has component a, b, c, do this, if it has a and b, not c, do this, and so on.
So, what do you want to do, to start a Graphics Engine. I go the "components have values only"-way
- Make a general Manager class, that renderer inherits from. If you want to expande on this basic graphis engine, you want to do that. TRUST ME! And do the same for components.
- Think of every aspect an Entity can have. Position and direction, Texture, Model... do that as atomic as possible. Working with 2d Sprites. have 2DTextureComponent and a spriteComponent. 3D? modelComponent, 3dTextureComponent and so on.
- Add some helper classes, like a camera (saves viewMatrix, helps with smooth camera movements and so on) and a texture manager (so textures get loaded into the gpu memory only once and return that texture ID in gpu)
- Now imagine a Master Manager class calls this Renderer class to create a graphical entity. It does that by givin all required informations (lets assume 2d): an Entity (lets assume by ID (a number)) with an exisiting positionComponent, a texture path and a sprite size (64 x 64 pixels or something like that. The renderer creates a spriteComponent (sprite size, no idea what other informations) and a textureComponent (texture by reference or id or whatever). It loads the texture into the gpu and is done for now.
- Your update( deltaTime ) of the renderer gets called. It gets (or calls for) every entity, that has corresponding components (why care for an object, if it has no spriteSize). It then iterates other every component of the wished type and does its thing: Read its positionComponent(mandatory for every Entity), calculate viewMatrix postition, then draw the entity with the given spriteSize and given texture. If it has a spriteComponent, but no textureComponent (or texture coulnt be loaded) you can fill in a default texture. If you have a shaderComponent, you can even order them and only do entities with shader A, then entities why shader b. Samething with textures (Entities with texture A -> Entities with texture B -> ...)
And that is basically it. You cant expend your Game Engine by adding more managers with their own components. Physics Component adds velocityComponent, physicalAttributCompnents (Weight and stuff, if you want it that sophisticated).