Added lookup table system to TimingData so that GetBeatAndBPS and GetElapsedTime don't have to walk through the entire list of timing segments several times every frame during gameplay.

This commit is contained in:
Kyzentun
2015-03-14 23:17:35 -06:00
parent 0d309e633d
commit 2b7ebb3729
7 changed files with 410 additions and 237 deletions
+4 -5
View File
@@ -712,16 +712,15 @@ void BackgroundImpl::Layer::UpdateCurBGChange( const Song *pSong, float fLastMus
if( m_aBGChanges.size() == 0 ) if( m_aBGChanges.size() == 0 )
return; return;
float fBeat, fBPS, fThrowAway; TimingData::GetBeatArgs beat_info;
bool bFreeze; beat_info.elapsed_time= fCurrentTime;
int iThrowAway; pSong->m_SongTiming.GetBeatAndBPSFromElapsedTime(beat_info);
pSong->m_SongTiming.GetBeatAndBPSFromElapsedTime( fCurrentTime, fBeat, fBPS, bFreeze, bFreeze, iThrowAway, fThrowAway );
// Calls to Update() should *not* be scaled by music rate; fCurrentTime is. Undo it. // Calls to Update() should *not* be scaled by music rate; fCurrentTime is. Undo it.
const float fRate = GAMESTATE->m_SongOptions.GetCurrent().m_fMusicRate; const float fRate = GAMESTATE->m_SongOptions.GetCurrent().m_fMusicRate;
// Find the BGSegment we're in // Find the BGSegment we're in
const int i = FindBGSegmentForBeat( fBeat ); const int i = FindBGSegmentForBeat(beat_info.beat);
float fDeltaTime = fCurrentTime - fLastMusicSeconds; float fDeltaTime = fCurrentTime - fLastMusicSeconds;
if( i != -1 && i != m_iCurBGChangeIndex ) // we're changing backgrounds if( i != -1 && i != m_iCurBGChangeIndex ) // we're changing backgrounds
+6 -9
View File
@@ -2701,20 +2701,17 @@ void Player::UpdateTapNotesMissedOlderThan( float fMissIfOlderThanSeconds )
int iMissIfOlderThanThisRow; int iMissIfOlderThanThisRow;
const float fEarliestTime = m_pPlayerState->m_Position.m_fMusicSeconds - fMissIfOlderThanSeconds; const float fEarliestTime = m_pPlayerState->m_Position.m_fMusicSeconds - fMissIfOlderThanSeconds;
{ {
bool bFreeze, bDelay; TimingData::GetBeatArgs beat_info;
float fMissIfOlderThanThisBeat; beat_info.elapsed_time= fEarliestTime;
float fThrowAway; m_Timing->GetBeatAndBPSFromElapsedTime(beat_info);
int iWarpBeginRow;
float fWarpLength;
m_Timing->GetBeatAndBPSFromElapsedTime( fEarliestTime, fMissIfOlderThanThisBeat, fThrowAway, bFreeze, bDelay, iWarpBeginRow, fWarpLength );
iMissIfOlderThanThisRow = BeatToNoteRow( fMissIfOlderThanThisBeat ); iMissIfOlderThanThisRow = BeatToNoteRow(beat_info.beat);
if( bFreeze || bDelay ) if(beat_info.freeze_out || beat_info.delay_out )
{ {
/* If there is a freeze on iMissIfOlderThanThisIndex, include this index too. /* If there is a freeze on iMissIfOlderThanThisIndex, include this index too.
* Otherwise we won't show misses for tap notes on freezes until the * Otherwise we won't show misses for tap notes on freezes until the
* freeze finishes. */ * freeze finishes. */
if( !bDelay ) if(!beat_info.delay_out)
iMissIfOlderThanThisRow++; iMissIfOlderThanThisRow++;
} }
} }
+14
View File
@@ -1486,6 +1486,13 @@ void ScreenGameplay::StartPlayingSong( float fMinTimeToNotes, float fMinTimeToMu
UpdateSongPosition(0); UpdateSongPosition(0);
ASSERT( GAMESTATE->m_Position.m_fMusicSeconds > -4000 ); /* make sure the "fake timer" code doesn't trigger */ ASSERT( GAMESTATE->m_Position.m_fMusicSeconds > -4000 ); /* make sure the "fake timer" code doesn't trigger */
FOREACH_EnabledPlayer(pn)
{
if(GAMESTATE->m_pCurSteps[pn])
{
GAMESTATE->m_pCurSteps[pn]->GetTimingData()->PrepareLookup();
}
}
} }
@@ -2548,6 +2555,13 @@ void ScreenGameplay::SaveStats()
void ScreenGameplay::SongFinished() void ScreenGameplay::SongFinished()
{ {
FOREACH_EnabledPlayer(pn)
{
if(GAMESTATE->m_pCurSteps[pn])
{
GAMESTATE->m_pCurSteps[pn]->GetTimingData()->ReleaseLookup();
}
}
AdjustSync::HandleSongEnd(); AdjustSync::HandleSongEnd();
SaveStats(); // Let subclasses save the stats. SaveStats(); // Let subclasses save the stats.
/* Extremely important: if we don't remove attacks before moving on to the next /* Extremely important: if we don't remove attacks before moving on to the next
+13
View File
@@ -198,6 +198,19 @@ bool ScreenSyncOverlay::Input( const InputEventPlus &input )
return true; return true;
} }
// Release the lookup tables being used for the timing data because
// changing the timing data invalidates them. -Kyz
if(a != Action_Invalid)
{
FOREACH_EnabledPlayer(pn)
{
if(GAMESTATE->m_pCurSteps[pn])
{
GAMESTATE->m_pCurSteps[pn]->GetTimingData()->ReleaseLookup();
}
}
}
switch( a ) switch( a )
{ {
case RevertSyncChanges: case RevertSyncChanges:
+12 -6
View File
@@ -11,7 +11,15 @@ void SongPosition::UpdateSongPosition( float fPositionSeconds, const TimingData
else else
m_LastBeatUpdate.Touch(); m_LastBeatUpdate.Touch();
timing.GetBeatAndBPSFromElapsedTime( fPositionSeconds, m_fSongBeat, m_fCurBPS, m_bFreeze, m_bDelay, m_iWarpBeginRow, m_fWarpDestination ); TimingData::GetBeatArgs beat_info;
beat_info.elapsed_time= fPositionSeconds;
timing.GetBeatAndBPSFromElapsedTime(beat_info);
m_fSongBeat= beat_info.beat;
m_fCurBPS= beat_info.bps_out;
m_bFreeze= beat_info.freeze_out;
m_bDelay= beat_info.delay_out;
m_iWarpBeginRow= beat_info.warp_begin_out;
m_fWarpDestination= beat_info.warp_dest_out;
// "Crash reason : -243478.890625 -48695.773438" // "Crash reason : -243478.890625 -48695.773438"
// The question is why is -2000 used as the limit? -aj // The question is why is -2000 used as the limit? -aj
@@ -24,11 +32,9 @@ void SongPosition::UpdateSongPosition( float fPositionSeconds, const TimingData
m_fSongBeatNoOffset = timing.GetBeatFromElapsedTimeNoOffset( fPositionSeconds ); m_fSongBeatNoOffset = timing.GetBeatFromElapsedTimeNoOffset( fPositionSeconds );
m_fMusicSecondsVisible = fPositionSeconds - g_fVisualDelaySeconds.Get(); m_fMusicSecondsVisible = fPositionSeconds - g_fVisualDelaySeconds.Get();
float fThrowAway, fThrowAway2; beat_info.elapsed_time= m_fMusicSecondsVisible;
bool bThrowAway; timing.GetBeatAndBPSFromElapsedTime(beat_info);
int iThrowAway; m_fSongBeatVisible= beat_info.beat;
timing.GetBeatAndBPSFromElapsedTime( m_fMusicSecondsVisible, m_fSongBeatVisible, fThrowAway, bThrowAway, bThrowAway, iThrowAway, fThrowAway2 );
} }
void SongPosition::Reset() void SongPosition::Reset()
+273 -187
View File
@@ -51,6 +51,89 @@ TimingData::~TimingData()
Clear(); Clear();
} }
void TimingData::PrepareLookup()
{
// If multiple players have the same timing data, then adding to the
// lookups would probably cause FindEntryInLookup to return the wrong
// thing. So release the lookups. -Kyz
ReleaseLookup();
const unsigned int segments_per_lookup= 32;
const vector<TimingSegment*>* segs= m_avpTimingSegments;
const vector<TimingSegment*>& bpms= segs[SEGMENT_BPM];
const vector<TimingSegment*>& warps= segs[SEGMENT_WARP];
const vector<TimingSegment*>& stops= segs[SEGMENT_STOP];
const vector<TimingSegment*>& delays= segs[SEGMENT_DELAY];
unsigned int total_segments= bpms.size() + warps.size() + stops.size() + delays.size();
unsigned int lookup_entries= total_segments / segments_per_lookup;
m_beat_start_lookup.reserve(lookup_entries);
m_time_start_lookup.reserve(lookup_entries);
for(unsigned int curr_segment= segments_per_lookup;
curr_segment < total_segments; curr_segment+= segments_per_lookup)
{
GetBeatStarts beat_start;
beat_start.last_time= -m_fBeat0OffsetInSeconds;
GetBeatArgs args;
args.elapsed_time= FLT_MAX;
GetBeatInternal(beat_start, args, curr_segment);
m_beat_start_lookup.push_back(lookup_item_t(args.elapsed_time, beat_start));
GetBeatStarts time_start;
time_start.last_time= -m_fBeat0OffsetInSeconds;
float time= GetElapsedTimeInternal(time_start, FLT_MAX, curr_segment);
m_time_start_lookup.push_back(lookup_item_t(time, time_start));
}
}
void TimingData::ReleaseLookup()
{
// According to The C++ Programming Language 3rd Ed., decreasing the size
// of a vector doesn't actually free the memory it has allocated. So this
// small trick is required to actually free the memory. -Kyz
#define CLEAR_LOOKUP(lookup) \
{ \
lookup.clear(); \
beat_start_lookup_t tmp= lookup; \
lookup.swap(tmp); \
}
CLEAR_LOOKUP(m_beat_start_lookup);
CLEAR_LOOKUP(m_time_start_lookup);
#undef CLEAR_LOOKUP
}
TimingData::beat_start_lookup_t::const_iterator FindEntryInLookup(
const TimingData::beat_start_lookup_t& lookup, float entry)
{
if(lookup.empty())
{
return lookup.end();
}
size_t lower= 0;
size_t upper= lookup.size()-1;
if(lookup[lower].first > entry)
{
return lookup.end();
}
while(upper - lower > 1)
{
size_t next= (upper + lower) / 2;
if(lookup[next].first > entry)
{
upper= next;
}
else if(lookup[next].first < entry)
{
lower= next;
}
else
{
lower= next;
break;
}
}
return lookup.begin() + lower;
}
bool TimingData::empty() const bool TimingData::empty() const
{ {
FOREACH_TimingSegmentType( tst ) FOREACH_TimingSegmentType( tst )
@@ -491,11 +574,10 @@ bool TimingData::DoesLabelExist( const RString& sLabel ) const
return false; return false;
} }
void TimingData::GetBeatAndBPSFromElapsedTime( float fElapsedTime, float &fBeatOut, float &fBPSOut, bool &bFreezeOut, bool &bDelayOut, int &iWarpBeginOut, float &fWarpLengthOut ) const void TimingData::GetBeatAndBPSFromElapsedTime(GetBeatArgs& args) const
{ {
fElapsedTime += PREFSMAN->m_fGlobalOffsetSeconds; args.elapsed_time += PREFSMAN->m_fGlobalOffsetSeconds;
GetBeatAndBPSFromElapsedTimeNoOffset(args);
GetBeatAndBPSFromElapsedTimeNoOffset( fElapsedTime, fBeatOut, fBPSOut, bFreezeOut, bDelayOut, iWarpBeginOut, fWarpLengthOut );
} }
enum enum
@@ -510,137 +592,226 @@ enum
NOT_FOUND NOT_FOUND
}; };
void TimingData::GetBeatAndBPSFromElapsedTimeNoOffset( float fElapsedTime, float &fBeatOut, float &fBPSOut, bool &bFreezeOut, bool &bDelayOut, int &iWarpBeginOut, float &fWarpDestinationOut ) const void FindEvent(int& event_row, int& event_type,
TimingData::GetBeatStarts& start, float beat, bool find_marker,
const vector<TimingSegment*>& bpms, const vector<TimingSegment*>& warps,
const vector<TimingSegment*>& stops, const vector<TimingSegment*>& delays)
{
if(start.is_warping && BeatToNoteRow(start.warp_destination) < event_row)
{
event_row= BeatToNoteRow(start.warp_destination);
event_type= FOUND_WARP_DESTINATION;
}
if(start.bpm < bpms.size() && bpms[start.bpm]->GetRow() < event_row)
{
event_row= bpms[start.bpm]->GetRow();
event_type= FOUND_BPM_CHANGE;
}
if(start.delay < delays.size() && delays[start.delay]->GetRow() < event_row)
{
event_row= delays[start.delay]->GetRow();
event_type= FOUND_DELAY;
}
if(find_marker && BeatToNoteRow(beat) < event_row)
{
event_row= BeatToNoteRow(beat);
event_type= FOUND_MARKER;
}
if(start.stop < stops.size() && stops[start.stop]->GetRow() < event_row)
{
int tmp_row= event_row;
event_row= stops[start.stop]->GetRow();
event_type= (tmp_row == event_row) ? FOUND_STOP_DELAY : FOUND_STOP;
}
if(start.warp < warps.size() && warps[start.warp]->GetRow() < event_row)
{
event_row= warps[start.warp]->GetRow();
event_type= FOUND_WARP;
}
}
void TimingData::GetBeatInternal(GetBeatStarts& start, GetBeatArgs& args,
unsigned int max_segment) const
{ {
const vector<TimingSegment*>* segs= m_avpTimingSegments; const vector<TimingSegment*>* segs= m_avpTimingSegments;
vector<TimingSegment *>::const_iterator itBPMS = segs[SEGMENT_BPM].begin(); const vector<TimingSegment*>& bpms= segs[SEGMENT_BPM];
vector<TimingSegment *>::const_iterator itWS = segs[SEGMENT_WARP].begin(); const vector<TimingSegment*>& warps= segs[SEGMENT_WARP];
vector<TimingSegment *>::const_iterator itSS = segs[SEGMENT_STOP].begin(); const vector<TimingSegment*>& stops= segs[SEGMENT_STOP];
vector<TimingSegment *>::const_iterator itDS = segs[SEGMENT_DELAY].begin(); const vector<TimingSegment*>& delays= segs[SEGMENT_DELAY];
unsigned int curr_segment= start.bpm+start.warp+start.stop+start.delay;
bFreezeOut = false; float bps= GetBPMAtRow(start.last_row) / 60.0f;
bDelayOut = false; #define INC_INDEX(index) ++curr_segment; ++index;
iWarpBeginOut = -1; while(curr_segment < max_segment)
int iLastRow = 0;
float fLastTime = -m_fBeat0OffsetInSeconds;
float fBPS = GetBPMAtRow(0) / 60.0f;
float bIsWarping = false;
float fWarpDestination = 0;
for( ;; )
{ {
int iEventRow = INT_MAX; int event_row= INT_MAX;
int iEventType = NOT_FOUND; int event_type= NOT_FOUND;
if( bIsWarping && BeatToNoteRow(fWarpDestination) < iEventRow ) FindEvent(event_row, event_type, start, 0, false, bpms, warps, stops,
{ delays);
iEventRow = BeatToNoteRow(fWarpDestination); if(event_type == NOT_FOUND)
iEventType = FOUND_WARP_DESTINATION;
}
if (itBPMS != segs[SEGMENT_BPM].end() &&
(*itBPMS)->GetRow() < iEventRow )
{
iEventRow = (*itBPMS)->GetRow();
iEventType = FOUND_BPM_CHANGE;
}
if (itDS != segs[SEGMENT_DELAY].end() &&
(*itDS)->GetRow() < iEventRow)
{
iEventRow = (*itDS)->GetRow();
iEventType = FOUND_DELAY;
}
if (itSS != segs[SEGMENT_STOP].end() &&
(*itSS)->GetRow() < iEventRow ) // && iEventType != FOUND_DELAY )
{
int tmpRow = iEventRow;
iEventRow = (*itSS)->GetRow();
iEventType = (tmpRow == iEventRow) ? FOUND_STOP_DELAY : FOUND_STOP;
}
if (itWS != segs[SEGMENT_WARP].end() &&
(*itWS)->GetRow() < iEventRow )
{
iEventRow = (*itWS)->GetRow();
iEventType = FOUND_WARP;
}
if( iEventType == NOT_FOUND )
{ {
break; break;
} }
float fTimeToNextEvent = bIsWarping ? 0 : NoteRowToBeat( iEventRow - iLastRow ) / fBPS; float time_to_next_event= start.is_warping ? 0 :
float fNextEventTime = fLastTime + fTimeToNextEvent; NoteRowToBeat(event_row - start.last_row) / bps;
if ( fElapsedTime < fNextEventTime ) float next_event_time= start.last_time + time_to_next_event;
if(args.elapsed_time < next_event_time)
{ {
break; break;
} }
fLastTime = fNextEventTime; start.last_time= next_event_time;
switch( iEventType ) switch(event_type)
{ {
case FOUND_WARP_DESTINATION: case FOUND_WARP_DESTINATION:
bIsWarping = false; start.is_warping= false;
break; break;
case FOUND_BPM_CHANGE: case FOUND_BPM_CHANGE:
fBPS = ToBPM(*itBPMS)->GetBPS(); bps= ToBPM(bpms[start.bpm])->GetBPS();
itBPMS ++; INC_INDEX(start.bpm);
break; break;
case FOUND_DELAY: case FOUND_DELAY:
case FOUND_STOP_DELAY: case FOUND_STOP_DELAY:
{ {
const DelaySegment *ss = ToDelay(*itDS); const DelaySegment* ss= ToDelay(delays[start.delay]);
fTimeToNextEvent = ss->GetPause(); time_to_next_event= ss->GetPause();
fNextEventTime = fLastTime + fTimeToNextEvent; next_event_time= start.last_time + time_to_next_event;
if ( fElapsedTime < fNextEventTime ) if(args.elapsed_time < next_event_time)
{ {
bFreezeOut = false; args.freeze_out= false;
bDelayOut = true; args.delay_out= true;
fBeatOut = ss->GetBeat(); args.beat= ss->GetBeat();
fBPSOut = fBPS; args.bps_out= bps;
return; return;
} }
fLastTime = fNextEventTime; start.last_time= next_event_time;
itDS ++; INC_INDEX(start.delay);
if (iEventType == FOUND_DELAY) if(event_type == FOUND_DELAY)
{
break; break;
} }
}
case FOUND_STOP: case FOUND_STOP:
{ {
const StopSegment *ss = ToStop(*itSS); const StopSegment* ss= ToStop(stops[start.stop]);
fTimeToNextEvent = ss->GetPause(); time_to_next_event= ss->GetPause();
fNextEventTime = fLastTime + fTimeToNextEvent; next_event_time= start.last_time + time_to_next_event;
if ( fElapsedTime < fNextEventTime ) if(args.elapsed_time < next_event_time)
{ {
bFreezeOut = true; args.freeze_out= true;
bDelayOut = false; args.delay_out= false;
fBeatOut = ss->GetBeat(); args.beat= ss->GetBeat();
fBPSOut = fBPS; args.bps_out= bps;
return; return;
} }
fLastTime = fNextEventTime; start.last_time= next_event_time;
itSS ++; INC_INDEX(start.stop);
break; break;
} }
case FOUND_WARP: case FOUND_WARP:
{ {
bIsWarping = true; start.is_warping= true;
const WarpSegment *ws = ToWarp(*itWS); const WarpSegment* ws= ToWarp(warps[start.warp]);
float fWarpSum = ws->GetLength() + ws->GetBeat(); float warp_sum= ws->GetLength() + ws->GetBeat();
if( fWarpSum > fWarpDestination ) if(warp_sum > start.warp_destination)
{ {
fWarpDestination = fWarpSum; start.warp_destination= warp_sum;
} }
iWarpBeginOut = iEventRow; args.warp_begin_out= event_row;
fWarpDestinationOut = fWarpDestination; args.warp_dest_out= start.warp_destination;
itWS ++; INC_INDEX(start.warp);
break; break;
} }
} }
iLastRow = iEventRow; start.last_row= event_row;
}
#undef INC_INDEX
if(args.elapsed_time == FLT_MAX)
{
args.elapsed_time= start.last_time;
}
args.beat= NoteRowToBeat(start.last_row) +
(args.elapsed_time - start.last_time) * bps;
args.bps_out= bps;
} }
fBeatOut = NoteRowToBeat( iLastRow ) + (fElapsedTime - fLastTime) * fBPS; void TimingData::GetBeatAndBPSFromElapsedTimeNoOffset(GetBeatArgs& args) const
fBPSOut = fBPS; {
GetBeatStarts start;
start.last_time= -m_fBeat0OffsetInSeconds;
beat_start_lookup_t::const_iterator looked_up_start=
FindEntryInLookup(m_beat_start_lookup, args.elapsed_time);
if(looked_up_start != m_beat_start_lookup.end())
{
start= looked_up_start->second;
}
GetBeatInternal(start, args, INT_MAX);
}
float TimingData::GetElapsedTimeInternal(GetBeatStarts& start, float beat,
unsigned int max_segment) const
{
const vector<TimingSegment*>* segs= m_avpTimingSegments;
const vector<TimingSegment*>& bpms= segs[SEGMENT_BPM];
const vector<TimingSegment*>& warps= segs[SEGMENT_WARP];
const vector<TimingSegment*>& stops= segs[SEGMENT_STOP];
const vector<TimingSegment*>& delays= segs[SEGMENT_DELAY];
unsigned int curr_segment= start.bpm+start.warp+start.stop+start.delay;
float bps= GetBPMAtRow(start.last_row) / 60.0f;
#define INC_INDEX(index) ++curr_segment; ++index;
while(curr_segment < max_segment)
{
int event_row= INT_MAX;
int event_type= NOT_FOUND;
FindEvent(event_row, event_type, start, beat, true, bpms, warps, stops,
delays);
float time_to_next_event= start.is_warping ? 0 :
NoteRowToBeat(event_row - start.last_row) / bps;
float next_event_time= start.last_time + time_to_next_event;
start.last_time= next_event_time;
switch(event_type)
{
case FOUND_WARP_DESTINATION:
start.is_warping= false;
break;
case FOUND_BPM_CHANGE:
bps= ToBPM(bpms[start.bpm])->GetBPS();
INC_INDEX(start.bpm);
break;
case FOUND_STOP:
case FOUND_STOP_DELAY:
time_to_next_event= ToStop(stops[start.stop])->GetPause();
next_event_time= start.last_time + time_to_next_event;
start.last_time= next_event_time;
INC_INDEX(start.stop);
break;
case FOUND_DELAY:
time_to_next_event= ToDelay(delays[start.delay])->GetPause();
next_event_time= start.last_time + time_to_next_event;
start.last_time= next_event_time;
INC_INDEX(start.delay);
break;
case FOUND_MARKER:
return start.last_time;
case FOUND_WARP:
{
start.is_warping= true;
WarpSegment* ws= ToWarp(warps[start.warp]);
float warp_sum= ws->GetLength() + ws->GetBeat();
if(warp_sum > start.warp_destination)
{
start.warp_destination= warp_sum;
}
INC_INDEX(start.warp);
break;
}
}
start.last_row= event_row;
}
#undef INC_INDEX
return start.last_time;
} }
float TimingData::GetElapsedTimeFromBeat( float fBeat ) const float TimingData::GetElapsedTimeFromBeat( float fBeat ) const
@@ -650,101 +821,16 @@ float TimingData::GetElapsedTimeFromBeat( float fBeat ) const
float TimingData::GetElapsedTimeFromBeatNoOffset( float fBeat ) const float TimingData::GetElapsedTimeFromBeatNoOffset( float fBeat ) const
{ {
const vector<TimingSegment *> * segs = m_avpTimingSegments; GetBeatStarts start;
vector<TimingSegment *>::const_iterator itBPMS = segs[SEGMENT_BPM].begin(); start.last_time= -m_fBeat0OffsetInSeconds;
vector<TimingSegment *>::const_iterator itWS = segs[SEGMENT_WARP].begin(); beat_start_lookup_t::const_iterator looked_up_start=
vector<TimingSegment *>::const_iterator itSS = segs[SEGMENT_STOP].begin(); FindEntryInLookup(m_time_start_lookup, fBeat);
vector<TimingSegment *>::const_iterator itDS = segs[SEGMENT_DELAY].begin(); if(looked_up_start != m_time_start_lookup.end())
int iLastRow = 0;
float fLastTime = -m_fBeat0OffsetInSeconds;
float fBPS = GetBPMAtRow(0) / 60.0f;
float bIsWarping = false;
float fWarpDestination = 0;
for( ;; )
{ {
int iEventRow = INT_MAX; start= looked_up_start->second;
int iEventType = NOT_FOUND;
if( bIsWarping && BeatToNoteRow(fWarpDestination) < iEventRow )
{
iEventRow = BeatToNoteRow(fWarpDestination);
iEventType = FOUND_WARP_DESTINATION;
} }
if (itBPMS != segs[SEGMENT_BPM].end() && GetElapsedTimeInternal(start, fBeat, INT_MAX);
(*itBPMS)->GetRow() < iEventRow ) return start.last_time;
{
iEventRow = (*itBPMS)->GetRow();
iEventType = FOUND_BPM_CHANGE;
}
if (itDS != segs[SEGMENT_DELAY].end() &&
(*itDS)->GetRow() < iEventRow ) // delays (come before marker)
{
iEventRow = (*itDS)->GetRow();
iEventType = FOUND_DELAY;
}
if( BeatToNoteRow(fBeat) < iEventRow )
{
iEventRow = BeatToNoteRow(fBeat);
iEventType = FOUND_MARKER;
}
if (itSS != segs[SEGMENT_STOP].end() &&
(*itSS)->GetRow() < iEventRow ) // stops (come after marker)
{
iEventRow = (*itSS)->GetRow();
iEventType = FOUND_STOP;
}
if (itWS != segs[SEGMENT_WARP].end() &&
(*itWS)->GetRow() < iEventRow )
{
iEventRow = (*itWS)->GetRow();
iEventType = FOUND_WARP;
}
float fTimeToNextEvent = bIsWarping ? 0 : NoteRowToBeat( iEventRow - iLastRow ) / fBPS;
float fNextEventTime = fLastTime + fTimeToNextEvent;
fLastTime = fNextEventTime;
switch( iEventType )
{
case FOUND_WARP_DESTINATION:
bIsWarping = false;
break;
case FOUND_BPM_CHANGE:
fBPS = ToBPM(*itBPMS)->GetBPS();
itBPMS ++;
break;
case FOUND_STOP:
fTimeToNextEvent = ToStop(*itSS)->GetPause();
fNextEventTime = fLastTime + fTimeToNextEvent;
fLastTime = fNextEventTime;
itSS ++;
break;
case FOUND_DELAY:
fTimeToNextEvent = ToDelay(*itDS)->GetPause();
fNextEventTime = fLastTime + fTimeToNextEvent;
fLastTime = fNextEventTime;
itDS ++;
break;
case FOUND_MARKER:
return fLastTime;
case FOUND_WARP:
{
bIsWarping = true;
WarpSegment *ws = ToWarp(*itWS);
float fWarpSum = ws->GetLength() + ws->GetBeat();
if( fWarpSum > fWarpDestination )
{
fWarpDestination = fWarpSum;
}
itWS ++;
break;
}
}
iLastRow = iEventRow;
}
// won't reach here, unless BeatToNoteRow(fBeat == INT_MAX) (impossible)
} }
float TimingData::GetDisplayedBeat( float fBeat ) const float TimingData::GetDisplayedBeat( float fBeat ) const
+72 -14
View File
@@ -71,6 +71,62 @@ public:
TimingData( const TimingData &cpy ) { Copy(cpy); } TimingData( const TimingData &cpy ) { Copy(cpy); }
TimingData& operator=( const TimingData &cpy ) { Copy(cpy); return *this; } TimingData& operator=( const TimingData &cpy ) { Copy(cpy); return *this; }
// GetBeatArgs, GetBeatStarts, m_beat_start_lookup, m_time_start_lookup,
// PrepareLookup, and ReleaseLookup form a system for speeding up finding
// the current beat and bps from the time, or finding the time from the
// current beat.
// The lookup tables contain indices for the beat and time finding
// functions to start at so they don't have to walk through all the timing
// segments.
// PrepareLookup should be called before gameplay starts, so that the lookup
// tables are populated. ReleaseLookup should be called after gameplay
// finishes so that memory isn't wasted.
// -Kyz
struct GetBeatArgs
{
float elapsed_time;
float beat;
float bps_out;
float warp_dest_out;
int warp_begin_out;
bool freeze_out;
bool delay_out;
GetBeatArgs() :elapsed_time(0), beat(0), bps_out(0), warp_dest_out(0),
warp_begin_out(-1), freeze_out(false), delay_out(false) {}
};
struct GetBeatStarts
{
unsigned int bpm;
unsigned int warp;
unsigned int stop;
unsigned int delay;
int last_row;
float last_time;
float warp_destination;
bool is_warping;
GetBeatStarts() :bpm(0), warp(0), stop(0), delay(0), last_row(0),
last_time(0), warp_destination(0), is_warping(false) {}
};
// map can't be used for the lookup table because its find or *_bound
// functions would return the wrong entry.
// In a map<int, int> with three entries, [-1]= 3, [6]= 1, [8]= 2,
// lower_bound(0) and upper_bound(0) both returned the entry at [6]= 1.
// So the lookup table is a vector of entries and FindEntryInLookup does a
// binary search.
// -Kyz
struct lookup_item_t
{
float first;
GetBeatStarts second;
lookup_item_t(float f, GetBeatStarts& s) :first(f), second(s) {}
};
typedef vector<lookup_item_t> beat_start_lookup_t;
beat_start_lookup_t m_beat_start_lookup;
beat_start_lookup_t m_time_start_lookup;
void PrepareLookup();
void ReleaseLookup();
int GetSegmentIndexAtRow(TimingSegmentType tst, int row) const; int GetSegmentIndexAtRow(TimingSegmentType tst, int row) const;
int GetSegmentIndexAtBeat(TimingSegmentType tst, float beat) const int GetSegmentIndexAtBeat(TimingSegmentType tst, float beat) const
{ {
@@ -290,25 +346,27 @@ public:
void NoteRowToMeasureAndBeat( int iNoteRow, int &iMeasureIndexOut, int &iBeatIndexOut, int &iRowsRemainder ) const; void NoteRowToMeasureAndBeat( int iNoteRow, int &iMeasureIndexOut, int &iBeatIndexOut, int &iRowsRemainder ) const;
void GetBeatAndBPSFromElapsedTime( float fElapsedTime, float &fBeatOut, float &fBPSOut, bool &bFreezeOut, bool &bDelayOut, int &iWarpBeginOut, float &fWarpLengthOut ) const; void GetBeatInternal(GetBeatStarts& start, GetBeatArgs& args,
float GetBeatFromElapsedTime( float fElapsedTime ) const // shortcut for places that care only about the beat unsigned int max_segment) const;
float GetElapsedTimeInternal(GetBeatStarts& start, float beat,
unsigned int max_segment) const;
void GetBeatAndBPSFromElapsedTime(GetBeatArgs& args) const;
float GetBeatFromElapsedTime(float elapsed_time) const // shortcut for places that care only about the beat
{ {
float fBeat, fThrowAway, fThrowAway2; GetBeatArgs args;
bool bThrowAway, bThrowAway2; args.elapsed_time= elapsed_time;
int iThrowAway; GetBeatAndBPSFromElapsedTime(args);
GetBeatAndBPSFromElapsedTime( fElapsedTime, fBeat, fThrowAway, bThrowAway, bThrowAway2, iThrowAway, fThrowAway2 ); return args.beat;
return fBeat;
} }
float GetElapsedTimeFromBeat( float fBeat ) const; float GetElapsedTimeFromBeat( float fBeat ) const;
void GetBeatAndBPSFromElapsedTimeNoOffset( float fElapsedTime, float &fBeatOut, float &fBPSOut, bool &bFreezeOut, bool &bDelayOut, int &iWarpBeginOut, float &fWarpDestinationOut ) const; void GetBeatAndBPSFromElapsedTimeNoOffset(GetBeatArgs& args) const;
float GetBeatFromElapsedTimeNoOffset( float fElapsedTime ) const // shortcut for places that care only about the beat float GetBeatFromElapsedTimeNoOffset(float elapsed_time) const // shortcut for places that care only about the beat
{ {
float fBeat, fThrowAway, fThrowAway2; GetBeatArgs args;
bool bThrowAway, bThrowAway2; args.elapsed_time= elapsed_time;
int iThrowAway; GetBeatAndBPSFromElapsedTimeNoOffset(args);
GetBeatAndBPSFromElapsedTimeNoOffset( fElapsedTime, fBeat, fThrowAway, bThrowAway, bThrowAway2, iThrowAway, fThrowAway2 ); return args.beat;
return fBeat;
} }
float GetElapsedTimeFromBeatNoOffset( float fBeat ) const; float GetElapsedTimeFromBeatNoOffset( float fBeat ) const;
float GetDisplayedBeat( float fBeat ) const; float GetDisplayedBeat( float fBeat ) const;