PDA

View Full Version : Combining Event Loops



Gimpyfuzznut
27th June 2009, 03:08
I'm building a Qt client for the open-source client/server 4X strategy game Thousand Parsec. I'm however stuck at a dead end. Basically, the client interfaces with the server via a C++ protocol layer that facilitates client/server communication. The protocol's documentation is available here (http://www.thousandparsec.net/tp/dev/documents/libtpproto-cpp/html/index.html).

Now my problem is that the protocol requires you to create a subclass of the virtual EventLoop class (link (http://www.thousandparsec.net/tp/dev/documents/libtpproto-cpp/html/classTPProto_1_1EventLoop.html)) in your client. There is an example SimpleEventLoop used for console clients on the same link. I'm having difficulty figuring out how I can design my own event loop subclass that handles the protocol's events while hooking into the Qt application at the same time. My research has lead me to believe QAbstractEventDispatcher is the Qt class I want to use but the documentation seems pretty slim and I'm not exactly sure how I would go about doing this.

Does anyone else have experience linking external event loops with a Qt application? I've also found this (http://doc.trolltech.com/solutions/4/qtmotifextension/qtmotif-overview.html)example on the Qt page but it wasn't very helpful - or at least I didn't really understand it.

Thanks!

caduel
27th June 2009, 11:43
you can go two directions afaik:

1) hook up the eventloop with Qt's - if Qt is built against the glib event loop, see e.g. http://labs.trolltech.com/blogs/2006/02/24/qt-and-glib/

2) avoid the external event loop: from looking at the url you provided, it might work out to write a Qt-based subclass of the TPProto::EventLoop, somehow along the following (untried) lines



class QtTPProtoEventLoop : public QObject, public TPProto::EventLoop
{
Q_OBJECT

struct sock_info
{
sock_info(TPProto::TPSocket *sock, QSocketNotifier *sn)
: sock(sock), sn(sn) {}
TPProto::TPSocket *sock;
QSocketNotifier *sn;
};
QHash<int,sock_info> read_notifiers_;
QHash<int,sock_info> write_notifiers_;

public:
void listenForSocketRead (TPProto::TPSocket *sock)
{
int fd = sock->getFileDescriptor();
QSocketNotifier sn = new QSocketNotifier(fd, QSocketNotifier::Read, this);
notifiers_[fd] = sock_info(sock, sn);
connect(sn, SIGNAL(activated(int)), SLOT(activated_read(int)));
}
void listenForSocketWrite (TPProto::TPSocket *sock)
{
int fd = sock->getFileDescriptor();
if (!write_notifiers_.contains(fd))
{
QSocketNotifier *sn = new QSocketNotifier(fd, QSocketNotifier::Write, this);
notifiers_[fd] = sock_info(sock, sn);
}
// (re-) connect "singleshot" see below
sock_info si = write_notifiers_[fd];
connect(si.sn, SIGNAL(activated(int)), SLOT(activated_read(int)));
}
TPProto::TimerConnection setTimer(uint32_t interval, const TPProto::TimerSignal::slot_type &callback)
{
// store the callback
// setup a QTimer (singleshot after interval)
// have a slot call the callback
// no idea what a TimerConnection is

}

public Q_SLOTS:
void activated_read(int fd)
{
read_notifiers_[fd].sock->readyToRead();
}
void activated_write(int fd)
{
// tpproto docs say: single shot: disconnect
sock_info si = write_notifiers_[fd];
si.sn->disconnect(this);
si.sock->readyToWrite();
}

};


(Take care: any number of typos and copy/paste errors possible...)

Tell us if you manage to get it done along this way (or another).

HTH

Gimpyfuzznut
27th June 2009, 20:19
Wow! Thanks for the amazingly detailed reply. I think your use of SocketNotifiers is the right idea. I'm going to try to build on what you did there and see how it works.

One of the Parsec mentors also linked to me the avahi project which contains an Qt implementation of their eventloop allowing Qt applications to easily plug into the framework. The ideas are similar and they do it for GTK as well. Check out http://avahi.org/download/doxygen/ and look for avahi-qt.

Thanks again caduel - I'll let you know how it works out.

Gimpyfuzznut
9th July 2009, 08:26
Hello again,

So I've tried to put this together but I'm still having some trouble. I took the advice you gave me in the private message as well Caduel but I still can't get a working compile. Here's my code. It is more or less similar to the suggestions you made with some twists.

It basically throws up a huge piles of errors about the QHash objects. I think the way I'm using them is wrong. I'm not quite sure but I dont think it likes that I'm trying to create a QHash<QTimer* timer, boost::signal> object. I also tried the typedef suggestions you made Caduel but they didn't seem to work either. They created similiar errors. Any ideas? Anything you see wrong with this code?

For reference these types used throughout the class are:
typedef boost::signal<void (void)> TimerSignal;
typedef boost::signals::connection TimerConnection;

HEADER


#ifndef PARSEKEVENTLOOP_H
#define PARSEKEVENTLOOP_H

#include <QObject>
#include <QTimer>
#include <QHash>
#include <QSocketNotifier>
#include <tpproto/eventloop.h>

//#include <boost/function.hpp>
//typedef TPProto::TimerSignal::slot_type Callback;
//typedef boost::function<void(void)> Callback;

class ParsekEventLoop : public QObject, public TPProto::EventLoop
{
Q_OBJECT

public:
ParsekEventLoop();
virtual ~ParsekEventLoop();
virtual void listenForSocketRead(TPProto::TPSocket *sock);
virtual void listenForSocketWrite(TPProto::TPSocket *sock);
virtual TPProto::TimerConnection setTimer(uint32_t interval, const TPProto::TimerSignal::slot_type &callback);
public Q_SLOTS:
void activated_read(int fd);
void activated_write(int fd);
void timeout();
private:
//TIMERS AND CALLBACK STUFF
QHash<QTimer*,TPProto::TimerSignal> timers;

//SOCKET STUFF
struct ParsekSocket //covert to dedicated class later??
{
ParsekSocket(TPProto::TPSocket *socket, QSocketNotifier *notifier) : socket(socket), notifier(notifier) {}
TPProto::TPSocket *socket;
QSocketNotifier *notifier;
};
QHash<int,ParsekSocket> readset;
QHash<int,ParsekSocket> writeset;

};
#endif




#include <tpproto/tpsocket.h>
#include <parsekeventloop.h>

ParsekEventLoop::ParsekEventLoop()
{
//initialize QHash objects?
}

ParsekEventLoop::~ParsekEventLoop()
{
readset.clear();
writeset.clear();
timers.clear();
}

void ParsekEventLoop::listenForSocketRead(TPProto::TPSo cket *sock)
{
int fd = sock->getFileDescriptor();
QSocketNotifier *notifier = new QSocketNotifier(fd, QSocketNotifier::Read, this);
//readset[fd] = ParsekSocket(sock,notifier);
readset.insert(fd,ParsekSocket(sock,notifier));
connect(notifier,SIGNAL(activated(int)),SLOT(activ ated_read(int)));
}

void ParsekEventLoop::listenForSocketWrite(TPProto::TPS ocket *sock)
{
int fd = sock->getFileDescriptor();
if (!writeset.contains(fd))
{
QSocketNotifier *notifier = new QSocketNotifier(fd, QSocketNotifier::Write, this);
//writeset[fd] = ParsekSocket(sock,notifier);
writeset.insert(fd,ParsekSocket(sock,notifier));
}
connect(writeset[fd].notifier, SIGNAL(activated(int)), SLOT(activated_write(int)));
}


TPProto::TimerConnection ParsekEventLoop::setTimer(uint32_t interval, const TPProto::TimerSignal::slot_type &callback)
{
TPProto::TimerSignal *signal = new TPProto::TimerSignal();
TPProto::TimerConnection tc = signal->connect(callback);

QTimer *timer = new QTimer(this);
//timers[timer] = signal;
timers.insert(timer,signal);
connect(timer,SIGNAL(timeout()),SLOT(timeout()));
timer->setSingleShot(true);
timer->start(interval*1000);

return tc;
}

void ParsekEventLoop::activated_read(int fd)
{
readset[fd].socket->readyToRead();
}

void ParsekEventLoop::activated_write(int fd)
{
writeset[fd].notifier->disconnect(this);
writeset[fd].socket->readyToSend();
}

void ParsekEventLoop::timeout()
{
QTimer *timeout = static_cast<QTimer*>(sender());
TPProto::TimerSignal *signal = new TPProto::TimerSignal();
signal = timers[timeout];
(*signal)();
}


BUILD ERRORS


/home/marwan/Documents/parsek/src/parsekeventloop.cpp: In member function ‘virtual TPProto::TimerConnection ParsekEventLoop::setTimer(uint32_t, const boost::slot<boost::function<void ()()> >&)’:
/home/marwan/Documents/parsek/src/parsekeventloop.cpp:50: error: no matching function for call to ‘QHash<QTimer*, boost::signal<void ()(), boost::last_value<void>, int, std::less<int>, boost::function<void ()()> > >::insert(QTimer*&, TPProto::TimerSignal*&)’
/usr/include/QtCore/qhash.h:730: note: candidates are: typename QHash<Key, T>::iterator QHash<Key, T>::insert(const Key&, const T&) [with Key = QTimer*, T = boost::signal<void ()(), boost::last_value<void>, int, std::less<int>, boost::function<void ()()> >]
/home/marwan/Documents/parsek/src/parsekeventloop.cpp: In member function ‘void ParsekEventLoop::timeout()’:
/home/marwan/Documents/parsek/src/parsekeventloop.cpp:73: error: cannot convert ‘boost::signal<void ()(), boost::last_value<void>, int, std::less<int>, boost::function<void ()()> >’ to ‘TPProto::TimerSignal*’ in assignment
/usr/include/QtCore/qhash.h: In member function ‘T& QHash<Key, T>::operator[](const Key&) [with Key = int, T = ParsekEventLoop::ParsekSocket]’:
/home/marwan/Documents/parsek/src/parsekeventloop.cpp:39: instantiated from here
/usr/include/QtCore/qhash.h:723: error: no matching function for call to ‘ParsekEventLoop::ParsekSocket::ParsekSocket ()’
/home/marwan/Documents/parsek/src/parsekeventloop.h:54: note: candidates are: ParsekEventLoop::ParsekSocket::ParsekSocket(TPProt o::TPSocket*, QSocketNotifier*)
/home/marwan/Documents/parsek/src/parsekeventloop.h:53: note: ParsekEventLoop::ParsekSocket::ParsekSocket(const ParsekEventLoop::ParsekSocket&)
/usr/include/boost/noncopyable.hpp: In copy constructor ‘boost::signals::detail::signal_base::signal _base(const boost::signals::detail::signal_base&)’:
/usr/include/boost/signals/detail/signal_base.hpp:119: instantiated from ‘QHashNode<Key, T>::QHashNode(const Key&, const T&) [with Key = QTimer*, T = boost::signal<void ()(), boost::last_value<void>, int, std::less<int>, boost::function<void ()()> >]’
/usr/include/QtCore/qhash.h:515: instantiated from ‘typename QHash<Key, T>::Node* QHash<Key, T>::createNode(uint, const Key&, const T&, QHashNode<Key, T>**) [with Key = QTimer*, T = boost::signal<void ()(), boost::last_value<void>, int, std::less<int>, boost::function<void ()()> >]’
/usr/include/QtCore/qhash.h:723: instantiated from ‘T& QHash<Key, T>::operator[](const Key&) [with Key = QTimer*, T = boost::signal<void ()(), boost::last_value<void>, int, std::less<int>, boost::function<void ()()> >]’
/home/marwan/Documents/parsek/src/parsekeventloop.cpp:73: instantiated from here
/usr/include/boost/noncopyable.hpp:27: error: ‘boost::noncopyable_::noncopyable::noncopyab le(const boost::noncopyable_::noncopyable&)’ is private
/usr/include/boost/signals/detail/signal_base.hpp:119: error: within this context
/usr/include/boost/signals/signal_template.hpp: In copy constructor ‘boost::signal0<void, boost::last_value<void>, int, std::less<int>, boost::function<void ()()> >::signal0(const boost::signal0<void, boost::last_value<void>, int, std::less<int>, boost::function<void ()()> >&)’:
/usr/include/boost/signals/signal_template.hpp:142: instantiated from ‘QHashNode<Key, T>::QHashNode(const Key&, const T&) [with Key = QTimer*, T = boost::signal<void ()(), boost::last_value<void>, int, std::less<int>, boost::function<void ()()> >]’
/usr/include/QtCore/qhash.h:515: instantiated from ‘typename QHash<Key, T>::Node* QHash<Key, T>::createNode(uint, const Key&, const T&, QHashNode<Key, T>**) [with Key = QTimer*, T = boost::signal<void ()(), boost::last_value<void>, int, std::less<int>, boost::function<void ()()> >]’
/usr/include/QtCore/qhash.h:723: instantiated from ‘T& QHash<Key, T>::operator[](const Key&) [with Key = QTimer*, T = boost::signal<void ()(), boost::last_value<void>, int, std::less<int>, boost::function<void ()()> >]’
/home/marwan/Documents/parsek/src/parsekeventloop.cpp:73: instantiated from here
/usr/include/boost/signals/signal_template.hpp:142: note: synthesized method ‘boost::signals::detail::signal_base::signal _base(const boost::signals::detail::signal_base&)’ first required here
/usr/include/boost/signal.hpp: In copy constructor ‘boost::signal<void ()(), boost::last_value<void>, int, std::less<int>, boost::function<void ()()> >::signal(const boost::signal<void ()(), boost::last_value<void>, int, std::less<int>, boost::function<void ()()> >&)’:
/usr/include/boost/signal.hpp:335: note: synthesized method ‘boost::signal0<void, boost::last_value<void>, int, std::less<int>, boost::function<void ()()> >::signal0(const boost::signal0<void, boost::last_value<void>, int, std::less<int>, boost::function<void ()()> >&)’ first required here
/usr/include/QtCore/qhash.h: In constructor ‘QHashNode<Key, T>::QHashNode(const Key&, const T&) [with Key = QTimer*, T = boost::signal<void ()(), boost::last_value<void>, int, std::less<int>, boost::function<void ()()> >]’:
/usr/include/QtCore/qhash.h:213: note: synthesized method ‘boost::signal<void ()(), boost::last_value<void>, int, std::less<int>, boost::function<void ()()> >::signal(const boost::signal<void ()(), boost::last_value<void>, int, std::less<int>, boost::function<void ()()> >&)’ first required here

caduel
9th July 2009, 10:37
ok, ... to get that working, you need

i) add

ParsekSocket() : socket(0), notifier(0) {}
to your ParsekSocket class (only classes with a default constructor can be put in containers).

ii) be sure to have the line

CONFIG += no_keywords // for Boost.Signals
and use the macros Q_SLOTS etc (you did that)

iii) yes, error messages for templated boost code are always lots of fun...
I have attached a compilable version of the code.
(Boost.Signals are not copyable, so you need to store them by pointer (don't forget the cleanup...). You do indeed need to create a TimerSignal in order to return a TimerConnection. You need to find out if/when you can release/destroy that signal.)

Please note:
* I have not cared about "cleanup" of no longer needed Timer signals
(maybe you need to iterate now and then over the container and remove (and delete) unconnected signals...)
Be sure to read up on the lifetime of those objects, maybe ask in the thousandparsek forum regarding that
* in the class's destructor you need to cleanup all that too

iv) to your questions:
* no, hashes do not need to be initialized (they are empty by default)
* it is not necessary to clear() containers in a destructor: the containers are deleted anyway
* it IS necessary to release the "pointed to" objects: if you store pointers the objects pointed to are not deleted (and their destructors not called)
* and, do not "just" add a destructor to your ParsekSocket class, you'd get interesting effects (when accessing the container)... see http://www.artima.com/cppsource/bigtwo.html
* an option would be to use boost::shared_ptr in such cases

HTH

Gimpyfuzznut
10th July 2009, 07:08
i) Doh - that was a silly thing to miss.

ii) Yes my initial problems with Boost lead me to find the compatibility problems with Boost and Qt together. As you said, the CONFIG += no_keywords is required in the qmake configuration file.

Just for reference, since I am using Cmake, the way to do this is to add
add_definitions(-DQT_NO_KEYWORDS) to your CMakelists.txt. Like you said, you need to use the uppercase Qmacros afterwards.

iii+iv) I'll see how to best handle the cleanup. First thing is that the signal can be removed after being called so I added
timers.remove(timeout);
to the timeout() function where (*signal)() is called. I believe this should remove the need to later clean up the signals from the timers hash. Thanks for the tips and the link regarding proper destructor cleanup. I think I need to pull out my copy of Meyer's Effective C++ and re-educate myself about a couple of topics.

v) Another thing that was required of me to get a proper compile was to add

#define BOOST_NO_EXCEPTIONS
#include <boost/throw_exception.hpp>
void boost::throw_exception(std::exception const & e){
//do nothing
}
Without this Boost was complaining about not finding a proper exception handler in the case that the call to (*signal)() is empty, or something along those lines. Adding the above code remedied the situation so if anyone sees an error similar to one below, try something similar.

undefined reference to `boost::throw_exception(std::exception const&)'