Systems in the Entity-Component-System Design Pattern are responsible for applying logic to a set of components. Examples of systems are a Rendering System or a Physics System.
The Rendering System sends data to the GPU and calls OpenGL functions to display the scene on the screen.
The Physics System updates each object according to a set of rules.
A valuable property of the systems is that they only need to know a subset of the components of an entity to do its job (e.g., the Physics System doesn’t require any vertex data). This way, the complexity can be kept low, which could be seen as an application of the Law of Demeter.
Another advantage is that it allows for optimisations much more easily (e.g., grouping of rendering calls) because a single system is aware of everything that needs to be rendered.
Rendering System
Unfortunately, the advantages of Systems discussed in the previous section, won’t be discussed further in this post. The focus of the initial implementation is to reduce the amount of responsibilities in the Entity class. The current implementation is very far from a good application of the Single-Responsibility Principle.
An entity is a grouping of components, but the render and update functions seem out of place in this context.
With the Rendering System in place, the render function in Entity is not required anymore. Preferably, the System only works with components, but that is not possible in this first implementation, because there are dependencies in rendering order. These are the result of the glow effect that disables rendering to the depth buffer.
Because of the way the effect is implemented, it always has to be rendered before the object itself, creating a strict dependency in rendering.
The implementation of the rendering system is as follows:
Entities explicitly have to be unregistered from the Rendering System, because the Rendering System uses pointers to Entities and can’t know when they have been destructed. This leads to additional code in the Scene implementation:
Looking at the remaining code in Entity, only the update function should still be replaced by a
system. Besides that, all complexity has been removed.
Other Updates
Finally, let’s also discuss some of the other code improvements that have been done in the meantime.
getOrCreate in Shader Registry
The ShaderRegistry had to be aware of every type of shader that was used in the application because they had to be instantiated in the constructor. A cleaner way to avoid this dependency is to allow users to lazy instantiate the shaders. This way, shaders are only compiled when they are actually used and the Shader Registry doesn’t require the dependency anymore.
Removing the Generic Shader Component
When shaders were implemented as components, there were two types of components created: ShaderComponent and GenericShaderComponent. ShaderComponent was the interface for a component, while the GenericShaderComponent was the implementation of that interface that contained a reference to the shader itself.
This was an unnecessary level of indirection. The original idea was to have specialised components for each shader that contained the GUI for the options in the shader. At this point, there are no specialisations, so this complexity can be removed. I prefer to only introduce the complexity when it is needed.
Copying components
I always try to avoid putting too many restrictions on what users can do with an instance. There are cases though, where you know up front that some behaviour is simply a bug. An example of this is the copyability of components. Normally, a component is created once for an entity and never copied afterwards. In the original implementation of the render function in Entity, the components were accidentally copied:
It is a very subtle difference, only a single &-sign, but it becomes a costly mistake once you start copying over vertex data on each render call. The simple solution to prevent the bug from happening again is to remove the copy constructor (and copy assignment operator) of the component class.