diff options
| author | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
|---|---|---|
| committer | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
| commit | 3bf9df6b2785fa6d951086978a3e66f49427166a (patch) | |
| tree | 2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /engine/host_state.cpp | |
| download | archived-source-engine-2018-hl2-src-3bf9df6b2785fa6d951086978a3e66f49427166a.tar.xz archived-source-engine-2018-hl2-src-3bf9df6b2785fa6d951086978a3e66f49427166a.zip | |
Diffstat (limited to 'engine/host_state.cpp')
| -rw-r--r-- | engine/host_state.cpp | 803 |
1 files changed, 803 insertions, 0 deletions
diff --git a/engine/host_state.cpp b/engine/host_state.cpp new file mode 100644 index 0000000..edcb922 --- /dev/null +++ b/engine/host_state.cpp @@ -0,0 +1,803 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Runs the state machine for the host & server +// +// $NoKeywords: $ +//=============================================================================// + + +#include "host_state.h" +#include "eiface.h" +#include "quakedef.h" +#include "server.h" +#include "sv_main.h" +#include "host_cmd.h" +#include "host.h" +#include "screen.h" +#include "icliententity.h" +#include "icliententitylist.h" +#include "client.h" +#include "host_jmp.h" +#include "tier0/vprof.h" +#include "tier0/icommandline.h" +#include "filesystem_engine.h" +#include "zone.h" +#include "iengine.h" +#include "snd_audio_source.h" +#include "sv_steamauth.h" +#ifndef SWDS +#include "vgui_baseui_interface.h" +#endif +#include "sv_plugin.h" +#include "cl_main.h" +#include "sv_steamauth.h" +#include "datacache/imdlcache.h" +#include "sys_dll.h" +#include "testscriptmgr.h" +#if defined( REPLAY_ENABLED ) +#include "replay_internal.h" +#include "replayserver.h" +#endif +#include "GameEventManager.h" +#include "tier0/etwprof.h" + +#include "ccs.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +extern bool g_bAbortServerSet; +#ifndef SWDS +extern ConVar reload_materials; +#endif + +typedef enum +{ + HS_NEW_GAME = 0, + HS_LOAD_GAME, + HS_CHANGE_LEVEL_SP, + HS_CHANGE_LEVEL_MP, + HS_RUN, + HS_GAME_SHUTDOWN, + HS_SHUTDOWN, + HS_RESTART, +} HOSTSTATES; + +// a little class that manages the state machine for the host +class CHostState +{ +public: + CHostState(); + void Init(); + void FrameUpdate( float time ); + void SetNextState( HOSTSTATES nextState ); + + void RunGameInit(); + + void SetState( HOSTSTATES newState, bool clearNext ); + void GameShutdown(); + + void State_NewGame(); + void State_LoadGame(); + void State_ChangeLevelMP(); + void State_ChangeLevelSP(); + void State_Run( float time ); + void State_GameShutdown(); + void State_Shutdown(); + void State_Restart(); + + bool IsGameShuttingDown(); + + void RememberLocation(); + void OnClientConnected(); // Client fully connected + void OnClientDisconnected(); // Client disconnected + + HOSTSTATES m_currentState; + HOSTSTATES m_nextState; + Vector m_vecLocation; + QAngle m_angLocation; + char m_levelName[256]; + char m_landmarkName[256]; + char m_saveName[256]; + float m_flShortFrameTime; // run a few one-tick frames to avoid large timesteps while loading assets + + bool m_activeGame; + bool m_bRememberLocation; + bool m_bBackgroundLevel; + bool m_bWaitingForConnection; +}; + +static bool Host_ValidGame( void ); +static CHostState g_HostState; + + +//----------------------------------------------------------------------------- +// external API for manipulating the host state machine +//----------------------------------------------------------------------------- +void HostState_Init() +{ + g_HostState.Init(); +} + +void HostState_Frame( float time ) +{ + g_HostState.FrameUpdate( time ); +} + +void HostState_RunGameInit() +{ + g_HostState.RunGameInit(); + g_ServerGlobalVariables.bMapLoadFailed = false; +} + +//----------------------------------------------------------------------------- +// start a new game as soon as possible +//----------------------------------------------------------------------------- +void HostState_NewGame( char const *pMapName, bool remember_location, bool background ) +{ + Q_strncpy( g_HostState.m_levelName, pMapName, sizeof( g_HostState.m_levelName ) ); + + g_HostState.m_landmarkName[0] = 0; + g_HostState.m_bRememberLocation = remember_location; + g_HostState.m_bWaitingForConnection = true; + g_HostState.m_bBackgroundLevel = background; + if ( remember_location ) + { + g_HostState.RememberLocation(); + } + g_HostState.SetNextState( HS_NEW_GAME ); +} + +//----------------------------------------------------------------------------- +// load a new game as soon as possible +//----------------------------------------------------------------------------- +void HostState_LoadGame( char const *pSaveFileName, bool remember_location ) +{ +#ifndef SWDS + // Make sure the freaking save file exists.... + if ( !saverestore->SaveFileExists( pSaveFileName ) ) + { + Warning("Save file %s can't be found!\n", pSaveFileName ); + SCR_EndLoadingPlaque(); + return; + } + + Q_strncpy( g_HostState.m_saveName, pSaveFileName, sizeof( g_HostState.m_saveName ) ); + + // Tell the game .dll we are loading another game + serverGameDLL->PreSaveGameLoaded( pSaveFileName, sv.IsActive() ); + + g_HostState.m_bRememberLocation = remember_location; + g_HostState.m_bBackgroundLevel = false; + g_HostState.m_bWaitingForConnection = true; + if ( remember_location ) + { + g_HostState.RememberLocation(); + } + + g_HostState.SetNextState( HS_LOAD_GAME ); +#endif +} + +// change level (single player style - smooth transition) +void HostState_ChangeLevelSP( char const *pNewLevel, char const *pLandmarkName ) +{ + Q_strncpy( g_HostState.m_levelName, pNewLevel, sizeof( g_HostState.m_levelName ) ); + Q_strncpy( g_HostState.m_landmarkName, pLandmarkName, sizeof( g_HostState.m_landmarkName ) ); + g_HostState.SetNextState( HS_CHANGE_LEVEL_SP ); +} + +// change level (multiplayer style - respawn all connected clients) +void HostState_ChangeLevelMP( char const *pNewLevel, char const *pLandmarkName ) +{ + Steam3Server().NotifyOfLevelChange(); + + Q_strncpy( g_HostState.m_levelName, pNewLevel, sizeof( g_HostState.m_levelName ) ); + Q_strncpy( g_HostState.m_landmarkName, pLandmarkName, sizeof( g_HostState.m_landmarkName ) ); + g_HostState.SetNextState( HS_CHANGE_LEVEL_MP ); +} + +// shutdown the game as soon as possible +void HostState_GameShutdown() +{ + Steam3Server().NotifyOfLevelChange(); + + // This will get called during shutdown, ignore it. + if ( g_HostState.m_currentState != HS_SHUTDOWN && + g_HostState.m_currentState != HS_RESTART && + g_HostState.m_currentState != HS_GAME_SHUTDOWN + ) + { + g_HostState.SetNextState( HS_GAME_SHUTDOWN ); + } +} + +// shutdown the engine/program as soon as possible +void HostState_Shutdown() +{ +#if defined( REPLAY_ENABLED ) + // If we're recording the game, finalize the replay so players can download it. + if ( sv.IsDedicated() && g_pReplay && g_pReplay->IsRecording() ) + { + // Stop recording and publish all blocks/session info data synchronously. + g_pReplay->SV_EndRecordingSession( true ); + } +#endif + + g_HostState.SetNextState( HS_SHUTDOWN ); +} + +//----------------------------------------------------------------------------- +// Purpose: Restart the engine +//----------------------------------------------------------------------------- +void HostState_Restart() +{ + g_HostState.SetNextState( HS_RESTART ); +} + +bool HostState_IsGameShuttingDown() +{ + return g_HostState.IsGameShuttingDown(); +} + +bool HostState_IsShuttingDown() +{ + return ( g_HostState.m_currentState == HS_SHUTDOWN || + g_HostState.m_currentState == HS_RESTART || + g_HostState.m_currentState == HS_GAME_SHUTDOWN ); +} + + +void HostState_OnClientConnected() +{ + g_HostState.OnClientConnected(); +} + +void HostState_OnClientDisconnected() +{ + g_HostState.OnClientDisconnected(); +} + +void HostState_SetSpawnPoint(Vector &position, QAngle &angle) +{ + g_HostState.m_angLocation = angle; + g_HostState.m_vecLocation = position; + g_HostState.m_bRememberLocation = true; +} + +//----------------------------------------------------------------------------- +static void WatchDogHandler() +{ + Host_Error( "WatchdogHandler called - server exiting.\n" ); +} + +//----------------------------------------------------------------------------- +// Class implementation +//----------------------------------------------------------------------------- + +CHostState::CHostState() +{ +} + +void CHostState::Init() +{ + SetState( HS_RUN, true ); + m_currentState = HS_RUN; + m_nextState = HS_RUN; + m_activeGame = false; + m_levelName[0] = 0; + m_saveName[0] = 0; + m_landmarkName[0] = 0; + m_bRememberLocation = 0; + m_bBackgroundLevel = false; + m_vecLocation.Init(); + m_angLocation.Init(); + m_bWaitingForConnection = false; + m_flShortFrameTime = 1.0; + + CCS_Init(); + + Plat_SetWatchdogHandlerFunction( WatchDogHandler ); +} + +void CHostState::SetState( HOSTSTATES newState, bool clearNext ) +{ + m_currentState = newState; + if ( clearNext ) + { + m_nextState = newState; + } +} + +void CHostState::SetNextState( HOSTSTATES next ) +{ + Assert( m_currentState == HS_RUN ); + m_nextState = next; +} + +void CHostState::RunGameInit() +{ + Assert( !m_activeGame ); + + if ( serverGameDLL ) + { + serverGameDLL->GameInit(); + } + m_activeGame = true; +} + +void CHostState::GameShutdown() +{ + if ( m_activeGame ) + { + serverGameDLL->GameShutdown(); + m_activeGame = false; + } +} + + +// These State_ functions execute that state's code right away +// The external API queues up state changes to happen when the state machine is processed. +void CHostState::State_NewGame() +{ + CETWScope timer( "CHostState::State_NewGame" ); + + if ( Host_ValidGame() ) + { + // Demand load game .dll if running with -nogamedll flag, etc. + if ( !serverGameClients ) + { + SV_InitGameDLL(); + } + + if ( !serverGameClients ) + { + Warning( "Can't start game, no valid server.dll loaded\n" ); + } + else + { + if ( Host_NewGame( m_levelName, false, m_bBackgroundLevel ) ) + { + // succesfully started the new game + SetState( HS_RUN, true ); + return; + } + } + } + + SCR_EndLoadingPlaque(); + + // new game failed + GameShutdown(); + // run the server at the console + SetState( HS_RUN, true ); +} + +void CHostState::State_LoadGame() +{ +#ifndef SWDS + HostState_RunGameInit(); + + if ( saverestore->LoadGame( m_saveName ) ) + { + // succesfully started the new game + GetTestScriptMgr()->CheckPoint( "load_game" ); + SetState( HS_RUN, true ); + return; + } +#endif + + SCR_EndLoadingPlaque(); + + // load game failed + GameShutdown(); + // run the server at the console + SetState( HS_RUN, true ); + + if ( IsX360() ) + { + // On the 360 we need to return to the background map + g_ServerGlobalVariables.bMapLoadFailed = true; + Cbuf_Clear(); + Cbuf_AddText( "startupmenu force" ); + Cbuf_Execute(); + } +} + + +void CHostState::State_ChangeLevelMP() +{ + if ( Host_ValidGame() ) + { + Steam3Server().NotifyOfLevelChange(); + +#ifndef SWDS + // start progress bar immediately for multiplayer level transitions + EngineVGui()->EnabledProgressBarForNextLoad(); +#endif + if ( Host_Changelevel( false, m_levelName, m_landmarkName ) ) + { + SetState( HS_RUN, true ); + return; + } + } + // fail + ConMsg( "Unable to change level!\n" ); + SetState( HS_RUN, true ); + + IGameEvent *event = g_GameEventManager.CreateEvent( "server_changelevel_failed" ); + if ( event ) + { + event->SetString( "levelname", m_levelName ); + g_GameEventManager.FireEvent( event ); + } +} + + +void CHostState::State_ChangeLevelSP() +{ + if ( Host_ValidGame() ) + { + Host_Changelevel( true, m_levelName, m_landmarkName ); + SetState( HS_RUN, true ); + return; + } + // fail + ConMsg( "Unable to change level!\n" ); + SetState( HS_RUN, true ); +} + +static bool IsClientActive() +{ + if ( !sv.IsActive() ) + return cl.IsActive(); + + for ( int i = 0; i < sv.GetClientCount(); i++ ) + { + CGameClient *pClient = sv.Client( i ); + if ( pClient->IsActive() ) + return true; + } + return false; +} + +static bool IsClientConnected() +{ + if ( !sv.IsActive() ) + return cl.IsConnected(); + + for ( int i = 0; i < sv.GetClientCount(); i++ ) + { + CGameClient *pClient = sv.Client( i ); + if ( pClient->IsConnected() ) + return true; + } + return false; +} + +void CHostState::State_Run( float frameTime ) +{ + static bool s_bFirstRunFrame = true; + + if ( m_flShortFrameTime > 0 ) + { + if ( IsClientActive() ) + { + m_flShortFrameTime = (m_flShortFrameTime > frameTime) ? (m_flShortFrameTime-frameTime) : 0; + } + // Only clamp time if client is in process of connecting or is already connected. + if ( IsClientConnected() ) + { + frameTime = min( frameTime, host_state.interval_per_tick ); + } + } + + // Nice big timeout to play it safe while still ensuring that we don't get stuck in + // infinite loops. + int nTimerWaitSeconds = 60; + if ( s_bFirstRunFrame ) + { + // The first frame can take a while especially during fork startup. + s_bFirstRunFrame = false; + nTimerWaitSeconds *= 2; + } + if ( sv.IsDedicated() ) + { + Plat_BeginWatchdogTimer( nTimerWaitSeconds ); + } + + Host_RunFrame( frameTime ); + + if ( sv.IsDedicated() ) + { + Plat_EndWatchdogTimer(); + } + + switch( m_nextState ) + { + case HS_RUN: + break; + + case HS_LOAD_GAME: + case HS_NEW_GAME: +#if !defined( SWDS ) + SCR_BeginLoadingPlaque(); +#endif + // FALL THROUGH INTENTIONALLY TO SHUTDOWN + + case HS_SHUTDOWN: + case HS_RESTART: + // NOTE: The game must be shutdown before a new game can start, + // before a game can load, and before the system can be shutdown. + // This is done here instead of pathfinding through a state transition graph. + // That would be equivalent as the only way to get from HS_RUN to HS_LOAD_GAME is through HS_GAME_SHUTDOWN. + case HS_GAME_SHUTDOWN: + SetState( HS_GAME_SHUTDOWN, false ); + break; + + case HS_CHANGE_LEVEL_MP: + case HS_CHANGE_LEVEL_SP: + SetState( m_nextState, true ); + break; + + default: + SetState( HS_RUN, true ); + break; + } +} + +void CHostState::State_GameShutdown() +{ + if ( serverGameDLL ) + { + Steam3Server().NotifyOfLevelChange(); + g_pServerPluginHandler->LevelShutdown(); +#if !defined(SWDS) + audiosourcecache->LevelShutdown(); +#endif + } + + GameShutdown(); +#ifndef SWDS + saverestore->ClearSaveDir(); +#endif + Host_ShutdownServer(); + + switch( m_nextState ) + { + case HS_LOAD_GAME: + case HS_NEW_GAME: + case HS_SHUTDOWN: + case HS_RESTART: + SetState( m_nextState, true ); + break; + default: + SetState( HS_RUN, true ); + break; + } +} + + +// Tell the launcher we're done. +void CHostState::State_Shutdown() +{ + CCS_Shutdown(); + +#if !defined(SWDS) + CL_EndMovie(); +#endif + + eng->SetNextState( IEngine::DLL_CLOSE ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CHostState::State_Restart( void ) +{ + // Just like a regular shutdown + State_Shutdown(); + + // But signal launcher/front end to restart engine + eng->SetNextState( IEngine::DLL_RESTART ); +} + + +//----------------------------------------------------------------------------- +// this is the state machine's main processing loop +//----------------------------------------------------------------------------- +void CHostState::FrameUpdate( float time ) +{ + CCS_Tick( time ); + +#if _DEBUG + int loopCount = 0; +#endif + + if ( setjmp (host_abortserver) ) + { + Init(); + return; + } + + g_bAbortServerSet = true; + + while ( true ) + { + int oldState = m_currentState; + + // execute the current state (and transition to the next state if not in HS_RUN) + switch( m_currentState ) + { + case HS_NEW_GAME: + g_pMDLCache->BeginMapLoad(); + State_NewGame(); + break; + case HS_LOAD_GAME: + g_pMDLCache->BeginMapLoad(); + State_LoadGame(); + break; + case HS_CHANGE_LEVEL_MP: + g_pMDLCache->BeginMapLoad(); + m_flShortFrameTime = 0.5f; + State_ChangeLevelMP(); + break; + case HS_CHANGE_LEVEL_SP: + g_pMDLCache->BeginMapLoad(); + m_flShortFrameTime = 1.5f; // 1.5s of slower frames + State_ChangeLevelSP(); + break; + case HS_RUN: + State_Run( time ); + break; + case HS_GAME_SHUTDOWN: + State_GameShutdown(); + break; + case HS_SHUTDOWN: + State_Shutdown(); + break; + case HS_RESTART: + g_pMDLCache->BeginMapLoad(); + State_Restart(); + break; + } + + // only do a single pass at HS_RUN per frame. All other states loop until they reach HS_RUN + if ( oldState == HS_RUN ) + break; + + // shutting down + if ( oldState == HS_SHUTDOWN || + oldState == HS_RESTART ) + break; + + // Only HS_RUN is allowed to persist across loops!!! + // Also, detect circular state graphs (more than 8 cycling changes is probably a loop) + // NOTE: In the current graph, there are at most 2. +#if _DEBUG + if ( (m_currentState == oldState) || (++loopCount > 8) ) + { + Host_Error( "state crash!\n" ); + } +#endif + } +} + + +bool CHostState::IsGameShuttingDown( void ) +{ + return ( ( m_currentState == HS_GAME_SHUTDOWN ) || ( m_nextState == HS_GAME_SHUTDOWN ) ); +} + +void CHostState::RememberLocation() +{ +#ifndef SWDS + Assert( m_bRememberLocation ); + + m_vecLocation = MainViewOrigin(); + VectorAngles( MainViewForward(), m_angLocation ); + + IClientEntity *localPlayer = entitylist ? entitylist->GetClientEntity( cl.m_nPlayerSlot + 1 ) : NULL; + if ( localPlayer ) + { + m_vecLocation = localPlayer->GetAbsOrigin(); + } + + m_vecLocation.z -= 64.0f; // subtract out a bit of Z to position the player lower +#endif +} + +void CHostState::OnClientConnected() +{ + if ( !m_bWaitingForConnection ) + return; + m_bWaitingForConnection = false; + + if ( m_bRememberLocation ) + { + m_bRememberLocation = false; + Cbuf_AddText( va( "setpos_exact %f %f %f\n", m_vecLocation.x, m_vecLocation.y, m_vecLocation.z ) ); + Cbuf_AddText( va( "setang_exact %f %f %f\n", m_angLocation.x, m_angLocation.y, m_angLocation.z ) ); + } + +#if !defined( SWDS ) + if ( reload_materials.GetBool() ) + { + // building cubemaps requires the materials to reload after map + reload_materials.SetValue( 0 ); + Cbuf_AddText( "mat_reloadallmaterials\n" ); + } + + // Spew global texture memory usage if asked to + if( CommandLine()->CheckParm( "-dumpvidmemstats" ) ) + { + FileHandle_t fp; + fp = g_pFileSystem->Open( "vidmemstats.txt", "a" ); + + g_pFileSystem->FPrintf( fp, "%s:\n", g_HostState.m_levelName ); + +#ifdef VPROF_ENABLED + CVProfile *pProf = &g_VProfCurrentProfile; + + int prefixLen = strlen( "TexGroup_Global_" ); + float total = 0.0f; + for ( int i=0; i < pProf->GetNumCounters(); i++ ) + { + if ( pProf->GetCounterGroup( i ) == COUNTER_GROUP_TEXTURE_GLOBAL ) + { + // The counters are in bytes and the panel is all in kilobytes. + float value = pProf->GetCounterValue( i ) * ( 1.0f / ( 1024.0f * 1024.0f ) ); + total += value; + const char *pName = pProf->GetCounterName( i ); + if( Q_strnicmp( pName, "TexGroup_Global_", prefixLen ) == 0 ) + { + pName += prefixLen; + } + g_pFileSystem->FPrintf( fp, "%s: %0.3fMB\n", pName, value ); + } + } + g_pFileSystem->FPrintf( fp, "vidmem total: %0.3fMB\n", total ); +#endif + +#if 0 + g_pFileSystem->FPrintf( fp, "hunk total: %0.3fMB\n", Cache_TotalUsed() * ( 1.0f / ( 1024.0f * 1024.0f ) ) ); + g_pFileSystem->FPrintf( fp, "hunk sound: %0.3fMB\n", Cache_TotalUsed_Sound() * ( 1.0f / ( 1024.0f * 1024.0f ) ) ); + g_pFileSystem->FPrintf( fp, "hunk models: %0.3fMB\n", Cache_TotalUsed_Models() * ( 1.0f / ( 1024.0f * 1024.0f ) ) ); +#endif + g_pFileSystem->FPrintf( fp, "---------------------------------\n" ); + g_pFileSystem->Close( fp ); + Cbuf_AddText( "quit\n" ); + } +#endif +} + +void CHostState::OnClientDisconnected() +{ +} + +// Determine if this is a valid game +static bool Host_ValidGame( void ) +{ + // No multi-client single player games + if ( sv.IsMultiplayer() ) + { + if ( deathmatch.GetInt() ) + return true; + } + else + { + /* + // Single player must have CD validation + if ( IsValidCD() ) + { + return true; + } + */ + return true; + } + + ConDMsg("Unable to launch game\n"); + return false; +} + |