Monday, January 7, 2013

Actor Companions

In the previous post about actors with Qt, we have already briefly discussed the concept of actor companions, but this post presents the general approach.

What is it Good For?

libcppa automatically converts threads to actors if needed (using thread-local storage), whenever a thread uses a message passing primitive such as send(). However, sometimes this conversion is too coarse-grained. Simply put, whenever several independent entities share a thread, but should be addressed individually as actors, we need actor companions. The classical example is a widget. The thread is owned by the event loop, which manages any number of widgets, why we cannot rely on libcppa's automatic thread conversion. If you need to support a library wich internally schedules its entities (perhaps based on green threads or coroutines) - or if you just want to learn more about libcppa, this post is for you.

Background

If you want to mix libcppa with an other framework, multiple inheritance is not an option. Usually, each framework manages its entities in an incompatible way. For example, Qt uses hierarchical object ownership by default. This obviously interferes with the reference counting strategy of libcppa. Even if the framework uses reference counts, you will end up having two reference counts in your object. Instead of using multiple inheritance, we use co-existing objects (companions), which serve as gateway. The companion implements the actor interface of libcppa but does not have a mailbox. It forwards all messages it receives to its parent.

Mixing in a Solution

Actor companions are enabled by the mixin actor_companion_mixin. The mixin provides the member functions actor_ptr as_actor(), void set_message_handler(...), and void handle_message(const message_pointer&). It also has one pure virtual member function: void new_message(message_pointer). This function must be implemented in a thread-safe way. The type message_pointer is a smart pointer holding a tuple along with meta data such as sender information. There is no need to interact with a message_pointer, as the message handler does everything necessary.

A Working Example

template<typename Base, int EventId = static_cast<int>(QEvent::User + 31337)>
class actor_widget_mixin : public actor_companion_mixin<Base> {

  typedef actor_companion_mixin<Base> super;

  typedef typename super::message_pointer message_pointer;

 public:

  template<typename... Args>
  actor_widget_mixin(Args&&... args) : super(std::forward<Args>(args)...) { }

  struct event_type : public QEvent {

    message_pointer msg;

    event_type(message_pointer mptr)
    : QEvent(static_cast<QEvent::Type>(EventId)), msg(std::move(mptr)) { }

  };

  virtual bool event(QEvent* event) {
    if (event->type() == static_cast<QEvent::Type>(EventId)) {
      auto ptr = dynamic_cast<event_type*>(event);
      if (ptr) {
        this->handle_message(ptr->msg);
        return true;
      }
    }
    return super::event(event);
  }

 protected:

  virtual void new_message(message_pointer ptr) {
    qApp->postEvent(this, new event_type(std::move(ptr)));
  }

};

The code shown above is the actual code from cppa/qtsupport/actor_widget_mixin.hpp. As you can see, it is Qt specific, but straightforward. Our "derived mixin" has no constructor arguments and only forwards all arguments to the base constructor (line 11).

The crucial point of this implementation is to convert libcppa messages to Qt events in the implementation of new_message (line 35). The runtime system of Qt will then call the event member function (line 22) later on from inside the event loop. All our implementation has to do is to unwrap the message_pointer and pass it to handle_message.

Once we have called handle_message, our message handler gets invoked – with self pointing to the companion. This way, we can use send and reply as usual in the handler. Even though the code is Qt specific, it should be straightforward to implement a comparable mixin to support your framework of choice.

No comments:

Post a Comment