X-Git-Url: https://git.quassel-irc.org/?p=quassel.git;a=blobdiff_plain;f=src%2Fcommon%2Fsignalproxy.h;h=885ebe60c0ad4dcc7a202d6b2f1e3319fbcc5f49;hp=77ef3a46731050256f8f4a5a23d2ee2956b5ffdf;hb=db00831bca59a012242d1ad5fac52a20c6cd2956;hpb=53861faa5551606eea31588b65ba501b24fb2e1a diff --git a/src/common/signalproxy.h b/src/common/signalproxy.h index 77ef3a46..885ebe60 100644 --- a/src/common/signalproxy.h +++ b/src/common/signalproxy.h @@ -1,5 +1,5 @@ /*************************************************************************** - * Copyright (C) 2005-2016 by the Quassel Project * + * Copyright (C) 2005-2018 by the Quassel Project * * devel@quassel-irc.org * * * * This program is free software; you can redistribute it and/or modify * @@ -18,17 +18,26 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ -#ifndef SIGNALPROXY_H -#define SIGNALPROXY_H +#pragma once -#include -#include -#include +#include "common-export.h" #include +#include #include +#include +#include +#include + +#include +#include +#include +#include +#include +#include "funchelpers.h" #include "protocol.h" +#include "types.h" struct QMetaObject; class QIODevice; @@ -36,25 +45,29 @@ class QIODevice; class Peer; class SyncableObject; -class SignalProxy : public QObject +class COMMON_EXPORT SignalProxy : public QObject { Q_OBJECT - class SignalRelay; + template::FunctionType> + class SlotObject; + class SlotObjectBase; public: - enum ProxyMode { + enum ProxyMode + { Server, Client }; - enum EventType { + enum EventType + { RemovePeerEvent = QEvent::User }; - SignalProxy(QObject *parent); - SignalProxy(ProxyMode mode, QObject *parent); - virtual ~SignalProxy(); + SignalProxy(QObject* parent); + SignalProxy(ProxyMode mode, QObject* parent); + ~SignalProxy() override; void setProxyMode(ProxyMode mode); inline ProxyMode proxyMode() const { return _proxyMode; } @@ -64,27 +77,85 @@ public: void setMaxHeartBeatCount(int max); inline int maxHeartBeatCount() const { return _maxHeartBeatCount; } - bool addPeer(Peer *peer); + bool addPeer(Peer* peer); - bool attachSignal(QObject *sender, const char *signal, const QByteArray &sigName = QByteArray()); - bool attachSlot(const QByteArray &sigName, QObject *recv, const char *slot); + /** + * Attaches a signal for remote emission. + * + * After calling this method, whenever the sender emits the given signal, an RpcCall message is sent to connected peers. + * On the other end, a slot can be attached to handle this message by calling attachSlot(). + + * By default, the signal name being sent is as if the SIGNAL() macro had been used, i.e. the normalized signature prefixed with a '2'. + * This can be overridden by explicitly providing the signalName argument. + * + * @sa attachSlot + * + * @param sender The sender of the signal + * @param signal The signal itself (given as a member function pointer) + * @param signalName Optional string to be used instead of the actual signal name. Will be normalized. + * @returns true if attaching the signal was successful + */ + template + bool attachSignal(const typename FunctionTraits::ClassType* sender, Signal signal, const QByteArray& signalName = {}); - void synchronize(SyncableObject *obj); - void stopSynchronize(SyncableObject *obj); + /** + * Attaches a slot to a remote signal. + * + * After calling this method, upon receipt of an RpcCall message with a signalName matching the signalName parameter, the given slot + * is called with the parameters contained in the message. This is intended to be used in conjunction with attachSignal() on the other + * end of the connection. + * + * Normally, the signalName should be given using the SIGNAL() macro; it will be normalized automatically. + * + * @sa attachSignal + * + * @param signalName Name of the signal as stored in the RpcCall message + * @param receiver Receiver of the signal + * @param slot Slot to be called (given as a member function pointer) + * @returns true if attaching the slot was successful + */ + template::value>> + bool attachSlot(const QByteArray& signalName, typename FunctionTraits::ClassType* receiver, Slot slot); + + /** + * @overload + * + * Attaches a functor to a remote signal. + * + * After calling this method, upon receipt of an RpcCall message with a signalName matching the signalName parameter, the given functor + * is invoked with the parameters contained in the message. This is intended to be used in conjunction with attachSignal() on the other + * end of the connection. This overload can be used, for example, with a lambda that accepts arguments matching the RpcCall parameter + * list. + * + * The context parameter controls the lifetime of the connection; if the context is deleted, the functor is deleted as well. + * + * @sa attachSignal + * + * @param signalName Name of the signal as stored in the RpcCall message + * @param context QObject context controlling the lifetime of the callable + * @param slot The functor to be invoked + * @returns true if attaching the functor was successful + */ + template::value>> + bool attachSlot(const QByteArray& signalName, const QObject* context, Slot slot); + + void synchronize(SyncableObject* obj); + void stopSynchronize(SyncableObject* obj); class ExtendedMetaObject; - ExtendedMetaObject *extendedMetaObject(const QMetaObject *meta) const; - ExtendedMetaObject *createExtendedMetaObject(const QMetaObject *meta, bool checkConflicts = false); - inline ExtendedMetaObject *extendedMetaObject(const QObject *obj) const { return extendedMetaObject(metaObject(obj)); } - inline ExtendedMetaObject *createExtendedMetaObject(const QObject *obj, bool checkConflicts = false) { return createExtendedMetaObject(metaObject(obj), checkConflicts); } + ExtendedMetaObject* extendedMetaObject(const QMetaObject* meta) const; + ExtendedMetaObject* createExtendedMetaObject(const QMetaObject* meta, bool checkConflicts = false); + inline ExtendedMetaObject* extendedMetaObject(const QObject* obj) const { return extendedMetaObject(metaObject(obj)); } + inline ExtendedMetaObject* createExtendedMetaObject(const QObject* obj, bool checkConflicts = false) + { + return createExtendedMetaObject(metaObject(obj), checkConflicts); + } bool isSecure() const { return _secure; } void dumpProxyStats(); - void dumpSyncMap(SyncableObject *object); + void dumpSyncMap(SyncableObject* object); - static SignalProxy *current() { - return _current; - } + static SignalProxy* current(); /**@{*/ /** @@ -93,59 +164,54 @@ public: * @param closure Code you want to execute within of that restricted environment */ void restrictTargetPeers(QSet peers, std::function closure); - void restrictTargetPeers(Peer *peer, std::function closure) { + void restrictTargetPeers(Peer* peer, std::function closure) + { QSet set; set.insert(peer); restrictTargetPeers(set, std::move(closure)); } - //A better version, but only implemented on Qt5 if Initializer Lists exist -#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) + // A better version, but only implemented on Qt5 if Initializer Lists exist #ifdef Q_COMPILER_INITIALIZER_LISTS - void restrictTargetPeers(std::initializer_list peers, std::function closure) { + void restrictTargetPeers(std::initializer_list peers, std::function closure) + { restrictTargetPeers(QSet(peers), std::move(closure)); } -#endif #endif /**}@*/ inline int peerCount() const { return _peerMap.size(); } QVariantList peerData(); - Peer *peerById(int peerId); + Peer* peerById(int peerId); /** * @return If handling a signal, the Peer from which the current signal originates */ - Peer *sourcePeer(); - void setSourcePeer(Peer *sourcePeer); + Peer* sourcePeer(); + void setSourcePeer(Peer* sourcePeer); /** * @return If sending a signal, the Peer to which the current signal is directed */ - Peer *targetPeer(); - void setTargetPeer(Peer *targetPeer); - -public slots: - void detachObject(QObject *obj); - void detachSignals(QObject *sender); - void detachSlots(QObject *receiver); + Peer* targetPeer(); + void setTargetPeer(Peer* targetPeer); protected: - void customEvent(QEvent *event); - void sync_call__(const SyncableObject *obj, ProxyMode modeType, const char *funcname, va_list ap); - void renameObject(const SyncableObject *obj, const QString &newname, const QString &oldname); + void customEvent(QEvent* event) override; + void sync_call__(const SyncableObject* obj, ProxyMode modeType, const char* funcname, va_list ap); + void renameObject(const SyncableObject* obj, const QString& newname, const QString& oldname); private slots: void removePeerBySender(); - void objectRenamed(const QByteArray &classname, const QString &newname, const QString &oldname); + void objectRenamed(const QByteArray& classname, const QString& newname, const QString& oldname); void updateSecureState(); signals: - void peerRemoved(Peer *peer); + void peerRemoved(Peer* peer); void connected(); void disconnected(); - void objectInitialized(SyncableObject *); + void objectInitialized(SyncableObject*); void heartBeatIntervalChanged(int secs); void maxHeartBeatCountChanged(int max); void lagUpdated(int lag); @@ -159,75 +225,209 @@ private: void initServer(); void initClient(); - static const QMetaObject *metaObject(const QObject *obj); + static const QMetaObject* metaObject(const QObject* obj); - void removePeer(Peer *peer); + void removePeer(Peer* peer); void removeAllPeers(); - int nextPeerId() { - return _lastPeerId++; - } + int nextPeerId() { return _lastPeerId++; } + + /** + * Attaches a SlotObject for the given signal name. + * + * @param signalName Signal name to be associated with the SlotObject + * @param slotObject The SlotObject instance to be invoked for incoming and matching RpcCall messages + */ + void attachSlotObject(const QByteArray& signalName, std::unique_ptr slotObject); + + /** + * Deletes all SlotObjects associated with the given context. + * + * @param context The context + */ + void detachSlotObjects(const QObject* context); + + /** + * Dispatches an RpcMessage for the given signal and parameters. + * + * @param signalName The signal + * @param params The parameters + */ + void dispatchSignal(QByteArray signalName, QVariantList params); template - void dispatch(const T &protoMessage); + void dispatch(const T& protoMessage); template - void dispatch(Peer *peer, const T &protoMessage); + void dispatch(Peer* peer, const T& protoMessage); - void handle(Peer *peer, const Protocol::SyncMessage &syncMessage); - void handle(Peer *peer, const Protocol::RpcCall &rpcCall); - void handle(Peer *peer, const Protocol::InitRequest &initRequest); - void handle(Peer *peer, const Protocol::InitData &initData); + void handle(Peer* peer, const Protocol::SyncMessage& syncMessage); + void handle(Peer* peer, const Protocol::RpcCall& rpcCall); + void handle(Peer* peer, const Protocol::InitRequest& initRequest); + void handle(Peer* peer, const Protocol::InitData& initData); template - void handle(Peer *, T) { Q_ASSERT(0); } + void handle(Peer*, T) + { + Q_ASSERT(0); + } - bool invokeSlot(QObject *receiver, int methodId, const QVariantList ¶ms, QVariant &returnValue, Peer *peer = 0); - bool invokeSlot(QObject *receiver, int methodId, const QVariantList ¶ms = QVariantList(), Peer *peer = 0); + bool invokeSlot(QObject* receiver, int methodId, const QVariantList& params, QVariant& returnValue, Peer* peer = nullptr); + bool invokeSlot(QObject* receiver, int methodId, const QVariantList& params = QVariantList(), Peer* peer = nullptr); - void requestInit(SyncableObject *obj); - QVariantMap initData(SyncableObject *obj) const; - void setInitData(SyncableObject *obj, const QVariantMap &properties); + void requestInit(SyncableObject* obj); + QVariantMap initData(SyncableObject* obj) const; + void setInitData(SyncableObject* obj, const QVariantMap& properties); - static void disconnectDevice(QIODevice *dev, const QString &reason = QString()); + static void disconnectDevice(QIODevice* dev, const QString& reason = QString()); +private: QHash _peerMap; // containg a list of argtypes for fast access - QHash _extendedMetaObjects; + QHash _extendedMetaObjects; - // SignalRelay for all manually attached signals - SignalRelay *_signalRelay; - - // RPC function -> (object, slot ID) - typedef QPair MethodId; - typedef QMultiHash SlotHash; - SlotHash _attachedSlots; + std::unordered_multimap, Hash> _attachedSlots; ///< Attached slot objects // slaves for sync - typedef QHash ObjectId; + using ObjectId = QHash; QHash _syncSlave; ProxyMode _proxyMode; int _heartBeatInterval; int _maxHeartBeatCount; - bool _secure; // determines if all connections are in a secured state (using ssl or internal connections) + bool _secure; // determines if all connections are in a secured state (using ssl or internal connections) int _lastPeerId = 0; - QSet _restrictedTargets; + QSet _restrictedTargets; bool _restrictMessageTarget = false; - Peer *_sourcePeer = nullptr; - Peer *_targetPeer = nullptr; - - thread_local static SignalProxy *_current; + Peer* _sourcePeer = nullptr; + Peer* _targetPeer = nullptr; - friend class SignalRelay; friend class SyncableObject; friend class Peer; }; +// ---- Template function implementations --------------------------------------- + +template +bool SignalProxy::attachSignal(const typename FunctionTraits::ClassType* sender, Signal signal, const QByteArray& signalName) +{ + static_assert(std::is_member_function_pointer::value, "Signal must be given as member function pointer"); + + // Determine the signalName to be stored in the RpcCall + QByteArray name; + if (signalName.isEmpty()) { + auto method = QMetaMethod::fromSignal(signal); + if (!method.isValid()) { + qWarning().nospace() << Q_FUNC_INFO << ": Function pointer is not a signal"; + return false; + } + name = "2" + method.methodSignature(); // SIGNAL() prefixes the signature with "2" + } + else { + name = QMetaObject::normalizedSignature(signalName.constData()); + } + + // Upon signal emission, marshall the signal's arguments into a QVariantList and dispatch an RpcCall message + connect(sender, signal, this, [this, signalName = std::move(name)](auto&&... args) { + this->dispatchSignal(std::move(signalName), {QVariant::fromValue(args)...}); + }); + + return true; +} + +template +bool SignalProxy::attachSlot(const QByteArray& signalName, typename FunctionTraits::ClassType* receiver, Slot slot) +{ + // Create a wrapper function that invokes the member function pointer for the receiver instance + attachSlotObject(signalName, std::make_unique>(receiver, [receiver, slot = std::move(slot)](auto&&... args) { + (receiver->*slot)(std::forward(args)...); + })); + return true; +} + +template +bool SignalProxy::attachSlot(const QByteArray& signalName, const QObject* context, Slot slot) +{ + static_assert(!std::is_same::value, "Old-style slots not supported"); + + attachSlotObject(signalName, std::make_unique>(context, std::move(slot))); + return true; +} + +/** + * Base object for storing a slot (or functor) to be invoked with a list of arguments. + * + * @note Having this untemplated base class for SlotObject allows for handling slots in the implementation rather than in the header. + */ +class COMMON_EXPORT SignalProxy::SlotObjectBase +{ +public: + virtual ~SlotObjectBase() = default; + + /** + * @returns The context associated with the slot + */ + const QObject* context() const; + + /** + * Invokes the slot with the given list of parameters. + * + * If the parameters cannot all be converted to the slot's argument types, or there is a mismatch in argument count, + * the invocation will fail. + * + * @param params List of arguments marshalled as QVariants + * @returns true if the invocation was successful + */ + virtual bool invoke(const QVariantList& params) const = 0; + +protected: + SlotObjectBase(const QObject* context); + +private: + const QObject* _context; +}; + +/** + * Specialization of SlotObjectBase for a particular type of slot. + * + * Callable may be a function wrapper around a member function pointer of type Slot, + * or a functor that can be invoked directly. + * + * @tparam Slot Type of the slot, used for determining the callable's signature + * @tparam Callable Type of the actual callable to be invoked + */ +template +class SignalProxy::SlotObject : public SlotObjectBase +{ +public: + /** + * Constructs a SlotObject for the given callable, whose lifetime is controlled by context. + * + * @param context Context object; if destroyed, the slot object will be destroyed as well by SignalProxy. + * @param callable Callable to be invoked + */ + SlotObject(const QObject* context, Callable callable) + : SlotObjectBase(context) + , _callable(std::move(callable)) + {} + + // See base class + bool invoke(const QVariantList& params) const override + { + if (QThread::currentThread() != context()->thread()) { + qWarning() << "Cannot call slot in different thread!"; + return false; + } + return invokeWithArgsList(_callable, params); + } + +private: + Callable _callable; +}; // ================================================== // ExtendedMetaObject @@ -237,11 +437,11 @@ class SignalProxy::ExtendedMetaObject class MethodDescriptor { public: - MethodDescriptor(const QMetaMethod &method); - MethodDescriptor() : _returnType(-1), _minArgCount(-1), _receiverMode(SignalProxy::Client) {} + MethodDescriptor(const QMetaMethod& method); + MethodDescriptor() = default; - inline const QByteArray &methodName() const { return _methodName; } - inline const QList &argTypes() const { return _argTypes; } + inline const QByteArray& methodName() const { return _methodName; } + inline const QList& argTypes() const { return _argTypes; } inline int returnType() const { return _returnType; } inline int minArgCount() const { return _minArgCount; } inline SignalProxy::ProxyMode receiverMode() const { return _receiverMode; } @@ -249,42 +449,40 @@ class SignalProxy::ExtendedMetaObject private: QByteArray _methodName; QList _argTypes; - int _returnType; - int _minArgCount; - SignalProxy::ProxyMode _receiverMode; // Only acceptable as a Sync Call if the receiving SignalProxy is in this mode. + int _returnType{-1}; + int _minArgCount{-1}; + SignalProxy::ProxyMode _receiverMode{ + SignalProxy::Client}; // Only acceptable as a Sync Call if the receiving SignalProxy is in this mode. }; - public: - ExtendedMetaObject(const QMetaObject *meta, bool checkConflicts); + ExtendedMetaObject(const QMetaObject* meta, bool checkConflicts); - inline const QByteArray &methodName(int methodId) { return methodDescriptor(methodId).methodName(); } - inline const QList &argTypes(int methodId) { return methodDescriptor(methodId).argTypes(); } + inline const QByteArray& methodName(int methodId) { return methodDescriptor(methodId).methodName(); } + inline const QList& argTypes(int methodId) { return methodDescriptor(methodId).argTypes(); } inline int returnType(int methodId) { return methodDescriptor(methodId).returnType(); } inline int minArgCount(int methodId) { return methodDescriptor(methodId).minArgCount(); } inline SignalProxy::ProxyMode receiverMode(int methodId) { return methodDescriptor(methodId).receiverMode(); } - inline int methodId(const QByteArray &methodName) { return _methodIds.contains(methodName) ? _methodIds[methodName] : -1; } + inline int methodId(const QByteArray& methodName) { return _methodIds.contains(methodName) ? _methodIds[methodName] : -1; } inline int updatedRemotelyId() { return _updatedRemotelyId; } - inline const QHash &slotMap() { return _methodIds; } - const QHash &receiveMap(); + inline const QHash& slotMap() { return _methodIds; } + const QHash& receiveMap(); - const QMetaObject *metaObject() const { return _meta; } + const QMetaObject* metaObject() const { return _meta; } - static QByteArray methodName(const QMetaMethod &method); - static QString methodBaseName(const QMetaMethod &method); + static QByteArray methodName(const QMetaMethod& method); + static QString methodBaseName(const QMetaMethod& method); private: - const MethodDescriptor &methodDescriptor(int methodId); + const MethodDescriptor& methodDescriptor(int methodId); - const QMetaObject *_meta; - int _updatedRemotelyId; // id of the updatedRemotely() signal - makes things faster + const QMetaObject* _meta; + int _updatedRemotelyId; // id of the updatedRemotely() signal - makes things faster QHash _methods; QHash _methodIds; - QHash _receiveMap; // if slot x is called then hand over the result to slot y + QHash _receiveMap; // if slot x is called then hand over the result to slot y }; - -#endif