3

In Qt you can connect two objects by setting up a signal in one object a slot in the other and then connecting them using "connect()".

Now by emitting a signal in one object it is sent to the second object. I have a system that takes user inputs and if there are too many user inputs I want my "queue" to fill up and not accept any more inputs.

I could implement a reply mechanism on the receiving object, but I want to know if we can make a queue size of (for example) 1. So only one message will be handled, and any new emittions are simply chucked away until the "pipe" is has space.

Is this possible in Qt?

In my case the two objects are in different threads and have a queued connection (if that makes any difference)...

MainWindow::MainWindow()
{
    // Make object 1, stick it in another thread
    MyObjType1 *obj1 = new MyObjType1();
    anotherThread = new QThread; // anotherThread  is type QThread *
    obj1->moveToThread(anotherThread);
    anotherThread->start();

    // Make object 2, connect a signal to obj1
    MyObjType2 *obj2 = new MyObjType2();
    connect(obj2, SIGNAL(obj2Signal(int), obj1, SLOT(obj1Slot(int), Qt::QueuedConnection);

    // Hammer obj1 with signals to its queue
    for (int i = 0; i < 100000; i++)
    {
        emit obj2->obj2Signal(i);
    }
}

So the idea would be that obj1 gets lots of signals, it handles the first one, and somehow throws the others away until it finishes, then takes on the next one that is emitted.

Kuba hasn't forgotten Monica
  • 95,931
  • 16
  • 151
  • 313
code_fodder
  • 15,263
  • 17
  • 90
  • 167
  • The behavior you likely want is to retain the most recent value, not the oldest one. So, whenever `obj1` thread's event loop sends the events to `obj1`, the most recent call is is executed. That's how Qt compresses its own compressible gui events such as widget resize and move events. The way you wanted it leads to pretty bad user experience and is a common mistake. – Kuba hasn't forgotten Monica Jan 22 '14 at 20:38
  • I think I worded it in-correctly, I actually wanted it the way you are saying... so we throw away all events until we are ready for a new one and take the next "new/incomming" event :) – code_fodder Jan 23 '14 at 07:20

1 Answers1

1

With a queued connection, for each slot connected to a signal there is a QMetaCallEvent posted to the connected slot object's event queue. The events are delivered when the event loop runs. The code below outputs:

about to emit
done emitting
in aSlot() 
class MyObject {
  Q_OBJECT
  Q_SIGNAL void aSignal();
  Q_SLOT void aSlot() { qDebug() << "in aSlot()"; }
public:
  MyObject(Qt::ConnectionType conn = Qt::AutoConnection) {
    // QObject::connect() defaults the connection type to Qt::AutoConnection,
    // we merely duplicate this behavior.
    connect(this, SIGNAL(aSignal()), SLOT(aSlot()), conn);
    qDebug() << "about to emit";
    emit aSignal();
    qDebug() << "done emitting";
  }
};
int main(int argc, char ** argv) {
  QCoreApplication app(argc, argv);
  MyObject obj(Qt::QueuedConnection);
  QCoreApplication::processEvents();
  return 0;
}

The problem can now be reformulated to: How to force removal of duplicate QMetaCallEvent events from the event queue? This is known as event compression. I have already provided a canonical answer to that question. For user input, you want the most recently emitted signal to be retained, not the oldest one, but I've implemented both behaviors in the answer code.

Using the code from my answer, your example merely needs the following in the main() function:

int main(int argc, char ** argv) {
    CompressorApplication<QApplication> app(argc, argv);
    app.addCompressedSignal(MyObjType2::staticMetaObject.method(MyObjType2::staticMetaObject.indexOfSignal("obj2Signal(int)")));
    MainWindow w;
    w.show();
    return app.exec();
}

Note: If one were connecting objects with the default Qt::AutoConnection and the objects were in the same thread, then the concept of a queue wouldn't apply at all. The slot is called before the signal function returns and nothing needs to be queued! The code below will output:

about to emit
in aSlot()
done emitting
// MyObject as above
int main(int argc, char ** argv) {
  QCoreApplication app(argc, argv);
  MyObject obj;
  return 0;
}
Community
  • 1
  • 1
Kuba hasn't forgotten Monica
  • 95,931
  • 16
  • 151
  • 313
  • Thanks, I will have a look at this other question :) – code_fodder Jan 22 '14 at 15:51
  • After reading the answers, it is quite a long code snippet, I am not sure how I would apply this to a simple mechanism or if it applies to my particular code. I will put a code example in my question to see if that can be modified to work in the method you explain... – code_fodder Jan 22 '14 at 18:38
  • @code_fodder: The length of the snippet doesn't matter much. What matters is the contents of the `main()` function: that's where you declare certain signals to be compressible. That's all you need to know. The gui code is just for demonstration, you can remove it. – Kuba hasn't forgotten Monica Jan 22 '14 at 19:06
  • So, in my example, in main() (which I have not shown) all I need to do is add this line: `a.addCompressedSignal(Signaller::staticMetaObject.method(Signaller::staticMetaObject.indexOfSignal("obj2Signal(int)"))); ` to make this a compressed signal? (sorry if I am being slow, this is new to me and the syntax is still quite confusing) – code_fodder Jan 23 '14 at 07:24
  • @code_fodder: See my answer, I've shown exactly how your main function should look. You don't have a `Signaller` class, it's `MyObjType2`. You also need to make the application be a `CompressorApplication`. – Kuba hasn't forgotten Monica Jan 23 '14 at 14:57
  • I could not find CompressorApplication anywhere... and then I realised it was a template class that you created I did not pick up on that, and this was the whole point! - I got it now, I have not yet implemented it all in my code I will do, but for now I will mark this answer up (and the original too) since its proven stuff by the look of it :) – code_fodder Jan 27 '14 at 11:11