Message ID | 20210827023829.5871-5-laurent.pinchart@ideasonboard.com |
---|---|
State | Accepted |
Commit | 58720e1dc98186e79ef4e758a851b58df562f7b4 |
Headers | show |
Series |
|
Related | show |
Hi Laurent, Thanks for the patch. I have a few questions regarding the documentation On 8/27/21 8:08 AM, Laurent Pinchart wrote: > It can be useful to connect a signal to a functor, and in particular a > lambda function, while still operating in the context of a receiver > object (to support both object-based disconnection and queued > connections to Object instances). > > Add a BoundMethodFunctor class to bind a functor, and a corresponding > Signal::connect() function. There is no corresponding disconnect() > function, as a lambda passed to connect() can't be later passed to > disconnect(). Disconnection typically uses disconnect(T *object), which > will cover the vast majority of use cases. > > Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> > --- > Documentation/Doxyfile.in | 1 + > include/libcamera/base/bound_method.h | 31 +++++++++++++++++++++ > include/libcamera/base/signal.h | 19 +++++++++++++ > src/libcamera/base/signal.cpp | 24 +++++++++++++++++ > test/signal.cpp | 39 +++++++++++++++++++++++++++ > 5 files changed, 114 insertions(+) > > diff --git a/Documentation/Doxyfile.in b/Documentation/Doxyfile.in > index dc03cbea4b02..d562510e902e 100644 > --- a/Documentation/Doxyfile.in > +++ b/Documentation/Doxyfile.in > @@ -878,6 +878,7 @@ EXCLUDE_PATTERNS = @TOP_BUILDDIR@/include/libcamera/ipa/*_serializer.h \ > > EXCLUDE_SYMBOLS = libcamera::BoundMethodArgs \ > libcamera::BoundMethodBase \ > + libcamera::BoundMethodFunctor \ > libcamera::BoundMethodMember \ > libcamera::BoundMethodPack \ > libcamera::BoundMethodPackBase \ > diff --git a/include/libcamera/base/bound_method.h b/include/libcamera/base/bound_method.h > index 76ce8017e721..ebd297ab8209 100644 > --- a/include/libcamera/base/bound_method.h > +++ b/include/libcamera/base/bound_method.h > @@ -128,6 +128,37 @@ public: > virtual R invoke(Args... args) = 0; > }; > > +template<typename T, typename R, typename Func, typename... Args> > +class BoundMethodFunctor : public BoundMethodArgs<R, Args...> > +{ > +public: > + using PackType = typename BoundMethodArgs<R, Args...>::PackType; > + > + BoundMethodFunctor(T *obj, Object *object, Func func, > + ConnectionType type = ConnectionTypeAuto) > + : BoundMethodArgs<R, Args...>(obj, object, type), func_(func) > + { > + } > + > + R activate(Args... args, bool deleteMethod = false) override > + { > + if (!this->object_) > + return func_(args...); > + > + auto pack = std::make_shared<PackType>(args...); > + bool sync = BoundMethodBase::activatePack(pack, deleteMethod); > + return sync ? pack->returnValue() : R(); > + } > + > + R invoke(Args... args) override > + { > + return func_(args...); > + } > + > +private: > + Func func_; > +}; > + > template<typename T, typename R, typename... Args> > class BoundMethodMember : public BoundMethodArgs<R, Args...> > { > diff --git a/include/libcamera/base/signal.h b/include/libcamera/base/signal.h > index c2521769a843..8d9f82f62d0d 100644 > --- a/include/libcamera/base/signal.h > +++ b/include/libcamera/base/signal.h > @@ -61,6 +61,25 @@ public: > SignalBase::connect(new BoundMethodMember<T, R, Args...>(obj, nullptr, func)); > } > > +#ifndef __DOXYGEN__ > + template<typename T, typename Func, > + typename std::enable_if_t<std::is_base_of<Object, T>::value> * = nullptr> > + void connect(T *obj, Func func, ConnectionType type = ConnectionTypeAuto) > + { > + Object *object = static_cast<Object *>(obj); > + SignalBase::connect(new BoundMethodFunctor<T, void, Func, Args...>(obj, object, func, type)); > + } > + > + template<typename T, typename Func, > + typename std::enable_if_t<!std::is_base_of<Object, T>::value> * = nullptr> > +#else > + template<typename T, typename Func> > +#endif > + void connect(T *obj, Func func) > + { > + SignalBase::connect(new BoundMethodFunctor<T, void, Func, Args...>(obj, nullptr, func)); > + } > + > template<typename R> > void connect(R (*func)(Args...)) > { > diff --git a/src/libcamera/base/signal.cpp b/src/libcamera/base/signal.cpp > index adcfa796870e..9c2319c59106 100644 > --- a/src/libcamera/base/signal.cpp > +++ b/src/libcamera/base/signal.cpp > @@ -121,6 +121,30 @@ SignalBase::SlotList SignalBase::slots() > * \context This function is \threadsafe. > */ > > +/** > + * \fn Signal::connect(T *object, Func func) > + * \brief Connect the signal to a function object slot > + * \param[in] object The slot object pointer > + * \param[in] func The function object > + * > + * If the typename T inherits from Object, the signal will be automatically > + * disconnected from the \a func slot of \a object when \a object is destroyed. > + * Otherwise the caller shall disconnect signals manually before destroying \a > + * object. So, if I have a instance _not_ inherited from Object, how would I manually disconnect the signal ? > + * > + * The function object is typically a lambda function, but may be any object > + * that satisfies the FunctionObject named requirements. The types of the > + * function object arguments shall match the types of the signal arguments. > + * > + * No matching disconnect() function exist, as it wouldn't be possible to pass > + * to a disconnect() function the same lambda that was passed to connect(). The > + * connection created by this function can not be removed selectively if the > + * signal is connected to multiple slots of the same receiver, but may be > + * otherwise be removed using the disconnect(T *object) function. I am not sure if I am understanding this correctly, but do you mean that the functor version of Signal, should essentially be used within a class inherited from Object /only/? I ask this because, i the previous paragraph there is: + * Otherwise the caller shall disconnect signals manually before destroying \a + * object. The 'Otherwise' seems to address the classes, that are not inherit from Object. It's stated that signal within those needs manual disconnection. But 'How?' isn't clear to me. The documentation doesn't seem to clarify to me that how a class _not_ inheriting from Object handle disconnection of the signal? Can you please clarify? > + * > + * \context This function is \threadsafe. > + */ > + > /** > * \fn Signal::connect(R (*func)(Args...)) > * \brief Connect the signal to a static function slot > diff --git a/test/signal.cpp b/test/signal.cpp > index 595782a2cd6e..fcf2def18df4 100644 > --- a/test/signal.cpp > +++ b/test/signal.cpp > @@ -191,6 +191,24 @@ protected: > signalVoid_.connect(slotStaticReturn); > signalVoid_.connect(this, &SignalTest::slotReturn); > > + /* Test signal connection to a lambda. */ > + int value = 0; > + signalInt_.connect(this, [&](int v) { value = v; }); > + signalInt_.emit(42); > + > + if (value != 42) { > + cout << "Signal connection to lambda failed" << endl; > + return TestFail; > + } > + > + signalInt_.disconnect(this); > + signalInt_.emit(0); > + > + if (value != 42) { > + cout << "Signal disconnection from lambda failed" << endl; > + return TestFail; > + } > + > /* ----------------- Signal -> Object tests ----------------- */ > > /* > @@ -256,6 +274,27 @@ protected: > > delete slotObject; > > + /* Test signal connection to a lambda. */ > + slotObject = new SlotObject(); > + value = 0; > + signalInt_.connect(slotObject, [&](int v) { value = v; }); > + signalInt_.emit(42); > + > + if (value != 42) { > + cout << "Signal connection to Object lambda failed" << endl; > + return TestFail; > + } > + > + signalInt_.disconnect(slotObject); > + signalInt_.emit(0); > + > + if (value != 42) { > + cout << "Signal disconnection from Object lambda failed" << endl; > + return TestFail; > + } > + > + delete slotObject; > + > /* --------- Signal -> Object (multiple inheritance) -------- */ > > /*
Hi Umang, On Tue, Aug 31, 2021 at 11:32:41AM +0530, Umang Jain wrote: > Hi Laurent, > > Thanks for the patch. I have a few questions regarding the documentation > > On 8/27/21 8:08 AM, Laurent Pinchart wrote: > > It can be useful to connect a signal to a functor, and in particular a > > lambda function, while still operating in the context of a receiver > > object (to support both object-based disconnection and queued > > connections to Object instances). > > > > Add a BoundMethodFunctor class to bind a functor, and a corresponding > > Signal::connect() function. There is no corresponding disconnect() > > function, as a lambda passed to connect() can't be later passed to > > disconnect(). Disconnection typically uses disconnect(T *object), which > > will cover the vast majority of use cases. > > > > Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> > > --- > > Documentation/Doxyfile.in | 1 + > > include/libcamera/base/bound_method.h | 31 +++++++++++++++++++++ > > include/libcamera/base/signal.h | 19 +++++++++++++ > > src/libcamera/base/signal.cpp | 24 +++++++++++++++++ > > test/signal.cpp | 39 +++++++++++++++++++++++++++ > > 5 files changed, 114 insertions(+) > > > > diff --git a/Documentation/Doxyfile.in b/Documentation/Doxyfile.in > > index dc03cbea4b02..d562510e902e 100644 > > --- a/Documentation/Doxyfile.in > > +++ b/Documentation/Doxyfile.in > > @@ -878,6 +878,7 @@ EXCLUDE_PATTERNS = @TOP_BUILDDIR@/include/libcamera/ipa/*_serializer.h \ > > > > EXCLUDE_SYMBOLS = libcamera::BoundMethodArgs \ > > libcamera::BoundMethodBase \ > > + libcamera::BoundMethodFunctor \ > > libcamera::BoundMethodMember \ > > libcamera::BoundMethodPack \ > > libcamera::BoundMethodPackBase \ > > diff --git a/include/libcamera/base/bound_method.h b/include/libcamera/base/bound_method.h > > index 76ce8017e721..ebd297ab8209 100644 > > --- a/include/libcamera/base/bound_method.h > > +++ b/include/libcamera/base/bound_method.h > > @@ -128,6 +128,37 @@ public: > > virtual R invoke(Args... args) = 0; > > }; > > > > +template<typename T, typename R, typename Func, typename... Args> > > +class BoundMethodFunctor : public BoundMethodArgs<R, Args...> > > +{ > > +public: > > + using PackType = typename BoundMethodArgs<R, Args...>::PackType; > > + > > + BoundMethodFunctor(T *obj, Object *object, Func func, > > + ConnectionType type = ConnectionTypeAuto) > > + : BoundMethodArgs<R, Args...>(obj, object, type), func_(func) > > + { > > + } > > + > > + R activate(Args... args, bool deleteMethod = false) override > > + { > > + if (!this->object_) > > + return func_(args...); > > + > > + auto pack = std::make_shared<PackType>(args...); > > + bool sync = BoundMethodBase::activatePack(pack, deleteMethod); > > + return sync ? pack->returnValue() : R(); > > + } > > + > > + R invoke(Args... args) override > > + { > > + return func_(args...); > > + } > > + > > +private: > > + Func func_; > > +}; > > + > > template<typename T, typename R, typename... Args> > > class BoundMethodMember : public BoundMethodArgs<R, Args...> > > { > > diff --git a/include/libcamera/base/signal.h b/include/libcamera/base/signal.h > > index c2521769a843..8d9f82f62d0d 100644 > > --- a/include/libcamera/base/signal.h > > +++ b/include/libcamera/base/signal.h > > @@ -61,6 +61,25 @@ public: > > SignalBase::connect(new BoundMethodMember<T, R, Args...>(obj, nullptr, func)); > > } > > > > +#ifndef __DOXYGEN__ > > + template<typename T, typename Func, > > + typename std::enable_if_t<std::is_base_of<Object, T>::value> * = nullptr> > > + void connect(T *obj, Func func, ConnectionType type = ConnectionTypeAuto) > > + { > > + Object *object = static_cast<Object *>(obj); > > + SignalBase::connect(new BoundMethodFunctor<T, void, Func, Args...>(obj, object, func, type)); > > + } > > + > > + template<typename T, typename Func, > > + typename std::enable_if_t<!std::is_base_of<Object, T>::value> * = nullptr> > > +#else > > + template<typename T, typename Func> > > +#endif > > + void connect(T *obj, Func func) > > + { > > + SignalBase::connect(new BoundMethodFunctor<T, void, Func, Args...>(obj, nullptr, func)); > > + } > > + > > template<typename R> > > void connect(R (*func)(Args...)) > > { > > diff --git a/src/libcamera/base/signal.cpp b/src/libcamera/base/signal.cpp > > index adcfa796870e..9c2319c59106 100644 > > --- a/src/libcamera/base/signal.cpp > > +++ b/src/libcamera/base/signal.cpp > > @@ -121,6 +121,30 @@ SignalBase::SlotList SignalBase::slots() > > * \context This function is \threadsafe. > > */ > > > > +/** > > + * \fn Signal::connect(T *object, Func func) > > + * \brief Connect the signal to a function object slot > > + * \param[in] object The slot object pointer > > + * \param[in] func The function object > > + * > > + * If the typename T inherits from Object, the signal will be automatically > > + * disconnected from the \a func slot of \a object when \a object is destroyed. > > + * Otherwise the caller shall disconnect signals manually before destroying \a > > + * object. > > So, if I have a instance _not_ inherited from Object, how would I > manually disconnect the signal ? With one of the disconnect() functions of the signal. > > + * > > + * The function object is typically a lambda function, but may be any object > > + * that satisfies the FunctionObject named requirements. The types of the > > + * function object arguments shall match the types of the signal arguments. > > + * > > + * No matching disconnect() function exist, as it wouldn't be possible to pass > > + * to a disconnect() function the same lambda that was passed to connect(). The > > + * connection created by this function can not be removed selectively if the > > + * signal is connected to multiple slots of the same receiver, but may be > > + * otherwise be removed using the disconnect(T *object) function. > > I am not sure if I am understanding this correctly, but do you mean that > the functor version of Signal, should essentially be used within a class > inherited from Object /only/? No, what it means is that Signal::connect(T *object, R T::*func)(Args...)) has a matching Signal::disconnect(T *object, R (T::*func)(Args...)) function, but Signal::connect(T *object, Func func) doesn't have Signal::disconnect(T *object, Func func) so the Signal::disconnect(T *object) function should be used instead. > I ask this because, i the previous paragraph there is: > > + * Otherwise the caller shall disconnect signals manually before destroying \a > + * object. > > The 'Otherwise' seems to address the classes, that are not inherit from > Object. It's stated that signal within those needs manual disconnection. > But 'How?' isn't clear to me. > > The documentation doesn't seem to clarify to me that how a class _not_ > inheriting from Object handle disconnection of the signal? Can you > please clarify? > > > + * > > + * \context This function is \threadsafe. > > + */ > > + > > /** > > * \fn Signal::connect(R (*func)(Args...)) > > * \brief Connect the signal to a static function slot > > diff --git a/test/signal.cpp b/test/signal.cpp > > index 595782a2cd6e..fcf2def18df4 100644 > > --- a/test/signal.cpp > > +++ b/test/signal.cpp > > @@ -191,6 +191,24 @@ protected: > > signalVoid_.connect(slotStaticReturn); > > signalVoid_.connect(this, &SignalTest::slotReturn); > > > > + /* Test signal connection to a lambda. */ > > + int value = 0; > > + signalInt_.connect(this, [&](int v) { value = v; }); > > + signalInt_.emit(42); > > + > > + if (value != 42) { > > + cout << "Signal connection to lambda failed" << endl; > > + return TestFail; > > + } > > + > > + signalInt_.disconnect(this); > > + signalInt_.emit(0); > > + > > + if (value != 42) { > > + cout << "Signal disconnection from lambda failed" << endl; > > + return TestFail; > > + } > > + > > /* ----------------- Signal -> Object tests ----------------- */ > > > > /* > > @@ -256,6 +274,27 @@ protected: > > > > delete slotObject; > > > > + /* Test signal connection to a lambda. */ > > + slotObject = new SlotObject(); > > + value = 0; > > + signalInt_.connect(slotObject, [&](int v) { value = v; }); > > + signalInt_.emit(42); > > + > > + if (value != 42) { > > + cout << "Signal connection to Object lambda failed" << endl; > > + return TestFail; > > + } > > + > > + signalInt_.disconnect(slotObject); > > + signalInt_.emit(0); > > + > > + if (value != 42) { > > + cout << "Signal disconnection from Object lambda failed" << endl; > > + return TestFail; > > + } > > + > > + delete slotObject; > > + > > /* --------- Signal -> Object (multiple inheritance) -------- */ > > > > /*
Hi Laurent, On 8/31/21 3:02 PM, Laurent Pinchart wrote: > Hi Umang, > > On Tue, Aug 31, 2021 at 11:32:41AM +0530, Umang Jain wrote: >> Hi Laurent, >> >> Thanks for the patch. I have a few questions regarding the documentation >> >> On 8/27/21 8:08 AM, Laurent Pinchart wrote: >>> It can be useful to connect a signal to a functor, and in particular a >>> lambda function, while still operating in the context of a receiver >>> object (to support both object-based disconnection and queued >>> connections to Object instances). >>> >>> Add a BoundMethodFunctor class to bind a functor, and a corresponding >>> Signal::connect() function. There is no corresponding disconnect() >>> function, as a lambda passed to connect() can't be later passed to >>> disconnect(). Disconnection typically uses disconnect(T *object), which >>> will cover the vast majority of use cases. >>> >>> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> >>> --- >>> Documentation/Doxyfile.in | 1 + >>> include/libcamera/base/bound_method.h | 31 +++++++++++++++++++++ >>> include/libcamera/base/signal.h | 19 +++++++++++++ >>> src/libcamera/base/signal.cpp | 24 +++++++++++++++++ >>> test/signal.cpp | 39 +++++++++++++++++++++++++++ >>> 5 files changed, 114 insertions(+) >>> >>> diff --git a/Documentation/Doxyfile.in b/Documentation/Doxyfile.in >>> index dc03cbea4b02..d562510e902e 100644 >>> --- a/Documentation/Doxyfile.in >>> +++ b/Documentation/Doxyfile.in >>> @@ -878,6 +878,7 @@ EXCLUDE_PATTERNS = @TOP_BUILDDIR@/include/libcamera/ipa/*_serializer.h \ >>> >>> EXCLUDE_SYMBOLS = libcamera::BoundMethodArgs \ >>> libcamera::BoundMethodBase \ >>> + libcamera::BoundMethodFunctor \ >>> libcamera::BoundMethodMember \ >>> libcamera::BoundMethodPack \ >>> libcamera::BoundMethodPackBase \ >>> diff --git a/include/libcamera/base/bound_method.h b/include/libcamera/base/bound_method.h >>> index 76ce8017e721..ebd297ab8209 100644 >>> --- a/include/libcamera/base/bound_method.h >>> +++ b/include/libcamera/base/bound_method.h >>> @@ -128,6 +128,37 @@ public: >>> virtual R invoke(Args... args) = 0; >>> }; >>> >>> +template<typename T, typename R, typename Func, typename... Args> >>> +class BoundMethodFunctor : public BoundMethodArgs<R, Args...> >>> +{ >>> +public: >>> + using PackType = typename BoundMethodArgs<R, Args...>::PackType; >>> + >>> + BoundMethodFunctor(T *obj, Object *object, Func func, >>> + ConnectionType type = ConnectionTypeAuto) >>> + : BoundMethodArgs<R, Args...>(obj, object, type), func_(func) >>> + { >>> + } >>> + >>> + R activate(Args... args, bool deleteMethod = false) override >>> + { >>> + if (!this->object_) >>> + return func_(args...); >>> + >>> + auto pack = std::make_shared<PackType>(args...); >>> + bool sync = BoundMethodBase::activatePack(pack, deleteMethod); >>> + return sync ? pack->returnValue() : R(); >>> + } >>> + >>> + R invoke(Args... args) override >>> + { >>> + return func_(args...); >>> + } >>> + >>> +private: >>> + Func func_; >>> +}; >>> + >>> template<typename T, typename R, typename... Args> >>> class BoundMethodMember : public BoundMethodArgs<R, Args...> >>> { >>> diff --git a/include/libcamera/base/signal.h b/include/libcamera/base/signal.h >>> index c2521769a843..8d9f82f62d0d 100644 >>> --- a/include/libcamera/base/signal.h >>> +++ b/include/libcamera/base/signal.h >>> @@ -61,6 +61,25 @@ public: >>> SignalBase::connect(new BoundMethodMember<T, R, Args...>(obj, nullptr, func)); >>> } >>> >>> +#ifndef __DOXYGEN__ >>> + template<typename T, typename Func, >>> + typename std::enable_if_t<std::is_base_of<Object, T>::value> * = nullptr> >>> + void connect(T *obj, Func func, ConnectionType type = ConnectionTypeAuto) >>> + { >>> + Object *object = static_cast<Object *>(obj); >>> + SignalBase::connect(new BoundMethodFunctor<T, void, Func, Args...>(obj, object, func, type)); >>> + } >>> + >>> + template<typename T, typename Func, >>> + typename std::enable_if_t<!std::is_base_of<Object, T>::value> * = nullptr> >>> +#else >>> + template<typename T, typename Func> >>> +#endif >>> + void connect(T *obj, Func func) >>> + { >>> + SignalBase::connect(new BoundMethodFunctor<T, void, Func, Args...>(obj, nullptr, func)); >>> + } >>> + >>> template<typename R> >>> void connect(R (*func)(Args...)) >>> { >>> diff --git a/src/libcamera/base/signal.cpp b/src/libcamera/base/signal.cpp >>> index adcfa796870e..9c2319c59106 100644 >>> --- a/src/libcamera/base/signal.cpp >>> +++ b/src/libcamera/base/signal.cpp >>> @@ -121,6 +121,30 @@ SignalBase::SlotList SignalBase::slots() >>> * \context This function is \threadsafe. >>> */ >>> >>> +/** >>> + * \fn Signal::connect(T *object, Func func) >>> + * \brief Connect the signal to a function object slot >>> + * \param[in] object The slot object pointer >>> + * \param[in] func The function object >>> + * >>> + * If the typename T inherits from Object, the signal will be automatically >>> + * disconnected from the \a func slot of \a object when \a object is destroyed. >>> + * Otherwise the caller shall disconnect signals manually before destroying \a >>> + * object. >> So, if I have a instance _not_ inherited from Object, how would I >> manually disconnect the signal ? > With one of the disconnect() functions of the signal. > >>> + * >>> + * The function object is typically a lambda function, but may be any object >>> + * that satisfies the FunctionObject named requirements. The types of the >>> + * function object arguments shall match the types of the signal arguments. >>> + * >>> + * No matching disconnect() function exist, as it wouldn't be possible to pass >>> + * to a disconnect() function the same lambda that was passed to connect(). The >>> + * connection created by this function can not be removed selectively if the >>> + * signal is connected to multiple slots of the same receiver, but may be >>> + * otherwise be removed using the disconnect(T *object) function. >> I am not sure if I am understanding this correctly, but do you mean that >> the functor version of Signal, should essentially be used within a class >> inherited from Object /only/? > No, what it means is that > > Signal::connect(T *object, R T::*func)(Args...)) > > has a matching > > Signal::disconnect(T *object, R (T::*func)(Args...)) Ah yeah, duh me! > > function, but > > Signal::connect(T *object, Func func) > > doesn't have > > Signal::disconnect(T *object, Func func) > > so the > > Signal::disconnect(T *object) > > function should be used instead. Got it. Reviewed-by: Umang Jain <umang.jain@ideasonboard.com> thanks for clarifications :D > >> I ask this because, i the previous paragraph there is: >> >> + * Otherwise the caller shall disconnect signals manually before destroying \a >> + * object. >> >> The 'Otherwise' seems to address the classes, that are not inherit from >> Object. It's stated that signal within those needs manual disconnection. >> But 'How?' isn't clear to me. >> >> The documentation doesn't seem to clarify to me that how a class _not_ >> inheriting from Object handle disconnection of the signal? Can you >> please clarify? >> >>> + * >>> + * \context This function is \threadsafe. >>> + */ >>> + >>> /** >>> * \fn Signal::connect(R (*func)(Args...)) >>> * \brief Connect the signal to a static function slot >>> diff --git a/test/signal.cpp b/test/signal.cpp >>> index 595782a2cd6e..fcf2def18df4 100644 >>> --- a/test/signal.cpp >>> +++ b/test/signal.cpp >>> @@ -191,6 +191,24 @@ protected: >>> signalVoid_.connect(slotStaticReturn); >>> signalVoid_.connect(this, &SignalTest::slotReturn); >>> >>> + /* Test signal connection to a lambda. */ >>> + int value = 0; >>> + signalInt_.connect(this, [&](int v) { value = v; }); >>> + signalInt_.emit(42); >>> + >>> + if (value != 42) { >>> + cout << "Signal connection to lambda failed" << endl; >>> + return TestFail; >>> + } >>> + >>> + signalInt_.disconnect(this); >>> + signalInt_.emit(0); >>> + >>> + if (value != 42) { >>> + cout << "Signal disconnection from lambda failed" << endl; >>> + return TestFail; >>> + } >>> + >>> /* ----------------- Signal -> Object tests ----------------- */ >>> >>> /* >>> @@ -256,6 +274,27 @@ protected: >>> >>> delete slotObject; >>> >>> + /* Test signal connection to a lambda. */ >>> + slotObject = new SlotObject(); >>> + value = 0; >>> + signalInt_.connect(slotObject, [&](int v) { value = v; }); >>> + signalInt_.emit(42); >>> + >>> + if (value != 42) { >>> + cout << "Signal connection to Object lambda failed" << endl; >>> + return TestFail; >>> + } >>> + >>> + signalInt_.disconnect(slotObject); >>> + signalInt_.emit(0); >>> + >>> + if (value != 42) { >>> + cout << "Signal disconnection from Object lambda failed" << endl; >>> + return TestFail; >>> + } >>> + >>> + delete slotObject; >>> + >>> /* --------- Signal -> Object (multiple inheritance) -------- */ >>> >>> /*
diff --git a/Documentation/Doxyfile.in b/Documentation/Doxyfile.in index dc03cbea4b02..d562510e902e 100644 --- a/Documentation/Doxyfile.in +++ b/Documentation/Doxyfile.in @@ -878,6 +878,7 @@ EXCLUDE_PATTERNS = @TOP_BUILDDIR@/include/libcamera/ipa/*_serializer.h \ EXCLUDE_SYMBOLS = libcamera::BoundMethodArgs \ libcamera::BoundMethodBase \ + libcamera::BoundMethodFunctor \ libcamera::BoundMethodMember \ libcamera::BoundMethodPack \ libcamera::BoundMethodPackBase \ diff --git a/include/libcamera/base/bound_method.h b/include/libcamera/base/bound_method.h index 76ce8017e721..ebd297ab8209 100644 --- a/include/libcamera/base/bound_method.h +++ b/include/libcamera/base/bound_method.h @@ -128,6 +128,37 @@ public: virtual R invoke(Args... args) = 0; }; +template<typename T, typename R, typename Func, typename... Args> +class BoundMethodFunctor : public BoundMethodArgs<R, Args...> +{ +public: + using PackType = typename BoundMethodArgs<R, Args...>::PackType; + + BoundMethodFunctor(T *obj, Object *object, Func func, + ConnectionType type = ConnectionTypeAuto) + : BoundMethodArgs<R, Args...>(obj, object, type), func_(func) + { + } + + R activate(Args... args, bool deleteMethod = false) override + { + if (!this->object_) + return func_(args...); + + auto pack = std::make_shared<PackType>(args...); + bool sync = BoundMethodBase::activatePack(pack, deleteMethod); + return sync ? pack->returnValue() : R(); + } + + R invoke(Args... args) override + { + return func_(args...); + } + +private: + Func func_; +}; + template<typename T, typename R, typename... Args> class BoundMethodMember : public BoundMethodArgs<R, Args...> { diff --git a/include/libcamera/base/signal.h b/include/libcamera/base/signal.h index c2521769a843..8d9f82f62d0d 100644 --- a/include/libcamera/base/signal.h +++ b/include/libcamera/base/signal.h @@ -61,6 +61,25 @@ public: SignalBase::connect(new BoundMethodMember<T, R, Args...>(obj, nullptr, func)); } +#ifndef __DOXYGEN__ + template<typename T, typename Func, + typename std::enable_if_t<std::is_base_of<Object, T>::value> * = nullptr> + void connect(T *obj, Func func, ConnectionType type = ConnectionTypeAuto) + { + Object *object = static_cast<Object *>(obj); + SignalBase::connect(new BoundMethodFunctor<T, void, Func, Args...>(obj, object, func, type)); + } + + template<typename T, typename Func, + typename std::enable_if_t<!std::is_base_of<Object, T>::value> * = nullptr> +#else + template<typename T, typename Func> +#endif + void connect(T *obj, Func func) + { + SignalBase::connect(new BoundMethodFunctor<T, void, Func, Args...>(obj, nullptr, func)); + } + template<typename R> void connect(R (*func)(Args...)) { diff --git a/src/libcamera/base/signal.cpp b/src/libcamera/base/signal.cpp index adcfa796870e..9c2319c59106 100644 --- a/src/libcamera/base/signal.cpp +++ b/src/libcamera/base/signal.cpp @@ -121,6 +121,30 @@ SignalBase::SlotList SignalBase::slots() * \context This function is \threadsafe. */ +/** + * \fn Signal::connect(T *object, Func func) + * \brief Connect the signal to a function object slot + * \param[in] object The slot object pointer + * \param[in] func The function object + * + * If the typename T inherits from Object, the signal will be automatically + * disconnected from the \a func slot of \a object when \a object is destroyed. + * Otherwise the caller shall disconnect signals manually before destroying \a + * object. + * + * The function object is typically a lambda function, but may be any object + * that satisfies the FunctionObject named requirements. The types of the + * function object arguments shall match the types of the signal arguments. + * + * No matching disconnect() function exist, as it wouldn't be possible to pass + * to a disconnect() function the same lambda that was passed to connect(). The + * connection created by this function can not be removed selectively if the + * signal is connected to multiple slots of the same receiver, but may be + * otherwise be removed using the disconnect(T *object) function. + * + * \context This function is \threadsafe. + */ + /** * \fn Signal::connect(R (*func)(Args...)) * \brief Connect the signal to a static function slot diff --git a/test/signal.cpp b/test/signal.cpp index 595782a2cd6e..fcf2def18df4 100644 --- a/test/signal.cpp +++ b/test/signal.cpp @@ -191,6 +191,24 @@ protected: signalVoid_.connect(slotStaticReturn); signalVoid_.connect(this, &SignalTest::slotReturn); + /* Test signal connection to a lambda. */ + int value = 0; + signalInt_.connect(this, [&](int v) { value = v; }); + signalInt_.emit(42); + + if (value != 42) { + cout << "Signal connection to lambda failed" << endl; + return TestFail; + } + + signalInt_.disconnect(this); + signalInt_.emit(0); + + if (value != 42) { + cout << "Signal disconnection from lambda failed" << endl; + return TestFail; + } + /* ----------------- Signal -> Object tests ----------------- */ /* @@ -256,6 +274,27 @@ protected: delete slotObject; + /* Test signal connection to a lambda. */ + slotObject = new SlotObject(); + value = 0; + signalInt_.connect(slotObject, [&](int v) { value = v; }); + signalInt_.emit(42); + + if (value != 42) { + cout << "Signal connection to Object lambda failed" << endl; + return TestFail; + } + + signalInt_.disconnect(slotObject); + signalInt_.emit(0); + + if (value != 42) { + cout << "Signal disconnection from Object lambda failed" << endl; + return TestFail; + } + + delete slotObject; + /* --------- Signal -> Object (multiple inheritance) -------- */ /*
It can be useful to connect a signal to a functor, and in particular a lambda function, while still operating in the context of a receiver object (to support both object-based disconnection and queued connections to Object instances). Add a BoundMethodFunctor class to bind a functor, and a corresponding Signal::connect() function. There is no corresponding disconnect() function, as a lambda passed to connect() can't be later passed to disconnect(). Disconnection typically uses disconnect(T *object), which will cover the vast majority of use cases. Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> --- Documentation/Doxyfile.in | 1 + include/libcamera/base/bound_method.h | 31 +++++++++++++++++++++ include/libcamera/base/signal.h | 19 +++++++++++++ src/libcamera/base/signal.cpp | 24 +++++++++++++++++ test/signal.cpp | 39 +++++++++++++++++++++++++++ 5 files changed, 114 insertions(+)