blob: 9b502abb1aa4b628d703526efe061611d9a44e71 [file] [log] [blame]
Junxiao Shi8d71fdb2014-12-07 21:55:19 -07001/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
Junxiao Shi5722cfe2017-07-05 18:52:01 +00002/*
3 * Copyright (c) 2013-2017 Regents of the University of California.
Junxiao Shi8d71fdb2014-12-07 21:55:19 -07004 *
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
Junxiao Shi5722cfe2017-07-05 18:52:01 +000025#include "connection.hpp"
Junxiao Shi8d71fdb2014-12-07 21:55:19 -070026#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
Junxiao Shibbb24352015-02-28 21:23:51 -070059 ~Signal();
60
Junxiao Shi8d71fdb2014-12-07 21:55:19 -070061 /** \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 Shi2cec7072014-12-19 19:37:40 -070063 * \warning The handler is permitted to disconnect itself, but it must ensure its validity.
Junxiao Shi8d71fdb2014-12-07 21:55:19 -070064 */
65 Connection
66 connect(const Handler& handler);
67
Junxiao Shiecc57b52015-01-01 10:47:08 -070068 /** \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 Shi8d71fdb2014-12-07 21:55:19 -070075private: // 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 Shibbb24352015-02-28 21:23:51 -070084 * \warning Destructing the Signal object during signal emission is undefined behavior.
Junxiao Shi8d71fdb2014-12-07 21:55:19 -070085 * \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
Junxiao Shi1aecae22016-08-30 11:23:59 +000089 operator()(const TArgs&... args);
Junxiao Shi8d71fdb2014-12-07 21:55:19 -070090
Junxiao Shi018e30d2014-12-25 19:42:35 -070091 /** \brief (implementation detail) emits a signal
92 * \note This overload is used by signal-emit.hpp.
93 */
94 void
Junxiao Shi1aecae22016-08-30 11:23:59 +000095 operator()(const TArgs&... args, const DummyExtraArg&);
Junxiao Shi018e30d2014-12-25 19:42:35 -070096
Junxiao Shi8d71fdb2014-12-07 21:55:19 -070097 // make Owner a friend of Signal<Owner, ...> so that API for owner can be called
Junxiao Shi8d71fdb2014-12-07 21:55:19 -070098 friend Owner;
Junxiao Shi8d71fdb2014-12-07 21:55:19 -070099
100private: // internal implementation
101 typedef Signal<Owner, TArgs...> Self;
102 struct Slot;
103
104 /** \brief stores slots
105 * \note std::list is used because iterators must not be invalidated
106 * when other slots are added or removed
107 */
108 typedef std::list<Slot> SlotList;
109
110 /** \brief stores a handler function, and a function to disconnect this handler
111 */
112 struct Slot
113 {
114 /** \brief the handler function who will receive emitted signals
115 */
116 Handler handler;
117
118 /** \brief the disconnect function which will disconnect this handler
119 *
Davide Pesaventobe7804f2015-10-23 00:53:47 +0200120 * In practice this is the Signal::disconnect method bound to an iterator
121 * pointing at this slot.
Junxiao Shi8d71fdb2014-12-07 21:55:19 -0700122 *
123 * This is the only shared_ptr to this function object.
124 * Connection class has a weak_ptr which references the same function object.
Davide Pesaventobe7804f2015-10-23 00:53:47 +0200125 * When the slot is erased or the signal is destructed, this function object is
Junxiao Shi8d71fdb2014-12-07 21:55:19 -0700126 * 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
150template<typename Owner, typename ...TArgs>
151Signal<Owner, TArgs...>::Signal()
152 : m_isExecuting(false)
153{
154}
155
156template<typename Owner, typename ...TArgs>
Junxiao Shibbb24352015-02-28 21:23:51 -0700157Signal<Owner, TArgs...>::~Signal()
158{
159 BOOST_ASSERT(!m_isExecuting);
160}
161
162template<typename Owner, typename ...TArgs>
Davide Pesaventobe7804f2015-10-23 00:53:47 +0200163Connection
Junxiao Shi8d71fdb2014-12-07 21:55:19 -0700164Signal<Owner, TArgs...>::connect(const Handler& handler)
165{
166 typename SlotList::iterator it = m_slots.insert(m_slots.end(), {handler, nullptr});
167 it->disconnect = make_shared<function<void()>>(bind(&Self::disconnect, this, it));
168
169 return signal::Connection(weak_ptr<function<void()>>(it->disconnect));
170}
171
172template<typename Owner, typename ...TArgs>
Davide Pesaventobe7804f2015-10-23 00:53:47 +0200173Connection
Junxiao Shiecc57b52015-01-01 10:47:08 -0700174Signal<Owner, TArgs...>::connectSingleShot(const Handler& handler)
175{
176 typename SlotList::iterator it = m_slots.insert(m_slots.end(), {nullptr, nullptr});
177 it->disconnect = make_shared<function<void()>>(bind(&Self::disconnect, this, it));
178 signal::Connection conn(weak_ptr<function<void()>>(it->disconnect));
179
180 it->handler = [conn, handler] (const TArgs&... args) mutable {
181 handler(args...);
182 conn.disconnect();
183 };
184
185 return conn;
186}
187
188template<typename Owner, typename ...TArgs>
Davide Pesaventobe7804f2015-10-23 00:53:47 +0200189void
Junxiao Shi8d71fdb2014-12-07 21:55:19 -0700190Signal<Owner, TArgs...>::disconnect(typename SlotList::iterator it)
191{
Davide Pesaventobe7804f2015-10-23 00:53:47 +0200192 // 'it' could be const_iterator, but gcc 4.6 doesn't support std::list::erase(const_iterator)
Junxiao Shi8d71fdb2014-12-07 21:55:19 -0700193
194 if (m_isExecuting) {
195 // during signal emission, only the currently executing handler can be disconnected
Davide Pesaventobe7804f2015-10-23 00:53:47 +0200196 BOOST_ASSERT_MSG(it == m_currentSlot, "cannot disconnect another handler from a handler");
197
198 // this serves to indicate that the current slot needs to be erased from the list
199 // after it finishes executing; we cannot do it here because of bug #2333
200 m_currentSlot = m_slots.end();
201
202 // expire all weak_ptrs, to prevent double disconnections
203 it->disconnect.reset();
Junxiao Shi8d71fdb2014-12-07 21:55:19 -0700204 }
Davide Pesaventobe7804f2015-10-23 00:53:47 +0200205 else {
206 m_slots.erase(it);
207 }
Junxiao Shi8d71fdb2014-12-07 21:55:19 -0700208}
209
210template<typename Owner, typename ...TArgs>
Davide Pesaventobe7804f2015-10-23 00:53:47 +0200211bool
Junxiao Shi8d71fdb2014-12-07 21:55:19 -0700212Signal<Owner, TArgs...>::isEmpty() const
213{
214 return !m_isExecuting && m_slots.empty();
215}
216
217template<typename Owner, typename ...TArgs>
Davide Pesaventobe7804f2015-10-23 00:53:47 +0200218void
Junxiao Shi8d71fdb2014-12-07 21:55:19 -0700219Signal<Owner, TArgs...>::operator()(const TArgs&... args)
220{
221 BOOST_ASSERT_MSG(!m_isExecuting, "cannot emit signal from a handler");
Davide Pesaventobe7804f2015-10-23 00:53:47 +0200222
Junxiao Shi8d71fdb2014-12-07 21:55:19 -0700223 if (m_slots.empty()) {
224 return;
225 }
Junxiao Shi8d71fdb2014-12-07 21:55:19 -0700226
Davide Pesaventobe7804f2015-10-23 00:53:47 +0200227 auto it = m_slots.begin();
228 auto last = std::prev(m_slots.end());
229 m_isExecuting = true;
Junxiao Shi8d71fdb2014-12-07 21:55:19 -0700230
231 try {
232 bool isLast = false;
233 while (!isLast) {
234 m_currentSlot = it;
235 isLast = it == last;
Junxiao Shi8d71fdb2014-12-07 21:55:19 -0700236
237 m_currentSlot->handler(args...);
Davide Pesaventobe7804f2015-10-23 00:53:47 +0200238
239 if (m_currentSlot == m_slots.end())
240 it = m_slots.erase(it);
241 else
242 ++it;
Junxiao Shi8d71fdb2014-12-07 21:55:19 -0700243 }
244 }
245 catch (...) {
246 m_isExecuting = false;
247 throw;
248 }
Davide Pesaventobe7804f2015-10-23 00:53:47 +0200249
Junxiao Shi8d71fdb2014-12-07 21:55:19 -0700250 m_isExecuting = false;
251}
252
Junxiao Shi018e30d2014-12-25 19:42:35 -0700253template<typename Owner, typename ...TArgs>
Davide Pesaventobe7804f2015-10-23 00:53:47 +0200254void
Junxiao Shi018e30d2014-12-25 19:42:35 -0700255Signal<Owner, TArgs...>::operator()(const TArgs&... args, const DummyExtraArg&)
256{
257 this->operator()(args...);
258}
259
Junxiao Shi8d71fdb2014-12-07 21:55:19 -0700260} // namespace signal
261
262// expose as ndn::util::Signal
263using signal::Signal;
264
265} // namespace util
266} // namespace ndn
267
268#endif // NDN_UTIL_SIGNAL_SIGNAL_HPP