openMSX
EventDelay.cc
Go to the documentation of this file.
1#include "EventDelay.hh"
2#include "EventDistributor.hh"
4#include "ReverseManager.hh"
5#include "Event.hh"
6#include "Timer.hh"
7#include "MSXException.hh"
8#include "one_of.hh"
9#include "ranges.hh"
10#include "stl.hh"
11#include <cassert>
12
13namespace openmsx {
14
16 CommandController& commandController,
17 EventDistributor& eventDistributor_,
18 MSXEventDistributor& msxEventDistributor_,
19 ReverseManager& reverseManager)
20 : Schedulable(scheduler_)
21 , eventDistributor(eventDistributor_)
22 , msxEventDistributor(msxEventDistributor_)
23 , prevEmu(EmuTime::zero())
24 , prevReal(Timer::getTime())
25 , delaySetting(
26 commandController, "inputdelay",
27 "delay input to avoid key-skips", 0.0, 0.0, 10.0)
28{
29 eventDistributor.registerEventListener(
31 eventDistributor.registerEventListener(
33
34 eventDistributor.registerEventListener(
36 eventDistributor.registerEventListener(
38 eventDistributor.registerEventListener(
40
41 eventDistributor.registerEventListener(
43 eventDistributor.registerEventListener(
45 eventDistributor.registerEventListener(
47 eventDistributor.registerEventListener(
49
50 reverseManager.registerEventDelay(*this);
51}
52
54{
55 eventDistributor.unregisterEventListener(
56 EventType::KEY_DOWN, *this);
57 eventDistributor.unregisterEventListener(
58 EventType::KEY_UP, *this);
59
60 eventDistributor.unregisterEventListener(
62 eventDistributor.unregisterEventListener(
64 eventDistributor.unregisterEventListener(
66
67 eventDistributor.unregisterEventListener(
69 eventDistributor.unregisterEventListener(
70 EventType::JOY_HAT, *this);
71 eventDistributor.unregisterEventListener(
73 eventDistributor.unregisterEventListener(
75}
76
77int EventDelay::signalEvent(const Event& event) noexcept
78{
79 toBeScheduledEvents.push_back(event);
80 if (delaySetting.getDouble() == 0.0) {
81 sync(getCurrentTime());
82 }
83 return 0;
84}
85
86void EventDelay::sync(EmuTime::param curEmu)
87{
88 auto curRealTime = Timer::getTime();
89 auto realDuration = curRealTime - prevReal;
90 prevReal = curRealTime;
91 auto emuDuration = curEmu - prevEmu;
92 prevEmu = curEmu;
93
94 double factor = emuDuration.toDouble() / realDuration;
95 EmuDuration extraDelay(delaySetting.getDouble());
96
97#if PLATFORM_ANDROID
98 // The virtual keyboard on Android sends a key press and the
99 // corresponding key release event directly after each other, without a
100 // delay. It sends both events either when the user has finished a
101 // short tap or alternatively after the user has hold the button
102 // pressed for a few seconds and then has selected the appropriate
103 // character from the multi-character-popup that the virtual keyboard
104 // displays when the user holds a button pressed for a short while.
105 // Either way, the key release event comes way too short after the
106 // press event for the MSX to process it. The two events follow each
107 // other within a few milliseconds at most. Therefore, on Android,
108 // special logic must be foreseen to delay the release event for a
109 // short while. This special logic correlates each key release event
110 // with the corresponding press event for the same key. If they are
111 // less then 2/50 second apart, the release event gets delayed until
112 // the next sync call. The 2/50 second has been chosen because it can
113 // take up to 2 vertical interrupts (2 screen refreshes) for the MSX to
114 // see the key press in the keyboard matrix, thus, 2/50 seconds is the
115 // minimum delay required for an MSX running in PAL mode.
116 std::vector<Event> toBeRescheduledEvents;
117#endif
118
119 EmuTime time = curEmu + extraDelay;
120 for (auto& e : toBeScheduledEvents) {
121#if PLATFORM_ANDROID
123 const auto& keyEvent = get<KeyEvent>(e);
124 int maskedKeyCode = int(keyEvent.getKeyCode()) & int(Keys::K_MASK);
125 auto it = ranges::find(nonMatchedKeyPresses, maskedKeyCode,
126 [](const auto& p) { return p.first; });
127 if (getType(e) == EventType::KEY_DOWN) {
128 if (it == end(nonMatchedKeyPresses)) {
129 nonMatchedKeyPresses.emplace_back(maskedKeyCode, e);
130 } else {
131 it->second = e;
132 }
133 } else {
134 if (it != end(nonMatchedKeyPresses)) {
135 const auto& timedPressEvent = get<TimedEvent>(it->second);
136 const auto& timedReleaseEvent = get<TimedEvent>(e);
137 auto pressRealTime = timedPressEvent.getRealTime();
138 auto releaseRealTime = timedReleaseEvent.getRealTime();
139 auto deltaTime = releaseRealTime - pressRealTime;
140 if (deltaTime <= 2000000 / 50) {
141 // The key release came less then 2 MSX interrupts from the key press.
142 // Reschedule it for the next sync, with the realTime updated to now, so that it seems like the
143 // key was released now and not when android released it.
144 // Otherwise, the offset calculation for the emutime further down below will go wrong on the next sync
145 Event newKeyupEvent = Event::create<KeyUpEvent>(keyEvent.getKeyCode());
146 toBeRescheduledEvents.push_back(newKeyupEvent);
147 continue; // continue with next to be scheduled event
148 }
149 move_pop_back(nonMatchedKeyPresses, it);
150 }
151 }
152 }
153#endif
154 scheduledEvents.push_back(e);
155 const auto& timedEvent = get<TimedEvent>(e);
156 auto eventRealTime = timedEvent.getRealTime();
157 assert(eventRealTime <= curRealTime);
158 auto offset = curRealTime - eventRealTime;
159 EmuDuration emuOffset(factor * offset);
160 auto schedTime = (emuOffset < extraDelay)
161 ? time - emuOffset
162 : curEmu;
163 assert(curEmu <= schedTime);
164 setSyncPoint(schedTime);
165 }
166 toBeScheduledEvents.clear();
167
168#if PLATFORM_ANDROID
169 append(toBeScheduledEvents, std::move(toBeRescheduledEvents));
170#endif
171}
172
173void EventDelay::executeUntil(EmuTime::param time)
174{
175 try {
176 auto event = std::move(scheduledEvents.front());
177 scheduledEvents.pop_front();
178 msxEventDistributor.distributeEvent(event, time);
179 } catch (MSXException&) {
180 // ignore
181 }
182}
183
185{
186 EmuTime time = getCurrentTime();
187
188 for (auto& e : scheduledEvents) {
189 msxEventDistributor.distributeEvent(e, time);
190 }
191 scheduledEvents.clear();
192
193 for (auto& e : toBeScheduledEvents) {
194 msxEventDistributor.distributeEvent(e, time);
195 }
196 toBeScheduledEvents.clear();
197
199}
200
201} // namespace openmsx
Definition: one_of.hh:7
void sync(EmuTime::param curEmu)
Definition: EventDelay.cc:86
EventDelay(Scheduler &scheduler, CommandController &commandController, EventDistributor &eventDistributor, MSXEventDistributor &msxEventDistributor, ReverseManager &reverseManager)
Definition: EventDelay.cc:15
void unregisterEventListener(EventType type, EventListener &listener)
Unregisters a previously registered event listener.
void registerEventListener(EventType type, EventListener &listener, Priority priority=OTHER)
Registers a given object to receive certain events.
double getDouble() const noexcept
Definition: FloatSetting.hh:20
void distributeEvent(const Event &event, EmuTime::param time)
Deliver the event to all registered listeners.
void registerEventDelay(EventDelay &eventDelay_)
Every class that wants to get scheduled at some point must inherit from this class.
Definition: Schedulable.hh:34
void setSyncPoint(EmuTime::param timestamp)
Definition: Schedulable.cc:23
EmuTime::param getCurrentTime() const
Convenience method: This is the same as getScheduler().getCurrentTime().
Definition: Schedulable.cc:49
constexpr double e
Definition: Math.hh:18
uint64_t getTime()
Get current (real) time in us.
Definition: Timer.cc:7
This file implemented 3 utility functions:
Definition: Autofire.cc:9
EventType getType(const Event &event)
Definition: Event.hh:644
auto find(InputRange &&range, const T &value)
Definition: ranges.hh:144
void move_pop_back(VECTOR &v, typename VECTOR::iterator it)
Erase the pointed to element from the given vector.
Definition: stl.hh:125
constexpr auto end(const zstring_view &x)