blob: e1c01df86bf024931a6d91405b08fa0a359f4e43 [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
66private: // API for owner
67 /** \retval true if there is no connection
68 */
69 bool
70 isEmpty() const;
71
72 /** \brief emits a signal
73 * \param args arguments passed to all handlers
74 * \warning Emitting the signal from a handler is undefined behavior.
75 * \note If a handler throws, the exception will be propagated to the caller
76 * who emits this signal, and some handlers may not be executed.
77 */
78 void
79 operator()(const TArgs&...args);
80
Junxiao Shi018e30d2014-12-25 19:42:35 -070081 /** \brief (implementation detail) emits a signal
82 * \note This overload is used by signal-emit.hpp.
83 */
84 void
85 operator()(const TArgs&...args, const DummyExtraArg&);
86
Junxiao Shi8d71fdb2014-12-07 21:55:19 -070087 // make Owner a friend of Signal<Owner, ...> so that API for owner can be called
88#if NDN_CXX_HAVE_CXX_FRIEND_TYPENAME
89 friend Owner;
90#elif NDN_CXX_HAVE_CXX_FRIEND_TYPENAME_WRAPPER
91 template<typename T>
92 struct TypeWrapper
93 {
94 typedef T Type;
95 }; // http://stackoverflow.com/a/5608542/3729203
96 friend class TypeWrapper<Owner>::Type;
97#else
98# error "cannot declare Owner as friend"
99#endif
100
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 *
121 * This is .disconnect method bound with the iterator to this slot.
122 *
123 * This is the only shared_ptr to this function object.
124 * Connection class has a weak_ptr which references the same function object.
125 * When the slot is removed or the signal is destructed, this function object would be
126 * 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>
157inline Connection
158Signal<Owner, TArgs...>::connect(const Handler& handler)
159{
160 typename SlotList::iterator it = m_slots.insert(m_slots.end(), {handler, nullptr});
161 it->disconnect = make_shared<function<void()>>(bind(&Self::disconnect, this, it));
162
163 return signal::Connection(weak_ptr<function<void()>>(it->disconnect));
164}
165
166template<typename Owner, typename ...TArgs>
167inline void
168Signal<Owner, TArgs...>::disconnect(typename SlotList::iterator it)
169{
170 // it could be const_iterator, but gcc 4.6 doesn't support std::list::erase(const_iterator)
171
172 if (m_isExecuting) {
173 // during signal emission, only the currently executing handler can be disconnected
174 BOOST_ASSERT_MSG(it == m_currentSlot,
175 "cannot disconnect another handler from a handler");
176 m_currentSlot = m_slots.end(); // prevent disconnect twice
177 }
178 m_slots.erase(it);
179}
180
181template<typename Owner, typename ...TArgs>
182inline bool
183Signal<Owner, TArgs...>::isEmpty() const
184{
185 return !m_isExecuting && m_slots.empty();
186}
187
188template<typename Owner, typename ...TArgs>
189inline void
190Signal<Owner, TArgs...>::operator()(const TArgs&... args)
191{
192 BOOST_ASSERT_MSG(!m_isExecuting, "cannot emit signal from a handler");
193 if (m_slots.empty()) {
194 return;
195 }
196 m_isExecuting = true;
197
198 typename SlotList::iterator it = m_slots.begin();
199 typename SlotList::iterator last = m_slots.end();
200 --last;
201
202 try {
203 bool isLast = false;
204 while (!isLast) {
205 m_currentSlot = it;
206 isLast = it == last;
207 ++it;
208
209 m_currentSlot->handler(args...);
210 }
211 }
212 catch (...) {
213 m_isExecuting = false;
214 throw;
215 }
216 m_isExecuting = false;
217}
218
Junxiao Shi018e30d2014-12-25 19:42:35 -0700219template<typename Owner, typename ...TArgs>
220inline void
221Signal<Owner, TArgs...>::operator()(const TArgs&... args, const DummyExtraArg&)
222{
223 this->operator()(args...);
224}
225
Junxiao Shi8d71fdb2014-12-07 21:55:19 -0700226} // namespace signal
227
228// expose as ndn::util::Signal
229using signal::Signal;
230
231} // namespace util
232} // namespace ndn
233
234#endif // NDN_UTIL_SIGNAL_SIGNAL_HPP