https://ibob.bg/dynamix/index.html
DynaMix 1.3.7
A new take on polymorphism in C++
* Main Page
* Related Pages
* Classes
* Files
* [mag_sel] [Search ] [close]
Introduction
Table of Contents
* Abstract
+ Other meanings of "mixin" in C++
+ Other meanings of "mixin" in other languages
+ Basic look and feel of the code with DynaMix
* List of features
* When (and when not) to use DynaMix
* Comparisons
+ Comparison with multiple inheritance
+ DynaMix as an entity-component-system
+ Comparison with COM
+ Comparison with traits
+ Comparison with Ruby mixins
logo.png
Abstract
DynaMix is a library which allows the composition and modification of
polymorphic objects at run time. Objects are constructed from
building blocks called "mixins" enabling an effect similar to
multiple inheritance, while allowing the client code to remain
oblivious to the actual composition of the objects.
A take on the Composition over inheritance technique, the result
resembles the mixins in Ruby. It can also be compared to the
inheritance in Eiffel or the traits in Self, Scala, PHP, and many
others, or the roles in Perl.
This is given while also having full abstraction between the
interface and the definition of types - a problem often given as the
motivation for the PIMPL idiom.
In short, DynaMix is an alternative way to accomplish polymorphism.
It is a means to create a project's architecture, rather than
implement its purpose.
The library uses the type dynamix::object as a placeholder, whose
instances can be extended with existing classes (mixins), thus
providing a particular instance with the functionality of all those
types. Accessing the newly formed type's interface is made through
messages - stand-alone functions generated by the library, which can
be thought of as methods.
DynaMix focuses on maximal performance and minimal memory overhead.
Other meanings of "mixin" in C++
The term "Mixin" is not to be confused with another meaning, popular
in C++, namely CRTP mixins. This particular use of CRTP in this
document shall henceforth be referred to as "traits", as the exact
same functionality is called "traits" in many other languages.
Other meanings of "mixin" in other languages
In D the term mixin exists and mixin is a keyword. It is a compile
time feature, that has some similarities with the macros in C and
C++, and none at all with the mixins in DynaMix. It is a completely
different concept.
Basic look and feel of the code with DynaMix
Here is a very small and incomplete example of what your code may
look like if you use the library:
// assuming my_objects.get_ally(0); is a way to get an ally to the
// main character in a game
dynamix::object& obj = my_objects.get_ally(0);
// now let's make the object think some positive thoughts about the
// main character
think(obj); // C++ doesn't allow us to have obj.think().
// DynaMix's messages are standalone functions
// object composition
dynamix::mutate(obj)
.add();
// object can now respond to fly()
fly(obj); // ...instead of obj.fly()
// object mutation
dynamix::mutate(obj)
.remove()
.add();
think(obj); // the same object now thinks negative thoughts about the
main
// character, since it's no longer an ally, but an enemy
For a more detailed, working example see the basic usage tutorial.
List of features
* Compose objects from mixins at run time
* Physically separate interface and implementation
* Non-intrusive - mixins don't need to have a common parent or any
special code inside
* No external dependencies other than the standard library
* Fast polymorphic calls - comparable to std::function
* Mutate "live" objects by changing their composition at run time
* Have multicast messages, which are handled by many mixins within
an object
* Possibility to have custom allocators to finely tune the memory
and aim for cache-locality for critical parts of the code
* Ability to have dynamic libraries that can enrich or modify
objects, without modifying (or even rebuilding) the executable.
* Thread safe message calls - as thread safe as the underlying
methods.
When (and when not) to use DynaMix
The more complex your objects are, the more beneficial it will be to
use the library. Pieces of software that typically have very complex
objects include games (especially role-playing ones or strategies),
CAD systems, enterprise systems, UI libraries, and more.
As a general rule of thumb: if you have complex polymorphic objects,
DynaMix is a good choice.
We should emphasize on the polymorphism. In many very
high-performance systems polymorphism is avoided at the cost of code
that is (at least somewhat) harder to write and maintain (this is
most often the case with high-end games). Since such systems will try
to "squeeze" every possible piece of processing power out of the CPU,
cache locality and lack of cache misses are critical in some parts of
their code. As is the case with all instances of polymorphism,
including C++ virtual methods and std::function, uses of DynaMix's
features will almost certainly lead to cache misses. Of course, you
may still rely on the library in other parts of your code, like the
business (or gameplay) logic. For more information about the library
performance, see the Performance section.
Of course, small projects with simple objects, even if they are
polymorphic, may end up not finding any particular benefits in using
the library, since their size makes them fast to compile and easy to
maintain as they are. If a piece of software can be created in a
couple of days, by one or two programmers, there will hardly be any
need for DynaMix.
Comparisons
Comparison with multiple inheritance
The closest thing to DynaMix that C++ can offer out of the box is
multiple inheritance. A dynamix::object composed of some mixins, can
be thought of as an instance of an empty class that's derived from
these classes - the object's interface will be equal to the union of
the interfaces of its mixins, and it will internally instantiate
them.
Here's a comprehensive list of the most important differences between
DynaMix and C++ multiple inheritance:
* No combinatorial explosion of types: you compose types at
runtime, and don't need to explicitly list all possible
combinations of building-block types.
* No type-bound interfaces: since the interface is physically
separated from the implementation (or type), you don't need to
have a single class implement an interface. You could separate it
between multiple classes or combine interfaces in a single class.
* Live object mutation: change the interface and/or implementation
of messages in an object at runtime.
* Natural common reference: in order to have containers of your
objects composed by multiple inheritance, you need to define a
"master type" from which all parents need to be derived by
virtual inheritance. Such a type would be a coupling "focal
point" and would need to be frequently changed while you develop
your software. In DynaMix this is a no-issue, since all objects
in the library are of type dynamix::object.
However, even-though compared to other libraries that have similar
features, DynaMix is one of the fastest and with the least memory
overhead, using the library comes with some inevitable downsides when
compared with plain multiple inheritance:
* No compile-time type: An object derived from, say, foo and bar
can implicitly be cast to foo or bar. Since the library uses a
placeholder type - dynamix::object - implicit casts to any of its
mixins are impossible. That aside, the hypothetical object from
above will receive all of the methods from foo and bar, but while
this is true for the dynamix::object with such mixins, a
compilation error cannot be generated if a message is called that
the object can't handle. A runtime error will be generated
instead, which is the norm in similar libraries, but is harder to
catch and debug than it is with plain multiple inheritance.
* Memory overhead per type: Each unique combination of mixins is
internally represented by a type, which has type metadata - for
example an internal alternative to a virtual table. The type
metadata can take up to two kilobytes of memory. Unless a type is
instantiated its metadata won't be created, but still if you
expect to have a huge variety of types, you may want to keep this
in mind. Of course systems that are expected to have thousands of
different object types, usually wouldn't care about a couple of
extra megabytes of memory, but it could happen.
* Memory overhead per object: An instance of an empty class that
has multiple parents takes up memory equal to the sizes of all of
its parents. This is inevitable. A dynamix::objects naturally
takes up the same amount plus an additional pointer for the type
metadata, plus N pointers used for the special dm_this pointer,
where N is equal to the number of mixins within. So, in short,
the memory overhead of an object composed on N mixins, is N+1
times sizeof(intptr_t) ((N+1) * 8 bytes on a 64 bit system).
* Speed of message calls: A message call is slower than a regular
non-virtual method call. Exactly how much is very hard to
estimate, since this depends very much on cache locality, but a
message call can easily be compared to a std::function call in
terms of speed, which in most (but not all) cases is a negligible
overhead.
DynaMix as an entity-component-system
If you're familiar with entity-component-systems, one way of looking
at at the library is as if it is one of those, and indeed, it has
many features that are characteristic for such systems. More
specifically the Interface to Component pattern
If you're not familiar with entity-component-systems, you might want
to check out the appendix entry on them.
Here's how DynaMix is like an Interface to Component ECS:
* dynamix::object can be interpreted as an entity in an ECS. It's
just an empty class, that needs to be "built" from mixins.
* consequently mixins can be thought of as components. You use
mixins to build objects just as you use components to build
entities.
* As with any ECS, you can mutate objects by adding/removing/
substituting mixins.
However, DynaMix is not strictly an ECS. Here's a list of the
differences.
* It is non-intrusive: Mixins don't need to inherit from a common
parent, nor do you need to change the code of a class to "turn it
into" a mixin.
* Instead of classes with pure virtual methods, messages are used
to represent the functional interface of mixins, and through that
- of objects.
* Because of the previous two differences, dynamix::object-s can be
(and are) completely oblivious to what kinds of mixins there may
be, allowing you to truly, physically separate a program's
subsystems. The entity in an ECS on the other hand, usually has
at least some knowledge of all the possible component types (like
the top level parent classes, for example).
* Some entity-component-systems allow several entities to point to
the same instance of a component. This is not possible in
DynaMix. A mixin instance is bound to an object instance
* Some entity-component-systems allow an entity to contain several
instances of the same component type. This too is not possible in
DynaMix. An object can have either zero or one instances of a
given mixin type.
Note that some data structures, sometimes called
entity-component-systems, are not used to create and manage
polymorphic objects, but instead are used to bind a strictly
predefined set of concrete components to the same entity for a very
performance intensive piece of software. The components are used by
different subsystems, that require them to be aligned in dense arrays
for faster processing, without cache misses. As we mentioned before,
DynaMix isn't designed for such cases, and while in terms of design
and ease of use, it is a better choice than such an approach, it
cannot help you in their main goal. Still, a non-polymorphic ECS and
DynaMix can be (and have been) used together in a signle piece of
software where DynaMix is used in the business logic subsystem, while
the performance critical low-level parts of the software make use of
a non-polymorphic ECS.
Comparison with COM
There are indeed similarities between DynaMix and Microsoft Component
Object Model. DynaMix object are composed out of mixins much like
COM's objects are composed from components. Indeed many patterns in
DynaMix might be familiar to programmers familiar with COM. The
notable differences are:
* DynaMix has multicasts
* With DynaMix there are no interfaces per se. The messages are
standalone functions and a mixin implements an arbitrary
combination of them.
* Removing a mixin from an object in restores the object's previous
state
* DynaMix objects feel more like idiomatic C++ objects: They can be
moved and copied and they have destructors instead of relying on
a Release method.
Comparison with traits
As mentioned above DynaMix is similar to a feature of many other
languages, called "traits". The exact same feature can be mimicked in
C++ with CRTP. You may have heard the term "mixin" being mentioned in
a C++ context before. It is very likely that what was meant was this
CRTP style of creating types from existing ones.
Indeed, at least superficially, both DynaMix and said traits are very
similar as both are used to create new types by combining existing
ones, while also solving one of the problems of plain old multiple
inheritance - the communication between the different components that
comprise the object.
However much of the multiple inheritance problems remain:
* Types composed of traits are static. Most languages (including
C++ using the aforementioned mimic) don't allow you to change an
object's type.
* There is still a combinatorial explosion of types: each possible
combination needs to be explicitly coded.
* In strongly typed languages, like C++, there is still the problem
of having a common interface to all objects.
* In C++ you are bound to having the same compilation dependencies
because of the header files that need to be included to compose
types.
DynaMix solves all of these problems.
Comparison with Ruby mixins
If you're familiar with the mixins in Ruby, perhaps you will find a
lot of similarities between them and DynaMix.
Ruby's mixins can of course be used just like traits, however
extending existing objects with modules is also allowed via
Object::extend. This is almost exactly what DynaMix allows you to do.
Indeed, Ruby has been a great inspiration for this library.
Still, barring the differences that arise from C++ being a strongly
typed language, for a small amount of extra code DynaMix allows you
to do much more:
* Removing mixins: in Ruby, when you extend an object with a
module, all functions that both object and the module have are
permanently overridden in the object by their versions from the
module. DynaMix allows you to temporarily mutate the object and
then remove the mixin while restoring the object exactly to its
former self.
* Prioritize messages: in DynaMix you can set priorities to the
messages and thus when extending an object with a mixin, if a
message is implemented by the object with a higher priority, even
though it is also supplied by the mixin, in won't override the
one already in the object.
* Multicast messages: in Ruby (as in almost every other language
with a similar functionality) a method call leads to a piece of
code, defined for that method. DynaMix allows you to have
messages that are handled by more than one of mixins in an
object.
* Generated on Sun Nov 11 2018 10:50:42 for DynaMix by doxygen
1.8.11