Files
itgmania212121/stepmania/src/InputFilter.cpp
T
Glenn Maynard a6a1801b58 Store the real DeviceInput inside ButtonState. Store just the identifier
of the button in the map.  Do add unpressed buttons to the button state lists
(since they may have analog state); purge when level == 0.  This way, we
always have access to the current state, even when a button is debounced off.
2006-12-26 23:10:19 +00:00

405 lines
12 KiB
C++

#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 <set>
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<DeviceButtonPair, ButtonState> 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<DeviceInput> 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<float> 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 fDelay, float fRepeatRate )
{
g_fTimeBeforeRepeats = fDelay;
g_fTimeBetweenRepeats = 1/fRepeatRate;
}
void InputFilter::ResetRepeatRate()
{
SetRepeatRate( TIME_BEFORE_REPEATS, REPEATS_PER_SEC );
}
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;
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<DeviceInput> &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<ButtonStateMap::iterator> 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<typename T, typename IT>
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();
}
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<InputEvent> &array )
{
array.clear();
LockMut(*queuemutex);
array.swap( queue );
}
void InputFilter::GetPressedButtons( vector<DeviceInput> &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.
*/