Junxiao Shi | 8d71fdb | 2014-12-07 21:55:19 -0700 | [diff] [blame] | 1 | /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ |
| 2 | /** |
Alexander Afanasyev | af99f46 | 2015-01-19 21:43:09 -0800 | [diff] [blame] | 3 | * Copyright (c) 2013-2015 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 |
| 89 | operator()(const TArgs&...args); |
| 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 |
| 95 | operator()(const TArgs&...args, const DummyExtraArg&); |
| 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 |
| 98 | #if NDN_CXX_HAVE_CXX_FRIEND_TYPENAME |
| 99 | friend Owner; |
| 100 | #elif NDN_CXX_HAVE_CXX_FRIEND_TYPENAME_WRAPPER |
| 101 | template<typename T> |
| 102 | struct TypeWrapper |
| 103 | { |
| 104 | typedef T Type; |
| 105 | }; // http://stackoverflow.com/a/5608542/3729203 |
| 106 | friend class TypeWrapper<Owner>::Type; |
| 107 | #else |
| 108 | # error "cannot declare Owner as friend" |
| 109 | #endif |
| 110 | |
| 111 | private: // internal implementation |
| 112 | typedef Signal<Owner, TArgs...> Self; |
| 113 | struct Slot; |
| 114 | |
| 115 | /** \brief stores slots |
| 116 | * \note std::list is used because iterators must not be invalidated |
| 117 | * when other slots are added or removed |
| 118 | */ |
| 119 | typedef std::list<Slot> SlotList; |
| 120 | |
| 121 | /** \brief stores a handler function, and a function to disconnect this handler |
| 122 | */ |
| 123 | struct Slot |
| 124 | { |
| 125 | /** \brief the handler function who will receive emitted signals |
| 126 | */ |
| 127 | Handler handler; |
| 128 | |
| 129 | /** \brief the disconnect function which will disconnect this handler |
| 130 | * |
| 131 | * This is .disconnect method bound with the iterator to this slot. |
| 132 | * |
| 133 | * This is the only shared_ptr to this function object. |
| 134 | * Connection class has a weak_ptr which references the same function object. |
| 135 | * When the slot is removed or the signal is destructed, this function object would be |
| 136 | * destructed, and the related Connections cannot disconnect this slot again. |
| 137 | */ |
| 138 | shared_ptr<function<void()>> disconnect; |
| 139 | }; |
| 140 | |
| 141 | /** \brief stores slots |
| 142 | */ |
| 143 | SlotList m_slots; |
| 144 | |
| 145 | /** \brief is a signal handler executing? |
| 146 | */ |
| 147 | bool m_isExecuting; |
| 148 | |
| 149 | /** \brief iterator to current executing slot |
| 150 | * \note This field is meaningful when isExecuting==true |
| 151 | */ |
| 152 | typename SlotList::iterator m_currentSlot; |
| 153 | |
| 154 | /** \brief disconnects the handler in a slot |
| 155 | */ |
| 156 | void |
| 157 | disconnect(typename SlotList::iterator it); |
| 158 | }; |
| 159 | |
| 160 | template<typename Owner, typename ...TArgs> |
| 161 | Signal<Owner, TArgs...>::Signal() |
| 162 | : m_isExecuting(false) |
| 163 | { |
| 164 | } |
| 165 | |
| 166 | template<typename Owner, typename ...TArgs> |
Junxiao Shi | bbb2435 | 2015-02-28 21:23:51 -0700 | [diff] [blame] | 167 | Signal<Owner, TArgs...>::~Signal() |
| 168 | { |
| 169 | BOOST_ASSERT(!m_isExecuting); |
| 170 | } |
| 171 | |
| 172 | template<typename Owner, typename ...TArgs> |
Junxiao Shi | 8d71fdb | 2014-12-07 21:55:19 -0700 | [diff] [blame] | 173 | inline Connection |
| 174 | Signal<Owner, TArgs...>::connect(const Handler& handler) |
| 175 | { |
| 176 | typename SlotList::iterator it = m_slots.insert(m_slots.end(), {handler, nullptr}); |
| 177 | it->disconnect = make_shared<function<void()>>(bind(&Self::disconnect, this, it)); |
| 178 | |
| 179 | return signal::Connection(weak_ptr<function<void()>>(it->disconnect)); |
| 180 | } |
| 181 | |
| 182 | template<typename Owner, typename ...TArgs> |
Junxiao Shi | ecc57b5 | 2015-01-01 10:47:08 -0700 | [diff] [blame] | 183 | inline Connection |
| 184 | Signal<Owner, TArgs...>::connectSingleShot(const Handler& handler) |
| 185 | { |
| 186 | typename SlotList::iterator it = m_slots.insert(m_slots.end(), {nullptr, nullptr}); |
| 187 | it->disconnect = make_shared<function<void()>>(bind(&Self::disconnect, this, it)); |
| 188 | signal::Connection conn(weak_ptr<function<void()>>(it->disconnect)); |
| 189 | |
| 190 | it->handler = [conn, handler] (const TArgs&... args) mutable { |
| 191 | handler(args...); |
| 192 | conn.disconnect(); |
| 193 | }; |
| 194 | |
| 195 | return conn; |
| 196 | } |
| 197 | |
| 198 | template<typename Owner, typename ...TArgs> |
Junxiao Shi | 8d71fdb | 2014-12-07 21:55:19 -0700 | [diff] [blame] | 199 | inline void |
| 200 | Signal<Owner, TArgs...>::disconnect(typename SlotList::iterator it) |
| 201 | { |
| 202 | // it could be const_iterator, but gcc 4.6 doesn't support std::list::erase(const_iterator) |
| 203 | |
| 204 | if (m_isExecuting) { |
| 205 | // during signal emission, only the currently executing handler can be disconnected |
| 206 | BOOST_ASSERT_MSG(it == m_currentSlot, |
| 207 | "cannot disconnect another handler from a handler"); |
| 208 | m_currentSlot = m_slots.end(); // prevent disconnect twice |
| 209 | } |
| 210 | m_slots.erase(it); |
| 211 | } |
| 212 | |
| 213 | template<typename Owner, typename ...TArgs> |
| 214 | inline bool |
| 215 | Signal<Owner, TArgs...>::isEmpty() const |
| 216 | { |
| 217 | return !m_isExecuting && m_slots.empty(); |
| 218 | } |
| 219 | |
| 220 | template<typename Owner, typename ...TArgs> |
| 221 | inline void |
| 222 | Signal<Owner, TArgs...>::operator()(const TArgs&... args) |
| 223 | { |
| 224 | BOOST_ASSERT_MSG(!m_isExecuting, "cannot emit signal from a handler"); |
| 225 | if (m_slots.empty()) { |
| 226 | return; |
| 227 | } |
| 228 | m_isExecuting = true; |
| 229 | |
| 230 | typename SlotList::iterator it = m_slots.begin(); |
| 231 | typename SlotList::iterator last = m_slots.end(); |
| 232 | --last; |
| 233 | |
| 234 | try { |
| 235 | bool isLast = false; |
| 236 | while (!isLast) { |
| 237 | m_currentSlot = it; |
| 238 | isLast = it == last; |
| 239 | ++it; |
| 240 | |
| 241 | m_currentSlot->handler(args...); |
| 242 | } |
| 243 | } |
| 244 | catch (...) { |
| 245 | m_isExecuting = false; |
| 246 | throw; |
| 247 | } |
| 248 | m_isExecuting = false; |
| 249 | } |
| 250 | |
Junxiao Shi | 018e30d | 2014-12-25 19:42:35 -0700 | [diff] [blame] | 251 | template<typename Owner, typename ...TArgs> |
| 252 | inline void |
| 253 | Signal<Owner, TArgs...>::operator()(const TArgs&... args, const DummyExtraArg&) |
| 254 | { |
| 255 | this->operator()(args...); |
| 256 | } |
| 257 | |
Junxiao Shi | 8d71fdb | 2014-12-07 21:55:19 -0700 | [diff] [blame] | 258 | } // namespace signal |
| 259 | |
| 260 | // expose as ndn::util::Signal |
| 261 | using signal::Signal; |
| 262 | |
| 263 | } // namespace util |
| 264 | } // namespace ndn |
| 265 | |
| 266 | #endif // NDN_UTIL_SIGNAL_SIGNAL_HPP |