blob: 6fd6c70e0bca88596ca58fc8f580c5922e6ecf79 [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
32/** \brief provides a lightweight signal / event system
33 *
34 * To declare a signal:
35 * public:
36 * Signal<Owner, T1, T2> signalName;
37 * To connect to a signal:
38 * owner->signalName.connect(f);
39 * Multiple functions can connect to the same signal.
40 * To emit a signal from owner:
41 * this->signalName(arg1, arg2);
42 *
43 * \tparam Owner the signal owner class; only this class can emit the signal
44 * \tparam TArgs types of signal arguments
45 * \sa signal-emit.hpp allows owner's derived classes to emit signals
46 */
47template<typename Owner, typename ...TArgs>
48class Signal : noncopyable
49{
50public: // API for anyone
51 /** \brief represents a function that can connect to the signal
52 */
53 typedef function<void(const TArgs&...)> Handler;
54
55 Signal();
56
57 /** \brief connects a handler to the signal
58 * \note If invoked from a handler, the new handler won't receive the current emitted signal.
59 */
60 Connection
61 connect(const Handler& handler);
62
63private: // API for owner
64 /** \retval true if there is no connection
65 */
66 bool
67 isEmpty() const;
68
69 /** \brief emits a signal
70 * \param args arguments passed to all handlers
71 * \warning Emitting the signal from a handler is undefined behavior.
72 * \note If a handler throws, the exception will be propagated to the caller
73 * who emits this signal, and some handlers may not be executed.
74 */
75 void
76 operator()(const TArgs&...args);
77
78 // make Owner a friend of Signal<Owner, ...> so that API for owner can be called
79#if NDN_CXX_HAVE_CXX_FRIEND_TYPENAME
80 friend Owner;
81#elif NDN_CXX_HAVE_CXX_FRIEND_TYPENAME_WRAPPER
82 template<typename T>
83 struct TypeWrapper
84 {
85 typedef T Type;
86 }; // http://stackoverflow.com/a/5608542/3729203
87 friend class TypeWrapper<Owner>::Type;
88#else
89# error "cannot declare Owner as friend"
90#endif
91
92private: // internal implementation
93 typedef Signal<Owner, TArgs...> Self;
94 struct Slot;
95
96 /** \brief stores slots
97 * \note std::list is used because iterators must not be invalidated
98 * when other slots are added or removed
99 */
100 typedef std::list<Slot> SlotList;
101
102 /** \brief stores a handler function, and a function to disconnect this handler
103 */
104 struct Slot
105 {
106 /** \brief the handler function who will receive emitted signals
107 */
108 Handler handler;
109
110 /** \brief the disconnect function which will disconnect this handler
111 *
112 * This is .disconnect method bound with the iterator to this slot.
113 *
114 * This is the only shared_ptr to this function object.
115 * Connection class has a weak_ptr which references the same function object.
116 * When the slot is removed or the signal is destructed, this function object would be
117 * destructed, and the related Connections cannot disconnect this slot again.
118 */
119 shared_ptr<function<void()>> disconnect;
120 };
121
122 /** \brief stores slots
123 */
124 SlotList m_slots;
125
126 /** \brief is a signal handler executing?
127 */
128 bool m_isExecuting;
129
130 /** \brief iterator to current executing slot
131 * \note This field is meaningful when isExecuting==true
132 */
133 typename SlotList::iterator m_currentSlot;
134
135 /** \brief disconnects the handler in a slot
136 */
137 void
138 disconnect(typename SlotList::iterator it);
139};
140
141template<typename Owner, typename ...TArgs>
142Signal<Owner, TArgs...>::Signal()
143 : m_isExecuting(false)
144{
145}
146
147template<typename Owner, typename ...TArgs>
148inline Connection
149Signal<Owner, TArgs...>::connect(const Handler& handler)
150{
151 typename SlotList::iterator it = m_slots.insert(m_slots.end(), {handler, nullptr});
152 it->disconnect = make_shared<function<void()>>(bind(&Self::disconnect, this, it));
153
154 return signal::Connection(weak_ptr<function<void()>>(it->disconnect));
155}
156
157template<typename Owner, typename ...TArgs>
158inline void
159Signal<Owner, TArgs...>::disconnect(typename SlotList::iterator it)
160{
161 // it could be const_iterator, but gcc 4.6 doesn't support std::list::erase(const_iterator)
162
163 if (m_isExecuting) {
164 // during signal emission, only the currently executing handler can be disconnected
165 BOOST_ASSERT_MSG(it == m_currentSlot,
166 "cannot disconnect another handler from a handler");
167 m_currentSlot = m_slots.end(); // prevent disconnect twice
168 }
169 m_slots.erase(it);
170}
171
172template<typename Owner, typename ...TArgs>
173inline bool
174Signal<Owner, TArgs...>::isEmpty() const
175{
176 return !m_isExecuting && m_slots.empty();
177}
178
179template<typename Owner, typename ...TArgs>
180inline void
181Signal<Owner, TArgs...>::operator()(const TArgs&... args)
182{
183 BOOST_ASSERT_MSG(!m_isExecuting, "cannot emit signal from a handler");
184 if (m_slots.empty()) {
185 return;
186 }
187 m_isExecuting = true;
188
189 typename SlotList::iterator it = m_slots.begin();
190 typename SlotList::iterator last = m_slots.end();
191 --last;
192
193 try {
194 bool isLast = false;
195 while (!isLast) {
196 m_currentSlot = it;
197 isLast = it == last;
198 ++it;
199
200 m_currentSlot->handler(args...);
201 }
202 }
203 catch (...) {
204 m_isExecuting = false;
205 throw;
206 }
207 m_isExecuting = false;
208}
209
210} // namespace signal
211
212// expose as ndn::util::Signal
213using signal::Signal;
214
215} // namespace util
216} // namespace ndn
217
218#endif // NDN_UTIL_SIGNAL_SIGNAL_HPP