No momento, minha tarefa é desenvolver um aplicativo para desenho. Para resolver esse problema, foi decidido usar QT/C++ . Como esta é a primeira vez na minha carreira que estou desenvolvendo um aplicativo GUI que consiste em um grande número de módulos, plug-ins e lógica, foi decidido usar um EventManager para comunicar todos os módulos entre si.
Quase todos os módulos (plugins) não sabem sobre a existência de outros módulos e se comunicam com o aplicativo principal usando o mesmo gerenciador de eventos enviando eventos para ele ou esperando por eventos. Mas há plugins que enviam eventos com muita frequência, o que afeta o desempenho. Como otimizar o gerenciador de eventos para melhor desempenho. Como exemplo, apresento abaixo um instantâneo de como os módulos existem isolados uns dos outros e se comunicam com a "Página de desenho" apenas enviando mensagens.
O Gerenciador de Eventos em si é uma classe estática simples que recebe uma string como o tipo de evento e QVariant como o parâmetro deste evento!
// 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;
}
Na imagem acima, "ToolBarWidget1" envia um evento para "ZoomIn", e "Drawing Page" escuta esse evento e, ao receber tal evento, reage a ele. Não há problemas, mas assim que preciso, por exemplo, enviar um evento de movimento do mouse da "Drawing page", aparecem travamentos e o programa cai de desempenho. Não é recomendado conectar diretamente a "Drawing page" com módulos, porque dessa forma evitamos a vinculação rígida de módulos. Como isso pode ser otimizado?
PS : 1. Exemplo de envio de um evento:
EventManager::instance().triggerEvent("toolBarWidget1", "zoomIn");
PS : 2. Exemplo de tratamento de eventos:
EventManager::instance().registerEvent("toolBarWidget1",
this, [this](const QString& eventType, const QVariant& eventValue) {
(void) eventType;
if (eventValue.toString() == "zoomIn") {
drawingPage->zoomIn();
}
});
Você disse que usaria o Qt para esse propósito. O Qt tem seu próprio loop de eventos, um conjunto de eventos usados internamente e um QObject::customEvent() está lá apenas para você reimplementar para escolher o evento certo. Então, você provavelmente não precisa dessa classe EventManager. Você já deve ter um loop de eventos baseado em QCoreApplication que é basicamente um gerenciador de eventos e muito mais.
Mas, esqueça o comentário acima por um momento. Por que você precisa de um despachador de eventos como este? O Qt já tem um mecanismo de sinal/slot conveniente, que também cuida da sinalização multithread. No seu exemplo, você está usando mutexes excessivamente que podem explicar gagueiras na sua IU. Mesmo que você precise de uma classe central/singleton, sugiro que você use a classe como um emissor de sinal e deixe os consumidores se conectarem a esses sinais.