blob: 6eab9943daa5d9c96dd6572a74ceeba448b1c4a5 [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/*
Davide Pesavento844b0932018-05-07 01:00:16 -04003 * Copyright (c) 2013-2018 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"
Davide Pesavento844b0932018-05-07 01:00:16 -040026
Junxiao Shi8d71fdb2014-12-07 21:55:19 -070027#include <list>
28
29namespace ndn {
30namespace util {
31namespace signal {
32
Junxiao Shi018e30d2014-12-25 19:42:35 -070033class DummyExtraArg;
34
Junxiao Shi8d71fdb2014-12-07 21:55:19 -070035/** \brief provides a lightweight signal / event system
36 *
37 * To declare a signal:
38 * public:
39 * Signal<Owner, T1, T2> signalName;
40 * To connect to a signal:
41 * owner->signalName.connect(f);
42 * Multiple functions can connect to the same signal.
43 * To emit a signal from owner:
44 * this->signalName(arg1, arg2);
45 *
46 * \tparam Owner the signal owner class; only this class can emit the signal
47 * \tparam TArgs types of signal arguments
48 * \sa signal-emit.hpp allows owner's derived classes to emit signals
49 */
50template<typename Owner, typename ...TArgs>
51class Signal : noncopyable
52{
53public: // API for anyone
54 /** \brief represents a function that can connect to the signal
55 */
56 typedef function<void(const TArgs&...)> Handler;
57
58 Signal();
59
Junxiao Shibbb24352015-02-28 21:23:51 -070060 ~Signal();
61
Junxiao Shi8d71fdb2014-12-07 21:55:19 -070062 /** \brief connects a handler to the signal
63 * \note If invoked from a handler, the new handler won't receive the current emitted signal.
Junxiao Shi2cec7072014-12-19 19:37:40 -070064 * \warning The handler is permitted to disconnect itself, but it must ensure its validity.
Junxiao Shi8d71fdb2014-12-07 21:55:19 -070065 */
66 Connection
Davide Pesaventodb4da5e2018-06-15 11:37:52 -040067 connect(Handler handler);
Junxiao Shi8d71fdb2014-12-07 21:55:19 -070068
Junxiao Shiecc57b52015-01-01 10:47:08 -070069 /** \brief connects a single-shot handler to the signal
70 *
71 * After the handler is executed once, it is automatically disconnected.
72 */
73 Connection
Davide Pesaventodb4da5e2018-06-15 11:37:52 -040074 connectSingleShot(Handler handler);
Junxiao Shiecc57b52015-01-01 10:47:08 -070075
Junxiao Shi8d71fdb2014-12-07 21:55:19 -070076private: // API for owner
77 /** \retval true if there is no connection
78 */
79 bool
80 isEmpty() const;
81
82 /** \brief emits a signal
83 * \param args arguments passed to all handlers
84 * \warning Emitting the signal from a handler is undefined behavior.
Junxiao Shibbb24352015-02-28 21:23:51 -070085 * \warning Destructing the Signal object during signal emission is undefined behavior.
Junxiao Shi8d71fdb2014-12-07 21:55:19 -070086 * \note If a handler throws, the exception will be propagated to the caller
87 * who emits this signal, and some handlers may not be executed.
88 */
89 void
Junxiao Shi1aecae22016-08-30 11:23:59 +000090 operator()(const TArgs&... args);
Junxiao Shi8d71fdb2014-12-07 21:55:19 -070091
Junxiao Shi018e30d2014-12-25 19:42:35 -070092 /** \brief (implementation detail) emits a signal
93 * \note This overload is used by signal-emit.hpp.
94 */
95 void
Junxiao Shi1aecae22016-08-30 11:23:59 +000096 operator()(const TArgs&... args, const DummyExtraArg&);
Junxiao Shi018e30d2014-12-25 19:42:35 -070097
Junxiao Shi8d71fdb2014-12-07 21:55:19 -070098 // make Owner a friend of Signal<Owner, ...> so that API for owner can be called
Junxiao Shi8d71fdb2014-12-07 21:55:19 -070099 friend Owner;
Junxiao Shi8d71fdb2014-12-07 21:55:19 -0700100
101private: // 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 *
Davide Pesaventobe7804f2015-10-23 00:53:47 +0200121 * In practice this is the Signal::disconnect method bound to an iterator
122 * pointing at this slot.
Junxiao Shi8d71fdb2014-12-07 21:55:19 -0700123 *
124 * This is the only shared_ptr to this function object.
125 * Connection class has a weak_ptr which references the same function object.
Davide Pesaventobe7804f2015-10-23 00:53:47 +0200126 * When the slot is erased or the signal is destructed, this function object is
Junxiao Shi8d71fdb2014-12-07 21:55:19 -0700127 * destructed, and the related Connections cannot disconnect this slot again.
128 */
129 shared_ptr<function<void()>> disconnect;
130 };
131
132 /** \brief stores slots
133 */
134 SlotList m_slots;
135
136 /** \brief is a signal handler executing?
137 */
138 bool m_isExecuting;
139
140 /** \brief iterator to current executing slot
141 * \note This field is meaningful when isExecuting==true
142 */
143 typename SlotList::iterator m_currentSlot;
144
145 /** \brief disconnects the handler in a slot
146 */
147 void
148 disconnect(typename SlotList::iterator it);
149};
150
151template<typename Owner, typename ...TArgs>
152Signal<Owner, TArgs...>::Signal()
153 : m_isExecuting(false)
154{
155}
156
157template<typename Owner, typename ...TArgs>
Junxiao Shibbb24352015-02-28 21:23:51 -0700158Signal<Owner, TArgs...>::~Signal()
159{
160 BOOST_ASSERT(!m_isExecuting);
161}
162
163template<typename Owner, typename ...TArgs>
Davide Pesaventobe7804f2015-10-23 00:53:47 +0200164Connection
Davide Pesaventodb4da5e2018-06-15 11:37:52 -0400165Signal<Owner, TArgs...>::connect(Handler handler)
Junxiao Shi8d71fdb2014-12-07 21:55:19 -0700166{
Davide Pesaventodb4da5e2018-06-15 11:37:52 -0400167 auto it = m_slots.insert(m_slots.end(), {std::move(handler), nullptr});
168 it->disconnect = make_shared<function<void()>>([=] { disconnect(it); });
Junxiao Shi8d71fdb2014-12-07 21:55:19 -0700169
170 return signal::Connection(weak_ptr<function<void()>>(it->disconnect));
171}
172
173template<typename Owner, typename ...TArgs>
Davide Pesaventobe7804f2015-10-23 00:53:47 +0200174Connection
Davide Pesaventodb4da5e2018-06-15 11:37:52 -0400175Signal<Owner, TArgs...>::connectSingleShot(Handler handler)
Junxiao Shiecc57b52015-01-01 10:47:08 -0700176{
Davide Pesavento844b0932018-05-07 01:00:16 -0400177 auto it = m_slots.insert(m_slots.end(), {nullptr, nullptr});
Davide Pesaventodb4da5e2018-06-15 11:37:52 -0400178 it->disconnect = make_shared<function<void()>>([=] { disconnect(it); });
Junxiao Shiecc57b52015-01-01 10:47:08 -0700179 signal::Connection conn(weak_ptr<function<void()>>(it->disconnect));
180
Davide Pesaventodb4da5e2018-06-15 11:37:52 -0400181 it->handler = [conn, handler = std::move(handler)] (const TArgs&... args) mutable {
Junxiao Shiecc57b52015-01-01 10:47:08 -0700182 handler(args...);
183 conn.disconnect();
184 };
185
186 return conn;
187}
188
189template<typename Owner, typename ...TArgs>
Davide Pesaventobe7804f2015-10-23 00:53:47 +0200190void
Junxiao Shi8d71fdb2014-12-07 21:55:19 -0700191Signal<Owner, TArgs...>::disconnect(typename SlotList::iterator it)
192{
Junxiao Shi8d71fdb2014-12-07 21:55:19 -0700193 if (m_isExecuting) {
194 // during signal emission, only the currently executing handler can be disconnected
Davide Pesaventobe7804f2015-10-23 00:53:47 +0200195 BOOST_ASSERT_MSG(it == m_currentSlot, "cannot disconnect another handler from a handler");
196
197 // this serves to indicate that the current slot needs to be erased from the list
198 // after it finishes executing; we cannot do it here because of bug #2333
199 m_currentSlot = m_slots.end();
200
201 // expire all weak_ptrs, to prevent double disconnections
202 it->disconnect.reset();
Junxiao Shi8d71fdb2014-12-07 21:55:19 -0700203 }
Davide Pesaventobe7804f2015-10-23 00:53:47 +0200204 else {
205 m_slots.erase(it);
206 }
Junxiao Shi8d71fdb2014-12-07 21:55:19 -0700207}
208
209template<typename Owner, typename ...TArgs>
Davide Pesaventobe7804f2015-10-23 00:53:47 +0200210bool
Junxiao Shi8d71fdb2014-12-07 21:55:19 -0700211Signal<Owner, TArgs...>::isEmpty() const
212{
213 return !m_isExecuting && m_slots.empty();
214}
215
216template<typename Owner, typename ...TArgs>
Davide Pesaventobe7804f2015-10-23 00:53:47 +0200217void
Junxiao Shi8d71fdb2014-12-07 21:55:19 -0700218Signal<Owner, TArgs...>::operator()(const TArgs&... args)
219{
220 BOOST_ASSERT_MSG(!m_isExecuting, "cannot emit signal from a handler");
Davide Pesaventobe7804f2015-10-23 00:53:47 +0200221
Junxiao Shi8d71fdb2014-12-07 21:55:19 -0700222 if (m_slots.empty()) {
223 return;
224 }
Junxiao Shi8d71fdb2014-12-07 21:55:19 -0700225
Davide Pesaventobe7804f2015-10-23 00:53:47 +0200226 auto it = m_slots.begin();
227 auto last = std::prev(m_slots.end());
228 m_isExecuting = true;
Junxiao Shi8d71fdb2014-12-07 21:55:19 -0700229
230 try {
231 bool isLast = false;
232 while (!isLast) {
233 m_currentSlot = it;
234 isLast = it == last;
Junxiao Shi8d71fdb2014-12-07 21:55:19 -0700235
236 m_currentSlot->handler(args...);
Davide Pesaventobe7804f2015-10-23 00:53:47 +0200237
238 if (m_currentSlot == m_slots.end())
239 it = m_slots.erase(it);
240 else
241 ++it;
Junxiao Shi8d71fdb2014-12-07 21:55:19 -0700242 }
243 }
244 catch (...) {
245 m_isExecuting = false;
246 throw;
247 }
Davide Pesaventobe7804f2015-10-23 00:53:47 +0200248
Junxiao Shi8d71fdb2014-12-07 21:55:19 -0700249 m_isExecuting = false;
250}
251
Junxiao Shi018e30d2014-12-25 19:42:35 -0700252template<typename Owner, typename ...TArgs>
Davide Pesaventobe7804f2015-10-23 00:53:47 +0200253void
Junxiao Shi018e30d2014-12-25 19:42:35 -0700254Signal<Owner, TArgs...>::operator()(const TArgs&... args, const DummyExtraArg&)
255{
256 this->operator()(args...);
257}
258
Junxiao Shi8d71fdb2014-12-07 21:55:19 -0700259} // namespace signal
260
261// expose as ndn::util::Signal
262using signal::Signal;
263
264} // namespace util
265} // namespace ndn
266
267#endif // NDN_UTIL_SIGNAL_SIGNAL_HPP