0
|
1 /* |
|
2 * signals.h -- synchronous observer mechanism |
|
3 * |
|
4 * Copyright (c) 2013-2016 David Demelier <markand@malikania.fr> |
|
5 * |
|
6 * Permission to use, copy, modify, and/or distribute this software for any |
|
7 * purpose with or without fee is hereby granted, provided that the above |
|
8 * copyright notice and this permission notice appear in all copies. |
|
9 * |
|
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
|
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
|
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR |
|
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
|
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN |
|
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF |
|
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
|
17 */ |
|
18 |
|
19 #ifndef _IRCCD_SIGNALS_H_ |
|
20 #define _IRCCD_SIGNALS_H_ |
|
21 |
|
22 #include <functional> |
|
23 #include <stack> |
|
24 #include <unordered_map> |
|
25 #include <vector> |
|
26 |
|
27 /** |
|
28 * @file Signals.h |
|
29 * @brief Similar Qt signal subsystem for irccd |
|
30 */ |
|
31 |
|
32 namespace irccd { |
|
33 |
|
34 /** |
|
35 * @class SignalConnection |
|
36 * @brief Stores the reference to the callable |
|
37 * |
|
38 * This class can be stored to remove a registered function from a Signal, be |
|
39 * careful to not mix connections between different signals as they are just |
|
40 * referenced by ids. |
|
41 */ |
|
42 class SignalConnection { |
|
43 private: |
|
44 unsigned m_id; |
|
45 |
|
46 public: |
|
47 /** |
|
48 * Create a signal connection. |
|
49 * |
|
50 * @param id the id |
|
51 */ |
|
52 inline SignalConnection(unsigned id) noexcept |
|
53 : m_id{id} |
|
54 { |
|
55 } |
|
56 |
|
57 /** |
|
58 * Get the reference object. |
|
59 * |
|
60 * @return the id |
|
61 */ |
|
62 inline unsigned id() const noexcept |
|
63 { |
|
64 return m_id; |
|
65 } |
|
66 }; |
|
67 |
|
68 /** |
|
69 * @class Signal |
|
70 * @brief Stores and call registered functions |
|
71 * |
|
72 * This class is intended to be use as a public field in the desired object. |
|
73 * |
|
74 * The user just have to call one of connect(), disconnect() or the call |
|
75 * operator to use this class. |
|
76 * |
|
77 * It stores the callable as std::function so type-erasure is complete. |
|
78 * |
|
79 * The user is responsible of taking care that the object is still alive |
|
80 * in case that the function takes a reference to the object. |
|
81 */ |
|
82 template <typename... Args> |
|
83 class Signal { |
|
84 private: |
|
85 using Function = std::function<void (Args...)>; |
|
86 using FunctionMap = std::unordered_map<unsigned, Function>; |
|
87 using Stack = std::stack<unsigned>; |
|
88 |
|
89 FunctionMap m_functions; |
|
90 Stack m_stack; |
|
91 unsigned m_max{0}; |
|
92 |
|
93 public: |
|
94 /** |
|
95 * Register a new function to the signal. |
|
96 * |
|
97 * @param function the function |
|
98 * @return the connection in case you want to remove it |
|
99 */ |
|
100 inline SignalConnection connect(Function function) noexcept |
|
101 { |
|
102 unsigned id; |
|
103 |
|
104 if (!m_stack.empty()) { |
|
105 id = m_stack.top(); |
|
106 m_stack.pop(); |
|
107 } else { |
|
108 id = m_max ++; |
|
109 } |
|
110 |
|
111 m_functions.emplace(id, std::move(function)); |
|
112 |
|
113 return SignalConnection{id}; |
|
114 } |
|
115 |
|
116 /** |
|
117 * Disconnect a connection. |
|
118 * |
|
119 * @param connection the connection |
|
120 * @warning Be sure that the connection belongs to that signal |
|
121 */ |
|
122 inline void disconnect(const SignalConnection &connection) noexcept |
|
123 { |
|
124 auto value = m_functions.find(connection.id()); |
|
125 |
|
126 if (value != m_functions.end()) { |
|
127 m_functions.erase(connection.id()); |
|
128 m_stack.push(connection.id()); |
|
129 } |
|
130 } |
|
131 |
|
132 /** |
|
133 * Remove all registered functions. |
|
134 */ |
|
135 inline void clear() |
|
136 { |
|
137 m_functions.clear(); |
|
138 m_max = 0; |
|
139 |
|
140 while (!m_stack.empty()) |
|
141 m_stack.pop(); |
|
142 } |
|
143 |
|
144 /** |
|
145 * Call every functions. |
|
146 * |
|
147 * @param args the arguments to pass to the signal |
|
148 */ |
|
149 void operator()(Args... args) const |
|
150 { |
|
151 /* |
|
152 * Make a copy of the ids before iterating because the callbacks may eventually remove or modify |
|
153 * the list. |
|
154 */ |
|
155 std::vector<unsigned> ids; |
|
156 |
|
157 for (auto &pair : m_functions) |
|
158 ids.push_back(pair.first); |
|
159 |
|
160 /* |
|
161 * Now iterate while checking if the next id is still available, however if any new signals were |
|
162 * added while iterating, they will not be called immediately. |
|
163 */ |
|
164 for (unsigned i : ids) { |
|
165 auto it = m_functions.find(i); |
|
166 |
|
167 if (it != m_functions.end()) { |
|
168 it->second(args...); |
|
169 } |
|
170 } |
|
171 } |
|
172 }; |
|
173 |
|
174 } // !irccd |
|
175 |
|
176 #endif // !_IRCCD_SIGNALS_H_ |