Team Fortress 2 Source Code as on 22/4/2020
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.

1302 lines
36 KiB

  1. //===== Copyright 1996-2005, Valve Corporation, All rights reserved. ======//
  2. //
  3. // Purpose:
  4. //
  5. // $NoKeywords: $
  6. //
  7. //===========================================================================//
  8. #include "vstdlib/cvar.h"
  9. #include <ctype.h>
  10. #include "tier0/icommandline.h"
  11. #include "tier1/utlrbtree.h"
  12. #include "tier1/strtools.h"
  13. #include "tier1/keyvalues.h"
  14. #include "tier1/convar.h"
  15. #include "tier0/vprof.h"
  16. #include "tier1/tier1.h"
  17. #include "tier1/utlbuffer.h"
  18. #include "tier1/utlmap.h"
  19. #include "tier1/fmtstr.h"
  20. #ifdef _X360
  21. #include "xbox/xbox_console.h"
  22. #elif defined( _PS3 )
  23. #include "ps3/ps3_console.h"
  24. #endif
  25. #ifdef POSIX
  26. #include <wctype.h>
  27. #include <wchar.h>
  28. #define VPROJ_INCREMENT_COUNTER(a, b) /* */
  29. #define VPROJ(a) /* */
  30. #endif
  31. // memdbgon must be the last include file in a .cpp file!!!
  32. #include "tier0/memdbgon.h"
  33. //-----------------------------------------------------------------------------
  34. // Default implementation of CvarQuery
  35. //-----------------------------------------------------------------------------
  36. class CDefaultCvarQuery : public CBaseAppSystem< ICvarQuery >
  37. {
  38. public:
  39. virtual void *QueryInterface( const char *pInterfaceName )
  40. {
  41. if ( !V_stricmp( pInterfaceName, CVAR_QUERY_INTERFACE_VERSION ) )
  42. return (ICvarQuery*)this;
  43. return NULL;
  44. }
  45. virtual bool AreConVarsLinkable( const ConVar *child, const ConVar *parent )
  46. {
  47. return true;
  48. }
  49. };
  50. static CDefaultCvarQuery s_DefaultCvarQuery;
  51. static ICvarQuery *s_pCVarQuery = NULL;
  52. #include "concommandhash.h"
  53. //-----------------------------------------------------------------------------
  54. // Default implementation
  55. //-----------------------------------------------------------------------------
  56. class CCvar : public CBaseAppSystem< ICvar >
  57. {
  58. public:
  59. CCvar();
  60. // Methods of IAppSystem
  61. virtual bool Connect( CreateInterfaceFn factory );
  62. virtual void Disconnect();
  63. virtual void *QueryInterface( const char *pInterfaceName );
  64. virtual InitReturnVal_t Init();
  65. virtual void Shutdown();
  66. // Inherited from ICVar
  67. virtual CVarDLLIdentifier_t AllocateDLLIdentifier();
  68. virtual void RegisterConCommand( ConCommandBase *pCommandBase );
  69. virtual void UnregisterConCommand( ConCommandBase *pCommandBase );
  70. virtual void UnregisterConCommands( CVarDLLIdentifier_t id );
  71. virtual const char* GetCommandLineValue( const char *pVariableName );
  72. virtual ConCommandBase *FindCommandBase( const char *name );
  73. virtual const ConCommandBase *FindCommandBase( const char *name ) const;
  74. virtual ConVar *FindVar ( const char *var_name );
  75. virtual const ConVar *FindVar ( const char *var_name ) const;
  76. virtual ConCommand *FindCommand( const char *name );
  77. virtual const ConCommand *FindCommand( const char *name ) const;
  78. virtual void InstallGlobalChangeCallback( FnChangeCallback_t callback );
  79. virtual void RemoveGlobalChangeCallback( FnChangeCallback_t callback );
  80. virtual void CallGlobalChangeCallbacks( ConVar *var, const char *pOldString, float flOldValue );
  81. virtual void InstallConsoleDisplayFunc( IConsoleDisplayFunc* pDisplayFunc );
  82. virtual void RemoveConsoleDisplayFunc( IConsoleDisplayFunc* pDisplayFunc );
  83. virtual void ConsoleColorPrintf( const Color& clr, const char *pFormat, ... ) const;
  84. virtual void ConsolePrintf( const char *pFormat, ... ) const;
  85. virtual void ConsoleDPrintf( const char *pFormat, ... ) const;
  86. virtual void RevertFlaggedConVars( int nFlag );
  87. virtual void InstallCVarQuery( ICvarQuery *pQuery );
  88. #if defined( USE_VXCONSOLE )
  89. virtual void PublishToVXConsole( );
  90. #endif
  91. virtual void SetMaxSplitScreenSlots( int nSlots );
  92. virtual int GetMaxSplitScreenSlots() const;
  93. virtual void AddSplitScreenConVars();
  94. virtual void RemoveSplitScreenConVars( CVarDLLIdentifier_t id );
  95. virtual int GetConsoleDisplayFuncCount() const;
  96. virtual void GetConsoleText( int nDisplayFuncIndex, char *pchText, size_t bufSize ) const;
  97. virtual bool IsMaterialThreadSetAllowed( ) const;
  98. virtual void QueueMaterialThreadSetValue( ConVar *pConVar, const char *pValue );
  99. virtual void QueueMaterialThreadSetValue( ConVar *pConVar, int nValue );
  100. virtual void QueueMaterialThreadSetValue( ConVar *pConVar, float flValue );
  101. virtual bool HasQueuedMaterialThreadConVarSets() const;
  102. virtual int ProcessQueuedMaterialThreadConVarSets();
  103. private:
  104. enum
  105. {
  106. CONSOLE_COLOR_PRINT = 0,
  107. CONSOLE_PRINT,
  108. CONSOLE_DPRINT,
  109. };
  110. void DisplayQueuedMessages( );
  111. CUtlVector< FnChangeCallback_t > m_GlobalChangeCallbacks;
  112. CUtlVector< IConsoleDisplayFunc* > m_DisplayFuncs;
  113. int m_nNextDLLIdentifier;
  114. ConCommandBase *m_pConCommandList;
  115. CConCommandHash m_CommandHash;
  116. // temporary console area so we can store prints before console display funs are installed
  117. mutable CUtlBuffer m_TempConsoleBuffer;
  118. int m_nMaxSplitScreenSlots;
  119. protected:
  120. // internals for ICVarIterator
  121. class CCVarIteratorInternal : public ICVarIteratorInternal
  122. {
  123. public:
  124. CCVarIteratorInternal( CCvar *outer )
  125. : m_pOuter( outer ), m_pHash( &outer->m_CommandHash ), // remember my CCvar,
  126. m_hashIter( -1, -1 ) // and invalid iterator
  127. {}
  128. virtual void SetFirst( void );
  129. virtual void Next( void );
  130. virtual bool IsValid( void );
  131. virtual ConCommandBase *Get( void );
  132. protected:
  133. CCvar * const m_pOuter;
  134. CConCommandHash * const m_pHash;
  135. CConCommandHash::CCommandHashIterator_t m_hashIter;
  136. };
  137. virtual ICVarIteratorInternal *FactoryInternalIterator( void );
  138. friend class CCVarIteratorInternal;
  139. enum ConVarSetType_t
  140. {
  141. CONVAR_SET_STRING = 0,
  142. CONVAR_SET_INT,
  143. CONVAR_SET_FLOAT,
  144. };
  145. struct QueuedConVarSet_t
  146. {
  147. ConVar *m_pConVar;
  148. ConVarSetType_t m_nType;
  149. int m_nInt;
  150. float m_flFloat;
  151. CUtlString m_String;
  152. };
  153. struct SplitScreenConVar_t
  154. {
  155. SplitScreenConVar_t()
  156. {
  157. Reset();
  158. }
  159. void Reset()
  160. {
  161. m_VarName = "";
  162. m_pVar = NULL;
  163. }
  164. CUtlString m_VarName;
  165. CSplitScreenAddedConVar *m_pVar;
  166. };
  167. struct SplitScreenAddedConVars_t
  168. {
  169. SplitScreenAddedConVars_t()
  170. {
  171. for ( int i = 0; i < MAX_SPLITSCREEN_CLIENTS - 1; ++i )
  172. {
  173. m_Vars[ i ].Reset();
  174. }
  175. }
  176. // Var names need "static" buffers...
  177. SplitScreenConVar_t m_Vars[ MAX_SPLITSCREEN_CLIENTS - 1 ];
  178. };
  179. CUtlMap< ConVar *, SplitScreenAddedConVars_t > m_SplitScreenAddedConVarsMap;
  180. CUtlVector< QueuedConVarSet_t > m_QueuedConVarSets;
  181. bool m_bMaterialSystemThreadSetAllowed;
  182. private:
  183. // Standard console commands -- DO NOT PLACE ANY HIGHER THAN HERE BECAUSE THESE MUST BE THE FIRST TO DESTRUCT
  184. CON_COMMAND_MEMBER_F( CCvar, "find", Find, "Find concommands with the specified string in their name/help text.", 0 )
  185. #ifdef _DEBUG
  186. CON_COMMAND_MEMBER_F( CCvar, "ccvar_hash_report", HashReport, "report info on bucket distribution of internal hash.", 0 )
  187. #endif
  188. };
  189. void CCvar::CCVarIteratorInternal::SetFirst( void )
  190. {
  191. m_hashIter = m_pHash->First();
  192. }
  193. void CCvar::CCVarIteratorInternal::Next( void )
  194. {
  195. m_hashIter = m_pHash->Next( m_hashIter );
  196. }
  197. bool CCvar::CCVarIteratorInternal::IsValid( void )
  198. {
  199. return m_pHash->IsValidIterator( m_hashIter );
  200. }
  201. ConCommandBase *CCvar::CCVarIteratorInternal::Get( void )
  202. {
  203. Assert( IsValid( ) );
  204. return (*m_pHash)[m_hashIter];
  205. }
  206. ICvar::ICVarIteratorInternal *CCvar::FactoryInternalIterator( void )
  207. {
  208. return new CCVarIteratorInternal( this );
  209. }
  210. //-----------------------------------------------------------------------------
  211. // Factor for CVars
  212. //-----------------------------------------------------------------------------
  213. static CCvar s_Cvar;
  214. EXPOSE_SINGLE_INTERFACE_GLOBALVAR( CCvar, ICvar, CVAR_INTERFACE_VERSION, s_Cvar );
  215. //-----------------------------------------------------------------------------
  216. // Returns a CVar dictionary for tool usage
  217. //-----------------------------------------------------------------------------
  218. CreateInterfaceFn VStdLib_GetICVarFactory()
  219. {
  220. return Sys_GetFactoryThis();
  221. }
  222. //-----------------------------------------------------------------------------
  223. // Constructor
  224. //-----------------------------------------------------------------------------
  225. CCvar::CCvar() : m_TempConsoleBuffer( 0, 1024 ), m_SplitScreenAddedConVarsMap( 0, 0, DefLessFunc( ConVar * ) )
  226. {
  227. m_nNextDLLIdentifier = 0;
  228. m_pConCommandList = NULL;
  229. m_nMaxSplitScreenSlots = 1;
  230. m_bMaterialSystemThreadSetAllowed = false;
  231. m_CommandHash.Init();
  232. }
  233. //-----------------------------------------------------------------------------
  234. // Methods of IAppSystem
  235. //-----------------------------------------------------------------------------
  236. bool CCvar::Connect( CreateInterfaceFn factory )
  237. {
  238. ConnectTier1Libraries( &factory, 1 );
  239. s_pCVarQuery = (ICvarQuery*)factory( CVAR_QUERY_INTERFACE_VERSION, NULL );
  240. if ( !s_pCVarQuery )
  241. {
  242. s_pCVarQuery = &s_DefaultCvarQuery;
  243. }
  244. ConVar_Register();
  245. return true;
  246. }
  247. void CCvar::Disconnect()
  248. {
  249. ConVar_Unregister();
  250. s_pCVarQuery = NULL;
  251. DisconnectTier1Libraries();
  252. Assert( m_SplitScreenAddedConVarsMap.Count() == 0 );
  253. }
  254. InitReturnVal_t CCvar::Init()
  255. {
  256. return INIT_OK;
  257. }
  258. void CCvar::Shutdown()
  259. {
  260. }
  261. void *CCvar::QueryInterface( const char *pInterfaceName )
  262. {
  263. // We implement the ICvar interface
  264. if ( !V_strcmp( pInterfaceName, CVAR_INTERFACE_VERSION ) )
  265. return (ICvar*)this;
  266. return NULL;
  267. }
  268. //-----------------------------------------------------------------------------
  269. // Method allowing the engine ICvarQuery interface to take over
  270. //-----------------------------------------------------------------------------
  271. void CCvar::InstallCVarQuery( ICvarQuery *pQuery )
  272. {
  273. Assert( s_pCVarQuery == &s_DefaultCvarQuery );
  274. s_pCVarQuery = pQuery ? pQuery : &s_DefaultCvarQuery;
  275. }
  276. //-----------------------------------------------------------------------------
  277. // Used by DLLs to be able to unregister all their commands + convars
  278. //-----------------------------------------------------------------------------
  279. CVarDLLIdentifier_t CCvar::AllocateDLLIdentifier()
  280. {
  281. return m_nNextDLLIdentifier++;
  282. }
  283. //-----------------------------------------------------------------------------
  284. // Purpose:
  285. // Input : *variable -
  286. //-----------------------------------------------------------------------------
  287. void CCvar::RegisterConCommand( ConCommandBase *variable )
  288. {
  289. // Already registered
  290. if ( variable->IsRegistered() )
  291. return;
  292. variable->m_bRegistered = true;
  293. const char *pName = variable->GetName();
  294. if ( !pName || !pName[0] )
  295. {
  296. variable->m_pNext = NULL;
  297. return;
  298. }
  299. // If the variable is already defined, then setup the new variable as a proxy to it.
  300. const ConCommandBase *pOther = FindCommandBase( variable->GetName() );
  301. if ( pOther )
  302. {
  303. if ( variable->IsCommand() || pOther->IsCommand() )
  304. {
  305. Warning( "WARNING: unable to link %s and %s because one or more is a ConCommand.\n", variable->GetName(), pOther->GetName() );
  306. }
  307. else
  308. {
  309. // This cast is ok because we make sure they're ConVars above.
  310. ConVar *pChildVar = const_cast< ConVar* >( static_cast< const ConVar* >( variable ) );
  311. ConVar *pParentVar = const_cast< ConVar* >( static_cast< const ConVar* >( pOther ) );
  312. // See if it's a valid linkage
  313. if ( s_pCVarQuery->AreConVarsLinkable( pChildVar, pParentVar ) )
  314. {
  315. // Make sure the default values are the same (but only spew about this for FCVAR_REPLICATED)
  316. if( pChildVar->m_pszDefaultValue && pParentVar->m_pszDefaultValue &&
  317. pChildVar->IsFlagSet( FCVAR_REPLICATED ) && pParentVar->IsFlagSet( FCVAR_REPLICATED ) )
  318. {
  319. if( V_stricmp( pChildVar->m_pszDefaultValue, pParentVar->m_pszDefaultValue ) != 0 )
  320. {
  321. Warning( "Parent and child ConVars with different default values! %s child: %s parent: %s (parent wins)\n",
  322. variable->GetName(), pChildVar->m_pszDefaultValue, pParentVar->m_pszDefaultValue );
  323. }
  324. }
  325. pChildVar->m_pParent = pParentVar->m_pParent;
  326. // Absorb material thread related convar flags
  327. pParentVar->m_nFlags |= pChildVar->m_nFlags & ( FCVAR_MATERIAL_THREAD_MASK | FCVAR_ACCESSIBLE_FROM_THREADS );
  328. // Transfer children's callbacks to parent
  329. if ( pChildVar->m_fnChangeCallbacks.Count() )
  330. {
  331. for ( int i = 0; i < pChildVar->m_fnChangeCallbacks.Count(); ++i )
  332. {
  333. pParentVar->m_fnChangeCallbacks.AddToTail( pChildVar->m_fnChangeCallbacks[ i ] );
  334. }
  335. // Wipe child callbacks
  336. pChildVar->m_fnChangeCallbacks.RemoveAll();
  337. }
  338. // make sure we don't have conflicting help strings.
  339. if ( pChildVar->m_pszHelpString && V_strlen( pChildVar->m_pszHelpString ) != 0 )
  340. {
  341. if ( pParentVar->m_pszHelpString && V_strlen( pParentVar->m_pszHelpString ) != 0 )
  342. {
  343. if ( V_stricmp( pParentVar->m_pszHelpString, pChildVar->m_pszHelpString ) != 0 )
  344. {
  345. Warning( "Convar %s has multiple help strings:\n\tparent (wins): \"%s\"\n\tchild: \"%s\"\n",
  346. variable->GetName(), pParentVar->m_pszHelpString, pChildVar->m_pszHelpString );
  347. }
  348. }
  349. else
  350. {
  351. pParentVar->m_pszHelpString = pChildVar->m_pszHelpString;
  352. }
  353. }
  354. // make sure we don't have conflicting FCVAR_*** flags.
  355. static int const nFlags[] =
  356. { FCVAR_CHEAT, FCVAR_REPLICATED, FCVAR_DONTRECORD, FCVAR_ARCHIVE, FCVAR_ARCHIVE_GAMECONSOLE };
  357. static char const * const szFlags[] =
  358. { "FCVAR_CHEAT", "FCVAR_REPLICATED", "FCVAR_DONTRECORD", "FCVAR_ARCHIVE", "FCVAR_ARCHIVE_GAMECONSOLE" };
  359. COMPILE_TIME_ASSERT( ARRAYSIZE( nFlags ) == ARRAYSIZE( szFlags ) );
  360. for ( int k = 0; k < ARRAYSIZE( nFlags ); ++ k )
  361. {
  362. if ( ( pChildVar->m_nFlags & nFlags[k] ) != ( pParentVar->m_nFlags & nFlags[k] ) )
  363. {
  364. Warning( "Convar %s has conflicting %s flags (child: %s%s, parent: %s%s, parent wins)\n",
  365. variable->GetName(), szFlags[k],
  366. ( pChildVar->m_nFlags & nFlags[k] ) ? "has " : "no ", szFlags[k],
  367. ( pParentVar->m_nFlags & nFlags[k] ) ? "has " : "no ", szFlags[k] );
  368. }
  369. }
  370. }
  371. }
  372. variable->m_pNext = NULL;
  373. return;
  374. }
  375. // link the variable in
  376. variable->m_pNext = m_pConCommandList;
  377. m_pConCommandList = variable;
  378. AssertMsg1(FindCommandBase(variable->GetName()) == NULL, "Console command %s added twice!",
  379. variable->GetName());
  380. m_CommandHash.Insert(variable);
  381. }
  382. void CCvar::AddSplitScreenConVars()
  383. {
  384. if ( m_nMaxSplitScreenSlots == 1 )
  385. return;
  386. for( ConCommandBase *pCommand = m_pConCommandList; pCommand; pCommand = pCommand->m_pNext )
  387. {
  388. if ( pCommand->IsCommand() )
  389. continue;
  390. ConVar *pConVar = static_cast< ConVar * >( pCommand );
  391. if ( !pConVar->IsFlagSet( FCVAR_SS ) )
  392. continue;
  393. // See if it's already mapped in
  394. int idx = m_SplitScreenAddedConVarsMap.Find( pConVar );
  395. if ( idx == m_SplitScreenAddedConVarsMap.InvalidIndex() )
  396. {
  397. idx = m_SplitScreenAddedConVarsMap.Insert( pConVar );
  398. }
  399. SplitScreenAddedConVars_t &info = m_SplitScreenAddedConVarsMap[ idx ];
  400. for ( int i = 1 ; i < m_nMaxSplitScreenSlots; ++i )
  401. {
  402. // Already registered it
  403. if ( info.m_Vars[ i - 1 ].m_pVar )
  404. continue;
  405. // start at name2, etc.
  406. info.m_Vars[ i - 1 ].m_VarName = CFmtStr( "%s%d", pConVar->GetName(), i + 1 );
  407. CSplitScreenAddedConVar *pVar = new CSplitScreenAddedConVar( i, info.m_Vars[ i - 1 ].m_VarName.Get(), pConVar );
  408. info.m_Vars[ i - 1 ].m_pVar = pVar;
  409. pVar->SetSplitScreenPlayerSlot( i );
  410. RegisterConCommand( pVar );
  411. }
  412. }
  413. ConCommandBase::s_pConCommandBases = NULL;
  414. }
  415. void CCvar::RemoveSplitScreenConVars( CVarDLLIdentifier_t id )
  416. {
  417. if ( m_nMaxSplitScreenSlots == 1 )
  418. {
  419. Assert( m_SplitScreenAddedConVarsMap.Count() == 0 );
  420. return;
  421. }
  422. CUtlVector< ConVar * > deleted;
  423. FOR_EACH_MAP( m_SplitScreenAddedConVarsMap, i )
  424. {
  425. ConVar *key = m_SplitScreenAddedConVarsMap.Key( i );
  426. if ( key->GetDLLIdentifier() != id )
  427. {
  428. continue;
  429. }
  430. SplitScreenAddedConVars_t &info = m_SplitScreenAddedConVarsMap[ i ];
  431. for ( int i = 1 ; i < m_nMaxSplitScreenSlots; ++i )
  432. {
  433. if ( info.m_Vars[ i - 1 ].m_pVar )
  434. {
  435. UnregisterConCommand( info.m_Vars[ i - 1 ].m_pVar );
  436. delete info.m_Vars[ i - 1 ].m_pVar;
  437. info.m_Vars[ i - 1 ].m_pVar = NULL;
  438. }
  439. }
  440. deleted.AddToTail( key );
  441. }
  442. for ( int i = 0; i < deleted.Count(); ++i )
  443. {
  444. m_SplitScreenAddedConVarsMap.Remove( deleted[ i ] );
  445. }
  446. }
  447. void CCvar::UnregisterConCommand( ConCommandBase *pCommandToRemove )
  448. {
  449. // Not registered? Don't bother
  450. if ( !pCommandToRemove->IsRegistered() )
  451. return;
  452. pCommandToRemove->m_bRegistered = false;
  453. // FIXME: Should we make this a doubly-linked list? Would remove faster
  454. ConCommandBase *pPrev = NULL;
  455. for( ConCommandBase *pCommand = m_pConCommandList; pCommand; pCommand = pCommand->m_pNext )
  456. {
  457. if ( pCommand != pCommandToRemove )
  458. {
  459. pPrev = pCommand;
  460. continue;
  461. }
  462. if ( pPrev == NULL )
  463. {
  464. m_pConCommandList = pCommand->m_pNext;
  465. }
  466. else
  467. {
  468. pPrev->m_pNext = pCommand->m_pNext;
  469. }
  470. pCommand->m_pNext = NULL;
  471. m_CommandHash.Remove(m_CommandHash.Find(pCommand));
  472. break;
  473. }
  474. }
  475. void CCvar::UnregisterConCommands( CVarDLLIdentifier_t id )
  476. {
  477. ConCommandBase *pNewList;
  478. ConCommandBase *pCommand, *pNext;
  479. pNewList = NULL;
  480. m_CommandHash.Purge( true );
  481. pCommand = m_pConCommandList;
  482. while ( pCommand )
  483. {
  484. pNext = pCommand->m_pNext;
  485. if ( pCommand->GetDLLIdentifier() != id )
  486. {
  487. pCommand->m_pNext = pNewList;
  488. pNewList = pCommand;
  489. m_CommandHash.Insert( pCommand );
  490. }
  491. else
  492. {
  493. // Unlink
  494. pCommand->m_bRegistered = false;
  495. pCommand->m_pNext = NULL;
  496. }
  497. pCommand = pNext;
  498. }
  499. m_pConCommandList = pNewList;
  500. }
  501. //-----------------------------------------------------------------------------
  502. // Finds base commands
  503. //-----------------------------------------------------------------------------
  504. const ConCommandBase *CCvar::FindCommandBase( const char *name ) const
  505. {
  506. VPROF_INCREMENT_COUNTER( "CCvar::FindCommandBase", 1 );
  507. VPROF_BUDGET( "CCvar::FindCommandBase", VPROF_BUDGETGROUP_CVAR_FIND );
  508. return m_CommandHash.FindPtr( name );
  509. }
  510. ConCommandBase *CCvar::FindCommandBase( const char *name )
  511. {
  512. VPROF_INCREMENT_COUNTER( "CCvar::FindCommandBase", 1 );
  513. VPROF_BUDGET( "CCvar::FindCommandBase", VPROF_BUDGETGROUP_CVAR_FIND );
  514. return m_CommandHash.FindPtr( name );
  515. }
  516. //-----------------------------------------------------------------------------
  517. // Purpose Finds ConVars
  518. //-----------------------------------------------------------------------------
  519. const ConVar *CCvar::FindVar( const char *var_name ) const
  520. {
  521. const ConCommandBase *var = FindCommandBase( var_name );
  522. if ( !var )
  523. {
  524. return NULL;
  525. }
  526. else
  527. {
  528. if (var->IsCommand())
  529. {
  530. AssertMsg1( false , "Tried to look up command %s as if it were a variable.\n", var_name );
  531. return NULL;
  532. }
  533. }
  534. return static_cast<const ConVar*>(var);
  535. }
  536. ConVar *CCvar::FindVar( const char *var_name )
  537. {
  538. ConCommandBase *var = FindCommandBase( var_name );
  539. if ( !var )
  540. {
  541. return NULL;
  542. }
  543. else
  544. {
  545. if (var->IsCommand())
  546. {
  547. AssertMsg1( false , "Tried to look up command %s as if it were a variable.\n", var_name );
  548. return NULL;
  549. }
  550. }
  551. return static_cast<ConVar*>( var );
  552. }
  553. //-----------------------------------------------------------------------------
  554. // Purpose Finds ConCommands
  555. //-----------------------------------------------------------------------------
  556. const ConCommand *CCvar::FindCommand( const char *pCommandName ) const
  557. {
  558. const ConCommandBase *var = FindCommandBase( pCommandName );
  559. if ( !var || !var->IsCommand() )
  560. return NULL;
  561. return static_cast<const ConCommand*>(var);
  562. }
  563. ConCommand *CCvar::FindCommand( const char *pCommandName )
  564. {
  565. ConCommandBase *var = FindCommandBase( pCommandName );
  566. if ( !var || !var->IsCommand() )
  567. return NULL;
  568. return static_cast<ConCommand*>( var );
  569. }
  570. const char* CCvar::GetCommandLineValue( const char *pVariableName )
  571. {
  572. int nLen = V_strlen(pVariableName);
  573. char *pSearch = (char*)stackalloc( nLen + 2 );
  574. pSearch[0] = '+';
  575. memcpy( &pSearch[1], pVariableName, nLen + 1 );
  576. return CommandLine()->ParmValue( pSearch );
  577. }
  578. //-----------------------------------------------------------------------------
  579. // Install, remove global callbacks
  580. //-----------------------------------------------------------------------------
  581. void CCvar::InstallGlobalChangeCallback( FnChangeCallback_t callback )
  582. {
  583. Assert( callback && m_GlobalChangeCallbacks.Find( callback ) < 0 );
  584. m_GlobalChangeCallbacks.AddToTail( callback );
  585. }
  586. void CCvar::RemoveGlobalChangeCallback( FnChangeCallback_t callback )
  587. {
  588. Assert( callback );
  589. m_GlobalChangeCallbacks.FindAndRemove( callback );
  590. }
  591. //-----------------------------------------------------------------------------
  592. // Purpose:
  593. //-----------------------------------------------------------------------------
  594. void CCvar::CallGlobalChangeCallbacks( ConVar *var, const char *pOldString, float flOldValue )
  595. {
  596. int nCallbackCount = m_GlobalChangeCallbacks.Count();
  597. for ( int i = 0; i < nCallbackCount; ++i )
  598. {
  599. (*m_GlobalChangeCallbacks[i])( var, pOldString, flOldValue );
  600. }
  601. }
  602. //-----------------------------------------------------------------------------
  603. // Sets convars containing the flags to their default value
  604. //-----------------------------------------------------------------------------
  605. void CCvar::RevertFlaggedConVars( int nFlag )
  606. {
  607. for ( CConCommandHash::CCommandHashIterator_t i = m_CommandHash.First() ;
  608. m_CommandHash.IsValidIterator( i ) ;
  609. i = m_CommandHash.Next( i ) )
  610. {
  611. ConCommandBase *var = m_CommandHash[ i ];
  612. if ( var->IsCommand() )
  613. continue;
  614. ConVar *cvar = ( ConVar * )var;
  615. if ( !cvar->IsFlagSet( nFlag ) )
  616. continue;
  617. // It's == to the default value, don't count
  618. if ( !V_stricmp( cvar->GetDefault(), cvar->GetString() ) )
  619. continue;
  620. cvar->Revert();
  621. // DevMsg( "%s = \"%s\" (reverted)\n", cvar->GetName(), cvar->GetString() );
  622. }
  623. }
  624. //-----------------------------------------------------------------------------
  625. // Deal with queued material system convars
  626. //-----------------------------------------------------------------------------
  627. bool CCvar::IsMaterialThreadSetAllowed( ) const
  628. {
  629. Assert( ThreadInMainThread() );
  630. return m_bMaterialSystemThreadSetAllowed;
  631. }
  632. void CCvar::QueueMaterialThreadSetValue( ConVar *pConVar, const char *pValue )
  633. {
  634. Assert( ThreadInMainThread() );
  635. int j = m_QueuedConVarSets.AddToTail();
  636. m_QueuedConVarSets[j].m_pConVar = pConVar;
  637. m_QueuedConVarSets[j].m_nType = CONVAR_SET_STRING;
  638. m_QueuedConVarSets[j].m_String = pValue;
  639. }
  640. void CCvar::QueueMaterialThreadSetValue( ConVar *pConVar, int nValue )
  641. {
  642. Assert( ThreadInMainThread() );
  643. int j = m_QueuedConVarSets.AddToTail();
  644. m_QueuedConVarSets[j].m_pConVar = pConVar;
  645. m_QueuedConVarSets[j].m_nType = CONVAR_SET_INT;
  646. m_QueuedConVarSets[j].m_nInt = nValue;
  647. }
  648. void CCvar::QueueMaterialThreadSetValue( ConVar *pConVar, float flValue )
  649. {
  650. Assert( ThreadInMainThread() );
  651. int j = m_QueuedConVarSets.AddToTail();
  652. m_QueuedConVarSets[j].m_pConVar = pConVar;
  653. m_QueuedConVarSets[j].m_nType = CONVAR_SET_FLOAT;
  654. m_QueuedConVarSets[j].m_flFloat = flValue;
  655. }
  656. bool CCvar::HasQueuedMaterialThreadConVarSets() const
  657. {
  658. Assert( ThreadInMainThread() );
  659. return m_QueuedConVarSets.Count() > 0;
  660. }
  661. int CCvar::ProcessQueuedMaterialThreadConVarSets()
  662. {
  663. Assert( ThreadInMainThread() );
  664. m_bMaterialSystemThreadSetAllowed = true;
  665. int nUpdateFlags = 0;
  666. int nCount = m_QueuedConVarSets.Count();
  667. for ( int i = 0; i < nCount; ++i )
  668. {
  669. const QueuedConVarSet_t& set = m_QueuedConVarSets[i];
  670. switch( set.m_nType )
  671. {
  672. case CONVAR_SET_FLOAT:
  673. set.m_pConVar->SetValue( set.m_flFloat );
  674. break;
  675. case CONVAR_SET_INT:
  676. set.m_pConVar->SetValue( set.m_nInt );
  677. break;
  678. case CONVAR_SET_STRING:
  679. set.m_pConVar->SetValue( set.m_String );
  680. break;
  681. }
  682. nUpdateFlags |= set.m_pConVar->GetFlags() & FCVAR_MATERIAL_THREAD_MASK;
  683. }
  684. m_QueuedConVarSets.RemoveAll();
  685. m_bMaterialSystemThreadSetAllowed = false;
  686. return nUpdateFlags;
  687. }
  688. //-----------------------------------------------------------------------------
  689. // Display queued messages
  690. //-----------------------------------------------------------------------------
  691. void CCvar::DisplayQueuedMessages( )
  692. {
  693. // Display any queued up messages
  694. if ( m_TempConsoleBuffer.TellPut() == 0 )
  695. return;
  696. Color clr;
  697. int nStringLength;
  698. while( m_TempConsoleBuffer.IsValid() )
  699. {
  700. int nType = m_TempConsoleBuffer.GetChar();
  701. if ( nType == CONSOLE_COLOR_PRINT )
  702. {
  703. clr.SetRawColor( m_TempConsoleBuffer.GetInt() );
  704. }
  705. nStringLength = m_TempConsoleBuffer.PeekStringLength();
  706. char* pTemp = (char*)stackalloc( nStringLength + 1 );
  707. m_TempConsoleBuffer.GetString( pTemp );
  708. switch( nType )
  709. {
  710. case CONSOLE_COLOR_PRINT:
  711. ConsoleColorPrintf( clr, pTemp );
  712. break;
  713. case CONSOLE_PRINT:
  714. ConsolePrintf( pTemp );
  715. break;
  716. case CONSOLE_DPRINT:
  717. ConsoleDPrintf( pTemp );
  718. break;
  719. }
  720. }
  721. m_TempConsoleBuffer.Purge();
  722. }
  723. //-----------------------------------------------------------------------------
  724. // Install a console printer
  725. //-----------------------------------------------------------------------------
  726. void CCvar::InstallConsoleDisplayFunc( IConsoleDisplayFunc* pDisplayFunc )
  727. {
  728. Assert( m_DisplayFuncs.Find( pDisplayFunc ) < 0 );
  729. m_DisplayFuncs.AddToTail( pDisplayFunc );
  730. DisplayQueuedMessages();
  731. }
  732. void CCvar::RemoveConsoleDisplayFunc( IConsoleDisplayFunc* pDisplayFunc )
  733. {
  734. m_DisplayFuncs.FindAndRemove( pDisplayFunc );
  735. }
  736. int CCvar::GetConsoleDisplayFuncCount() const
  737. {
  738. return m_DisplayFuncs.Count();
  739. }
  740. void CCvar::GetConsoleText( int nDisplayFuncIndex, char *pchText, size_t bufSize ) const
  741. {
  742. m_DisplayFuncs[ nDisplayFuncIndex ]->GetConsoleText( pchText, bufSize );
  743. }
  744. void CCvar::ConsoleColorPrintf( const Color& clr, const char *pFormat, ... ) const
  745. {
  746. char temp[ 8192 ];
  747. va_list argptr;
  748. va_start( argptr, pFormat );
  749. _vsnprintf( temp, sizeof( temp ) - 1, pFormat, argptr );
  750. va_end( argptr );
  751. temp[ sizeof( temp ) - 1 ] = 0;
  752. int c = m_DisplayFuncs.Count();
  753. if ( c == 0 )
  754. {
  755. m_TempConsoleBuffer.PutChar( CONSOLE_COLOR_PRINT );
  756. m_TempConsoleBuffer.PutInt( clr.GetRawColor() );
  757. m_TempConsoleBuffer.PutString( temp );
  758. return;
  759. }
  760. for ( int i = 0 ; i < c; ++i )
  761. {
  762. m_DisplayFuncs[ i ]->ColorPrint( clr, temp );
  763. }
  764. }
  765. void CCvar::ConsolePrintf( const char *pFormat, ... ) const
  766. {
  767. char temp[ 8192 ];
  768. va_list argptr;
  769. va_start( argptr, pFormat );
  770. _vsnprintf( temp, sizeof( temp ) - 1, pFormat, argptr );
  771. va_end( argptr );
  772. temp[ sizeof( temp ) - 1 ] = 0;
  773. int c = m_DisplayFuncs.Count();
  774. if ( c == 0 )
  775. {
  776. m_TempConsoleBuffer.PutChar( CONSOLE_PRINT );
  777. m_TempConsoleBuffer.PutString( temp );
  778. return;
  779. }
  780. for ( int i = 0 ; i < c; ++i )
  781. {
  782. m_DisplayFuncs[ i ]->Print( temp );
  783. }
  784. }
  785. void CCvar::ConsoleDPrintf( const char *pFormat, ... ) const
  786. {
  787. char temp[ 8192 ];
  788. va_list argptr;
  789. va_start( argptr, pFormat );
  790. _vsnprintf( temp, sizeof( temp ) - 1, pFormat, argptr );
  791. va_end( argptr );
  792. temp[ sizeof( temp ) - 1 ] = 0;
  793. int c = m_DisplayFuncs.Count();
  794. if ( c == 0 )
  795. {
  796. m_TempConsoleBuffer.PutChar( CONSOLE_DPRINT );
  797. m_TempConsoleBuffer.PutString( temp );
  798. return;
  799. }
  800. for ( int i = 0 ; i < c; ++i )
  801. {
  802. m_DisplayFuncs[ i ]->DPrint( temp );
  803. }
  804. }
  805. //-----------------------------------------------------------------------------
  806. // Purpose:
  807. //-----------------------------------------------------------------------------
  808. #if defined( USE_VXCONSOLE )
  809. #ifdef _PS3
  810. /*
  811. Here's a terrible hack.
  812. In porting the part of the game that speaks to VXConsole, EA chose to
  813. write it as a cluster of global functions, instead of a class interface like
  814. Aaron did with IXboxConsole. Some of these globals need access to symbols
  815. inside the engine, so they are defined there. However, CCvar is inside vstdlib.
  816. In the EA build this didn't make a difference because everything was a huge
  817. monolithic executable, and you could just access any symbol from anywhere.
  818. In our build, with its PRXes, that doesn't fly.
  819. So, the proper solution to this problem is to wrap all of the PS3 vxconsole
  820. stuff in an interface, put it inside vstlib, create the dcim connection there,
  821. and then export the interface pointer. The engine meanwhile would export the
  822. symbols the vxlib needs, and then we give that interface class inside
  823. vstlib a pointer to the engine once the engine is available.
  824. Right now however I just want to get the thing working with as little modification
  825. as possible so I can fix the vxconsole windows app itself and hopefully get
  826. bidirectional TTY to our game. So, instead of the proper solution,
  827. I'm just duct-taping everything together by simply passing a pointer to the engine
  828. symbol this function needs whenever I call it.
  829. Blech. I'll fix it later.
  830. -egr 4/29/10. (is it later than September 2010? go call egr and make fun of him.)
  831. */
  832. void CCvar::PublishToVXConsole()
  833. #else
  834. void CCvar::PublishToVXConsole()
  835. #endif
  836. {
  837. const char *commands[6*1024];
  838. const char *helptext[6*1024];
  839. int numCommands = 0;
  840. // iterate and publish commands to the remote console
  841. for ( CConCommandHash::CCommandHashIterator_t i = m_CommandHash.First() ;
  842. m_CommandHash.IsValidIterator( i ) ;
  843. i = m_CommandHash.Next( i ) )
  844. {
  845. ConCommandBase *pCur = m_CommandHash[ i ];
  846. // add unregistered commands to list
  847. if ( numCommands < sizeof(commands)/sizeof(commands[0]) )
  848. {
  849. commands[numCommands] = pCur->GetName();
  850. helptext[numCommands] = pCur->GetHelpText();
  851. numCommands++;
  852. }
  853. }
  854. if ( numCommands )
  855. {
  856. #ifdef _PS3
  857. g_pValvePS3Console->AddCommands( numCommands, commands, helptext );
  858. #else
  859. XBX_rAddCommands( numCommands, commands, helptext );
  860. #endif
  861. }
  862. }
  863. #endif
  864. static bool ConVarSortFunc( ConCommandBase * const &lhs, ConCommandBase * const &rhs )
  865. {
  866. return CaselessStringLessThan( lhs->GetName(), rhs->GetName() );
  867. }
  868. //-----------------------------------------------------------------------------
  869. // Console commands
  870. //-----------------------------------------------------------------------------
  871. void CCvar::Find( const CCommand &args )
  872. {
  873. const char *search;
  874. if ( args.ArgC() != 2 )
  875. {
  876. ConMsg( "Usage: find <string>\n" );
  877. return;
  878. }
  879. // Get substring to find
  880. search = args[1];
  881. CUtlRBTree< ConCommandBase *, int > sorted( 0, 0, ConVarSortFunc );
  882. // Loop through vars and print out findings
  883. for ( CConCommandHash::CCommandHashIterator_t i = m_CommandHash.First() ;
  884. m_CommandHash.IsValidIterator(i) ;
  885. i = m_CommandHash.Next(i) )
  886. {
  887. ConCommandBase *var = m_CommandHash[ i ];
  888. if ( var->IsFlagSet(FCVAR_DEVELOPMENTONLY) || var->IsFlagSet(FCVAR_HIDDEN) )
  889. continue;
  890. if ( !V_stristr( var->GetName(), search ) &&
  891. !V_stristr( var->GetHelpText(), search ) )
  892. continue;
  893. sorted.Insert( var );
  894. }
  895. for ( int i = sorted.FirstInorder(); i != sorted.InvalidIndex(); i = sorted.NextInorder( i ) )
  896. {
  897. ConVar_PrintDescription( sorted[ i ] );
  898. }
  899. }
  900. #ifdef _DEBUG
  901. void CCvar::HashReport( const CCommand &args )
  902. {
  903. m_CommandHash.Report();
  904. }
  905. #endif
  906. void CCvar::SetMaxSplitScreenSlots( int nSlots )
  907. {
  908. m_nMaxSplitScreenSlots = nSlots;
  909. AddSplitScreenConVars();
  910. }
  911. int CCvar::GetMaxSplitScreenSlots() const
  912. {
  913. return m_nMaxSplitScreenSlots;
  914. }
  915. //-----------------------------------------------------------------------------
  916. // Console command hash data structure
  917. //-----------------------------------------------------------------------------
  918. CConCommandHash::CConCommandHash()
  919. {
  920. Purge( true );
  921. }
  922. CConCommandHash::~CConCommandHash()
  923. {
  924. Purge( false );
  925. }
  926. void CConCommandHash::Purge( bool bReinitialize )
  927. {
  928. m_aBuckets.Purge();
  929. m_aDataPool.Purge();
  930. if ( bReinitialize )
  931. {
  932. Init();
  933. }
  934. }
  935. // Initialize.
  936. void CConCommandHash::Init( void )
  937. {
  938. // kNUM_BUCKETS must be a power of two.
  939. COMPILE_TIME_ASSERT((kNUM_BUCKETS & ( kNUM_BUCKETS - 1 )) == 0);
  940. // Set the bucket size.
  941. m_aBuckets.SetSize( kNUM_BUCKETS );
  942. for ( int iBucket = 0; iBucket < kNUM_BUCKETS; ++iBucket )
  943. {
  944. m_aBuckets[iBucket] = m_aDataPool.InvalidIndex();
  945. }
  946. // Calculate the grow size.
  947. int nGrowSize = 4 * kNUM_BUCKETS;
  948. m_aDataPool.SetGrowSize( nGrowSize );
  949. }
  950. //-----------------------------------------------------------------------------
  951. // Purpose: Insert data into the hash table given its key (unsigned int),
  952. // WITH a check to see if the element already exists within the hash.
  953. //-----------------------------------------------------------------------------
  954. CConCommandHash::CCommandHashHandle_t CConCommandHash::Insert( ConCommandBase *cmd )
  955. {
  956. // Check to see if that key already exists in the buckets (should be unique).
  957. CCommandHashHandle_t hHash = Find( cmd );
  958. if( hHash != InvalidHandle() )
  959. return hHash;
  960. return FastInsert( cmd );
  961. }
  962. //-----------------------------------------------------------------------------
  963. // Purpose: Insert data into the hash table given its key (unsigned int),
  964. // WITHOUT a check to see if the element already exists within the hash.
  965. //-----------------------------------------------------------------------------
  966. CConCommandHash::CCommandHashHandle_t CConCommandHash::FastInsert( ConCommandBase *cmd )
  967. {
  968. // Get a new element from the pool.
  969. int iHashData = m_aDataPool.Alloc( true );
  970. HashEntry_t * pHashData = &m_aDataPool[iHashData];
  971. if ( !pHashData )
  972. return InvalidHandle();
  973. HashKey_t key = Hash(cmd);
  974. // Add data to new element.
  975. pHashData->m_uiKey = key;
  976. pHashData->m_Data = cmd;
  977. // Link element.
  978. int iBucket = key & kBUCKETMASK ; // HashFuncs::Hash( uiKey, m_uiBucketMask );
  979. m_aDataPool.LinkBefore( m_aBuckets[iBucket], iHashData );
  980. m_aBuckets[iBucket] = iHashData;
  981. return iHashData;
  982. }
  983. //-----------------------------------------------------------------------------
  984. // Purpose: Remove a given element from the hash.
  985. //-----------------------------------------------------------------------------
  986. void CConCommandHash::Remove( CCommandHashHandle_t hHash )
  987. {
  988. HashEntry_t * entry = &m_aDataPool[hHash];
  989. HashKey_t iBucket = entry->m_uiKey & kBUCKETMASK ;
  990. if ( m_aBuckets[iBucket] == hHash )
  991. {
  992. // It is a bucket head.
  993. m_aBuckets[iBucket] = m_aDataPool.Next( hHash );
  994. }
  995. else
  996. {
  997. // Not a bucket head.
  998. m_aDataPool.Unlink( hHash );
  999. }
  1000. // Remove the element.
  1001. m_aDataPool.Remove( hHash );
  1002. }
  1003. //-----------------------------------------------------------------------------
  1004. // Purpose: Remove all elements from the hash
  1005. //-----------------------------------------------------------------------------
  1006. void CConCommandHash::RemoveAll( void )
  1007. {
  1008. m_aBuckets.RemoveAll();
  1009. m_aDataPool.RemoveAll();
  1010. }
  1011. //-----------------------------------------------------------------------------
  1012. // Find hash entry corresponding to a string name
  1013. //-----------------------------------------------------------------------------
  1014. CConCommandHash::CCommandHashHandle_t CConCommandHash::Find( const char *name, HashKey_t hashkey) const
  1015. {
  1016. // hash the "key" - get the correct hash table "bucket"
  1017. int iBucket = hashkey & kBUCKETMASK;
  1018. for ( datapool_t::IndexLocalType_t iElement = m_aBuckets[iBucket]; iElement != m_aDataPool.InvalidIndex(); iElement = m_aDataPool.Next( iElement ) )
  1019. {
  1020. const HashEntry_t &element = m_aDataPool[iElement];
  1021. if ( element.m_uiKey == hashkey && // if hashes of strings match,
  1022. V_stricmp( name, element.m_Data->GetName() ) == 0) // then test the actual strings
  1023. {
  1024. return iElement;
  1025. }
  1026. }
  1027. // found nuffink
  1028. return InvalidHandle();
  1029. }
  1030. //-----------------------------------------------------------------------------
  1031. // Find a command in the hash.
  1032. //-----------------------------------------------------------------------------
  1033. CConCommandHash::CCommandHashHandle_t CConCommandHash::Find( const ConCommandBase *cmd ) const
  1034. {
  1035. // Set this #if to 1 if the assert at bottom starts whining --
  1036. // that indicates that a console command is being double-registered,
  1037. // or something similarly nonfatally bad. With this #if 1, we'll search
  1038. // by name instead of by pointer, which is more robust in the face
  1039. // of double registered commands, but obviously slower.
  1040. #if 0
  1041. return Find(cmd->GetName());
  1042. #else
  1043. HashKey_t hashkey = Hash(cmd);
  1044. int iBucket = hashkey & kBUCKETMASK;
  1045. // hunt through all entries in that bucket
  1046. for ( datapool_t::IndexLocalType_t iElement = m_aBuckets[iBucket]; iElement != m_aDataPool.InvalidIndex(); iElement = m_aDataPool.Next( iElement ) )
  1047. {
  1048. const HashEntry_t &element = m_aDataPool[iElement];
  1049. if ( element.m_uiKey == hashkey && // if the hashes match...
  1050. element.m_Data == cmd ) // and the pointers...
  1051. {
  1052. // in debug, test to make sure we don't have commands under the same name
  1053. // or something goofy like that
  1054. AssertMsg1( iElement == Find(cmd->GetName()),
  1055. "ConCommand %s had two entries in the hash!", cmd->GetName() );
  1056. // return this element
  1057. return iElement;
  1058. }
  1059. }
  1060. // found nothing.
  1061. #ifdef DBGFLAG_ASSERT // double check against search by name
  1062. CCommandHashHandle_t dbghand = Find(cmd->GetName());
  1063. AssertMsg1( InvalidHandle() == dbghand,
  1064. "ConCommand %s couldn't be found by pointer, but was found by name!", cmd->GetName() );
  1065. #endif
  1066. return InvalidHandle();
  1067. #endif
  1068. }
  1069. #ifdef _DEBUG
  1070. // Dump a report to MSG
  1071. void CConCommandHash::Report( void )
  1072. {
  1073. Msg("Console command hash bucket load:\n");
  1074. int total = 0;
  1075. for ( int iBucket = 0 ; iBucket < kNUM_BUCKETS ; ++iBucket )
  1076. {
  1077. int count = 0;
  1078. CCommandHashHandle_t iElement = m_aBuckets[iBucket]; // get the head of the bucket
  1079. while ( iElement != m_aDataPool.InvalidIndex() )
  1080. {
  1081. ++count;
  1082. iElement = m_aDataPool.Next( iElement );
  1083. }
  1084. Msg( "%d: %d\n", iBucket, count );
  1085. total += count;
  1086. }
  1087. Msg("\tAverage: %.1f\n", total / ((float)(kNUM_BUCKETS)));
  1088. }
  1089. #endif