Aldebaran documentation What's new in NAOqi 2.4.3?

Registering types in the type system

Introduction

For types to be available in the type system and allow them to be type-erased, sent over the network or through signals, each one of them needs to be registered in the type system.

Most of these registration can be done through macros. It is very important that you call macros made to be used in .cpp only in .cpp files, these macros should be processed in a single translation unit.

All registration classes and macros are in:

#include <qi/anyobject.hpp>

Structures

To register a structure, you need to call the macro QI_TYPE_STRUCT() in the corresponding .hpp file.

// point.hpp

namespace Graph {
  struct Point {
    int x;
    int y;
  };
}

QI_TYPE_STRUCT(Graph::Point, x, y);

Or you can do the register in the .cpp:

// point.cpp

// call this outside of any namespace
QI_TYPE_STRUCT_REGISTER(Graph::Point, x, y);

Using PIMPL in structures

Sometime, you have a structure of data, but you don’t want the user to access it directly because you want to constrain values or to updates multiple values at the same time so that the structure stays coherent.

class Vector {
  public:
    void setValues(int x, int y)
    {
      _x = x;
      _y = y;
      _length = std::sqrt(x*x + y*y);
    }
    float length() { return _length; }
    // ...

  private:
    int _x;
    int _y;
    float _length;
};

// How can I register this??

You can’t register this struct because _x, _y and _length are private and you don’t want to make them public because the user could change _x and _y without changing _length.

To register that to the type system, you need to use pimpl:

// vector.hpp

class Vector {
  public:
    Vector();
    // you need a copy constructor because qitype makes copies
    Vector(const Vector& other);
    void setValues(int x, int y) { /* ... */ }
    float length() { /* ... */ }

  private:
    boost::scoped_ptr<struct VectorPrivate> _p;

    // you need this for later
    friend struct VectorPrivate* vectorPrivateAccess(Vector*);
};

// vector.cpp

struct VectorPrivate {
  int _x;
  int _y;
  float _length;
};

Vector::Vector() : _p(new VectorPrivate) {}
Vector::Vector(const Vector& other) : _p(new VectorPrivate(*other._p)) {}

Then you can register the private part of the struct and tell qitype how to access it:

VectorPrivate* vectorPrivateAccess(Vector* vector) {
  return vector->_p;
}

// call these outside of any namespace
QI_TYPE_STRUCT_REGISTER(VectorPrivate, _x, _y, _length);

QI_TYPE_STRUCT_BOUNCE_REGISTER(Vector, VectorPrivate, vectorPrivateAccess);

Every time you transfer a Vector, qimessaging will also transfer its private part and no one can access it without using the accessors.

Enums

Enums are easy to register:

// color.hpp

namespace Graph {
  enum Color {
    Red,
    Green,
    Blue
  };
}

// call this outside of any namespace
QI_TYPE_ENUM_REGISTER(Graph::Color);

Classes

Using registration helper

Classes can only be registered in .cpp files:

// drawer.hpp

namespace Graph {
  class Drawer {
    public:
      bool draw(const Point& p, Color color) {
        std::cout << "Drawing point" << std::endl;
        drawDone(p);
        return true;
      }

      qi::Signal<Point> drawDone;

      qi::Property<Point> origin;
  };
}

// drawer.cpp

namespace Graph {
  // call this from inside the namespace of the class
  QI_REGISTER_OBJECT(Drawer, draw, drawDone, origin);
}

There are two threading models for classes. Drawer is registered as single threaded in the above example. When doing multiple calls of its methods in parallel, they will be sequenced. If you need your object to support multithreaded calls, use the MT macro:

// drawer.cpp

namespace Graph {
  // call this from inside the namespace of the class
  QI_REGISTER_MT_OBJECT(Drawer, draw, drawDone, origin);
}

Doing it manually

The helper won’t always allow you to register a class, for example when you have method overloading in your class. In these cases, you need to register your type manually with qi::ObjectTypeBuilder so that you can specify the signature of the function.

// drawer.hpp

namespace Graph {
  class Drawer {
    public:
      bool init() {
        std::cout << "Initializing drawer" << std::endl;
      }
      void draw(const Point& p, Color color) {
        std::cout << "Drawing point with color" << std::endl;
      }
      void draw(const Point& p) {
        std::cout << "Drawing point" << std::endl;
      }

      qi::Signal<void> objectDrawn;
      qi::Property<int> objectCount;
  };
}

// drawer.cpp

namespace Graph {
  // this won't work because we can't differentiate the two draw methods
  //QI_REGISTER_OBJECT(Drawer, draw, draw);
}

namespace Graph {
  static bool _qiregisterDrawer() {
    ::qi::ObjectTypeBuilder<Drawer> builder;
    // use static_cast to remove ambiguity between overloads
    builder.advertiseMethod("draw",
        static_cast<void (Drawer::*)(const Point&, Color)>(&Drawer::draw));
    builder.advertiseMethod("draw",
        static_cast<void (Drawer::*)(const Point&)>(&Drawer::draw));
    // no need to static_cast if there is no overload
    builder.advertiseMethod("init", &Drawer::init);
    builder.advertiseSignal("objectDrawn", &Drawer::objectDrawn);
    builder.advertiseProperty("objectCount", &Drawer::objectCount);
    builder.registerType();
    return true;
  }
  static bool __qi_registrationDrawer = _qiregisterDrawer();
}

More on Threading Model

If you need your object to be multithreaded, set it on your builder:

builder.setThreadingModel(qi::ObjectThreadingModel_MultiThread);

It is also possible to have a single-threaded class on which only some methods are multithreaded:

builder.advertiseMethod("multiThreadedMethod", &Drawer::multiThreadedMethod,
    qi::MetaCallType_Queued);

If you also want non-type-erased calls to be single-threaded (on async, signals, etc), you must inherit from Actor. See qi::Actor and qi::Strand.

Factories

Sometimes, you may need to create objects from a type-erased context. Registering classes is not enough to instantiate them through the type system. For that, you need to register factories in the .cpp file. To register a factory which will just call the default constructor, use:

// drawer.cpp

// you can put that in a namespace
QI_REGISTER_OBJECT_FACTORY_CONSTRUCTOR(Graph::Drawer);

This will create a factory named "Graph::Drawer". If you want a different name, you can use:

QI_REGISTER_OBJECT_FACTORY_CONSTRUCTOR_FOR("MyDrawer", Graph::Drawer);

Note

Factories are unique. You can’t have two factories with the same name!

If you want to pass arguments to the constructor, you need to specify the signature to the macro:

// drawer.hpp

class Drawer {
  Drawer(int width, int height) {}
}

// drawer.cpp

QI_REGISTER_OBJECT_FACTORY_CONSTRUCTOR(Drawer, int, int);