aboutsummaryrefslogtreecommitdiff
path: root/sp/src/tier1/commandbuffer.cpp
diff options
context:
space:
mode:
authorJoe Ludwig <[email protected]>2013-06-26 15:22:04 -0700
committerJoe Ludwig <[email protected]>2013-06-26 15:22:04 -0700
commit39ed87570bdb2f86969d4be821c94b722dc71179 (patch)
treeabc53757f75f40c80278e87650ea92808274aa59 /sp/src/tier1/commandbuffer.cpp
downloadsource-sdk-2013-39ed87570bdb2f86969d4be821c94b722dc71179.tar.xz
source-sdk-2013-39ed87570bdb2f86969d4be821c94b722dc71179.zip
First version of the SOurce SDK 2013
Diffstat (limited to 'sp/src/tier1/commandbuffer.cpp')
-rw-r--r--sp/src/tier1/commandbuffer.cpp636
1 files changed, 636 insertions, 0 deletions
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