blob: 444c7022b2cf21224efc1b1ac3f6b4ef3fb8b60c [file] [log] [blame]
Junxiao Shi8d71fdb2014-12-07 21:55:19 -07001/* -*- 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
28namespace ndn {
29namespace util {
30namespace signal {
31
32/** \brief provides a lightweight signal / event system
33 *
34 * To declare a signal:
35 * public:
36 * Signal<Owner, T1, T2> signalName;
37 * To connect to a signal:
38 * owner->signalName.connect(f);
39 * Multiple functions can connect to the same signal.
40 * To emit a signal from owner:
41 * this->signalName(arg1, arg2);
42 *
43 * \tparam Owner the signal owner class; only this class can emit the signal
44 * \tparam TArgs types of signal arguments
45 * \sa signal-emit.hpp allows owner's derived classes to emit signals
46 */
47template<typename Owner, typename ...TArgs>
48class Signal : noncopyable
49{
50public: // API for anyone
51 /** \brief represents a function that can connect to the signal
52 */
53 typedef function<void(const TArgs&...)> Handler;
54
55 Signal();
56
57 /** \brief connects a handler to the signal
58 * \note If invoked from a handler, the new handler won't receive the current emitted signal.
Junxiao Shi2cec7072014-12-19 19:37:40 -070059 * \warning The handler is permitted to disconnect itself, but it must ensure its validity.
Junxiao Shi8d71fdb2014-12-07 21:55:19 -070060 */
61 Connection
62 connect(const Handler& handler);
63
64private: // API for owner
65 /** \retval true if there is no connection
66 */
67 bool
68 isEmpty() const;
69
70 /** \brief emits a signal
71 * \param args arguments passed to all handlers
72 * \warning Emitting the signal from a handler is undefined behavior.
73 * \note If a handler throws, the exception will be propagated to the caller
74 * who emits this signal, and some handlers may not be executed.
75 */
76 void
77 operator()(const TArgs&...args);
78
79 // make Owner a friend of Signal<Owner, ...> so that API for owner can be called
80#if NDN_CXX_HAVE_CXX_FRIEND_TYPENAME
81 friend Owner;
82#elif NDN_CXX_HAVE_CXX_FRIEND_TYPENAME_WRAPPER
83 template<typename T>
84 struct TypeWrapper
85 {
86 typedef T Type;
87 }; // http://stackoverflow.com/a/5608542/3729203
88 friend class TypeWrapper<Owner>::Type;
89#else
90# error "cannot declare Owner as friend"
91#endif
92
93private: // internal implementation
94 typedef Signal<Owner, TArgs...> Self;
95 struct Slot;
96
97 /** \brief stores slots
98 * \note std::list is used because iterators must not be invalidated
99 * when other slots are added or removed
100 */
101 typedef std::list<Slot> SlotList;
102
103 /** \brief stores a handler function, and a function to disconnect this handler
104 */
105 struct Slot
106 {
107 /** \brief the handler function who will receive emitted signals
108 */
109 Handler handler;
110
111 /** \brief the disconnect function which will disconnect this handler
112 *
113 * This is .disconnect method bound with the iterator to this slot.
114 *
115 * This is the only shared_ptr to this function object.
116 * Connection class has a weak_ptr which references the same function object.
117 * When the slot is removed or the signal is destructed, this function object would be
118 * destructed, and the related Connections cannot disconnect this slot again.
119 */
120 shared_ptr<function<void()>> disconnect;
121 };
122
123 /** \brief stores slots
124 */
125 SlotList m_slots;
126
127 /** \brief is a signal handler executing?
128 */
129 bool m_isExecuting;
130
131 /** \brief iterator to current executing slot
132 * \note This field is meaningful when isExecuting==true
133 */
134 typename SlotList::iterator m_currentSlot;
135
136 /** \brief disconnects the handler in a slot
137 */
138 void
139 disconnect(typename SlotList::iterator it);
140};
141
142template<typename Owner, typename ...TArgs>
143Signal<Owner, TArgs...>::Signal()
144 : m_isExecuting(false)
145{
146}
147
148template<typename Owner, typename ...TArgs>
149inline Connection
150Signal<Owner, TArgs...>::connect(const Handler& handler)
151{
152 typename SlotList::iterator it = m_slots.insert(m_slots.end(), {handler, nullptr});
153 it->disconnect = make_shared<function<void()>>(bind(&Self::disconnect, this, it));
154
155 return signal::Connection(weak_ptr<function<void()>>(it->disconnect));
156}
157
158template<typename Owner, typename ...TArgs>
159inline void
160Signal<Owner, TArgs...>::disconnect(typename SlotList::iterator it)
161{
162 // it could be const_iterator, but gcc 4.6 doesn't support std::list::erase(const_iterator)
163
164 if (m_isExecuting) {
165 // during signal emission, only the currently executing handler can be disconnected
166 BOOST_ASSERT_MSG(it == m_currentSlot,
167 "cannot disconnect another handler from a handler");
168 m_currentSlot = m_slots.end(); // prevent disconnect twice
169 }
170 m_slots.erase(it);
171}
172
173template<typename Owner, typename ...TArgs>
174inline bool
175Signal<Owner, TArgs...>::isEmpty() const
176{
177 return !m_isExecuting && m_slots.empty();
178}
179
180template<typename Owner, typename ...TArgs>
181inline void
182Signal<Owner, TArgs...>::operator()(const TArgs&... args)
183{
184 BOOST_ASSERT_MSG(!m_isExecuting, "cannot emit signal from a handler");
185 if (m_slots.empty()) {
186 return;
187 }
188 m_isExecuting = true;
189
190 typename SlotList::iterator it = m_slots.begin();
191 typename SlotList::iterator last = m_slots.end();
192 --last;
193
194 try {
195 bool isLast = false;
196 while (!isLast) {
197 m_currentSlot = it;
198 isLast = it == last;
199 ++it;
200
201 m_currentSlot->handler(args...);
202 }
203 }
204 catch (...) {
205 m_isExecuting = false;
206 throw;
207 }
208 m_isExecuting = false;
209}
210
211} // namespace signal
212
213// expose as ndn::util::Signal
214using signal::Signal;
215
216} // namespace util
217} // namespace ndn
218
219#endif // NDN_UTIL_SIGNAL_SIGNAL_HPP