From 39ed87570bdb2f86969d4be821c94b722dc71179 Mon Sep 17 00:00:00 2001 From: Joe Ludwig Date: Wed, 26 Jun 2013 15:22:04 -0700 Subject: First version of the SOurce SDK 2013 --- sp/src/tier1/commandbuffer.cpp | 636 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 636 insertions(+) create mode 100644 sp/src/tier1/commandbuffer.cpp (limited to 'sp/src/tier1/commandbuffer.cpp') diff --git a/sp/src/tier1/commandbuffer.cpp b/sp/src/tier1/commandbuffer.cpp new file mode 100644 index 00000000..e4968b90 --- /dev/null +++ b/sp/src/tier1/commandbuffer.cpp @@ -0,0 +1,636 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// $NoKeywords: $ +//===========================================================================// + +#include "tier1/CommandBuffer.h" +#include "tier1/utlbuffer.h" +#include "tier1/strtools.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +#define MAX_ALIAS_NAME 32 +#define MAX_COMMAND_LENGTH 1024 + +struct cmdalias_t +{ + cmdalias_t *next; + char name[ MAX_ALIAS_NAME ]; + char *value; +}; + + +//----------------------------------------------------------------------------- +// Constructor, destructor +//----------------------------------------------------------------------------- +CCommandBuffer::CCommandBuffer( ) : m_Commands( 32, 32 ) +{ + m_hNextCommand = m_Commands.InvalidIndex(); + m_nWaitDelayTicks = 1; + m_nCurrentTick = 0; + m_nLastTickToProcess = -1; + m_nArgSBufferSize = 0; + m_bIsProcessingCommands = false; + m_nMaxArgSBufferLength = ARGS_BUFFER_LENGTH; +} + +CCommandBuffer::~CCommandBuffer() +{ +} + + +//----------------------------------------------------------------------------- +// Indicates how long to delay when encoutering a 'wait' command +//----------------------------------------------------------------------------- +void CCommandBuffer::SetWaitDelayTime( int nTickDelay ) +{ + Assert( nTickDelay >= 0 ); + m_nWaitDelayTicks = nTickDelay; +} + + +//----------------------------------------------------------------------------- +// Specifies a max limit of the args buffer. For unittesting. Size == 0 means use default +//----------------------------------------------------------------------------- +void CCommandBuffer::LimitArgumentBufferSize( int nSize ) +{ + if ( nSize > ARGS_BUFFER_LENGTH ) + { + nSize = ARGS_BUFFER_LENGTH; + } + + m_nMaxArgSBufferLength = ( nSize == 0 ) ? ARGS_BUFFER_LENGTH : nSize; +} + + +//----------------------------------------------------------------------------- +// Parses argv0 out of the buffer +//----------------------------------------------------------------------------- +bool CCommandBuffer::ParseArgV0( CUtlBuffer &buf, char *pArgV0, int nMaxLen, const char **pArgS ) +{ + pArgV0[0] = 0; + *pArgS = NULL; + + if ( !buf.IsValid() ) + return false; + + int nSize = buf.ParseToken( CCommand::DefaultBreakSet(), pArgV0, nMaxLen ); + if ( ( nSize <= 0 ) || ( nMaxLen == nSize ) ) + return false; + + int nArgSLen = buf.TellMaxPut() - buf.TellGet(); + *pArgS = (nArgSLen > 0) ? (const char*)buf.PeekGet() : NULL; + return true; +} + + +//----------------------------------------------------------------------------- +// Insert a command into the command queue +//----------------------------------------------------------------------------- +void CCommandBuffer::InsertCommandAtAppropriateTime( int hCommand ) +{ + int i; + Command_t &command = m_Commands[hCommand]; + for ( i = m_Commands.Head(); i != m_Commands.InvalidIndex(); i = m_Commands.Next(i) ) + { + if ( m_Commands[i].m_nTick > command.m_nTick ) + break; + } + m_Commands.LinkBefore( i, hCommand ); +} + + +//----------------------------------------------------------------------------- +// Insert a command into the command queue at the appropriate time +//----------------------------------------------------------------------------- +void CCommandBuffer::InsertImmediateCommand( int hCommand ) +{ + m_Commands.LinkBefore( m_hNextCommand, hCommand ); +} + + +//----------------------------------------------------------------------------- +// Insert a command into the command queue +//----------------------------------------------------------------------------- +bool CCommandBuffer::InsertCommand( const char *pArgS, int nCommandSize, int nTick ) +{ + if ( nCommandSize >= CCommand::MaxCommandLength() ) + { + Warning( "WARNING: Command too long... ignoring!\n%s\n", pArgS ); + return false; + } + + // Add one for null termination + if ( m_nArgSBufferSize + nCommandSize + 1 > m_nMaxArgSBufferLength ) + { + Compact(); + if ( m_nArgSBufferSize + nCommandSize + 1 > m_nMaxArgSBufferLength ) + return false; + } + + memcpy( &m_pArgSBuffer[m_nArgSBufferSize], pArgS, nCommandSize ); + m_pArgSBuffer[m_nArgSBufferSize + nCommandSize] = 0; + ++nCommandSize; + + int hCommand = m_Commands.Alloc(); + Command_t &command = m_Commands[hCommand]; + command.m_nTick = nTick; + command.m_nFirstArgS = m_nArgSBufferSize; + command.m_nBufferSize = nCommandSize; + + m_nArgSBufferSize += nCommandSize; + + if ( !m_bIsProcessingCommands || ( nTick > m_nCurrentTick ) ) + { + InsertCommandAtAppropriateTime( hCommand ); + } + else + { + InsertImmediateCommand( hCommand ); + } + return true; +} + + +//----------------------------------------------------------------------------- +// Returns the length of the next command +//----------------------------------------------------------------------------- +void CCommandBuffer::GetNextCommandLength( const char *pText, int nMaxLen, int *pCommandLength, int *pNextCommandOffset ) +{ + int nCommandLength = 0; + int nNextCommandOffset; + bool bIsQuoted = false; + bool bIsCommented = false; + for ( nNextCommandOffset=0; nNextCommandOffset < nMaxLen; ++nNextCommandOffset, nCommandLength += bIsCommented ? 0 : 1 ) + { + char c = pText[nNextCommandOffset]; + if ( !bIsCommented ) + { + if ( c == '"' ) + { + bIsQuoted = !bIsQuoted; + continue; + } + + // don't break if inside a C++ style comment + if ( !bIsQuoted && c == '/' ) + { + bIsCommented = ( nNextCommandOffset < nMaxLen-1 ) && pText[nNextCommandOffset+1] == '/'; + if ( bIsCommented ) + { + ++nNextCommandOffset; + continue; + } + } + + // don't break if inside a quoted string + if ( !bIsQuoted && c == ';' ) + break; + } + + // FIXME: This is legacy behavior; should we not break if a \n is inside a quoted string? + if ( c == '\n' ) + break; + } + + *pCommandLength = nCommandLength; + *pNextCommandOffset = nNextCommandOffset; +} + + +//----------------------------------------------------------------------------- +// Add text to command buffer, return false if it couldn't owing to overflow +//----------------------------------------------------------------------------- +bool CCommandBuffer::AddText( const char *pText, int nTickDelay ) +{ + Assert( nTickDelay >= 0 ); + + int nLen = Q_strlen( pText ); + int nTick = m_nCurrentTick + nTickDelay; + + // Parse the text into distinct commands + const char *pCurrentCommand = pText; + int nOffsetToNextCommand; + for( ; nLen > 0; nLen -= nOffsetToNextCommand+1, pCurrentCommand += nOffsetToNextCommand+1 ) + { + // find a \n or ; line break + int nCommandLength; + GetNextCommandLength( pCurrentCommand, nLen, &nCommandLength, &nOffsetToNextCommand ); + if ( nCommandLength <= 0 ) + continue; + + const char *pArgS; + char *pArgV0 = (char*)_alloca( nCommandLength+1 ); + CUtlBuffer bufParse( pCurrentCommand, nCommandLength, CUtlBuffer::TEXT_BUFFER | CUtlBuffer::READ_ONLY ); + ParseArgV0( bufParse, pArgV0, nCommandLength+1, &pArgS ); + if ( pArgV0[0] == 0 ) + continue; + + // Deal with the special 'wait' command + if ( !Q_stricmp( pArgV0, "wait" ) && IsWaitEnabled() ) + { + int nDelay = pArgS ? atoi( pArgS ) : m_nWaitDelayTicks; + nTick += nDelay; + continue; + } + + if ( !InsertCommand( pCurrentCommand, nCommandLength, nTick ) ) + return false; + } + + return true; +} + + +//----------------------------------------------------------------------------- +// Are we in the middle of processing commands? +//----------------------------------------------------------------------------- +bool CCommandBuffer::IsProcessingCommands() +{ + return m_bIsProcessingCommands; +} + + +//----------------------------------------------------------------------------- +// Delays all queued commands to execute at a later time +//----------------------------------------------------------------------------- +void CCommandBuffer::DelayAllQueuedCommands( int nDelay ) +{ + if ( nDelay <= 0 ) + return; + + for ( int i = m_Commands.Head(); i != m_Commands.InvalidIndex(); i = m_Commands.Next(i) ) + { + m_Commands[i].m_nTick += nDelay; + } +} + + +//----------------------------------------------------------------------------- +// Call this to begin iterating over all commands up to flCurrentTime +//----------------------------------------------------------------------------- +void CCommandBuffer::BeginProcessingCommands( int nDeltaTicks ) +{ + if ( nDeltaTicks == 0 ) + return; + + Assert( !m_bIsProcessingCommands ); + m_bIsProcessingCommands = true; + m_nLastTickToProcess = m_nCurrentTick + nDeltaTicks - 1; + + // Necessary to insert commands while commands are being processed + m_hNextCommand = m_Commands.Head(); +} + + +//----------------------------------------------------------------------------- +// Returns the next command +//----------------------------------------------------------------------------- +bool CCommandBuffer::DequeueNextCommand( ) +{ + m_CurrentCommand.Reset(); + + Assert( m_bIsProcessingCommands ); + if ( m_Commands.Count() == 0 ) + return false; + + int nHead = m_Commands.Head(); + Command_t &command = m_Commands[ nHead ]; + if ( command.m_nTick > m_nLastTickToProcess ) + return false; + + m_nCurrentTick = command.m_nTick; + + // Copy the current command into a temp buffer + // NOTE: This is here to avoid the pointers returned by DequeueNextCommand + // to become invalid by calling AddText. Is there a way we can avoid the memcpy? + if ( command.m_nBufferSize > 0 ) + { + m_CurrentCommand.Tokenize( &m_pArgSBuffer[command.m_nFirstArgS] ); + } + + m_Commands.Remove( nHead ); + + // Necessary to insert commands while commands are being processed + m_hNextCommand = m_Commands.Head(); + +// Msg("Dequeue : "); +// for ( int i = 0; i < nArgc; ++i ) +// { +// Msg("%s ", m_pCurrentArgv[i] ); +// } +// Msg("\n"); + return true; +} + + +//----------------------------------------------------------------------------- +// Returns the next command +//----------------------------------------------------------------------------- +int CCommandBuffer::DequeueNextCommand( const char **& ppArgv ) +{ + DequeueNextCommand(); + ppArgv = ArgV(); + return ArgC(); +} + + +//----------------------------------------------------------------------------- +// Compacts the command buffer +//----------------------------------------------------------------------------- +void CCommandBuffer::Compact() +{ + // Compress argvbuffer + argv + // NOTE: I'm using this choice instead of calling malloc + free + // per command to allocate arguments because I expect to post a + // bunch of commands but not have many delayed commands; + // avoiding the allocation cost seems more important that the memcpy + // cost here since I expect to not have much to copy. + m_nArgSBufferSize = 0; + + char pTempBuffer[ ARGS_BUFFER_LENGTH ]; + for ( int i = m_Commands.Head(); i != m_Commands.InvalidIndex(); i = m_Commands.Next(i) ) + { + Command_t &command = m_Commands[ i ]; + + memcpy( &pTempBuffer[m_nArgSBufferSize], &m_pArgSBuffer[command.m_nFirstArgS], command.m_nBufferSize ); + command.m_nFirstArgS = m_nArgSBufferSize; + m_nArgSBufferSize += command.m_nBufferSize; + } + + // NOTE: We could also store 2 buffers in the command buffer and switch + // between the two to avoid the 2nd memcpy; but again I'm guessing the memory + // tradeoff isn't worth it + memcpy( m_pArgSBuffer, pTempBuffer, m_nArgSBufferSize ); +} + + +//----------------------------------------------------------------------------- +// Call this to finish iterating over all commands +//----------------------------------------------------------------------------- +void CCommandBuffer::EndProcessingCommands() +{ + Assert( m_bIsProcessingCommands ); + m_bIsProcessingCommands = false; + m_nCurrentTick = m_nLastTickToProcess + 1; + m_hNextCommand = m_Commands.InvalidIndex(); + + // Extract commands that are before the end time + // NOTE: This is a bug for this to + int i = m_Commands.Head(); + if ( i == m_Commands.InvalidIndex() ) + { + m_nArgSBufferSize = 0; + return; + } + + while ( i != m_Commands.InvalidIndex() ) + { + if ( m_Commands[i].m_nTick >= m_nCurrentTick ) + break; + + AssertMsgOnce( false, "CCommandBuffer::EndProcessingCommands() called before all appropriate commands were dequeued.\n" ); + int nNext = i; + Msg( "Warning: Skipping command %s\n", &m_pArgSBuffer[ m_Commands[i].m_nFirstArgS ] ); + m_Commands.Remove( i ); + i = nNext; + } + + Compact(); +} + + +//----------------------------------------------------------------------------- +// Returns a handle to the next command to process +//----------------------------------------------------------------------------- +CommandHandle_t CCommandBuffer::GetNextCommandHandle() +{ + Assert( m_bIsProcessingCommands ); + return m_Commands.Head(); +} + + +#if 0 +/* +=============== +Cmd_Alias_f + +Creates a new command that executes a command string (possibly ; seperated) +=============== +*/ +void Cmd_Alias_f (void) +{ + cmdalias_t *a; + char cmd[MAX_COMMAND_LENGTH]; + int i, c; + char *s; + + if (Cmd_Argc() == 1) + { + Con_Printf ("Current alias commands:\n"); + for (a = cmd_alias ; a ; a=a->next) + Con_Printf ("%s : %s\n", a->name, a->value); + return; + } + + s = Cmd_Argv(1); + if (strlen(s) >= MAX_ALIAS_NAME) + { + Con_Printf ("Alias name is too long\n"); + return; + } + +// copy the rest of the command line + cmd[0] = 0; // start out with a null string + c = Cmd_Argc(); + for (i=2 ; i< c ; i++) + { + Q_strncat(cmd, Cmd_Argv(i), sizeof( cmd ), COPY_ALL_CHARACTERS); + if (i != c) + { + Q_strncat (cmd, " ", sizeof( cmd ), COPY_ALL_CHARACTERS ); + } + } + Q_strncat (cmd, "\n", sizeof( cmd ), COPY_ALL_CHARACTERS); + + // if the alias already exists, reuse it + for (a = cmd_alias ; a ; a=a->next) + { + if (!strcmp(s, a->name)) + { + if ( !strcmp( a->value, cmd ) ) // Re-alias the same thing + return; + + delete[] a->value; + break; + } + } + + if (!a) + { + a = (cmdalias_t *)new cmdalias_t; + a->next = cmd_alias; + cmd_alias = a; + } + Q_strncpy (a->name, s, sizeof( a->name ) ); + + a->value = COM_StringCopy(cmd); +} + + + +/* +============================================================================= + + COMMAND EXECUTION + +============================================================================= +*/ + +#define MAX_ARGS 80 + +static int cmd_argc; +static char *cmd_argv[MAX_ARGS]; +static char *cmd_null_string = ""; +static const char *cmd_args = NULL; + +cmd_source_t cmd_source; + +//----------------------------------------------------------------------------- +// Purpose: +// Output : void Cmd_Init +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void Cmd_Shutdown( void ) +{ + // TODO, cleanup + while ( cmd_alias ) + { + cmdalias_t *next = cmd_alias->next; + delete cmd_alias->value; // created by StringCopy() + delete cmd_alias; + cmd_alias = next; + } +} + + + +/* +============ +Cmd_ExecuteString + +A complete command line has been parsed, so try to execute it +FIXME: lookupnoadd the token to speed search? +============ +*/ +const ConCommandBase *Cmd_ExecuteString (const char *text, cmd_source_t src) +{ + cmdalias_t *a; + + cmd_source = src; + Cmd_TokenizeString (text); + +// execute the command line + if (!Cmd_Argc()) + return NULL; // no tokens + +// check alias + for (a=cmd_alias ; a ; a=a->next) + { + if (!Q_strcasecmp (cmd_argv[0], a->name)) + { + Cbuf_InsertText (a->value); + return NULL; + } + } + +// check ConCommands + ConCommandBase const *pCommand = ConCommandBase::FindCommand( cmd_argv[ 0 ] ); + if ( pCommand && pCommand->IsCommand() ) + { + bool isServerCommand = ( pCommand->IsBitSet( FCVAR_GAMEDLL ) && + // Typed at console + cmd_source == src_command && + // Not HLDS + !sv.IsDedicated() ); + + // Hook to allow game .dll to figure out who type the message on a listen server + if ( serverGameClients ) + { + // We're actually the server, so set it up locally + if ( sv.IsActive() ) + { + g_pServerPluginHandler->SetCommandClient( -1 ); + +#ifndef SWDS + // Special processing for listen server player + if ( isServerCommand ) + { + g_pServerPluginHandler->SetCommandClient( cl.m_nPlayerSlot ); + } +#endif + } + // We're not the server, but we've been a listen server (game .dll loaded) + // forward this command tot he server instead of running it locally if we're still + // connected + // Otherwise, things like "say" won't work unless you quit and restart + else if ( isServerCommand ) + { + if ( cl.IsConnected() ) + { + Cmd_ForwardToServer(); + return NULL; + } + else + { + // It's a server command, but we're not connected to a server. Don't try to execute it. + return NULL; + } + } + } + + // Allow cheat commands in singleplayer, debug, or multiplayer with sv_cheats on +#ifndef _DEBUG + if ( pCommand->IsBitSet( FCVAR_CHEAT ) ) + { + if ( !Host_IsSinglePlayerGame() && sv_cheats.GetInt() == 0 ) + { + Msg( "Can't use cheat command %s in multiplayer, unless the server has sv_cheats set to 1.\n", pCommand->GetName() ); + return NULL; + } + } +#endif + + (( ConCommand * )pCommand )->Dispatch(); + return pCommand; + } + + // check cvars + if ( cv->IsCommand() ) + { + return pCommand; + } + + // forward the command line to the server, so the entity DLL can parse it + if ( cmd_source == src_command ) + { + if ( cl.IsConnected() ) + { + Cmd_ForwardToServer(); + return NULL; + } + } + + Msg("Unknown command \"%s\"\n", Cmd_Argv(0)); + + return NULL; +} +#endif -- cgit v1.2.3