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