Junxiao Shi | 8d71fdb | 2014-12-07 21:55:19 -0700 | [diff] [blame] | 1 | /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ |
Junxiao Shi | 5722cfe | 2017-07-05 18:52:01 +0000 | [diff] [blame] | 2 | /* |
Davide Pesavento | 844b093 | 2018-05-07 01:00:16 -0400 | [diff] [blame] | 3 | * Copyright (c) 2013-2018 Regents of the University of California. |
Junxiao Shi | 8d71fdb | 2014-12-07 21:55:19 -0700 | [diff] [blame] | 4 | * |
| 5 | * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions). |
| 6 | * |
| 7 | * ndn-cxx library is free software: you can redistribute it and/or modify it under the |
| 8 | * terms of the GNU Lesser General Public License as published by the Free Software |
| 9 | * Foundation, either version 3 of the License, or (at your option) any later version. |
| 10 | * |
| 11 | * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY |
| 12 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A |
| 13 | * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. |
| 14 | * |
| 15 | * You should have received copies of the GNU General Public License and GNU Lesser |
| 16 | * General Public License along with ndn-cxx, e.g., in COPYING.md file. If not, see |
| 17 | * <http://www.gnu.org/licenses/>. |
| 18 | * |
| 19 | * See AUTHORS.md for complete list of ndn-cxx authors and contributors. |
| 20 | */ |
| 21 | |
| 22 | #ifndef NDN_UTIL_SIGNAL_SIGNAL_HPP |
| 23 | #define NDN_UTIL_SIGNAL_SIGNAL_HPP |
| 24 | |
Junxiao Shi | 5722cfe | 2017-07-05 18:52:01 +0000 | [diff] [blame] | 25 | #include "connection.hpp" |
Davide Pesavento | 844b093 | 2018-05-07 01:00:16 -0400 | [diff] [blame] | 26 | |
Junxiao Shi | 8d71fdb | 2014-12-07 21:55:19 -0700 | [diff] [blame] | 27 | #include <list> |
| 28 | |
| 29 | namespace ndn { |
| 30 | namespace util { |
| 31 | namespace signal { |
| 32 | |
Junxiao Shi | 018e30d | 2014-12-25 19:42:35 -0700 | [diff] [blame] | 33 | class DummyExtraArg; |
| 34 | |
Junxiao Shi | 8d71fdb | 2014-12-07 21:55:19 -0700 | [diff] [blame] | 35 | /** \brief provides a lightweight signal / event system |
| 36 | * |
| 37 | * To declare a signal: |
| 38 | * public: |
| 39 | * Signal<Owner, T1, T2> signalName; |
| 40 | * To connect to a signal: |
| 41 | * owner->signalName.connect(f); |
| 42 | * Multiple functions can connect to the same signal. |
| 43 | * To emit a signal from owner: |
| 44 | * this->signalName(arg1, arg2); |
| 45 | * |
| 46 | * \tparam Owner the signal owner class; only this class can emit the signal |
| 47 | * \tparam TArgs types of signal arguments |
| 48 | * \sa signal-emit.hpp allows owner's derived classes to emit signals |
| 49 | */ |
| 50 | template<typename Owner, typename ...TArgs> |
| 51 | class Signal : noncopyable |
| 52 | { |
| 53 | public: // API for anyone |
| 54 | /** \brief represents a function that can connect to the signal |
| 55 | */ |
| 56 | typedef function<void(const TArgs&...)> Handler; |
| 57 | |
| 58 | Signal(); |
| 59 | |
Junxiao Shi | bbb2435 | 2015-02-28 21:23:51 -0700 | [diff] [blame] | 60 | ~Signal(); |
| 61 | |
Junxiao Shi | 8d71fdb | 2014-12-07 21:55:19 -0700 | [diff] [blame] | 62 | /** \brief connects a handler to the signal |
| 63 | * \note If invoked from a handler, the new handler won't receive the current emitted signal. |
Junxiao Shi | 2cec707 | 2014-12-19 19:37:40 -0700 | [diff] [blame] | 64 | * \warning The handler is permitted to disconnect itself, but it must ensure its validity. |
Junxiao Shi | 8d71fdb | 2014-12-07 21:55:19 -0700 | [diff] [blame] | 65 | */ |
| 66 | Connection |
| 67 | connect(const Handler& handler); |
| 68 | |
Junxiao Shi | ecc57b5 | 2015-01-01 10:47:08 -0700 | [diff] [blame] | 69 | /** \brief connects a single-shot handler to the signal |
| 70 | * |
| 71 | * After the handler is executed once, it is automatically disconnected. |
| 72 | */ |
| 73 | Connection |
| 74 | connectSingleShot(const Handler& handler); |
| 75 | |
Junxiao Shi | 8d71fdb | 2014-12-07 21:55:19 -0700 | [diff] [blame] | 76 | private: // API for owner |
| 77 | /** \retval true if there is no connection |
| 78 | */ |
| 79 | bool |
| 80 | isEmpty() const; |
| 81 | |
| 82 | /** \brief emits a signal |
| 83 | * \param args arguments passed to all handlers |
| 84 | * \warning Emitting the signal from a handler is undefined behavior. |
Junxiao Shi | bbb2435 | 2015-02-28 21:23:51 -0700 | [diff] [blame] | 85 | * \warning Destructing the Signal object during signal emission is undefined behavior. |
Junxiao Shi | 8d71fdb | 2014-12-07 21:55:19 -0700 | [diff] [blame] | 86 | * \note If a handler throws, the exception will be propagated to the caller |
| 87 | * who emits this signal, and some handlers may not be executed. |
| 88 | */ |
| 89 | void |
Junxiao Shi | 1aecae2 | 2016-08-30 11:23:59 +0000 | [diff] [blame] | 90 | operator()(const TArgs&... args); |
Junxiao Shi | 8d71fdb | 2014-12-07 21:55:19 -0700 | [diff] [blame] | 91 | |
Junxiao Shi | 018e30d | 2014-12-25 19:42:35 -0700 | [diff] [blame] | 92 | /** \brief (implementation detail) emits a signal |
| 93 | * \note This overload is used by signal-emit.hpp. |
| 94 | */ |
| 95 | void |
Junxiao Shi | 1aecae2 | 2016-08-30 11:23:59 +0000 | [diff] [blame] | 96 | operator()(const TArgs&... args, const DummyExtraArg&); |
Junxiao Shi | 018e30d | 2014-12-25 19:42:35 -0700 | [diff] [blame] | 97 | |
Junxiao Shi | 8d71fdb | 2014-12-07 21:55:19 -0700 | [diff] [blame] | 98 | // make Owner a friend of Signal<Owner, ...> so that API for owner can be called |
Junxiao Shi | 8d71fdb | 2014-12-07 21:55:19 -0700 | [diff] [blame] | 99 | friend Owner; |
Junxiao Shi | 8d71fdb | 2014-12-07 21:55:19 -0700 | [diff] [blame] | 100 | |
| 101 | private: // internal implementation |
| 102 | typedef Signal<Owner, TArgs...> Self; |
| 103 | struct Slot; |
| 104 | |
| 105 | /** \brief stores slots |
| 106 | * \note std::list is used because iterators must not be invalidated |
| 107 | * when other slots are added or removed |
| 108 | */ |
| 109 | typedef std::list<Slot> SlotList; |
| 110 | |
| 111 | /** \brief stores a handler function, and a function to disconnect this handler |
| 112 | */ |
| 113 | struct Slot |
| 114 | { |
| 115 | /** \brief the handler function who will receive emitted signals |
| 116 | */ |
| 117 | Handler handler; |
| 118 | |
| 119 | /** \brief the disconnect function which will disconnect this handler |
| 120 | * |
Davide Pesavento | be7804f | 2015-10-23 00:53:47 +0200 | [diff] [blame] | 121 | * In practice this is the Signal::disconnect method bound to an iterator |
| 122 | * pointing at this slot. |
Junxiao Shi | 8d71fdb | 2014-12-07 21:55:19 -0700 | [diff] [blame] | 123 | * |
| 124 | * This is the only shared_ptr to this function object. |
| 125 | * Connection class has a weak_ptr which references the same function object. |
Davide Pesavento | be7804f | 2015-10-23 00:53:47 +0200 | [diff] [blame] | 126 | * When the slot is erased or the signal is destructed, this function object is |
Junxiao Shi | 8d71fdb | 2014-12-07 21:55:19 -0700 | [diff] [blame] | 127 | * destructed, and the related Connections cannot disconnect this slot again. |
| 128 | */ |
| 129 | shared_ptr<function<void()>> disconnect; |
| 130 | }; |
| 131 | |
| 132 | /** \brief stores slots |
| 133 | */ |
| 134 | SlotList m_slots; |
| 135 | |
| 136 | /** \brief is a signal handler executing? |
| 137 | */ |
| 138 | bool m_isExecuting; |
| 139 | |
| 140 | /** \brief iterator to current executing slot |
| 141 | * \note This field is meaningful when isExecuting==true |
| 142 | */ |
| 143 | typename SlotList::iterator m_currentSlot; |
| 144 | |
| 145 | /** \brief disconnects the handler in a slot |
| 146 | */ |
| 147 | void |
| 148 | disconnect(typename SlotList::iterator it); |
| 149 | }; |
| 150 | |
| 151 | template<typename Owner, typename ...TArgs> |
| 152 | Signal<Owner, TArgs...>::Signal() |
| 153 | : m_isExecuting(false) |
| 154 | { |
| 155 | } |
| 156 | |
| 157 | template<typename Owner, typename ...TArgs> |
Junxiao Shi | bbb2435 | 2015-02-28 21:23:51 -0700 | [diff] [blame] | 158 | Signal<Owner, TArgs...>::~Signal() |
| 159 | { |
| 160 | BOOST_ASSERT(!m_isExecuting); |
| 161 | } |
| 162 | |
| 163 | template<typename Owner, typename ...TArgs> |
Davide Pesavento | be7804f | 2015-10-23 00:53:47 +0200 | [diff] [blame] | 164 | Connection |
Junxiao Shi | 8d71fdb | 2014-12-07 21:55:19 -0700 | [diff] [blame] | 165 | Signal<Owner, TArgs...>::connect(const Handler& handler) |
| 166 | { |
Davide Pesavento | 844b093 | 2018-05-07 01:00:16 -0400 | [diff] [blame] | 167 | auto it = m_slots.insert(m_slots.end(), {handler, nullptr}); |
Junxiao Shi | 8d71fdb | 2014-12-07 21:55:19 -0700 | [diff] [blame] | 168 | it->disconnect = make_shared<function<void()>>(bind(&Self::disconnect, this, it)); |
| 169 | |
| 170 | return signal::Connection(weak_ptr<function<void()>>(it->disconnect)); |
| 171 | } |
| 172 | |
| 173 | template<typename Owner, typename ...TArgs> |
Davide Pesavento | be7804f | 2015-10-23 00:53:47 +0200 | [diff] [blame] | 174 | Connection |
Junxiao Shi | ecc57b5 | 2015-01-01 10:47:08 -0700 | [diff] [blame] | 175 | Signal<Owner, TArgs...>::connectSingleShot(const Handler& handler) |
| 176 | { |
Davide Pesavento | 844b093 | 2018-05-07 01:00:16 -0400 | [diff] [blame] | 177 | auto it = m_slots.insert(m_slots.end(), {nullptr, nullptr}); |
Junxiao Shi | ecc57b5 | 2015-01-01 10:47:08 -0700 | [diff] [blame] | 178 | it->disconnect = make_shared<function<void()>>(bind(&Self::disconnect, this, it)); |
| 179 | signal::Connection conn(weak_ptr<function<void()>>(it->disconnect)); |
| 180 | |
| 181 | it->handler = [conn, handler] (const TArgs&... args) mutable { |
| 182 | handler(args...); |
| 183 | conn.disconnect(); |
| 184 | }; |
| 185 | |
| 186 | return conn; |
| 187 | } |
| 188 | |
| 189 | template<typename Owner, typename ...TArgs> |
Davide Pesavento | be7804f | 2015-10-23 00:53:47 +0200 | [diff] [blame] | 190 | void |
Junxiao Shi | 8d71fdb | 2014-12-07 21:55:19 -0700 | [diff] [blame] | 191 | Signal<Owner, TArgs...>::disconnect(typename SlotList::iterator it) |
| 192 | { |
Junxiao Shi | 8d71fdb | 2014-12-07 21:55:19 -0700 | [diff] [blame] | 193 | if (m_isExecuting) { |
| 194 | // during signal emission, only the currently executing handler can be disconnected |
Davide Pesavento | be7804f | 2015-10-23 00:53:47 +0200 | [diff] [blame] | 195 | BOOST_ASSERT_MSG(it == m_currentSlot, "cannot disconnect another handler from a handler"); |
| 196 | |
| 197 | // this serves to indicate that the current slot needs to be erased from the list |
| 198 | // after it finishes executing; we cannot do it here because of bug #2333 |
| 199 | m_currentSlot = m_slots.end(); |
| 200 | |
| 201 | // expire all weak_ptrs, to prevent double disconnections |
| 202 | it->disconnect.reset(); |
Junxiao Shi | 8d71fdb | 2014-12-07 21:55:19 -0700 | [diff] [blame] | 203 | } |
Davide Pesavento | be7804f | 2015-10-23 00:53:47 +0200 | [diff] [blame] | 204 | else { |
| 205 | m_slots.erase(it); |
| 206 | } |
Junxiao Shi | 8d71fdb | 2014-12-07 21:55:19 -0700 | [diff] [blame] | 207 | } |
| 208 | |
| 209 | template<typename Owner, typename ...TArgs> |
Davide Pesavento | be7804f | 2015-10-23 00:53:47 +0200 | [diff] [blame] | 210 | bool |
Junxiao Shi | 8d71fdb | 2014-12-07 21:55:19 -0700 | [diff] [blame] | 211 | Signal<Owner, TArgs...>::isEmpty() const |
| 212 | { |
| 213 | return !m_isExecuting && m_slots.empty(); |
| 214 | } |
| 215 | |
| 216 | template<typename Owner, typename ...TArgs> |
Davide Pesavento | be7804f | 2015-10-23 00:53:47 +0200 | [diff] [blame] | 217 | void |
Junxiao Shi | 8d71fdb | 2014-12-07 21:55:19 -0700 | [diff] [blame] | 218 | Signal<Owner, TArgs...>::operator()(const TArgs&... args) |
| 219 | { |
| 220 | BOOST_ASSERT_MSG(!m_isExecuting, "cannot emit signal from a handler"); |
Davide Pesavento | be7804f | 2015-10-23 00:53:47 +0200 | [diff] [blame] | 221 | |
Junxiao Shi | 8d71fdb | 2014-12-07 21:55:19 -0700 | [diff] [blame] | 222 | if (m_slots.empty()) { |
| 223 | return; |
| 224 | } |
Junxiao Shi | 8d71fdb | 2014-12-07 21:55:19 -0700 | [diff] [blame] | 225 | |
Davide Pesavento | be7804f | 2015-10-23 00:53:47 +0200 | [diff] [blame] | 226 | auto it = m_slots.begin(); |
| 227 | auto last = std::prev(m_slots.end()); |
| 228 | m_isExecuting = true; |
Junxiao Shi | 8d71fdb | 2014-12-07 21:55:19 -0700 | [diff] [blame] | 229 | |
| 230 | try { |
| 231 | bool isLast = false; |
| 232 | while (!isLast) { |
| 233 | m_currentSlot = it; |
| 234 | isLast = it == last; |
Junxiao Shi | 8d71fdb | 2014-12-07 21:55:19 -0700 | [diff] [blame] | 235 | |
| 236 | m_currentSlot->handler(args...); |
Davide Pesavento | be7804f | 2015-10-23 00:53:47 +0200 | [diff] [blame] | 237 | |
| 238 | if (m_currentSlot == m_slots.end()) |
| 239 | it = m_slots.erase(it); |
| 240 | else |
| 241 | ++it; |
Junxiao Shi | 8d71fdb | 2014-12-07 21:55:19 -0700 | [diff] [blame] | 242 | } |
| 243 | } |
| 244 | catch (...) { |
| 245 | m_isExecuting = false; |
| 246 | throw; |
| 247 | } |
Davide Pesavento | be7804f | 2015-10-23 00:53:47 +0200 | [diff] [blame] | 248 | |
Junxiao Shi | 8d71fdb | 2014-12-07 21:55:19 -0700 | [diff] [blame] | 249 | m_isExecuting = false; |
| 250 | } |
| 251 | |
Junxiao Shi | 018e30d | 2014-12-25 19:42:35 -0700 | [diff] [blame] | 252 | template<typename Owner, typename ...TArgs> |
Davide Pesavento | be7804f | 2015-10-23 00:53:47 +0200 | [diff] [blame] | 253 | void |
Junxiao Shi | 018e30d | 2014-12-25 19:42:35 -0700 | [diff] [blame] | 254 | Signal<Owner, TArgs...>::operator()(const TArgs&... args, const DummyExtraArg&) |
| 255 | { |
| 256 | this->operator()(args...); |
| 257 | } |
| 258 | |
Junxiao Shi | 8d71fdb | 2014-12-07 21:55:19 -0700 | [diff] [blame] | 259 | } // namespace signal |
| 260 | |
| 261 | // expose as ndn::util::Signal |
| 262 | using signal::Signal; |
| 263 | |
| 264 | } // namespace util |
| 265 | } // namespace ndn |
| 266 | |
| 267 | #endif // NDN_UTIL_SIGNAL_SIGNAL_HPP |