DynaMix
1.3.7
A new take on polymorphism in C++
|
Unfortunately debugging code that uses DynaMix is a bit more challenging than debugging plain classes and method calls.
This is in part due to the macros that generate the message calling code, and also because – to save memory – the data within an object instance is not packed in easy to use and watch structures.
Perhaps one of the most common operations you'll want to have, will be to place a watch on a dynamix::object
instance, in order to see the mixins that comprise it.
If you're using Microsoft Visual Studio, you can install one of the debug visualizers, provided with the library. There are instructions on how to do so in appendix entry B of this book.
If you're not using Microsoft Visual Studio or you don't want to install the visualizers, we'll assume you're trying to inspect an object instance called obj
and proceed with the instructions for watching objects and their mixins:
The list of mixins in an object can be found in a std::vector
in its type data. The field from our object's perspective is obj._type_info._compact_mixins
.
The length of this vector is the number of mixins. Each element of the vector is of type mixin_type_info
and has a field const char* name
with the mixin name (which is a stringized version of its type)
In order to see one of the mixins in an object, you have to examine its mixin data. The field in the object is obj._mixin_data
. This is a plain array of mixin_data_in_object
. Its length is equal to the mixin count and each element corresponds to an element of the aforementioned _compact_mixins
vector in the object type data. Each element has a field, called _mixin
. This is a pointer to the actual mixin instance.
So, as an example let's assume obj
is composed of the mixins opengl_rendering
, mesh
, and transform
. This means that obj._type_info._compact_mixins
will be a vector of three elements, and the names of those elements will be "opengl_rendering"
, "mesh"
, and "transform"
in some (but not necessarily this) order.
Let's assume you want to inspect the mesh mixin within our object. Let's say it's with index N in the _compact_mixins
vector. The value you'll need to watch then will be: (mesh*)obj._mixin_data[N]._mixin
The most difficult thing to see from an object is a list of the messages it implements. There is no compact structure that you can check.
The only way to do it is to check the call table of the type and compare it with the data in the domain.
So, first you need to inspect the plain array obj._type._call_table
. Its size is the maximum number of messages allowed in the system. Some of its members are going to ne none-null pointers. Those are the messages that are implemented by the objects.
Unfortunately this information cannot help you see the message names, but only their ids. To see the exact name you need to add a domain instance to the watches. To do so, you'll have to have a line in your code like this one:
const dynamix::internal::domain& bm_domain = dynamix::internal::domain::instance();
From this domain instance you should be able to check its member bm_domain._messages
. This again is an array of size DYNAMIX_MAX_MESSAGES
. Now you can use indexes of the non-null messages from obj._type._call_table
in bm_domain._messages
to see the message names. For example if our object implements message N, you can see its name in bm_domain._messages[N]->name
.
Unfortunately due to the fact the the message caller functions are generated by macros, to step into a message call is a slightly annoying operation when debugging code that uses the library.
If you know which method in a mixin class will be called for a message it is a good idea to run to its first line (or place a breakpoint there) in order to skip the macro steps.
If you don't know, you will have to step into the macro. The macro generates a one-liner function so you need to step it macro once to get to the make_call
function in the appropriate caller class (msg_unicast
or msg_multicast
). There, towards the end of the function, you'll see a call to func
(possibly within a loop for multicasts). You need to step into it to go the appropriate instantiated proxy msg_caller::caller
. The last line there is the call to the underlying function of your code. You need to step into it as well to go to your code.
The dynamix::object
class has two methods that have output parameters of type std::vector<const char*>
– get_message_names
and get_mixin_names
.
They will fill those vectors with the names of the messages the object implements and the mixins it has, respectively.