[libcamera-devel,3/6] libcamera: signal: Support cross-thread signals

Message ID 20190710191708.13049-3-laurent.pinchart@ideasonboard.com
State Accepted
Headers show
Series
  • [libcamera-devel,1/6] libcamera: Add thread support
Related show

Commit Message

Laurent Pinchart July 10, 2019, 7:17 p.m. UTC
Allow signals to cross thread boundaries by posting them to the
recipient through messages instead of calling the slot directly when the
recipient lives in a different thread.

Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
---
 include/libcamera/signal.h      | 70 +++++++++++++++++++++++++++++----
 src/libcamera/include/message.h | 14 +++++++
 src/libcamera/message.cpp       | 22 +++++++++++
 src/libcamera/object.cpp        | 15 +++++++
 src/libcamera/signal.cpp        | 26 ++++++++++++
 5 files changed, 139 insertions(+), 8 deletions(-)

Comments

Niklas Söderlund July 11, 2019, 5:39 a.m. UTC | #1
Hi Laurent,

Thanks for your patch.

On 2019-07-10 22:17:05 +0300, Laurent Pinchart wrote:
> Allow signals to cross thread boundaries by posting them to the
> recipient through messages instead of calling the slot directly when the
> recipient lives in a different thread.
> 
> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> ---
>  include/libcamera/signal.h      | 70 +++++++++++++++++++++++++++++----
>  src/libcamera/include/message.h | 14 +++++++
>  src/libcamera/message.cpp       | 22 +++++++++++
>  src/libcamera/object.cpp        | 15 +++++++
>  src/libcamera/signal.cpp        | 26 ++++++++++++
>  5 files changed, 139 insertions(+), 8 deletions(-)
> 
> diff --git a/include/libcamera/signal.h b/include/libcamera/signal.h
> index c8f3243eaf5a..04e8fe7eebe3 100644
> --- a/include/libcamera/signal.h
> +++ b/include/libcamera/signal.h
> @@ -8,6 +8,8 @@
>  #define __LIBCAMERA_SIGNAL_H__
>  
>  #include <list>
> +#include <tuple>
> +#include <type_traits>
>  #include <vector>
>  
>  #include <libcamera/object.h>
> @@ -16,6 +18,9 @@ namespace libcamera {
>  
>  template<typename... Args>
>  class Signal;
> +class SlotBase;
> +template<typename... Args>
> +class SlotArgs;

These forward declarations  don't seems to be needed right?

With this addressed,

Reviewed-by: Niklas Söderlund <niklas.soderlund@ragnatech.se>

>  
>  class SlotBase
>  {
> @@ -27,6 +32,9 @@ public:
>  	void *obj() { return obj_; }
>  	bool isObject() const { return isObject_; }
>  
> +	void activatePack(void *pack);
> +	virtual void invokePack(void *pack) = 0;
> +
>  protected:
>  	void *obj_;
>  	bool isObject_;
> @@ -35,24 +43,70 @@ protected:
>  template<typename... Args>
>  class SlotArgs : public SlotBase
>  {
> +private:
> +#ifndef __DOXYGEN__
> +	/*
> +	 * This is a cheap partial implementation of std::integer_sequence<>
> +	 * from C++14.
> +	 */
> +	template<int...>
> +	struct sequence {
> +	};
> +
> +	template<int N, int... S>
> +	struct generator : generator<N-1, N-1, S...> {
> +	};
> +
> +	template<int... S>
> +	struct generator<0, S...> {
> +		typedef sequence<S...> type;
> +	};
> +#endif
> +
>  public:
> +	using PackType = std::tuple<typename std::remove_reference<Args>::type...>;
> +
>  	SlotArgs(void *obj, bool isObject)
>  		: SlotBase(obj, isObject) {}
>  
> +	void invokePack(void *pack) override
> +	{
> +		invokePack(pack, typename generator<sizeof...(Args)>::type());
> +	}
> +
> +	template<int... S>
> +	void invokePack(void *pack, sequence<S...>)
> +	{
> +		PackType *args = static_cast<PackType *>(pack);
> +		invoke(std::get<S>(*args)...);
> +		delete args;
> +	}
> +
> +	virtual void activate(Args... args) = 0;
>  	virtual void invoke(Args... args) = 0;
> -
> -protected:
> -	friend class Signal<Args...>;
>  };
>  
>  template<typename T, typename... Args>
>  class SlotMember : public SlotArgs<Args...>
>  {
>  public:
> +	using PackType = std::tuple<typename std::remove_reference<Args>::type...>;
> +
>  	SlotMember(T *obj, bool isObject, void (T::*func)(Args...))
>  		: SlotArgs<Args...>(obj, isObject), func_(func) {}
>  
> -	void invoke(Args... args) { (static_cast<T *>(this->obj_)->*func_)(args...); }
> +	void activate(Args... args)
> +	{
> +		if (this->isObject_)
> +			SlotBase::activatePack(new PackType{ args... });
> +		else
> +			(static_cast<T *>(this->obj_)->*func_)(args...);
> +	}
> +
> +	void invoke(Args... args)
> +	{
> +		(static_cast<T *>(this->obj_)->*func_)(args...);
> +	}
>  
>  private:
>  	friend class Signal<Args...>;
> @@ -66,7 +120,8 @@ public:
>  	SlotStatic(void (*func)(Args...))
>  		: SlotArgs<Args...>(nullptr, false), func_(func) {}
>  
> -	void invoke(Args... args) { (*func_)(args...); }
> +	void activate(Args... args) { (*func_)(args...); }
> +	void invoke(Args... args) {}
>  
>  private:
>  	friend class Signal<Args...>;
> @@ -186,9 +241,8 @@ public:
>  		 * disconnect operation, invalidating the iterator.
>  		 */
>  		std::vector<SlotBase *> slots{ slots_.begin(), slots_.end() };
> -		for (SlotBase *slot : slots) {
> -			static_cast<SlotArgs<Args...> *>(slot)->invoke(args...);
> -		}
> +		for (SlotBase *slot : slots)
> +			static_cast<SlotArgs<Args...> *>(slot)->activate(args...);
>  	}
>  };
>  
> diff --git a/src/libcamera/include/message.h b/src/libcamera/include/message.h
> index 97c9b80ec0e0..db17d647c280 100644
> --- a/src/libcamera/include/message.h
> +++ b/src/libcamera/include/message.h
> @@ -10,6 +10,7 @@
>  namespace libcamera {
>  
>  class Object;
> +class SlotBase;
>  class Thread;
>  
>  class Message
> @@ -17,6 +18,7 @@ class Message
>  public:
>  	enum Type {
>  		None = 0,
> +		SignalMessage = 1,
>  	};
>  
>  	Message(Type type);
> @@ -32,6 +34,18 @@ private:
>  	Object *receiver_;
>  };
>  
> +class SignalMessage : public Message
> +{
> +public:
> +	SignalMessage(SlotBase *slot, void *pack)
> +		: Message(Message::SignalMessage), slot_(slot), pack_(pack)
> +	{
> +	}
> +
> +	SlotBase *slot_;
> +	void *pack_;
> +};
> +
>  } /* namespace libcamera */
>  
>  #endif /* __LIBCAMERA_MESSAGE_H__ */
> diff --git a/src/libcamera/message.cpp b/src/libcamera/message.cpp
> index 47caf44dc82d..66dd1e8bd618 100644
> --- a/src/libcamera/message.cpp
> +++ b/src/libcamera/message.cpp
> @@ -68,4 +68,26 @@ Message::~Message()
>   * \return The message receiver
>   */
>  
> +/**
> + * \class SignalMessage
> + * \brief A message carrying a Signal across threads
> + */
> +
> +/**
> + * \fn SignalMessage::SignalMessage()
> + * \brief Construct a SignalMessage
> + * \param[in] slot The slot that the signal targets
> + * \param[in] pack The signal arguments
> + */
> +
> +/**
> + * \var SignalMessage::slot_
> + * \brief The slot that the signal targets
> + */
> +
> +/**
> + * \var SignalMessage::pack_
> + * \brief The signal arguments
> + */
> +
>  }; /* namespace libcamera */
> diff --git a/src/libcamera/object.cpp b/src/libcamera/object.cpp
> index 695e6c11b3a4..1dfa159267fe 100644
> --- a/src/libcamera/object.cpp
> +++ b/src/libcamera/object.cpp
> @@ -10,6 +10,7 @@
>  #include <libcamera/signal.h>
>  
>  #include "log.h"
> +#include "message.h"
>  #include "thread.h"
>  
>  /**
> @@ -32,6 +33,10 @@ namespace libcamera {
>   * This allows implementing easy message passing between threads by inheriting
>   * from the Object class.
>   *
> + * Object slots connected to signals will also run in the context of the
> + * object's thread, regardless of whether the signal is emitted in the same or
> + * in another thread.
> + *
>   * \sa Message, Signal, Thread
>   */
>  
> @@ -82,6 +87,16 @@ void Object::postMessage(std::unique_ptr<Message> msg)
>   */
>  void Object::message(Message *msg)
>  {
> +	switch (msg->type()) {
> +	case Message::SignalMessage: {
> +		SignalMessage *smsg = static_cast<SignalMessage *>(msg);
> +		smsg->slot_->invokePack(smsg->pack_);
> +		break;
> +	}
> +
> +	default:
> +		break;
> +	}
>  }
>  
>  /**
> diff --git a/src/libcamera/signal.cpp b/src/libcamera/signal.cpp
> index 4cb85ecb0686..53c18535fee3 100644
> --- a/src/libcamera/signal.cpp
> +++ b/src/libcamera/signal.cpp
> @@ -7,6 +7,10 @@
>  
>  #include <libcamera/signal.h>
>  
> +#include "message.h"
> +#include "thread.h"
> +#include "utils.h"
> +
>  /**
>   * \file signal.h
>   * \brief Signal & slot implementation
> @@ -42,8 +46,30 @@ namespace libcamera {
>   * to the same slot. Duplicate connections between a signal and a slot are
>   * allowed and result in the slot being called multiple times for the same
>   * signal emission.
> + *
> + * When a slot belongs to an instance of the Object class, the slot is called
> + * in the context of the thread that the object is bound to. If the signal is
> + * emitted from the same thread, the slot will be called synchronously, before
> + * Signal::emit() returns. If the signal is emitted from a different thread,
> + * the slot will be called asynchronously from the object's thread's event
> + * loop, after the Signal::emit() method returns, with a copy of the signal's
> + * arguments. The emitter shall thus ensure that any pointer or reference
> + * passed through the signal will remain valid after the signal is emitted.
>   */
>  
> +void SlotBase::activatePack(void *pack)
> +{
> +	Object *obj = static_cast<Object *>(obj_);
> +
> +	if (Thread::current() == obj->thread()) {
> +		invokePack(pack);
> +	} else {
> +		std::unique_ptr<Message> msg =
> +			utils::make_unique<SignalMessage>(this, pack);
> +		obj->postMessage(std::move(msg));
> +	}
> +}
> +
>  /**
>   * \fn Signal::connect(T *object, void(T::*func)(Args...))
>   * \brief Connect the signal to a member function slot
> -- 
> Regards,
> 
> Laurent Pinchart
> 
> _______________________________________________
> libcamera-devel mailing list
> libcamera-devel@lists.libcamera.org
> https://lists.libcamera.org/listinfo/libcamera-devel

Patch

diff --git a/include/libcamera/signal.h b/include/libcamera/signal.h
index c8f3243eaf5a..04e8fe7eebe3 100644
--- a/include/libcamera/signal.h
+++ b/include/libcamera/signal.h
@@ -8,6 +8,8 @@ 
 #define __LIBCAMERA_SIGNAL_H__
 
 #include <list>
+#include <tuple>
+#include <type_traits>
 #include <vector>
 
 #include <libcamera/object.h>
@@ -16,6 +18,9 @@  namespace libcamera {
 
 template<typename... Args>
 class Signal;
+class SlotBase;
+template<typename... Args>
+class SlotArgs;
 
 class SlotBase
 {
@@ -27,6 +32,9 @@  public:
 	void *obj() { return obj_; }
 	bool isObject() const { return isObject_; }
 
+	void activatePack(void *pack);
+	virtual void invokePack(void *pack) = 0;
+
 protected:
 	void *obj_;
 	bool isObject_;
@@ -35,24 +43,70 @@  protected:
 template<typename... Args>
 class SlotArgs : public SlotBase
 {
+private:
+#ifndef __DOXYGEN__
+	/*
+	 * This is a cheap partial implementation of std::integer_sequence<>
+	 * from C++14.
+	 */
+	template<int...>
+	struct sequence {
+	};
+
+	template<int N, int... S>
+	struct generator : generator<N-1, N-1, S...> {
+	};
+
+	template<int... S>
+	struct generator<0, S...> {
+		typedef sequence<S...> type;
+	};
+#endif
+
 public:
+	using PackType = std::tuple<typename std::remove_reference<Args>::type...>;
+
 	SlotArgs(void *obj, bool isObject)
 		: SlotBase(obj, isObject) {}
 
+	void invokePack(void *pack) override
+	{
+		invokePack(pack, typename generator<sizeof...(Args)>::type());
+	}
+
+	template<int... S>
+	void invokePack(void *pack, sequence<S...>)
+	{
+		PackType *args = static_cast<PackType *>(pack);
+		invoke(std::get<S>(*args)...);
+		delete args;
+	}
+
+	virtual void activate(Args... args) = 0;
 	virtual void invoke(Args... args) = 0;
-
-protected:
-	friend class Signal<Args...>;
 };
 
 template<typename T, typename... Args>
 class SlotMember : public SlotArgs<Args...>
 {
 public:
+	using PackType = std::tuple<typename std::remove_reference<Args>::type...>;
+
 	SlotMember(T *obj, bool isObject, void (T::*func)(Args...))
 		: SlotArgs<Args...>(obj, isObject), func_(func) {}
 
-	void invoke(Args... args) { (static_cast<T *>(this->obj_)->*func_)(args...); }
+	void activate(Args... args)
+	{
+		if (this->isObject_)
+			SlotBase::activatePack(new PackType{ args... });
+		else
+			(static_cast<T *>(this->obj_)->*func_)(args...);
+	}
+
+	void invoke(Args... args)
+	{
+		(static_cast<T *>(this->obj_)->*func_)(args...);
+	}
 
 private:
 	friend class Signal<Args...>;
@@ -66,7 +120,8 @@  public:
 	SlotStatic(void (*func)(Args...))
 		: SlotArgs<Args...>(nullptr, false), func_(func) {}
 
-	void invoke(Args... args) { (*func_)(args...); }
+	void activate(Args... args) { (*func_)(args...); }
+	void invoke(Args... args) {}
 
 private:
 	friend class Signal<Args...>;
@@ -186,9 +241,8 @@  public:
 		 * disconnect operation, invalidating the iterator.
 		 */
 		std::vector<SlotBase *> slots{ slots_.begin(), slots_.end() };
-		for (SlotBase *slot : slots) {
-			static_cast<SlotArgs<Args...> *>(slot)->invoke(args...);
-		}
+		for (SlotBase *slot : slots)
+			static_cast<SlotArgs<Args...> *>(slot)->activate(args...);
 	}
 };
 
diff --git a/src/libcamera/include/message.h b/src/libcamera/include/message.h
index 97c9b80ec0e0..db17d647c280 100644
--- a/src/libcamera/include/message.h
+++ b/src/libcamera/include/message.h
@@ -10,6 +10,7 @@ 
 namespace libcamera {
 
 class Object;
+class SlotBase;
 class Thread;
 
 class Message
@@ -17,6 +18,7 @@  class Message
 public:
 	enum Type {
 		None = 0,
+		SignalMessage = 1,
 	};
 
 	Message(Type type);
@@ -32,6 +34,18 @@  private:
 	Object *receiver_;
 };
 
+class SignalMessage : public Message
+{
+public:
+	SignalMessage(SlotBase *slot, void *pack)
+		: Message(Message::SignalMessage), slot_(slot), pack_(pack)
+	{
+	}
+
+	SlotBase *slot_;
+	void *pack_;
+};
+
 } /* namespace libcamera */
 
 #endif /* __LIBCAMERA_MESSAGE_H__ */
diff --git a/src/libcamera/message.cpp b/src/libcamera/message.cpp
index 47caf44dc82d..66dd1e8bd618 100644
--- a/src/libcamera/message.cpp
+++ b/src/libcamera/message.cpp
@@ -68,4 +68,26 @@  Message::~Message()
  * \return The message receiver
  */
 
+/**
+ * \class SignalMessage
+ * \brief A message carrying a Signal across threads
+ */
+
+/**
+ * \fn SignalMessage::SignalMessage()
+ * \brief Construct a SignalMessage
+ * \param[in] slot The slot that the signal targets
+ * \param[in] pack The signal arguments
+ */
+
+/**
+ * \var SignalMessage::slot_
+ * \brief The slot that the signal targets
+ */
+
+/**
+ * \var SignalMessage::pack_
+ * \brief The signal arguments
+ */
+
 }; /* namespace libcamera */
diff --git a/src/libcamera/object.cpp b/src/libcamera/object.cpp
index 695e6c11b3a4..1dfa159267fe 100644
--- a/src/libcamera/object.cpp
+++ b/src/libcamera/object.cpp
@@ -10,6 +10,7 @@ 
 #include <libcamera/signal.h>
 
 #include "log.h"
+#include "message.h"
 #include "thread.h"
 
 /**
@@ -32,6 +33,10 @@  namespace libcamera {
  * This allows implementing easy message passing between threads by inheriting
  * from the Object class.
  *
+ * Object slots connected to signals will also run in the context of the
+ * object's thread, regardless of whether the signal is emitted in the same or
+ * in another thread.
+ *
  * \sa Message, Signal, Thread
  */
 
@@ -82,6 +87,16 @@  void Object::postMessage(std::unique_ptr<Message> msg)
  */
 void Object::message(Message *msg)
 {
+	switch (msg->type()) {
+	case Message::SignalMessage: {
+		SignalMessage *smsg = static_cast<SignalMessage *>(msg);
+		smsg->slot_->invokePack(smsg->pack_);
+		break;
+	}
+
+	default:
+		break;
+	}
 }
 
 /**
diff --git a/src/libcamera/signal.cpp b/src/libcamera/signal.cpp
index 4cb85ecb0686..53c18535fee3 100644
--- a/src/libcamera/signal.cpp
+++ b/src/libcamera/signal.cpp
@@ -7,6 +7,10 @@ 
 
 #include <libcamera/signal.h>
 
+#include "message.h"
+#include "thread.h"
+#include "utils.h"
+
 /**
  * \file signal.h
  * \brief Signal & slot implementation
@@ -42,8 +46,30 @@  namespace libcamera {
  * to the same slot. Duplicate connections between a signal and a slot are
  * allowed and result in the slot being called multiple times for the same
  * signal emission.
+ *
+ * When a slot belongs to an instance of the Object class, the slot is called
+ * in the context of the thread that the object is bound to. If the signal is
+ * emitted from the same thread, the slot will be called synchronously, before
+ * Signal::emit() returns. If the signal is emitted from a different thread,
+ * the slot will be called asynchronously from the object's thread's event
+ * loop, after the Signal::emit() method returns, with a copy of the signal's
+ * arguments. The emitter shall thus ensure that any pointer or reference
+ * passed through the signal will remain valid after the signal is emitted.
  */
 
+void SlotBase::activatePack(void *pack)
+{
+	Object *obj = static_cast<Object *>(obj_);
+
+	if (Thread::current() == obj->thread()) {
+		invokePack(pack);
+	} else {
+		std::unique_ptr<Message> msg =
+			utils::make_unique<SignalMessage>(this, pack);
+		obj->postMessage(std::move(msg));
+	}
+}
+
 /**
  * \fn Signal::connect(T *object, void(T::*func)(Args...))
  * \brief Connect the signal to a member function slot