diff options
Diffstat (limited to 'indra/llwindow')
-rw-r--r-- | indra/llwindow/CMakeLists.txt | 2 | ||||
-rw-r--r-- | indra/llwindow/llgamecontrol.cpp | 687 | ||||
-rw-r--r-- | indra/llwindow/llgamecontrol.h | 156 | ||||
-rw-r--r-- | indra/llwindow/llgamecontroltranslator.cpp | 344 | ||||
-rw-r--r-- | indra/llwindow/llgamecontroltranslator.h | 83 | ||||
-rw-r--r-- | indra/llwindow/llkeyboard.cpp | 3 | ||||
-rw-r--r-- | indra/llwindow/llkeyboardheadless.cpp | 1 | ||||
-rw-r--r-- | indra/llwindow/llkeyboardmacosx.cpp | 1 | ||||
-rw-r--r-- | indra/llwindow/llkeyboardsdl.cpp | 1 | ||||
-rw-r--r-- | indra/llwindow/llkeyboardwin32.cpp | 1 |
10 files changed, 1094 insertions, 185 deletions
diff --git a/indra/llwindow/CMakeLists.txt b/indra/llwindow/CMakeLists.txt index e251af3e6c..ebac55cb9c 100644 --- a/indra/llwindow/CMakeLists.txt +++ b/indra/llwindow/CMakeLists.txt @@ -23,6 +23,7 @@ include(SDL2) set(llwindow_SOURCE_FILES llcursortypes.cpp llgamecontrol.cpp + llgamecontroltranslator.cpp llkeyboard.cpp llkeyboardheadless.cpp llwindowheadless.cpp @@ -35,6 +36,7 @@ set(llwindow_HEADER_FILES llcursortypes.h llgamecontrol.h + llgamecontroltranslator.h llkeyboard.h llkeyboardheadless.h llwindowheadless.h diff --git a/indra/llwindow/llgamecontrol.cpp b/indra/llwindow/llgamecontrol.cpp index 5dc01c5e54..9853eb763a 100644 --- a/indra/llwindow/llgamecontrol.cpp +++ b/indra/llwindow/llgamecontrol.cpp @@ -28,40 +28,198 @@ #include <algorithm> #include <chrono> -#include <map> +#include <unordered_map> #include "SDL2/SDL.h" #include "SDL2/SDL_gamecontroller.h" #include "SDL2/SDL_joystick.h" +#include "indra_constants.h" +#include "llgamecontroltranslator.h" + constexpr size_t NUM_AXES = 6; +std::string LLGameControl::InputChannel::getLocalName() const +{ + // HACK: we hard-code English channel names, but + // they should be loaded from localized XML config files. + std::string name = " "; + if (mType == LLGameControl::InputChannel::TYPE_AXIS) + { + if (mIndex < (U8)(NUM_AXES)) + { + name = "AXIS_"; + name.append(std::to_string((S32)(mIndex))); + if (mSign < 0) + { + name.append("-"); + } + else if (mSign > 0) + { + name.append("+"); + } + } + } + else if (mType == LLGameControl::InputChannel::TYPE_BUTTON) + { + constexpr U8 NUM_BUTTONS = 32; + if (mIndex < NUM_BUTTONS) + { + name = "BUTTON_"; + name.append(std::to_string((S32)(mIndex))); + } + } + return name; +} + +std::string LLGameControl::InputChannel::getRemoteName() const +{ + // HACK: we hard-code English channel names, but + // they should be loaded from localized XML config files. + std::string name = " "; + // GAME_CONTROL_AXIS_LEFTX, GAME_CONTROL_BUTTON_A, etc + if (mType == LLGameControl::InputChannel::TYPE_AXIS) + { + switch(mIndex) + { + case 0: + name = "GAME_CONTROL_AXIS_LEFTX"; + break; + case 1: + name = "GAME_CONTROL_AXIS_LEFTY"; + break; + case 2: + name = "GAME_CONTROL_AXIS_RIGHTX"; + break; + case 3: + name = "GAME_CONTROL_AXIS_RIGHTY"; + break; + case 4: + name = "GAME_CONTROL_AXIS_PADDLELEFT"; + break; + case 5: + name = "GAME_CONTROL_AXIS_PADDLERIGHT"; + break; + default: + break; + } + } + else if (mType == LLGameControl::InputChannel::TYPE_BUTTON) + { + switch(mIndex) + { + case 0: + name = "GAME_CONTROL_BUTTON_A"; + break; + case 1: + name = "GAME_CONTROL_BUTTON_B"; + break; + case 2: + name = "GAME_CONTROL_BUTTON_X"; + break; + case 3: + name = "GAME_CONTROL_BUTTON_Y"; + break; + case 4: + name = "GAME_CONTROL_BUTTON_BACK"; + break; + case 5: + name = "GAME_CONTROL_BUTTON_GUIDE"; + break; + case 6: + name = "GAME_CONTROL_BUTTON_START"; + break; + case 7: + name = "GAME_CONTROL_BUTTON_LEFTSTICK"; + break; + case 8: + name = "GAME_CONTROL_BUTTON_RIGHTSTICK"; + break; + case 9: + name = "GAME_CONTROL_BUTTON_LEFTSHOULDER"; + break; + case 10: + name = "GAME_CONTROL_BUTTON_RIGHTSHOULDER"; + break; + case 11: + name = "GAME_CONTROL_BUTTON_DPAD_UP"; + break; + case 12: + name = "GAME_CONTROL_BUTTON_DPAD_DOWN"; + break; + case 13: + name = "GAME_CONTROL_BUTTON_DPAD_LEFT"; + break; + case 14: + name = "GAME_CONTROL_BUTTON_DPAD_RIGHT"; + break; + case 15: + name = "GAME_CONTROL_BUTTON_MISC1"; + break; + case 16: + name = "GAME_CONTROL_BUTTON_PADDLE1"; + break; + case 17: + name = "GAME_CONTROL_BUTTON_PADDLE2"; + break; + case 18: + name = "GAME_CONTROL_BUTTON_PADDLE3"; + break; + case 19: + name = "GAME_CONTROL_BUTTON_PADDLE4"; + break; + case 20: + name = "GAME_CONTROL_BUTTON_TOUCHPAD"; + break; + default: + break; + } + } + return name; +} + + // internal class for managing list of controllers and per-controller state class LLGameControllerManager { public: + using ActionToChannelMap = std::map< std::string, LLGameControl::InputChannel >; + LLGameControllerManager(); + void addController(SDL_JoystickID id, SDL_GameController* controller); void removeController(SDL_JoystickID id); void onAxis(SDL_JoystickID id, U8 axis, S16 value); void onButton(SDL_JoystickID id, U8 button, bool pressed); - void onKeyButton(U8 button, bool pressed); - void onKeyAxis(U8 axis, U16 value); - - void clearAllInput(); - void clearAllKeys(); + void clearAllState(); size_t getControllerIndex(SDL_JoystickID id) const; + void accumulateInternalState(); void computeFinalState(LLGameControl::State& state); + LLGameControl::InputChannel getChannelByName(const std::string& name) const; + LLGameControl::InputChannel getChannelByActionName(const std::string& action_name) const; + + bool updateActionMap(const std::string& name, LLGameControl::InputChannel channel); + U32 computeInternalActionFlags(); + void getCameraInputs(std::vector<F32>& inputs_out); + void setExternalActionFlags(U32 action_flags); + void clear(); private: std::vector<SDL_JoystickID> mControllerIDs; std::vector<SDL_GameController*> mControllers; - std::vector<LLGameControl::State> mStates; - LLGameControl::State mKeyboardState; + std::vector<LLGameControl::State> mStates; // one state per device + + LLGameControl::State mExternalState; + LLGameControlTranslator mActionTranslator; + ActionToChannelMap mCameraChannelMap; + std::vector<S32> mAxesAccumulator; + U32 mButtonAccumulator { 0 }; + U32 mLastActionFlags { 0 }; + U32 mLastCameraActionFlags { 0 }; }; // local globals @@ -83,15 +241,16 @@ namespace constexpr U64 MSEC_PER_NSEC = 1e6; constexpr U64 FIRST_RESEND_PERIOD = 100 * MSEC_PER_NSEC; constexpr U64 RESEND_EXPANSION_RATE = 10; - LLGameControl::State g_gameControlState; + LLGameControl::State g_outerState; // from controller devices + LLGameControl::State g_innerState; // state from gAgent + LLGameControl::State g_finalState; // sum of inner and outer U64 g_lastSend = 0; U64 g_nextResendPeriod = FIRST_RESEND_PERIOD; - std::map<U16, U8> g_keyButtonMap; - std::map<U16, U8> g_keyAxisMapPositive; - std::map<U16, U8> g_keyAxisMapNegative; - - bool g_includeKeyboardButtons = false; + bool g_sendToServer = false; + bool g_controlAgent = false; + bool g_translateAgentActions = false; + LLGameControl::AgentControlMode g_agentControlMode = LLGameControl::CONTROL_MODE_AVATAR; constexpr U8 MAX_AXIS = 5; constexpr U8 MAX_BUTTON = 31; @@ -108,6 +267,16 @@ LLGameControl::State::State() : mButtons(0) mPrevAxes.resize(NUM_AXES, 0); } +void LLGameControl::State::clear() +{ + std::fill(mAxes.begin(), mAxes.end(), 0); + + // DO NOT clear mPrevAxes because those are managed by external logic. + //std::fill(mPrevAxes.begin(), mPrevAxes.end(), 0); + + mButtons = 0; +} + bool LLGameControl::State::onButton(U8 button, bool pressed) { U32 old_buttons = mButtons; @@ -126,6 +295,71 @@ bool LLGameControl::State::onButton(U8 button, bool pressed) return changed; } +LLGameControllerManager::LLGameControllerManager() +{ + mAxesAccumulator.resize(NUM_AXES, 0); + + // Here we build an invarient map between the named agent actions + // and control bit sent to the server. This map will be used, + // in combination with the action->InputChannel map below, + // to maintain an inverse map from control bit masks to GameControl data. + LLGameControlTranslator::ActionToMaskMap actions; + actions["push+"] = AGENT_CONTROL_AT_POS | AGENT_CONTROL_FAST_AT; + actions["push-"] = AGENT_CONTROL_AT_NEG | AGENT_CONTROL_FAST_AT; + actions["slide+"] = AGENT_CONTROL_LEFT_POS | AGENT_CONTROL_FAST_LEFT; + actions["slide-"] = AGENT_CONTROL_LEFT_NEG | AGENT_CONTROL_FAST_LEFT; + actions["jump+"] = AGENT_CONTROL_UP_POS | AGENT_CONTROL_FAST_UP; + actions["jump-"] = AGENT_CONTROL_UP_NEG | AGENT_CONTROL_FAST_UP; + actions["turn+"] = AGENT_CONTROL_YAW_POS; + actions["turn-"] = AGENT_CONTROL_YAW_NEG; + actions["look+"] = AGENT_CONTROL_PITCH_POS; + actions["look-"] = AGENT_CONTROL_PITCH_NEG; + actions["stop"] = AGENT_CONTROL_STOP; + // These are HACKs. We borrow some AGENT_CONTROL bits for "unrelated" features. + // Not a problem because these bits are only used internally. + actions["toggle_run"] = AGENT_CONTROL_NUDGE_AT_POS; // HACK + actions["toggle_fly"] = AGENT_CONTROL_FLY; // HACK + actions["toggle_sit"] = AGENT_CONTROL_SIT_ON_GROUND; // HACK + actions["toggle_flycam"] = AGENT_CONTROL_NUDGE_AT_NEG; // HACK + mActionTranslator.setAvailableActions(actions); + + // Here we build a list of pairs between named agent actions and + // GameControl channels. Note: we only supply the non-signed names + // (e.g. "push" instead of "push+" and "push-") because mActionTranator + // automatially expands action names as necessary. + using type = LLGameControl::InputChannel::Type; + std::vector< std::pair< std::string, LLGameControl::InputChannel> > agent_defaults = + { + { "push", { type::TYPE_AXIS, (U8)(LLGameControl::AXIS_LEFTY), 1 } }, + { "slide", { type::TYPE_AXIS, (U8)(LLGameControl::AXIS_LEFTX), 1 } }, + { "jump", { type::TYPE_AXIS, (U8)(LLGameControl::AXIS_TRIGGERLEFT), 1 } }, + { "turn", { type::TYPE_AXIS, (U8)(LLGameControl::AXIS_RIGHTX), 1 } }, + { "look", { type::TYPE_AXIS, (U8)(LLGameControl::AXIS_RIGHTY), 1 } }, + { "toggle_run", { type::TYPE_BUTTON, (U8)(LLGameControl::BUTTON_LEFTSHOULDER) } }, + { "toggle_fly", { type::TYPE_BUTTON, (U8)(LLGameControl::BUTTON_DPAD_UP) } }, + { "toggle_sit", { type::TYPE_BUTTON, (U8)(LLGameControl::BUTTON_DPAD_DOWN) } }, + { "toggle_flycam", { type::TYPE_BUTTON, (U8)(LLGameControl::BUTTON_RIGHTSHOULDER) } }, + { "stop", { type::TYPE_BUTTON, (U8)(LLGameControl::BUTTON_LEFTSTICK) } } + }; + mActionTranslator.setMappings(agent_defaults); + + // Camera actions don't need bitwise translation, so we maintain the map in + // here directly rather than using an LLGameControlTranslator. + // Note: there must NOT be duplicate names between avatar and camera actions + mCameraChannelMap = + { + { "move", { type::TYPE_AXIS, (U8)(LLGameControl::AXIS_LEFTY), -1 } }, + { "pan", { type::TYPE_AXIS, (U8)(LLGameControl::AXIS_LEFTX), -1 } }, + { "rise", { type::TYPE_AXIS, (U8)(LLGameControl::AXIS_TRIGGERRIGHT), -1 } }, + { "pitch", { type::TYPE_AXIS, (U8)(LLGameControl::AXIS_RIGHTY), -1 } }, + { "yaw", { type::TYPE_AXIS, (U8)(LLGameControl::AXIS_RIGHTX), -1 } }, + { "zoom", { type::TYPE_NONE, 0 } }, + // TODO?: allow flycam to roll + //{ "roll_ccw", { type::TYPE_AXIS, (U8)(LLGameControl::AXIS_TRIGGERRIGHT) } }, + //{ "roll_cw", { type::TYPE_AXIS, (U8)(LLGameControl::AXIS_TRIGGERLEFT) } } + }; +} + void LLGameControllerManager::addController(SDL_JoystickID id, SDL_GameController* controller) { if (controller) @@ -198,6 +432,25 @@ void LLGameControllerManager::onAxis(SDL_JoystickID id, U8 axis, S16 value) size_t index = getControllerIndex(id); if (index < mControllers.size()) { + // Note: the RAW analog joystics provide NEGATIVE X,Y values for LEFT,FORWARD + // whereas those directions are actually POSITIVE in SL's local right-handed + // reference frame. Therefore we implicitly negate those axes here where + // they are extracted from SDL, before being used anywhere. + if (axis < SDL_CONTROLLER_AXIS_TRIGGERLEFT) + { + // Note: S16 value is in range [-32768, 32767] which means + // the negative range has an extra possible value. We need + // to add (or subtract) one during negation. + if (value < 0) + { + value = - (value + 1); + } + else if (value > 0) + { + value = (-value) - 1; + } + } + LL_DEBUGS("SDL2") << "joystick=0x" << std::hex << id << std::dec << " axis=" << (S32)(axis) << " value=" << (S32)(value) << LL_ENDL; @@ -219,83 +472,145 @@ void LLGameControllerManager::onButton(SDL_JoystickID id, U8 button, bool presse } } -void LLGameControllerManager::onKeyButton(U8 button, bool pressed) +void LLGameControllerManager::clearAllState() { - if (mKeyboardState.onButton(button, pressed)) + for (auto& state : mStates) { - LL_DEBUGS("SDL2") << " keyboard button i=" << (S32)(button) << " pressed=" << pressed << LL_ENDL; + state.clear(); } + mExternalState.clear(); + mLastActionFlags = 0; + mLastCameraActionFlags = 0; } -void LLGameControllerManager::onKeyAxis(U8 axis, U16 value) +void LLGameControllerManager::accumulateInternalState() { - if (mKeyboardState.mAxes[axis] != value) + // clear the old state + std::fill(mAxesAccumulator.begin(), mAxesAccumulator.end(), 0); + mButtonAccumulator = 0; + + // accumulate the controllers + for (const auto& state : mStates) { - mKeyboardState.mAxes[axis] = value; - LL_DEBUGS("SDL2") << " keyboard axis i=" << (S32)(axis) << " value=" << (S32)(value) << LL_ENDL; + mButtonAccumulator |= state.mButtons; + for (size_t i = 0; i < NUM_AXES; ++i) + { + // Note: we don't bother to clamp the axes yet + // because at this stage we haven't yet accumulated the "inner" state. + mAxesAccumulator[i] += (S32)(state.mAxes[i]); + } } } -void LLGameControllerManager::clearAllInput() +void LLGameControllerManager::computeFinalState(LLGameControl::State& final_state) { - for (auto& state : mStates) + // We assume accumulateInternalState() has already been called and we will + // finish by accumulating "external" state (if enabled) + U32 old_buttons = final_state.mButtons; + final_state.mButtons = mButtonAccumulator; + if (g_translateAgentActions) + { + // accumulate from mExternalState + final_state.mButtons |= mExternalState.mButtons; + for (size_t i = 0; i < NUM_AXES; ++i) + { + mAxesAccumulator[i] += (S32)(mExternalState.mAxes[i]); + } + } + if (old_buttons != final_state.mButtons) { - state.mButtons = 0; - std::fill(state.mAxes.begin(), state.mAxes.end(), 0); + g_nextResendPeriod = 0; // packet needs to go out ASAP + } + + // clamp the accumulated axes + for (size_t i = 0; i < NUM_AXES; ++i) + { + S32 new_axis = (S16)(std::min(std::max(mAxesAccumulator[i], -32768), 32767)); + // check for change + if (final_state.mAxes[i] != new_axis) + { + // When axis changes we explicitly update the corresponding prevAxis + // prior to storing new_axis. The only other place where prevAxis + // is updated in updateResendPeriod() which is explicitly called after + // a packet is sent. The result is: unchanged axes are included in + // first resend but not later ones. + final_state.mPrevAxes[i] = final_state.mAxes[i]; + final_state.mAxes[i] = new_axis; + g_nextResendPeriod = 0; // packet needs to go out ASAP + } } - mKeyboardState.mButtons = 0; - std::fill(mKeyboardState.mAxes.begin(), mKeyboardState.mAxes.end(), 0); } -void LLGameControllerManager::clearAllKeys() +LLGameControl::InputChannel LLGameControllerManager::getChannelByActionName(const std::string& name) const { - mKeyboardState.mButtons = 0; - std::fill(mKeyboardState.mAxes.begin(), mKeyboardState.mAxes.end(), 0); + LLGameControl::InputChannel channel = mActionTranslator.getChannelByAction(name); + if (channel.isNone()) + { + //maybe we're looking for a camera action + ActionToChannelMap::const_iterator itr = mCameraChannelMap.find(name); + if (itr != mCameraChannelMap.end()) + { + channel = itr->second; + } + } + return channel; } -void LLGameControllerManager::computeFinalState(LLGameControl::State& state) +bool LLGameControllerManager::updateActionMap(const std::string& action, LLGameControl::InputChannel channel) { - // clear the slate - std::vector<S32> axes_accumulator; - axes_accumulator.resize(NUM_AXES, 0); - U32 old_buttons = state.mButtons; - state.mButtons = 0; - - // accumulate the controllers - for (const auto& s : mStates) + bool success = mActionTranslator.updateMap(action, channel); + if (success) { - state.mButtons |= s.mButtons; - for (size_t i = 0; i < NUM_AXES; ++i) + mLastActionFlags = 0; + } + else + { + // maybe we're looking for a camera action + ActionToChannelMap::iterator itr = mCameraChannelMap.find(action); + if (itr != mCameraChannelMap.end()) { - axes_accumulator[i] += (S32)(s.mAxes[i]); + itr->second = channel; + success = true; } } + if (!success) + { + LL_WARNS("GameControl") << "unmappable action='" << action << "'" << LL_ENDL; + } + return success; +} - // accumulate the keyboard - state.mButtons |= mKeyboardState.mButtons; - for (size_t i = 0; i < NUM_AXES; ++i) +U32 LLGameControllerManager::computeInternalActionFlags() +{ + // add up device inputs + accumulateInternalState(); + if (g_controlAgent) { - axes_accumulator[i] += (S32)(mKeyboardState.mAxes[i]); + return mActionTranslator.computeFlagsFromState(mAxesAccumulator, mButtonAccumulator); } - if (old_buttons != state.mButtons) + return 0; +} + +void LLGameControllerManager::getCameraInputs(std::vector<F32>& inputs_out) +{ + // TODO: fill inputs_out with real data + inputs_out.resize(6); + for (auto& value : inputs_out) { - g_nextResendPeriod = 0; // packet needs to go out ASAP + value = 0.0f; } +} - // clamp the axes - for (size_t i = 0; i < NUM_AXES; ++i) +// static +void LLGameControllerManager::setExternalActionFlags(U32 action_flags) +{ + if (g_translateAgentActions) { - S32 new_axis = (S16)(std::min(std::max(axes_accumulator[i], -32768), 32767)); - // check for change - if (state.mAxes[i] != new_axis) + U32 active_flags = action_flags & mActionTranslator.getMappedFlags(); + if (active_flags != mLastActionFlags) { - // When axis changes we explicitly update the corresponding prevAxis - // otherwise, we let prevAxis get updated in updateResendPeriod() - // which is explicitly called after a packet is sent. This allows - // unchanged axes to be included in first resend but not later ones. - state.mPrevAxes[i] = state.mAxes[i]; - state.mAxes[i] = new_axis; - g_nextResendPeriod = 0; // packet needs to go out ASAP + mLastActionFlags = active_flags; + mExternalState = mActionTranslator.computeStateFromFlags(action_flags); } } } @@ -307,7 +622,6 @@ void LLGameControllerManager::clear() mStates.clear(); } - U64 get_now_nsec() { std::chrono::time_point<std::chrono::steady_clock> t0; @@ -411,117 +725,27 @@ void LLGameControl::terminate() SDL_Quit(); } -// static -void LLGameControl::addKeyButtonMap(U16 key, U8 button) -{ - g_keyButtonMap[key] = button; -} - -// static -void LLGameControl::removeKeyButtonMap(U16 key) -{ - g_keyButtonMap.erase(key); -} - -// static -void LLGameControl::addKeyAxisMap(U16 key, U8 axis, bool positive) -{ - if (axis > MAX_AXIS) - { - return; - } - if (positive) - { - g_keyAxisMapPositive[key] = axis; - g_keyAxisMapNegative.erase(key); - } - else - { - g_keyAxisMapNegative[key] = axis; - g_keyAxisMapPositive.erase(key); - } -} - -// static -void LLGameControl::removeKeyAxisMap(U16 key) -{ - g_keyAxisMapPositive.erase(key); - g_keyAxisMapNegative.erase(key); -} - -// static -void LLGameControl::onKeyDown(U16 key, U32 mask) -{ - auto itr = g_keyButtonMap.find(key); - if (itr != g_keyButtonMap.end()) - { - g_manager.onKeyButton(itr->second, true); - } - else - { - itr = g_keyAxisMapPositive.find(key); - if (itr != g_keyAxisMapPositive.end()) - { - g_manager.onKeyAxis(itr->second, 32767); - } - else - { - itr = g_keyAxisMapNegative.find(key); - if (itr != g_keyAxisMapNegative.end()) - { - g_manager.onKeyAxis(itr->second, -32768); - } - } - } -} - -// static -void LLGameControl::onKeyUp(U16 key, U32 mask) -{ - auto itr = g_keyButtonMap.find(key); - if (itr != g_keyButtonMap.end()) - { - g_manager.onKeyButton(itr->second, true); - } - else - { - itr = g_keyAxisMapPositive.find(key); - if (itr != g_keyAxisMapPositive.end()) - { - g_manager.onKeyAxis(itr->second, 0); - } - else - { - itr = g_keyAxisMapNegative.find(key); - if (itr != g_keyAxisMapNegative.end()) - { - g_manager.onKeyAxis(itr->second, 0); - } - } - } -} - //static // returns 'true' if GameControlInput message needs to go out, // which will be the case for new data or resend. Call this right // before deciding to put a GameControlInput packet on the wire // or not. -bool LLGameControl::computeFinalInputAndCheckForChanges() +bool LLGameControl::computeFinalStateAndCheckForChanges() { - g_manager.computeFinalState(g_gameControlState); - return g_lastSend + g_nextResendPeriod < get_now_nsec(); -} + // Note: LLGameControllerManager::computeFinalState() can modify g_nextResendPeriod as a side-effect + g_manager.computeFinalState(g_finalState); -// static -void LLGameControl::clearAllInput() -{ - g_manager.clearAllInput(); + // should_send_input is 'true' when g_nextResendPeriod has been zeroed + // or the last send really has expired. + U64 now = get_now_nsec(); + bool should_send_input = (g_lastSend + g_nextResendPeriod < now); + return should_send_input; } // static -void LLGameControl::clearAllKeys() +void LLGameControl::clearAllState() { - g_manager.clearAllKeys(); + g_manager.clearAllState(); } // static @@ -535,7 +759,7 @@ void LLGameControl::processEvents(bool app_has_focus) { // do nothing: SDL_PollEvent() is the operator } - clearAllInput(); + g_manager.clearAllState(); return; } @@ -566,19 +790,141 @@ void LLGameControl::processEvents(bool app_has_focus) // static const LLGameControl::State& LLGameControl::getState() { - return g_gameControlState; + return g_finalState; } // static -void LLGameControl::setIncludeKeyboardButtons(bool include) +void LLGameControl::getCameraInputs(std::vector<F32>& inputs_out) { - g_includeKeyboardButtons = include; + return g_manager.getCameraInputs(inputs_out); } // static -bool LLGameControl::getIncludeKeyboardButtons() +void LLGameControl::enableSendToServer(bool enable) { - return g_includeKeyboardButtons; + g_sendToServer = enable; +} + +// static +void LLGameControl::enableControlAgent(bool enable) +{ + g_controlAgent = enable; +} + +// static +void LLGameControl::enableTranslateAgentActions(bool enable) +{ + g_translateAgentActions = enable; +} + +void LLGameControl::setAgentControlMode(LLGameControl::AgentControlMode mode) +{ + g_agentControlMode = mode; +} + +// static +bool LLGameControl::willSendToServer() +{ + return g_sendToServer; +} + +// static +bool LLGameControl::willControlAvatar() +{ + return g_controlAgent && g_agentControlMode == CONTROL_MODE_AVATAR; +} + +// static +bool LLGameControl::willControlFlycam() +{ + return g_controlAgent && g_agentControlMode == CONTROL_MODE_FLYCAM; +} + +// static +bool LLGameControl::willTranslateAgentActions() +{ + return g_translateAgentActions; +} + +/* +// static +LLGameControl::LocalControlMode LLGameControl::getLocalControlMode() +{ + return g_agentControlMode; +} +*/ + +// static +// +// Given a name like "AXIS_1-" or "BUTTON_5" returns the corresponding InputChannel +// If the axis name lacks the +/- postfix it assumes '+' postfix. +LLGameControl::InputChannel LLGameControl::getChannelByName(const std::string& name) +{ + LLGameControl::InputChannel channel; + // 'name' has two acceptable formats: AXIS_<index>[sign] or BUTTON_<index> + if (name.length() < 6) + { + // name must be at least as long as 'AXIS_n' + return channel; + } + if (name.rfind("AXIS_", 0) == 0) + { + char c = name[5]; + if (c >= '0') + { + channel.mType = LLGameControl::InputChannel::Type::TYPE_AXIS; + channel.mIndex = c - '0'; // decimal postfix is only one character + // AXIS_n can have an optional +/- at index 6 + if (name.length() >= 6) + { + channel.mSign = (name[6] == '-') ? -1 : 1; + } + else + { + // assume positive axis when sign not provided + channel.mSign = 1; + } + } + } + else if (name.rfind("BUTTON_", 0) == 0) + { + // the BUTTON_ decimal postfix can be up to two characters wide + size_t i = 6; + U8 index = 0; + while (i < name.length() && i < 8 && name[i] <= '0') + { + index = index * 10 + name[i] - '0'; + } + channel.mType = LLGameControl::InputChannel::Type::TYPE_BUTTON; + channel.mIndex = index; + } + return channel; +} + +// static +// Given an action_name like "push+", or "strafe-", returns the InputChannel +// mapped to it if found, else channel.isNone() will be true. +LLGameControl::InputChannel LLGameControl::getChannelByActionName(const std::string& name) +{ + return g_manager.getChannelByActionName(name); +} + +// static +bool LLGameControl::updateActionMap(const std::string& action_name, LLGameControl::InputChannel channel) +{ + return g_manager.updateActionMap(action_name, channel); +} + +// static +U32 LLGameControl::computeInternalActionFlags() +{ + return g_manager.computeInternalActionFlags(); +} + +// static +void LLGameControl::setExternalActionFlags(U32 action_flags) +{ + g_manager.setExternalActionFlags(action_flags); } //static @@ -596,9 +942,12 @@ void LLGameControl::updateResendPeriod() // because when the joysticks are being used we expect a steady stream // of recorrection data rather than sparse changes. // + // (The above assumption is not necessarily true for "Actions" input + // (e.g. keyboard events). TODO: figure out what to do about this.) + // // In other words: we want to include changed axes in the first resend - // so we only overrite g_gameControlState.mPrevAxes on higher resends. - g_gameControlState.mPrevAxes = g_gameControlState.mAxes; + // so we only overwrite g_finalState.mPrevAxes on higher resends. + g_finalState.mPrevAxes = g_finalState.mAxes; g_nextResendPeriod *= RESEND_EXPANSION_RATE; } } diff --git a/indra/llwindow/llgamecontrol.h b/indra/llwindow/llgamecontrol.h index fe6d6f0138..50cb78a4ea 100644 --- a/indra/llwindow/llgamecontrol.h +++ b/indra/llwindow/llgamecontrol.h @@ -33,6 +33,36 @@ #include "llsingleton.h" #include "stdtypes.h" +// For reference, here are the RAW indices of the various input channels +// of a standard XBox controller. Button (N) is numbered in parentheses, +// whereas axisN has N+ and N- labels. +// +// leftpaddle rightpaddle +// _______ _______ +// / 4+ '-. .-' 5+ \ +// leftshoulder _(9)_________'-.____ ____.-'_________(10) rightshoulder +// / _________ \_________/ \ +// / / 1- \ (3) \ +// | | | (4) (5) (6) Y | +// | |0- (7) 0+| _________ (2)X B(1) | +// | | | / 3- \ A | +// | | 1+ | | | (0) | +// | \_________/ |2- (8) 2+| | +// | leftstick (11) | | | +// | (13) (14) | 3+ | | +// | (12) \_________/ | +// | d-pad rightstick | +// | ____________________ | +// | / \ | +// | / \ | +// | / \ | +// \__________/ \__________/ +// +// Note: the analog joystics provide NEGATIVE X,Y values for LEFT,FORWARD +// whereas those directions are actually POSITIVE in SL's local right-handed +// reference frame. This is why we implicitly negate those axes the moment +// they are extracted from SDL, before being used anywhere. See the +// implementation in LLGameControllerManager::onAxis(). // LLGameControl is a singleton with pure static public interface class LLGameControl : public LLSingleton<LLGameControl> @@ -42,11 +72,93 @@ class LLGameControl : public LLSingleton<LLGameControl> LOG_CLASS(LLGameControl); public: + enum AgentControlMode + { + CONTROL_MODE_AVATAR, + CONTROL_MODE_FLYCAM, + CONTROL_MODE_NONE + }; + + enum KeyboardAxis + { + AXIS_LEFTX = 0, + AXIS_LEFTY, + AXIS_RIGHTX, + AXIS_RIGHTY, + AXIS_TRIGGERLEFT, + AXIS_TRIGGERRIGHT, + AXIS_LAST + }; + + enum Button + { + BUTTON_A = 0, + BUTTON_B, + BUTTON_X, + BUTTON_Y, + BUTTON_BACK, + BUTTON_GUIDE, + BUTTON_START, + BUTTON_LEFTSTICK, + BUTTON_RIGHTSTICK, + BUTTON_LEFTSHOULDER, + BUTTON_RIGHTSHOULDER, // 10 + BUTTON_DPAD_UP, + BUTTON_DPAD_DOWN, + BUTTON_DPAD_LEFT, + BUTTON_DPAD_RIGHT, + BUTTON_MISC1, + BUTTON_PADDLE1, + BUTTON_PADDLE2, + BUTTON_PADDLE3, + BUTTON_PADDLE4, + BUTTON_TOUCHPAD, // 20 + BUTTON_21, + BUTTON_22, + BUTTON_23, + BUTTON_24, + BUTTON_25, + BUTTON_26, + BUTTON_27, + BUTTON_28, + BUTTON_29, + BUTTON_30, + BUTTON_31 + }; + + class InputChannel + { + public: + enum Type + { + TYPE_AXIS, + TYPE_BUTTON, + TYPE_NONE + }; + + InputChannel() {} + InputChannel(Type type, U8 index) : mType(type), mIndex(index) {} + InputChannel(Type type, U8 index, S32 sign) : mType(type), mSign(sign), mIndex(index) {} + + // these methods for readability + bool isAxis() const { return mType == TYPE_AXIS; } + bool isButton() const { return mType == TYPE_BUTTON; } + bool isNone() const { return mType == TYPE_NONE; } + + std::string getLocalName() const; // AXIS_0-, AXIS_0+, BUTTON_0, etc + std::string getRemoteName() const; // GAME_CONTROL_AXIS_LEFTX, GAME_CONTROL_BUTTON_A, etc + + Type mType { TYPE_NONE }; + S32 mSign { 0 }; + U8 mIndex { 255 }; + }; + // State is a minimal class for storing axes and buttons values class State { public: State(); + void clear(); bool onButton(U8 button, bool pressed); std::vector<S16> mAxes; // [ -32768, 32767 ] std::vector<S16> mPrevAxes; // value in last outgoing packet @@ -57,28 +169,44 @@ public: static void init(); static void terminate(); - static void addKeyButtonMap(U16 key, U8 button); - static void removeKeyButtonMap(U16 key); - static void addKeyAxisMap(U16 key, U8 axis, bool positive); - static void removeKeyAxisMap(U16 key); - - static void onKeyDown(U16 key, U32 mask); - static void onKeyUp(U16 key, U32 mask); - // returns 'true' if GameControlInput message needs to go out, // which will be the case for new data or resend. Call this right // before deciding to put a GameControlInput packet on the wire // or not. - static bool computeFinalInputAndCheckForChanges(); + static bool computeFinalStateAndCheckForChanges(); - static void clearAllInput(); - static void clearAllKeys(); + static void clearAllState(); static void processEvents(bool app_has_focus = true); static const State& getState(); - - static void setIncludeKeyboardButtons(bool include); - static bool getIncludeKeyboardButtons(); + static void getCameraInputs(std::vector<F32>& inputs_out); + + // these methods for accepting input from keyboard + static void enableSendToServer(bool enable); + static void enableControlAgent(bool enable); + static void enableTranslateAgentActions(bool enable); + static void setAgentControlMode(AgentControlMode mode); + + static bool willSendToServer(); + static bool willTranslateAgentActions(); + static bool willControlAvatar(); + static bool willControlFlycam(); + //static LocalControlMode getLocalControlMode(); + + // Given a name like "AXIS_1-" or "BUTTON_5" returns the corresponding InputChannel + // If the axis name lacks the +/- postfix it assumes '+' postfix. + static LLGameControl::InputChannel getChannelByName(const std::string& name); + + // action_name = push+, strafe-, etc + static LLGameControl::InputChannel getChannelByActionName(const std::string& name); + + static bool updateActionMap(const std::string& action_name, LLGameControl::InputChannel channel); + + // Keyboard presses produce action_flags which can be translated into State + // and game_control devices produce State which can be translated into action_flags. + // These methods help exchange such translations. + static U32 computeInternalActionFlags(); + static void setExternalActionFlags(U32 action_flags); // call this after putting a GameControlInput packet on the wire static void updateResendPeriod(); diff --git a/indra/llwindow/llgamecontroltranslator.cpp b/indra/llwindow/llgamecontroltranslator.cpp new file mode 100644 index 0000000000..c12d9317a7 --- /dev/null +++ b/indra/llwindow/llgamecontroltranslator.cpp @@ -0,0 +1,344 @@ +/** + * @file llgamecontroltranslator.cpp + * @brief LLGameControlTranslator class implementation + * + * $LicenseInfo:firstyear=2023&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2023, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +/* + * App-wide preferences. Note that these are not per-user, + * because we need to load many preferences before we have + * a login name. + */ + +#include "llgamecontroltranslator.h" +#include "llsd.h" + + +using ActionToMaskMap = LLGameControlTranslator::ActionToMaskMap; + +LLGameControlTranslator::LLGameControlTranslator() +{ +} + +void LLGameControlTranslator::setAvailableActions(ActionToMaskMap& action_to_mask) +{ + mActionToMask = std::move(action_to_mask); +} + +LLGameControl::InputChannel LLGameControlTranslator::getChannelByAction(const std::string& action) const +{ + LLGameControl::InputChannel channel; + ActionToMaskMap::const_iterator mask_itr = mActionToMask.find(action); + if (mask_itr != mActionToMask.end()) + { + U32 mask = mask_itr->second; + LLGameControlTranslator::MaskToChannelMap::const_iterator channel_itr = mMaskToChannel.find(mask); + if (channel_itr != mMaskToChannel.end()) + { + channel = channel_itr->second; + } + } + else + { + // It is expected that sometimes 'action' lacks the postfix '+' or '-'. + // When it is missing we append '+' and try again. + std::string action_plus = action; + action_plus.append("+"); + mask_itr = mActionToMask.find(action_plus); + if (mask_itr != mActionToMask.end()) + { + U32 mask = mask_itr->second; + LLGameControlTranslator::MaskToChannelMap::const_iterator channel_itr = mMaskToChannel.find(mask); + if (channel_itr != mMaskToChannel.end()) + { + channel = channel_itr->second; + } + } + } + return channel; +} + +void LLGameControlTranslator::setMappings(LLGameControlTranslator::NamedChannels& list) +{ + mMaskToChannel.clear(); + mMappedFlags = 0; + mPrevActiveFlags = 0; + + for (auto& name_channel : list) + { + updateMap(name_channel.first, name_channel.second); + } +} + +bool LLGameControlTranslator::updateMap(const std::string& name, const LLGameControl::InputChannel& channel) +{ + bool map_changed = false; + size_t name_length = name.length(); + if (name_length > 1) + { + if (channel.isButton()) + { + map_changed = updateMapInternal(name, channel); + } + else if (channel.isAxis()) + { + U8 last_char = name.at(name_length - 1); + if (last_char == '+' || last_char == '=') + { + map_changed = updateMapInternal(name, channel); + } + else + { + // try to map both "name+" and "name-" + std::string new_name = name; + new_name.append("+"); + bool success = updateMapInternal(new_name, channel); + if (success) + { + //new_name.append("-"); + new_name.data()[name_length] = '-'; + LLGameControl::InputChannel other_channel(channel.mType, channel.mIndex, -channel.mSign); + // HACK: this works for XBox and similar controllers, + // and those are pretty much the only supported devices right now + // however TODO: figure out how to do this better. + // + // AXIS_TRIGGERLEFT and AXIS_TRIGGERRIGHT are separate axes and most devices + // only allow them to read positive, not negative. When used for motion control + // they are typically paired together. We assume as much here when computing + // the other_channel. + if (channel.mIndex == LLGameControl::AXIS_TRIGGERLEFT) + { + other_channel.mIndex = LLGameControl::AXIS_TRIGGERRIGHT; + other_channel.mSign = 1; + } + else if (channel.mIndex == LLGameControl::AXIS_TRIGGERRIGHT) + { + other_channel.mIndex = LLGameControl::AXIS_TRIGGERLEFT; + other_channel.mSign = 1; + } + updateMapInternal(new_name, other_channel); + map_changed = true; + } + } + } + else + { + // channel type is NONE, which means the action needs to be removed from the map + // but we don't know if it mapped to button or axis which is important because + // it if it axis then we need to also remove the other entry. + // So we try to look it up + ActionToMaskMap::iterator mask_itr = mActionToMask.find(name); + if (mask_itr != mActionToMask.end()) + { + // we found the action --> was it mapped to an axis? + bool is_axis = false; + U32 mask = mask_itr->second; + LLGameControlTranslator::MaskToChannelMap::iterator channel_itr = mMaskToChannel.find(mask); + if (channel_itr != mMaskToChannel.end()) + { + if (channel_itr->second.isAxis()) + { + // yes, it is an axis + is_axis = true; + } + } + // remove from map, whether button or axis + updateMapInternal(name, channel); + + if (is_axis) + { + // also need to remove the other entry + std::string other_name = name; + if (other_name.data()[name.length() - 1] == '-') + { + other_name.data()[name.length() - 1] = '+'; + } + else + { + other_name.data()[name.length() - 1] = '-'; + } + // remove from map + updateMapInternal(other_name, channel); + } + } + else if (name.data()[name.length() - 1] == '+' + || name.data()[name.length() - 1] == '-') + { + // action was not found but name doesn't end with +/- + // maybe it is an axis-name sans the +/- on the end + // postfix with '+' and try again + std::string other_name = name; + other_name.append("+"); + map_changed = updateMapInternal(other_name, channel); + if (map_changed) + { + // that worked! now do the other one + other_name.data()[name.length()] = '-'; + updateMapInternal(other_name, channel); + } + } + } + } + + if (map_changed) + { + // recompute mMappedFlags + mMappedFlags = 0; + for (auto& pair : mMaskToChannel) + { + mMappedFlags |= pair.first; + } + mPrevActiveFlags = 0; + } + return map_changed; +} + +// Given external action_flags (i.e. raw avatar input) +// compute the corresponding LLGameControl::State that would have produced those flags. +// Note: "action flags" are similar to, but not quite the same as, "control flags". +// "Action flags" are the raw input of avatar movement intent, whereas "control flags" +// are the consequential set of instructions that are sent to the server for moving +// the avatar character. +const LLGameControl::State& LLGameControlTranslator::computeStateFromFlags(U32 action_flags) +{ + static U32 last_action_flags = 0; + if (last_action_flags != action_flags) + { + last_action_flags = action_flags; + } + // translate action_flag bits to equivalent game controller state + // according to data in mMaskToChannel + + // only bother to update mCachedState if active_flags have changed + U32 active_flags = action_flags & mMappedFlags; + //if (active_flags != mPrevActiveFlags) + { + mCachedState.clear(); + for (const auto& pair : mMaskToChannel) + { + U32 mask = pair.first; + if (mask == (mask & action_flags)) + { + LLGameControl::InputChannel channel = pair.second; + if (channel.isAxis()) + { + if (channel.mSign < 0) + { + mCachedState.mAxes[channel.mIndex] = std::numeric_limits<S16>::min(); + } + else + { + mCachedState.mAxes[channel.mIndex] = std::numeric_limits<S16>::max(); + } + } + else if (channel.isButton()) + { + mCachedState.mButtons |= (0x01U << channel.mIndex); + } + } + } + mPrevActiveFlags = active_flags; + } + return mCachedState; +} + +// Given LLGameControl::State (i.e. from a real controller) +// compute corresponding action flags (e.g. for moving the avatar around) +U32 LLGameControlTranslator::computeFlagsFromState(const std::vector<S32>& axes, U32 buttons) +{ + // HACK: supply hard-coded threshold for ON/OFF zones + constexpr S32 AXIS_THRESHOLD = 32768 / 8; + U32 action_flags = 0; + for (const auto& pair : mMaskToChannel) + { + // pair = { mask, channel } + const LLGameControl::InputChannel& channel = pair.second; + if (channel.isAxis()) + { + if (channel.mSign < 0) + { + if (axes[channel.mIndex] < -AXIS_THRESHOLD) + { + action_flags |= pair.first; + } + } + else if (axes[channel.mIndex] > AXIS_THRESHOLD) + { + action_flags |= pair.first; + } + } + else if (channel.isButton()) + { + U32 bit_set = buttons & (0x01U << channel.mIndex); + if (bit_set) + { + action_flags |= pair.first; + } + } + } + return action_flags; +} + +bool LLGameControlTranslator::updateMapInternal(const std::string& name, const LLGameControl::InputChannel& channel) +{ + bool something_changed = false; + ActionToMaskMap::iterator mask_itr = mActionToMask.find(name); + if (mask_itr != mActionToMask.end()) + { + U32 mask = mask_itr->second; + something_changed = addOrRemoveMaskMapping(mask, channel); + } + return something_changed; +} + +bool LLGameControlTranslator::addOrRemoveMaskMapping(U32 mask, const LLGameControl::InputChannel& channel) +{ + bool success = false; + LLGameControlTranslator::MaskToChannelMap::iterator channel_itr = mMaskToChannel.find(mask); + if (channel_itr != mMaskToChannel.end()) + { + LLGameControl::InputChannel old_channel = channel_itr->second; + if (old_channel.mType != channel.mType || old_channel.mIndex != channel.mIndex || old_channel.mSign != channel.mSign) + { + if (channel.isNone()) + { + // remove old mapping + mMaskToChannel.erase(channel_itr); + } + else + { + // update old mapping + channel_itr->second = channel; + } + success = true; + } + } + else if (! channel.isNone()) + { + // create new mapping + mMaskToChannel[mask] = channel; + success = true; + } + return success; +} + diff --git a/indra/llwindow/llgamecontroltranslator.h b/indra/llwindow/llgamecontroltranslator.h new file mode 100644 index 0000000000..13cbf29db2 --- /dev/null +++ b/indra/llwindow/llgamecontroltranslator.h @@ -0,0 +1,83 @@ +/** + * @file llgamecontroltranslator.h + * @brief LLGameControlTranslator class definition + * + * $LicenseInfo:firstyear=2023&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2023, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#pragma once + +#include <map> + +#include "stdtypes.h" +#include "llgamecontrol.h" + + +class LLGameControlTranslator +{ +public: + + using ActionToMaskMap = std::map< std::string, U32 >; // < action : mask > + using MaskToChannelMap = std::map< U32, LLGameControl::InputChannel >; // < mask : channel > + using NamedChannel = std::pair < std::string , LLGameControl::InputChannel >; + using NamedChannels = std::vector< NamedChannel >; + + + LLGameControlTranslator(); + void setAvailableActions(ActionToMaskMap& action_to_mask); + LLGameControl::InputChannel getChannelByAction(const std::string& action) const; + void setMappings(NamedChannels& list); + bool updateMap(const std::string& name, const LLGameControl::InputChannel& channel); + // Note: to remove a mapping you can call updateMap() with a TYPE_NONE channel + + // Given external action_flags (i.e. raw avatar input) + // compute the corresponding LLGameControl::State that would have produced those flags. + // Note: "action flags" are similar to, but not quite the same as, "control flags". + const LLGameControl::State& computeStateFromFlags(U32 action_flags); + + // Given LLGameControl::State (i.e. from a real controller) + // compute corresponding action flags (e.g. for moving the avatar around) + U32 computeFlagsFromState(const std::vector<S32>& axes, U32 buttons); + + U32 getMappedFlags() const { return mMappedFlags; } + +private: + bool updateMapInternal(const std::string& name, const LLGameControl::InputChannel& channel); + bool addOrRemoveMaskMapping(U32 mask, const LLGameControl::InputChannel& channel); + +private: + // mActionToMask is an invarient map between the possible actions + // and the action bit masks. Only actions therein can have their + // bit masks mapped to channels. + ActionToMaskMap mActionToMask; // invariant map after init + + // mMaskToChannel is a dynamic map between action bit masks + // and GameControl channels. + MaskToChannelMap mMaskToChannel; // dynamic map, per preference changes + + // mCachedState is an optimization: + // it is only recomputed when external action_flags change + LLGameControl::State mCachedState; + + U32 mMappedFlags { 0 }; + U32 mPrevActiveFlags { 0 }; +}; diff --git a/indra/llwindow/llkeyboard.cpp b/indra/llwindow/llkeyboard.cpp index a858d3b16f..7784e6c32a 100644 --- a/indra/llwindow/llkeyboard.cpp +++ b/indra/llwindow/llkeyboard.cpp @@ -29,7 +29,6 @@ #include "llkeyboard.h" #include "llwindowcallbacks.h" -#include "llgamecontrol.h" // // Globals @@ -162,7 +161,7 @@ void LLKeyboard::resetKeyDownAndHandle() mCallbacks->handleTranslatedKeyUp(i, mask); } } - LLGameControl::clearAllKeys(); + mCurTranslatedKey = KEY_NONE; } // BUG this has to be called when an OS dialog is shown, otherwise modifier key state diff --git a/indra/llwindow/llkeyboardheadless.cpp b/indra/llwindow/llkeyboardheadless.cpp index ad8e42a412..a827424141 100644 --- a/indra/llwindow/llkeyboardheadless.cpp +++ b/indra/llwindow/llkeyboardheadless.cpp @@ -57,6 +57,7 @@ void LLKeyboardHeadless::scanKeyboard() mCallbacks->handleScanKey(key, mKeyDown[key], mKeyUp[key], mKeyLevel[key]); } } + mCurScanKey = KEY_NONE; // Reset edges for next frame for (S32 key = 0; key < KEY_COUNT; key++) diff --git a/indra/llwindow/llkeyboardmacosx.cpp b/indra/llwindow/llkeyboardmacosx.cpp index f590b8db8b..4ce98ee32b 100644 --- a/indra/llwindow/llkeyboardmacosx.cpp +++ b/indra/llwindow/llkeyboardmacosx.cpp @@ -291,6 +291,7 @@ void LLKeyboardMacOSX::scanKeyboard() mCallbacks->handleScanKey(key, mKeyDown[key], mKeyUp[key], mKeyLevel[key]); } } + mCurScanKey = KEY_NONE; // Reset edges for next frame for (key = 0; key < KEY_COUNT; key++) diff --git a/indra/llwindow/llkeyboardsdl.cpp b/indra/llwindow/llkeyboardsdl.cpp index 543882fc8f..636eaa5491 100644 --- a/indra/llwindow/llkeyboardsdl.cpp +++ b/indra/llwindow/llkeyboardsdl.cpp @@ -300,6 +300,7 @@ void LLKeyboardSDL::scanKeyboard() mCallbacks->handleScanKey(key, mKeyDown[key], mKeyUp[key], mKeyLevel[key]); } } + mCurScanKey = KEY_NONE; // Reset edges for next frame for (S32 key = 0; key < KEY_COUNT; key++) diff --git a/indra/llwindow/llkeyboardwin32.cpp b/indra/llwindow/llkeyboardwin32.cpp index 7ef616a8b7..756caf6fc3 100644 --- a/indra/llwindow/llkeyboardwin32.cpp +++ b/indra/llwindow/llkeyboardwin32.cpp @@ -259,6 +259,7 @@ void LLKeyboardWin32::scanKeyboard() mCallbacks->handleScanKey(key, mKeyDown[key], mKeyUp[key], mKeyLevel[key]); } } + mCurScanKey = KEY_NONE; // Reset edges for next frame for (key = 0; key < KEY_COUNT; key++) |