signal.hpp
Go to the documentation of this file.
1 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
2 /*
3  * Copyright (c) 2013-2023 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_CXX_UTIL_SIGNAL_SIGNAL_HPP
23 #define NDN_CXX_UTIL_SIGNAL_SIGNAL_HPP
24 
25 #include "ndn-cxx/util/scope.hpp"
27 
28 #include <list>
29 
30 namespace ndn::signal {
31 
32 class DummyExtraArg;
33 
49 template<typename Owner, typename... TArgs>
50 class Signal : noncopyable
51 {
52 public: // API for anyone
53 #ifndef BOOST_ASSERT_IS_VOID
54  ~Signal() noexcept
55  {
56  BOOST_ASSERT(!m_isExecuting);
57  }
58 #endif
59 
63  using Handler = std::function<void(const TArgs&...)>;
64 
70  connect(Handler handler);
71 
78 
79 private: // API for owner
82  bool
83  isEmpty() const;
84 
92  void
93  operator()(const TArgs&... args);
94 
98  void
99  operator()(const TArgs&... args, const DummyExtraArg&);
100 
101  // make Owner a friend of Signal<Owner, ...> so that API for owner can be called
102  friend Owner;
103 
104 private: // internal implementation
105  using Self = Signal<Owner, TArgs...>;
106 
109  struct Slot
110  {
113  Handler handler;
114 
125  shared_ptr<DisconnectFunction> disconnect;
126  };
127 
132  using SlotList = std::list<Slot>;
133  SlotList m_slots;
134 
137  bool m_isExecuting = false;
138 
142  typename SlotList::iterator m_currentSlot;
143 
146  void
147  disconnect(typename SlotList::iterator it);
148 };
149 
150 template<typename Owner, typename... TArgs>
151 Connection
153 {
154  auto it = m_slots.insert(m_slots.end(), {std::move(handler), nullptr});
155  it->disconnect = make_shared<DisconnectFunction>([=] { disconnect(it); });
156 
157  return signal::Connection(it->disconnect);
158 }
159 
160 template<typename Owner, typename... TArgs>
163 {
164  auto it = m_slots.insert(m_slots.end(), {nullptr, nullptr});
165  it->disconnect = make_shared<DisconnectFunction>([=] { disconnect(it); });
166  signal::Connection conn(it->disconnect);
167 
168  it->handler = [conn, handler = std::move(handler)] (const TArgs&... args) mutable {
169  handler(args...);
170  conn.disconnect();
171  };
172 
173  return conn;
174 }
175 
176 template<typename Owner, typename... TArgs>
177 void
178 Signal<Owner, TArgs...>::disconnect(typename SlotList::iterator it)
179 {
180  if (m_isExecuting) {
181  // during signal emission, only the currently executing handler can be disconnected
182  BOOST_ASSERT_MSG(it == m_currentSlot, "cannot disconnect another handler from a handler");
183 
184  // this serves to indicate that the current slot needs to be erased from the list
185  // after it finishes executing; we cannot do it here because of bug #2333
186  m_currentSlot = m_slots.end();
187 
188  // expire all weak_ptrs, to prevent double disconnections
189  it->disconnect.reset();
190  }
191  else {
192  m_slots.erase(it);
193  }
194 }
195 
196 template<typename Owner, typename... TArgs>
197 bool
198 Signal<Owner, TArgs...>::isEmpty() const
199 {
200  return !m_isExecuting && m_slots.empty();
201 }
202 
203 template<typename Owner, typename... TArgs>
204 void
205 Signal<Owner, TArgs...>::operator()(const TArgs&... args)
206 {
207  BOOST_ASSERT_MSG(!m_isExecuting, "cannot emit signal from a handler");
208 
209  if (m_slots.empty()) {
210  return;
211  }
212 
213  auto guard = make_scope_exit([this] { m_isExecuting = false; });
214  m_isExecuting = true;
215 
216  auto it = m_slots.begin();
217  auto last = std::prev(m_slots.end());
218  bool isLast = false;
219  while (!isLast) {
220  m_currentSlot = it;
221  isLast = it == last;
222 
223  m_currentSlot->handler(args...);
224 
225  if (m_currentSlot == m_slots.end())
226  it = m_slots.erase(it);
227  else
228  ++it;
229  }
230 }
231 
232 template<typename Owner, typename... TArgs>
233 void
234 Signal<Owner, TArgs...>::operator()(const TArgs&... args, const DummyExtraArg&)
235 {
236  this->operator()(args...);
237 }
238 
239 } // namespace ndn::signal
240 
241 #endif // NDN_CXX_UTIL_SIGNAL_SIGNAL_HPP
Represents a connection to a signal.
Definition: connection.hpp:38
void disconnect()
Disconnects from the signal.
Definition: connection.cpp:32
(implementation detail) a filler for extra argument
Definition: emit.hpp:41
Provides a lightweight signal / event system.
Definition: signal.hpp:51
Connection connect(Handler handler)
Connects a handler to the signal.
Definition: signal.hpp:152
Connection connectSingleShot(Handler handler)
Connects a single-shot handler to the signal.
Definition: signal.hpp:162
~Signal() noexcept
Definition: signal.hpp:54
std::function< void(const TArgs &...)> Handler
Represents a function that can connect to the signal.
Definition: signal.hpp:63