//===== Copyright � 1996-2005, Valve Corporation, All rights reserved. ======// // // Purpose: command buffer class implementation // // $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" //----------------------------------------------------------------------------- // 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() { } //----------------------------------------------------------------------------- // Purpose: indicates how long to delay when encountering a 'wait' command // Input : nTickDelay - //----------------------------------------------------------------------------- void CCommandBuffer::SetWaitDelayTime( const int nTickDelay ) { Assert( nTickDelay >= 0 ); m_nWaitDelayTicks = nTickDelay; } //----------------------------------------------------------------------------- // Purpose: specifies a max limit of the args buffer. For unit testing. Size == 0 means use default // Input : nSize - //----------------------------------------------------------------------------- void CCommandBuffer::LimitArgumentBufferSize( ssize_t nSize ) { if ( nSize > ARGS_BUFFER_LENGTH ) { nSize = ARGS_BUFFER_LENGTH; } m_nMaxArgSBufferLength = ( nSize == 0 ) ? ARGS_BUFFER_LENGTH : nSize; } //----------------------------------------------------------------------------- // Purpose: parses argv0 out of the buffer // Input : &buf - // *pArgV0 - // nMaxLen - // **pArgS - // Output : true on success, false otherwise //----------------------------------------------------------------------------- bool CCommandBuffer::ParseArgV0( CUtlBuffer &buf, char *const pArgV0, const ssize_t nMaxLen, const char **const pArgS ) const { pArgV0[ 0 ] = 0; *pArgS = NULL; if ( !buf.IsValid() ) return false; const ssize_t nSize = buf.ParseToken( CCommand::DefaultBreakSet(), pArgV0, nMaxLen ); if ( ( nSize <= 0 ) || ( nMaxLen == nSize ) ) return false; const ssize_t nArgSLen = buf.TellMaxPut() - buf.TellGet(); *pArgS = ( nArgSLen > 0 ) ? (const char*)buf.PeekGet() : NULL; return true; } //----------------------------------------------------------------------------- // Purpose : insert a command into the command queue // Inpur : hCommand - //----------------------------------------------------------------------------- void CCommandBuffer::InsertCommandAtAppropriateTime( const intptr_t hCommand ) { intptr_t 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 ); } //----------------------------------------------------------------------------- // Purpose: insert a command into the command queue at the appropriate time // Input : hCommand - //----------------------------------------------------------------------------- void CCommandBuffer::InsertImmediateCommand( const intptr_t hCommand ) { m_Commands.LinkBefore( m_hNextCommand, hCommand ); } //----------------------------------------------------------------------------- // Purpose: insert a command into the command queue // Input : *pArgS - // nCommandSize - // nTick - // cmdSource - // Output : true on success, false otherwise //----------------------------------------------------------------------------- bool CCommandBuffer::InsertCommand( const char *const pArgS, ssize_t nCommandSize, const int nTick, const cmd_source_t cmdSource ) { if ( nCommandSize >= CCommand::MaxCommandLength() ) { Warning(eDLL_T::COMMON, "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; const intptr_t hCommand = m_Commands.Alloc(); Command_t& command = m_Commands[ hCommand ]; command.m_nTick = nTick; command.m_nFirstArgS = m_nArgSBufferSize; command.m_Source = cmdSource; command.m_nBufferSize = nCommandSize; m_nArgSBufferSize += nCommandSize; if ( !m_bIsProcessingCommands || ( nTick > m_nCurrentTick ) ) { InsertCommandAtAppropriateTime( hCommand ); } else { InsertImmediateCommand( hCommand ); } return true; } //----------------------------------------------------------------------------- // Purpose: returns the length of the next command // Input : *pText - // nMaxLen - // *pCommandLength - // *pNextCommandOffset - //----------------------------------------------------------------------------- void CCommandBuffer::GetNextCommandLength( const char *const pText, const ssize_t nMaxLen, ssize_t *const pCommandLength, ssize_t *const pNextCommandOffset ) const { ssize_t nCommandLength = 0; ssize_t nNextCommandOffset; bool bIsQuoted = false; bool bIsCommented = false; for ( nNextCommandOffset=0; nNextCommandOffset < nMaxLen; ++nNextCommandOffset, nCommandLength += bIsCommented ? 0 : 1 ) { const 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; } //----------------------------------------------------------------------------- // Purpose: add text to command buffer, return false if it couldn't owing to overflow // Input : *pText - // nTickDelay - // cmdSource - // Output : true on success, false otherwise //----------------------------------------------------------------------------- bool CCommandBuffer::AddText( const char *const pText, const int nTickDelay, const cmd_source_t cmdSource ) { Assert( nTickDelay >= 0 ); ssize_t nLen = Q_strlen( pText ); int nTick = m_nCurrentTick + nTickDelay; // Parse the text into distinct commands. const char *pCurrentCommand = pText; ssize_t nOffsetToNextCommand; for( ; nLen > 0; nLen -= nOffsetToNextCommand+1, pCurrentCommand += nOffsetToNextCommand+1 ) { // Find a \n or ; line break. ssize_t nCommandLength; GetNextCommandLength( pCurrentCommand, nLen, &nCommandLength, &nOffsetToNextCommand ); if ( nCommandLength <= 0 ) continue; const char *pArgS; char *const 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() ) { const int nDelay = pArgS ? atoi( pArgS ) : m_nWaitDelayTicks; nTick += nDelay; continue; } if ( !InsertCommand( pCurrentCommand, nCommandLength, nTick, cmdSource ) ) return false; } return true; } //----------------------------------------------------------------------------- // Purpose: checks if we are we in the middle of processing commands //----------------------------------------------------------------------------- bool CCommandBuffer::IsProcessingCommands() const { return m_bIsProcessingCommands; } //----------------------------------------------------------------------------- // Purpose: delays all queued commands to execute at a later time // Input : nDelay - //----------------------------------------------------------------------------- void CCommandBuffer::DelayAllQueuedCommands( const int nDelay ) { if ( nDelay <= 0 ) return; for ( intptr_t i = m_Commands.Head(); i != m_Commands.InvalidIndex(); i = m_Commands.Next( i ) ) { m_Commands[ i ].m_nTick += nDelay; } } //----------------------------------------------------------------------------- // Purpose: begin iterating over all commands up to flCurrentTime // Input : nDeltaTicks - //----------------------------------------------------------------------------- void CCommandBuffer::BeginProcessingCommands( const 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(); } //----------------------------------------------------------------------------- // Purpose: returns the next command //----------------------------------------------------------------------------- bool CCommandBuffer::DequeueNextCommand() { m_CurrentCommand.Reset(); Assert( m_bIsProcessingCommands ); if ( m_Commands.Count() == 0 ) return false; const intptr_t nHead = m_Commands.Head(); const 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; } //----------------------------------------------------------------------------- // Purpose: returns the next command // Input : **&ppArgv - //----------------------------------------------------------------------------- int CCommandBuffer::DequeueNextCommand( const char **& ppArgv ) { DequeueNextCommand(); ppArgv = ArgV(); return ArgC(); } //----------------------------------------------------------------------------- // Purpose: 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 ( intptr_t 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 ); } //----------------------------------------------------------------------------- // Purpose: 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. intptr_t 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" ); intptr_t nNext = i; Msg( eDLL_T::COMMON, "Warning: Skipping command \"%s\"\n", &m_pArgSBuffer[ m_Commands[ i ].m_nFirstArgS ] ); m_Commands.Remove( i ); i = nNext; } Compact(); } //----------------------------------------------------------------------------- // Purpose: returns a handle to the next command to process //----------------------------------------------------------------------------- CommandHandle_t CCommandBuffer::GetNextCommandHandle() const { Assert( m_bIsProcessingCommands ); return m_Commands.Head(); }