/
signal-signal.hpp
257 lines (219 loc) · 7.39 KB
/
signal-signal.hpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
/**
* Copyright (c) 2013-2015 Regents of the University of California.
*
* This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
*
* ndn-cxx library is free software: you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free Software
* Foundation, either version 3 of the License, or (at your option) any later version.
*
* ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received copies of the GNU General Public License and GNU Lesser
* General Public License along with ndn-cxx, e.g., in COPYING.md file. If not, see
* <http://www.gnu.org/licenses/>.
*
* See AUTHORS.md for complete list of ndn-cxx authors and contributors.
*/
#ifndef NDN_UTIL_SIGNAL_SIGNAL_HPP
#define NDN_UTIL_SIGNAL_SIGNAL_HPP
#include "signal-connection.hpp"
#include <list>
namespace ndn {
namespace util {
namespace signal {
class DummyExtraArg;
/** \brief provides a lightweight signal / event system
*
* To declare a signal:
* public:
* Signal<Owner, T1, T2> signalName;
* To connect to a signal:
* owner->signalName.connect(f);
* Multiple functions can connect to the same signal.
* To emit a signal from owner:
* this->signalName(arg1, arg2);
*
* \tparam Owner the signal owner class; only this class can emit the signal
* \tparam TArgs types of signal arguments
* \sa signal-emit.hpp allows owner's derived classes to emit signals
*/
template<typename Owner, typename ...TArgs>
class Signal : noncopyable
{
public: // API for anyone
/** \brief represents a function that can connect to the signal
*/
typedef function<void(const TArgs&...)> Handler;
Signal();
/** \brief connects a handler to the signal
* \note If invoked from a handler, the new handler won't receive the current emitted signal.
* \warning The handler is permitted to disconnect itself, but it must ensure its validity.
*/
Connection
connect(const Handler& handler);
/** \brief connects a single-shot handler to the signal
*
* After the handler is executed once, it is automatically disconnected.
*/
Connection
connectSingleShot(const Handler& handler);
private: // API for owner
/** \retval true if there is no connection
*/
bool
isEmpty() const;
/** \brief emits a signal
* \param args arguments passed to all handlers
* \warning Emitting the signal from a handler is undefined behavior.
* \note If a handler throws, the exception will be propagated to the caller
* who emits this signal, and some handlers may not be executed.
*/
void
operator()(const TArgs&...args);
/** \brief (implementation detail) emits a signal
* \note This overload is used by signal-emit.hpp.
*/
void
operator()(const TArgs&...args, const DummyExtraArg&);
// make Owner a friend of Signal<Owner, ...> so that API for owner can be called
#if NDN_CXX_HAVE_CXX_FRIEND_TYPENAME
friend Owner;
#elif NDN_CXX_HAVE_CXX_FRIEND_TYPENAME_WRAPPER
template<typename T>
struct TypeWrapper
{
typedef T Type;
}; // http://stackoverflow.com/a/5608542/3729203
friend class TypeWrapper<Owner>::Type;
#else
# error "cannot declare Owner as friend"
#endif
private: // internal implementation
typedef Signal<Owner, TArgs...> Self;
struct Slot;
/** \brief stores slots
* \note std::list is used because iterators must not be invalidated
* when other slots are added or removed
*/
typedef std::list<Slot> SlotList;
/** \brief stores a handler function, and a function to disconnect this handler
*/
struct Slot
{
/** \brief the handler function who will receive emitted signals
*/
Handler handler;
/** \brief the disconnect function which will disconnect this handler
*
* This is .disconnect method bound with the iterator to this slot.
*
* This is the only shared_ptr to this function object.
* Connection class has a weak_ptr which references the same function object.
* When the slot is removed or the signal is destructed, this function object would be
* destructed, and the related Connections cannot disconnect this slot again.
*/
shared_ptr<function<void()>> disconnect;
};
/** \brief stores slots
*/
SlotList m_slots;
/** \brief is a signal handler executing?
*/
bool m_isExecuting;
/** \brief iterator to current executing slot
* \note This field is meaningful when isExecuting==true
*/
typename SlotList::iterator m_currentSlot;
/** \brief disconnects the handler in a slot
*/
void
disconnect(typename SlotList::iterator it);
};
template<typename Owner, typename ...TArgs>
Signal<Owner, TArgs...>::Signal()
: m_isExecuting(false)
{
}
template<typename Owner, typename ...TArgs>
inline Connection
Signal<Owner, TArgs...>::connect(const Handler& handler)
{
typename SlotList::iterator it = m_slots.insert(m_slots.end(), {handler, nullptr});
it->disconnect = make_shared<function<void()>>(bind(&Self::disconnect, this, it));
return signal::Connection(weak_ptr<function<void()>>(it->disconnect));
}
template<typename Owner, typename ...TArgs>
inline Connection
Signal<Owner, TArgs...>::connectSingleShot(const Handler& handler)
{
typename SlotList::iterator it = m_slots.insert(m_slots.end(), {nullptr, nullptr});
it->disconnect = make_shared<function<void()>>(bind(&Self::disconnect, this, it));
signal::Connection conn(weak_ptr<function<void()>>(it->disconnect));
it->handler = [conn, handler] (const TArgs&... args) mutable {
handler(args...);
conn.disconnect();
};
return conn;
}
template<typename Owner, typename ...TArgs>
inline void
Signal<Owner, TArgs...>::disconnect(typename SlotList::iterator it)
{
// it could be const_iterator, but gcc 4.6 doesn't support std::list::erase(const_iterator)
if (m_isExecuting) {
// during signal emission, only the currently executing handler can be disconnected
BOOST_ASSERT_MSG(it == m_currentSlot,
"cannot disconnect another handler from a handler");
m_currentSlot = m_slots.end(); // prevent disconnect twice
}
m_slots.erase(it);
}
template<typename Owner, typename ...TArgs>
inline bool
Signal<Owner, TArgs...>::isEmpty() const
{
return !m_isExecuting && m_slots.empty();
}
template<typename Owner, typename ...TArgs>
inline void
Signal<Owner, TArgs...>::operator()(const TArgs&... args)
{
BOOST_ASSERT_MSG(!m_isExecuting, "cannot emit signal from a handler");
if (m_slots.empty()) {
return;
}
m_isExecuting = true;
typename SlotList::iterator it = m_slots.begin();
typename SlotList::iterator last = m_slots.end();
--last;
try {
bool isLast = false;
while (!isLast) {
m_currentSlot = it;
isLast = it == last;
++it;
m_currentSlot->handler(args...);
}
}
catch (...) {
m_isExecuting = false;
throw;
}
m_isExecuting = false;
}
template<typename Owner, typename ...TArgs>
inline void
Signal<Owner, TArgs...>::operator()(const TArgs&... args, const DummyExtraArg&)
{
this->operator()(args...);
}
} // namespace signal
// expose as ndn::util::Signal
using signal::Signal;
} // namespace util
} // namespace ndn
#endif // NDN_UTIL_SIGNAL_SIGNAL_HPP