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