DynaMix
1.3.7
A new take on polymorphism in C++
|
An entity-component-system (ECS) allows users to create entities which consist of components. A simplified way of thinking about that is a container of instances of unique types. Another way of thinking about said entities is like they are objects produced from multiple inheritance only they are constructed in runtime, as opposed to compile-time.
Here's a piece of code that might be used in a simple hypothetical ECS:
entity e; e.add_component(new xml_serializer); e.add_component(new book_data);
now the entity contains data and a way to serialize this data ... here we can test if an entity has the components we need if(e.has_component<xml_serializer>()) { e.get_component<xml_serializer>()->serialize(); }
See how we can think of e
as a container of various objects, or as a class derived from both serializer
and data
. However it is not exactly a container as it cannot have two instances of the same class, and it is not exactly a class with multiple inheritance as its contents cannot be statically analyzed.
They allow the users to achieve true decoupling of their subsystems. For example if we extend the above example to have a registered component of type serializer
, which is a pure virtual class with a method virtual void serialize() = 0
, the code example from above would still work, but we would be able to change the last part to something a bit more useful:
if(e.has_component<serializer>()) { e.get_component<serializer>()->serialize(); }
Now all functions that want to call serialize
for an object may as well be oblivious to what the actual serializer within the entity. Keep in mind that this is nearly impossible to achieve with plain multiple inheritance as users would have to handle the combinatorial explosion of different serializer
's, data
's and other possible super-classes.
The above example shows that an ECS is, in most cases, a better choice than multiple inheritance. But what about composition?
Well, an ECS is a type of composition, with one difference. An entity has no idea what kind of components may be a part of it.
Imagine a the straight-forward way to composition. An object from our system will have methods like get_serializer()
, get_data()
, and more. This means that every time we add a new sub-system, we'll have to change the object type to "enrich" it with its new components. Removing the aforementioned limitation allows independent sub-system development – without changing the code of the original product. I also allows the introduction of entire subsystems with dynamic libraries, which don't change the binary. For example, writing an entirely new serialization mechanism – say encrypted serialization – for a product and introducing it as an optional plug-in becomes a breeze.
There exists a technique, often also called entity-component-system, that is not polymorphic. It can be combined to a degree with code like the one described above but it requires special maintenance.
Essentially what it does is, the entity has pointers (or sometime indexes) of concrete components, that aren't managed by it, but by the subsystems that use these components. The subsystems contain dense arrays of these components, which they process, often being oblivious of entities and other higher-level concepts.
This is done in very performance heavy pieces of software, where the cache locality of the data is critical. Since polymorphism is incompatible with cache locality, those systems sacrifice (at least to some degree) the ease of use and maintainability for the speed.
This is especially popular in high-end games, where in order to produce state of the art graphics and physics with a decent framerate, each microsecond is of importance.
They are very popular in game development, as most objects in a game have many different aspects for different sub-systems. Still, being as powerful as they are, they find their place in many other pieces of software developed in C++, Java or C#.
It is the author's opinion that any big project with complex objects could benefit from using an ECS. Indeed games and game engines are a prime example of such a project, but there are many others. Any software that has optional dynamic library plug-ins, most CAD systems, graph editors, project management tools, and many others.
Most script-language objects natively support a functionality resembling the polymorphic form of these systems, and most of them call it Mixins.
If you're using Microsoft Visual Studio 2015, you can install a special watch visualizer which will make adding dynamix::object
in a watch window a better experience.
The result when adding a watch on an object should resemble this:
To install this visualizer:
dynamix.natvis
in your DynaMix installation. It should be in the tools
subdirectory.<Documents>\<Visual Studio Version>\Visualizers
. If the directory doesn't exist, create it manually.VSINSTALLDIR%\Common7\Packages\Debugger\Visualizers
. You will need administrator access to do this.To watch concrete mixin from an object, you should add a new watch where you explicitly cast the address to the correct mixin type. The visualizer only helps you quickly find the address and the target type. It doesn't (and unfortunately can't) perform the cast.
If you perform the cast correctly, the result will resemble this: