DUECA/DUSIME
DUECA code generator

The C++ objects that are sent over event or stream channels need to obey certain rules so that they can be packed and unpacked.

A code generator (dueca-codegen) is available to produce this code from simple specification input. A code generator file may contain:

There is also an option to expand your generated class with methods that you have programmed in C++. I must stress here that these must be methods (functions) only, not data, since data you add yourself will not be packed by the code generator. For example, you might want to send over quaternions (as a four-element dueca::fixvector), and add the operations for the quaternions to the class. You must supply a .hxx file, which is going to be included in the class definition, and a .cxx file, which is added to the body. Example:

(Object MyClass (IncludeFile MyClassExtra)
                (MyEnum num (Default One))
                (double f (Default 2.0f))
                )

The IncludeFile keyword indicates that files need to be added. You can add these files, MyClassExtra.cxx and MyClassExtra.hxx to the comm-objects directory. The alternative would be to implement your own transportable classes, which is a very error-prone habit, and breaks when there is a change to the code generator. Hopefully the default values and the additional methods provide enough flexibility to avoid this habit.

Note that the MyClassExtra.hxx gets included in the body of the generated class (in the MyClass.hxx file), and it gets included last. MyClassExtra.cxx gets included before other parts in the MyClass.cxx file. You may also use MyClassExtra.cxx to override a number of functions that are produced by default. A number of preprocessor defines are available to "switch off" these default implementations. You can use:

// override the print function
#define __CUSTOM_FUNCTION_PRINT
// override the assignment operator
#define __CUSTOM_OPERATOR_ASSIGN
// override the equality test operator
#define __CUSTOM_OPERATOR_EQUAL

// if you defined an enum, e.g. MyEnum, you can also add:
#define __CUSTOM_GETSTRING_MyEnum
// or
#define __CUSTOM_READFROMSTRING_MyEnum

If you override a function with a custom function, your code may of course break if the DUECA code generator is updated to generate a newer version of its code. To guard against that, you must also add a define that acknowledges the code generation version you are writing custom code for. So somewhere in your MyClassExtra.cxx, define:

#define __CUSTOM_COMPATLEVEL_111

If the code generation version changes in the future, you should check whether your custom code is still compatible, adapt if needed, and define a new acknowledgement. You will get a compiler warning when it is time to do so.

Another possibility is the addition of some c++ code to the default constructor (the one without arguments) or the full constructor (the one with arguments). This gives you the possiblity to initialize the arrays that cannot be initialized by default value arguments.

(Object MyClass
        (IncludeFile MyClassExtra)
        (ConstructorCode
         "std::cout << \"making a MyClass object\" << std::endl;" )
                (MyEnum num (Default One))
                (double f (Default 2.0f))
                (dueca::fixvector<10,double> r))

A third way to extend funcionality of the generated class is to have it inherit from a parent class. Example:

(Object MyClass (Inherits MyParentClass)
                (MyEnum num (Default One))
                (double f (Default 2.0f))
                (double r 10))

The Inherits keyword here indicates that MyClass should derive from MyParentClass. You need to define the MyParentClass as a type, and provide the code to include the appropriate file.

Take care that the parent that you inherit from is also a full-fledged packable and unpackable object. It works best if you also created the parent with the code generator.

Sometimes it is useful to be able to send an object over a channel and* be able to specify it as a complete object in the Scheme or Python script that you use to define the simulation. In that case you can use a ScriptCreatable option:

(Object MyClass (Option ScriptCreatable)
                (double f (Default 2.0f))
                (double r 10))

The code generator adds a templated class,

ScriptCreatableDataHolder<MyClass>

to the generated code. That class is creatable from your scheme script, given that the data members are simple enough to be specified in the script language (generally, simple floats, integers, strings and vectors of these). You can create it with the "lowercased" name of your datatype, and "set-" commands to set the data. Note that you are limited to the datatypes that you can set in the scheme script. So in the script you will be able to say:

(define x (make-my-class
           'set-f 5.0
           'set-f 9.0))
 ;; .... and later ...

   (make-module 'my-module "" sim-priority
                'set-my-data x)

Assuming you linked "set-my-data" to a function for your class in the parameter table, and you added this function to your class:

bool MyModule::setMyData(ScriptCreatable& d, bool in)
{
  if (in == true) {
    ScriptCreatableDataHolder<MyClass> *ptrx =
      dynamic_cast<ScriptCreatableDataHolder<ScriptCreatableTest>*>(&d);

    // check the pointer, if the cast failed, then the user supplied
    // the wrong type in the script
    if (!ptrx) {
      W_MOD("You supplied the wrong type in the script");
      return false;
    }

    // now you can copy the data
    localdata = ptrx->data();
  }
  return true;
}

When you use Python scripting, the case of the class name will be kept for the Python version.

If you use a combination of these above options, use the following order: Inherits, Option, IncludeFile, ConstructorCode, FullArgsConstructorCode, AddToHeader. You can not combine Inherits with the ScriptCreatable option.

There used to be an option of generating different kinds of code:

(Stream MyClassA (MyEnum num))
(EventAndStream MyClassB (double f))
(Event MyClassC (double f))

The current code generator generates the same code for all these types, and they can be used for all types of channel.

External use of the generated code

You might want to communicate with DUECA from another program, and use for example the UDPWriter and UDPReader modules to transmit this data. In that case it would be handy to use the generated code objects also in a non-dueca program. To do this, there are two preprocessor defines that select how the connection to DUECA is compiled for a DCO object:

#define __DCO_STANDALONE
#define __DCO_NOPACK

The first one generates standalone code, which does not need to be connected to a DUECA executable. This code does have packing and unpacking routines for the generated class, and you will need to link the AmorphStore and AmorphReStore (a set of classes in DUECA for packing and unpacking) code with your program. This enables you to pack data into net representation and send it over, or unpack.

If you also do not need packing and unpacking, also define __DCO_NOPACK, and the code compiles to a fully standalone class.

Comments in the generated code

You can add comments to the .dco file by starting the comment with a ';'. The semicolon and anything it, until the end of the line is considered a comment. The code generator tries to be smart about these comments, and transport them to the generated code. The following comments are preserved:

  1. Comment lines in front of the definition of an enumerated type. As the enumerated type becomes element of the class definition, the corresponding comment is put in front of the "enum" definition.
  2. Comment lines in front of an element of an enumerated type. These are copied into the c++ file as belonging to that element.
  3. Comment lines in front of the definition of a new class (Stream, Event or EventAndStream). These are copied into the c++ file in front of that class definition.
  4. Comment lines in front of the definition of elements in a class. These are put in front of the corresponding elements in the class definition.

All comments in the header file are compatible with doxygen documentation generation. So a "make doc" command in the directory with communication objects produces pretty html pages, with the comments you originally put in the .dco files, or, if you did not put comments in, generally silly comments about your class and elements in it.

Comments in front of type definitions or array size definitions are not copied into the c++ file.

Comment header

With a Header statement, you can add a header to the generated code. The header is a multi-line string, which is copied verbatim into the standard header for the generated code, here is a small example.

(Header "
original item : CycleCounter.dco
made by : Rene' van Paassen
date : 200612
description : Repeating, possibly overflowing counter
for message cycles
copyright : (c) 2020 TUDelft-AE-C&S - Rene van Paassen")

Enumerated types only

Sometimes you might want to share your enumerated types across multiple code generated objects. By default, the code generator will add the enumerated type as a type for the class you generate. But you can also let the code generator produce stand-alone code for the enumerated type, by defining an enumerator. Given a MyEnum type you defined previously, you can then write:

(Enumerator MyEnum)

This will produce a MyEnum.hxx and MyEnum.cxx file that has the code to pack and unpack the enum. This can then be included as an external enum when you want to generate a DCO object.

Additional options

The code generator is extensible with several options, which add or modify the code generation when used, to be selected with the Option keyword. Currently two different additions are available:

Generating the code for your dco file

To help you create nicely formatted dco files, there is a little script, new-dco, that creates a dco file for you.

Example

Finally, here is an example of the generated code. You can use it to create a simple template to edit, or let it enter a lot of the code for you already. A little example:

new-dco object --name MyData --description "Test object" --type float \
    --member float a --default 0.0f