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.
635 lines
17 KiB
635 lines
17 KiB
//========= 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; |
|
m_bWaitEnabled = true; |
|
} |
|
|
|
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( CommandHandle_t hCommand ) |
|
{ |
|
intp 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( CommandHandle_t 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; |
|
|
|
intp 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 ( intp 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; |
|
|
|
intp 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 ( intp 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 |
|
intp 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" ); |
|
Msg( "Warning: Skipping command %s\n", &m_pArgSBuffer[ m_Commands[i].m_nFirstArgS ] ); |
|
m_Commands.Remove( i ); |
|
} |
|
|
|
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
|
|
|