At the moment, my task is to develop an application for drawing. To solve this problem, it was decided to use QT/C++. Since this is the first time in my career that I am developing a GUI application consisting of a large number of modules, plug-ins, and logic, it was decided to use an EventManager to communicate all the modules with each other.
Almost every module (plugin) does not know about the existence of other modules and communicates with the main application using the same Event manager sending events to it, or waiting for events. But there are plugins that send events very often, which affects performance. How to optimize the Event manager for better performance. As an example, I present below a snapshot of how modules exist in isolation from each other, and communicate with the "Drawing page" only by sending messages.
The Event Manager itself is a simple static class that gets a string as the event type, and QVariant as the parameter of this event!
// event-manager.h
#include <QObject>
#include <functional>
class EventManager final :
public QObject
{
Q_OBJECT
public:
using EventCallback = std::function<void(const QString&, const QVariant&)>;
void triggerEvent(const QString &eventName, const QVariant& value) noexcept;
void registerEvent(const QString &eventName, QObject *receiver, EventCallback) noexcept;
void unregisterEvent(const QString &eventName, QObject *receiver) noexcept;
static EventManager& instance() noexcept;
signals:
void eventTriggered(const QString &eventName, const QVariant& value);
private:
explicit EventManager(QObject *parent = nullptr);
virtual ~EventManager() override;
private:
struct Private;
Private *d_ptr;
};
// event-manager.cpp
#include "event-manager.cpp"
#include <QHash>
#include <QMap>
#include <QMutex>
#include <QMutexLocker>
#include <QPointer>
#include <QVariant>
struct EventManager::Private {
struct EventSlot {
QPointer<QObject> receiver;
EventCallback callback;
};
QMutex mutex;
QMap<QString, QList<EventSlot>> events;
};
EventManager::EventManager(QObject *aParent) :
QObject(aParent),
d_ptr(new EventManager::Private)
{}
EventManager::~EventManager()
{
delete d_ptr;
}
void EventManager::triggerEvent(const QString& aEventName, const QVariant& aValue) noexcept
{
if (!aEventName.isEmpty()) {
QMutexLocker locker(&d_ptr->mutex);
const bool found = d_ptr->events.contains(aEventName);
locker.unlock();
if (!found) { return; }
locker.relock();
QList<Private::EventSlot> eventSlots;
eventSlots = d_ptr->events[aEventName];
locker.unlock();
for (const auto& eventSlot : std::as_const(eventSlots)) {
if (!eventSlot.receiver.isNull()) {
QMetaObject::invokeMethod(eventSlot.receiver,
[callback = eventSlot.callback, aEventName, aValue]() {
callback(aEventName, aValue);
}, Qt::QueuedConnection);
}
}
emit eventTriggered(aEventName, aValue);
}
}
void EventManager::registerEvent(const QString& aEventName, QObject* aReceiver, EventCallback aCallback) noexcept
{
if (!aEventName.isEmpty() && aReceiver) {
QMutexLocker locker(&d_ptr->mutex);
Private::EventSlot eventSlot;
eventSlot.callback = std::move(aCallback);
eventSlot.receiver = aReceiver;
d_ptr->events[aEventName].append(eventSlot);
}
}
void EventManager::unregisterEvent(const QString &aEventName, QObject *aReceiver) noexcept
{
if (!aEventName.isEmpty() && aReceiver) {
QMutexLocker locker(&d_ptr->mutex);
if (d_ptr->events.contains(aEventName)) {
auto &eventSlots = d_ptr->events[aEventName];
eventSlots.erase(std::remove_if(eventSlots.begin(), eventSlots.end(),
[aReceiver](const Private::EventSlot &slot) {
return slot.receiver == aReceiver;
}), eventSlots.end());
}
}
}
EventManager& EventManager::instance() noexcept
{
static EventManager EventManager;
return EventManager;
}
In the image above, "ToolBarWidget1" sends an event to "ZoomIn", and "Drawing Page" listens to this event and, upon receiving such an event, reacts to it. There are no problems, but as soon as I need, for example, to send a mouse movement event from the "Drawing page", freezes appear and the program drops in performance. It is not recommended to directly connect the "Drawing page" with modules, because this way we avoid rigid module binding. How can this be optimized?
PS: 1. Example of sending an event:
EventManager::instance().triggerEvent("toolBarWidget1", "zoomIn");
PS: 2. Example of event handling:
EventManager::instance().registerEvent("toolBarWidget1",
this, [this](const QString& eventType, const QVariant& eventValue) {
(void) eventType;
if (eventValue.toString() == "zoomIn") {
drawingPage->zoomIn();
}
});
You said you are going to use Qt for this purpose. Qt has its own event loop, a set of internally used events and a QObject::customEvent() is just there for you to re-implement to pick the right event. So, you probably do not need such EventManager class. You should already have a QCoreApplication based event loop which is basically an event manager plus much more.
But, forget the above comment for a moment. Why do you even need an event dispatcher like this. Qt already has a convenient signal/slot mechanism, that also takes care of multi-threaded signaling. In your example you are excessively using mutexes that can explain stutters in your UI. Even if you need a central/singleton class, I suggest you to use the class as a signal emitter, and let consumers connect to those signals.