[{"id":242,"web_url":"https://patchwork.libcamera.org/comment/242/","msgid":"<20190108092915.amzck7onoo7tbdwd@uno.localdomain>","date":"2019-01-08T09:29:15","subject":"Re: [libcamera-devel] [PATCH v2 05/11] libcamera: Add signal/slot\n\tcommunication mechanism","submitter":{"id":3,"url":"https://patchwork.libcamera.org/api/people/3/","name":"Jacopo Mondi","email":"jacopo@jmondi.org"},"content":"Hi Laurent,\n  a really stupid nit below, otherwise\n\nReviewed-by: Jacopo Mondi <jacopo@jmondi.org>\n\nOn Tue, Jan 08, 2019 at 01:11:45AM +0200, Laurent Pinchart wrote:\n> Introduce a Signal class that allows connecting event sources (signals)\n> to event listeners (slots) without adding any boilerplate code usually\n> associated with the observer or listener design patterns.\n>\n> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>\n> Reviewed-by: Niklas Söderlund <niklas.soderlund@ragnatech.se>\n> ---\n> Changes since v1:\n>\n> - Improve documentation\n> - Add support for static slots\n> ---\n>  Documentation/Doxyfile.in     |   4 +-\n>  include/libcamera/meson.build |   1 +\n>  include/libcamera/signal.h    | 154 ++++++++++++++++++++++++++++++++++\n>  src/libcamera/meson.build     |   1 +\n>  src/libcamera/signal.cpp      |  84 +++++++++++++++++++\n>  5 files changed, 243 insertions(+), 1 deletion(-)\n>  create mode 100644 include/libcamera/signal.h\n>  create mode 100644 src/libcamera/signal.cpp\n>\n> diff --git a/Documentation/Doxyfile.in b/Documentation/Doxyfile.in\n> index b1a70d36eee5..aac20839c837 100644\n> --- a/Documentation/Doxyfile.in\n> +++ b/Documentation/Doxyfile.in\n> @@ -860,7 +860,9 @@ EXCLUDE_PATTERNS       =\n>  # Note that the wildcards are matched against the file with absolute path, so to\n>  # exclude all test directories use the pattern */test/*\n>\n> -EXCLUDE_SYMBOLS        =\n> +EXCLUDE_SYMBOLS        = libcamera::SlotBase \\\n> +                         libcamera::SlotMember \\\n> +                         libcamera::SlotStatic\n>\n>  # The EXAMPLE_PATH tag can be used to specify one or more files or directories\n>  # that contain example code fragments that are included (see the \\include\n> diff --git a/include/libcamera/meson.build b/include/libcamera/meson.build\n> index 3e04557d66b1..6f87689ea528 100644\n> --- a/include/libcamera/meson.build\n> +++ b/include/libcamera/meson.build\n> @@ -2,6 +2,7 @@ libcamera_api = files([\n>      'camera.h',\n>      'camera_manager.h',\n>      'libcamera.h',\n> +    'signal.h',\n>  ])\n>\n>  install_headers(libcamera_api,\n> diff --git a/include/libcamera/signal.h b/include/libcamera/signal.h\n> new file mode 100644\n> index 000000000000..458db1ac0060\n> --- /dev/null\n> +++ b/include/libcamera/signal.h\n> @@ -0,0 +1,154 @@\n> +/* SPDX-License-Identifier: LGPL-2.1-or-later */\n> +/*\n> + * Copyright (C) 2019, Google Inc.\n> + *\n> + * signal.h - Signal & slot implementation\n> + */\n> +#ifndef __LIBCAMERA_SIGNAL_H__\n> +#define __LIBCAMERA_SIGNAL_H__\n> +\n> +#include <list>\n> +#include <vector>\n> +\n> +namespace libcamera {\n> +\n> +template<typename... Args>\n> +class Signal;\n> +\n> +template<typename... Args>\n> +class SlotBase\n> +{\n> +public:\n> +\tSlotBase(void *obj)\n> +\t\t: obj_(obj) { }\n> +\tvirtual ~SlotBase() { }\n> +\n> +\tvirtual void invoke(Args... args) = 0;\n> +\n> +protected:\n> +\tfriend class Signal<Args...>;\n> +\tvoid *obj_;\n> +};\n> +\n> +template<typename T, typename... Args>\n> +class SlotMember : public SlotBase<Args...>\n> +{\n> +public:\n> +\tSlotMember(T *obj, void(T::*func)(Args...))\n> +\t\t: SlotBase<Args...>(obj), func_(func) { }\n> +\n> +\tvoid invoke(Args... args) { (reinterpret_cast<T *>(this->obj_)->*func_)(args...); }\n> +\n> +private:\n> +\tfriend class Signal<Args...>;\n> +\tvoid(T::*func_)(Args...);\n> +};\n> +\n> +template<typename... Args>\n> +class SlotStatic : public SlotBase<Args...>\n> +{\n> +public:\n> +\tSlotStatic(void(*func)(Args...))\n> +\t\t: SlotBase<Args...>(nullptr), func_(func) { }\n> +\n> +\tvoid invoke(Args... args) { (*func_)(args...); }\n> +\n> +private:\n> +\tfriend class Signal<Args...>;\n> +\tvoid(*func_)(Args...);\n> +};\n> +\n> +template<typename... Args>\n> +class Signal\n> +{\n> +public:\n> +\tSignal() { }\n> +\t~Signal()\n> +\t{\n> +\t\tfor (SlotBase<Args...> *slot : slots_)\n> +\t\t\tdelete slot;\n> +\t}\n> +\n> +\ttemplate<typename T>\n> +\tvoid connect(T *object, void(T::*func)(Args...))\n> +\t{\n> +\t\tslots_.push_back(new SlotMember<T, Args...>(object, func));\n> +\t}\n> +\n> +\tvoid connect(void(*func)(Args...))\n> +\t{\n> +\t\tslots_.push_back(new SlotStatic<Args...>(func));\n> +\t}\n> +\n> +\tvoid disconnect()\n> +\t{\n> +\t\tfor (SlotBase<Args...> *slot : slots_)\n> +\t\t\tdelete slot;\n> +\t\tslots_.clear();\n> +\t}\n> +\n> +\ttemplate<typename T>\n> +\tvoid disconnect(T *object)\n> +\t{\n> +\t\tfor (auto iter = slots_.begin(); iter != slots_.end(); ) {\n> +\t\t\tSlotBase<Args...> *slot = *iter;\n> +\t\t\tif (slot->obj_ == object) {\n> +\t\t\t\titer = slots_.erase(iter);\n> +\t\t\t\tdelete slot;\n> +\t\t\t} else {\n> +\t\t\t\t++iter;\n> +\t\t\t}\n> +\t\t}\n> +\t}\n> +\n> +\ttemplate<typename T>\n> +\tvoid disconnect(T *object, void(T::*func)(Args...))\n> +\t{\n> +\t\tfor (auto iter = slots_.begin(); iter != slots_.end(); ) {\n> +\t\t\tSlotBase<Args...> *slot = *iter;\n> +\t\t\t/*\n> +\t\t\t * If the obj_ pointer matches the object types must\n> +\t\t\t * match, so we can safely case to SlotMember<T, Args>.\n\ns/case/cast\n\n> +\t\t\t */\n> +\t\t\tif (slot->obj_ == object &&\n> +\t\t\t    reinterpret_cast<SlotMember<T, Args...> *>(slot)->func_ == func) {\n> +\t\t\t\titer = slots_.erase(iter);\n> +\t\t\t\tdelete slot;\n> +\t\t\t} else {\n> +\t\t\t\t++iter;\n> +\t\t\t}\n> +\t\t}\n> +\t}\n> +\n> +\tvoid disconnect(void(*func)(Args...))\n> +\t{\n> +\t\tfor (auto iter = slots_.begin(); iter != slots_.end(); ) {\n> +\t\t\tSlotBase<Args...> *slot = *iter;\n> +\t\t\tif (slot->obj_ == nullptr &&\n> +\t\t\t    reinterpret_cast<SlotStatic<Args...> *>(slot)->func_ == func) {\n> +\t\t\t\titer = slots_.erase(iter);\n> +\t\t\t\tdelete slot;\n> +\t\t\t} else {\n> +\t\t\t\t++iter;\n> +\t\t\t}\n> +\t\t}\n> +\t}\n> +\n> +\tvoid emit(Args... args)\n> +\t{\n> +\t\t/*\n> +\t\t * Make a copy of the slots list as the slot could call the\n> +\t\t * disconnect operation, invalidating the iterator.\n> +\t\t */\n> +\t\tstd::vector<SlotBase<Args...> *> slots{ slots_.begin(), slots_.end() };\n> +\t\tfor (SlotBase<Args...> *slot : slots)\n> +\t\t\tslot->invoke(args...);\n> +\t}\n> +\n> +private:\n> +\tstd::list<SlotBase<Args...> *> slots_;\n> +};\n> +\n> +} /* namespace libcamera */\n> +\n> +#endif /* __LIBCAMERA_SIGNAL_H__ */\n> diff --git a/src/libcamera/meson.build b/src/libcamera/meson.build\n> index 78562299fc42..3ec86e75b57e 100644\n> --- a/src/libcamera/meson.build\n> +++ b/src/libcamera/meson.build\n> @@ -6,6 +6,7 @@ libcamera_sources = files([\n>      'media_device.cpp',\n>      'media_object.cpp',\n>      'pipeline_handler.cpp',\n> +    'signal.cpp',\n>  ])\n>\n>  libcamera_headers = files([\n> diff --git a/src/libcamera/signal.cpp b/src/libcamera/signal.cpp\n> new file mode 100644\n> index 000000000000..0fd3bb2a34a1\n> --- /dev/null\n> +++ b/src/libcamera/signal.cpp\n> @@ -0,0 +1,84 @@\n> +/* SPDX-License-Identifier: LGPL-2.1-or-later */\n> +/*\n> + * Copyright (C) 2019, Google Inc.\n> + *\n> + * signal.cpp - Signal & slot implementation\n> + */\n> +\n> +namespace libcamera {\n> +\n> +/**\n> + * \\class Signal\n> + * \\brief Generic signal and slot communication mechanism\n> + *\n> + * Signals and slots are a language construct aimed at communication between\n> + * objects through the observer pattern without the need for boilerplate code.\n> + * See http://doc.qt.io/qt-5/signalsandslots.html for more information.\n> + *\n> + * Signals model events that can be observed from objects unrelated to the event\n> + * source. Slots are functions that are called in response to a signal. Signals\n> + * can be connected to and disconnected from slots dynamically at runtime. When\n> + * a signal is emitted, all connected slots are called sequentially in the order\n> + * they have been connected.\n> + *\n> + * Signals are defined with zero, one or more typed parameters. They are emitted\n> + * with a value for each of the parameters, and those values are passed to the\n> + * connected slots.\n> + *\n> + * Slots are normal static or class member functions. In order to be connected\n> + * to a signal, their signature must match the signal type (taking the same\n> + * arguments as the signal and returning void).\n> + *\n> + * Connecting a signal to a slot results in the slot being called with the\n> + * arguments passed to the emit() function when the signal is emitted. Multiple\n> + * slots can be connected to the same signal, and multiple signals can connected\n> + * to the same slot. Duplicate connections between a signal and a slot are\n> + * allowed and result in the slot being called multiple times for the same\n> + * signal emission.\n\nThanks, this helps me a lot\n\n> + */\n> +\n> +/**\n> + * \\fn Signal::connect(T *object, void(T::*func)(Args...))\n> + * \\brief Connect the signal to a member function slot\n> + * \\param object The slot object pointer\n> + * \\param func The slot member function\n> + */\n> +\n> +/**\n> + * \\fn Signal::connect(void(*func)(Args...))\n> + * \\brief Connect the signal to a static function slot\n> + * \\param func The slot static function\n> + */\n> +\n> +/**\n> + * \\fn Signal::disconnect()\n> + * \\brief Disconnect the signal from all slots\n> + */\n> +\n> +/**\n> + * \\fn Signal::disconnect(T *object)\n> + * \\brief Disconnect the signal from all slots of the \\a object\n> + */\n> +\n> +/**\n> + * \\fn Signal::disconnect(T *object, void(T::*func)(Args...))\n> + * \\brief Disconnect the signal from the \\a object slot member function \\a func\n> + */\n> +\n> +/**\n> + * \\fn Signal::disconnect(void(*func)(Args...))\n> + * \\brief Disconnect the signal from the slot static function \\a func\n> + */\n> +\n> +/**\n> + * \\fn Signal::emit(Args... args)\n> + * \\brief Emit the signal and call all connected slots\n> + *\n> + * Emitting a signal calls all connected slots synchronously and sequentially in\n> + * the order the slots have been connected. The arguments passed to the emit()\n> + * function are passed to the slot functions unchanged. If a slot modifies one\n> + * of the arguments (when passed by pointer or reference), the modification is\n> + * thus visible to all subsequently called slots.\n\nThanks!\n  j\n\n> + */\n> +\n> +} /* namespace libcamera */\n> --\n> Regards,\n>\n> Laurent Pinchart\n>\n> _______________________________________________\n> libcamera-devel mailing list\n> libcamera-devel@lists.libcamera.org\n> https://lists.libcamera.org/listinfo/libcamera-devel","headers":{"Return-Path":"<jacopo@jmondi.org>","Received":["from relay2-d.mail.gandi.net (relay2-d.mail.gandi.net\n\t[217.70.183.194])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 8AC9260B2D\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tTue,  8 Jan 2019 10:29:09 +0100 (CET)","from uno.localdomain (2-224-242-101.ip172.fastwebnet.it\n\t[2.224.242.101]) (Authenticated sender: jacopo@jmondi.org)\n\tby relay2-d.mail.gandi.net (Postfix) with ESMTPSA id 191C24000C;\n\tTue,  8 Jan 2019 09:29:08 +0000 (UTC)"],"X-Originating-IP":"2.224.242.101","Date":"Tue, 8 Jan 2019 10:29:15 +0100","From":"Jacopo Mondi <jacopo@jmondi.org>","To":"Laurent Pinchart <laurent.pinchart@ideasonboard.com>","Cc":"libcamera-devel@lists.libcamera.org","Message-ID":"<20190108092915.amzck7onoo7tbdwd@uno.localdomain>","References":"<20190107231151.23291-1-laurent.pinchart@ideasonboard.com>\n\t<20190107231151.23291-6-laurent.pinchart@ideasonboard.com>","MIME-Version":"1.0","Content-Type":"multipart/signed; micalg=pgp-sha256;\n\tprotocol=\"application/pgp-signature\"; boundary=\"kbdwxvcn6dbmh4ul\"","Content-Disposition":"inline","In-Reply-To":"<20190107231151.23291-6-laurent.pinchart@ideasonboard.com>","User-Agent":"NeoMutt/20180716","Subject":"Re: [libcamera-devel] [PATCH v2 05/11] libcamera: Add signal/slot\n\tcommunication mechanism","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.23","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","X-List-Received-Date":"Tue, 08 Jan 2019 09:29:09 -0000"}}]