From 82969003914c5a1d33e8b56e3298b6805d205b5b Mon Sep 17 00:00:00 2001 From: Glenn Maynard Date: Wed, 8 Sep 2004 02:17:08 +0000 Subject: [PATCH] This is a helper to precache files in the background, which is used to make the music selection screen playable when music is being read off a CD. This is actually disabled; I'm adding it so I can shift ScreenSelectMusic to use the API. --- stepmania/src/RageUtil_BackgroundLoader.cpp | 266 ++++++++++++++++++++ stepmania/src/RageUtil_BackgroundLoader.h | 86 +++++++ 2 files changed, 352 insertions(+) create mode 100644 stepmania/src/RageUtil_BackgroundLoader.cpp create mode 100644 stepmania/src/RageUtil_BackgroundLoader.h diff --git a/stepmania/src/RageUtil_BackgroundLoader.cpp b/stepmania/src/RageUtil_BackgroundLoader.cpp new file mode 100644 index 0000000000..3687c782d0 --- /dev/null +++ b/stepmania/src/RageUtil_BackgroundLoader.cpp @@ -0,0 +1,266 @@ +#include "global.h" +#include "RageUtil_BackgroundLoader.h" +#include "RageFile.h" +#include "RageFileManager.h" +#include "RageUtil.h" +#include "RageLog.h" +#include "RageThreads.h" + + +/* If we're on an OS with a good caching system, writing to our own cache will only + * waste memory. In that case, just read the file, to force it into system cache. + * If we're on a system with an unreliable cache, read it into our own cache. This + * helps in Windows, where even reading directory entries off of a CD--even if they've + * just been read--can cause long delays if the disk is in use. */ +static const bool g_bWriteToCache = true; + +static const bool g_bEnableBackgroundLoading = false; + +BackgroundLoader::BackgroundLoader(): + m_Mutex( "BackgroundLoaderMutex" ), + m_StartSem( "BackgroundLoaderSem" ) +{ + if( !g_bEnableBackgroundLoading ) + return; + + m_sCachePathPrefix = ssprintf( "@mem/%p", this ); + + m_bShutdownThread = false; + m_sThreadIsActive = m_sThreadShouldAbort = false; + m_LoadThread.SetName( "BackgroundLoader" ); + m_LoadThread.Create( LoadThread_Start, this ); +} + +static void DeleteEmptyDirectories( CString sDir ) +{ + vector asNewDirs; + GetDirListing( sDir + "/*", asNewDirs, false, true ); + for( unsigned i = 0; i < asNewDirs.size(); ++i ) + { + ASSERT_M( IsADirectory(asNewDirs[i]), asNewDirs[i] ); + DeleteEmptyDirectories( asNewDirs[i] ); + } + + FILEMAN->Remove( sDir ); +} + +BackgroundLoader::~BackgroundLoader() +{ + if( !g_bEnableBackgroundLoading ) + return; + + Abort(); + + m_bShutdownThread = true; + m_StartSem.Post(); + m_LoadThread.Wait(); + + /* Delete all leftover cached files. */ + map::iterator it; + for( it = m_FinishedRequests.begin(); it != m_FinishedRequests.end(); ++it ) + FILEMAN->Remove( GetCachePath( it->first ) ); + + /* m_sCachePathPrefix should be filled with several empty directories. Delete + * them and m_sCachePathPrefix, so we don't leak them. */ + DeleteEmptyDirectories( m_sCachePathPrefix ); +} + +/* Pull a request out of m_CacheRequests. */ +CString BackgroundLoader::GetRequest() +{ + if( !g_bEnableBackgroundLoading ) + return ""; + + LockMut( m_Mutex ); + if( !m_CacheRequests.size() ) + return ""; + + CString ret; + ret = m_CacheRequests.front(); + m_CacheRequests.erase( m_CacheRequests.begin(), m_CacheRequests.begin()+1 ); + return ret; +} + +CString BackgroundLoader::GetCachePath( CString sPath ) const +{ + return m_sCachePathPrefix + "/" + sPath; +} + +void BackgroundLoader::LoadThread() +{ + while( !m_bShutdownThread ) + { + /* Wait for a request. It's normal for this to wait for a long time; don't + * fail on timeout. */ + m_StartSem.Wait( false ); + + CString sFile = GetRequest(); + if( sFile.empty() ) + continue; + + { + /* If the file already exists, short circuit. */ + LockMut( m_Mutex ); + map::iterator it; + it = m_FinishedRequests.find( sFile ); + if( it != m_FinishedRequests.end() ) + { + ++it->second; + LOG->Trace("XXX: request %s done loading (already done), cnt now %i", sFile.c_str(), m_FinishedRequests[sFile] ); + continue; + } + } + + m_sThreadIsActive = true; + + LOG->Trace("XXX: reading %s", sFile.c_str()); + + CString sCachePath = GetCachePath( sFile ); + + /* Open the file and read it. */ + RageFile src; + if( src.Open(sFile) ) + { + /* If we're writing to a file cache ... */ + RageFile dst; + + bool bWriteToCache = g_bWriteToCache; + if( bWriteToCache ) + bWriteToCache = dst.Open( sCachePath, RageFile::WRITE ); + LOG->Trace("XXX: go on '%s' to '%s'", sFile.c_str(), sCachePath.c_str()); + + char buf[1024*4]; + while( !m_sThreadShouldAbort && !src.AtEOF() ) + { + int got = src.Read( buf, sizeof(buf) ); + if( got > 0 && bWriteToCache ) + dst.Write( buf, got ); + } + if( bWriteToCache ) + dst.Close(); + + LOG->Trace("XXX: done"); + } + src.Close(); + + LockMut( m_Mutex ); + if( !m_sThreadShouldAbort ) + { + ++m_FinishedRequests[sFile]; + LOG->Trace("XXX: request %s done loading, cnt now %i", sFile.c_str(), m_FinishedRequests[sFile] ); + } + else + { + FILEMAN->Remove( sCachePath ); + + LOG->Trace("XXX: request %s aborted", sFile.c_str() ); + } + + m_sThreadShouldAbort = false; + m_sThreadIsActive = false; + } +} + +void BackgroundLoader::CacheFile( const CString &sFile ) +{ + if( !g_bEnableBackgroundLoading ) + return; + + LockMut( m_Mutex ); + if( sFile == "" ) + ++m_FinishedRequests[sFile]; + else + { + m_CacheRequests.push_back( sFile ); + m_StartSem.Post(); + } +} + +bool BackgroundLoader::IsCacheFileFinished( const CString &sFile, CString &sActualPath ) +{ + if( !g_bEnableBackgroundLoading ) + { + sActualPath = sFile; + return true; + } + + LockMut( m_Mutex ); + + if( sFile == "" ) + { + sActualPath = ""; + return true; + } + + map::iterator it; + it = m_FinishedRequests.find( sFile ); + if( it == m_FinishedRequests.end() ) + return false; + + LOG->Trace("XXX: %s finished (%i)", sFile.c_str(), it->second); + sActualPath = GetCachePath( sFile ); + + return true; +} + +void BackgroundLoader::FinishedWithCachedFile( const CString &sFile ) +{ + if( !g_bEnableBackgroundLoading ) + return; + + if( sFile == "" ) + return; + + map::iterator it; + it = m_FinishedRequests.find( sFile ); + ASSERT_M( it != m_FinishedRequests.end(), sFile ); + + --it->second; + ASSERT_M( it->second >= 0, ssprintf("%i", it->second) ); + if( !it->second ) + { + m_FinishedRequests.erase( it ); + FILEMAN->Remove( GetCachePath( sFile ) ); + } +} + +void BackgroundLoader::Abort() +{ + if( !g_bEnableBackgroundLoading ) + return; + + LockMut( m_Mutex ); + + /* Clear any pending requests. */ + while( !GetRequest().empty() ) + ; + + /* Tell the thread to abort any request it's handling now. */ + if( m_sThreadIsActive ) + m_sThreadShouldAbort = true; +} + +/* + * (c) 2004 Glenn Maynard + * 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. + */ diff --git a/stepmania/src/RageUtil_BackgroundLoader.h b/stepmania/src/RageUtil_BackgroundLoader.h new file mode 100644 index 0000000000..8bf591e4e5 --- /dev/null +++ b/stepmania/src/RageUtil_BackgroundLoader.h @@ -0,0 +1,86 @@ +/* XXX */ + +#ifndef RAGE_UTIL_BACKGROUND_LOADER_H +#define RAGE_UTIL_BACKGROUND_LOADER_H + +#include "RageThreads.h" +#include + +class RageFileDriverCached; + +class BackgroundLoader +{ +public: + BackgroundLoader(); + + /* Note that destruction of this object will wait for any existing requests + * to finish aborting before returning. */ + ~BackgroundLoader(); + + /* Read the file in a background thread. Files will be read in the order requested. */ + void CacheFile( const CString &file ); + + /* Return true if the requested CacheFile request has finished. If true is returned, + * the cached file can be read using the path returned in sActualPath. */ + bool IsCacheFileFinished( const CString &sFile, CString &sActualPath ); + + /* Call this when finished with a cached file, to release any resources. */ + void FinishedWithCachedFile( const CString &sFile ); + + /* Abort all loads. */ + void Abort(); + +private: + RageThread m_LoadThread; + bool m_bShutdownThread; + void LoadThread(); + static int LoadThread_Start( void *p ) { ((BackgroundLoader *) p)->LoadThread(); return 0; } + + CString GetRequest(); + + CString GetCachePath( CString sPath ) const; + CString m_sCachePathPrefix; + + RageSemaphore m_StartSem; + + /* Lock before accessing any of the rest of the object. Don't keep this locked + * while doing expensive operations, like reading files. */ + RageMutex m_Mutex; + + vector m_CacheRequests; + + /* Filename to number of completed requests */ + map m_FinishedRequests; + + bool m_sThreadIsActive; + bool m_sThreadShouldAbort; + + RageFileDriverCached *m_pDriver; +}; + +#endif + +/* + * (c) 2004 Glenn Maynard + * 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. + */