OXYGENHUB
◆ main openmsx / src / utils / Subject.hh
2.0 KiB · C++ · last changed chore(): initial commit 1 month ago source blame history raw
1
#ifndef SUBJECT_HH
2
#define SUBJECT_HH
3
4
#include "Observer.hh"
5
#include "stl.hh"
6
7
#include <cassert>
8
#include <cstdint>
9
#include <vector>
10
11
namespace openmsx {
12
13
/**
14
 * Generic Gang-of-Four Subject class of the Observer pattern, templatized
15
 * edition.
16
 */
17
template<typename T> class Subject
18
{
19
public:
20
	Subject(const Subject&) = delete;
21
	Subject(Subject&&) = delete;
22
	Subject& operator=(const Subject&) = delete;
23
	Subject& operator=(Subject&&) = delete;
24
25
	void attach(Observer<T>& observer);
26
	void detach(Observer<T>& observer);
27
	[[nodiscard]] bool anyObservers() const { return !observers.empty(); }
28
29
protected:
30
	Subject() = default;
31
	~Subject();
32
	void notify() const;
33
34
private:
35
	enum NotifyState : uint8_t {
36
		IDLE,        // no notify in progress
37
		IN_PROGRESS, // notify in progress, no detach
38
		DETACH,      // notify in progress, some observer(s) have been detached
39
	};
40
41
	mutable std::vector<Observer<T>*> observers; // unordered
42
	mutable NotifyState notifyState = IDLE;
43
};
44
45
template<typename T> Subject<T>::~Subject()
46
{
47
	assert(notifyState == IDLE);
48
	for (auto copy = observers; auto& o : copy) {
49
		o->subjectDeleted(*static_cast<const T*>(this));
50
	}
51
	assert(observers.empty());
52
}
53
54
template<typename T> void Subject<T>::attach(Observer<T>& observer)
55
{
56
	assert(notifyState == IDLE);
57
	observers.push_back(&observer);
58
}
59
60
template<typename T> void Subject<T>::detach(Observer<T>& observer)
61
{
62
	auto it = rfind_unguarded(observers, &observer);
63
	if (notifyState == IDLE) {
64
		move_pop_back(observers, it);
65
	} else {
66
		*it = nullptr; // mark for removal
67
		notifyState = DETACH; // schedule actual removal pass
68
	}
69
}
70
71
template<typename T> void Subject<T>::notify() const
72
{
73
	assert(notifyState == IDLE);
74
	notifyState = IN_PROGRESS;
75
76
	for (auto& o : observers) {
77
		try {
78
			o->update(*static_cast<const T*>(this));
79
		} catch (...) {
80
			assert(false && "Observer::update() shouldn't throw");
81
		}
82
	}
83
84
	if (notifyState == DETACH) {
85
		std::erase(observers, nullptr);
86
	}
87
	notifyState = IDLE;
88
}
89
90
} // namespace openmsx
91
92
#endif