Junxiao Shi | 8d71fdb | 2014-12-07 21:55:19 -0700 | [diff] [blame] | 1 | /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ |
| 2 | /** |
| 3 | * Copyright (c) 2013-2014 Regents of the University of California. |
| 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 | |
| 59 | /** \brief connects a handler to the signal |
| 60 | * \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] | 61 | * \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] | 62 | */ |
| 63 | Connection |
| 64 | connect(const Handler& handler); |
| 65 | |
| 66 | private: // API for owner |
| 67 | /** \retval true if there is no connection |
| 68 | */ |
| 69 | bool |
| 70 | isEmpty() const; |
| 71 | |
| 72 | /** \brief emits a signal |
| 73 | * \param args arguments passed to all handlers |
| 74 | * \warning Emitting the signal from a handler is undefined behavior. |
| 75 | * \note If a handler throws, the exception will be propagated to the caller |
| 76 | * who emits this signal, and some handlers may not be executed. |
| 77 | */ |
| 78 | void |
| 79 | operator()(const TArgs&...args); |
| 80 | |
Junxiao Shi | 018e30d | 2014-12-25 19:42:35 -0700 | [diff] [blame] | 81 | /** \brief (implementation detail) emits a signal |
| 82 | * \note This overload is used by signal-emit.hpp. |
| 83 | */ |
| 84 | void |
| 85 | operator()(const TArgs&...args, const DummyExtraArg&); |
| 86 | |
Junxiao Shi | 8d71fdb | 2014-12-07 21:55:19 -0700 | [diff] [blame] | 87 | // make Owner a friend of Signal<Owner, ...> so that API for owner can be called |
| 88 | #if NDN_CXX_HAVE_CXX_FRIEND_TYPENAME |
| 89 | friend Owner; |
| 90 | #elif NDN_CXX_HAVE_CXX_FRIEND_TYPENAME_WRAPPER |
| 91 | template<typename T> |
| 92 | struct TypeWrapper |
| 93 | { |
| 94 | typedef T Type; |
| 95 | }; // http://stackoverflow.com/a/5608542/3729203 |
| 96 | friend class TypeWrapper<Owner>::Type; |
| 97 | #else |
| 98 | # error "cannot declare Owner as friend" |
| 99 | #endif |
| 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 | * |
| 121 | * This is .disconnect method bound with the iterator to this slot. |
| 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. |
| 125 | * When the slot is removed or the signal is destructed, this function object would be |
| 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> |
| 157 | inline Connection |
| 158 | Signal<Owner, TArgs...>::connect(const Handler& handler) |
| 159 | { |
| 160 | typename SlotList::iterator it = m_slots.insert(m_slots.end(), {handler, nullptr}); |
| 161 | it->disconnect = make_shared<function<void()>>(bind(&Self::disconnect, this, it)); |
| 162 | |
| 163 | return signal::Connection(weak_ptr<function<void()>>(it->disconnect)); |
| 164 | } |
| 165 | |
| 166 | template<typename Owner, typename ...TArgs> |
| 167 | inline void |
| 168 | Signal<Owner, TArgs...>::disconnect(typename SlotList::iterator it) |
| 169 | { |
| 170 | // it could be const_iterator, but gcc 4.6 doesn't support std::list::erase(const_iterator) |
| 171 | |
| 172 | if (m_isExecuting) { |
| 173 | // during signal emission, only the currently executing handler can be disconnected |
| 174 | BOOST_ASSERT_MSG(it == m_currentSlot, |
| 175 | "cannot disconnect another handler from a handler"); |
| 176 | m_currentSlot = m_slots.end(); // prevent disconnect twice |
| 177 | } |
| 178 | m_slots.erase(it); |
| 179 | } |
| 180 | |
| 181 | template<typename Owner, typename ...TArgs> |
| 182 | inline bool |
| 183 | Signal<Owner, TArgs...>::isEmpty() const |
| 184 | { |
| 185 | return !m_isExecuting && m_slots.empty(); |
| 186 | } |
| 187 | |
| 188 | template<typename Owner, typename ...TArgs> |
| 189 | inline void |
| 190 | Signal<Owner, TArgs...>::operator()(const TArgs&... args) |
| 191 | { |
| 192 | BOOST_ASSERT_MSG(!m_isExecuting, "cannot emit signal from a handler"); |
| 193 | if (m_slots.empty()) { |
| 194 | return; |
| 195 | } |
| 196 | m_isExecuting = true; |
| 197 | |
| 198 | typename SlotList::iterator it = m_slots.begin(); |
| 199 | typename SlotList::iterator last = m_slots.end(); |
| 200 | --last; |
| 201 | |
| 202 | try { |
| 203 | bool isLast = false; |
| 204 | while (!isLast) { |
| 205 | m_currentSlot = it; |
| 206 | isLast = it == last; |
| 207 | ++it; |
| 208 | |
| 209 | m_currentSlot->handler(args...); |
| 210 | } |
| 211 | } |
| 212 | catch (...) { |
| 213 | m_isExecuting = false; |
| 214 | throw; |
| 215 | } |
| 216 | m_isExecuting = false; |
| 217 | } |
| 218 | |
Junxiao Shi | 018e30d | 2014-12-25 19:42:35 -0700 | [diff] [blame] | 219 | template<typename Owner, typename ...TArgs> |
| 220 | inline void |
| 221 | Signal<Owner, TArgs...>::operator()(const TArgs&... args, const DummyExtraArg&) |
| 222 | { |
| 223 | this->operator()(args...); |
| 224 | } |
| 225 | |
Junxiao Shi | 8d71fdb | 2014-12-07 21:55:19 -0700 | [diff] [blame] | 226 | } // namespace signal |
| 227 | |
| 228 | // expose as ndn::util::Signal |
| 229 | using signal::Signal; |
| 230 | |
| 231 | } // namespace util |
| 232 | } // namespace ndn |
| 233 | |
| 234 | #endif // NDN_UTIL_SIGNAL_SIGNAL_HPP |