Counter Strike : Global Offensive Source Code
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.

407 lines
12 KiB

  1. //===== Copyright � 1996-2006, Valve Corporation, All rights reserved. ======//
  2. //
  3. // Purpose:
  4. //
  5. // $Workfile: $
  6. // $Date: $
  7. // $NoKeywords: $
  8. //===========================================================================//
  9. #include "tier1/commandbuffer.h"
  10. #include "tier1/utlbuffer.h"
  11. #include "tier1/strtools.h"
  12. // memdbgon must be the last include file in a .cpp file!!!
  13. #include "tier0/memdbgon.h"
  14. #define MAX_ALIAS_NAME 32
  15. #define MAX_COMMAND_LENGTH 1024
  16. struct cmdalias_t
  17. {
  18. cmdalias_t *next;
  19. char name[ MAX_ALIAS_NAME ];
  20. char *value;
  21. };
  22. //-----------------------------------------------------------------------------
  23. // Constructor, destructor
  24. //-----------------------------------------------------------------------------
  25. CCommandBuffer::CCommandBuffer( ) : m_Commands( 32, 32 )
  26. {
  27. m_hNextCommand = m_Commands.InvalidIndex();
  28. m_nWaitDelayTicks = 1;
  29. m_nCurrentTick = 0;
  30. m_nLastTickToProcess = -1;
  31. m_nArgSBufferSize = 0;
  32. m_bIsProcessingCommands = false;
  33. m_nMaxArgSBufferLength = ARGS_BUFFER_LENGTH;
  34. }
  35. CCommandBuffer::~CCommandBuffer()
  36. {
  37. }
  38. //-----------------------------------------------------------------------------
  39. // Indicates how long to delay when encoutering a 'wait' command
  40. //-----------------------------------------------------------------------------
  41. void CCommandBuffer::SetWaitDelayTime( int nTickDelay )
  42. {
  43. Assert( nTickDelay >= 0 );
  44. m_nWaitDelayTicks = nTickDelay;
  45. }
  46. //-----------------------------------------------------------------------------
  47. // Specifies a max limit of the args buffer. For unittesting. Size == 0 means use default
  48. //-----------------------------------------------------------------------------
  49. void CCommandBuffer::LimitArgumentBufferSize( int nSize )
  50. {
  51. if ( nSize > ARGS_BUFFER_LENGTH )
  52. {
  53. nSize = ARGS_BUFFER_LENGTH;
  54. }
  55. m_nMaxArgSBufferLength = ( nSize == 0 ) ? ARGS_BUFFER_LENGTH : nSize;
  56. }
  57. //-----------------------------------------------------------------------------
  58. // Parses argv0 out of the buffer
  59. //-----------------------------------------------------------------------------
  60. bool CCommandBuffer::ParseArgV0( CUtlBuffer &buf, char *pArgV0, int nMaxLen, const char **pArgS )
  61. {
  62. pArgV0[0] = 0;
  63. *pArgS = NULL;
  64. if ( !buf.IsValid() )
  65. return false;
  66. int nSize = buf.ParseToken( CCommand::DefaultBreakSet(), pArgV0, nMaxLen );
  67. if ( ( nSize <= 0 ) || ( nMaxLen == nSize ) )
  68. return false;
  69. int nArgSLen = buf.TellMaxPut() - buf.TellGet();
  70. *pArgS = (nArgSLen > 0) ? (const char*)buf.PeekGet() : NULL;
  71. return true;
  72. }
  73. //-----------------------------------------------------------------------------
  74. // Insert a command into the command queue
  75. //-----------------------------------------------------------------------------
  76. void CCommandBuffer::InsertCommandAtAppropriateTime( intp hCommand )
  77. {
  78. intp i;
  79. Command_t &command = m_Commands[hCommand];
  80. for ( i = m_Commands.Head(); i != m_Commands.InvalidIndex(); i = m_Commands.Next(i) )
  81. {
  82. if ( m_Commands[i].m_nTick > command.m_nTick )
  83. break;
  84. }
  85. m_Commands.LinkBefore( i, hCommand );
  86. }
  87. //-----------------------------------------------------------------------------
  88. // Insert a command into the command queue at the appropriate time
  89. //-----------------------------------------------------------------------------
  90. void CCommandBuffer::InsertImmediateCommand( intp hCommand )
  91. {
  92. m_Commands.LinkBefore( m_hNextCommand, hCommand );
  93. }
  94. //-----------------------------------------------------------------------------
  95. // Insert a command into the command queue
  96. //-----------------------------------------------------------------------------
  97. bool CCommandBuffer::InsertCommand( const char *pArgS, int nCommandSize, int nTick, cmd_source_t cmdSource )
  98. {
  99. if ( nCommandSize >= CCommand::MaxCommandLength() )
  100. {
  101. Warning( "WARNING: Command too long... ignoring!\n%s\n", pArgS );
  102. return false;
  103. }
  104. // Add one for null termination
  105. if ( m_nArgSBufferSize + nCommandSize + 1 > m_nMaxArgSBufferLength )
  106. {
  107. Compact();
  108. if ( m_nArgSBufferSize + nCommandSize + 1 > m_nMaxArgSBufferLength )
  109. return false;
  110. }
  111. memcpy( &m_pArgSBuffer[m_nArgSBufferSize], pArgS, nCommandSize );
  112. m_pArgSBuffer[m_nArgSBufferSize + nCommandSize] = 0;
  113. ++nCommandSize;
  114. intp hCommand = m_Commands.Alloc();
  115. Command_t &command = m_Commands[hCommand];
  116. command.m_nTick = nTick;
  117. command.m_nFirstArgS = m_nArgSBufferSize;
  118. command.m_nBufferSize = nCommandSize;
  119. command.m_source = cmdSource;
  120. m_nArgSBufferSize += nCommandSize;
  121. if ( !m_bIsProcessingCommands || ( nTick > m_nCurrentTick ) )
  122. {
  123. InsertCommandAtAppropriateTime( hCommand );
  124. }
  125. else
  126. {
  127. InsertImmediateCommand( hCommand );
  128. }
  129. return true;
  130. }
  131. //-----------------------------------------------------------------------------
  132. // Returns the length of the next command
  133. //-----------------------------------------------------------------------------
  134. void CCommandBuffer::GetNextCommandLength( const char *pText, int nMaxLen, int *pCommandLength, int *pNextCommandOffset )
  135. {
  136. int nCommandLength = 0;
  137. int nNextCommandOffset;
  138. bool bIsQuoted = false;
  139. bool bIsCommented = false;
  140. for ( nNextCommandOffset=0; nNextCommandOffset < nMaxLen; ++nNextCommandOffset, nCommandLength += bIsCommented ? 0 : 1 )
  141. {
  142. char c = pText[nNextCommandOffset];
  143. if ( !bIsCommented )
  144. {
  145. if ( c == '"' )
  146. {
  147. bIsQuoted = !bIsQuoted;
  148. continue;
  149. }
  150. // don't break if inside a C++ style comment
  151. if ( !bIsQuoted && c == '/' )
  152. {
  153. bIsCommented = ( nNextCommandOffset < nMaxLen-1 ) && pText[nNextCommandOffset+1] == '/';
  154. if ( bIsCommented )
  155. {
  156. ++nNextCommandOffset;
  157. continue;
  158. }
  159. }
  160. // don't break if inside a quoted string
  161. if ( !bIsQuoted && c == ';' )
  162. break;
  163. }
  164. // FIXME: This is legacy behavior; should we not break if a \n is inside a quoted string?
  165. if ( c == '\n' )
  166. break;
  167. }
  168. *pCommandLength = nCommandLength;
  169. *pNextCommandOffset = nNextCommandOffset;
  170. }
  171. //-----------------------------------------------------------------------------
  172. // Add text to command buffer, return false if it couldn't owing to overflow
  173. //-----------------------------------------------------------------------------
  174. bool CCommandBuffer::AddText( const char *pText, cmd_source_t cmdSource, int nTickDelay )
  175. {
  176. Assert( nTickDelay >= 0 );
  177. int nLen = Q_strlen( pText );
  178. int nTick = m_nCurrentTick + nTickDelay;
  179. // Parse the text into distinct commands
  180. const char *pCurrentCommand = pText;
  181. int nOffsetToNextCommand;
  182. for( ; nLen > 0; nLen -= nOffsetToNextCommand+1, pCurrentCommand += nOffsetToNextCommand+1 )
  183. {
  184. // find a \n or ; line break
  185. int nCommandLength;
  186. GetNextCommandLength( pCurrentCommand, nLen, &nCommandLength, &nOffsetToNextCommand );
  187. if ( nCommandLength <= 0 )
  188. continue;
  189. const char *pArgS;
  190. char *pArgV0 = (char*)stackalloc( nCommandLength+1 );
  191. CUtlBuffer bufParse( pCurrentCommand, nCommandLength, CUtlBuffer::TEXT_BUFFER | CUtlBuffer::READ_ONLY );
  192. ParseArgV0( bufParse, pArgV0, nCommandLength+1, &pArgS );
  193. if ( pArgV0[0] == 0 )
  194. continue;
  195. // Deal with the special 'wait' command
  196. if ( !Q_stricmp( pArgV0, "wait" ) && IsWaitEnabled() )
  197. {
  198. int nDelay = pArgS ? atoi( pArgS ) : m_nWaitDelayTicks;
  199. nTick += nDelay;
  200. continue;
  201. }
  202. if ( !InsertCommand( pCurrentCommand, nCommandLength, nTick, cmdSource ) )
  203. return false;
  204. }
  205. return true;
  206. }
  207. //-----------------------------------------------------------------------------
  208. // Are we in the middle of processing commands?
  209. //-----------------------------------------------------------------------------
  210. bool CCommandBuffer::IsProcessingCommands()
  211. {
  212. return m_bIsProcessingCommands;
  213. }
  214. //-----------------------------------------------------------------------------
  215. // Delays all queued commands to execute at a later time
  216. //-----------------------------------------------------------------------------
  217. void CCommandBuffer::DelayAllQueuedCommands( int nDelay )
  218. {
  219. if ( nDelay <= 0 )
  220. return;
  221. for ( intp i = m_Commands.Head(); i != m_Commands.InvalidIndex(); i = m_Commands.Next(i) )
  222. {
  223. m_Commands[i].m_nTick += nDelay;
  224. }
  225. }
  226. //-----------------------------------------------------------------------------
  227. // Call this to begin iterating over all commands up to flCurrentTime
  228. //-----------------------------------------------------------------------------
  229. void CCommandBuffer::BeginProcessingCommands( int nDeltaTicks )
  230. {
  231. if ( nDeltaTicks == 0 )
  232. return;
  233. Assert( !m_bIsProcessingCommands );
  234. m_bIsProcessingCommands = true;
  235. m_nLastTickToProcess = m_nCurrentTick + nDeltaTicks - 1;
  236. // Necessary to insert commands while commands are being processed
  237. m_hNextCommand = m_Commands.Head();
  238. }
  239. //-----------------------------------------------------------------------------
  240. // Returns the next command
  241. //-----------------------------------------------------------------------------
  242. bool CCommandBuffer::DequeueNextCommand( CCommand* pCommand )
  243. {
  244. pCommand->Reset();
  245. Assert( m_bIsProcessingCommands );
  246. if ( m_Commands.Count() == 0 )
  247. return false;
  248. intp nHead = m_Commands.Head();
  249. Command_t &command = m_Commands[ nHead ];
  250. if ( command.m_nTick > m_nLastTickToProcess )
  251. return false;
  252. m_nCurrentTick = command.m_nTick;
  253. // Copy the current command into a temp buffer
  254. // NOTE: This is here to avoid the pointers returned by DequeueNextCommand
  255. // to become invalid by calling AddText. Is there a way we can avoid the memcpy?
  256. if ( command.m_nBufferSize > 0 )
  257. {
  258. pCommand->Tokenize( &m_pArgSBuffer[command.m_nFirstArgS], command.m_source );
  259. }
  260. m_Commands.Remove( nHead );
  261. // Necessary to insert commands while commands are being processed
  262. m_hNextCommand = m_Commands.Head();
  263. // Msg("Dequeue : ");
  264. // for ( int i = 0; i < nArgc; ++i )
  265. // {
  266. // Msg("%s ", m_pCurrentArgv[i] );
  267. // }
  268. // Msg("\n");
  269. return true;
  270. }
  271. //-----------------------------------------------------------------------------
  272. // Compacts the command buffer
  273. //-----------------------------------------------------------------------------
  274. void CCommandBuffer::Compact()
  275. {
  276. // Compress argvbuffer + argv
  277. // NOTE: I'm using this choice instead of calling malloc + free
  278. // per command to allocate arguments because I expect to post a
  279. // bunch of commands but not have many delayed commands;
  280. // avoiding the allocation cost seems more important that the memcpy
  281. // cost here since I expect to not have much to copy.
  282. m_nArgSBufferSize = 0;
  283. char pTempBuffer[ ARGS_BUFFER_LENGTH ];
  284. for ( intp i = m_Commands.Head(); i != m_Commands.InvalidIndex(); i = m_Commands.Next(i) )
  285. {
  286. Command_t &command = m_Commands[ i ];
  287. memcpy( &pTempBuffer[m_nArgSBufferSize], &m_pArgSBuffer[command.m_nFirstArgS], command.m_nBufferSize );
  288. command.m_nFirstArgS = m_nArgSBufferSize;
  289. m_nArgSBufferSize += command.m_nBufferSize;
  290. }
  291. // NOTE: We could also store 2 buffers in the command buffer and switch
  292. // between the two to avoid the 2nd memcpy; but again I'm guessing the memory
  293. // tradeoff isn't worth it
  294. memcpy( m_pArgSBuffer, pTempBuffer, m_nArgSBufferSize );
  295. }
  296. //-----------------------------------------------------------------------------
  297. // Call this to finish iterating over all commands
  298. //-----------------------------------------------------------------------------
  299. void CCommandBuffer::EndProcessingCommands()
  300. {
  301. Assert( m_bIsProcessingCommands );
  302. m_bIsProcessingCommands = false;
  303. m_nCurrentTick = m_nLastTickToProcess + 1;
  304. m_hNextCommand = m_Commands.InvalidIndex();
  305. // Extract commands that are before the end time
  306. // NOTE: This is a bug for this to
  307. intp i = m_Commands.Head();
  308. if ( i == m_Commands.InvalidIndex() )
  309. {
  310. m_nArgSBufferSize = 0;
  311. return;
  312. }
  313. while ( i != m_Commands.InvalidIndex() )
  314. {
  315. if ( m_Commands[i].m_nTick >= m_nCurrentTick )
  316. break;
  317. AssertMsgOnce( false, "CCommandBuffer::EndProcessingCommands() called before all appropriate commands were dequeued.\n" );
  318. int nNext = i;
  319. Msg( "Warning: Skipping command %s\n", &m_pArgSBuffer[ m_Commands[i].m_nFirstArgS ] );
  320. m_Commands.Remove( i );
  321. i = nNext;
  322. }
  323. Compact();
  324. }
  325. //-----------------------------------------------------------------------------
  326. // Returns a handle to the next command to process
  327. //-----------------------------------------------------------------------------
  328. CommandHandle_t CCommandBuffer::GetNextCommandHandle()
  329. {
  330. Assert( m_bIsProcessingCommands );
  331. return m_Commands.Head();
  332. }