OXYGENHUB
◆ main openmsx / src / events / EventDistributor.cc
3.6 KiB · C++ · last changed chore(): initial commit 1 month ago source blame history raw
1
#include "EventDistributor.hh"
2
3
#include "EventListener.hh"
4
#include "InputEventGenerator.hh"
5
6
#include "Interpreter.hh"
7
#include "RTScheduler.hh"
8
#include "Reactor.hh"
9
#include "Thread.hh"
10
11
#include "stl.hh"
12
13
#include <algorithm>
14
#include <cassert>
15
16
namespace openmsx {
17
18
EventDistributor::EventDistributor(Reactor& reactor_)
19
	: reactor(reactor_)
20
{
21
}
22
23
void EventDistributor::registerEventListener(
24
		EventType type, EventListener& listener, Priority priority)
25
{
26
	std::scoped_lock lock(mutex);
27
	auto& priorityMap = listeners[size_t(type)];
28
	// a listener may only be registered once for each type
29
	assert(!contains(priorityMap, &listener, &Entry::listener));
30
	// insert at highest position that keeps listeners sorted on priority
31
	auto it = std::ranges::upper_bound(priorityMap, priority, {}, &Entry::priority);
32
	priorityMap.emplace(it, Entry{.priority = priority, .listener = &listener});
33
}
34
35
void EventDistributor::unregisterEventListener(
36
		EventType type, EventListener& listener)
37
{
38
	std::scoped_lock lock(mutex);
39
	auto& priorityMap = listeners[size_t(type)];
40
	priorityMap.erase(rfind_unguarded(priorityMap, &listener, &Entry::listener));
41
}
42
43
void EventDistributor::distributeEvent(Event&& event)
44
{
45
	// TODO: Implement a real solution against modifying data structure while
46
	//       iterating through it.
47
	//       For example, assign nullptr first and then iterate again after
48
	//       delivering events to remove the nullptr values.
49
	// TODO: Is it useful to test for 0 listeners or should we just always
50
	//       queue the event?
51
	std::unique_lock lock(mutex);
52
	if (!listeners[size_t(getType(event))].empty()) {
53
		scheduledEvents.push_back(std::move(event));
54
		// must release lock, otherwise there's a deadlock:
55
		//   thread 1: Reactor::deleteMotherBoard()
56
		//             EventDistributor::unregisterEventListener()
57
		//   thread 2: EventDistributor::distributeEvent()
58
		//             Reactor::enterMainLoop()
59
		lock.unlock();
60
		reactor.enterMainLoop();
61
	}
62
}
63
64
bool EventDistributor::isRegistered(EventType type, EventListener* listener) const
65
{
66
	return contains(listeners[size_t(type)], listener, &Entry::listener);
67
}
68
69
void EventDistributor::deliverEvents(std::optional<int> timeoutMs)
70
{
71
	static PriorityMap priorityMapCopy; // static to preserve capacity
72
	static EventQueue eventsCopy;       // static to preserve capacity
73
74
	assert(Thread::isMainThread());
75
76
	reactor.getInputEventGenerator().poll(timeoutMs);
77
	reactor.getInterpreter().poll();
78
	reactor.getRTScheduler().execute();
79
80
	std::unique_lock lock(mutex);
81
	// It's possible that executing an event triggers scheduling of another
82
	// event. We also want to execute those secondary events. That's why
83
	// we have this while loop here.
84
	// For example the 'loadstate' command event, triggers a machine switch
85
	// event and as reaction to the latter event, AfterCommand will
86
	// unsubscribe from the ols MSXEventDistributor. This really should be
87
	// done before we exit this method.
88
	while (!scheduledEvents.empty()) {
89
		assert(eventsCopy.empty());
90
		swap(eventsCopy, scheduledEvents);
91
		for (const auto& event : eventsCopy) {
92
			auto type = getType(event);
93
			priorityMapCopy = listeners[size_t(type)];
94
			lock.unlock();
95
			auto allowPriority = Priority::LOWEST; // allow all
96
			for (const auto& e : priorityMapCopy) {
97
				// It's possible delivery to one of the previous
98
				// Listeners unregistered the current Listener.
99
				if (!isRegistered(type, e.listener)) continue;
100
101
				if (e.priority > allowPriority) break;
102
103
				// This might throw, e.g. when failing to initialize video system
104
				if (e.listener->signalEvent(event)) {
105
					allowPriority = e.priority;
106
				}
107
			}
108
			lock.lock();
109
		}
110
		eventsCopy.clear();
111
	}
112
}
113
114
} // namespace openmsx