#pragma once #include #include #include #include "Runtime/Input/CFinalInput.hpp" #include "Runtime/Input/CKeyboardMouseController.hpp" #include namespace metaforce { class CArchitectureQueue; enum class EIOPort { Zero, One, Two, Three }; enum class EMotorState { Stop, Rumble, StopHard }; class CInputGenerator : public boo::DeviceFinder { enum class EStatusChange { NoChange = 0, Connected = 1, Disconnected = 2 }; /* When the sticks are used as logical (digital) input, * these thresholds determine the vector magnitude indicating * the logical state */ float m_leftDiv; float m_rightDiv; CKeyboardMouseControllerData m_data; CFinalInput m_lastUpdate; const CFinalInput& getFinalInput(unsigned idx, float dt) { m_lastUpdate = CFinalInput(idx, dt, m_data, m_lastUpdate); return m_lastUpdate; } bool m_firstFrame = true; public: CInputGenerator(float leftDiv, float rightDiv) : boo::DeviceFinder({dev_typeid(DolphinSmashAdapter)}), m_leftDiv(leftDiv), m_rightDiv(rightDiv) {} ~CInputGenerator() override { if (smashAdapter) { smashAdapter->setCallback(nullptr); smashAdapter->closeDevice(); } } /* Keyboard and mouse events are delivered on the main game * thread as part of the app's main event loop. The OS is responsible * for buffering events in its own way, then boo flushes the buffer * at the start of each frame, invoking these methods. No atomic locking * is necessary, only absolute state tracking. */ void mouseDown(const boo::SWindowCoord&, boo::EMouseButton button, boo::EModifierKey) { m_data.m_mouseButtons[size_t(button)] = true; } void mouseUp(const boo::SWindowCoord&, boo::EMouseButton button, boo::EModifierKey) { m_data.m_mouseButtons[size_t(button)] = false; } void mouseMove(const boo::SWindowCoord& coord) { m_data.m_mouseCoord = coord; } void scroll(const boo::SWindowCoord&, const boo::SScrollDelta& scroll) { m_data.m_accumScroll += scroll; } void charKeyDown(unsigned long charCode, boo::EModifierKey, bool) { charCode = tolower(charCode); if (charCode > 255) return; m_data.m_charKeys[charCode] = true; } void charKeyUp(unsigned long charCode, boo::EModifierKey mods) { charCode = tolower(charCode); if (charCode > 255) return; m_data.m_charKeys[charCode] = false; } void specialKeyDown(boo::ESpecialKey key, boo::EModifierKey, bool) { m_data.m_specialKeys[size_t(key)] = true; } void specialKeyUp(boo::ESpecialKey key, boo::EModifierKey) { m_data.m_specialKeys[size_t(key)] = false; } void modKeyDown(boo::EModifierKey mod, bool) { m_data.m_modMask = m_data.m_modMask | mod; } void modKeyUp(boo::EModifierKey mod) { m_data.m_modMask = m_data.m_modMask & ~mod; } void reset() { m_data.m_accumScroll.zeroOut(); } /* Input via the smash adapter is received asynchronously on a USB * report thread. This class atomically exchanges that data to the * game thread as needed */ struct DolphinSmashAdapterCallback : boo::IDolphinSmashAdapterCallback { std::array, 4> m_statusChanges; std::array m_connected{}; std::array m_states; std::mutex m_stateLock; void controllerConnected(unsigned idx, boo::EDolphinControllerType) override { /* Controller thread */ m_statusChanges[idx].store(EStatusChange::Connected); } void controllerDisconnected(unsigned idx) override { /* Controller thread */ std::unique_lock lk{m_stateLock}; m_statusChanges[idx].store(EStatusChange::Disconnected); m_states[idx].reset(); } void controllerUpdate(unsigned idx, boo::EDolphinControllerType, const boo::DolphinControllerState& state) override { /* Controller thread */ std::unique_lock lk{m_stateLock}; m_states[idx] = state; } std::array m_lastUpdates; const CFinalInput& getFinalInput(unsigned idx, float dt, float leftDiv, float rightDiv) { /* Game thread */ std::unique_lock lk{m_stateLock}; boo::DolphinControllerState state = m_states[idx]; lk.unlock(); state.clamp(); /* PADClamp equivalent */ m_lastUpdates[idx] = CFinalInput(idx, dt, state, m_lastUpdates[idx], leftDiv, rightDiv); return m_lastUpdates[idx]; } EStatusChange getStatusChange(unsigned idx, bool& connected) { /* Game thread */ EStatusChange ch = m_statusChanges[idx].exchange(EStatusChange::NoChange); if (ch == EStatusChange::Connected) m_connected[idx] = true; else if (ch == EStatusChange::Disconnected) m_connected[idx] = false; connected = m_connected[idx]; return ch; } } m_dolphinCb; /* Device connection/disconnection events are handled on a separate thread * using the relevant OS API. This thread blocks in a loop until an event is * received. Device pointers should only be manipulated by this thread using * the deviceConnected() and deviceDisconnected() callbacks. */ std::shared_ptr smashAdapter; void deviceConnected(boo::DeviceToken& tok) override { /* Device listener thread */ if (!smashAdapter) { auto dev = tok.openAndGetDevice(); if (dev && dev->getTypeHash() == dev_typeid(DolphinSmashAdapter)) { smashAdapter = std::static_pointer_cast(tok.openAndGetDevice()); smashAdapter->setCallback(&m_dolphinCb); } } } void deviceDisconnected(boo::DeviceToken&, boo::DeviceBase* device) override { if (smashAdapter.get() == device) smashAdapter.reset(); } void SetMotorState(EIOPort port, EMotorState state) { if (smashAdapter) { switch (state) { case EMotorState::Stop: smashAdapter->stopRumble(unsigned(port)); break; case EMotorState::Rumble: smashAdapter->startRumble(unsigned(port)); break; case EMotorState::StopHard: smashAdapter->stopRumble(unsigned(port), true); break; } } } void ControlAllMotors(const std::array& states) { if (smashAdapter) { for (size_t i = 0; i < states.size(); ++i) { switch (states[i]) { case EMotorState::Stop: smashAdapter->stopRumble(i); break; case EMotorState::Rumble: smashAdapter->startRumble(i); break; case EMotorState::StopHard: smashAdapter->stopRumble(i, true); break; } } } } /* This is where the game thread enters */ void Update(float dt, CArchitectureQueue& queue); CFinalInput GetLastInput() const { return m_lastUpdate; } }; } // namespace metaforce