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 "InputEvents.hh"
6 #include "Timer.hh"
7 #include "MSXException.hh"
8 #include "checked_cast.hh"
9 #include "stl.hh"
10 #include <cassert>
11 
12 using std::make_shared;
13 
14 namespace 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  OPENMSX_KEY_DOWN_EVENT, *this);
58  eventDistributor.unregisterEventListener(
59  OPENMSX_KEY_UP_EVENT, *this);
60 
61  eventDistributor.unregisterEventListener(
63  eventDistributor.unregisterEventListener(
65  eventDistributor.unregisterEventListener(
67 
68  eventDistributor.unregisterEventListener(
70  eventDistributor.unregisterEventListener(
71  OPENMSX_JOY_HAT_EVENT, *this);
72  eventDistributor.unregisterEventListener(
74  eventDistributor.unregisterEventListener(
76 }
77 
78 int EventDelay::signalEvent(const EventPtr& event)
79 {
80  toBeScheduledEvents.push_back(event);
81  if (delaySetting.getDouble() == 0.0) {
83  }
84  return 0;
85 }
86 
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() / 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<EventPtr> toBeRescheduledEvents;
118 #endif
119 
120  EmuTime time = curEmu + extraDelay;
121  for (auto& e : toBeScheduledEvents) {
122 #if PLATFORM_ANDROID
123  if (e->getType() == OPENMSX_KEY_DOWN_EVENT ||
124  e->getType() == OPENMSX_KEY_UP_EVENT) {
125  auto keyEvent = checked_cast<const KeyEvent*>(e.get());
126  int maskedKeyCode = int(keyEvent->getKeyCode()) & int(Keys::K_MASK);
127  auto it = find_if(begin(nonMatchedKeyPresses), end(nonMatchedKeyPresses),
128  EqualTupleValue<0>(maskedKeyCode));
129  if (e->getType() == OPENMSX_KEY_DOWN_EVENT) {
130  if (it == end(nonMatchedKeyPresses)) {
131  nonMatchedKeyPresses.emplace_back(maskedKeyCode, e);
132  } else {
133  it->second = e;
134  }
135  } else {
136  if (it != end(nonMatchedKeyPresses)) {
137  auto timedPressEvent = checked_cast<const TimedEvent*>(it->second.get());
138  auto timedReleaseEvent = checked_cast<const TimedEvent*>(e.get());
139  auto pressRealTime = timedPressEvent->getRealTime();
140  auto releaseRealTime = timedReleaseEvent->getRealTime();
141  auto deltaTime = releaseRealTime - pressRealTime;
142  if (deltaTime <= 2000000 / 50) {
143  // The key release came less then 2 MSX interrupts from the key press.
144  // Reschedule it for the next sync, with the realTime updated to now, so that it seems like the
145  // key was released now and not when android released it.
146  // Otherwise, the offset calculation for the emutime further down below will go wrong on the next sync
147  EventPtr newKeyupEvent = make_shared<KeyUpEvent>(keyEvent->getKeyCode(), keyEvent->getUnicode());
148  toBeRescheduledEvents.push_back(newKeyupEvent);
149  continue; // continue with next to be scheduled event
150  }
151  auto backIt = end(nonMatchedKeyPresses) - 1;
152  if (it != backIt) std::swap(*it, *backIt);
153  nonMatchedKeyPresses.pop_back();
154  }
155  }
156  }
157 #endif
158  scheduledEvents.push_back(e);
159  auto timedEvent = checked_cast<const TimedEvent*>(e.get());
160  auto eventRealTime = timedEvent->getRealTime();
161  assert(eventRealTime <= curRealTime);
162  auto offset = curRealTime - eventRealTime;
163  EmuDuration emuOffset(factor * offset);
164  auto schedTime = (emuOffset < extraDelay)
165  ? time - emuOffset
166  : curEmu;
167  assert(curEmu <= schedTime);
168  setSyncPoint(schedTime);
169  }
170  toBeScheduledEvents.clear();
171 
172 #if PLATFORM_ANDROID
173  toBeScheduledEvents.insert(end(toBeScheduledEvents),
174  make_move_iterator(begin(toBeRescheduledEvents)),
175  make_move_iterator(end (toBeRescheduledEvents)));
176 #endif
177 }
178 
179 void EventDelay::executeUntil(EmuTime::param time)
180 {
181  try {
182  auto event = std::move(scheduledEvents.front());
183  scheduledEvents.pop_front();
184  msxEventDistributor.distributeEvent(std::move(event), time);
185  } catch (MSXException&) {
186  // ignore
187  }
188 }
189 
191 {
192  EmuTime time = getCurrentTime();
193 
194  for (auto& e : scheduledEvents) {
195  msxEventDistributor.distributeEvent(e, time);
196  }
197  scheduledEvents.clear();
198 
199  for (auto& e : toBeScheduledEvents) {
200  msxEventDistributor.distributeEvent(e, time);
201  }
202  toBeScheduledEvents.clear();
203 
205 }
206 
207 } // namespace openmsx
void sync(EmuTime::param time)
Definition: EventDelay.cc:87
string_ref::const_iterator end(const string_ref &x)
Definition: string_ref.hh:150
void registerEventListener(EventType type, EventListener &listener, Priority priority=OTHER)
Registers a given object to receive certain events.
void registerEventDelay(EventDelay &eventDelay_)
void unregisterEventListener(EventType type, EventListener &listener)
Unregisters a previously registered event listener.
void distributeEvent(const EventPtr &event, EmuTime::param time)
Deliver the event to all registered listeners.
Every class that wants to get scheduled at some point must inherit from this class.
Definition: Schedulable.hh:34
uint64_t getRealTime() const
Query creation time.
Definition: InputEvents.hh:15
EventDelay(Scheduler &scheduler, CommandController &commandController, EventDistributor &eventDistributor, MSXEventDistributor &msxEventDistributor, ReverseManager &reverseManager)
Definition: EventDelay.cc:16
std::shared_ptr< const Event > EventPtr
EmuTime::param getCurrentTime() const
Convenience method: This is the same as getScheduler().getCurrentTime().
Definition: Schedulable.cc:49
Thanks to enen for testing this on a real cartridge:
Definition: Autofire.cc:5
double getDouble() const
Definition: FloatSetting.hh:20
uint64_t getTime()
Get current (real) time in us.
Definition: Timer.cc:18
string_ref::const_iterator begin(const string_ref &x)
Definition: string_ref.hh:149
void setSyncPoint(EmuTime::param timestamp)
Definition: Schedulable.cc:23