OXYGENHUB
◆ main openmsx / src / input / EventDelay.cc
6.1 KiB · C++ · last changed chore(): initial commit 1 month ago source blame history raw
e8555c89 Andy McCall 1
#include "EventDelay.hh"
2
3
#include "Event.hh"
4
#include "EventDistributor.hh"
5
#include "MSXEventDistributor.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 "stl.hh"
13
14
#include <SDL.h>
15
16
#include <algorithm>
17
#include <cassert>
18
19
namespace openmsx {
20
21
EventDelay::EventDelay(Scheduler& scheduler_,
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,
36
	                  MOUSE_MOTION, MOUSE_BUTTON_DOWN, MOUSE_BUTTON_UP,
37
	                  JOY_AXIS_MOTION, JOY_HAT, JOY_BUTTON_DOWN, JOY_BUTTON_UP}) {
38
		eventDistributor.registerEventListener(type, *this, EventDistributor::Priority::MSX);
39
	}
40
41
	reverseManager.registerEventDelay(*this);
42
}
43
44
EventDelay::~EventDelay()
45
{
46
	using enum EventType;
47
	for (auto type : {JOY_BUTTON_UP, JOY_BUTTON_DOWN, JOY_HAT, JOY_AXIS_MOTION,
48
	                  MOUSE_BUTTON_UP, MOUSE_BUTTON_DOWN, MOUSE_MOTION,
49
	                  KEY_UP, KEY_DOWN}) {
50
		eventDistributor.unregisterEventListener(type, *this);
51
	}
52
}
53
54
bool EventDelay::signalEvent(const Event& event)
55
{
56
	toBeScheduledEvents.push_back(event);
57
	if (delaySetting.getDouble() == 0.0) {
58
		sync(getCurrentTime());
59
	}
60
	return false;
61
}
62
63
void EventDelay::sync(EmuTime 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 = EmuDuration::sec(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
99
		if (getType(e) == one_of(EventType::KEY_DOWN, EventType::KEY_UP)) {
100
			const auto& keyEvent = get_event<KeyEvent>(e);
101
			int maskedKeyCode = int(keyEvent.getKeyCode()) & int(Keys::K_MASK);
102
			auto it = std::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 = EmuDuration::sec(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
152
void EventDelay::executeUntil(EmuTime 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
163
void EventDelay::flush()
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
177
	removeSyncPoints();
178
}
179
180
} // namespace openmsx