1
votes

Is there a way to consume future slots (and stop them from executing) in one of many slots connected to the same signal?

My goal here is to emit a signal with a message to many QObjects and consume (stopping iteration to future slots) when the QObject in which the message belongs to finds it.

From what I understand in the Qt Documentation:

If several slots are connected to one signal, the slots will be executed one after the other, in the order they have been connected, when the signal is emitted.

I want to be able to stop this process from within a slot.

Suggestions?

2

2 Answers

1
votes

No, there's no way to do it, and you should not think of it this way. The sender should perform the same no matter what number of slots are connected to a signal. That's the basic contract of the signal-slot mechanism: the sender is completely decoupled from, and unaware of, the receiver.

What you're trying to do is qualified dispatch: there are multiple receivers, and each receiver can process one or more message types. One way of implementing it is as follows:

  1. Emit (signal) a QEvent. This lets you maintain the signal-slot decoupling between the transmitter and the receiver(s).

  2. The event can then be consumed by a custom event dispatcher that knows which objects process events of given type.

  3. The objects are sent the event in the usual fashion, and receive it in their event() method.

The implementation below allows the receiver objects to live in other threads. That's why it needs to be able to clone events.

class <QCoreApplication>
class <QEvent>

class ClonableEvent : public QEvent {
  Q_DISABLE_COPY(ClonableEvent)
public:
  ClonableEvent(int type) : QEvent(static_cast<QEvent::Type>(type)) {}
  virtual ClonableEvent * clone() const { return new ClonableEvent(type()); }
}
Q_REGISTER_METATYPE(ClonableEvent*)

class Dispatcher : public QObject {
  Q_OBJECT
  QMap<int, QSet<QObject*>> m_handlers;
public:
  Q_SLOT void dispatch(ClonableEvent * ev) {
    auto it = m_handlers.find(ev->type());
    if (it == m_handlers.end()) return;
    for (auto object : *it) {
      if (obj->thread() == QThread::currentThread())
        QCoreApplication::sendEvent(obj, ev);
      else
        QCoreApplication::postEvent(obj, ev.clone());
    }
  }
  void addMapping(QClonableEvent * ev, QObject * obj) { 
    addMapping(ev->type(), obj);
  }
  void addMapping(int type, QObject * obj) {
    QSet<QObject*> & handlers = m_handlers[type];
    auto it = handlers.find(obj);
    if (it != handlers.end()) return;
    handlers.insert(obj);
    QObject::connect(obj, &QObject::destroyed, [this, type, obj]{
      unregister(type, obj);
    });
    m_handlers[type].insert(obj);
  }
  void removeMapping(int type, QObject * obj) {
    auto it = m_handlers.find(type);
    if (it == m_handlers.end()) return;
    it->remove(obj);
  }
}

class EventDisplay : public QObject {
  bool event(QEvent * ev) {
    qDebug() << objectName() << "got event" << ev.type();
    return QObject::event(ev);
  }
public:
  EventDisplay() {}
};

class EventSource : public QObject {
  Q_OBJECT
public:
  Q_SIGNAL void indication(ClonableEvent *);
}

#define NAMED(x) x; x.setObjectName(#x)
int main(int argc, char ** argv) {
  QCoreApplication app(argc, argv);
  ClonableEvent ev1(QEvent::User + 1);
  ClonableEvent ev2(QEvent::User + 2);
  EventDisplay NAMED(dp1);
  EventDisplay NAMED(dp12);
  EventDisplay NAMED(dp2);

  Dispatcher d;
  d.addMapping(ev1, dp1);  // dp1 handles only ev1
  d.addMapping(ev1, dp12); // dp12 handles both ev1 and ev2
  d.addMapping(ev2, dp12);
  d.addMapping(ev2, dp2);  // dp2 handles only ev2

  EventSource s;
  QObject::connect(&s, &EventSource::indication, &d, &Dispatcher::dispatch);

  emit s.indication(&ev1);
  emit s.indication(&ev2);
  return 0;
}

#include "main.moc"
0
votes

If connection was in one thread, I think that you can throw an exception. But in this case you should be catch any exception during emit a signal:

try {
    emit someSignal();
} catch(...) {
    qDebug() << "catched";
}

But I think that it's bad idea. I'll would be use event dispatching for this.