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 "build-info.hh"
13 #include <memory>
14 
15 using std::string;
16 using std::shared_ptr;
17 
18 namespace openmsx {
19 
20 #if PLATFORM_ANDROID
21 static const int THRESHOLD = 32768 / 4;
22 #else
23 static const int THRESHOLD = 32768 / 10;
24 #endif
25 
27  StateChangeDistributor& stateChangeDistributor,
28  PluggingController& controller)
29 {
30 #ifdef SDL_JOYSTICK_DISABLED
31  (void)eventDistributor;
32  (void)stateChangeDistributor;
33  (void)controller;
34 #else
35  unsigned numJoysticks = SDL_NumJoysticks();
36  for (unsigned i = 0; i < numJoysticks; i++) {
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  unsigned getJoystick() const { return joyNum; }
69  unsigned getPress() const { return press; }
70  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 
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  int numButtons = InputEventGenerator::joystickNumButtons(joystick);
219  for (int button = 0; button < numButtons; ++button) {
220  if (InputEventGenerator::joystickGetButton(joystick, button)) {
221  result &= ~encodeButton(button, 7);
222  }
223  }
224  return result;
225 }
226 
227 // MSXEventListener
228 void JoyMega::signalMSXEvent(const shared_ptr<const Event>& event, EmuTime::param time)
229 {
230  auto joyEvent = dynamic_cast<const JoystickEvent*>(event.get());
231  if (!joyEvent) return;
232 
233  // TODO: It would be more efficient to make a dispatcher instead of
234  // sending the event to all joysticks.
235  if (joyEvent->getJoystick() != joyNum) return;
236 
237  switch (event->getType()) {
239  auto& mev = checked_cast<const JoystickAxisMotionEvent&>(*event);
240  int value = mev.getValue();
241  switch (mev.getAxis() & 1) {
242  case JoystickAxisMotionEvent::X_AXIS: // Horizontal
243  if (value < -THRESHOLD) {
244  // left, not right
245  createEvent(time, JOY_LEFT, JOY_RIGHT);
246  } else if (value > THRESHOLD) {
247  // not left, right
248  createEvent(time, JOY_RIGHT, JOY_LEFT);
249  } else {
250  // not left, not right
251  createEvent(time, 0, JOY_LEFT | JOY_RIGHT);
252  }
253  break;
254  case JoystickAxisMotionEvent::Y_AXIS: // Vertical
255  if (value < -THRESHOLD) {
256  // up, not down
257  createEvent(time, JOY_UP, JOY_DOWN);
258  } else if (value > THRESHOLD) {
259  // not up, down
260  createEvent(time, JOY_DOWN, JOY_UP);
261  } else {
262  // not up, not down
263  createEvent(time, 0, JOY_UP | JOY_DOWN);
264  }
265  break;
266  default:
267  // ignore other axis
268  break;
269  }
270  break;
271  }
273  auto& butEv = checked_cast<const JoystickButtonEvent&>(*event);
274  createEvent(time, encodeButton(butEv.getButton(), cycleMask), 0);
275  break;
276  }
278  auto& butEv = checked_cast<const JoystickButtonEvent&>(*event);
279  createEvent(time, 0, encodeButton(butEv.getButton(), cycleMask));
280  break;
281  }
282  default:
283  UNREACHABLE;
284  }
285 }
286 
287 void JoyMega::createEvent(EmuTime::param time, unsigned press, unsigned release)
288 {
289  unsigned newStatus = (status & ~press) | release;
290  createEvent(time, newStatus);
291 }
292 
293 void JoyMega::createEvent(EmuTime::param time, unsigned newStatus)
294 {
295  unsigned diff = status ^ newStatus;
296  if (!diff) {
297  // event won't actually change the status, so ignore it
298  return;
299  }
300  // make sure we create an event with minimal changes
301  unsigned press = status & diff;
302  unsigned release = newStatus & diff;
303  stateChangeDistributor.distributeNew(std::make_shared<JoyMegaState>(
304  time, joyNum, press, release));
305 }
306 
307 // StateChangeListener
308 void JoyMega::signalStateChange(const shared_ptr<StateChange>& event)
309 {
310  auto js = dynamic_cast<const JoyMegaState*>(event.get());
311  if (!js) return;
312 
313  // TODO: It would be more efficient to make a dispatcher instead of
314  // sending the event to all joysticks.
315  // TODO an alternative is to log events based on the connector instead
316  // of the joystick. That would make it possible to replay on a
317  // different host without an actual SDL joystick connected.
318  if (js->getJoystick() != joyNum) return;
319 
320  status = (status & ~js->getPress()) | js->getRelease();
321 }
322 
323 void JoyMega::stopReplay(EmuTime::param time)
324 {
325  createEvent(time, calcInitialState());
326 }
327 
328 template<typename Archive>
329 void JoyMega::serialize(Archive& ar, unsigned /*version*/)
330 {
331  ar.serialize("lastTime", lastTime,
332  "status", status,
333  "cycle", cycle,
334  "cycleMask", cycleMask);
335  if (ar.isLoader()) {
336  if (isPluggedIn()) {
337  plugHelper2();
338  }
339  }
340 }
343 
344 #endif // SDL_JOYSTICK_DISABLED
345 
346 } // namespace openmsx
void plugHelper(Connector &connector, EmuTime::param time) override
Definition: JoyMega.cc:123
static const int JOY_LEFT
static const int JOY_UP
void serialize(Archive &ar, unsigned version)
Definition: JoyMega.cc:329
JoyMegaState(EmuTime::param time_, unsigned joyNum_, unsigned press_, unsigned release_)
Definition: JoyMega.cc:60
REGISTER_POLYMORPHIC_INITIALIZER(Pluggable, CassettePlayer, "CassettePlayer")
Represents something you can plug devices into.
Definition: Connector.hh:20
bool isPluggedIn() const
Returns true if this pluggable is currently plugged into a connector.
Definition: Pluggable.hh:49
void unregisterListener(StateChangeListener &listener)
static const unsigned Y_AXIS
Definition: InputEvents.hh:173
static void registerAll(MSXEventDistributor &eventDistributor, StateChangeDistributor &stateChangeDistributor, PluggingController &controller)
Definition: JoyMega.cc:26
uint8_t byte
8 bit unsigned integer
Definition: openmsx.hh:26
void registerEventListener(MSXEventListener &listener)
Registers a given object to receive certain events.
Central administration of Connectors and Pluggables.
JoyMega(MSXEventDistributor &eventDistributor, StateChangeDistributor &stateChangeDistributor, SDL_Joystick *joystick)
Definition: JoyMega.cc:90
void distributeNew(const EventPtr &event)
Deliver the event to all registered listeners MSX input devices should call the distributeNew() versi...
void registerListener(StateChangeListener &listener)
(Un)registers the given object to receive state change events.
void write(byte value, EmuTime::param time) override
Write a value to the joystick device.
Definition: JoyMega.cc:177
unsigned getRelease() const
Definition: JoyMega.cc:70
unsigned getJoystick() const
Definition: JoyMega.cc:68
static int joystickNumButtons(SDL_Joystick *joystick)
Normally the following two functions simply delegate to SDL_JoystickNumButtons() and SDL_JoystickGetB...
void unregisterEventListener(MSXEventListener &listener)
Unregisters a previously registered event listener.
void unplugHelper(EmuTime::param time) override
Definition: JoyMega.cc:139
Thanks to enen for testing this on a real cartridge:
Definition: Autofire.cc:5
static EmuDuration usec(unsigned x)
Definition: EmuDuration.hh:41
static const int JOY_DOWN
void registerPluggable(std::unique_ptr< Pluggable > pluggable)
Add a Pluggable to the registry.
~JoyMega() override
Definition: JoyMega.cc:104
This class implements a (close approximation) of the std::string_view class.
Definition: string_view.hh:16
#define INSTANTIATE_SERIALIZE_METHODS(CLASS)
Definition: serialize.hh:1006
Base class for all external MSX state changing events.
Definition: StateChange.hh:13
static bool joystickGetButton(SDL_Joystick *joystick, int button)
static const unsigned X_AXIS
Definition: InputEvents.hh:172
void serialize(Archive &ar, unsigned)
Definition: JoyMega.cc:72
string_view getDescription() const override
Description for this pluggable.
Definition: JoyMega.cc:118
REGISTER_POLYMORPHIC_CLASS(DiskContainer, NowindRomDisk, "NowindRomDisk")
byte read(EmuTime::param time) override
Read from the joystick device.
Definition: JoyMega.cc:147
unsigned getPress() const
Definition: JoyMega.cc:69
static const int JOY_RIGHT
#define UNREACHABLE
Definition: unreachable.hh:38
const std::string & getName() const override
Name used to identify this pluggable.
Definition: JoyMega.cc:113