From 0e91e5b32d559b2933ea9b77ba3d3a3edf4e020b Mon Sep 17 00:00:00 2001 From: Jason Felds Date: Sat, 27 Apr 2013 15:14:08 -0400 Subject: [PATCH] Another map to for each WHITE SPACE. --- src/InputFilter.cpp | 974 ++++++++++++++++++++++---------------------- 1 file changed, 487 insertions(+), 487 deletions(-) diff --git a/src/InputFilter.cpp b/src/InputFilter.cpp index bec28d0dcb..2149bfb005 100644 --- a/src/InputFilter.cpp +++ b/src/InputFilter.cpp @@ -1,487 +1,487 @@ -#include "global.h" -#include "InputFilter.h" -#include "RageLog.h" -#include "RageInput.h" -#include "RageUtil.h" -#include "RageThreads.h" -#include "Preference.h" -#include "Foreach.h" -// for mouse stuff: -aj -#include "PrefsManager.h" -#include "ScreenDimensions.h" - -#include -struct ButtonState -{ - ButtonState(); - bool m_BeingHeld; // actual current state - bool m_bLastReportedHeld; // last state reported by Update() - RString m_sComment; - float m_fSecsHeld; - DeviceInput m_DeviceInput; - - // Timestamp of m_BeingHeld changing. - RageTimer m_BeingHeldTime; - - // The time that we actually reported the last event (used for debouncing). - RageTimer m_LastReportTime; - - // The timestamp of the last reported change. Unlike m_BeingHeldTime, this - // value is debounced along with the input state. (This is the same as - // m_fSecsHeld, except this isn't affected by Update scaling.) - RageTimer m_LastInputTime; -}; - -struct DeviceButtonPair -{ - InputDevice device; - DeviceButton button; - DeviceButtonPair( InputDevice d, DeviceButton b ): device(d), button(b){ } - bool operator<( const DeviceButtonPair &other ) const - { - if( device != other.device ) - return device < other.device; - return button < other.button; - } -}; - -namespace -{ - /* Maintain a set of all interesting buttons: buttons which are being held - * down, or which were held down and need a RELEASE event. We use this to - * optimize InputFilter::Update, so we don't have to process every button - * we know about when most of them aren't in use. This set is protected - * by queuemutex. */ - typedef map ButtonStateMap; - ButtonStateMap g_ButtonStates; - ButtonState &GetButtonState( const DeviceInput &di ) - { - DeviceButtonPair db(di.device, di.button); - ButtonState &bs = g_ButtonStates[db]; - bs.m_DeviceInput.button = di.button; - bs.m_DeviceInput.device = di.device; - return bs; - } - - DeviceInputList g_CurrentState; - set g_DisableRepeat; -} - -/* Some input devices require debouncing. Do this on both press and release. - * After reporting a change in state, don't report another for the debounce - * period. If a button is reported pressed, report it. If the button is - * immediately reported released, wait a period before reporting it; if the - * button is repressed during that time, the release is never reported. - * The detail is important: if a button is pressed for 1ms and released, we must - * always report it, even if the debounce period is 10ms, since it might be a - * coin counter with a very short signal. The only time we discard events is if - * a button is pressed, released and then pressed again quickly. - * - * This delay in events is ordinarily not noticable, because we report initial - * presses and releases immediately. However, if a real press is ever delayed, - * this won't cause timing problems, because the event timestamp is preserved. */ -static Preference g_fInputDebounceTime( "InputDebounceTime", 0 ); - -InputFilter* INPUTFILTER = NULL; // global and accessable from anywhere in our program - -static const float TIME_BEFORE_REPEATS = 0.375f; - -static const float REPEATS_PER_SEC = 8; - -static float g_fTimeBeforeRepeats, g_fTimeBetweenRepeats; - - -InputFilter::InputFilter() -{ - queuemutex = new RageMutex("InputFilter"); - - Reset(); - ResetRepeatRate(); - - m_MouseCoords.fX = 0; - m_MouseCoords.fY = 0; - m_MouseCoords.fZ = 0; - - // Register with Lua. - { - Lua *L = LUA->Get(); - lua_pushstring( L, "INPUTFILTER" ); - this->PushSelf( L ); - lua_settable( L, LUA_GLOBALSINDEX ); - LUA->Release( L ); - } -} - -InputFilter::~InputFilter() -{ - delete queuemutex; - g_ButtonStates.clear(); - // Unregister with Lua. - LUA->UnsetGlobal( "INPUTFILTER" ); -} - -void InputFilter::Reset() -{ - FOREACH_InputDevice( i ) - ResetDevice( InputDevice(i) ); -} - -void InputFilter::SetRepeatRate( float fRepeatRate ) -{ - g_fTimeBetweenRepeats = 1/fRepeatRate; -} - -void InputFilter::SetRepeatDelay( float fDelay ) -{ - g_fTimeBeforeRepeats = fDelay; -} - -void InputFilter::ResetRepeatRate() -{ - SetRepeatRate( REPEATS_PER_SEC ); - SetRepeatDelay( TIME_BEFORE_REPEATS ); -} - -ButtonState::ButtonState(): - m_BeingHeldTime(RageZeroTimer), - m_LastReportTime(RageZeroTimer) -{ - m_BeingHeld = false; - m_bLastReportedHeld = false; - m_fSecsHeld = 0; -} - -void InputFilter::ButtonPressed( const DeviceInput &di ) -{ - LockMut(*queuemutex); - - if( di.ts.IsZero() ) - LOG->Warn( "InputFilter::ButtonPressed: zero timestamp is invalid" ); - - ASSERT_M( di.device < NUM_InputDevice, ssprintf("Invalid device %i", di.device) ); - ASSERT_M( di.button < NUM_DeviceButton, ssprintf("Invalid button %i", di.button) ); - - ButtonState &bs = GetButtonState( di ); - - // Flush any delayed input, like Update() (in case Update() isn't being called). - RageTimer now; - CheckButtonChange( bs, di, now ); - - bs.m_DeviceInput = di; - - bool Down = di.bDown; - if( bs.m_BeingHeld != Down ) - { - bs.m_BeingHeld = Down; - bs.m_BeingHeldTime = di.ts; - } - - // Try to report presses immediately. - MakeButtonStateList( g_CurrentState ); - CheckButtonChange( bs, di, now ); -} - -void InputFilter::SetButtonComment( const DeviceInput &di, const RString &sComment ) -{ - LockMut(*queuemutex); - ButtonState &bs = GetButtonState( di ); - bs.m_sComment = sComment; -} - -/** @brief Release all buttons on the given device. */ -void InputFilter::ResetDevice( InputDevice device ) -{ - LockMut(*queuemutex); - RageTimer now; - - const ButtonStateMap ButtonStates( g_ButtonStates ); - FOREACHM_CONST( DeviceButtonPair, ButtonState, ButtonStates, b ) - { - const DeviceButtonPair &db = b->first; - if( db.device == device ) - ButtonPressed( DeviceInput(device, db.button, 0, now) ); - } -} - -/** @brief Check for reportable presses. */ -void InputFilter::CheckButtonChange( ButtonState &bs, DeviceInput di, const RageTimer &now ) -{ - if( bs.m_BeingHeld == bs.m_bLastReportedHeld ) - return; - - /* If the last IET_FIRST_PRESS or IET_RELEASE event was sent too recently, - * wait a while before sending it. */ - if( now - bs.m_LastReportTime < g_fInputDebounceTime ) - return; - - bs.m_LastReportTime = now; - bs.m_bLastReportedHeld = bs.m_BeingHeld; - bs.m_fSecsHeld = 0; - bs.m_LastInputTime = bs.m_BeingHeldTime; - - di.ts = bs.m_BeingHeldTime; - if( !bs.m_bLastReportedHeld ) - di.level = 0; - - MakeButtonStateList( g_CurrentState ); - ReportButtonChange( di, bs.m_bLastReportedHeld? IET_FIRST_PRESS:IET_RELEASE ); - - if( !bs.m_bLastReportedHeld ) - g_DisableRepeat.erase( di ); -} - -void InputFilter::ReportButtonChange( const DeviceInput &di, InputEventType t ) -{ - queue.push_back( InputEvent() ); - InputEvent &ie = queue.back(); - ie.type = t; - ie.di = di; - - /* Include a list of all buttons that were pressed at the time of this event. - * We can create this efficiently using g_ButtonStates. Use a vector and not - * a map, for efficiency; most code will not use this information. Iterating - * over g_ButtonStates will be in DeviceInput order, so users can binary - * search this list (eg. std::lower_bound). */ - ie.m_ButtonState = g_CurrentState; -} - -void InputFilter::MakeButtonStateList( vector &aInputOut ) const -{ - aInputOut.clear(); - aInputOut.reserve( g_ButtonStates.size() ); - for( ButtonStateMap::const_iterator it = g_ButtonStates.begin(); it != g_ButtonStates.end(); ++it ) - { - const ButtonState &bs = it->second; - aInputOut.push_back( bs.m_DeviceInput ); - aInputOut.back().ts = bs.m_LastInputTime; - aInputOut.back().bDown = bs.m_bLastReportedHeld; - } -} - -void InputFilter::Update( float fDeltaTime ) -{ - RageTimer now; - - INPUTMAN->Update(); - - /* Make sure that nothing gets inserted while we do this, to prevent things - * like "key pressed, key release, key repeat". */ - LockMut(*queuemutex); - - DeviceInput di( InputDevice_Invalid, DeviceButton_Invalid, 1.0f, now ); - - MakeButtonStateList( g_CurrentState ); - - vector ButtonsToErase; - - FOREACHM( DeviceButtonPair, ButtonState, g_ButtonStates, b ) - { - di.device = b->first.device; - di.button = b->first.button; - ButtonState &bs = b->second; - - // Generate IET_FIRST_PRESS and IET_RELEASE events that were delayed. - CheckButtonChange( bs, di, now ); - - // Generate IET_REPEAT events. - if( !bs.m_bLastReportedHeld ) - { - // If the key isn't pressed, and hasn't been pressed for a while - // (so debouncing isn't interested in it), purge the entry. - if( now - bs.m_LastReportTime > g_fInputDebounceTime && - bs.m_DeviceInput.level == 0.0f ) - ButtonsToErase.push_back( b ); - continue; - } - - // If repeats are disabled for this button, skip. - if( g_DisableRepeat.find(di) != g_DisableRepeat.end() ) - continue; - - const float fOldHoldTime = bs.m_fSecsHeld; - bs.m_fSecsHeld += fDeltaTime; - const float fNewHoldTime = bs.m_fSecsHeld; - - if( fNewHoldTime <= g_fTimeBeforeRepeats ) - continue; - - float fRepeatTime; - if( fOldHoldTime < g_fTimeBeforeRepeats ) - { - fRepeatTime = g_fTimeBeforeRepeats; - } - else - { - float fAdjustedOldHoldTime = fOldHoldTime - g_fTimeBeforeRepeats; - float fAdjustedNewHoldTime = fNewHoldTime - g_fTimeBeforeRepeats; - if( int(fAdjustedOldHoldTime/g_fTimeBetweenRepeats) == int(fAdjustedNewHoldTime/g_fTimeBetweenRepeats) ) - continue; - fRepeatTime = ftruncf( fNewHoldTime, g_fTimeBetweenRepeats ); - } - - /* Set the timestamp to the exact time of the repeat. This way, as long - * as tab/` aren't being used, the timestamp will always increase steadily - * during repeats. */ - di.ts = bs.m_LastInputTime + fRepeatTime; - - ReportButtonChange( di, IET_REPEAT ); - } - - FOREACH( ButtonStateMap::iterator, ButtonsToErase, it ) - g_ButtonStates.erase( *it ); -} - -template -const T *FindItemBinarySearch( IT begin, IT end, const T &i ) -{ - IT it = lower_bound( begin, end, i ); - if( it == end || *it != i ) - return NULL; - - return &*it; -} - -bool InputFilter::IsBeingPressed( const DeviceInput &di, const DeviceInputList *pButtonState ) const -{ - LockMut(*queuemutex); - if( pButtonState == NULL ) - pButtonState = &g_CurrentState; - const DeviceInput *pDI = FindItemBinarySearch( pButtonState->begin(), pButtonState->end(), di ); - return pDI != NULL && pDI->bDown; -} - -float InputFilter::GetSecsHeld( const DeviceInput &di, const DeviceInputList *pButtonState ) const -{ - LockMut(*queuemutex); - if( pButtonState == NULL ) - pButtonState = &g_CurrentState; - const DeviceInput *pDI = FindItemBinarySearch( pButtonState->begin(), pButtonState->end(), di ); - if( pDI == NULL ) - return 0; - return pDI->ts.Ago(); -} - -float InputFilter::GetLevel( const DeviceInput &di, const DeviceInputList *pButtonState ) const -{ - LockMut(*queuemutex); - if( pButtonState == NULL ) - pButtonState = &g_CurrentState; - const DeviceInput *pDI = FindItemBinarySearch( pButtonState->begin(), pButtonState->end(), di ); - if( pDI == NULL ) - return 0.0f; - return pDI->level; -} - -RString InputFilter::GetButtonComment( const DeviceInput &di ) const -{ - LockMut(*queuemutex); - return GetButtonState( di ).m_sComment; -} - -void InputFilter::ResetKeyRepeat( const DeviceInput &di ) -{ - LockMut(*queuemutex); - GetButtonState( di ).m_fSecsHeld = 0; -} - -/** @brief Stop repeating the specified key until released. */ -void InputFilter::RepeatStopKey( const DeviceInput &di ) -{ - LockMut(*queuemutex); - - // If the button is up, do nothing. - ButtonState &bs = GetButtonState( di ); - if( !bs.m_bLastReportedHeld ) - return; - - g_DisableRepeat.insert( di ); -} - -void InputFilter::GetInputEvents( vector &array ) -{ - array.clear(); - LockMut(*queuemutex); - array.swap( queue ); -} - -void InputFilter::GetPressedButtons( vector &array ) const -{ - LockMut(*queuemutex); - array = g_CurrentState; -} - -void InputFilter::UpdateCursorLocation(float _fX, float _fY) -{ - m_MouseCoords.fX = _fX; - m_MouseCoords.fY = _fY; -} - -void InputFilter::UpdateMouseWheel(float _fZ) -{ - m_MouseCoords.fZ = _fZ; -} - -// lua start -#include "LuaBinding.h" - -/** @brief Allow Lua to have access to InputFilter. */ -class LunaInputFilter: public Luna -{ -public: - // todo: Should the input be locked to the theme's width/height instead of - // the window's width/height? -aj - static int GetMouseX( T* p, lua_State *L ){ - float fX = p->GetCursorX(); - // Scale input to the theme's dimensions - fX = SCALE( fX, 0, (PREFSMAN->m_iDisplayHeight * PREFSMAN->m_fDisplayAspectRatio), SCREEN_LEFT, SCREEN_RIGHT ); - lua_pushnumber( L, fX ); - return 1; - } - static int GetMouseY( T* p, lua_State *L ){ - float fY = p->GetCursorY(); - // Scale input to the theme's dimensions - fY = SCALE( fY, 0, PREFSMAN->m_iDisplayHeight, SCREEN_TOP, SCREEN_BOTTOM ); - lua_pushnumber( L, fY ); - return 1; - } - static int GetMouseWheel( T* p, lua_State *L ){ - float fZ = p->GetMouseWheel(); - lua_pushnumber( L, fZ ); - return 1; - } - - LunaInputFilter() - { - ADD_METHOD( GetMouseX ); - ADD_METHOD( GetMouseY ); - ADD_METHOD( GetMouseWheel ); - } -}; - -LUA_REGISTER_CLASS( InputFilter ) -// lua end - -/* - * (c) 2001-2004 Chris Danford - * All rights reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, and/or sell copies of the Software, and to permit persons to - * whom the Software is furnished to do so, provided that the above - * copyright notice(s) and this permission notice appear in all copies of - * the Software and that both the above copyright notice(s) and this - * permission notice appear in supporting documentation. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF - * THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS - * INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT - * OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS - * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR - * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR - * PERFORMANCE OF THIS SOFTWARE. - */ +#include "global.h" +#include "InputFilter.h" +#include "RageLog.h" +#include "RageInput.h" +#include "RageUtil.h" +#include "RageThreads.h" +#include "Preference.h" +#include "Foreach.h" +// for mouse stuff: -aj +#include "PrefsManager.h" +#include "ScreenDimensions.h" + +#include +struct ButtonState +{ + ButtonState(); + bool m_BeingHeld; // actual current state + bool m_bLastReportedHeld; // last state reported by Update() + RString m_sComment; + float m_fSecsHeld; + DeviceInput m_DeviceInput; + + // Timestamp of m_BeingHeld changing. + RageTimer m_BeingHeldTime; + + // The time that we actually reported the last event (used for debouncing). + RageTimer m_LastReportTime; + + // The timestamp of the last reported change. Unlike m_BeingHeldTime, this + // value is debounced along with the input state. (This is the same as + // m_fSecsHeld, except this isn't affected by Update scaling.) + RageTimer m_LastInputTime; +}; + +struct DeviceButtonPair +{ + InputDevice device; + DeviceButton button; + DeviceButtonPair( InputDevice d, DeviceButton b ): device(d), button(b){ } + bool operator<( const DeviceButtonPair &other ) const + { + if( device != other.device ) + return device < other.device; + return button < other.button; + } +}; + +namespace +{ + /* Maintain a set of all interesting buttons: buttons which are being held + * down, or which were held down and need a RELEASE event. We use this to + * optimize InputFilter::Update, so we don't have to process every button + * we know about when most of them aren't in use. This set is protected + * by queuemutex. */ + typedef map ButtonStateMap; + ButtonStateMap g_ButtonStates; + ButtonState &GetButtonState( const DeviceInput &di ) + { + DeviceButtonPair db(di.device, di.button); + ButtonState &bs = g_ButtonStates[db]; + bs.m_DeviceInput.button = di.button; + bs.m_DeviceInput.device = di.device; + return bs; + } + + DeviceInputList g_CurrentState; + set g_DisableRepeat; +} + +/* Some input devices require debouncing. Do this on both press and release. + * After reporting a change in state, don't report another for the debounce + * period. If a button is reported pressed, report it. If the button is + * immediately reported released, wait a period before reporting it; if the + * button is repressed during that time, the release is never reported. + * The detail is important: if a button is pressed for 1ms and released, we must + * always report it, even if the debounce period is 10ms, since it might be a + * coin counter with a very short signal. The only time we discard events is if + * a button is pressed, released and then pressed again quickly. + * + * This delay in events is ordinarily not noticable, because we report initial + * presses and releases immediately. However, if a real press is ever delayed, + * this won't cause timing problems, because the event timestamp is preserved. */ +static Preference g_fInputDebounceTime( "InputDebounceTime", 0 ); + +InputFilter* INPUTFILTER = NULL; // global and accessable from anywhere in our program + +static const float TIME_BEFORE_REPEATS = 0.375f; + +static const float REPEATS_PER_SEC = 8; + +static float g_fTimeBeforeRepeats, g_fTimeBetweenRepeats; + + +InputFilter::InputFilter() +{ + queuemutex = new RageMutex("InputFilter"); + + Reset(); + ResetRepeatRate(); + + m_MouseCoords.fX = 0; + m_MouseCoords.fY = 0; + m_MouseCoords.fZ = 0; + + // Register with Lua. + { + Lua *L = LUA->Get(); + lua_pushstring( L, "INPUTFILTER" ); + this->PushSelf( L ); + lua_settable( L, LUA_GLOBALSINDEX ); + LUA->Release( L ); + } +} + +InputFilter::~InputFilter() +{ + delete queuemutex; + g_ButtonStates.clear(); + // Unregister with Lua. + LUA->UnsetGlobal( "INPUTFILTER" ); +} + +void InputFilter::Reset() +{ + FOREACH_InputDevice( i ) + ResetDevice( InputDevice(i) ); +} + +void InputFilter::SetRepeatRate( float fRepeatRate ) +{ + g_fTimeBetweenRepeats = 1/fRepeatRate; +} + +void InputFilter::SetRepeatDelay( float fDelay ) +{ + g_fTimeBeforeRepeats = fDelay; +} + +void InputFilter::ResetRepeatRate() +{ + SetRepeatRate( REPEATS_PER_SEC ); + SetRepeatDelay( TIME_BEFORE_REPEATS ); +} + +ButtonState::ButtonState(): + m_BeingHeldTime(RageZeroTimer), + m_LastReportTime(RageZeroTimer) +{ + m_BeingHeld = false; + m_bLastReportedHeld = false; + m_fSecsHeld = 0; +} + +void InputFilter::ButtonPressed( const DeviceInput &di ) +{ + LockMut(*queuemutex); + + if( di.ts.IsZero() ) + LOG->Warn( "InputFilter::ButtonPressed: zero timestamp is invalid" ); + + ASSERT_M( di.device < NUM_InputDevice, ssprintf("Invalid device %i", di.device) ); + ASSERT_M( di.button < NUM_DeviceButton, ssprintf("Invalid button %i", di.button) ); + + ButtonState &bs = GetButtonState( di ); + + // Flush any delayed input, like Update() (in case Update() isn't being called). + RageTimer now; + CheckButtonChange( bs, di, now ); + + bs.m_DeviceInput = di; + + bool Down = di.bDown; + if( bs.m_BeingHeld != Down ) + { + bs.m_BeingHeld = Down; + bs.m_BeingHeldTime = di.ts; + } + + // Try to report presses immediately. + MakeButtonStateList( g_CurrentState ); + CheckButtonChange( bs, di, now ); +} + +void InputFilter::SetButtonComment( const DeviceInput &di, const RString &sComment ) +{ + LockMut(*queuemutex); + ButtonState &bs = GetButtonState( di ); + bs.m_sComment = sComment; +} + +/** @brief Release all buttons on the given device. */ +void InputFilter::ResetDevice( InputDevice device ) +{ + LockMut(*queuemutex); + RageTimer now; + + const ButtonStateMap ButtonStates( g_ButtonStates ); + for (std::pair const &b : ButtonStates) + { + const DeviceButtonPair &db = b.first; + if( db.device == device ) + ButtonPressed( DeviceInput(device, db.button, 0, now) ); + } +} + +/** @brief Check for reportable presses. */ +void InputFilter::CheckButtonChange( ButtonState &bs, DeviceInput di, const RageTimer &now ) +{ + if( bs.m_BeingHeld == bs.m_bLastReportedHeld ) + return; + + /* If the last IET_FIRST_PRESS or IET_RELEASE event was sent too recently, + * wait a while before sending it. */ + if( now - bs.m_LastReportTime < g_fInputDebounceTime ) + return; + + bs.m_LastReportTime = now; + bs.m_bLastReportedHeld = bs.m_BeingHeld; + bs.m_fSecsHeld = 0; + bs.m_LastInputTime = bs.m_BeingHeldTime; + + di.ts = bs.m_BeingHeldTime; + if( !bs.m_bLastReportedHeld ) + di.level = 0; + + MakeButtonStateList( g_CurrentState ); + ReportButtonChange( di, bs.m_bLastReportedHeld? IET_FIRST_PRESS:IET_RELEASE ); + + if( !bs.m_bLastReportedHeld ) + g_DisableRepeat.erase( di ); +} + +void InputFilter::ReportButtonChange( const DeviceInput &di, InputEventType t ) +{ + queue.push_back( InputEvent() ); + InputEvent &ie = queue.back(); + ie.type = t; + ie.di = di; + + /* Include a list of all buttons that were pressed at the time of this event. + * We can create this efficiently using g_ButtonStates. Use a vector and not + * a map, for efficiency; most code will not use this information. Iterating + * over g_ButtonStates will be in DeviceInput order, so users can binary + * search this list (eg. std::lower_bound). */ + ie.m_ButtonState = g_CurrentState; +} + +void InputFilter::MakeButtonStateList( vector &aInputOut ) const +{ + aInputOut.clear(); + aInputOut.reserve( g_ButtonStates.size() ); + for( ButtonStateMap::const_iterator it = g_ButtonStates.begin(); it != g_ButtonStates.end(); ++it ) + { + const ButtonState &bs = it->second; + aInputOut.push_back( bs.m_DeviceInput ); + aInputOut.back().ts = bs.m_LastInputTime; + aInputOut.back().bDown = bs.m_bLastReportedHeld; + } +} + +void InputFilter::Update( float fDeltaTime ) +{ + RageTimer now; + + INPUTMAN->Update(); + + /* Make sure that nothing gets inserted while we do this, to prevent things + * like "key pressed, key release, key repeat". */ + LockMut(*queuemutex); + + DeviceInput di( InputDevice_Invalid, DeviceButton_Invalid, 1.0f, now ); + + MakeButtonStateList( g_CurrentState ); + + vector ButtonsToErase; + + FOREACHM( DeviceButtonPair, ButtonState, g_ButtonStates, b ) + { + di.device = b->first.device; + di.button = b->first.button; + ButtonState &bs = b->second; + + // Generate IET_FIRST_PRESS and IET_RELEASE events that were delayed. + CheckButtonChange( bs, di, now ); + + // Generate IET_REPEAT events. + if( !bs.m_bLastReportedHeld ) + { + // If the key isn't pressed, and hasn't been pressed for a while + // (so debouncing isn't interested in it), purge the entry. + if( now - bs.m_LastReportTime > g_fInputDebounceTime && + bs.m_DeviceInput.level == 0.0f ) + ButtonsToErase.push_back( b ); + continue; + } + + // If repeats are disabled for this button, skip. + if( g_DisableRepeat.find(di) != g_DisableRepeat.end() ) + continue; + + const float fOldHoldTime = bs.m_fSecsHeld; + bs.m_fSecsHeld += fDeltaTime; + const float fNewHoldTime = bs.m_fSecsHeld; + + if( fNewHoldTime <= g_fTimeBeforeRepeats ) + continue; + + float fRepeatTime; + if( fOldHoldTime < g_fTimeBeforeRepeats ) + { + fRepeatTime = g_fTimeBeforeRepeats; + } + else + { + float fAdjustedOldHoldTime = fOldHoldTime - g_fTimeBeforeRepeats; + float fAdjustedNewHoldTime = fNewHoldTime - g_fTimeBeforeRepeats; + if( int(fAdjustedOldHoldTime/g_fTimeBetweenRepeats) == int(fAdjustedNewHoldTime/g_fTimeBetweenRepeats) ) + continue; + fRepeatTime = ftruncf( fNewHoldTime, g_fTimeBetweenRepeats ); + } + + /* Set the timestamp to the exact time of the repeat. This way, as long + * as tab/` aren't being used, the timestamp will always increase steadily + * during repeats. */ + di.ts = bs.m_LastInputTime + fRepeatTime; + + ReportButtonChange( di, IET_REPEAT ); + } + + FOREACH( ButtonStateMap::iterator, ButtonsToErase, it ) + g_ButtonStates.erase( *it ); +} + +template +const T *FindItemBinarySearch( IT begin, IT end, const T &i ) +{ + IT it = lower_bound( begin, end, i ); + if( it == end || *it != i ) + return NULL; + + return &*it; +} + +bool InputFilter::IsBeingPressed( const DeviceInput &di, const DeviceInputList *pButtonState ) const +{ + LockMut(*queuemutex); + if( pButtonState == NULL ) + pButtonState = &g_CurrentState; + const DeviceInput *pDI = FindItemBinarySearch( pButtonState->begin(), pButtonState->end(), di ); + return pDI != NULL && pDI->bDown; +} + +float InputFilter::GetSecsHeld( const DeviceInput &di, const DeviceInputList *pButtonState ) const +{ + LockMut(*queuemutex); + if( pButtonState == NULL ) + pButtonState = &g_CurrentState; + const DeviceInput *pDI = FindItemBinarySearch( pButtonState->begin(), pButtonState->end(), di ); + if( pDI == NULL ) + return 0; + return pDI->ts.Ago(); +} + +float InputFilter::GetLevel( const DeviceInput &di, const DeviceInputList *pButtonState ) const +{ + LockMut(*queuemutex); + if( pButtonState == NULL ) + pButtonState = &g_CurrentState; + const DeviceInput *pDI = FindItemBinarySearch( pButtonState->begin(), pButtonState->end(), di ); + if( pDI == NULL ) + return 0.0f; + return pDI->level; +} + +RString InputFilter::GetButtonComment( const DeviceInput &di ) const +{ + LockMut(*queuemutex); + return GetButtonState( di ).m_sComment; +} + +void InputFilter::ResetKeyRepeat( const DeviceInput &di ) +{ + LockMut(*queuemutex); + GetButtonState( di ).m_fSecsHeld = 0; +} + +/** @brief Stop repeating the specified key until released. */ +void InputFilter::RepeatStopKey( const DeviceInput &di ) +{ + LockMut(*queuemutex); + + // If the button is up, do nothing. + ButtonState &bs = GetButtonState( di ); + if( !bs.m_bLastReportedHeld ) + return; + + g_DisableRepeat.insert( di ); +} + +void InputFilter::GetInputEvents( vector &array ) +{ + array.clear(); + LockMut(*queuemutex); + array.swap( queue ); +} + +void InputFilter::GetPressedButtons( vector &array ) const +{ + LockMut(*queuemutex); + array = g_CurrentState; +} + +void InputFilter::UpdateCursorLocation(float _fX, float _fY) +{ + m_MouseCoords.fX = _fX; + m_MouseCoords.fY = _fY; +} + +void InputFilter::UpdateMouseWheel(float _fZ) +{ + m_MouseCoords.fZ = _fZ; +} + +// lua start +#include "LuaBinding.h" + +/** @brief Allow Lua to have access to InputFilter. */ +class LunaInputFilter: public Luna +{ +public: + // todo: Should the input be locked to the theme's width/height instead of + // the window's width/height? -aj + static int GetMouseX( T* p, lua_State *L ){ + float fX = p->GetCursorX(); + // Scale input to the theme's dimensions + fX = SCALE( fX, 0, (PREFSMAN->m_iDisplayHeight * PREFSMAN->m_fDisplayAspectRatio), SCREEN_LEFT, SCREEN_RIGHT ); + lua_pushnumber( L, fX ); + return 1; + } + static int GetMouseY( T* p, lua_State *L ){ + float fY = p->GetCursorY(); + // Scale input to the theme's dimensions + fY = SCALE( fY, 0, PREFSMAN->m_iDisplayHeight, SCREEN_TOP, SCREEN_BOTTOM ); + lua_pushnumber( L, fY ); + return 1; + } + static int GetMouseWheel( T* p, lua_State *L ){ + float fZ = p->GetMouseWheel(); + lua_pushnumber( L, fZ ); + return 1; + } + + LunaInputFilter() + { + ADD_METHOD( GetMouseX ); + ADD_METHOD( GetMouseY ); + ADD_METHOD( GetMouseWheel ); + } +}; + +LUA_REGISTER_CLASS( InputFilter ) +// lua end + +/* + * (c) 2001-2004 Chris Danford + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, and/or sell copies of the Software, and to permit persons to + * whom the Software is furnished to do so, provided that the above + * copyright notice(s) and this permission notice appear in all copies of + * the Software and that both the above copyright notice(s) and this + * permission notice appear in supporting documentation. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF + * THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS + * INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT + * OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS + * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */