openMSX
JoyMega.cc
Go to the documentation of this file.
1#include "JoyMega.hh"
5#include "Event.hh"
7#include "StateChange.hh"
8#include "serialize.hh"
9#include "serialize_meta.hh"
10#include "unreachable.hh"
11#include "xrange.hh"
12#include "build-info.hh"
13#include <memory>
14
15namespace openmsx {
16
17#if PLATFORM_ANDROID
18static constexpr int THRESHOLD = 32768 / 4;
19#else
20static constexpr int THRESHOLD = 32768 / 10;
21#endif
22
24 StateChangeDistributor& stateChangeDistributor,
25 PluggingController& controller)
26{
27#ifdef SDL_JOYSTICK_DISABLED
28 (void)eventDistributor;
29 (void)stateChangeDistributor;
30 (void)controller;
31#else
32 for (auto i : xrange(SDL_NumJoysticks())) {
33 if (SDL_Joystick* joystick = SDL_JoystickOpen(i)) {
34 // Avoid devices that have axes but no buttons, like accelerometers.
35 // SDL 1.2.14 in Linux has an issue where it rejects a device from
36 // /dev/input/event* if it has no buttons but does not reject a
37 // device from /dev/input/js* if it has no buttons, while
38 // accelerometers do end up being symlinked as a joystick in
39 // practice.
40 if (InputEventGenerator::joystickNumButtons(joystick) != 0) {
41 controller.registerPluggable(
42 std::make_unique<JoyMega>(
43 eventDistributor,
44 stateChangeDistributor,
45 joystick));
46 }
47 }
48 }
49#endif
50}
51
52class JoyMegaState final : public StateChange
53{
54public:
55 JoyMegaState() = default; // for serialize
56 JoyMegaState(EmuTime::param time_, int joyNum_,
57 unsigned press_, unsigned release_)
58 : StateChange(time_)
59 , joyNum(joyNum_), press(press_), release(release_)
60 {
61 assert((press != 0) || (release != 0));
62 assert((press & release) == 0);
63 }
64 [[nodiscard]] int getJoystick() const { return joyNum; }
65 [[nodiscard]] unsigned getPress() const { return press; }
66 [[nodiscard]] unsigned getRelease() const { return release; }
67
68 template<typename Archive> void serialize(Archive& ar, unsigned /*version*/)
69 {
70 ar.template serializeBase<StateChange>(*this);
71 ar.serialize("joyNum", joyNum,
72 "press", press,
73 "release", release);
74 }
75private:
76 int joyNum;
77 unsigned press, release;
78};
80
81#ifndef SDL_JOYSTICK_DISABLED
82// Note: It's OK to open/close the same SDL_Joystick multiple times (we open it
83// once per MSX machine). The SDL documentation doesn't state this, but I
84// checked the implementation and a SDL_Joystick uses a 'reference count' on
85// the open/close calls.
87 StateChangeDistributor& stateChangeDistributor_,
88 SDL_Joystick* joystick_)
89 : eventDistributor(eventDistributor_)
90 , stateChangeDistributor(stateChangeDistributor_)
91 , joystick(joystick_)
92 , joyNum(SDL_JoystickInstanceID(joystick_))
93 , name("joymegaX") // 'X' is filled in below
94 , desc(SDL_JoystickName(joystick_))
95 , lastTime(EmuTime::zero())
96{
97 const_cast<std::string&>(name)[7] = char('1' + joyNum);
98}
99
101{
102 if (isPluggedIn()) {
103 JoyMega::unplugHelper(EmuTime::dummy());
104 }
105 SDL_JoystickClose(joystick);
106}
107
108// Pluggable
109std::string_view JoyMega::getName() const
110{
111 return name;
112}
113
114std::string_view JoyMega::getDescription() const
115{
116 return desc;
117}
118
119void JoyMega::plugHelper(Connector& /*connector*/, EmuTime::param /*time*/)
120{
121 plugHelper2();
122 status = calcInitialState();
123 cycle = 0;
124 // when mode button is pressed when joystick is plugged in, then
125 // act as a 3-button joypad (otherwise 6-button)
126 cycleMask = (status & 0x800) ? 7 : 1;
127}
128
129void JoyMega::plugHelper2()
130{
131 eventDistributor.registerEventListener(*this);
132 stateChangeDistributor.registerListener(*this);
133}
134
135void JoyMega::unplugHelper(EmuTime::param /*time*/)
136{
137 stateChangeDistributor.unregisterListener(*this);
138 eventDistributor.unregisterEventListener(*this);
139}
140
141
142// JoystickDevice
143uint8_t JoyMega::read(EmuTime::param time)
144{
145 // See http://segaretro.org/Control_Pad_(Mega_Drive)
146 // and http://frs.badcoffee.info/hardware/joymega-en.html
147 // for a detailed description of the MegaDrive joystick.
148 checkTime(time);
149 switch (cycle) {
150 case 0: case 2: case 4:
151 // up/down/left/right/b/c
152 return (status & 0x00f) | ((status & 0x060) >> 1);
153 case 1: case 3:
154 // up/down/0/0/a/start
155 return (status & 0x013) | ((status & 0x080) >> 2);
156 case 5:
157 // 0/0/0/0/a/start
158 return (status & 0x010) | ((status & 0x080) >> 2);
159 case 6:
160 // z/y/x/mode/b/c
161 return ((status & 0x400) >> 10) | // z
162 ((status & 0xA00) >> 8) | // start+y
163 ((status & 0x100) >> 6) | // x
164 ((status & 0x060) >> 1); // c+b
165 case 7:
166 // 1/1/1/1/a/start
167 return (status & 0x010) | ((status & 0x080) >> 2) | 0x0f;
168 default:
169 UNREACHABLE; return 0;
170 }
171}
172
173void JoyMega::write(uint8_t value, EmuTime::param time)
174{
175 checkTime(time);
176 lastTime = time;
177 if (((value >> 2) & 1) != (cycle & 1)) {
178 cycle = (cycle + 1) & cycleMask;
179 }
180 assert(((value >> 2) & 1) == (cycle & 1));
181}
182
183void JoyMega::checkTime(EmuTime::param time)
184{
185 if ((time - lastTime) > EmuDuration::usec(1500)) {
186 // longer than 1.5ms since last write -> reset cycle
187 cycle = 0;
188 }
189}
190
191static constexpr unsigned encodeButton(unsigned button, uint8_t cycleMask)
192{
193 unsigned n = (cycleMask == 7) ? 7 : 3; // 6- or 3-button mode
194 return 1 << (4 + (button & n));
195}
196
197unsigned JoyMega::calcInitialState()
198{
199 unsigned result = 0xfff;
200 int xAxis = SDL_JoystickGetAxis(joystick, 0);
201 if (xAxis < -THRESHOLD) {
202 result &= ~JOY_LEFT;
203 } else if (xAxis > THRESHOLD) {
204 result &= ~JOY_RIGHT;
205 }
206
207 int yAxis = SDL_JoystickGetAxis(joystick, 1);
208 if (yAxis < -THRESHOLD) {
209 result &= ~JOY_UP;
210 } else if (yAxis > THRESHOLD) {
211 result &= ~JOY_DOWN;
212 }
213
214 for (auto button : xrange(InputEventGenerator::joystickNumButtons(joystick))) {
215 if (InputEventGenerator::joystickGetButton(joystick, button)) {
216 result &= ~encodeButton(button, 7);
217 }
218 }
219 return result;
220}
221
222// MSXEventListener
223void JoyMega::signalMSXEvent(const Event& event, EmuTime::param time) noexcept
224{
225 const auto* joyEvent = get_if<JoystickEvent>(event);
226 if (!joyEvent) return;
227
228 // TODO: It would be more efficient to make a dispatcher instead of
229 // sending the event to all joysticks.
230 if (joyEvent->getJoystick() != joyNum) return;
231
233 [&](const JoystickAxisMotionEvent& e) {
234 int value = e.getValue();
235 switch (e.getAxis() & 1) {
236 case JoystickAxisMotionEvent::X_AXIS: // Horizontal
237 if (value < -THRESHOLD) {
238 // left, not right
239 createEvent(time, JOY_LEFT, JOY_RIGHT);
240 } else if (value > THRESHOLD) {
241 // not left, right
242 createEvent(time, JOY_RIGHT, JOY_LEFT);
243 } else {
244 // not left, not right
245 createEvent(time, 0, JOY_LEFT | JOY_RIGHT);
246 }
247 break;
248 case JoystickAxisMotionEvent::Y_AXIS: // Vertical
249 if (value < -THRESHOLD) {
250 // up, not down
251 createEvent(time, JOY_UP, JOY_DOWN);
252 } else if (value > THRESHOLD) {
253 // not up, down
254 createEvent(time, JOY_DOWN, JOY_UP);
255 } else {
256 // not up, not down
257 createEvent(time, 0, JOY_UP | JOY_DOWN);
258 }
259 break;
260 default:
261 // ignore other axis
262 break;
263 }
264 },
265 [&](const JoystickButtonDownEvent& e) {
266 createEvent(time, encodeButton(e.getButton(), cycleMask), 0);
267 },
268 [&](const JoystickButtonUpEvent& e) {
269 createEvent(time, 0, encodeButton(e.getButton(), cycleMask));
270 },
271 [&](const EventBase&) { UNREACHABLE; }
272 }, event);
273}
274
275void JoyMega::createEvent(EmuTime::param time, unsigned press, unsigned release)
276{
277 unsigned newStatus = (status & ~press) | release;
278 createEvent(time, newStatus);
279}
280
281void JoyMega::createEvent(EmuTime::param time, unsigned newStatus)
282{
283 unsigned diff = status ^ newStatus;
284 if (!diff) {
285 // event won't actually change the status, so ignore it
286 return;
287 }
288 // make sure we create an event with minimal changes
289 unsigned press = status & diff;
290 unsigned release = newStatus & diff;
291 stateChangeDistributor.distributeNew<JoyMegaState>(
292 time, joyNum, press, release);
293}
294
295// StateChangeListener
296void JoyMega::signalStateChange(const StateChange& event)
297{
298 const auto* js = dynamic_cast<const JoyMegaState*>(&event);
299 if (!js) return;
300
301 // TODO: It would be more efficient to make a dispatcher instead of
302 // sending the event to all joysticks.
303 // TODO an alternative is to log events based on the connector instead
304 // of the joystick. That would make it possible to replay on a
305 // different host without an actual SDL joystick connected.
306 if (js->getJoystick() != joyNum) return;
307
308 status = (status & ~js->getPress()) | js->getRelease();
309}
310
311void JoyMega::stopReplay(EmuTime::param time) noexcept
312{
313 createEvent(time, calcInitialState());
314}
315
316template<typename Archive>
317void JoyMega::serialize(Archive& ar, unsigned /*version*/)
318{
319 ar.serialize("lastTime", lastTime,
320 "status", status,
321 "cycle", cycle,
322 "cycleMask", cycleMask);
323 if constexpr (Archive::IS_LOADER) {
324 if (isPluggedIn()) {
325 plugHelper2();
326 }
327 }
328}
331
332#endif // SDL_JOYSTICK_DISABLED
333
334} // namespace openmsx
Represents something you can plug devices into.
Definition: Connector.hh:21
static constexpr EmuDuration usec(unsigned x)
Definition: EmuDuration.hh:45
static int joystickNumButtons(SDL_Joystick *joystick)
Normally the following two functions simply delegate to SDL_JoystickNumButtons() and SDL_JoystickGetB...
static bool joystickGetButton(SDL_Joystick *joystick, int button)
unsigned getPress() const
Definition: JoyMega.cc:65
int getJoystick() const
Definition: JoyMega.cc:64
unsigned getRelease() const
Definition: JoyMega.cc:66
JoyMegaState(EmuTime::param time_, int joyNum_, unsigned press_, unsigned release_)
Definition: JoyMega.cc:56
void serialize(Archive &ar, unsigned)
Definition: JoyMega.cc:68
void unplugHelper(EmuTime::param time) override
Definition: JoyMega.cc:135
JoyMega(MSXEventDistributor &eventDistributor, StateChangeDistributor &stateChangeDistributor, SDL_Joystick *joystick)
Definition: JoyMega.cc:86
uint8_t read(EmuTime::param time) override
Read from the joystick device.
Definition: JoyMega.cc:143
std::string_view getName() const override
Name used to identify this pluggable.
Definition: JoyMega.cc:109
static void registerAll(MSXEventDistributor &eventDistributor, StateChangeDistributor &stateChangeDistributor, PluggingController &controller)
Definition: JoyMega.cc:23
~JoyMega() override
Definition: JoyMega.cc:100
void plugHelper(Connector &connector, EmuTime::param time) override
Definition: JoyMega.cc:119
std::string_view getDescription() const override
Description for this pluggable.
Definition: JoyMega.cc:114
void write(uint8_t value, EmuTime::param time) override
Write a value to the joystick device.
Definition: JoyMega.cc:173
void serialize(Archive &ar, unsigned version)
Definition: JoyMega.cc:317
static constexpr unsigned X_AXIS
Definition: Event.hh:242
static constexpr unsigned Y_AXIS
Definition: Event.hh:243
void registerEventListener(MSXEventListener &listener)
Registers a given object to receive certain events.
void unregisterEventListener(MSXEventListener &listener)
Unregisters a previously registered event listener.
bool isPluggedIn() const
Returns true if this pluggable is currently plugged into a connector.
Definition: Pluggable.hh:49
Central administration of Connectors and Pluggables.
void registerPluggable(std::unique_ptr< Pluggable > pluggable)
Add a Pluggable to the registry.
void registerListener(StateChangeListener &listener)
(Un)registers the given object to receive state change events.
void distributeNew(EmuTime::param time, Args &&...args)
Deliver the event to all registered listeners MSX input devices should call the distributeNew() versi...
void unregisterListener(StateChangeListener &listener)
Base class for all external MSX state changing events.
Definition: StateChange.hh:20
constexpr double e
Definition: Math.hh:21
This file implemented 3 utility functions:
Definition: Autofire.cc:9
REGISTER_POLYMORPHIC_INITIALIZER(Pluggable, CassettePlayer, "CassettePlayer")
auto visit(Visitor &&visitor, const Event &event)
Definition: Event.hh:655
REGISTER_POLYMORPHIC_CLASS(StateChange, AutofireStateChange, "AutofireStateChange")
#define INSTANTIATE_SERIALIZE_METHODS(CLASS)
Definition: serialize.hh:1021
#define UNREACHABLE
Definition: unreachable.hh:38
constexpr auto xrange(T e)
Definition: xrange.hh:132