diff --git a/stepmania/src/Course.cpp b/stepmania/src/Course.cpp index 56bbb79eb7..0d19ce7878 100644 --- a/stepmania/src/Course.cpp +++ b/stepmania/src/Course.cpp @@ -10,6 +10,7 @@ #include "MsdFile.h" #include "PlayerOptions.h" #include "SongOptions.h" +#include "SongCacheIndex.h" #include "RageUtil.h" #include "TitleSubstitution.h" #include "Steps.h" @@ -47,12 +48,33 @@ PlayMode Course::GetPlayMode() const void Course::LoadFromCRSFile( CString sPath ) { - LOG->Trace( "Course::LoadFromCRSFile( '%s' )", sPath.c_str() ); - Init(); m_sPath = sPath; // save path + bool bUseCache = true; + { + /* First look in the cache for this course. Don't bother + * honoring FastLoad for checking the course hash, since + * courses are normally grouped into a few directories, not + * one directory per course. XXX: if !FastLoad, regen + * cache if the used songs have changed */ + unsigned uHash = SONGINDEX->GetCacheHash( m_sPath ); + if( !DoesFileExist(GetCacheFilePath()) ) + bUseCache = false; + if( !PREFSMAN->m_bFastLoad && GetHashForDirectory(m_sPath) != uHash ) + bUseCache = false; // this cache is out of date + } + + if( bUseCache ) + { + CString sCacheFile = GetCacheFilePath(); + LOG->Trace( "Course::LoadFromCRSFile(\"%s\") (\"%s\")", sPath.c_str(), sCacheFile.c_str() ); + sPath = sCacheFile.c_str(); + } + else + LOG->Trace( "Course::LoadFromCRSFile(\"%s\")", sPath.c_str() ); + MsdFile msd; if( !msd.ReadFile(sPath) ) RageException::Throw( "Error opening CRS file '%s'.", sPath.c_str() ); @@ -243,7 +265,15 @@ void Course::LoadFromCRSFile( CString sPath ) m_entries.push_back( new_entry ); } + else if( bUseCache && !stricmp(sValueName, "RADAR") ) + { + StepsType st = (StepsType) atoi(sParams[1]); + CourseDifficulty cd = (CourseDifficulty) atoi(sParams[2]); + RadarValues rv; + rv.FromString( sParams[3] ); + m_RadarCache[CacheEntry(st, cd)] = rv; + } else LOG->Trace( "Unexpected value named '%s'", sValueName.c_str() ); } @@ -260,6 +290,33 @@ void Course::LoadFromCRSFile( CString sPath ) * song was found in the course. */ if( m_sBannerPath != "" && !m_entries.empty() ) BANNERCACHE->CacheBanner( m_sBannerPath ); + + /* Cache each trail RadarValues that's slow to load, so we + * don't have to do it at runtime. */ + if( !bUseCache ) + { + FOREACH_StepsType( st ) + { + FOREACH_CourseDifficulty( cd ) + { + Trail *pTrail = GetTrail( st, cd ); + if( pTrail == NULL || !pTrail->SlowGetRadarValues() ) + continue; + + RadarValues rv = pTrail->GetRadarValues(); + m_RadarCache[CacheEntry(st, cd)] = rv; + } + } + + /* If we have any cache data, write the cache file. */ + if( m_RadarCache.size() ) + { + CString sCachePath = GetCacheFilePath(); + Save( sCachePath, true ); + + SONGINDEX->AddCacheIndex( m_sPath, GetHashForFile(m_sPath) ); + } + } } void Course::RevertFromDisk() @@ -270,6 +327,11 @@ void Course::RevertFromDisk() LoadFromCRSFile( m_sPath ); } +CString Course::GetCacheFilePath() const +{ + return SongCacheIndex::GetCacheFilePath( "Courses", m_sPath ); +} + void Course::Init() { m_bIsAutogen = false; @@ -291,14 +353,18 @@ void Course::Init() m_iTrailCacheSeed = 0; } -void Course::Save() +void Course::Save( CString sPath, bool bSavingCache ) { ASSERT( !m_bIsAutogen ); + /* By default, save to the file we loaded from. */ + if( sPath == "" ) + sPath = m_sPath; + RageFile f; - if( !f.Open( m_sPath, RageFile::WRITE ) ) + if( !f.Open( sPath, RageFile::WRITE ) ) { - LOG->Warn( "Could not write course file '%s': %s", m_sPath.c_str(), f.GetError().c_str() ); + LOG->Warn( "Could not write course file '%s': %s", sPath.c_str(), f.GetError().c_str() ); return; } @@ -316,6 +382,29 @@ void Course::Save() f.PutLine( ssprintf("#METER:%s:%i;", CourseDifficultyToString(cd).c_str(), m_iCustomMeter[cd]) ); } + if( bSavingCache ) + { + f.PutLine( "// cache tags:" ); + + RadarCache_t::const_iterator it; + for( it = m_RadarCache.begin(); it != m_RadarCache.end(); ++it ) + { + // #RADAR:type:difficulty:value,value,value...; + const CacheEntry &entry = it->first; + StepsType st = entry.first; + CourseDifficulty cd = entry.second; + + CStringArray asRadarValues; + const RadarValues &rv = it->second; + for( int r=0; r < NUM_RADAR_CATEGORIES; r++ ) + asRadarValues.push_back( ssprintf("%.3f", rv[r]) ); + CString sLine = ssprintf( "#RADAR:%i:%i:", st, cd ); + sLine += join( ",", asRadarValues ) + ";"; + f.PutLine( sLine ); + } + f.PutLine( "// end cache tags" ); + } + for( unsigned i=0; isecond; - if( cache.null ) - return NULL; - return &cache.trail; + TrailCache_t::iterator it = m_TrailCache.find( CacheEntry(st, cd) ); + if( it != m_TrailCache.end() ) + { + CacheData &cache = it->second; + if( cache.null ) + return NULL; + return &cache.trail; + } } // @@ -630,6 +721,18 @@ Trail* Course::GetTrail( StepsType st, CourseDifficulty cd ) const return NULL; } + // + // If we have cached RadarValues for this trail, insert them. + // + { + RadarCache_t::const_iterator it = m_RadarCache.find( CacheEntry( st, cd ) ); + if( it != m_RadarCache.end() ) + { + const RadarValues &rv = it->second; + trail.SetRadarValues(rv); + } + } + cache.null = false; return &cache.trail; } diff --git a/stepmania/src/Course.h b/stepmania/src/Course.h index b0b79fdce0..9d5fc1f3a7 100644 --- a/stepmania/src/Course.h +++ b/stepmania/src/Course.h @@ -132,7 +132,7 @@ public: void LoadFromCRSFile( CString sPath ); void RevertFromDisk(); void Init(); - void Save(); + void Save( CString sPath = "", bool bSavingCache=false ); /* default is source file */ void AutogenEndlessFromGroup( CString sGroupName, Difficulty dc ); void AutogenNonstopFromGroup( CString sGroupName, Difficulty dc ); void AutogenOniFromArtist( CString sArtistName, CString sArtistNameTranslit, vector aSongs, Difficulty dc ); @@ -151,6 +151,7 @@ public: void Invalidate( Song *pStaleSong ); void GetAllCachedTrails( vector &out ); + CString GetCacheFilePath() const; const CourseEntry *FindFixedSong( const Song *pSong ) const; @@ -170,6 +171,9 @@ private: typedef map TrailCache_t; mutable TrailCache_t m_TrailCache; mutable int m_iTrailCacheSeed; + + typedef map RadarCache_t; + RadarCache_t m_RadarCache; }; #endif diff --git a/stepmania/src/RadarValues.cpp b/stepmania/src/RadarValues.cpp index 3c2733dd29..49be940953 100644 --- a/stepmania/src/RadarValues.cpp +++ b/stepmania/src/RadarValues.cpp @@ -58,6 +58,37 @@ void RadarValues::LoadFromNode( const XNode* pNode ) pNode->GetChildValue( RadarCategoryToString(rc), m_fValues[rc] ); } +/* iMaxValues is only used for writing compatibility fields in non-cache + * SM files; they're never actually read. */ +CString RadarValues::ToString( int iMaxValues ) const +{ + if( iMaxValues == -1 ) + iMaxValues = NUM_RADAR_CATEGORIES; + iMaxValues = min( iMaxValues, (int)NUM_RADAR_CATEGORIES ); + + CStringArray asRadarValues; + for( int r=0; r < iMaxValues; r++ ) + asRadarValues.push_back( ssprintf("%.3f", m_fValues[r]) ); + + return join( ",",asRadarValues ); +} + +void RadarValues::FromString( CString sRadarValues ) +{ + CStringArray saValues; + split( sRadarValues, ",", saValues, true ); + + if( saValues.size() != NUM_RADAR_CATEGORIES ) + { + MakeUnknown(); + return; + } + + FOREACH_RadarCategory(rc) + m_fValues[rc] = strtof( saValues[rc], NULL ); + +} + /* * (c) 2001-2004 Chris Danford * All rights reserved. diff --git a/stepmania/src/RadarValues.h b/stepmania/src/RadarValues.h index 864483d1e0..becef606d3 100644 --- a/stepmania/src/RadarValues.h +++ b/stepmania/src/RadarValues.h @@ -43,6 +43,9 @@ struct RadarValues XNode* CreateNode() const; void LoadFromNode( const XNode* pNode ); + + CString ToString( int iMaxValues = -1 ) const; // default = all + void FromString( CString sValues ); }; diff --git a/stepmania/src/SongCacheIndex.cpp b/stepmania/src/SongCacheIndex.cpp index 5e308239e0..a4fb1a839e 100644 --- a/stepmania/src/SongCacheIndex.cpp +++ b/stepmania/src/SongCacheIndex.cpp @@ -65,6 +65,7 @@ void SongCacheIndex::ReadCacheIndex() EmptyDir( CACHE_DIR ); EmptyDir( CACHE_DIR "Banners/" ); EmptyDir( CACHE_DIR "Songs/" ); + EmptyDir( CACHE_DIR "Courses/" ); CacheIndex.Clear(); } diff --git a/stepmania/src/Trail.cpp b/stepmania/src/Trail.cpp index 9da631ddf1..d686ddf378 100644 --- a/stepmania/src/Trail.cpp +++ b/stepmania/src/Trail.cpp @@ -40,6 +40,26 @@ bool TrailEntry::ContainsTransformOrTurn() const return false; } +/* Return true if GetRadarValues may be very slow (reads, decodes and + * transforms the Steps), in which case it's worth caching it in Course. */ +bool Trail::SlowGetRadarValues() const +{ + FOREACH_CONST( TrailEntry, m_vEntries, e ) + { + const Steps *pSteps = e->pSteps; + if( !pSteps->IsAutogen() && e->ContainsTransformOrTurn() ) + return true; + } + + return false; +} + +void Trail::SetRadarValues( const RadarValues &rv ) +{ + m_CachedRadarValues = rv; + m_bRadarValuesCached = true; +} + RadarValues Trail::GetRadarValues() const { if( IsMystery() ) @@ -55,8 +75,8 @@ RadarValues Trail::GetRadarValues() const } else { - m_bRadarValuesCached = true; - m_CachedRadarValues.Zero(); + RadarValues rv; + rv.Zero(); FOREACH_CONST( TrailEntry, m_vEntries, e ) { @@ -75,16 +95,21 @@ RadarValues Trail::GetRadarValues() const if( po.ContainsTransformOrTurn() ) NoteDataUtil::TransformNoteData( nd, po, pSteps->m_StepsType ); NoteDataUtil::TransformNoteData( nd, e->Attacks, pSteps->m_StepsType, e->pSong ); - RadarValues rv; - NoteDataUtil::GetRadarValues( nd, e->pSong->m_fMusicLengthSeconds, rv ); - m_CachedRadarValues += rv; + RadarValues transformed_rv; + NoteDataUtil::GetRadarValues( nd, e->pSong->m_fMusicLengthSeconds, transformed_rv ); + rv += transformed_rv; } else { - m_CachedRadarValues += pSteps->GetRadarValues(); + rv += pSteps->GetRadarValues(); } } + /* Hack: SetRadarValues is non-const (a const setter doesn't + * make sense), but it only modifies a mutable value. Just + * cast away const. */ + const_cast(this)->SetRadarValues( rv ); + return m_CachedRadarValues; } } diff --git a/stepmania/src/Trail.h b/stepmania/src/Trail.h index 2b0fc669d6..308b1fdff8 100644 --- a/stepmania/src/Trail.h +++ b/stepmania/src/Trail.h @@ -62,7 +62,9 @@ public: m_bRadarValuesCached = false; } + bool SlowGetRadarValues() const; RadarValues GetRadarValues() const; + void SetRadarValues( const RadarValues &rv ); // for pre-populating cache int GetMeter() const; int GetTotalMeter() const; float GetLengthSeconds() const;