OXYGENHUB
◆ main openmsx / src / input / MSXJoystick.cc
5.8 KiB · C++ · last changed chore(): initial commit 1 month ago source blame history raw
1
#include "MSXJoystick.hh"
2
3
#include "CommandController.hh"
4
#include "Event.hh"
5
#include "IntegerSetting.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 <algorithm>
19
20
namespace openmsx {
21
22
TclObject MSXJoystick::getDefaultConfig(JoystickId joyId, const JoystickManager& joystickManager)
23
{
24
	auto buttons = joystickManager.getNumButtons(joyId);
25
	if (!buttons) return {};
26
27
	TclObject listA, listB;
28
	auto joy = joyId.str();
29
	for (auto b : xrange(*buttons)) {
30
		((b & 1) ? listB : listA).addListElement(tmpStrCat(joy, " button", b));
31
	}
32
	return TclObject(TclObject::MakeDictTag{},
33
		"UP",    makeTclList(tmpStrCat(joy, " -axis1"), tmpStrCat(joy, " hat0 up")),
34
		"DOWN",  makeTclList(tmpStrCat(joy, " +axis1"), tmpStrCat(joy, " hat0 down")),
35
		"LEFT",  makeTclList(tmpStrCat(joy, " -axis0"), tmpStrCat(joy, " hat0 left")),
36
		"RIGHT", makeTclList(tmpStrCat(joy, " +axis0"), tmpStrCat(joy, " hat0 right")),
37
		"A",     listA,
38
		"B",     listB);
39
}
40
41
MSXJoystick::MSXJoystick(CommandController& commandController_,
42
                         MSXEventDistributor& eventDistributor_,
43
                         StateChangeDistributor& stateChangeDistributor_,
44
                         JoystickManager& joystickManager_,
45
                         uint8_t id_)
46
	: commandController(commandController_)
47
	, eventDistributor(eventDistributor_)
48
	, stateChangeDistributor(stateChangeDistributor_)
49
	, joystickManager(joystickManager_)
50
	, configSetting(commandController, tmpStrCat("msxjoystick", id_, "_config"),
51
		"msxjoystick mapping configuration", getDefaultConfig(JoystickId(id_ - 1), joystickManager).getString())
52
	, description(strCat("MSX joystick ", id_, ". Mapping is fully configurable."))
53
	, id(id_)
54
{
55
	configSetting.setChecker([this](const TclObject& newValue) {
56
		this->checkJoystickConfig(newValue); });
57
	try {
58
		// fill in 'bindings'
59
		checkJoystickConfig(configSetting.getValue());
60
	} catch (MSXException& /*e*/) {
61
		configSetting.setValue(getDefaultConfig(JoystickId(id - 1), joystickManager));
62
	}
63
}
64
65
MSXJoystick::~MSXJoystick()
66
{
67
	if (isPluggedIn()) {
68
		MSXJoystick::unplugHelper(EmuTime::dummy());
69
	}
70
}
71
72
void MSXJoystick::checkJoystickConfig(const TclObject& newValue)
73
{
74
	std::array<std::vector<BooleanInput>, 6> newBindings;
75
76
	auto& interp = commandController.getInterpreter();
77
	auto n = newValue.getListLength(interp);
78
	if (n & 1) {
79
		throw CommandException("Need an even number of elements");
80
	}
81
	for (decltype(n) i = 0; i < n; i += 2) {
82
		static constexpr std::array<std::string_view, 6> keys = {
83
			// order is important!
84
			"UP", "DOWN", "LEFT", "RIGHT", "A", "B"
85
		};
86
		std::string_view key  = newValue.getListIndex(interp, i + 0).getString();
87
		auto it = std::ranges::find(keys, key);
88
		if (it == keys.end()) {
89
			throw CommandException(
90
				"Invalid key: must be one of ", join(keys, ", "));
91
		}
92
		auto idx = std::distance(keys.begin(), it);
93
94
		TclObject value = newValue.getListIndex(interp, i + 1);
95
		for (auto j : xrange(value.getListLength(interp))) {
96
			std::string_view val = value.getListIndex(interp, j).getString();
97
			auto bind = parseBooleanInput(val);
98
			if (!bind) {
99
				throw CommandException("Invalid binding: ", val);
100
			}
101
			newBindings[idx].push_back(*bind);
102
		}
103
	}
104
105
	// only change current bindings when parsing was fully successful
106
	copy_to_range(newBindings, bindings);
107
}
108
109
// Pluggable
110
zstring_view MSXJoystick::getName() const
111
{
112
	switch (id) {
113
		case 1: return "msxjoystick1";
114
		case 2: return "msxjoystick2";
115
		default: UNREACHABLE;
116
	}
117
}
118
119
zstring_view MSXJoystick::getDescription() const
120
{
121
	return description;
122
}
123
124
void MSXJoystick::plugHelper(Connector& /*connector*/, EmuTime /*time*/)
125
{
126
	eventDistributor.registerEventListener(*this);
127
	stateChangeDistributor.registerListener(*this);
128
}
129
130
void MSXJoystick::unplugHelper(EmuTime /*time*/)
131
{
132
	stateChangeDistributor.unregisterListener(*this);
133
	eventDistributor.unregisterEventListener(*this);
134
}
135
136
137
// MSXJoystickDevice
138
uint8_t MSXJoystick::read(EmuTime /*time*/)
139
{
140
	return pin8 ? 0x3F : status;
141
}
142
143
void MSXJoystick::write(uint8_t value, EmuTime /*time*/)
144
{
145
	pin8 = (value & 0x04) != 0;
146
}
147
148
149
// MSXEventListener
150
void MSXJoystick::signalMSXEvent(const Event& event,
151
                                 EmuTime time) noexcept
152
{
153
	uint8_t press = 0;
154
	uint8_t release = 0;
155
156
	auto getJoyDeadThreshold = [&](JoystickId joyId) {
157
		return joystickManager.getJoyDeadThreshold(joyId);
158
	};
159
	for (int i : xrange(6)) {
160
		for (const auto& binding : bindings[i]) {
161
			if (auto onOff = match(binding, event, getJoyDeadThreshold)) {
162
				(*onOff ? press : release) |= 1 << i;
163
			}
164
		}
165
	}
166
167
	if (((status & ~press) | release) != status) {
168
		stateChangeDistributor.distributeNew<MSXJoyState>(
169
			time, id, press, release);
170
	}
171
}
172
173
// StateChangeListener
174
void MSXJoystick::signalStateChange(const StateChange& event)
175
{
176
	const auto* mjs = std::get_if<MSXJoyState>(&event);
177
	if (!mjs) return;
178
	if (mjs->getId() != id) return;
179
180
	status = (status & ~mjs->getPress()) | mjs->getRelease();
181
}
182
183
void MSXJoystick::stopReplay(EmuTime time) noexcept
184
{
185
	uint8_t newStatus = JOY_UP | JOY_DOWN | JOY_LEFT | JOY_RIGHT |
186
	                    JOY_BUTTONA | JOY_BUTTONB;
187
	if (newStatus != status) {
188
		uint8_t release = newStatus & ~status;
189
		stateChangeDistributor.distributeNew<MSXJoyState>(
190
			time, id, uint8_t(0), release);
191
	}
192
}
193
194
195
template<typename Archive>
196
void MSXJoystick::serialize(Archive& ar, unsigned /*version*/)
197
{
198
	ar.serialize("status", status);
199
	if constexpr (Archive::IS_LOADER) {
200
		if (isPluggedIn()) {
201
			plugHelper(*getConnector(), EmuTime::dummy());
202
		}
203
	}
204
	// no need to serialize 'pin8'
205
}
206
INSTANTIATE_SERIALIZE_METHODS(MSXJoystick);
207
REGISTER_POLYMORPHIC_INITIALIZER(Pluggable, MSXJoystick, "MSXJoystick");
208
209
} // namespace openmsx