sigh.hpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469
  1. #ifndef ENTT_SIGNAL_SIGH_HPP
  2. #define ENTT_SIGNAL_SIGH_HPP
  3. #include <cstddef>
  4. #include <memory>
  5. #include <type_traits>
  6. #include <utility>
  7. #include <vector>
  8. #include "delegate.hpp"
  9. #include "fwd.hpp"
  10. namespace entt {
  11. /**
  12. * @brief Sink class.
  13. *
  14. * Primary template isn't defined on purpose. All the specializations give a
  15. * compile-time error unless the template parameter is a function type.
  16. *
  17. * @tparam Type A valid signal handler type.
  18. */
  19. template<typename Type>
  20. class sink;
  21. /**
  22. * @brief Unmanaged signal handler.
  23. *
  24. * Primary template isn't defined on purpose. All the specializations give a
  25. * compile-time error unless the template parameter is a function type.
  26. *
  27. * @tparam Type A valid function type.
  28. * @tparam Allocator Type of allocator used to manage memory and elements.
  29. */
  30. template<typename Type, typename Allocator>
  31. class sigh;
  32. /**
  33. * @brief Unmanaged signal handler.
  34. *
  35. * It works directly with references to classes and pointers to member functions
  36. * as well as pointers to free functions. Users of this class are in charge of
  37. * disconnecting instances before deleting them.
  38. *
  39. * This class serves mainly two purposes:
  40. *
  41. * * Creating signals to use later to notify a bunch of listeners.
  42. * * Collecting results from a set of functions like in a voting system.
  43. *
  44. * @tparam Ret Return type of a function type.
  45. * @tparam Args Types of arguments of a function type.
  46. * @tparam Allocator Type of allocator used to manage memory and elements.
  47. */
  48. template<typename Ret, typename... Args, typename Allocator>
  49. class sigh<Ret(Args...), Allocator> {
  50. friend class sink<sigh<Ret(Args...), Allocator>>;
  51. using alloc_traits = std::allocator_traits<Allocator>;
  52. using delegate_type = delegate<Ret(Args...)>;
  53. using container_type = std::vector<delegate_type, typename alloc_traits::template rebind_alloc<delegate_type>>;
  54. public:
  55. /*! @brief Allocator type. */
  56. using allocator_type = Allocator;
  57. /*! @brief Unsigned integer type. */
  58. using size_type = std::size_t;
  59. /*! @brief Sink type. */
  60. using sink_type = sink<sigh<Ret(Args...), Allocator>>;
  61. /*! @brief Default constructor. */
  62. sigh() noexcept(std::is_nothrow_default_constructible_v<allocator_type> &&std::is_nothrow_constructible_v<container_type, const allocator_type &>)
  63. : sigh{allocator_type{}} {}
  64. /**
  65. * @brief Constructs a signal handler with a given allocator.
  66. * @param allocator The allocator to use.
  67. */
  68. explicit sigh(const allocator_type &allocator) noexcept(std::is_nothrow_constructible_v<container_type, const allocator_type &>)
  69. : calls{allocator} {}
  70. /**
  71. * @brief Copy constructor.
  72. * @param other The instance to copy from.
  73. */
  74. sigh(const sigh &other) noexcept(std::is_nothrow_copy_constructible_v<container_type>)
  75. : calls{other.calls} {}
  76. /**
  77. * @brief Allocator-extended copy constructor.
  78. * @param other The instance to copy from.
  79. * @param allocator The allocator to use.
  80. */
  81. sigh(const sigh &other, const allocator_type &allocator) noexcept(std::is_nothrow_constructible_v<container_type, const container_type &, const allocator_type &>)
  82. : calls{other.calls, allocator} {}
  83. /**
  84. * @brief Move constructor.
  85. * @param other The instance to move from.
  86. */
  87. sigh(sigh &&other) noexcept(std::is_nothrow_move_constructible_v<container_type>)
  88. : calls{std::move(other.calls)} {}
  89. /**
  90. * @brief Allocator-extended move constructor.
  91. * @param other The instance to move from.
  92. * @param allocator The allocator to use.
  93. */
  94. sigh(sigh &&other, const allocator_type &allocator) noexcept(std::is_nothrow_constructible_v<container_type, container_type &&, const allocator_type &>)
  95. : calls{std::move(other.calls), allocator} {}
  96. /**
  97. * @brief Copy assignment operator.
  98. * @param other The instance to copy from.
  99. * @return This signal handler.
  100. */
  101. sigh &operator=(const sigh &other) noexcept(std::is_nothrow_copy_assignable_v<container_type>) {
  102. calls = other.calls;
  103. return *this;
  104. }
  105. /**
  106. * @brief Move assignment operator.
  107. * @param other The instance to move from.
  108. * @return This signal handler.
  109. */
  110. sigh &operator=(sigh &&other) noexcept(std::is_nothrow_move_assignable_v<container_type>) {
  111. calls = std::move(other.calls);
  112. return *this;
  113. }
  114. /**
  115. * @brief Exchanges the contents with those of a given signal handler.
  116. * @param other Signal handler to exchange the content with.
  117. */
  118. void swap(sigh &other) noexcept(std::is_nothrow_swappable_v<container_type>) {
  119. using std::swap;
  120. swap(calls, other.calls);
  121. }
  122. /**
  123. * @brief Returns the associated allocator.
  124. * @return The associated allocator.
  125. */
  126. [[nodiscard]] constexpr allocator_type get_allocator() const noexcept {
  127. return calls.get_allocator();
  128. }
  129. /**
  130. * @brief Number of listeners connected to the signal.
  131. * @return Number of listeners currently connected.
  132. */
  133. [[nodiscard]] size_type size() const noexcept {
  134. return calls.size();
  135. }
  136. /**
  137. * @brief Returns false if at least a listener is connected to the signal.
  138. * @return True if the signal has no listeners connected, false otherwise.
  139. */
  140. [[nodiscard]] bool empty() const noexcept {
  141. return calls.empty();
  142. }
  143. /**
  144. * @brief Triggers a signal.
  145. *
  146. * All the listeners are notified. Order isn't guaranteed.
  147. *
  148. * @param args Arguments to use to invoke listeners.
  149. */
  150. void publish(Args... args) const {
  151. for(auto pos = calls.size(); pos; --pos) {
  152. calls[pos - 1u](args...);
  153. }
  154. }
  155. /**
  156. * @brief Collects return values from the listeners.
  157. *
  158. * The collector must expose a call operator with the following properties:
  159. *
  160. * * The return type is either `void` or such that it's convertible to
  161. * `bool`. In the second case, a true value will stop the iteration.
  162. * * The list of parameters is empty if `Ret` is `void`, otherwise it
  163. * contains a single element such that `Ret` is convertible to it.
  164. *
  165. * @tparam Func Type of collector to use, if any.
  166. * @param func A valid function object.
  167. * @param args Arguments to use to invoke listeners.
  168. */
  169. template<typename Func>
  170. void collect(Func func, Args... args) const {
  171. for(auto pos = calls.size(); pos; --pos) {
  172. if constexpr(std::is_void_v<Ret> || !std::is_invocable_v<Func, Ret>) {
  173. calls[pos - 1u](args...);
  174. if constexpr(std::is_invocable_r_v<bool, Func>) {
  175. if(func()) {
  176. break;
  177. }
  178. } else {
  179. func();
  180. }
  181. } else {
  182. if constexpr(std::is_invocable_r_v<bool, Func, Ret>) {
  183. if(func(calls[pos - 1u](args...))) {
  184. break;
  185. }
  186. } else {
  187. func(calls[pos - 1u](args...));
  188. }
  189. }
  190. }
  191. }
  192. private:
  193. container_type calls;
  194. };
  195. /**
  196. * @brief Connection class.
  197. *
  198. * Opaque object the aim of which is to allow users to release an already
  199. * estabilished connection without having to keep a reference to the signal or
  200. * the sink that generated it.
  201. */
  202. class connection {
  203. template<typename>
  204. friend class sink;
  205. connection(delegate<void(void *)> fn, void *ref)
  206. : disconnect{fn}, signal{ref} {}
  207. public:
  208. /*! @brief Default constructor. */
  209. connection()
  210. : disconnect{},
  211. signal{} {}
  212. /**
  213. * @brief Checks whether a connection is properly initialized.
  214. * @return True if the connection is properly initialized, false otherwise.
  215. */
  216. [[nodiscard]] explicit operator bool() const noexcept {
  217. return static_cast<bool>(disconnect);
  218. }
  219. /*! @brief Breaks the connection. */
  220. void release() {
  221. if(disconnect) {
  222. disconnect(signal);
  223. disconnect.reset();
  224. }
  225. }
  226. private:
  227. delegate<void(void *)> disconnect;
  228. void *signal;
  229. };
  230. /**
  231. * @brief Scoped connection class.
  232. *
  233. * Opaque object the aim of which is to allow users to release an already
  234. * estabilished connection without having to keep a reference to the signal or
  235. * the sink that generated it.<br/>
  236. * A scoped connection automatically breaks the link between the two objects
  237. * when it goes out of scope.
  238. */
  239. struct scoped_connection {
  240. /*! @brief Default constructor. */
  241. scoped_connection() = default;
  242. /**
  243. * @brief Constructs a scoped connection from a basic connection.
  244. * @param other A valid connection object.
  245. */
  246. scoped_connection(const connection &other)
  247. : conn{other} {}
  248. /*! @brief Default copy constructor, deleted on purpose. */
  249. scoped_connection(const scoped_connection &) = delete;
  250. /**
  251. * @brief Move constructor.
  252. * @param other The scoped connection to move from.
  253. */
  254. scoped_connection(scoped_connection &&other) noexcept
  255. : conn{std::exchange(other.conn, {})} {}
  256. /*! @brief Automatically breaks the link on destruction. */
  257. ~scoped_connection() {
  258. conn.release();
  259. }
  260. /**
  261. * @brief Default copy assignment operator, deleted on purpose.
  262. * @return This scoped connection.
  263. */
  264. scoped_connection &operator=(const scoped_connection &) = delete;
  265. /**
  266. * @brief Move assignment operator.
  267. * @param other The scoped connection to move from.
  268. * @return This scoped connection.
  269. */
  270. scoped_connection &operator=(scoped_connection &&other) noexcept {
  271. conn = std::exchange(other.conn, {});
  272. return *this;
  273. }
  274. /**
  275. * @brief Acquires a connection.
  276. * @param other The connection object to acquire.
  277. * @return This scoped connection.
  278. */
  279. scoped_connection &operator=(connection other) {
  280. conn = std::move(other);
  281. return *this;
  282. }
  283. /**
  284. * @brief Checks whether a scoped connection is properly initialized.
  285. * @return True if the connection is properly initialized, false otherwise.
  286. */
  287. [[nodiscard]] explicit operator bool() const noexcept {
  288. return static_cast<bool>(conn);
  289. }
  290. /*! @brief Breaks the connection. */
  291. void release() {
  292. conn.release();
  293. }
  294. private:
  295. connection conn;
  296. };
  297. /**
  298. * @brief Sink class.
  299. *
  300. * A sink is used to connect listeners to signals and to disconnect them.<br/>
  301. * The function type for a listener is the one of the signal to which it
  302. * belongs.
  303. *
  304. * The clear separation between a signal and a sink permits to store the former
  305. * as private data member without exposing the publish functionality to the
  306. * users of the class.
  307. *
  308. * @warning
  309. * Lifetime of a sink must not overcome that of the signal to which it refers.
  310. * In any other case, attempting to use a sink results in undefined behavior.
  311. *
  312. * @tparam Ret Return type of a function type.
  313. * @tparam Args Types of arguments of a function type.
  314. * @tparam Allocator Type of allocator used to manage memory and elements.
  315. */
  316. template<typename Ret, typename... Args, typename Allocator>
  317. class sink<sigh<Ret(Args...), Allocator>> {
  318. using signal_type = sigh<Ret(Args...), Allocator>;
  319. using delegate_type = typename signal_type::delegate_type;
  320. using difference_type = typename signal_type::container_type::difference_type;
  321. template<auto Candidate, typename Type>
  322. static void release(Type value_or_instance, void *signal) {
  323. sink{*static_cast<signal_type *>(signal)}.disconnect<Candidate>(value_or_instance);
  324. }
  325. template<auto Candidate>
  326. static void release(void *signal) {
  327. sink{*static_cast<signal_type *>(signal)}.disconnect<Candidate>();
  328. }
  329. template<typename Func>
  330. void disconnect_if(Func callback) {
  331. for(auto pos = signal->calls.size(); pos; --pos) {
  332. if(auto &elem = signal->calls[pos - 1u]; callback(elem)) {
  333. elem = std::move(signal->calls.back());
  334. signal->calls.pop_back();
  335. }
  336. }
  337. }
  338. public:
  339. /**
  340. * @brief Constructs a sink that is allowed to modify a given signal.
  341. * @param ref A valid reference to a signal object.
  342. */
  343. sink(sigh<Ret(Args...), Allocator> &ref) noexcept
  344. : signal{&ref} {}
  345. /**
  346. * @brief Returns false if at least a listener is connected to the sink.
  347. * @return True if the sink has no listeners connected, false otherwise.
  348. */
  349. [[nodiscard]] bool empty() const noexcept {
  350. return signal->calls.empty();
  351. }
  352. /**
  353. * @brief Connects a free function (with or without payload), a bound or an
  354. * unbound member to a signal.
  355. * @tparam Candidate Function or member to connect to the signal.
  356. * @tparam Type Type of class or type of payload, if any.
  357. * @param value_or_instance A valid object that fits the purpose, if any.
  358. * @return A properly initialized connection object.
  359. */
  360. template<auto Candidate, typename... Type>
  361. connection connect(Type &&...value_or_instance) {
  362. disconnect<Candidate>(value_or_instance...);
  363. delegate_type call{};
  364. call.template connect<Candidate>(value_or_instance...);
  365. signal->calls.push_back(std::move(call));
  366. delegate<void(void *)> conn{};
  367. conn.template connect<&release<Candidate, Type...>>(value_or_instance...);
  368. return {std::move(conn), signal};
  369. }
  370. /**
  371. * @brief Disconnects a free function (with or without payload), a bound or
  372. * an unbound member from a signal.
  373. * @tparam Candidate Function or member to disconnect from the signal.
  374. * @tparam Type Type of class or type of payload, if any.
  375. * @param value_or_instance A valid object that fits the purpose, if any.
  376. */
  377. template<auto Candidate, typename... Type>
  378. void disconnect(Type &&...value_or_instance) {
  379. delegate_type call{};
  380. call.template connect<Candidate>(value_or_instance...);
  381. disconnect_if([&call](const auto &elem) { return elem == call; });
  382. }
  383. /**
  384. * @brief Disconnects free functions with payload or bound members from a
  385. * signal.
  386. * @param value_or_instance A valid object that fits the purpose.
  387. */
  388. void disconnect(const void *value_or_instance) {
  389. if(value_or_instance) {
  390. disconnect_if([value_or_instance](const auto &elem) { return elem.data() == value_or_instance; });
  391. }
  392. }
  393. /*! @brief Disconnects all the listeners from a signal. */
  394. void disconnect() {
  395. signal->calls.clear();
  396. }
  397. private:
  398. signal_type *signal;
  399. };
  400. /**
  401. * @brief Deduction guide.
  402. *
  403. * It allows to deduce the signal handler type of a sink directly from the
  404. * signal it refers to.
  405. *
  406. * @tparam Ret Return type of a function type.
  407. * @tparam Args Types of arguments of a function type.
  408. * @tparam Allocator Type of allocator used to manage memory and elements.
  409. */
  410. template<typename Ret, typename... Args, typename Allocator>
  411. sink(sigh<Ret(Args...), Allocator> &) -> sink<sigh<Ret(Args...), Allocator>>;
  412. } // namespace entt
  413. #endif