SPARK  0.1.0
A general purpose game engine written in C++.
Loading...
Searching...
No Matches
Signal.h
1#pragma once
2
3#include "spark/patterns/Slot.h"
4
5#include "spark/mpl/typelist.h"
6
7#include <algorithm>
8#include <ranges>
9
10namespace spark::patterns
11{
12 namespace details
13 {
14 template <typename T, typename U>
16 {
17 static constexpr bool value = std::is_convertible_v<T, U> || std::is_same_v<T, U>;
18 };
19 }
20
21 template <typename... Args>
23 : m_connections(), m_sequence(0) {}
24
25 template <typename... Args>
26 Signal<Args...>::Signal(Signal&& signal) noexcept
27 {
28 move(&signal);
29 }
30
31 template <typename... Args>
32 Signal<Args...>::~Signal()
33 {
34 clear();
35 }
36
37 template <typename... Args>
38 Signal<Args...>& Signal<Args...>::operator=(Signal&& signal) noexcept
39 {
40 move(&signal);
41 return *this;
42 }
43
44 template <typename... Args>
45 void Signal<Args...>::operator()(Args&&... args) const
46 {
47 emit(std::forward<Args>(args)...);
48 }
49
50 template <typename... Args>
52 {
53 return connect(slot, false);
54 }
55
56 template <typename... Args>
58 {
59 return connect(&slot, false);
60 }
62 template <typename... Args>
64 {
65 return connect(new Slot<Args...>(std::move(slot)), true);
66 }
68 template <typename... Args>
69 std::size_t Signal<Args...>::connect(const std::function<void(Args...)>& callback)
70 {
71 return connect(new Slot<Args...>(callback), true);
72 }
74 template <typename... Args>
75 std::size_t Signal<Args...>::connect(std::function<void(Args...)>&& callback)
76 {
77 return connect(new Slot<Args...>(std::move(callback)), true);
78 }
80 template <typename... Args>
81 void Signal<Args...>::disconnect(std::size_t key)
82 {
83 auto it = std::ranges::find_if(m_connections, [key](const auto& connection) { return connection.second.m_slotKey == key; });
84 if (it != m_connections.end())
85 {
86 it->second.releaseSlot();
87 m_connections.erase(it);
88 }
89 }
90
91 template <typename... Args>
93 {
94 if (slot && slot->isConnected() && slot->m_connection->m_signal == this)
95 disconnect(slot->m_connection->m_slotKey);
96 }
98 template <typename... Args>
100 {
101 disconnect(&slot);
102 }
104 template <typename... Args>
106 {
107 for (auto& connection : m_connections | std::views::values)
108 connection.releaseSlot();
109 m_connections.clear();
110 }
112 template <typename... Args>
113 bool Signal<Args...>::isConnected(const std::size_t key) const
114 {
115 const auto keys = connectedKeys();
116 return std::ranges::find(keys, key) != keys.end();
118
119 template <typename... Args>
120 std::vector<std::size_t> Signal<Args...>::connectedKeys() const
121 {
122 std::vector<std::size_t> keys;
123 keys.reserve(m_connections.size());
124 for (const auto& connection : m_connections | std::views::values)
125 keys.push_back(connection.m_slotKey);
126 return keys;
127 }
128
129 template <typename... Args>
130 std::vector<const Slot<Args...>*> Signal<Args...>::connectedSlots() const
131 {
132 std::vector<const Slot<Args...>*> slots;
133 for (const auto& connection : m_connections | std::views::values)
134 slots.push_back(connection.m_slot);
135 return slots;
136 }
137
138 template <typename... Args>
139 template <typename... FnArgs>
140 void Signal<Args...>::emit(FnArgs&&... args) const
141 {
142 if constexpr (sizeof...(Args) != sizeof...(FnArgs))
143 static_assert(false, "Signal::emit() called with different number of arguments than the signal.");
144 else if constexpr (sizeof...(Args) != 0)
145 static_assert(mpl::typelist_match<details::convertible_or_same,
146 typename mpl::typelist<FnArgs...>::template transform<std::remove_cvref>,
147 typename mpl::typelist<Args...>::template transform<std::remove_cvref>>,
148 "Cannot call Signal::emit() with args that are not in the signal.");
149
150 /*
151 * When emitting, we need to make sure that:
152 * - the program does not crash if a slot is destroyed during the emit
153 * - the program does not crash if the object that emits the signal is destroyed during the emit
154 *
155 * So, we copy the connections into a vector, and then iterate over the vector to avoid any iterator invalidation and check if the connection still exists.
156 */
157
158 auto keys_view = m_connections | std::views::keys;
159 std::vector<std::size_t> keys = {keys_view.begin(), keys_view.end()};
160
161 for (const auto& key : keys)
162 {
163 if (m_connections.contains(key))
164 {
165 const auto& connection = m_connections.at(key);
166 if (connection.m_slot->m_callback)
167 m_connections.at(key).m_slot->m_callback(std::forward<FnArgs>(args)...);
168 }
169 }
170 }
171
172 template <typename... Args>
173 std::size_t Signal<Args...>::connect(Slot<Args...>* slot, bool managed)
174 {
175 if (!slot)
176 return 0;
177
178 if (slot->m_connection)
179 {
180 if (slot->m_connection->m_signal == this)
181 return slot->m_connection->m_slotKey;
182 slot->disconnect();
183 }
184
185 const std::size_t key = ++m_sequence;
186 auto res = m_connections.insert({key - 1, details::Connection(this, slot, key, managed)});
187 slot->m_connection = &res.first->second;
188 return m_sequence;
189 }
190
191 template <typename... Args>
192 void Signal<Args...>::move(Signal* signal)
193 {
194 clear();
195 m_connections = std::move(signal->m_connections);
196 m_sequence = signal->m_sequence;
197
198 for (auto& connection : m_connections | std::views::values)
199 connection.m_signal = this;
200
201 signal->m_sequence = 0;
202 signal->m_connections.clear();
203 }
204}
A signal is a class used to emit events.
Definition Slot.h:10
void disconnect(std::size_t key)
Disconnects a slot from the signal.
Definition Signal.h:81
std::vector< std::size_t > connectedKeys() const
Gets all the keys of the connected slots.
Definition Signal.h:120
bool isConnected(std::size_t key) const
Finds if a slot is connected to the signal.
Definition Signal.h:113
std::size_t connect(Slot< Args... > *slot)
Connects a slot to the signal.
Definition Signal.h:51
void clear()
Disconnects all connected slots from the signal.
Definition Signal.h:105
std::vector< const Slot< Args... > * > connectedSlots() const
Gets all the connected slots.
Definition Signal.h:130
void emit(FnArgs &&... args) const
Emits the signal to all connected slots.
Definition Signal.h:140
void operator()(Args &&... args) const
Emits the signal to all connected slots. Same as emit.
Definition Signal.h:45
A slot is a connection between a signal and a callback.
Definition Slot.h:18
bool isConnected() const
Checks if this slot is connected to a signal.
Definition Slot.h:72
void disconnect()
Disconnects this slot from the signal.
Definition Slot.h:78
Definition typelist.h:8