PageRenderTime 29ms CodeModel.GetById 13ms RepoModel.GetById 0ms app.codeStats 1ms

/Code/GameSDK/GameDll/CircularStatsStorage.cpp

https://gitlab.com/blackbird91/CS188_AI_Game
C++ | 1156 lines | 918 code | 182 blank | 56 comment | 116 complexity | 50118888a892a77f90ba0d317194cfcf MD5 | raw file
  1. /*
  2. * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
  3. * its licensors.
  4. *
  5. * For complete copyright and license terms please see the LICENSE at the root of this
  6. * distribution (the "License"). All use of this software is governed by the License,
  7. * or, if provided, by the license below or the license accompanying this file. Do not
  8. * remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
  9. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. *
  11. */
  12. // Original file Copyright Crytek GMBH or its affiliates, used under license.
  13. /*
  14. classes relating to memory management for game statistics
  15. statistics are allocated from a circular buffer and will be purged and lost
  16. if the circular buffer is not sufficiently large enough
  17. */
  18. #include "StdAfx.h"
  19. #include "CircularStatsStorage.h"
  20. #include "GameCVars.h"
  21. #include "Utility/CryWatch.h"
  22. #include "TypeInfo_impl.h"
  23. // this define allows for structured 'newed' from allocators other than the circular buffer to be added to the stats system
  24. // we can allow this to ease the transition to the circular buffer system but it generally defeats the purpose
  25. #define ALSO_SUPPORT_NON_CIRCULAR_BUFFER_ALLOCS 0
  26. const CCircularBufferTimeline *CCircularBufferStatsContainer::GetTimeline(
  27. size_t inTimelineId) const
  28. {
  29. CCircularBufferTimeline *tl = NULL;
  30. #ifndef _RELEASE
  31. Validate();
  32. #endif
  33. if (inTimelineId >= 0 && (int)inTimelineId < m_numTimelines)
  34. {
  35. tl = &m_timelines[inTimelineId];
  36. }
  37. else
  38. {
  39. CryWarning(VALIDATOR_MODULE_GAME,VALIDATOR_ERROR,"Statistics event %" PRISIZE_T " is larger than the max registered of %" PRISIZE_T ", event ignored",inTimelineId,m_numTimelines);
  40. }
  41. return tl;
  42. }
  43. CCircularBufferTimeline *CCircularBufferStatsContainer::GetMutableTimeline(
  44. size_t inTimelineId)
  45. {
  46. return const_cast<CCircularBufferTimeline*>(GetTimeline(inTimelineId));
  47. }
  48. bool CCircularBufferStatsContainer::HasAnyTimelineEvents() const
  49. {
  50. bool gotEvents=false;
  51. for (size_t i=0; i<m_numTimelines; i++)
  52. {
  53. if (!m_timelines[i].m_list.empty())
  54. {
  55. gotEvents=true;
  56. break;
  57. }
  58. }
  59. return gotEvents;
  60. }
  61. const SStatAnyValue *CCircularBufferStatsContainer::GetState(
  62. size_t inStateId) const
  63. {
  64. const SStatAnyValue *result=NULL;
  65. if (inStateId < m_numStates)
  66. {
  67. result = &m_states[inStateId];
  68. }
  69. else
  70. {
  71. CryWarning(VALIDATOR_MODULE_GAME,VALIDATOR_ERROR,"Statistics state %" PRISIZE_T " is larger than the max registered of %" PRISIZE_T ", state ignored",inStateId,m_numStates);
  72. }
  73. return result;
  74. }
  75. SStatAnyValue *CCircularBufferStatsContainer::GetMutableState(
  76. size_t inStateId)
  77. {
  78. return const_cast<SStatAnyValue*>(GetState(inStateId));
  79. }
  80. const char *CCircularBufferStatsContainer::GetEventName(
  81. size_t inEventId)
  82. {
  83. IGameStatistics *gs=g_pGame->GetIGameFramework()->GetIGameStatistics();
  84. const SGameStatDesc *dc=gs ? gs->GetEventDesc(inEventId) : NULL;
  85. return dc ? dc->serializeName.c_str() : NULL;
  86. }
  87. CCircularBufferStatsContainer::CCircularBufferStatsContainer(
  88. CCircularBufferStatsStorage *inStorage) :
  89. m_storage(inStorage),
  90. m_timelines(NULL),
  91. m_states(NULL),
  92. m_refCount(0),
  93. m_numTimelines(0),
  94. m_numStates(0)
  95. {
  96. }
  97. CCircularBufferStatsContainer::~CCircularBufferStatsContainer()
  98. {
  99. Clear(); // not strictly necessary as circular buffer entries are all purgable anyway and the states will be freed by the destructors called by the array delete below, but this will allow us to verify the circular buffer is empty at the end of a stats save
  100. delete [] m_states;
  101. delete [] m_timelines;
  102. }
  103. void CCircularBufferStatsContainer::Init(size_t numEvents, size_t numStates)
  104. {
  105. assert(m_states==NULL && m_timelines==NULL);
  106. m_states=new SStatAnyValue[numStates];
  107. m_numStates=numStates;
  108. m_timelines=new CCircularBufferTimeline[numEvents];
  109. m_numTimelines=numEvents;
  110. }
  111. #ifndef _RELEASE
  112. // checks for memory stomps and fatals out if any have occurred
  113. void CCircularBufferStatsContainer::Validate() const
  114. {
  115. for (size_t i=0; i<m_numTimelines; i++)
  116. {
  117. CCircularBufferTimeline *pTL=&m_timelines[i];
  118. CDoubleLinkedElement *pHead=pTL->m_list.Head();
  119. CDoubleLinkedElement *pTail=pTL->m_list.Tail();
  120. if (pTL->m_type==eRBPT_Invalid)
  121. {
  122. if (pHead!=pTL->m_list.GetLink() || pTail!=pTL->m_list.GetLink())
  123. {
  124. CryFatalError(string().Format("Memory stomp, stats container %p timeline %d has had nothing added to it, yet its head and tail have been altered (head %p , tail %p)",this,i,pHead,pTail));
  125. }
  126. }
  127. else
  128. {
  129. // check they're in the expected range
  130. if (m_storage->IsUsingCircularBuffer() && !(m_storage->ContainsPtr(pHead) && m_storage->ContainsPtr(pTail)) && !(pHead==pTL->m_list.GetLink() && pTail==pTL->m_list.GetLink()))
  131. {
  132. CryFatalError(string().Format("Memory stomp, stats container %p timeline %d has its head or tail out of the accepted range (head %p , tail %p)",this,i,pHead,pTail));
  133. }
  134. }
  135. }
  136. }
  137. #endif
  138. void CCircularBufferStatsContainer::AddEvent(size_t eventID, const CTimeValue& time, const SStatAnyValue& val)
  139. {
  140. #ifndef _RELEASE
  141. Validate();
  142. #endif
  143. if (val.type==eSAT_TXML && m_storage->ContainsPtr(val.pSerializable))
  144. {
  145. // link into linked list
  146. CCircularBufferTimeline *timeline=GetMutableTimeline(eventID);
  147. if (timeline)
  148. {
  149. CCircularBufferTimelineEntry *timelineEntry=static_cast<CCircularBufferTimelineEntry*>(val.pSerializable);
  150. timeline->m_list.AddTail(&timelineEntry->m_timelineLink);
  151. timeline->SetType(eRBPT_TimelineEntry);
  152. timelineEntry->AddRef();
  153. timelineEntry->SetTime(time);
  154. #if DEBUG_CIRCULAR_STATS
  155. m_storage->m_numCircularEvents++;
  156. #endif
  157. }
  158. }
  159. else
  160. {
  161. bool record=true;
  162. if (val.type==eSAT_TXML && m_storage->IsUsingCircularBuffer()) // if trying to add a serialisable xml class that's not been allocated from the circular buffer, emit a warning
  163. {
  164. #if ALSO_SUPPORT_NON_CIRCULAR_BUFFER_ALLOCS
  165. if (val.pSerializable)
  166. {
  167. CryWarning(VALIDATOR_MODULE_GAME,VALIDATOR_ERROR,"Statistics event %s needs moving to work with the circular buffer storage, this event IS being recorded but is using memory suboptimally",GetEventName(eventID));
  168. m_storage->m_numLegacyEvents++;
  169. }
  170. else
  171. {
  172. record=false;
  173. }
  174. #else
  175. if (val.pSerializable) // If this is NULL it's probably because we ran out of memory in m_circularBuffer
  176. {
  177. CryWarning(VALIDATOR_MODULE_GAME,VALIDATOR_ERROR,"Statistics event %s needs moving to work with the circular buffer storage, this event is being ignored",GetEventName(eventID));
  178. }
  179. record=false;
  180. #endif
  181. }
  182. if (record)
  183. {
  184. CCircularBufferTimeline *timeline=GetMutableTimeline(eventID);
  185. if (timeline)
  186. {
  187. SCircularStatAnyValue *stat=new (m_storage) SCircularStatAnyValue;
  188. if (stat)
  189. {
  190. stat->val=val;
  191. stat->time=time;
  192. timeline->m_list.AddTail(&stat->link);
  193. timeline->SetType(eRBPT_StatAnyValue);
  194. #if DEBUG_CIRCULAR_STATS
  195. m_storage->m_numCircularEvents++;
  196. #endif
  197. }
  198. }
  199. }
  200. }
  201. #ifndef _RELEASE
  202. Validate();
  203. #endif
  204. }
  205. void CCircularBufferStatsContainer::AddState(size_t stateID, const SStatAnyValue& val)
  206. {
  207. SStatAnyValue *state=GetMutableState(stateID);
  208. if (state)
  209. {
  210. *state=val;
  211. }
  212. }
  213. size_t CCircularBufferStatsContainer::GetEventTrackLength(size_t eventID) const
  214. {
  215. size_t result=0;
  216. const CCircularBufferTimeline *timeline=GetTimeline(eventID);
  217. if (timeline)
  218. {
  219. result=timeline->m_list.size();
  220. }
  221. return result;
  222. }
  223. // FIXME : this is very inefficient as the data model is in a linked list - would be better to change the interface to use an iterator... or for it to just ask the container to convert itself
  224. // to XML (preferably into a text buffer rather than xml node hierarchy) - that would be ideal
  225. void CCircularBufferStatsContainer::GetEventInfo(size_t eventID, size_t idx, CTimeValue& outTime, SStatAnyValue& outParam) const
  226. {
  227. bool found=false;
  228. int maxEvents=-1;
  229. const CCircularBufferTimeline *timeline=GetTimeline(eventID);
  230. int i=0;
  231. for (CDoubleLinkedList::const_iterator iter=timeline->m_list.begin(); iter!=timeline->m_list.end(); ++iter)
  232. {
  233. if (i==idx)
  234. {
  235. switch (timeline->GetType())
  236. {
  237. case eRBPT_TimelineEntry:
  238. {
  239. CCircularBufferTimelineEntry *timelineEntry=CCircularBufferTimelineEntry::EntryFromListElement(*iter);
  240. outParam=SStatAnyValue(timelineEntry);
  241. outTime=timelineEntry->GetTime();
  242. }
  243. break;
  244. case eRBPT_StatAnyValue:
  245. {
  246. SCircularStatAnyValue *stat=SCircularStatAnyValue::EntryFromListElement(*iter);
  247. outParam=stat->val;
  248. outTime=stat->time;
  249. }
  250. break;
  251. case eRBPT_Invalid:
  252. break;
  253. default:
  254. CRY_ASSERT_MESSAGE(0,"Found unrecognised timeline time in stats circular buffer - cannot return stats");
  255. break;
  256. }
  257. found=true;
  258. break;
  259. }
  260. i++;
  261. }
  262. maxEvents=i;
  263. CRY_ASSERT_MESSAGE(found==true, string().Format("Failed to find event info index %d for event %u, max events on timeline is %d",idx,eventID,maxEvents));
  264. }
  265. void CCircularBufferStatsContainer::GetStateInfo(size_t stateID, SStatAnyValue& outValue) const
  266. {
  267. const SStatAnyValue *state=GetState(stateID);
  268. if (state)
  269. {
  270. outValue=*state;
  271. }
  272. }
  273. void CCircularBufferStatsContainer::Clear()
  274. {
  275. const SStatAnyValue null(0);
  276. for (size_t i=0; i<m_numStates; i++)
  277. {
  278. SStatAnyValue *state=GetMutableState(i);
  279. *state=null; // would be better if SStatAnyValue had a Clear() function of its own
  280. }
  281. for (size_t i=0; i<m_numTimelines; i++)
  282. {
  283. CCircularBufferTimeline *timeline=GetMutableTimeline(i);
  284. while (!timeline->m_list.empty())
  285. {
  286. switch (timeline->GetType())
  287. {
  288. case eRBPT_TimelineEntry:
  289. CCircularBufferTimelineEntry::EntryFromListElement(timeline->m_list.Head())->ForceRelease(); // we don't need to ForceRelease(), could do Unlink() then Release() but
  290. // i don't want people holding refs to these objects as they can be purged at any point
  291. // so lets try and catch some here
  292. break;
  293. case eRBPT_StatAnyValue:
  294. {
  295. SCircularStatAnyValue *val=SCircularStatAnyValue::EntryFromListElement(timeline->m_list.Head());
  296. delete val;
  297. }
  298. break;
  299. default:
  300. CRY_ASSERT_MESSAGE(0,"unknown data type found in timeline, cannot cleanly destruct it - possible memory leak");
  301. break;
  302. }
  303. }
  304. }
  305. }
  306. bool CCircularBufferStatsContainer::IsEmpty() const
  307. {
  308. bool empty=true;
  309. for (size_t i=0; i<m_numTimelines && empty; i++)
  310. {
  311. const CCircularBufferTimeline *ll=GetTimeline(i);
  312. empty=ll->m_list.empty();
  313. }
  314. for (size_t i=0; i<m_numStates && empty; i++)
  315. {
  316. const SStatAnyValue *val=GetState(i);
  317. empty=(val->type==eSAT_NONE);
  318. }
  319. return empty;
  320. }
  321. void CCircularBufferStatsContainer::GetMemoryStatistics(ICrySizer *pSizer)
  322. {
  323. pSizer->Add(*this);
  324. for (size_t i=0; i<m_numStates ; i++)
  325. {
  326. const SStatAnyValue *val=GetState(i);
  327. val->GetMemoryUsage(pSizer);
  328. }
  329. for (size_t i=0; i<m_numTimelines; i++)
  330. {
  331. const CCircularBufferTimeline *timeline=GetTimeline(i);
  332. pSizer->Add(*timeline);
  333. // why no iterate through timeline and adding of element sizes?
  334. // i don't think it's correct to sum the amounts used from the circular buffer here - that memory is already allocated as part of the circular buffer storage and
  335. // shouldn't be counted twice - depends on how you want to use the memory statistics to analyse memory relating to pools i guess
  336. }
  337. }
  338. void CCircularBufferStatsContainer::AddRef()
  339. {
  340. ++m_refCount;
  341. }
  342. void CCircularBufferStatsContainer::Release()
  343. {
  344. --m_refCount;
  345. if (m_refCount<=0)
  346. {
  347. delete this;
  348. }
  349. }
  350. CCircularBufferStatsStorage *CCircularBufferStatsStorage::s_storage=NULL;
  351. // a value of 0 stops the buffer being allocated allowing both unlimited allocation or for the memory not to be reserved if stats is disabled / unwanted
  352. CCircularBufferStatsStorage::CCircularBufferStatsStorage(
  353. size_t inBufferSize) :
  354. #if DEBUG_CIRCULAR_STATS
  355. CGameMechanismBase("CircularStatsDebug"),
  356. #endif
  357. m_totalBytesAlloced(0),
  358. m_totalBytesRequested(0),
  359. m_peakAlloc(0),
  360. m_numDiscards(0),
  361. m_refCount(0),
  362. m_serializeLocked(false)
  363. {
  364. #if DEBUG_CIRCULAR_STATS
  365. m_numLegacyEvents=0;
  366. m_numCircularEvents=0;
  367. #endif
  368. if (inBufferSize>0)
  369. {
  370. m_circularBuffer.Init(inBufferSize,NULL);
  371. m_circularBuffer.SetPacketDiscardCallback(DiscardCallback,this);
  372. }
  373. CRY_ASSERT_MESSAGE(!s_storage,"Creating multiple CCircularBufferStatsStorage instances isn't currently supported, would need to remove default 'new' operator for CCircularBufferTimelineEntry and force the specification of which CCircularBufferStatsStorage should be used");
  374. s_storage=this;
  375. }
  376. CCircularBufferStatsStorage::~CCircularBufferStatsStorage()
  377. {
  378. m_circularBuffer.SetPacketDiscardCallback(NULL,NULL);
  379. if (s_storage==this)
  380. {
  381. s_storage=NULL;
  382. }
  383. }
  384. void CCircularBufferStatsStorage::AddRef()
  385. {
  386. ++m_refCount;
  387. }
  388. void CCircularBufferStatsStorage::Release()
  389. {
  390. --m_refCount;
  391. if (m_refCount<=0)
  392. {
  393. delete this;
  394. }
  395. }
  396. void CCircularBufferStatsStorage::LockForSerialization()
  397. {
  398. CRY_ASSERT_MESSAGE(!m_serializeLocked,"Attempted to lock stats storage for serialization, but it is already locked??");
  399. m_serializeLocked=true;
  400. }
  401. void CCircularBufferStatsStorage::UnlockFromSerialization()
  402. {
  403. m_serializeLocked=false;
  404. }
  405. bool CCircularBufferStatsStorage::IsLockedForSerialization()
  406. {
  407. return m_serializeLocked;
  408. }
  409. #if DEBUG_CIRCULAR_STATS
  410. void CCircularBufferStatsStorage::DebugUpdate()
  411. {
  412. if (g_pGameCVars->g_telemetry_memory_display)
  413. {
  414. if (IsUsingCircularBuffer())
  415. {
  416. CryWatch("[MT] Stats circular buffer, using %d / %d bytes, # discards %d, # legacy events %d, # new events %d",m_circularBuffer.size(),m_circularBuffer.capacity(),m_numDiscards,m_numLegacyEvents,m_numCircularEvents);
  417. CryWatch("[MT] Stats circular buffer, total alloc requests %d bytes, peak %d bytes",m_totalBytesRequested,m_peakAlloc);
  418. }
  419. else
  420. {
  421. CryWatch("[MT] Stats circular buffer set to UNLIMITED, current alloc %d, peak %d",m_totalBytesRequested,m_peakAlloc);
  422. }
  423. if (IsLockedForSerialization())
  424. {
  425. CryWatch("[MT] Stats circular buffer is locked for serialization");
  426. }
  427. }
  428. }
  429. #endif
  430. // static
  431. CCircularBufferStatsStorage *CCircularBufferStatsStorage::GetDefaultStorage()
  432. {
  433. CRY_ASSERT_MESSAGE(s_storage!=NULL,"Tried to access default circular buffer storage, but none exists");
  434. return s_storage;
  435. }
  436. // static
  437. CCircularBufferStatsStorage *CCircularBufferStatsStorage::GetDefaultStorageNoCheck()
  438. {
  439. return s_storage;
  440. }
  441. IStatsContainer *CCircularBufferStatsStorage::CreateContainer()
  442. {
  443. return new CCircularBufferStatsContainer(this);
  444. }
  445. bool CCircularBufferStatsStorage::ContainsPtr(
  446. const void *inPtr)
  447. {
  448. return m_circularBuffer.ContainsPtr(inPtr);
  449. }
  450. // allocates storage of the specified size
  451. void *CCircularBufferStatsStorage::Alloc(int inSize, uint8 inType)
  452. {
  453. CRY_ASSERT_MESSAGE(!m_serializeLocked,"Shouldn't be trying to allocate currently, circular storage is locked for serialisation (may corrupt data)");
  454. CRY_ASSERT_MESSAGE(this==GetDefaultStorage(),"CCircularBufferStatsStorage Alloc and Free need to know which circular buffer allocations belong to if allowing more than one circular buffer");
  455. void *result=NULL;
  456. int headerSize=sizeof(SRecording_Packet);
  457. int totalSize=inSize+headerSize;
  458. SRecording_Packet *header;
  459. if (IsUsingCircularBuffer())
  460. {
  461. size_t freeSpace = m_circularBuffer.capacity() - m_circularBuffer.size(); // WARNING: This only works because we never wrap round (i.e. using it as a straight array)
  462. if (freeSpace >= (size_t)totalSize)
  463. {
  464. // Potential optimisation here, we could replace this with a much simpler allocator
  465. header=m_circularBuffer.AllocEmptyPacket(totalSize,inType); // PTR ARITH
  466. }
  467. else
  468. {
  469. // Ran out of space
  470. m_numDiscards++;
  471. m_totalBytesRequested+=totalSize;
  472. return NULL;
  473. }
  474. }
  475. else
  476. {
  477. header=(SRecording_Packet*)(new char[totalSize]);
  478. header->size=totalSize;
  479. header->type=inType;
  480. }
  481. result=((char*)header)+headerSize;
  482. m_totalBytesAlloced+=totalSize;
  483. m_peakAlloc=max(m_peakAlloc,m_totalBytesAlloced);
  484. m_totalBytesRequested+=totalSize;
  485. return result;
  486. }
  487. // static
  488. // frees storage
  489. void CCircularBufferStatsStorage::Free(void *inPtr)
  490. {
  491. CCircularBufferStatsStorage *storage=GetDefaultStorage();
  492. if (storage)
  493. {
  494. CRY_ASSERT_MESSAGE(!storage->m_serializeLocked,"Shouldn't be trying to free currently, circular storage is locked for serialisation (may corrupt data)");
  495. int headerSize=sizeof(SRecording_Packet);
  496. SRecording_Packet *header=(SRecording_Packet*)(((char*)inPtr)-headerSize); // PTR ARITH
  497. header->type=eRBPT_Free;
  498. storage->m_totalBytesAlloced-=header->size;
  499. CRY_ASSERT_MESSAGE(storage->m_totalBytesAlloced>=0,"CCircularBufferStatsStorage memory stats aren't adding up, total used is negative");
  500. if (storage->IsUsingCircularBuffer())
  501. {
  502. if (storage->m_totalBytesAlloced<=0)
  503. {
  504. #ifdef _DEBUG
  505. // validate that all allocations are freed
  506. for (CRecordingBuffer::iterator iter=storage->m_circularBuffer.begin(), end=storage->m_circularBuffer.end(); iter!=end; ++iter)
  507. {
  508. const SRecording_Packet &packet=*iter;
  509. CRY_ASSERT_TRACE(packet.type==eRBPT_Free,("CCircularBufferStatsStorage thinks there should be no allocations in the circular buffer, but one of type %d remains",packet.type));
  510. }
  511. #endif
  512. storage->m_circularBuffer.Reset();
  513. }
  514. }
  515. else
  516. {
  517. delete [] ((char*)header);
  518. }
  519. }
  520. }
  521. // resets the per session counters used to put usage stats into the output
  522. void CCircularBufferStatsStorage::ResetUsageCounters()
  523. {
  524. m_numDiscards=0;
  525. m_totalBytesRequested=0;
  526. }
  527. // discard callback
  528. void CCircularBufferStatsStorage::DiscardCallback(SRecording_Packet *ps, float recordedTime, void *inUserData)
  529. {
  530. CRY_ASSERT_MESSAGE(false, "This should never happen, we no longer add data to the circular buffer if it is full");
  531. switch (ps->type)
  532. {
  533. case eRBPT_Free:
  534. // nothing to do
  535. break;
  536. case eRBPT_TimelineEntry:
  537. {
  538. // memory layout is
  539. // SRecording_Packet
  540. // class storage - vtable if present will be part of the class storage
  541. int headerSize=sizeof(SRecording_Packet);
  542. CCircularBufferTimelineEntry *entry=(CCircularBufferTimelineEntry*)(((char*)ps)+headerSize); // PTR ARITH
  543. entry->ForceRelease();
  544. CCircularBufferStatsStorage *storage=static_cast<CCircularBufferStatsStorage*>(inUserData);
  545. storage->m_numDiscards++;
  546. }
  547. break;
  548. case eRBPT_StatAnyValue:
  549. {
  550. int headerSize=sizeof(SRecording_Packet);
  551. SCircularStatAnyValue *entry=(SCircularStatAnyValue*)(((char*)ps)+headerSize); // PTR ARITH
  552. delete entry;
  553. CCircularBufferStatsStorage *storage=static_cast<CCircularBufferStatsStorage*>(inUserData);
  554. storage->m_numDiscards++;
  555. }
  556. break;
  557. default:
  558. CRY_ASSERT_MESSAGE(0,"Unknown packet type in CCircularBufferStatsStorage discard callback");
  559. break;
  560. }
  561. }
  562. // static
  563. CCircularBufferTimelineEntry *CCircularBufferTimelineEntry::EntryFromListElement(
  564. const CDoubleLinkedElement *inElement)
  565. {
  566. size_t linkOffset=offsetof(CCircularBufferTimelineEntry,m_timelineLink);
  567. CCircularBufferTimelineEntry *result=(CCircularBufferTimelineEntry*)(((char*)inElement)-linkOffset); // PTR ARITH
  568. assert((&result->m_timelineLink)==inElement);
  569. return result;
  570. }
  571. CCircularBufferTimelineEntry::CCircularBufferTimelineEntry()
  572. {
  573. }
  574. CCircularBufferTimelineEntry::CCircularBufferTimelineEntry(
  575. const CCircularBufferTimelineEntry &inCopyMe)
  576. {
  577. *this=inCopyMe;
  578. }
  579. CCircularBufferTimelineEntry::~CCircularBufferTimelineEntry()
  580. {
  581. assert(GetRefCount()==0);
  582. }
  583. // static
  584. SCircularStatAnyValue *SCircularStatAnyValue::EntryFromListElement(
  585. const CDoubleLinkedElement *inElement)
  586. {
  587. size_t linkOffset=offsetof(SCircularStatAnyValue,link);
  588. SCircularStatAnyValue *result=(SCircularStatAnyValue*)(((char*)inElement)-linkOffset); // PTR ARITH
  589. assert((&result->link)==inElement);
  590. return result;
  591. }
  592. void CCircularBufferTimelineEntry::ForceRelease()
  593. {
  594. int expectedRefcount=(m_timelineLink.IsInList()) ? 1 : 0;
  595. CRY_ASSERT_MESSAGE(GetRefCount()==expectedRefcount,"Circular buffer stats entry is being purged due to lack of space, but some one some where is still holding a ref to it - this is very likely to crash! Code is not permitted to hold references to these objects");
  596. if (expectedRefcount==1)
  597. {
  598. Release();
  599. }
  600. else
  601. {
  602. delete this;
  603. }
  604. }
  605. CCircularXMLSerializer::CCircularXMLSerializer(
  606. CCircularBufferStatsStorage *pInStorage) :
  607. m_pStorage(pInStorage),
  608. m_state(k_notStartedProducing),
  609. m_containerIterator(-1),
  610. m_eventIterator(NULL),
  611. m_timelineIterator(-1),
  612. m_stateIterator(-1),
  613. m_indentLevel(0)
  614. {
  615. m_pStorage->LockForSerialization();
  616. m_entries.reserve(256);
  617. IGameStatistics *pStat=g_pGame->GetIGameFramework()->GetIGameStatistics();
  618. if (pStat)
  619. {
  620. pStat->RegisterSerializer(this);
  621. }
  622. memset(m_indentStr,' ',sizeof(m_indentStr));
  623. }
  624. CCircularXMLSerializer::~CCircularXMLSerializer()
  625. {
  626. m_pStorage->UnlockFromSerialization();
  627. IGameStatistics *pStat=g_pGame->GetIGameFramework()->GetIGameStatistics();
  628. if (pStat)
  629. {
  630. pStat->UnregisterSerializer(this);
  631. }
  632. m_entries.clear(); // important, clear all references to serialize entries (which are stored in the storage buffer) before we release our reference to the storage buffer
  633. m_pStorage=NULL;
  634. }
  635. void CCircularXMLSerializer::IncreaseIndentation(
  636. int inDelta)
  637. {
  638. m_indentLevel+=inDelta;
  639. m_indentLevel=clamp_tpl<int>(m_indentLevel,0,ARRAY_COUNT(m_indentStr));
  640. }
  641. // outputs some text into the telemetry buffer with the current indentation level
  642. // returns true if successful, false if it didn't fit
  643. bool CCircularXMLSerializer::Output(
  644. SWriteState *pIOState,
  645. const char *pInDataToWrite,
  646. int inDataLenToWrite,
  647. bool inDoIndent)
  648. {
  649. bool result=true;
  650. int dataWritten=pIOState->dataWritten;
  651. int indentLevel=inDoIndent ? m_indentLevel : 0;
  652. char *pBuffer=pIOState->pBuffer;
  653. if ((inDataLenToWrite+indentLevel)<=(pIOState->bufferSize-dataWritten))
  654. {
  655. if (indentLevel>0)
  656. {
  657. memcpy(pBuffer+dataWritten,m_indentStr,indentLevel);
  658. dataWritten+=indentLevel;
  659. }
  660. memcpy(pBuffer+dataWritten,pInDataToWrite,inDataLenToWrite);
  661. dataWritten+=inDataLenToWrite;
  662. }
  663. else
  664. {
  665. // buffer full, return what we've got
  666. pIOState->full=true;
  667. result=false;
  668. }
  669. pIOState->dataWritten=dataWritten;
  670. return result;
  671. }
  672. #define OUTPUT(str,len) Output(pIOState,str,len,true)
  673. #define OUTPUT_NO_INDENT(str,len) Output(pIOState,str,len,false)
  674. void CCircularXMLSerializer::SerializeContainerTag(
  675. SWriteState *pIOState,
  676. CCircularBufferStatsContainer *pInCont,
  677. IGameStatistics *pInStats)
  678. {
  679. if (m_stateIterator==-1 && !pIOState->full)
  680. {
  681. const size_t numStates=pInStats->GetStateCount();
  682. CryFixedStringT<512> tag;
  683. const SSerializeEntry *entry=&m_entries[m_containerIterator];
  684. switch (entry->action)
  685. {
  686. case k_openTag:
  687. tag.Format("<%s",entry->tagName.c_str());
  688. for (int i=0; i<int(numStates); i++)
  689. {
  690. const SStatAnyValue *pState=pInCont->GetState(i);
  691. if (pState != NULL && pState->IsValidType() && pState->type!=eSAT_TXML) // states which are not xml sub nodes get encoded as attributes in the container's start element tag
  692. {
  693. stack_string strValue;
  694. if(pState->ToString(strValue))
  695. {
  696. CryFixedStringT<128> str2;
  697. str2.Format(" %s=\"%s\"",pInStats->GetStateDesc(i)->serializeName.c_str(),strValue.c_str());
  698. tag+=(const char*)str2;
  699. }
  700. }
  701. }
  702. tag+=">\n";
  703. if (OUTPUT(tag.c_str(),tag.length()))
  704. {
  705. IncreaseIndentation(1);
  706. }
  707. break;
  708. case k_closeTag:
  709. IncreaseIndentation(-1);
  710. tag.Format("</%s>\n",entry->tagName.c_str());
  711. if (!OUTPUT(tag.c_str(),tag.length()))
  712. {
  713. IncreaseIndentation(1);
  714. }
  715. break;
  716. default:
  717. assert(0);
  718. break;
  719. }
  720. if (!pIOState->full)
  721. {
  722. ++m_stateIterator;
  723. }
  724. }
  725. }
  726. void CCircularXMLSerializer::SerializeTimeline(
  727. SWriteState *pIOState,
  728. CCircularBufferStatsContainer *pInCont,
  729. IGameStatistics *pInStats)
  730. {
  731. const CCircularBufferTimeline *pTimeline=pInCont->GetTimeline(m_timelineIterator);
  732. if (m_eventIterator==NULL)
  733. {
  734. if (!pTimeline->m_list.empty())
  735. {
  736. CryFixedStringT<80> tlStart;
  737. tlStart.Format("<timeline name=\"%s\">\n",pInStats->GetEventDesc(m_timelineIterator)->serializeName.c_str());
  738. if (OUTPUT(tlStart.c_str(),tlStart.length()))
  739. {
  740. IncreaseIndentation(1);
  741. }
  742. }
  743. if (!pIOState->full)
  744. {
  745. m_eventIterator=pTimeline->m_list.begin();
  746. }
  747. }
  748. CDoubleLinkedList::const_iterator end=pTimeline->m_list.end();
  749. uint32 bufferSize=64*1024;
  750. char* buffer=(char*)malloc(bufferSize);
  751. while (m_eventIterator!=end && !pIOState->full)
  752. {
  753. // currently individual events are still serialised through an xml node,
  754. // but the entire tree is no longer built up
  755. // if usage of these xml nodes presents a problem, we need to replace the GetXML() method
  756. // with a GetXMLString() method instead, which can return the xml text without creating
  757. // nodes
  758. // however, as the lifespan of the xml nodes is so short, i don't anticipate these being
  759. // necessary - they're practically stack objects
  760. XmlNodeRef xml;
  761. switch (pTimeline->GetType())
  762. {
  763. case eRBPT_TimelineEntry:
  764. {
  765. CCircularBufferTimelineEntry *pTimelineEntry=CCircularBufferTimelineEntry::EntryFromListElement(*m_eventIterator);
  766. xml=pInStats->CreateStatXMLNode("val");
  767. xml->setAttr("time",pTimelineEntry->GetTime().GetMilliSecondsAsInt64());
  768. XmlNodeRef child=pTimelineEntry->GetXML(pInStats);
  769. child->setTag("param");
  770. xml->addChild(child);
  771. }
  772. break;
  773. case eRBPT_StatAnyValue:
  774. {
  775. SCircularStatAnyValue *pVal=SCircularStatAnyValue::EntryFromListElement(*m_eventIterator);
  776. xml=pInStats->CreateStatXMLNode("val");
  777. xml->setAttr("time",pVal->time.GetMilliSecondsAsInt64());
  778. CRY_ASSERT_MESSAGE(pVal->val.type!=eSAT_TXML,"adding SStatAnyValue() with xmlnodes is no longer supported, add a data type derived from IXMLSerializable instead");
  779. stack_string strValue;
  780. if(pVal->val.ToString(strValue))
  781. {
  782. xml->setAttr("prm",strValue);
  783. }
  784. }
  785. break;
  786. default:
  787. CRY_ASSERT_MESSAGE(0,"unknown data type found in timeline, cannot save data into telemetry");
  788. break;
  789. }
  790. if (xml)
  791. {
  792. XmlString str=xml->getXMLUnsafe(m_indentLevel, buffer, bufferSize); // use the xml indentation to handle multi line xml nodes correctly
  793. OUTPUT_NO_INDENT(str.c_str(),str.length());
  794. }
  795. if (!pIOState->full)
  796. {
  797. ++m_eventIterator;
  798. }
  799. }
  800. free(buffer);
  801. if (m_eventIterator==end && !pTimeline->m_list.empty() && !pIOState->full)
  802. {
  803. IncreaseIndentation(-1);
  804. if (!OUTPUT("</timeline>\n",12))
  805. {
  806. IncreaseIndentation(1);
  807. }
  808. }
  809. }
  810. void CCircularXMLSerializer::SerializeTimelines(
  811. SWriteState *pIOState,
  812. CCircularBufferStatsContainer *pInCont,
  813. IGameStatistics *pInStats)
  814. {
  815. const size_t numEventTimelines=pInStats->GetEventCount();
  816. while (m_timelineIterator<=int(numEventTimelines) && !pIOState->full)
  817. {
  818. if (m_timelineIterator==-1)
  819. {
  820. if (pInCont->HasAnyTimelineEvents())
  821. {
  822. if (OUTPUT("<timelines>\n",12))
  823. {
  824. IncreaseIndentation(1);
  825. }
  826. }
  827. }
  828. else if (m_timelineIterator==int(numEventTimelines))
  829. {
  830. if (pInCont->HasAnyTimelineEvents())
  831. {
  832. IncreaseIndentation(-1);
  833. if (!OUTPUT("</timelines>\n",13))
  834. {
  835. IncreaseIndentation(1);
  836. }
  837. }
  838. }
  839. else
  840. {
  841. SerializeTimeline(pIOState,pInCont,pInStats);
  842. }
  843. if (!pIOState->full)
  844. {
  845. ++m_timelineIterator;
  846. m_eventIterator=NULL;
  847. }
  848. }
  849. }
  850. void CCircularXMLSerializer::SerializeStates(
  851. SWriteState *pIOState,
  852. CCircularBufferStatsContainer *pInCont,
  853. IGameStatistics *pInStats)
  854. {
  855. const size_t numStates=pInStats->GetStateCount();
  856. while (m_stateIterator<int(numStates) && !pIOState->full)
  857. {
  858. const SStatAnyValue *pState=pInCont->GetState(m_stateIterator);
  859. if (pState != NULL && pState->IsValidType() && pState->type==eSAT_TXML && pState->pSerializable)
  860. {
  861. XmlNodeRef xml=pState->pSerializable->GetXML(pInStats);
  862. xml->setTag(pInStats->GetStateDesc(m_stateIterator)->serializeName.c_str());
  863. if (xml)
  864. {
  865. XmlString str=xml->getXML(m_indentLevel);
  866. OUTPUT_NO_INDENT(str.c_str(),str.length());
  867. }
  868. }
  869. if (!pIOState->full)
  870. {
  871. ++m_stateIterator;
  872. }
  873. }
  874. }
  875. ITelemetryProducer::EResult CCircularXMLSerializer::ProduceTelemetry(
  876. char *pOutBuffer,
  877. int inMinRequired,
  878. int inBufferSize,
  879. int *pOutWritten)
  880. {
  881. EResult result=eTS_EndOfStream;
  882. IGameStatistics *pStat=(g_pGame && g_pGame->GetIGameFramework()) ? g_pGame->GetIGameFramework()->GetIGameStatistics() : NULL;
  883. SWriteState state;
  884. state.pBuffer=pOutBuffer;
  885. state.bufferSize=inBufferSize;
  886. state.dataWritten=0;
  887. state.full=false;
  888. if (pStat)
  889. {
  890. if (m_state==k_notStartedProducing)
  891. {
  892. m_state=k_producing;
  893. m_containerIterator=0;
  894. m_stateIterator=-1;
  895. m_timelineIterator=-1;
  896. m_eventIterator=NULL;
  897. }
  898. int containerSize=m_entries.size();
  899. while (m_containerIterator!=containerSize && !state.full)
  900. {
  901. const SSerializeEntry *entry=&m_entries[m_containerIterator];
  902. CCircularBufferStatsContainer *pCont=static_cast<CCircularBufferStatsContainer*>(entry->pContainer.get());
  903. SerializeContainerTag(&state,pCont,pStat);
  904. if (entry->action==k_openTag)
  905. {
  906. SerializeTimelines(&state,pCont,pStat);
  907. SerializeStates(&state,pCont,pStat);
  908. }
  909. if (!state.full)
  910. {
  911. ++m_containerIterator;
  912. m_stateIterator=-1;
  913. m_timelineIterator=-1;
  914. }
  915. }
  916. if (m_state==k_producing && m_containerIterator!=containerSize)
  917. {
  918. assert(state.dataWritten>0);
  919. result=eTS_Available;
  920. }
  921. }
  922. else
  923. {
  924. CryWarning(VALIDATOR_MODULE_GAME,VALIDATOR_ERROR,"Failed to serialise data - IGameStatistics interface removed during serialisation"); // possibly could happen during game tear down
  925. }
  926. *pOutWritten=state.dataWritten;
  927. return result;
  928. }
  929. #undef OUTPUT
  930. #undef OUTPUT_NO_INDENT
  931. void CCircularXMLSerializer::VisitNode(const SNodeLocator& locator, const char* serializeName, IStatsContainer& container, EStatNodeState state)
  932. {
  933. switch (state)
  934. {
  935. case eSNS_Alive:
  936. CryWarning(VALIDATOR_MODULE_GAME,VALIDATOR_ERROR,"Live stats node encountered during stats serialization, ignoring %s\n",serializeName);
  937. break;
  938. case eSNS_Dead:
  939. {
  940. m_entries.resize(m_entries.size()+1);
  941. SSerializeEntry &entry=m_entries.back();
  942. entry.action=k_openTag;
  943. entry.tagName=serializeName;
  944. entry.pContainer=&container;
  945. assert(m_state==k_notStartedProducing); // don't mess with array once iteration has started!
  946. }
  947. break;
  948. default:
  949. assert(0);
  950. break;
  951. }
  952. }
  953. void CCircularXMLSerializer::LeaveNode(const SNodeLocator& locator, const char* serializeName, IStatsContainer& container, EStatNodeState state)
  954. {
  955. switch (state)
  956. {
  957. case eSNS_Alive:
  958. CryWarning(VALIDATOR_MODULE_GAME,VALIDATOR_ERROR,"Live stats node encountered during stats serialization, ignoring %s\n",serializeName);
  959. break;
  960. case eSNS_Dead:
  961. {
  962. m_entries.resize(m_entries.size()+1);
  963. SSerializeEntry &entry=m_entries.back();
  964. entry.action=k_closeTag;
  965. entry.tagName=serializeName;
  966. entry.pContainer=&container;
  967. assert(m_state==k_notStartedProducing); // don't mess with array once iteration has started!
  968. }
  969. break;
  970. default:
  971. assert(0);
  972. break;
  973. }
  974. }