openMSX
EventDelay.cc
Go to the documentation of this file.
1 #include "EventDelay.hh"
2 #include "EventDistributor.hh"
3 #include "MSXEventDistributor.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 
13 namespace 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 
77 int 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 
86 void 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_if(nonMatchedKeyPresses,
126  EqualTupleValue<0>(maskedKeyCode));
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 
173 void EventDelay::executeUntil(EmuTime::param time)
174 {
175  try {
176  auto event = std::move(scheduledEvents.front());
177  scheduledEvents.pop_front();
178  msxEventDistributor.distributeEvent(std::move(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
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:656
auto find_if(InputRange &&range, UnaryPredicate pred)
Definition: ranges.hh:113
void move_pop_back(VECTOR &v, typename VECTOR::iterator it)
Erase the pointed to element from the given vector.
Definition: stl.hh:182
constexpr auto end(const zstring_view &x)
Definition: zstring_view.hh:84