OXYGENHUB
◆ main openmsx / src / input / JoyHandle.cc
7.1 KiB · C++ · last changed chore(): initial commit 1 month ago source blame history raw
e8555c89 Andy McCall 1
#include "JoyHandle.hh"
2
3
#include "Clock.hh"
4
#include "CommandController.hh"
5
#include "Event.hh"
6
#include "JoystickManager.hh"
7
#include "MSXEventDistributor.hh"
8
#include "StateChange.hh"
9
#include "StateChangeDistributor.hh"
10
#include "serialize.hh"
11
#include "serialize_meta.hh"
12
13
#include "join.hh"
14
#include "ranges.hh"
15
#include "unreachable.hh"
16
#include "xrange.hh"
17
18
#include <cmath>
19
20
namespace openmsx {
21
22
using namespace std::literals;
23
24
TclObject JoyHandle::getDefaultConfig(JoystickId joyId, const JoystickManager& joystickManager)
25
{
26
	auto buttons = joystickManager.getNumButtons(joyId);
27
	if (!buttons) return {};
28
29
	TclObject listA, listB;
30
	auto joy = joyId.str();
31
	for (auto b : xrange(*buttons)) {
32
		((b & 1) ? listB : listA).addListElement(tmpStrCat(joy, " button", b));
33
	}
34
	return TclObject(TclObject::MakeDictTag{},
35
		"UP",     makeTclList(tmpStrCat(joy, " hat0 up")),
36
		"DOWN",   makeTclList(tmpStrCat(joy, " hat0 down")),
37
		"LEFT",   makeTclList(tmpStrCat(joy, " hat0 left")),
38
		"RIGHT",  makeTclList(tmpStrCat(joy, " hat0 right")),
39
		"A",      listA,
40
		"B",      listB,
41
		"WHEEL",  makeTclList(tmpStrCat(joy, " axis0")));
42
}
43
44
JoyHandle::JoyHandle(CommandController& commandController_,
45
                         MSXEventDistributor& eventDistributor_,
46
                         StateChangeDistributor& stateChangeDistributor_,
47
                         JoystickManager& joystickManager_,
48
                         uint8_t id_)
49
	: commandController(commandController_)
50
	, eventDistributor(eventDistributor_)
51
	, stateChangeDistributor(stateChangeDistributor_)
52
	, joystickManager(joystickManager_)
53
	, configSetting(commandController, tmpStrCat("joyhandle", id_, "_config"),
54
		"joyhandle mapping configuration", getDefaultConfig(JoystickId(id_ - 1), joystickManager).getString())
55
	, description(strCat("Panasonic FS-JH1 Joy Handle ", id_, ". Mapping is fully configurable."))
56
	, id(id_)
57
{
58
	configSetting.setChecker([this](const TclObject& newValue) {
59
		this->checkJoystickConfig(newValue); });
60
	try {
61
		// fill in 'bindings'
62
		checkJoystickConfig(configSetting.getValue());
63
	} catch (MSXException& /*e*/) {
64
		configSetting.setValue(getDefaultConfig(JoystickId(id - 1), joystickManager));
65
	}
66
}
67
68
JoyHandle::~JoyHandle()
69
{
70
	if (isPluggedIn()) {
71
		JoyHandle::unplugHelper(EmuTime::dummy());
72
	}
73
}
74
75
void JoyHandle::checkJoystickConfig(const TclObject& newValue)
76
{
77
	std::array<std::vector<BooleanInput>, 6> newBindings;
78
	std::vector<AnalogStatus> newWheelBindings;
79
80
	auto& interp = commandController.getInterpreter();
81
	unsigned n = newValue.getListLength(interp);
82
	if (n & 1) {
83
		throw CommandException("Need an even number of elements");
84
	}
85
	for (unsigned i = 0; i < n; i += 2) {
86
		static constexpr std::array keys = {
87
			// order is important!
88
			"UP"sv, "DOWN"sv, "LEFT"sv, "RIGHT"sv, "A"sv, "B"sv, "WHEEL"sv
89
		};
90
		std::string_view key  = newValue.getListIndex(interp, i + 0).getString();
91
		auto it = std::ranges::find(keys, key);
92
		if (it == keys.end()) {
93
			throw CommandException(
94
				"Invalid key: must be one of ", join(keys, ", "));
95
		}
96
		auto idx = std::distance(keys.begin(), it);
97
98
		TclObject value = newValue.getListIndex(interp, i + 1);
99
		for (auto j : xrange(value.getListLength(interp))) {
100
			std::string_view val = value.getListIndex(interp, j).getString();
101
			if (key == "WHEEL") {
102
				auto bind = parseAnalogInput(val);
103
				if (!bind) {
104
					throw CommandException("Invalid binding: ", val);
105
				}
106
				newWheelBindings.emplace_back(*bind, 0.0f);
107
			} else {
108
				auto bind = parseBooleanInput(val);
109
				if (!bind) {
110
					throw CommandException("Invalid binding: ", val);
111
				}
112
				newBindings[idx].push_back(*bind);
113
			}
114
		}
115
	}
116
117
	// only change current bindings when parsing was fully successful
118
	copy_to_range(newBindings, bindings);
119
	wheelBindings = std::move(newWheelBindings);
120
}
121
122
// Pluggable
123
zstring_view JoyHandle::getName() const
124
{
125
	switch (id) {
126
		case 1: return "joyhandle1";
127
		case 2: return "joyhandle2";
128
		default: UNREACHABLE;
129
	}
130
}
131
132
zstring_view JoyHandle::getDescription() const
133
{
134
	return description;
135
}
136
137
void JoyHandle::plugHelper(Connector& /*connector*/, EmuTime /*time*/)
138
{
139
	eventDistributor.registerEventListener(*this);
140
	stateChangeDistributor.registerListener(*this);
141
142
	analogValue = 0.0f;
143
	resetWheelCache();
144
}
145
146
void JoyHandle::unplugHelper(EmuTime /*time*/)
147
{
148
	stateChangeDistributor.unregisterListener(*this);
149
	eventDistributor.unregisterEventListener(*this);
150
}
151
152
void JoyHandle::resetWheelCache()
153
{
154
	for (auto& entry : wheelBindings) {
155
		entry.value = 0.0f;
156
	}
157
}
158
159
// MSXJoystickDevice
160
uint8_t JoyHandle::read(EmuTime time)
161
{
162
	Clock<2> clock(EmuTime::zero()); // ticks at 2Hz
163
	uint8_t cycle = clock.getTicksTill(time) & 1;
164
165
	const uint8_t wheelStatus =
166
	    ((analogValue < 0.0f) && ((analogValue < -0.5f) || (cycle == 1))) ? JOY_LEFT
167
	  : ((analogValue > 0.0f) && ((analogValue >  0.5f) || (cycle == 1))) ? JOY_RIGHT
168
	  : 0;
169
	return status & ~wheelStatus;
170
}
171
172
void JoyHandle::write(uint8_t /*value*/, EmuTime /*time*/)
173
{
174
}
175
176
[[nodiscard]] static float quantizeWheel(float a)
177
{
178
	if (a == 0.0f) return 0.0f;
179
	return std::copysign(std::abs(a) <= 0.5f ? 0.5f : 1.0f, a);
180
}
181
182
// MSXEventListener
183
void JoyHandle::signalMSXEvent(const Event& event,
184
                               EmuTime time) noexcept
185
{
186
	auto getJoyDeadThreshold = [&](JoystickId joyId) {
187
		return joystickManager.getJoyDeadThreshold(joyId);
188
	};
189
	auto getJoyValue = [&](JoystickId joyId, const JoystickAxisMotionEvent& e) {
190
		return joystickManager.getJoyValue(joyId, e);
191
	};
192
193
	uint8_t press = 0;
194
	uint8_t release = 0;
195
	for (int i : xrange(6)) {
196
		for (const auto& binding : bindings[i]) {
197
			if (auto onOff = match(binding, event, getJoyDeadThreshold)) {
198
				(*onOff ? press : release) |= 1 << i;
199
			}
200
		}
201
	}
202
203
	float newAnalog = 0;
204
	for (auto& [binding, value] : wheelBindings) {
205
		if (auto v = match(binding, event, getJoyValue)) {
206
			value = *v;
207
		}
208
		if (std::abs(value) > std::abs(newAnalog)) {
209
			newAnalog = value;
210
		}
211
	}
212
	newAnalog = quantizeWheel(newAnalog);
213
214
	if ((((status & ~press) | release) != status) || (newAnalog != analogValue)) {
215
		stateChangeDistributor.distributeNew<JoyHandleState>(
216
			time, id, press, release, newAnalog);
217
	}
218
}
219
220
// StateChangeListener
221
void JoyHandle::signalStateChange(const StateChange& event)
222
{
223
	const auto* jhs = std::get_if<JoyHandleState>(&event);
224
	if (!jhs) return;
225
	if (jhs->getId() != id) return;
226
227
	status = (status & ~jhs->getPress()) | jhs->getRelease();
228
	analogValue = jhs->getAnalog();
229
}
230
231
void JoyHandle::stopReplay(EmuTime time) noexcept
232
{
233
	uint8_t newStatus = JOY_UP | JOY_DOWN | JOY_LEFT | JOY_RIGHT |
234
	                    JOY_BUTTONA | JOY_BUTTONB;
235
	if (newStatus != status || (analogValue != 0.0f)) {
236
		resetWheelCache();
237
		uint8_t release = newStatus & ~status;
238
		stateChangeDistributor.distributeNew<JoyHandleState>(
239
			time, id, uint8_t(0), release, 0.0f);
240
	}
241
}
242
243
244
template<typename Archive>
245
void JoyHandle::serialize(Archive& ar, unsigned /*version*/)
246
{
247
	if constexpr (Archive::IS_LOADER) {
248
		if (isPluggedIn()) {
249
			plugHelper(*getConnector(), EmuTime::dummy());
250
		}
251
	}
252
	ar.serialize("status",      status,
253
	             "analogValue", analogValue);
254
}
255
INSTANTIATE_SERIALIZE_METHODS(JoyHandle);
256
REGISTER_POLYMORPHIC_INITIALIZER(Pluggable, JoyHandle, "JoyHandle");
257
258
} // namespace openmsx