aboutsummaryrefslogtreecommitdiff
path: root/core/src/Utils/Sigslot.hpp
blob: 9aa5f4b9a05a04a135502fe455175b8ade0867a2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
#pragma once

#include "Utils/fwd.hpp"

#include <cstddef>
#include <functional>
#include <span>
#include <utility>
#include <vector>

class SignalStub {
public:
	/// Non-template interface for Signal<T...> to implement (a barrier to stop template
	/// arguments propagation).
	class IWrapper {
	public:
		virtual ~IWrapper() = default;
		virtual void RemoveFunction(int id) = 0;
	};

	enum {
		InvalidId = -1,
	};

	struct Connection {
		SlotGuard* guard;
		int slotId;
		int id = InvalidId; // If `InvalidId`, then this "spot" is unused

		bool IsOccupied() const;
	};

private:
	std::vector<Connection> mConnections;
	IWrapper* mWrapper;

private:
	template <class...>
	friend class Signal;
	friend class SlotGuard;

	SignalStub(IWrapper& wrapper);
	~SignalStub();

	SignalStub(const SignalStub&) = delete;
	SignalStub& operator=(const SignalStub&) = delete;
	SignalStub(SignalStub&&) = default;
	SignalStub& operator=(SignalStub&&) = default;

	std::span<const Connection> GetConnections() const;
	Connection& InsertConnection(SlotGuard* guard = nullptr);
	void RemoveConnection(int id);
	void RemoveConnectionFor(SlotGuard& guard);
	void RemoveAllConnections();
};

template <class... TArgs>
class Signal : public SignalStub::IWrapper {
private:
	// Must be in this order so that mFunctions is still intact when mStub's destructor runs
	std::vector<std::function<void(TArgs...)>> mFunctions;
	SignalStub mStub;

public:
	Signal()
		: mStub(*this) {
	}

	virtual ~Signal() = default;

	Signal(const Signal&) = delete;
	Signal& operator=(const Signal&) = delete;
	Signal(Signal&&) = default;
	Signal& operator=(Signal&&) = default;

	void operator()(TArgs... args) {
		for (auto& conn : mStub.GetConnections()) {
			if (conn.IsOccupied()) {
				mFunctions[conn.id](std::forward<TArgs>(args)...);
			}
		}
	}

	template <class TFunction>
	int Connect(TFunction slot) {
		auto& conn = mStub.InsertConnection();
		mFunctions.resize(std::max(mFunctions.size(), (size_t)conn.id + 1));
		mFunctions[conn.id] = std::move(slot);
		return conn.id;
	}

	template <class TFunction>
	int Connect(SlotGuard& guard, TFunction slot) {
		auto& conn = mStub.InsertConnection(&guard);
		mFunctions.resize(std::max(mFunctions.size(), (size_t)conn.id + 1));
		mFunctions[conn.id] = std::move(slot);
		return conn.id;
	}

	void Disconnect(int id) {
		mStub.RemoveConnection(id);
	}

	void DisconnectFor(SlotGuard& guard) {
		mStub.RemoveConnectionFor(guard);
	}

	void DisconnectAll() {
		mStub.RemoveAllConnections();
	}

	virtual void RemoveFunction(int id) {
		mFunctions[id] = {};
	}
};

/// Automatic disconnection mechanism for Signal<>.
/// Bind connection to this guard by using the Connect(SlotGuard&, TFunction) overload.
/// Either DisconnectAll() or the destructor disconnects all connections bound to this guard.
class SlotGuard {
private:
	struct Connection {
		SignalStub* stub = nullptr;
		int stubId = SignalStub::InvalidId;
	};
	std::vector<Connection> mConnections;

public:
	friend class SignalStub;
	SlotGuard();
	~SlotGuard();

	SlotGuard(const SlotGuard&) = delete;
	SlotGuard& operator=(const SlotGuard&) = delete;
	SlotGuard(SlotGuard&&) = default;
	SlotGuard& operator=(SlotGuard&&) = default;

	/// Disconnect all connection associated with this SlotGuard.
	void DisconnectAll();

private:
	/// \return Slot id.
	int InsertConnection(SignalStub& stub, int stubId);
	/// Remove the connection data in this associated with slotId. This does not invoke
	/// the connections' stub's RemoveConnection function.
	void RemoveConnection(int slotId);
	/// Disconnect all connections from the given stub associated with this SlotGuard.
	/// Implementation for SignalStub::RemoveConnectionsFor(SlotGuard&)
	void RemoveConnectionFor(SignalStub& stub);
};