openMSX
JoyMega.cc
Go to the documentation of this file.
1#include "JoyMega.hh"
2
4#include "Event.hh"
5#include "IntegerSetting.hh"
6#include "JoystickManager.hh"
8#include "StateChange.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 <memory>
19
20namespace openmsx {
21
22class JoyMegaState final : public StateChange
23{
24public:
25 JoyMegaState() = default; // for serialize
26 JoyMegaState(EmuTime::param time_, uint8_t id_,
27 unsigned press_, unsigned release_)
28 : StateChange(time_)
29 , press(press_), release(release_), id(id_) {}
30
31 [[nodiscard]] auto getId() const { return id; }
32 [[nodiscard]] auto getPress() const { return press; }
33 [[nodiscard]] auto getRelease() const { return release; }
34
35 template<typename Archive> void serialize(Archive& ar, unsigned /*version*/)
36 {
37 ar.template serializeBase<StateChange>(*this);
38 ar.serialize("id", id,
39 "press", press,
40 "release", release);
41 }
42private:
43 unsigned press, release;
44 uint8_t id;
45};
47
49{
50 auto buttons = joystickManager.getNumButtons(joyId);
51 if (!buttons) return {};
52
53 std::array<TclObject, 8> lists;
54 auto joy = joyId.str();
55 for (auto b : xrange(*buttons)) {
56 lists[b % 8].addListElement(tmpStrCat(joy, " button", b));
57 }
59 "UP", makeTclList(tmpStrCat(joy, " -axis1"), tmpStrCat(joy, " hat0 up")),
60 "DOWN", makeTclList(tmpStrCat(joy, " +axis1"), tmpStrCat(joy, " hat0 down")),
61 "LEFT", makeTclList(tmpStrCat(joy, " -axis0"), tmpStrCat(joy, " hat0 left")),
62 "RIGHT", makeTclList(tmpStrCat(joy, " +axis0"), tmpStrCat(joy, " hat0 right")),
63 "A", lists[0],
64 "B", lists[1],
65 "C", lists[2],
66 "X", lists[3],
67 "Y", lists[4],
68 "Z", lists[5],
69 "SELECT",lists[6],
70 "START", lists[7]);
71}
72
74 MSXEventDistributor& eventDistributor_,
75 StateChangeDistributor& stateChangeDistributor_,
76 JoystickManager& joystickManager_,
77 uint8_t id_)
78 : commandController(commandController_)
79 , eventDistributor(eventDistributor_)
80 , stateChangeDistributor(stateChangeDistributor_)
81 , joystickManager(joystickManager_)
82 , configSetting(commandController, tmpStrCat("joymega", id_, "_config"),
83 "joymega mapping configuration", getDefaultConfig(JoystickId(id_ - 1), joystickManager).getString())
84 , description(strCat("JoyMega based Mega Drive controller ", id_, ". Mapping is fully configurable."))
85 , id(id_)
86{
87 configSetting.setChecker([this](TclObject& newValue) {
88 this->checkJoystickConfig(newValue); });
89 // fill in 'bindings'
90 checkJoystickConfig(const_cast<TclObject&>(configSetting.getValue()));
91}
92
94{
95 if (isPluggedIn()) {
96 JoyMega::unplugHelper(EmuTime::dummy());
97 }
98}
99
100void JoyMega::checkJoystickConfig(const TclObject& newValue)
101{
102 std::array<std::vector<BooleanInput>, 12> newBindings;
103
104 auto& interp = commandController.getInterpreter();
105 unsigned n = newValue.getListLength(interp);
106 if (n & 1) {
107 throw CommandException("Need an even number of elements");
108 }
109 for (unsigned i = 0; i < n; i += 2) {
110 static constexpr std::array<std::string_view, 12> keys = {
111 // order is important!
112 "UP", "DOWN", "LEFT", "RIGHT",
113 "A", "B", "C", "START",
114 "X", "Y", "Z", "SELECT",
115 };
116 std::string_view key = newValue.getListIndex(interp, i + 0).getString();
117 auto it = ranges::find(keys, key);
118 if (it == keys.end()) {
119 throw CommandException(
120 "Invalid key: must be one of ", join(keys, ", "));
121 }
122 auto idx = std::distance(keys.begin(), it);
123
124 TclObject value = newValue.getListIndex(interp, i + 1);
125 for (auto j : xrange(value.getListLength(interp))) {
126 std::string_view val = value.getListIndex(interp, j).getString();
127 auto bind = parseBooleanInput(val);
128 if (!bind) {
129 throw CommandException("Invalid binding: ", val);
130 }
131 newBindings[idx].push_back(*bind);
132 }
133 }
134
135 // only change current bindings when parsing was fully successful
136 ranges::copy(newBindings, bindings);
137}
138
139// Pluggable
140std::string_view JoyMega::getName() const
141{
142 switch (id) {
143 case 1: return "joymega1";
144 case 2: return "joymega2";
145 default: UNREACHABLE;
146 }
147}
148
149std::string_view JoyMega::getDescription() const
150{
151 return description;
152}
153
154void JoyMega::plugHelper(Connector& /*connector*/, EmuTime::param /*time*/)
155{
156 plugHelper2();
157 status = 0xfff;
158 cycle = 0;
159 // when mode button is pressed when joystick is plugged in, then
160 // act as a 3-button joypad (otherwise 6-button)
161 cycleMask = (status & 0x800) ? 7 : 1;
162}
163
164void JoyMega::plugHelper2()
165{
166 eventDistributor.registerEventListener(*this);
167 stateChangeDistributor.registerListener(*this);
168}
169
170void JoyMega::unplugHelper(EmuTime::param /*time*/)
171{
172 stateChangeDistributor.unregisterListener(*this);
173 eventDistributor.unregisterEventListener(*this);
174}
175
176
177// JoystickDevice
178uint8_t JoyMega::read(EmuTime::param time)
179{
180 // See http://segaretro.org/Control_Pad_(Mega_Drive)
181 // and http://frs.badcoffee.info/hardware/joymega-en.html
182 // for a detailed description of the MegaDrive joystick.
183 checkTime(time);
184 switch (cycle) {
185 case 0: case 2: case 4:
186 // up/down/left/right/b/c
187 return (status & 0x00f) | ((status & 0x060) >> 1);
188 case 1: case 3:
189 // up/down/0/0/a/start
190 return (status & 0x013) | ((status & 0x080) >> 2);
191 case 5:
192 // 0/0/0/0/a/start
193 return (status & 0x010) | ((status & 0x080) >> 2);
194 case 6:
195 // z/y/x/mode/b/c
196 return ((status & 0x400) >> 10) | // z
197 ((status & 0xA00) >> 8) | // start+y
198 ((status & 0x100) >> 6) | // x
199 ((status & 0x060) >> 1); // c+b
200 case 7:
201 // 1/1/1/1/a/start
202 return (status & 0x010) | ((status & 0x080) >> 2) | 0x0f;
203 default:
205 }
206}
207
208void JoyMega::write(uint8_t value, EmuTime::param time)
209{
210 checkTime(time);
211 lastTime = time;
212 if (((value >> 2) & 1) != (cycle & 1)) {
213 cycle = (cycle + 1) & cycleMask;
214 }
215 assert(((value >> 2) & 1) == (cycle & 1));
216}
217
218void JoyMega::checkTime(EmuTime::param time)
219{
220 if ((time - lastTime) > EmuDuration::usec(1500)) {
221 // longer than 1.5ms since last write -> reset cycle
222 cycle = 0;
223 }
224}
225
226// MSXEventListener
227void JoyMega::signalMSXEvent(const Event& event, EmuTime::param time) noexcept
228{
229 unsigned press = 0;
230 unsigned release = 0;
231
232 auto getJoyDeadZone = [&](JoystickId joyId) {
233 auto* setting = joystickManager.getJoyDeadZoneSetting(joyId);
234 return setting ? setting->getInt() : 0;
235 };
236 for (int i : xrange(12)) {
237 for (const auto& binding : bindings[i]) {
238 if (auto onOff = match(binding, event, getJoyDeadZone)) {
239 (*onOff ? press : release) |= 1 << i;
240 }
241 }
242 }
243
244 if (((status & ~press) | release) != status) {
245 stateChangeDistributor.distributeNew<JoyMegaState>(
246 time, id, press, release);
247 }
248}
249
250// StateChangeListener
251void JoyMega::signalStateChange(const StateChange& event)
252{
253 const auto* js = dynamic_cast<const JoyMegaState*>(&event);
254 if (!js) return;
255 if (js->getId() != id) return;
256
257 status = (status & ~js->getPress()) | js->getRelease();
258}
259
260void JoyMega::stopReplay(EmuTime::param time) noexcept
261{
262 unsigned newStatus = 0xfff;
263 if (newStatus != status) {
264 auto release = newStatus & ~status;
265 stateChangeDistributor.distributeNew<JoyMegaState>(
266 time, id, 0, release);
267 }
268}
269
270template<typename Archive>
271void JoyMega::serialize(Archive& ar, unsigned /*version*/)
272{
273 ar.serialize("lastTime", lastTime,
274 "status", status,
275 "cycle", cycle,
276 "cycleMask", cycleMask);
277 if constexpr (Archive::IS_LOADER) {
278 if (isPluggedIn()) {
279 plugHelper2();
280 }
281 }
282}
285
286} // namespace openmsx
BaseSetting * setting
uintptr_t id
virtual Interpreter & getInterpreter()=0
static constexpr EmuDuration usec(unsigned x)
auto getPress() const
Definition JoyMega.cc:32
JoyMegaState(EmuTime::param time_, uint8_t id_, unsigned press_, unsigned release_)
Definition JoyMega.cc:26
auto getRelease() const
Definition JoyMega.cc:33
auto getId() const
Definition JoyMega.cc:31
void serialize(Archive &ar, unsigned)
Definition JoyMega.cc:35
static TclObject getDefaultConfig(JoystickId joyId, const JoystickManager &joystickManager)
Definition JoyMega.cc:48
JoyMega(CommandController &commandController, MSXEventDistributor &eventDistributor, StateChangeDistributor &stateChangeDistributor, JoystickManager &joystickManager, uint8_t id)
Definition JoyMega.cc:73
~JoyMega() override
Definition JoyMega.cc:93
void serialize(Archive &ar, unsigned version)
Definition JoyMega.cc:271
std::string str() const
Definition JoystickId.hh:16
std::optional< unsigned > getNumButtons(JoystickId joyId) const
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
void setChecker(std::function< void(TclObject &)> checkFunc_)
Set value-check-callback.
Definition Setting.hh:145
const TclObject & getValue() const final
Gets the current value of this setting as a TclObject.
Definition Setting.hh:133
void registerListener(StateChangeListener &listener)
(Un)registers the given object to receive state change events.
void unregisterListener(StateChangeListener &listener)
Base class for all external MSX state changing events.
unsigned getListLength(Interpreter &interp) const
Definition TclObject.cc:155
TclObject getListIndex(Interpreter &interp, unsigned index) const
Definition TclObject.cc:173
zstring_view getString() const
Definition TclObject.cc:141
detail::Joiner< Collection, Separator > join(Collection &&col, Separator &&sep)
Definition join.hh:60
This file implemented 3 utility functions:
Definition Autofire.cc:11
std::optional< bool > match(const BooleanInput &binding, const Event &event, function_ref< int(JoystickId)> getJoyDeadZone)
std::optional< BooleanInput > parseBooleanInput(std::string_view text)
std::variant< KeyUpEvent, KeyDownEvent, MouseMotionEvent, MouseButtonUpEvent, MouseButtonDownEvent, MouseWheelEvent, JoystickAxisMotionEvent, JoystickHatEvent, JoystickButtonUpEvent, JoystickButtonDownEvent, OsdControlReleaseEvent, OsdControlPressEvent, WindowEvent, TextEvent, FileDropEvent, QuitEvent, FinishFrameEvent, CliCommandEvent, GroupEvent, BootEvent, FrameDrawnEvent, BreakEvent, SwitchRendererEvent, TakeReverseSnapshotEvent, AfterTimedEvent, MachineLoadedEvent, MachineActivatedEvent, MachineDeactivatedEvent, MidiInReaderEvent, MidiInWindowsEvent, MidiInCoreMidiEvent, MidiInCoreMidiVirtualEvent, MidiInALSAEvent, Rs232TesterEvent, Rs232NetEvent, ImGuiDelayedActionEvent, ImGuiActiveEvent > Event
Definition Event.hh:444
TclObject makeTclList(Args &&... args)
Definition TclObject.hh:293
auto copy(InputRange &&range, OutputIter out)
Definition ranges.hh:250
auto find(InputRange &&range, const T &value)
Definition ranges.hh:160
constexpr auto keys(Map &&map)
Definition view.hh:525
#define INSTANTIATE_SERIALIZE_METHODS(CLASS)
#define REGISTER_POLYMORPHIC_INITIALIZER(BASE, CLASS, NAME)
#define REGISTER_POLYMORPHIC_CLASS(BASE, CLASS, NAME)
std::string strCat()
Definition strCat.hh:703
TemporaryString tmpStrCat(Ts &&... ts)
Definition strCat.hh:742
#define UNREACHABLE
constexpr auto xrange(T e)
Definition xrange.hh:132