GameDB: Fix long loading times of the database, by using a very efficient block-based allocation system, and reserving enough memory for about 9500 titles. Also added a thread at startup to load the gamedb in the background while PCSX2 is opening windows and printing to the console.

git-svn-id: http://pcsx2.googlecode.com/svn/trunk@3330 96395faa-99c1-11dd-bbfe-3dabce05a288
This commit is contained in:
Jake.Stine 2010-06-28 03:05:10 +00:00
parent b7f2af1160
commit 00ec820198
6 changed files with 161 additions and 73 deletions

View File

@ -16,6 +16,35 @@
#include "PrecompiledHeader.h" #include "PrecompiledHeader.h"
#include "GameDatabase.h" #include "GameDatabase.h"
BaseGameDatabaseImpl::BaseGameDatabaseImpl()
: gHash( 9400 )
, m_baseKey( L"Serial" )
{
m_BlockTable.reserve(48);
m_CurBlockWritePos = 0;
m_BlockTableWritePos = 0;
m_GamesPerBlock = 256;
m_BlockTable.push_back(NULL);
}
BaseGameDatabaseImpl::~BaseGameDatabaseImpl() throw()
{
for(uint blockidx=0; blockidx<=m_BlockTableWritePos; ++blockidx)
{
if( !m_BlockTable[blockidx] ) continue;
const uint endidx = (blockidx == m_BlockTableWritePos) ? m_CurBlockWritePos : m_GamesPerBlock;
for( uint gameidx=0; gameidx<endidx; ++gameidx )
m_BlockTable[blockidx][gameidx].~Game_Data();
safe_free( m_BlockTable[blockidx] );
}
}
// Sets the current game to the one matching the serial id given // Sets the current game to the one matching the serial id given
// Returns true if game found, false if not found... // Returns true if game found, false if not found...
bool BaseGameDatabaseImpl::findGame(Game_Data& dest, const wxString& id) { bool BaseGameDatabaseImpl::findGame(Game_Data& dest, const wxString& id) {
@ -25,14 +54,30 @@ bool BaseGameDatabaseImpl::findGame(Game_Data& dest, const wxString& id) {
dest.clear(); dest.clear();
return false; return false;
} }
dest = gList[iter->second]; dest = *iter->second;
return true; return true;
} }
void BaseGameDatabaseImpl::addNewGame(const Game_Data& game) Game_Data* BaseGameDatabaseImpl::createNewGame( const wxString& id )
{ {
gList.push_back(game); if(!m_BlockTable[m_BlockTableWritePos])
gHash[game.id] = gList.size()-1; m_BlockTable[m_BlockTableWritePos] = (Game_Data*)malloc(m_GamesPerBlock * sizeof(Game_Data));
Game_Data* block = m_BlockTable[m_BlockTableWritePos];
Game_Data* retval = &block[m_CurBlockWritePos];
gHash[id] = retval;
new (retval) Game_Data(id);
if( ++m_CurBlockWritePos >= m_GamesPerBlock )
{
++m_BlockTableWritePos;
m_CurBlockWritePos = 0;
m_BlockTable.push_back(NULL);
}
return retval;
} }
void BaseGameDatabaseImpl::updateGame(const Game_Data& game) void BaseGameDatabaseImpl::updateGame(const Game_Data& game)
@ -40,13 +85,12 @@ void BaseGameDatabaseImpl::updateGame(const Game_Data& game)
GameDataHash::const_iterator iter( gHash.find(game.id) ); GameDataHash::const_iterator iter( gHash.find(game.id) );
if( iter == gHash.end() ) { if( iter == gHash.end() ) {
gList.push_back(game); *(createNewGame( game.id )) = game;
gHash[game.id] = gList.size()-1;
} }
else else
{ {
// Re-assign existing vector/array entry! // Re-assign existing vector/array entry!
gList[gHash[game.id]] = game; *gHash[game.id] = game;
} }
} }
@ -54,7 +98,7 @@ void BaseGameDatabaseImpl::updateGame(const Game_Data& game)
bool Game_Data::keyExists(const wxChar* key) const { bool Game_Data::keyExists(const wxChar* key) const {
KeyPairArray::const_iterator it( kList.begin() ); KeyPairArray::const_iterator it( kList.begin() );
for ( ; it != kList.end(); ++it) { for ( ; it != kList.end(); ++it) {
if (it[0].CompareKey(key)) { if (it->CompareKey(key)) {
return true; return true;
} }
} }
@ -65,7 +109,7 @@ bool Game_Data::keyExists(const wxChar* key) const {
void Game_Data::deleteKey(const wxChar* key) { void Game_Data::deleteKey(const wxChar* key) {
KeyPairArray::iterator it( kList.begin() ); KeyPairArray::iterator it( kList.begin() );
for ( ; it != kList.end(); ++it) { for ( ; it != kList.end(); ++it) {
if (it[0].CompareKey(key)) { if (it->CompareKey(key)) {
kList.erase(it); kList.erase(it);
return; return;
} }
@ -76,8 +120,8 @@ void Game_Data::deleteKey(const wxChar* key) {
wxString Game_Data::getString(const wxChar* key) const { wxString Game_Data::getString(const wxChar* key) const {
KeyPairArray::const_iterator it( kList.begin() ); KeyPairArray::const_iterator it( kList.begin() );
for ( ; it != kList.end(); ++it) { for ( ; it != kList.end(); ++it) {
if (it[0].CompareKey(key)) { if (it->CompareKey(key)) {
return it[0].value; return it->value;
} }
} }
return wxString(); return wxString();
@ -86,11 +130,11 @@ wxString Game_Data::getString(const wxChar* key) const {
void Game_Data::writeString(const wxString& key, const wxString& value) { void Game_Data::writeString(const wxString& key, const wxString& value) {
KeyPairArray::iterator it( kList.begin() ); KeyPairArray::iterator it( kList.begin() );
for ( ; it != kList.end(); ++it) { for ( ; it != kList.end(); ++it) {
if (it[0].CompareKey(key)) { if (it->CompareKey(key)) {
if( value.IsEmpty() ) if( value.IsEmpty() )
kList.erase(it); kList.erase(it);
else else
it[0].value = value; it->value = value;
return; return;
} }
} }

View File

@ -18,12 +18,25 @@
//#include "Common.h" //#include "Common.h"
#include "AppConfig.h" #include "AppConfig.h"
#include "Utilities/HashMap.h" #include "Utilities/HashMap.h"
#include "Utilities/SafeArray.h"
#include <wx/wfstream.h> #include <wx/wfstream.h>
struct key_pair; struct key_pair;
struct Game_Data; struct Game_Data;
class StringHashNoCase
{
public:
StringHashNoCase() {}
HashTools::hash_key_t operator()( const wxString& src ) const
{
return HashTools::Hash( (const char *)src.Lower().data(), src.length() * sizeof( wxChar ) );
}
};
typedef std::vector<key_pair> KeyPairArray; typedef std::vector<key_pair> KeyPairArray;
struct key_pair { struct key_pair {
@ -159,23 +172,11 @@ public:
virtual wxString getBaseKey() const=0; virtual wxString getBaseKey() const=0;
virtual bool findGame(Game_Data& dest, const wxString& id)=0; virtual bool findGame(Game_Data& dest, const wxString& id)=0;
virtual void addNewGame(const Game_Data& game)=0; virtual Game_Data* createNewGame( const wxString& id )=0;
virtual void updateGame(const Game_Data& game)=0; virtual void updateGame(const Game_Data& game)=0;
}; };
class StringHashNoCase typedef pxDictionary<Game_Data*,StringHashNoCase> GameDataHash;
{
public:
StringHashNoCase() {}
HashTools::hash_key_t operator()( const wxString& src ) const
{
return HashTools::Hash( (const char *)src.Lower().data(), src.length() * sizeof( wxChar ) );
}
};
typedef std::vector<Game_Data> GameDataArray;
typedef pxDictionary<int,StringHashNoCase> GameDataHash;
// -------------------------------------------------------------------------------------- // --------------------------------------------------------------------------------------
// BaseGameDatabaseImpl // BaseGameDatabaseImpl
@ -184,24 +185,24 @@ typedef pxDictionary<int,StringHashNoCase> GameDataHash;
// faster that way. // faster that way.
class BaseGameDatabaseImpl : public IGameDatabase class BaseGameDatabaseImpl : public IGameDatabase
{ {
public: protected:
GameDataArray gList; // List of all game data
GameDataHash gHash; // hash table of game serials matched to their gList indexes! GameDataHash gHash; // hash table of game serials matched to their gList indexes!
wxString m_baseKey; wxString m_baseKey;
public: std::vector<Game_Data*> m_BlockTable;
BaseGameDatabaseImpl() uint m_BlockTableWritePos;
{ int m_CurBlockWritePos;
m_baseKey = L"Serial"; int m_GamesPerBlock;
}
virtual ~BaseGameDatabaseImpl() throw() {} public:
BaseGameDatabaseImpl();
virtual ~BaseGameDatabaseImpl() throw();
wxString getBaseKey() const { return m_baseKey; } wxString getBaseKey() const { return m_baseKey; }
void setBaseKey( const wxString& key ) { m_baseKey = key; } void setBaseKey( const wxString& key ) { m_baseKey = key; }
bool findGame(Game_Data& dest, const wxString& id); bool findGame(Game_Data& dest, const wxString& id);
void addNewGame(const Game_Data& game); Game_Data* createNewGame( const wxString& id );
void updateGame(const Game_Data& game); void updateGame(const Game_Data& game);
}; };

View File

@ -118,13 +118,14 @@ wxString DBLoaderHelper::ReadHeader()
void DBLoaderHelper::ReadGames() void DBLoaderHelper::ReadGames()
{ {
Game_Data game; Game_Data* game = NULL;
if (m_keyPair.IsOk()) if (m_keyPair.IsOk())
{ {
game.writeString(m_keyPair.key, m_keyPair.value); game = m_gamedb.createNewGame(m_keyPair.value);
if( m_keyPair.CompareKey(m_gamedb.getBaseKey()) ) game->writeString(m_keyPair.key, m_keyPair.value);
game.id = m_keyPair.value; //if( m_keyPair.CompareKey(m_gamedb.getBaseKey()) )
// game.id = m_keyPair.value;
} }
while(!m_reader.Eof()) { // Fill game data, find new game, repeat... while(!m_reader.Eof()) { // Fill game data, find new game, repeat...
@ -136,13 +137,10 @@ void DBLoaderHelper::ReadGames()
if (!extractMultiLine()) extract(); if (!extractMultiLine()) extract();
if (!m_keyPair.IsOk()) continue; if (!m_keyPair.IsOk()) continue;
if( m_keyPair.CompareKey(m_gamedb.getBaseKey()) ) if (m_keyPair.CompareKey(m_gamedb.getBaseKey()))
{ game = m_gamedb.createNewGame(m_keyPair.value);
m_gamedb.addNewGame(game);
game.clear(); game->writeString( m_keyPair.key, m_keyPair.value );
game.id = m_keyPair.value;
}
game.writeString( m_keyPair.key, m_keyPair.value );
} }
} }
@ -168,8 +166,13 @@ AppGameDatabase& AppGameDatabase::LoadFromFile(const wxString& file, const wxStr
DBLoaderHelper loader( reader, *this ); DBLoaderHelper loader( reader, *this );
u64 qpc_Start = GetCPUTicks();
header = loader.ReadHeader(); header = loader.ReadHeader();
loader.ReadGames(); loader.ReadGames();
u64 qpc_end = GetCPUTicks();
Console.WriteLn( "(GameDB) %d games on record (loaded in %ums)",
gHash.size(), (u32)(((qpc_end-qpc_Start)*1000) / GetTickFrequency()) );
return *this; return *this;
} }
@ -178,13 +181,40 @@ AppGameDatabase& AppGameDatabase::LoadFromFile(const wxString& file, const wxStr
void AppGameDatabase::SaveToFile(const wxString& file) { void AppGameDatabase::SaveToFile(const wxString& file) {
wxFFileOutputStream writer( file ); wxFFileOutputStream writer( file );
pxWriteMultiline(writer, header); pxWriteMultiline(writer, header);
GameDataArray::iterator it( gList.begin() );
for ( ; it != gList.end(); ++it) { for(uint blockidx=0; blockidx<=m_BlockTableWritePos; ++blockidx)
KeyPairArray::iterator i = it[0].kList.begin(); {
for ( ; i != it[0].kList.end(); ++i) { if( !m_BlockTable[blockidx] ) continue;
pxWriteMultiline(writer, i[0].toString() );
const uint endidx = (blockidx == m_BlockTableWritePos) ? m_CurBlockWritePos : m_GamesPerBlock;
for( uint gameidx=0; gameidx<=endidx; ++gameidx )
{
const Game_Data& game( m_BlockTable[blockidx][gameidx] );
KeyPairArray::const_iterator i(game.kList.begin());
for ( ; i != game.kList.end(); ++i) {
pxWriteMultiline(writer, i->toString() );
}
pxWriteLine(writer, L"---------------------------------------------");
} }
pxWriteLine(writer, L"---------------------------------------------");
} }
} }
AppGameDatabase* Pcsx2App::GetGameDatabase()
{
pxAppResources& res( GetResourceCache() );
ScopedLock lock( m_mtx_LoadingGameDB );
if( !res.GameDB )
{
res.GameDB = new AppGameDatabase();
res.GameDB->LoadFromFile();
}
return res.GameDB;
}
IGameDatabase* AppHost_GetGameDatabase()
{
return wxGetApp().GetGameDatabase();
}

View File

@ -475,6 +475,35 @@ bool Pcsx2App::OnCmdLineParsed( wxCmdLineParser& parser )
typedef void (wxEvtHandler::*pxInvokeAppMethodEventFunction)(Pcsx2AppMethodEvent&); typedef void (wxEvtHandler::*pxInvokeAppMethodEventFunction)(Pcsx2AppMethodEvent&);
typedef void (wxEvtHandler::*pxStuckThreadEventHandler)(pxMessageBoxEvent&); typedef void (wxEvtHandler::*pxStuckThreadEventHandler)(pxMessageBoxEvent&);
// --------------------------------------------------------------------------------------
// CompressThread_gzip
// --------------------------------------------------------------------------------------
class GameDatabaseLoaderThread : public pxThread
{
typedef pxThread _parent;
protected:
gzFile m_gzfp;
public:
GameDatabaseLoaderThread() : pxThread( L"GameDatabaseLoader" ) {}
virtual ~GameDatabaseLoaderThread() throw()
{
_parent::Cancel();
}
protected:
void ExecuteTaskInThread()
{
wxGetApp().GetGameDatabase();
}
void OnCleanupInThread()
{
wxGetApp().DeleteThread(this);
}
};
bool Pcsx2App::OnInit() bool Pcsx2App::OnInit()
{ {
EnableAllLogging(); EnableAllLogging();
@ -535,9 +564,11 @@ bool Pcsx2App::OnInit()
// ------------------------------------- // -------------------------------------
if( Startup.ForceConsole ) g_Conf->ProgLogBox.Visible = true; if( Startup.ForceConsole ) g_Conf->ProgLogBox.Visible = true;
OpenProgramLog(); OpenProgramLog();
if( m_UseGUI ) OpenMainFrame();
AllocateCoreStuffs(); AllocateCoreStuffs();
if( m_UseGUI ) OpenMainFrame();
(new GameDatabaseLoaderThread())->Start();
if( Startup.SysAutoRun ) if( Startup.SysAutoRun )
{ {
// Notes: Saving/remembering the Iso file is probably fine and desired, so using // Notes: Saving/remembering the Iso file is probably fine and desired, so using

View File

@ -1088,21 +1088,3 @@ SysCoreAllocations& GetSysCoreAlloc()
{ {
return *wxGetApp().m_CoreAllocs; return *wxGetApp().m_CoreAllocs;
} }
AppGameDatabase* Pcsx2App::GetGameDatabase()
{
pxAppResources& res( GetResourceCache() );
ScopedLock lock( m_mtx_LoadingGameDB );
if( !res.GameDB )
{
res.GameDB = new AppGameDatabase();
res.GameDB->LoadFromFile();
}
return res.GameDB;
}
IGameDatabase* AppHost_GetGameDatabase()
{
return wxGetApp().GetGameDatabase();
}

View File

@ -190,7 +190,7 @@ void pxEvtHandler::ProcessEvents( pxEvtList& list )
} }
u64 qpc_end = GetCPUTicks(); u64 qpc_end = GetCPUTicks();
DevCon.WriteLn( L"(pxEvtHandler) Event '%s' completed in %dms", deleteMe->GetEventName().c_str(), ((qpc_end-m_qpc_Start)*1000) / GetTickFrequency() ); DevCon.WriteLn( L"(pxEvtHandler) Event '%s' completed in %ums", deleteMe->GetEventName().c_str(), (u32)(((qpc_end-m_qpc_Start)*1000) / GetTickFrequency()) );
synclock.Acquire(); synclock.Acquire();
m_qpc_Start = 0; // lets the main thread know the message completed. m_qpc_Start = 0; // lets the main thread know the message completed.