Some optimizations with the scroll segments.
Scroll segments seem to cause the lag when there's a lot of segment in the steps, say, 165. On my Mac, it drops the frame-rate down by about 30 FPS in the end. This is because the way the the game tries to determine the exact position of the note, it has to go through all the scroll segments from the beginning until it finds the currently active scroll segment and keep track of the beat to display instead. And each frame, the game needs to determine the position for each note, and also when figuring out the first beat and last beat to display. This makes the game slow even with just 165 scroll segments. The approach is that at every game update, we create an optimized data structure that can calculate the displayed beat from real song beat in O(log n) time. So in this approach we iterate through EVERY scroll segments each frame instead of iterating through the scroll segments for each note for each frame. This eliminates the lag on this side. The data structure is recomputed each update because I assume that the timing segments can change at any time, but not during draws. A similar problem is: when using Cmods, GetElapsedTimeFromBeat() is also called very often (say, for each note and also in FindFirst/Last DisplayedBeat), we may as well have to make these queries faster for songs that has hundreds of BPM changes or stops or delays to prevent the game from lagging.
This commit is contained in:
+26
-1
@@ -213,6 +213,31 @@ void ArrowEffects::Update()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static float GetDisplayedBeat( const PlayerState* pPlayerState, float beat )
|
||||||
|
{
|
||||||
|
// do a binary search here
|
||||||
|
const vector<CacheDisplayedBeat> &data = pPlayerState->m_CacheDisplayedBeat;
|
||||||
|
int max = data.size() - 1;
|
||||||
|
int l = 0, r = max;
|
||||||
|
while( l <= r )
|
||||||
|
{
|
||||||
|
int m = ( l + r ) / 2;
|
||||||
|
if( ( m == 0 || data[m].beat <= beat ) && ( m == max || beat < data[m + 1].beat ) )
|
||||||
|
{
|
||||||
|
return data[m].displayedBeat + data[m].velocity * (beat - data[m].beat);
|
||||||
|
}
|
||||||
|
else if( data[m].beat <= beat )
|
||||||
|
{
|
||||||
|
l = m + 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
r = m - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return beat;
|
||||||
|
}
|
||||||
|
|
||||||
/* For visibility testing: if bAbsolute is false, random modifiers must return
|
/* For visibility testing: if bAbsolute is false, random modifiers must return
|
||||||
* the minimum possible scroll speed. */
|
* the minimum possible scroll speed. */
|
||||||
float ArrowEffects::GetYOffset( const PlayerState* pPlayerState, int iCol, float fNoteBeat, float &fPeakYOffsetOut, bool &bIsPastPeakOut, bool bAbsolute )
|
float ArrowEffects::GetYOffset( const PlayerState* pPlayerState, int iCol, float fNoteBeat, float &fPeakYOffsetOut, bool &bIsPastPeakOut, bool bAbsolute )
|
||||||
@@ -235,7 +260,7 @@ float ArrowEffects::GetYOffset( const PlayerState* pPlayerState, int iCol, float
|
|||||||
float bShowEffects = !( GAMESTATE->m_bInStepEditor || !GAMESTATE->m_bIsUsingStepTiming );
|
float bShowEffects = !( GAMESTATE->m_bInStepEditor || !GAMESTATE->m_bIsUsingStepTiming );
|
||||||
float fBeatsUntilStep = fNoteBeat - fSongBeat;
|
float fBeatsUntilStep = fNoteBeat - fSongBeat;
|
||||||
if( bShowEffects )
|
if( bShowEffects )
|
||||||
fBeatsUntilStep = pCurSteps->m_Timing.GetDisplayedBeat(fNoteBeat) - pCurSteps->m_Timing.GetDisplayedBeat(fSongBeat);
|
fBeatsUntilStep = GetDisplayedBeat(pPlayerState, fNoteBeat) - GetDisplayedBeat(pPlayerState, fSongBeat);
|
||||||
float fYOffsetBeatSpacing = fBeatsUntilStep;
|
float fYOffsetBeatSpacing = fBeatsUntilStep;
|
||||||
float fSpeedMultiplier = bShowEffects ?
|
float fSpeedMultiplier = bShowEffects ?
|
||||||
pCurSteps->m_Timing.GetDisplayedSpeedPercent(
|
pCurSteps->m_Timing.GetDisplayedSpeedPercent(
|
||||||
|
|||||||
@@ -751,6 +751,27 @@ void Player::SendComboMessages( int iOldCombo, int iOldMissCombo )
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void GenerateCacheDataStructure(PlayerState *pPlayerState, NoteData ¬es) {
|
||||||
|
|
||||||
|
pPlayerState->m_CacheDisplayedBeat.clear();
|
||||||
|
|
||||||
|
const vector<TimingSegment *> *segs = pPlayerState->GetDisplayedTiming().m_avpTimingSegments;
|
||||||
|
|
||||||
|
float displayedBeat = 0.0f;
|
||||||
|
float lastRealBeat = 0.0f;
|
||||||
|
float lastRatio = 1.0f;
|
||||||
|
for ( unsigned i = 0; i < segs[SEGMENT_SCROLL].size(); i++ )
|
||||||
|
{
|
||||||
|
ScrollSegment *seg = static_cast<ScrollSegment *>(segs[SEGMENT_SCROLL][i]);
|
||||||
|
displayedBeat += ( seg->GetBeat() - lastRealBeat ) * lastRatio;
|
||||||
|
lastRealBeat = seg->GetBeat();
|
||||||
|
lastRatio = seg->GetRatio();
|
||||||
|
CacheDisplayedBeat c = { seg->GetBeat(), displayedBeat, seg->GetRatio() };
|
||||||
|
pPlayerState->m_CacheDisplayedBeat.push_back( c );
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
void Player::Update( float fDeltaTime )
|
void Player::Update( float fDeltaTime )
|
||||||
{
|
{
|
||||||
const RageTimer now;
|
const RageTimer now;
|
||||||
@@ -880,6 +901,9 @@ void Player::Update( float fDeltaTime )
|
|||||||
if( m_bPaused )
|
if( m_bPaused )
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
// Generate some cache data structure.
|
||||||
|
GenerateCacheDataStructure(m_pPlayerState, m_NoteData);
|
||||||
|
|
||||||
// Check for a strum miss
|
// Check for a strum miss
|
||||||
if( m_pPlayerState->m_fLastStrumMusicSeconds != -1 &&
|
if( m_pPlayerState->m_fLastStrumMusicSeconds != -1 &&
|
||||||
m_pPlayerState->m_fLastStrumMusicSeconds + g_fTimingWindowStrum < m_pPlayerState->m_Position.m_fMusicSeconds )
|
m_pPlayerState->m_fLastStrumMusicSeconds + g_fTimingWindowStrum < m_pPlayerState->m_Position.m_fMusicSeconds )
|
||||||
|
|||||||
@@ -12,6 +12,12 @@
|
|||||||
#include "SampleHistory.h"
|
#include "SampleHistory.h"
|
||||||
struct lua_State;
|
struct lua_State;
|
||||||
|
|
||||||
|
struct CacheDisplayedBeat {
|
||||||
|
float beat;
|
||||||
|
float displayedBeat;
|
||||||
|
float velocity;
|
||||||
|
};
|
||||||
|
|
||||||
/** @brief The player's indivdual state. */
|
/** @brief The player's indivdual state. */
|
||||||
class PlayerState
|
class PlayerState
|
||||||
{
|
{
|
||||||
@@ -45,6 +51,16 @@ public:
|
|||||||
const SongPosition &GetDisplayedPosition() const;
|
const SongPosition &GetDisplayedPosition() const;
|
||||||
const TimingData &GetDisplayedTiming() const;
|
const TimingData &GetDisplayedTiming() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Holds a vector sorted by real beat, the beat that would be displayed
|
||||||
|
* in the NoteField (because they are affected by scroll segments), and
|
||||||
|
* also the velocity.
|
||||||
|
* This vector will be populated each frame by Player and will probably
|
||||||
|
* be used a lot in ArrowEffects. This is way better than iterating through
|
||||||
|
* all scroll segments per tap note per frame, which is very slow!
|
||||||
|
*/
|
||||||
|
vector<CacheDisplayedBeat> m_CacheDisplayedBeat;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Change the PlayerOptions to their default.
|
* @brief Change the PlayerOptions to their default.
|
||||||
* @param l the level of mods to reset.
|
* @param l the level of mods to reset.
|
||||||
|
|||||||
Reference in New Issue
Block a user