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 "narrow.hh"
9#include "one_of.hh"
10#include "ranges.hh"
11#include "stl.hh"
12#include <cassert>
13
14namespace openmsx {
15
17 CommandController& commandController,
18 EventDistributor& eventDistributor_,
19 MSXEventDistributor& msxEventDistributor_,
20 ReverseManager& reverseManager)
21 : Schedulable(scheduler_)
22 , eventDistributor(eventDistributor_)
23 , msxEventDistributor(msxEventDistributor_)
24 , prevEmu(EmuTime::zero())
25 , prevReal(Timer::getTime())
26 , delaySetting(
27 commandController, "inputdelay",
28 "delay input to avoid key-skips", 0.0, 0.0, 10.0)
29{
30 eventDistributor.registerEventListener(
32 eventDistributor.registerEventListener(
34
35 eventDistributor.registerEventListener(
37 eventDistributor.registerEventListener(
39 eventDistributor.registerEventListener(
41
42 eventDistributor.registerEventListener(
44 eventDistributor.registerEventListener(
46 eventDistributor.registerEventListener(
48 eventDistributor.registerEventListener(
50
51 reverseManager.registerEventDelay(*this);
52}
53
55{
56 eventDistributor.unregisterEventListener(
57 EventType::KEY_DOWN, *this);
58 eventDistributor.unregisterEventListener(
59 EventType::KEY_UP, *this);
60
61 eventDistributor.unregisterEventListener(
63 eventDistributor.unregisterEventListener(
65 eventDistributor.unregisterEventListener(
67
68 eventDistributor.unregisterEventListener(
70 eventDistributor.unregisterEventListener(
71 EventType::JOY_HAT, *this);
72 eventDistributor.unregisterEventListener(
74 eventDistributor.unregisterEventListener(
76}
77
78int EventDelay::signalEvent(const Event& event)
79{
80 toBeScheduledEvents.push_back(event);
81 if (delaySetting.getDouble() == 0.0) {
83 }
84 return 0;
85}
86
87void EventDelay::sync(EmuTime::param curEmu)
88{
89 auto curRealTime = Timer::getTime();
90 auto realDuration = curRealTime - prevReal;
91 prevReal = curRealTime;
92 auto emuDuration = curEmu - prevEmu;
93 prevEmu = curEmu;
94
95 double factor = emuDuration.toDouble() / narrow_cast<double>(realDuration);
96 EmuDuration extraDelay(delaySetting.getDouble());
97
98#if PLATFORM_ANDROID
99 // The virtual keyboard on Android sends a key press and the
100 // corresponding key release event directly after each other, without a
101 // delay. It sends both events either when the user has finished a
102 // short tap or alternatively after the user has hold the button
103 // pressed for a few seconds and then has selected the appropriate
104 // character from the multi-character-popup that the virtual keyboard
105 // displays when the user holds a button pressed for a short while.
106 // Either way, the key release event comes way too short after the
107 // press event for the MSX to process it. The two events follow each
108 // other within a few milliseconds at most. Therefore, on Android,
109 // special logic must be foreseen to delay the release event for a
110 // short while. This special logic correlates each key release event
111 // with the corresponding press event for the same key. If they are
112 // less then 2/50 second apart, the release event gets delayed until
113 // the next sync call. The 2/50 second has been chosen because it can
114 // take up to 2 vertical interrupts (2 screen refreshes) for the MSX to
115 // see the key press in the keyboard matrix, thus, 2/50 seconds is the
116 // minimum delay required for an MSX running in PAL mode.
117 std::vector<Event> toBeRescheduledEvents;
118#endif
119
120 EmuTime time = curEmu + extraDelay;
121 for (auto& e : toBeScheduledEvents) {
122#if PLATFORM_ANDROID
124 const auto& keyEvent = get<KeyEvent>(e);
125 int maskedKeyCode = int(keyEvent.getKeyCode()) & int(Keys::K_MASK);
126 auto it = ranges::find(nonMatchedKeyPresses, maskedKeyCode,
127 [](const auto& p) { return p.first; });
128 if (getType(e) == EventType::KEY_DOWN) {
129 if (it == end(nonMatchedKeyPresses)) {
130 nonMatchedKeyPresses.emplace_back(maskedKeyCode, e);
131 } else {
132 it->second = e;
133 }
134 } else {
135 if (it != end(nonMatchedKeyPresses)) {
136 const auto& timedPressEvent = get<TimedEvent>(it->second);
137 const auto& timedReleaseEvent = get<TimedEvent>(e);
138 auto pressRealTime = timedPressEvent.getRealTime();
139 auto releaseRealTime = timedReleaseEvent.getRealTime();
140 auto deltaTime = releaseRealTime - pressRealTime;
141 if (deltaTime <= 2000000 / 50) {
142 // The key release came less then 2 MSX interrupts from the key press.
143 // Reschedule it for the next sync, with the realTime updated to now, so that it seems like the
144 // key was released now and not when android released it.
145 // Otherwise, the offset calculation for the emuTime further down below will go wrong on the next sync
146 Event newKeyupEvent = Event::create<KeyUpEvent>(keyEvent.getKeyCode());
147 toBeRescheduledEvents.push_back(newKeyupEvent);
148 continue; // continue with next to be scheduled event
149 }
150 move_pop_back(nonMatchedKeyPresses, it);
151 }
152 }
153 }
154#endif
155 scheduledEvents.push_back(e);
156 const auto& timedEvent = get<TimedEvent>(e);
157 auto eventRealTime = timedEvent.getRealTime();
158 assert(eventRealTime <= curRealTime);
159 auto offset = curRealTime - eventRealTime;
160 EmuDuration emuOffset(factor * narrow_cast<double>(offset));
161 auto schedTime = (emuOffset < extraDelay)
162 ? time - emuOffset
163 : curEmu;
164 assert(curEmu <= schedTime);
165 setSyncPoint(schedTime);
166 }
167 toBeScheduledEvents.clear();
168
169#if PLATFORM_ANDROID
170 append(toBeScheduledEvents, std::move(toBeRescheduledEvents));
171#endif
172}
173
174void EventDelay::executeUntil(EmuTime::param time)
175{
176 try {
177 auto event = std::move(scheduledEvents.front());
178 scheduledEvents.pop_front();
179 msxEventDistributor.distributeEvent(event, time);
180 } catch (MSXException&) {
181 // ignore
182 }
183}
184
186{
187 EmuTime time = getCurrentTime();
188
189 for (auto& e : scheduledEvents) {
190 msxEventDistributor.distributeEvent(e, time);
191 }
192 scheduledEvents.clear();
193
194 for (auto& e : toBeScheduledEvents) {
195 msxEventDistributor.distributeEvent(e, time);
196 }
197 toBeScheduledEvents.clear();
198
200}
201
202} // namespace openmsx
Definition: one_of.hh:7
void sync(EmuTime::param curEmu)
Definition: EventDelay.cc:87
EventDelay(Scheduler &scheduler, CommandController &commandController, EventDistributor &eventDistributor, MSXEventDistributor &msxEventDistributor, ReverseManager &reverseManager)
Definition: EventDelay.cc:16
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:20
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:160
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)