openMSX
EventDelay.cc
Go to the documentation of this file.
1#include "EventDelay.hh"
2
3#include "Event.hh"
4#include "EventDistributor.hh"
6#include "MSXException.hh"
7#include "ReverseManager.hh"
8#include "Timer.hh"
9
10#include "narrow.hh"
11#include "one_of.hh"
12#include "ranges.hh"
13#include "stl.hh"
14
15#include <cassert>
16
17#include <SDL.h>
18
19namespace openmsx {
20
22 CommandController& commandController,
23 EventDistributor& eventDistributor_,
24 MSXEventDistributor& msxEventDistributor_,
25 ReverseManager& reverseManager)
26 : Schedulable(scheduler_)
27 , eventDistributor(eventDistributor_)
28 , msxEventDistributor(msxEventDistributor_)
29 , prevReal(Timer::getTime())
30 , delaySetting(
31 commandController, "inputdelay",
32 "delay input to avoid key-skips", 0.0, 0.0, 10.0)
33{
34 using enum EventType;
35 for (auto type : {KEY_DOWN, KEY_UP,
38 eventDistributor.registerEventListener(type, *this, EventDistributor::Priority::MSX);
39 }
40
41 reverseManager.registerEventDelay(*this);
42}
43
45{
46 using enum EventType;
49 KEY_UP, KEY_DOWN}) {
50 eventDistributor.unregisterEventListener(type, *this);
51 }
52}
53
54bool EventDelay::signalEvent(const Event& event)
55{
56 toBeScheduledEvents.push_back(event);
57 if (delaySetting.getDouble() == 0.0) {
59 }
60 return false;
61}
62
63void EventDelay::sync(EmuTime::param curEmu)
64{
65 auto curRealTime = Timer::getTime();
66 auto realDuration = curRealTime - prevReal;
67 prevReal = curRealTime;
68 auto emuDuration = curEmu - prevEmu;
69 prevEmu = curEmu;
70
71 double factor = emuDuration.toDouble() / narrow_cast<double>(realDuration);
72 EmuDuration extraDelay(delaySetting.getDouble());
73
74#if PLATFORM_ANDROID
75 // The virtual keyboard on Android sends a key press and the
76 // corresponding key release event directly after each other, without a
77 // delay. It sends both events either when the user has finished a
78 // short tap or alternatively after the user has hold the button
79 // pressed for a few seconds and then has selected the appropriate
80 // character from the multi-character-popup that the virtual keyboard
81 // displays when the user holds a button pressed for a short while.
82 // Either way, the key release event comes way too short after the
83 // press event for the MSX to process it. The two events follow each
84 // other within a few milliseconds at most. Therefore, on Android,
85 // special logic must be foreseen to delay the release event for a
86 // short while. This special logic correlates each key release event
87 // with the corresponding press event for the same key. If they are
88 // less then 2/50 second apart, the release event gets delayed until
89 // the next sync call. The 2/50 second has been chosen because it can
90 // take up to 2 vertical interrupts (2 screen refreshes) for the MSX to
91 // see the key press in the keyboard matrix, thus, 2/50 seconds is the
92 // minimum delay required for an MSX running in PAL mode.
93 std::vector<Event> toBeRescheduledEvents;
94#endif
95
96 EmuTime time = curEmu + extraDelay;
97 for (auto& e : toBeScheduledEvents) {
98#if PLATFORM_ANDROID
100 const auto& keyEvent = get_event<KeyEvent>(e);
101 int maskedKeyCode = int(keyEvent.getKeyCode()) & int(Keys::K_MASK);
102 auto it = ranges::find(nonMatchedKeyPresses, maskedKeyCode,
103 [](const auto& p) { return p.first; });
104 if (getType(e) == EventType::KEY_DOWN) {
105 if (it == end(nonMatchedKeyPresses)) {
106 nonMatchedKeyPresses.emplace_back(maskedKeyCode, e);
107 } else {
108 it->second = e;
109 }
110 } else {
111 if (it != end(nonMatchedKeyPresses)) {
112 const auto& timedPressEvent = get_event<TimedEvent>(it->second);
113 const auto& timedReleaseEvent = get_event<TimedEvent>(e);
114 auto pressRealTime = timedPressEvent.getRealTime();
115 auto releaseRealTime = timedReleaseEvent.getRealTime();
116 auto deltaTime = releaseRealTime - pressRealTime;
117 if (deltaTime <= 2000000 / 50) {
118 // The key release came less then 2 MSX interrupts from the key press.
119 // Reschedule it for the next sync, with the realTime updated to now, so that it seems like the
120 // key was released now and not when android released it.
121 // Otherwise, the offset calculation for the emuTime further down below will go wrong on the next sync
122 Event newKeyupEvent = KeyUpEvent(keyEvent.getKeyCode());
123 toBeRescheduledEvents.push_back(newKeyupEvent);
124 continue; // continue with next to be scheduled event
125 }
126 move_pop_back(nonMatchedKeyPresses, it);
127 }
128 }
129 }
130#endif
131 scheduledEvents.push_back(e);
132 const auto& sdlEvent = get_event<SdlEvent>(e);
133 uint32_t eventSdlTime = sdlEvent.getCommonSdlEvent().timestamp;
134 uint32_t sdlNow = SDL_GetTicks();
135 auto sdlOffset = int32_t(sdlNow - eventSdlTime);
136 assert(sdlOffset >= 0);
137 auto offset = 1000 * int64_t(sdlOffset); // ms -> us
138 EmuDuration emuOffset(factor * narrow_cast<double>(offset));
139 auto schedTime = (emuOffset < extraDelay)
140 ? time - emuOffset
141 : curEmu;
142 assert(curEmu <= schedTime);
143 setSyncPoint(schedTime);
144 }
145 toBeScheduledEvents.clear();
146
147#if PLATFORM_ANDROID
148 append(toBeScheduledEvents, std::move(toBeRescheduledEvents));
149#endif
150}
151
152void EventDelay::executeUntil(EmuTime::param time)
153{
154 try {
155 auto event = std::move(scheduledEvents.front());
156 scheduledEvents.pop_front();
157 msxEventDistributor.distributeEvent(event, time);
158 } catch (MSXException&) {
159 // ignore
160 }
161}
162
164{
165 EmuTime time = getCurrentTime();
166
167 for (const auto& e : scheduledEvents) {
168 msxEventDistributor.distributeEvent(e, time);
169 }
170 scheduledEvents.clear();
171
172 for (const auto& e : toBeScheduledEvents) {
173 msxEventDistributor.distributeEvent(e, time);
174 }
175 toBeScheduledEvents.clear();
176
178}
179
180} // namespace openmsx
void sync(EmuTime::param curEmu)
Definition EventDelay.cc:63
EventDelay(Scheduler &scheduler, CommandController &commandController, EventDistributor &eventDistributor, MSXEventDistributor &msxEventDistributor, ReverseManager &reverseManager)
Definition EventDelay.cc:21
void unregisterEventListener(EventType type, EventListener &listener)
Unregisters a previously registered event listener.
void registerEventListener(EventType type, EventListener &listener, Priority priority=Priority::OTHER)
Registers a given object to receive certain events.
double getDouble() const noexcept
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.
void setSyncPoint(EmuTime::param timestamp)
EmuTime::param getCurrentTime() const
Convenience method: This is the same as getScheduler().getCurrentTime().
uint64_t getTime()
Get current (real) time in us.
Definition Timer.cc:7
This file implemented 3 utility functions:
Definition Autofire.cc:11
EventType
Definition Event.hh:454
EventType getType(const Event &event)
Definition Event.hh:517
std::variant< KeyUpEvent, KeyDownEvent, MouseMotionEvent, MouseButtonUpEvent, MouseButtonDownEvent, MouseWheelEvent, JoystickAxisMotionEvent, JoystickHatEvent, JoystickButtonUpEvent, JoystickButtonDownEvent, OsdControlReleaseEvent, OsdControlPressEvent, WindowEvent, TextEvent, FileDropEvent, QuitEvent, FinishFrameEvent, CliCommandEvent, GroupEvent, BootEvent, FrameDrawnEvent, BreakEvent, SwitchRendererEvent, TakeReverseSnapshotEvent, AfterTimedEvent, MachineLoadedEvent, MachineActivatedEvent, MachineDeactivatedEvent, MidiInReaderEvent, MidiInWindowsEvent, MidiInCoreMidiEvent, MidiInCoreMidiVirtualEvent, MidiInALSAEvent, Rs232TesterEvent, Rs232NetEvent, ImGuiDelayedActionEvent, ImGuiActiveEvent > Event
Definition Event.hh:445
auto find(InputRange &&range, const T &value)
Definition ranges.hh:162
void move_pop_back(VECTOR &v, typename VECTOR::iterator it)
Erase the pointed to element from the given vector.
Definition stl.hh:134
constexpr auto end(const zstring_view &x)