blob: 7f91e15cde657e0392b6f35611a3e4034f82ecdb [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
Junxiao Shi018e30d2014-12-25 19:42:35 -070032class DummyExtraArg;
33
Junxiao Shi8d71fdb2014-12-07 21:55:19 -070034/** \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 */
49template<typename Owner, typename ...TArgs>
50class Signal : noncopyable
51{
52public: // 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 Shi2cec7072014-12-19 19:37:40 -070061 * \warning The handler is permitted to disconnect itself, but it must ensure its validity.
Junxiao Shi8d71fdb2014-12-07 21:55:19 -070062 */
63 Connection
64 connect(const Handler& handler);
65
Junxiao Shiecc57b52015-01-01 10:47:08 -070066 /** \brief connects a single-shot handler to the signal
67 *
68 * After the handler is executed once, it is automatically disconnected.
69 */
70 Connection
71 connectSingleShot(const Handler& handler);
72
Junxiao Shi8d71fdb2014-12-07 21:55:19 -070073private: // API for owner
74 /** \retval true if there is no connection
75 */
76 bool
77 isEmpty() const;
78
79 /** \brief emits a signal
80 * \param args arguments passed to all handlers
81 * \warning Emitting the signal from a handler is undefined behavior.
82 * \note If a handler throws, the exception will be propagated to the caller
83 * who emits this signal, and some handlers may not be executed.
84 */
85 void
86 operator()(const TArgs&...args);
87
Junxiao Shi018e30d2014-12-25 19:42:35 -070088 /** \brief (implementation detail) emits a signal
89 * \note This overload is used by signal-emit.hpp.
90 */
91 void
92 operator()(const TArgs&...args, const DummyExtraArg&);
93
Junxiao Shi8d71fdb2014-12-07 21:55:19 -070094 // make Owner a friend of Signal<Owner, ...> so that API for owner can be called
95#if NDN_CXX_HAVE_CXX_FRIEND_TYPENAME
96 friend Owner;
97#elif NDN_CXX_HAVE_CXX_FRIEND_TYPENAME_WRAPPER
98 template<typename T>
99 struct TypeWrapper
100 {
101 typedef T Type;
102 }; // http://stackoverflow.com/a/5608542/3729203
103 friend class TypeWrapper<Owner>::Type;
104#else
105# error "cannot declare Owner as friend"
106#endif
107
108private: // internal implementation
109 typedef Signal<Owner, TArgs...> Self;
110 struct Slot;
111
112 /** \brief stores slots
113 * \note std::list is used because iterators must not be invalidated
114 * when other slots are added or removed
115 */
116 typedef std::list<Slot> SlotList;
117
118 /** \brief stores a handler function, and a function to disconnect this handler
119 */
120 struct Slot
121 {
122 /** \brief the handler function who will receive emitted signals
123 */
124 Handler handler;
125
126 /** \brief the disconnect function which will disconnect this handler
127 *
128 * This is .disconnect method bound with the iterator to this slot.
129 *
130 * This is the only shared_ptr to this function object.
131 * Connection class has a weak_ptr which references the same function object.
132 * When the slot is removed or the signal is destructed, this function object would be
133 * destructed, and the related Connections cannot disconnect this slot again.
134 */
135 shared_ptr<function<void()>> disconnect;
136 };
137
138 /** \brief stores slots
139 */
140 SlotList m_slots;
141
142 /** \brief is a signal handler executing?
143 */
144 bool m_isExecuting;
145
146 /** \brief iterator to current executing slot
147 * \note This field is meaningful when isExecuting==true
148 */
149 typename SlotList::iterator m_currentSlot;
150
151 /** \brief disconnects the handler in a slot
152 */
153 void
154 disconnect(typename SlotList::iterator it);
155};
156
157template<typename Owner, typename ...TArgs>
158Signal<Owner, TArgs...>::Signal()
159 : m_isExecuting(false)
160{
161}
162
163template<typename Owner, typename ...TArgs>
164inline Connection
165Signal<Owner, TArgs...>::connect(const Handler& handler)
166{
167 typename SlotList::iterator it = m_slots.insert(m_slots.end(), {handler, nullptr});
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
173template<typename Owner, typename ...TArgs>
Junxiao Shiecc57b52015-01-01 10:47:08 -0700174inline Connection
175Signal<Owner, TArgs...>::connectSingleShot(const Handler& handler)
176{
177 typename SlotList::iterator it = m_slots.insert(m_slots.end(), {nullptr, nullptr});
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
189template<typename Owner, typename ...TArgs>
Junxiao Shi8d71fdb2014-12-07 21:55:19 -0700190inline void
191Signal<Owner, TArgs...>::disconnect(typename SlotList::iterator it)
192{
193 // it could be const_iterator, but gcc 4.6 doesn't support std::list::erase(const_iterator)
194
195 if (m_isExecuting) {
196 // during signal emission, only the currently executing handler can be disconnected
197 BOOST_ASSERT_MSG(it == m_currentSlot,
198 "cannot disconnect another handler from a handler");
199 m_currentSlot = m_slots.end(); // prevent disconnect twice
200 }
201 m_slots.erase(it);
202}
203
204template<typename Owner, typename ...TArgs>
205inline bool
206Signal<Owner, TArgs...>::isEmpty() const
207{
208 return !m_isExecuting && m_slots.empty();
209}
210
211template<typename Owner, typename ...TArgs>
212inline void
213Signal<Owner, TArgs...>::operator()(const TArgs&... args)
214{
215 BOOST_ASSERT_MSG(!m_isExecuting, "cannot emit signal from a handler");
216 if (m_slots.empty()) {
217 return;
218 }
219 m_isExecuting = true;
220
221 typename SlotList::iterator it = m_slots.begin();
222 typename SlotList::iterator last = m_slots.end();
223 --last;
224
225 try {
226 bool isLast = false;
227 while (!isLast) {
228 m_currentSlot = it;
229 isLast = it == last;
230 ++it;
231
232 m_currentSlot->handler(args...);
233 }
234 }
235 catch (...) {
236 m_isExecuting = false;
237 throw;
238 }
239 m_isExecuting = false;
240}
241
Junxiao Shi018e30d2014-12-25 19:42:35 -0700242template<typename Owner, typename ...TArgs>
243inline void
244Signal<Owner, TArgs...>::operator()(const TArgs&... args, const DummyExtraArg&)
245{
246 this->operator()(args...);
247}
248
Junxiao Shi8d71fdb2014-12-07 21:55:19 -0700249} // namespace signal
250
251// expose as ndn::util::Signal
252using signal::Signal;
253
254} // namespace util
255} // namespace ndn
256
257#endif // NDN_UTIL_SIGNAL_SIGNAL_HPP