#include "global.h" #include "InputFilter.h" #include "RageLog.h" #include "RageInput.h" #include "RageUtil.h" #include "RageThreads.h" #include "Preference.h" #include "Foreach.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 the 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(); } InputFilter::~InputFilter() { delete queuemutex; g_ButtonStates.clear(); } 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("%i", di.device) ); ASSERT_M( di.button < NUM_DeviceButton, ssprintf("%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; } /* 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) ); } } /* 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; } /* 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; } /* * (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. */