DynaMix  1.3.7
A new take on polymorphism in C++
Debugging with DynaMix

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.

Watching dynamix::object instances

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:

Inspecting what mixins are in an object

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)

Inspecting a concrete mixin

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

Inspecting the messages in an object

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.

Stepping into messages

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.

Tracing/Logging information with 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.