openMSX
EventDistributor.cc
Go to the documentation of this file.
1 #include "EventDistributor.hh"
2 #include "EventListener.hh"
3 #include "Reactor.hh"
4 #include "Thread.hh"
5 #include "openmsx.hh"
6 #include <algorithm>
7 #include <cassert>
8 
9 using std::pair;
10 using std::string;
11 
12 namespace openmsx {
13 
15  : reactor(reactor_)
16  , sem(1)
17 {
18 }
19 
21  EventType type, EventListener& listener, Priority priority)
22 {
23  ScopedLock lock(sem);
24  auto& priorityMap = listeners[type];
25  for (auto& p : priorityMap) {
26  // a listener may only be registered once for each type
27  assert(p.second != &listener); (void)p;
28  }
29  priorityMap.insert(PriorityMap::value_type(priority, &listener));
30 }
31 
33  EventType type, EventListener& listener)
34 {
35  ScopedLock lock(sem);
36  auto& priorityMap = listeners[type];
37  auto it = find_if(priorityMap.begin(), priorityMap.end(),
38  [&](PriorityMap::value_type v) { return v.second == &listener; });
39  assert(it != priorityMap.end());
40  priorityMap.erase(it);
41 }
42 
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  assert(event.get());
52  ScopedLock lock(sem);
53  if (!listeners[event->getType()].empty()) {
54  scheduledEvents.push_back(event);
55  // must release lock, otherwise there's a deadlock:
56  // thread 1: Reactor::deleteMotherBoard()
57  // EventDistributor::unregisterEventListener()
58  // thread 2: EventDistributor::distributeEvent()
59  // Reactor::enterMainLoop()
60  cond.signalAll();
61  lock.release();
62  reactor.enterMainLoop();
63  }
64 }
65 
66 bool EventDistributor::isRegistered(EventType type, EventListener* listener) const
67 {
68  auto it = listeners.find(type);
69  if (it == listeners.end()) return false;
70 
71  for (auto& p : it->second) {
72  if (p.second == listener) {
73  return true;
74  }
75  }
76  return false;
77 }
78 
80 {
81  assert(Thread::isMainThread());
82 
83  ScopedLock lock(sem);
84  // It's possible that executing an event triggers scheduling of another
85  // event. We also want to execute those secondary events. That's why
86  // we have this while loop here.
87  // For example the 'loadstate' command event, triggers a machine switch
88  // event and as reaction to the latter event, AfterCommand will
89  // unsubscribe from the ols MSXEventDistributor. This really should be
90  // done before we exit this method.
91  while (!scheduledEvents.empty()) {
92  EventQueue eventsCopy;
93  swap(eventsCopy, scheduledEvents);
94 
95  for (auto& event : eventsCopy) {
96  auto type = event->getType();
97  auto priorityMapCopy = listeners[type];
98  sem.up();
99  unsigned allowPriorities = unsigned(-1); // all priorities
100  for (auto& p : priorityMapCopy) {
101  // It's possible delivery to one of the previous
102  // Listeners unregistered the current Listener.
103  if (!isRegistered(type, p.second)) continue;
104 
105  unsigned currentPriority = p.first;
106  if (!(currentPriority & allowPriorities)) continue;
107 
108  unsigned maskPriorities = p.second->signalEvent(event);
109 
110  assert(maskPriorities < currentPriority);
111  allowPriorities &= ~maskPriorities;
112  }
113  sem.down();
114  }
115  }
116 }
117 
118 bool EventDistributor::sleep(unsigned us)
119 {
120  return cond.waitTimeout(us);
121 }
122 
123 } // namespace openmsx