You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
573 lines
16 KiB
573 lines
16 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
// Purpose: Defines a group of app systems that all have the same lifetime |
|
// that need to be connected/initialized, etc. in a well-defined order |
|
// |
|
// $Revision: $ |
|
// $NoKeywords: $ |
|
//=============================================================================// |
|
|
|
#include "appframework/IAppSystemGroup.h" |
|
#include "appframework/IAppSystem.h" |
|
#include "interface.h" |
|
#include "filesystem.h" |
|
#include "filesystem_init.h" |
|
|
|
// memdbgon must be the last include file in a .cpp file!!! |
|
#include "tier0/memdbgon.h" |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// constructor, destructor |
|
//----------------------------------------------------------------------------- |
|
//extern ILoggingListener *g_pDefaultLoggingListener; |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// constructor, destructor |
|
//----------------------------------------------------------------------------- |
|
CAppSystemGroup::CAppSystemGroup( CAppSystemGroup *pAppSystemParent ) : m_SystemDict(false, 0, 16) |
|
{ |
|
m_pParentAppSystem = pAppSystemParent; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Actually loads a DLL |
|
//----------------------------------------------------------------------------- |
|
CSysModule *CAppSystemGroup::LoadModuleDLL( const char *pDLLName ) |
|
{ |
|
return Sys_LoadModule( pDLLName ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Methods to load + unload DLLs |
|
//----------------------------------------------------------------------------- |
|
AppModule_t CAppSystemGroup::LoadModule( const char *pDLLName ) |
|
{ |
|
// Remove the extension when creating the name. |
|
int nLen = Q_strlen( pDLLName ) + 1; |
|
char *pModuleName = (char*)stackalloc( nLen ); |
|
Q_StripExtension( pDLLName, pModuleName, nLen ); |
|
|
|
// See if we already loaded it... |
|
for ( int i = m_Modules.Count(); --i >= 0; ) |
|
{ |
|
if ( m_Modules[i].m_pModuleName ) |
|
{ |
|
if ( !Q_stricmp( pModuleName, m_Modules[i].m_pModuleName ) ) |
|
return i; |
|
} |
|
} |
|
|
|
CSysModule *pSysModule = LoadModuleDLL( pDLLName ); |
|
if (!pSysModule) |
|
{ |
|
Warning("AppFramework : Unable to load module %s!\n", pDLLName ); |
|
return APP_MODULE_INVALID; |
|
} |
|
|
|
int nIndex = m_Modules.AddToTail(); |
|
m_Modules[nIndex].m_pModule = pSysModule; |
|
m_Modules[nIndex].m_Factory = 0; |
|
m_Modules[nIndex].m_pModuleName = (char*)malloc( nLen ); |
|
Q_strncpy( m_Modules[nIndex].m_pModuleName, pModuleName, nLen ); |
|
|
|
return nIndex; |
|
} |
|
|
|
AppModule_t CAppSystemGroup::LoadModule( CreateInterfaceFn factory ) |
|
{ |
|
if (!factory) |
|
{ |
|
Warning("AppFramework : Unable to load module %p!\n", factory ); |
|
return APP_MODULE_INVALID; |
|
} |
|
|
|
// See if we already loaded it... |
|
for ( int i = m_Modules.Count(); --i >= 0; ) |
|
{ |
|
if ( m_Modules[i].m_Factory ) |
|
{ |
|
if ( m_Modules[i].m_Factory == factory ) |
|
return i; |
|
} |
|
} |
|
|
|
int nIndex = m_Modules.AddToTail(); |
|
m_Modules[nIndex].m_pModule = NULL; |
|
m_Modules[nIndex].m_Factory = factory; |
|
m_Modules[nIndex].m_pModuleName = NULL; |
|
return nIndex; |
|
} |
|
|
|
void CAppSystemGroup::UnloadAllModules() |
|
{ |
|
// NOTE: Iterate in reverse order so they are unloaded in opposite order |
|
// from loading |
|
for (int i = m_Modules.Count(); --i >= 0; ) |
|
{ |
|
if ( m_Modules[i].m_pModule ) |
|
{ |
|
Sys_UnloadModule( m_Modules[i].m_pModule ); |
|
} |
|
if ( m_Modules[i].m_pModuleName ) |
|
{ |
|
free( m_Modules[i].m_pModuleName ); |
|
} |
|
} |
|
m_Modules.RemoveAll(); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Methods to add/remove various global singleton systems |
|
//----------------------------------------------------------------------------- |
|
IAppSystem *CAppSystemGroup::AddSystem( AppModule_t module, const char *pInterfaceName ) |
|
{ |
|
if (module == APP_MODULE_INVALID) |
|
return NULL; |
|
|
|
Assert( (module >= 0) && (module < m_Modules.Count()) ); |
|
CreateInterfaceFn pFactory = m_Modules[module].m_pModule ? Sys_GetFactory( m_Modules[module].m_pModule ) : m_Modules[module].m_Factory; |
|
|
|
int retval; |
|
void *pSystem = pFactory( pInterfaceName, &retval ); |
|
if ((retval != IFACE_OK) || (!pSystem)) |
|
{ |
|
Warning("AppFramework : Unable to create system %s!\n", pInterfaceName ); |
|
return NULL; |
|
} |
|
|
|
IAppSystem *pAppSystem = static_cast<IAppSystem*>(pSystem); |
|
|
|
int sysIndex = m_Systems.AddToTail( pAppSystem ); |
|
|
|
// Inserting into the dict will help us do named lookup later |
|
MEM_ALLOC_CREDIT(); |
|
m_SystemDict.Insert( pInterfaceName, sysIndex ); |
|
return pAppSystem; |
|
} |
|
|
|
static char const *g_StageLookup[] = |
|
{ |
|
"CREATION", |
|
"CONNECTION", |
|
"PREINITIALIZATION", |
|
"INITIALIZATION", |
|
"SHUTDOWN", |
|
"POSTSHUTDOWN", |
|
"DISCONNECTION", |
|
"DESTRUCTION", |
|
"NONE", |
|
}; |
|
|
|
void CAppSystemGroup::ReportStartupFailure( int nErrorStage, int nSysIndex ) |
|
{ |
|
char const *pszStageDesc = "Unknown"; |
|
if ( nErrorStage >= 0 && nErrorStage < ARRAYSIZE( g_StageLookup ) ) |
|
{ |
|
pszStageDesc = g_StageLookup[ nErrorStage ]; |
|
} |
|
|
|
char const *pszSystemName = "(Unknown)"; |
|
for ( int i = m_SystemDict.First(); i != m_SystemDict.InvalidIndex(); i = m_SystemDict.Next( i ) ) |
|
{ |
|
if ( m_SystemDict[ i ] != nSysIndex ) |
|
continue; |
|
|
|
pszSystemName = m_SystemDict.GetElementName( i ); |
|
break; |
|
} |
|
|
|
// Walk the dictionary |
|
Warning( "System (%s) failed during stage %s\n", pszSystemName, pszStageDesc ); |
|
} |
|
|
|
void CAppSystemGroup::AddSystem( IAppSystem *pAppSystem, const char *pInterfaceName ) |
|
{ |
|
if ( !pAppSystem ) |
|
return; |
|
|
|
int sysIndex = m_Systems.AddToTail( pAppSystem ); |
|
|
|
// Inserting into the dict will help us do named lookup later |
|
MEM_ALLOC_CREDIT(); |
|
m_SystemDict.Insert( pInterfaceName, sysIndex ); |
|
} |
|
|
|
void CAppSystemGroup::RemoveAllSystems() |
|
{ |
|
// NOTE: There's no deallcation here since we don't really know |
|
// how the allocation has happened. We could add a deallocation method |
|
// to the code in interface.h; although when the modules are unloaded |
|
// the deallocation will happen anyways |
|
m_Systems.RemoveAll(); |
|
m_SystemDict.RemoveAll(); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Simpler method of doing the LoadModule/AddSystem thing. |
|
//----------------------------------------------------------------------------- |
|
bool CAppSystemGroup::AddSystems( AppSystemInfo_t *pSystemList ) |
|
{ |
|
while ( pSystemList->m_pModuleName[0] ) |
|
{ |
|
AppModule_t module = LoadModule( pSystemList->m_pModuleName ); |
|
IAppSystem *pSystem = AddSystem( module, pSystemList->m_pInterfaceName ); |
|
if ( !pSystem ) |
|
{ |
|
Warning( "Unable to load interface %s from %s\n", pSystemList->m_pInterfaceName, pSystemList->m_pModuleName ); |
|
return false; |
|
} |
|
++pSystemList; |
|
} |
|
|
|
return true; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Methods to find various global singleton systems |
|
//----------------------------------------------------------------------------- |
|
void *CAppSystemGroup::FindSystem( const char *pSystemName ) |
|
{ |
|
unsigned short i = m_SystemDict.Find( pSystemName ); |
|
if (i != m_SystemDict.InvalidIndex()) |
|
return m_Systems[m_SystemDict[i]]; |
|
|
|
// If it's not an interface we know about, it could be an older |
|
// version of an interface, or maybe something implemented by |
|
// one of the instantiated interfaces... |
|
|
|
// QUESTION: What order should we iterate this in? |
|
// It controls who wins if multiple ones implement the same interface |
|
for ( i = 0; i < m_Systems.Count(); ++i ) |
|
{ |
|
void *pInterface = m_Systems[i]->QueryInterface( pSystemName ); |
|
if (pInterface) |
|
return pInterface; |
|
} |
|
|
|
if ( m_pParentAppSystem ) |
|
{ |
|
void* pInterface = m_pParentAppSystem->FindSystem( pSystemName ); |
|
if ( pInterface ) |
|
return pInterface; |
|
} |
|
|
|
// No dice.. |
|
return NULL; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Gets at the parent appsystem group |
|
//----------------------------------------------------------------------------- |
|
CAppSystemGroup *CAppSystemGroup::GetParent() |
|
{ |
|
return m_pParentAppSystem; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Method to connect/disconnect all systems |
|
//----------------------------------------------------------------------------- |
|
bool CAppSystemGroup::ConnectSystems() |
|
{ |
|
for (int i = 0; i < m_Systems.Count(); ++i ) |
|
{ |
|
IAppSystem *sys = m_Systems[i]; |
|
|
|
if (!sys->Connect( GetFactory() )) |
|
{ |
|
ReportStartupFailure( CONNECTION, i ); |
|
return false; |
|
} |
|
} |
|
return true; |
|
} |
|
|
|
void CAppSystemGroup::DisconnectSystems() |
|
{ |
|
// Disconnect in reverse order of connection |
|
for (int i = m_Systems.Count(); --i >= 0; ) |
|
{ |
|
m_Systems[i]->Disconnect(); |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Method to initialize/shutdown all systems |
|
//----------------------------------------------------------------------------- |
|
InitReturnVal_t CAppSystemGroup::InitSystems() |
|
{ |
|
for (int i = 0; i < m_Systems.Count(); ++i ) |
|
{ |
|
InitReturnVal_t nRetVal = m_Systems[i]->Init(); |
|
if ( nRetVal != INIT_OK ) |
|
{ |
|
ReportStartupFailure( INITIALIZATION, i ); |
|
return nRetVal; |
|
} |
|
} |
|
return INIT_OK; |
|
} |
|
|
|
void CAppSystemGroup::ShutdownSystems() |
|
{ |
|
// Shutdown in reverse order of initialization |
|
for (int i = m_Systems.Count(); --i >= 0; ) |
|
{ |
|
m_Systems[i]->Shutdown(); |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Returns the stage at which the app system group ran into an error |
|
//----------------------------------------------------------------------------- |
|
CAppSystemGroup::AppSystemGroupStage_t CAppSystemGroup::GetErrorStage() const |
|
{ |
|
return m_nErrorStage; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Gets at a factory that works just like FindSystem |
|
//----------------------------------------------------------------------------- |
|
// This function is used to make this system appear to the outside world to |
|
// function exactly like the currently existing factory system |
|
CAppSystemGroup *s_pCurrentAppSystem; |
|
void *AppSystemCreateInterfaceFn(const char *pName, int *pReturnCode) |
|
{ |
|
void *pInterface = s_pCurrentAppSystem->FindSystem( pName ); |
|
if ( pReturnCode ) |
|
{ |
|
*pReturnCode = pInterface ? IFACE_OK : IFACE_FAILED; |
|
} |
|
return pInterface; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Gets at a class factory for the topmost appsystem group in an appsystem stack |
|
//----------------------------------------------------------------------------- |
|
CreateInterfaceFn CAppSystemGroup::GetFactory() |
|
{ |
|
return AppSystemCreateInterfaceFn; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Main application loop |
|
//----------------------------------------------------------------------------- |
|
int CAppSystemGroup::Run() |
|
{ |
|
// The factory now uses this app system group |
|
s_pCurrentAppSystem = this; |
|
|
|
// Load, connect, init |
|
int nRetVal = OnStartup(); |
|
if ( m_nErrorStage != NONE ) |
|
return nRetVal; |
|
|
|
// Main loop implemented by the application |
|
// FIXME: HACK workaround to avoid vgui porting |
|
nRetVal = Main(); |
|
|
|
// Shutdown, disconnect, unload |
|
OnShutdown(); |
|
|
|
// The factory now uses the parent's app system group |
|
s_pCurrentAppSystem = GetParent(); |
|
|
|
return nRetVal; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Virtual methods for override |
|
//----------------------------------------------------------------------------- |
|
int CAppSystemGroup::Startup() |
|
{ |
|
return OnStartup(); |
|
} |
|
|
|
void CAppSystemGroup::Shutdown() |
|
{ |
|
return OnShutdown(); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Use this version in cases where you can't control the main loop and |
|
// expect to be ticked |
|
//----------------------------------------------------------------------------- |
|
int CAppSystemGroup::OnStartup() |
|
{ |
|
// The factory now uses this app system group |
|
s_pCurrentAppSystem = this; |
|
|
|
m_nErrorStage = NONE; |
|
|
|
// Call an installed application creation function |
|
if ( !Create() ) |
|
{ |
|
m_nErrorStage = CREATION; |
|
return -1; |
|
} |
|
|
|
// Let all systems know about each other |
|
if ( !ConnectSystems() ) |
|
{ |
|
m_nErrorStage = CONNECTION; |
|
return -1; |
|
} |
|
|
|
// Allow the application to do some work before init |
|
if ( !PreInit() ) |
|
{ |
|
m_nErrorStage = PREINITIALIZATION; |
|
return -1; |
|
} |
|
|
|
// Call Init on all App Systems |
|
int nRetVal = InitSystems(); |
|
if ( nRetVal != INIT_OK ) |
|
{ |
|
m_nErrorStage = INITIALIZATION; |
|
return -1; |
|
} |
|
|
|
return nRetVal; |
|
} |
|
|
|
void CAppSystemGroup::OnShutdown() |
|
{ |
|
// The factory now uses this app system group |
|
s_pCurrentAppSystem = this; |
|
|
|
switch( m_nErrorStage ) |
|
{ |
|
case NONE: |
|
break; |
|
|
|
case PREINITIALIZATION: |
|
case INITIALIZATION: |
|
goto disconnect; |
|
|
|
case CREATION: |
|
case CONNECTION: |
|
goto destroy; |
|
} |
|
|
|
// Cal Shutdown on all App Systems |
|
ShutdownSystems(); |
|
|
|
// Allow the application to do some work after shutdown |
|
PostShutdown(); |
|
|
|
disconnect: |
|
// Systems should disconnect from each other |
|
DisconnectSystems(); |
|
|
|
destroy: |
|
// Unload all DLLs loaded in the AppCreate block |
|
RemoveAllSystems(); |
|
|
|
// Have to do this because the logging listeners & response policies may live in modules which are being unloaded |
|
// @TODO: this seems like a bad legacy practice... app systems should unload their spew handlers gracefully. |
|
// LoggingSystem_ResetCurrentLoggingState(); |
|
// Assert( g_pDefaultLoggingListener != NULL ); |
|
// LoggingSystem_RegisterLoggingListener( g_pDefaultLoggingListener ); |
|
|
|
UnloadAllModules(); |
|
|
|
// Call an installed application destroy function |
|
Destroy(); |
|
} |
|
|
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// |
|
// This class represents a group of app systems that are loaded through steam |
|
// |
|
//----------------------------------------------------------------------------- |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Constructor |
|
//----------------------------------------------------------------------------- |
|
CSteamAppSystemGroup::CSteamAppSystemGroup( IFileSystem *pFileSystem, CAppSystemGroup *pAppSystemParent ) |
|
{ |
|
m_pFileSystem = pFileSystem; |
|
m_pGameInfoPath[0] = 0; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Used by CSteamApplication to set up necessary pointers if we can't do it in the constructor |
|
//----------------------------------------------------------------------------- |
|
void CSteamAppSystemGroup::Setup( IFileSystem *pFileSystem, CAppSystemGroup *pParentAppSystem ) |
|
{ |
|
m_pFileSystem = pFileSystem; |
|
m_pParentAppSystem = pParentAppSystem; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Loads the module from Steam |
|
//----------------------------------------------------------------------------- |
|
CSysModule *CSteamAppSystemGroup::LoadModuleDLL( const char *pDLLName ) |
|
{ |
|
return m_pFileSystem->LoadModule( pDLLName ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Returns the game info path |
|
//----------------------------------------------------------------------------- |
|
const char *CSteamAppSystemGroup::GetGameInfoPath() const |
|
{ |
|
return m_pGameInfoPath; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Sets up the search paths |
|
//----------------------------------------------------------------------------- |
|
bool CSteamAppSystemGroup::SetupSearchPaths( const char *pStartingDir, bool bOnlyUseStartingDir, bool bIsTool ) |
|
{ |
|
CFSSteamSetupInfo steamInfo; |
|
steamInfo.m_pDirectoryName = pStartingDir; |
|
steamInfo.m_bOnlyUseDirectoryName = bOnlyUseStartingDir; |
|
steamInfo.m_bToolsMode = bIsTool; |
|
steamInfo.m_bSetSteamDLLPath = true; |
|
steamInfo.m_bSteam = m_pFileSystem->IsSteam(); |
|
if ( FileSystem_SetupSteamEnvironment( steamInfo ) != FS_OK ) |
|
return false; |
|
|
|
CFSMountContentInfo fsInfo; |
|
fsInfo.m_pFileSystem = m_pFileSystem; |
|
fsInfo.m_bToolsMode = bIsTool; |
|
fsInfo.m_pDirectoryName = steamInfo.m_GameInfoPath; |
|
|
|
if ( FileSystem_MountContent( fsInfo ) != FS_OK ) |
|
return false; |
|
|
|
// Finally, load the search paths for the "GAME" path. |
|
CFSSearchPathsInit searchPathsInit; |
|
searchPathsInit.m_pDirectoryName = steamInfo.m_GameInfoPath; |
|
searchPathsInit.m_pFileSystem = fsInfo.m_pFileSystem; |
|
if ( FileSystem_LoadSearchPaths( searchPathsInit ) != FS_OK ) |
|
return false; |
|
|
|
FileSystem_AddSearchPath_Platform( fsInfo.m_pFileSystem, steamInfo.m_GameInfoPath ); |
|
Q_strncpy( m_pGameInfoPath, steamInfo.m_GameInfoPath, sizeof(m_pGameInfoPath) ); |
|
return true; |
|
}
|
|
|