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