refactor: improve Proxy signal subscription (#389)

This makes D-Bus proxy signal registration more flexible, more dynamic, and less error-prone since no `finishRegistration()` call is needed. A proxy can register to a signal at any time during its lifetime, and can unregister freely by simply destroying the associated slot.
This commit is contained in:
Stanislav Angelovič
2023-12-30 21:11:02 +01:00
committed by Stanislav Angelovic
parent e3040c0998
commit bdd0bc2c27
17 changed files with 227 additions and 202 deletions
+4 -11
View File
@@ -129,6 +129,10 @@ namespace sdbus {
SignalSubscriber(IProxy& proxy, const std::string& signalName);
SignalSubscriber& onInterface(std::string interfaceName);
template <typename _Function> void call(_Function&& callback);
template <typename _Function> [[nodiscard]] Slot call(_Function&& callback, request_slot_t);
private:
template <typename _Function> signal_handler makeSignalHandler(_Function&& callback);
private:
IProxy& proxy_;
@@ -136,17 +140,6 @@ namespace sdbus {
std::string interfaceName_;
};
class SignalUnsubscriber
{
public:
SignalUnsubscriber(IProxy& proxy, const std::string& signalName);
void onInterface(const std::string& interfaceName);
private:
IProxy& proxy_;
const std::string& signalName_;
};
class PropertyGetter
{
public:
+19 -17
View File
@@ -307,7 +307,24 @@ namespace sdbus {
proxy_.registerSignalHandler( interfaceName_
, signalName_
, [callback = std::forward<_Function>(callback)](Signal signal)
, makeSignalHandler(std::forward<_Function>(callback)) );
}
template <typename _Function>
inline Slot SignalSubscriber::call(_Function&& callback, request_slot_t)
{
assert(!interfaceName_.empty()); // onInterface() must be placed/called prior to this function
return proxy_.registerSignalHandler( interfaceName_
, signalName_
, makeSignalHandler(std::forward<_Function>(callback))
, request_slot );
}
template <typename _Function>
inline signal_handler SignalSubscriber::makeSignalHandler(_Function&& callback)
{
return [callback = std::forward<_Function>(callback)](Signal signal)
{
// Create a tuple of callback input arguments' types, which will be used
// as a storage for the argument values deserialized from the signal message.
@@ -343,22 +360,7 @@ namespace sdbus {
// Invoke callback with input arguments from the tuple.
sdbus::apply(callback, signalArgs);
}
});
}
/*** ------------------ ***/
/*** SignalUnsubscriber ***/
/*** ------------------ ***/
inline SignalUnsubscriber::SignalUnsubscriber(IProxy& proxy, const std::string& signalName)
: proxy_(proxy)
, signalName_(signalName)
{
}
inline void SignalUnsubscriber::onInterface(const std::string& interfaceName)
{
proxy_.unregisterSignalHandler(interfaceName, signalName_);
};
}
/*** -------------- ***/
+19 -36
View File
@@ -197,6 +197,9 @@ namespace sdbus {
* @param[in] signalName Name of the signal
* @param[in] signalHandler Callback that implements the body of the signal handler
*
* A signal can be subscribed to and unsubscribed from at any time during proxy
* lifetime. The subscription is active immediately after the call.
*
* @throws sdbus::Error in case of failure
*/
virtual void registerSignalHandler( const std::string& interfaceName
@@ -204,28 +207,27 @@ namespace sdbus {
, signal_handler signalHandler ) = 0;
/*!
* @brief Unregisters the handler of the desired signal
* @brief Registers a handler for the desired signal emitted by the D-Bus object
*
* @param[in] interfaceName Name of an interface that the signal belongs to
* @param[in] signalName Name of the signal
* @param[in] signalHandler Callback that implements the body of the signal handler
*
* @return RAII-style slot handle representing the ownership of the subscription
*
* A signal can be subscribed to and unsubscribed from at any time during proxy
* lifetime. The subscription is active immediately after the call. The subscription
* is unregistered when the client destroys the returned slot object.
*
* @throws sdbus::Error in case of failure
*/
virtual void unregisterSignalHandler( const std::string& interfaceName
, const std::string& signalName ) = 0;
[[nodiscard]] virtual Slot registerSignalHandler( const std::string& interfaceName
, const std::string& signalName
, signal_handler signalHandler
, request_slot_t ) = 0;
/*!
* @brief Finishes the registration of signal handlers
*
* The method physically subscribes to the desired signals.
* Must be called only once, after all signals have been registered already.
*
* @throws sdbus::Error in case of failure
*/
virtual void finishRegistration() = 0;
/*!
* @brief Unregisters proxy's signal handlers and stops receving replies to pending async calls
* @brief Unregisters proxy's signal handlers and stops receiving replies to pending async calls
*
* Unregistration is done automatically also in proxy's destructor. This method makes
* sense if, in the process of proxy removal, we need to make sure that callbacks
@@ -291,6 +293,9 @@ namespace sdbus {
* in a message and D-Bus signatures automatically deduced from the parameters
* of the provided native signal callback.
*
* A signal can be subscribed to and unsubscribed from at any time during proxy
* lifetime. The subscription is active immediately after the call.
*
* Example of use:
* @code
* object_.uponSignal("fooSignal").onInterface("com.kistler.foo").call([this](int arg1, double arg2){ this->onFooSignal(arg1, arg2); });
@@ -300,23 +305,6 @@ namespace sdbus {
*/
[[nodiscard]] SignalSubscriber uponSignal(const std::string& signalName);
/*!
* @brief Unregisters signal handler of a given signal of the D-Bus object
*
* @param[in] signalName Name of the signal
* @return A helper object for convenient unregistration of the signal handler
*
* This is a high-level, convenience way of unregistering a D-Bus signal's handler.
*
* Example of use:
* @code
* object_.muteSignal("fooSignal").onInterface("com.kistler.foo");
* @endcode
*
* @throws sdbus::Error in case of failure
*/
[[nodiscard]] SignalUnsubscriber muteSignal(const std::string& signalName);
/*!
* @brief Gets value of a property of the D-Bus object
*
@@ -543,11 +531,6 @@ namespace sdbus {
return SignalSubscriber(*this, signalName);
}
inline SignalUnsubscriber IProxy::muteSignal(const std::string& signalName)
{
return SignalUnsubscriber(*this, signalName);
}
inline PropertyGetter IProxy::getProperty(const std::string& propertyName)
{
return PropertyGetter(*this, propertyName);
+7 -6
View File
@@ -83,9 +83,10 @@ namespace sdbus {
* methods. So the _Interfaces template parameter is a list of sdbus-c++-xml2cpp-generated
* proxy-side interface classes representing interfaces of the corresponding remote D-Bus object.
*
* In the final proxy class inherited from ProxyInterfaces, it is necessary to finish proxy
* registration in class constructor (`finishRegistration();`), and, conversely, unregister
* the proxy in class destructor (`unregister();`).
* In the final adaptor class inherited from ProxyInterfaces, one needs to make sure:
* 1. to call `registerProxy();` in the class constructor, and, conversely,
* 2. to call `unregisterProxy();` in the class destructor,
* so that the signals are subscribed to and unsubscribed from at a proper time.
*
***********************************************/
template <typename... _Interfaces>
@@ -173,15 +174,15 @@ namespace sdbus {
}
/*!
* @brief Finishes proxy registration and makes the proxy ready for use
* @brief Registers handlers for D-Bus signals of the remote object
*
* This function must be called in the constructor of the final proxy class that implements ProxyInterfaces.
*
* For more information, see underlying @ref IProxy::finishRegistration()
* See also @ref IProxy::registerSignalHandler()
*/
void registerProxy()
{
getProxy().finishRegistration();
(_Interfaces::registerProxy(), ...);
}
/*!
+30 -14
View File
@@ -54,6 +54,10 @@ namespace sdbus {
~Peer_proxy() = default;
void registerProxy()
{
}
public:
void Ping()
{
@@ -89,6 +93,10 @@ namespace sdbus {
~Introspectable_proxy() = default;
void registerProxy()
{
}
public:
std::string Introspect()
{
@@ -109,6 +117,17 @@ namespace sdbus {
protected:
Properties_proxy(sdbus::IProxy& proxy)
: proxy_(&proxy)
{
}
Properties_proxy(const Properties_proxy&) = delete;
Properties_proxy& operator=(const Properties_proxy&) = delete;
Properties_proxy(Properties_proxy&&) = default;
Properties_proxy& operator=(Properties_proxy&&) = default;
~Properties_proxy() = default;
void registerProxy()
{
proxy_
->uponSignal("PropertiesChanged")
@@ -121,13 +140,6 @@ namespace sdbus {
});
}
Properties_proxy(const Properties_proxy&) = delete;
Properties_proxy& operator=(const Properties_proxy&) = delete;
Properties_proxy(Properties_proxy&&) = default;
Properties_proxy& operator=(Properties_proxy&&) = default;
~Properties_proxy() = default;
virtual void onPropertiesChanged( const std::string& interfaceName
, const std::map<std::string, sdbus::Variant>& changedProperties
, const std::vector<std::string>& invalidatedProperties ) = 0;
@@ -198,6 +210,17 @@ namespace sdbus {
protected:
ObjectManager_proxy(sdbus::IProxy& proxy)
: proxy_(&proxy)
{
}
ObjectManager_proxy(const ObjectManager_proxy&) = delete;
ObjectManager_proxy& operator=(const ObjectManager_proxy&) = delete;
ObjectManager_proxy(ObjectManager_proxy&&) = default;
ObjectManager_proxy& operator=(ObjectManager_proxy&&) = default;
~ObjectManager_proxy() = default;
void registerProxy()
{
proxy_
->uponSignal("InterfacesAdded")
@@ -217,13 +240,6 @@ namespace sdbus {
});
}
ObjectManager_proxy(const ObjectManager_proxy&) = delete;
ObjectManager_proxy& operator=(const ObjectManager_proxy&) = delete;
ObjectManager_proxy(ObjectManager_proxy&&) = default;
ObjectManager_proxy& operator=(ObjectManager_proxy&&) = default;
~ObjectManager_proxy() = default;
virtual void onInterfacesAdded( const sdbus::ObjectPath& objectPath
, const std::map<std::string, std::map<std::string, sdbus::Variant>>& interfacesAndProperties) = 0;
virtual void onInterfacesRemoved( const sdbus::ObjectPath& objectPath