DebugLogManager.cs 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796
  1. using UnityEngine;
  2. using UnityEngine.UI;
  3. using UnityEngine.EventSystems;
  4. using System.Collections.Generic;
  5. using System.IO;
  6. // Receives debug entries and custom events (e.g. Clear, Collapse, Filter by Type)
  7. // and notifies the recycled list view of changes to the list of debug entries
  8. //
  9. // - Vocabulary -
  10. // Debug/Log entry: a Debug.Log/LogError/LogWarning/LogException/LogAssertion request made by
  11. // the client and intercepted by this manager object
  12. // Debug/Log item: a visual (uGUI) representation of a debug entry
  13. //
  14. // There can be a lot of debug entries in the system but there will only be a handful of log items
  15. // to show their properties on screen (these log items are recycled as the list is scrolled)
  16. // An enum to represent filtered log types
  17. namespace IngameDebugConsole
  18. {
  19. public enum DebugLogFilter
  20. {
  21. None = 0,
  22. Info = 1,
  23. Warning = 2,
  24. Error = 4,
  25. All = 7
  26. }
  27. public class DebugLogManager : MonoBehaviour
  28. {
  29. private static DebugLogManager instance = null;
  30. #pragma warning disable 0649
  31. // Debug console will persist between scenes
  32. [Header( "Properties" )]
  33. [SerializeField]
  34. [HideInInspector]
  35. private bool singleton = true;
  36. // Minimum height of the console window
  37. [SerializeField]
  38. [HideInInspector]
  39. private float minimumHeight = 200f;
  40. [SerializeField]
  41. [HideInInspector]
  42. private bool enablePopup = true;
  43. [SerializeField]
  44. [HideInInspector]
  45. private bool startInPopupMode = true;
  46. [SerializeField]
  47. [HideInInspector]
  48. private bool toggleWithKey = false;
  49. [SerializeField]
  50. [HideInInspector]
  51. private KeyCode toggleKey = KeyCode.BackQuote;
  52. // Should command input field be cleared after pressing Enter
  53. [SerializeField]
  54. [HideInInspector]
  55. private bool clearCommandAfterExecution = true;
  56. [SerializeField]
  57. [HideInInspector]
  58. private int commandHistorySize = 15;
  59. [SerializeField]
  60. [HideInInspector]
  61. private bool receiveLogcatLogsInAndroid = false;
  62. [SerializeField]
  63. [HideInInspector]
  64. private string logcatArguments;
  65. [Header( "Visuals" )]
  66. [SerializeField]
  67. private DebugLogItem logItemPrefab;
  68. // Visuals for different log types
  69. [SerializeField]
  70. private Sprite infoLog;
  71. [SerializeField]
  72. private Sprite warningLog;
  73. [SerializeField]
  74. private Sprite errorLog;
  75. private Dictionary<LogType, Sprite> logSpriteRepresentations;
  76. [SerializeField]
  77. private Color collapseButtonNormalColor;
  78. [SerializeField]
  79. private Color collapseButtonSelectedColor;
  80. [SerializeField]
  81. private Color filterButtonsNormalColor;
  82. [SerializeField]
  83. private Color filterButtonsSelectedColor;
  84. [Header( "Internal References" )]
  85. [SerializeField]
  86. private RectTransform logWindowTR;
  87. private RectTransform canvasTR;
  88. [SerializeField]
  89. private RectTransform logItemsContainer;
  90. [SerializeField]
  91. private InputField commandInputField;
  92. [SerializeField]
  93. private Image collapseButton;
  94. [SerializeField]
  95. private Image filterInfoButton;
  96. [SerializeField]
  97. private Image filterWarningButton;
  98. [SerializeField]
  99. private Image filterErrorButton;
  100. [SerializeField]
  101. private Text infoEntryCountText;
  102. [SerializeField]
  103. private Text warningEntryCountText;
  104. [SerializeField]
  105. private Text errorEntryCountText;
  106. [SerializeField]
  107. private GameObject snapToBottomButton;
  108. // Canvas group to modify visibility of the log window
  109. [SerializeField]
  110. private CanvasGroup logWindowCanvasGroup;
  111. [SerializeField]
  112. private DebugLogPopup popupManager;
  113. [SerializeField]
  114. private ScrollRect logItemsScrollRect;
  115. // Recycled list view to handle the log items efficiently
  116. [SerializeField]
  117. private DebugLogRecycledListView recycledListView;
  118. #pragma warning restore 0649
  119. // Number of entries filtered by their types
  120. private int infoEntryCount = 0, warningEntryCount = 0, errorEntryCount = 0;
  121. private bool isLogWindowVisible = true;
  122. private bool screenDimensionsChanged = false;
  123. // Filters to apply to the list of debug entries to show
  124. private bool isCollapseOn = false;
  125. private DebugLogFilter logFilter = DebugLogFilter.All;
  126. // If the last log item is completely visible (scrollbar is at the bottom),
  127. // scrollbar will remain at the bottom when new debug entries are received
  128. private bool snapToBottom = true;
  129. // List of unique debug entries (duplicates of entries are not kept)
  130. private List<DebugLogEntry> collapsedLogEntries;
  131. // Dictionary to quickly find if a log already exists in collapsedLogEntries
  132. private Dictionary<DebugLogEntry, int> collapsedLogEntriesMap;
  133. // The order the collapsedLogEntries are received
  134. // (duplicate entries have the same index (value))
  135. private DebugLogIndexList uncollapsedLogEntriesIndices;
  136. // Filtered list of debug entries to show
  137. private DebugLogIndexList indicesOfListEntriesToShow;
  138. // Logs that should be registered in Update-loop
  139. private List<QueuedDebugLogEntry> queuedLogs;
  140. private List<DebugLogItem> pooledLogItems;
  141. // History of the previously entered commands
  142. private CircularBuffer<string> commandHistory;
  143. private int commandHistoryIndex = -1;
  144. // Required in ValidateScrollPosition() function
  145. private PointerEventData nullPointerEventData;
  146. #if !UNITY_EDITOR && UNITY_ANDROID
  147. private DebugLogLogcatListener logcatListener;
  148. #endif
  149. private void Awake()
  150. {
  151. // Only one instance of debug console is allowed
  152. if( instance == null )
  153. {
  154. instance = this;
  155. // If it is a singleton object, don't destroy it between scene changes
  156. if( singleton )
  157. DontDestroyOnLoad( gameObject );
  158. }
  159. else if( this != instance )
  160. {
  161. Destroy( gameObject );
  162. return;
  163. }
  164. pooledLogItems = new List<DebugLogItem>();
  165. queuedLogs = new List<QueuedDebugLogEntry>();
  166. commandHistory = new CircularBuffer<string>( commandHistorySize );
  167. canvasTR = (RectTransform) transform;
  168. // Associate sprites with log types
  169. logSpriteRepresentations = new Dictionary<LogType, Sprite>
  170. {
  171. { LogType.Log, infoLog },
  172. { LogType.Warning, warningLog },
  173. { LogType.Error, errorLog },
  174. { LogType.Exception, errorLog },
  175. { LogType.Assert, errorLog }
  176. };
  177. // Initially, all log types are visible
  178. filterInfoButton.color = filterButtonsSelectedColor;
  179. filterWarningButton.color = filterButtonsSelectedColor;
  180. filterErrorButton.color = filterButtonsSelectedColor;
  181. collapsedLogEntries = new List<DebugLogEntry>( 128 );
  182. collapsedLogEntriesMap = new Dictionary<DebugLogEntry, int>( 128 );
  183. uncollapsedLogEntriesIndices = new DebugLogIndexList();
  184. indicesOfListEntriesToShow = new DebugLogIndexList();
  185. recycledListView.Initialize( this, collapsedLogEntries, indicesOfListEntriesToShow, logItemPrefab.Transform.sizeDelta.y );
  186. recycledListView.UpdateItemsInTheList( true );
  187. if( minimumHeight < 200f )
  188. minimumHeight = 200f;
  189. nullPointerEventData = new PointerEventData( null );
  190. }
  191. private void OnEnable()
  192. {
  193. // Intercept debug entries
  194. Application.logMessageReceived -= ReceivedLog;
  195. Application.logMessageReceived += ReceivedLog;
  196. // Listen for entered commands
  197. commandInputField.onValidateInput -= OnValidateCommand;
  198. commandInputField.onValidateInput += OnValidateCommand;
  199. if( receiveLogcatLogsInAndroid )
  200. {
  201. #if !UNITY_EDITOR && UNITY_ANDROID
  202. if( logcatListener == null )
  203. logcatListener = new DebugLogLogcatListener();
  204. logcatListener.Start( logcatArguments );
  205. #endif
  206. }
  207. DebugLogConsole.AddCommandInstance( "save_logs", "Saves logs to a file", "SaveLogsToFile", this );
  208. //Debug.LogAssertion( "assert" );
  209. //Debug.LogError( "error" );
  210. //Debug.LogException( new System.IO.EndOfStreamException() );
  211. //Debug.LogWarning( "warning" );
  212. //Debug.Log( "log" );
  213. }
  214. private void OnDisable()
  215. {
  216. if( instance != this )
  217. return;
  218. // Stop receiving debug entries
  219. Application.logMessageReceived -= ReceivedLog;
  220. #if !UNITY_EDITOR && UNITY_ANDROID
  221. if( logcatListener != null )
  222. logcatListener.Stop();
  223. #endif
  224. // Stop receiving commands
  225. commandInputField.onValidateInput -= OnValidateCommand;
  226. DebugLogConsole.RemoveCommand( "save_logs" );
  227. }
  228. // Launch in popup mode
  229. private void Start()
  230. {
  231. if( enablePopup && startInPopupMode )
  232. ShowPopup();
  233. else
  234. {
  235. ShowLogWindow();
  236. popupManager.gameObject.SetActive( enablePopup );
  237. }
  238. }
  239. // Window is resized, update the list
  240. private void OnRectTransformDimensionsChange()
  241. {
  242. screenDimensionsChanged = true;
  243. }
  244. // If snapToBottom is enabled, force the scrollbar to the bottom
  245. private void LateUpdate()
  246. {
  247. int queuedLogCount = queuedLogs.Count;
  248. if( queuedLogCount > 0 )
  249. {
  250. for( int i = 0; i < queuedLogCount; i++ )
  251. {
  252. QueuedDebugLogEntry logEntry = queuedLogs[i];
  253. ReceivedLog( logEntry.logString, logEntry.stackTrace, logEntry.logType );
  254. }
  255. queuedLogs.Clear();
  256. }
  257. if( screenDimensionsChanged )
  258. {
  259. // Update the recycled list view
  260. if( isLogWindowVisible )
  261. recycledListView.OnViewportDimensionsChanged();
  262. else
  263. popupManager.OnViewportDimensionsChanged();
  264. screenDimensionsChanged = false;
  265. }
  266. if( snapToBottom )
  267. {
  268. logItemsScrollRect.verticalNormalizedPosition = 0f;
  269. if( snapToBottomButton.activeSelf )
  270. snapToBottomButton.SetActive( false );
  271. }
  272. else
  273. {
  274. float scrollPos = logItemsScrollRect.verticalNormalizedPosition;
  275. if( snapToBottomButton.activeSelf != ( scrollPos > 1E-6f && scrollPos < 0.9999f ) )
  276. snapToBottomButton.SetActive( !snapToBottomButton.activeSelf );
  277. }
  278. if( toggleWithKey )
  279. {
  280. if( Input.GetKeyDown( toggleKey ) )
  281. {
  282. if( isLogWindowVisible )
  283. ShowPopup();
  284. else
  285. ShowLogWindow();
  286. }
  287. }
  288. if( isLogWindowVisible && commandInputField.isFocused )
  289. {
  290. if( Input.GetKeyDown( KeyCode.UpArrow ) )
  291. {
  292. if( commandHistoryIndex == -1 )
  293. commandHistoryIndex = commandHistory.Count - 1;
  294. else if( --commandHistoryIndex < 0 )
  295. commandHistoryIndex = 0;
  296. if( commandHistoryIndex >= 0 && commandHistoryIndex < commandHistory.Count )
  297. {
  298. commandInputField.text = commandHistory[commandHistoryIndex];
  299. commandInputField.caretPosition = commandInputField.text.Length;
  300. }
  301. }
  302. else if( Input.GetKeyDown( KeyCode.DownArrow ) )
  303. {
  304. if( commandHistoryIndex == -1 )
  305. commandHistoryIndex = commandHistory.Count - 1;
  306. else if( ++commandHistoryIndex >= commandHistory.Count )
  307. commandHistoryIndex = commandHistory.Count - 1;
  308. if( commandHistoryIndex >= 0 && commandHistoryIndex < commandHistory.Count )
  309. commandInputField.text = commandHistory[commandHistoryIndex];
  310. }
  311. }
  312. #if !UNITY_EDITOR && UNITY_ANDROID
  313. if( logcatListener != null )
  314. {
  315. string log;
  316. while( ( log = logcatListener.GetLog() ) != null )
  317. ReceivedLog( "LOGCAT: " + log, string.Empty, LogType.Log );
  318. }
  319. #endif
  320. }
  321. public void ShowLogWindow()
  322. {
  323. // Show the log window
  324. logWindowCanvasGroup.interactable = true;
  325. logWindowCanvasGroup.blocksRaycasts = true;
  326. logWindowCanvasGroup.alpha = 1f;
  327. popupManager.Hide();
  328. // Update the recycled list view
  329. // (in case new entries were intercepted while log window was hidden)
  330. recycledListView.OnLogEntriesUpdated( true );
  331. isLogWindowVisible = true;
  332. }
  333. public void ShowPopup()
  334. {
  335. // Hide the log window
  336. logWindowCanvasGroup.interactable = false;
  337. logWindowCanvasGroup.blocksRaycasts = false;
  338. logWindowCanvasGroup.alpha = 0f;
  339. popupManager.Show();
  340. commandHistoryIndex = -1;
  341. isLogWindowVisible = false;
  342. }
  343. // Command field input is changed, check if command is submitted
  344. public char OnValidateCommand( string text, int charIndex, char addedChar )
  345. {
  346. if( addedChar == '\t' ) // Autocomplete attempt
  347. {
  348. if( !string.IsNullOrEmpty( text ) )
  349. {
  350. string autoCompletedCommand = DebugLogConsole.GetAutoCompleteCommand( text );
  351. if( !string.IsNullOrEmpty( autoCompletedCommand ) )
  352. commandInputField.text = autoCompletedCommand;
  353. }
  354. return '\0';
  355. }
  356. else if( addedChar == '\n' ) // Command is submitted
  357. {
  358. // Clear the command field
  359. if( clearCommandAfterExecution )
  360. commandInputField.text = "";
  361. if( text.Length > 0 )
  362. {
  363. if( commandHistory.Count == 0 || commandHistory[commandHistory.Count - 1] != text )
  364. commandHistory.Add( text );
  365. commandHistoryIndex = -1;
  366. // Execute the command
  367. DebugLogConsole.ExecuteCommand( text );
  368. // Snap to bottom and select the latest entry
  369. SetSnapToBottom( true );
  370. }
  371. return '\0';
  372. }
  373. return addedChar;
  374. }
  375. // A debug entry is received
  376. private void ReceivedLog( string logString, string stackTrace, LogType logType )
  377. {
  378. if( CanvasUpdateRegistry.IsRebuildingGraphics() || CanvasUpdateRegistry.IsRebuildingLayout() )
  379. {
  380. // Trying to update the UI while the canvas is being rebuilt will throw warnings in the Unity console
  381. queuedLogs.Add( new QueuedDebugLogEntry( logString, stackTrace, logType ) );
  382. return;
  383. }
  384. DebugLogEntry logEntry = new DebugLogEntry( logString, stackTrace, null );
  385. // Check if this entry is a duplicate (i.e. has been received before)
  386. int logEntryIndex;
  387. bool isEntryInCollapsedEntryList = collapsedLogEntriesMap.TryGetValue( logEntry, out logEntryIndex );
  388. if( !isEntryInCollapsedEntryList )
  389. {
  390. // It is not a duplicate,
  391. // add it to the list of unique debug entries
  392. logEntry.logTypeSpriteRepresentation = logSpriteRepresentations[logType];
  393. logEntryIndex = collapsedLogEntries.Count;
  394. collapsedLogEntries.Add( logEntry );
  395. collapsedLogEntriesMap[logEntry] = logEntryIndex;
  396. }
  397. else
  398. {
  399. // It is a duplicate,
  400. // increment the original debug item's collapsed count
  401. logEntry = collapsedLogEntries[logEntryIndex];
  402. logEntry.count++;
  403. }
  404. // Add the index of the unique debug entry to the list
  405. // that stores the order the debug entries are received
  406. uncollapsedLogEntriesIndices.Add( logEntryIndex );
  407. // If this debug entry matches the current filters,
  408. // add it to the list of debug entries to show
  409. Sprite logTypeSpriteRepresentation = logEntry.logTypeSpriteRepresentation;
  410. if( isCollapseOn && isEntryInCollapsedEntryList )
  411. {
  412. if( isLogWindowVisible )
  413. recycledListView.OnCollapsedLogEntryAtIndexUpdated( logEntryIndex );
  414. }
  415. else if( logFilter == DebugLogFilter.All ||
  416. ( logTypeSpriteRepresentation == infoLog && ( ( logFilter & DebugLogFilter.Info ) == DebugLogFilter.Info ) ) ||
  417. ( logTypeSpriteRepresentation == warningLog && ( ( logFilter & DebugLogFilter.Warning ) == DebugLogFilter.Warning ) ) ||
  418. ( logTypeSpriteRepresentation == errorLog && ( ( logFilter & DebugLogFilter.Error ) == DebugLogFilter.Error ) ) )
  419. {
  420. indicesOfListEntriesToShow.Add( logEntryIndex );
  421. if( isLogWindowVisible )
  422. recycledListView.OnLogEntriesUpdated( false );
  423. }
  424. if( logType == LogType.Log )
  425. {
  426. infoEntryCount++;
  427. infoEntryCountText.text = infoEntryCount.ToString();
  428. // If debug popup is visible, notify it of the new debug entry
  429. if( !isLogWindowVisible )
  430. popupManager.NewInfoLogArrived();
  431. }
  432. else if( logType == LogType.Warning )
  433. {
  434. warningEntryCount++;
  435. warningEntryCountText.text = warningEntryCount.ToString();
  436. // If debug popup is visible, notify it of the new debug entry
  437. if( !isLogWindowVisible )
  438. popupManager.NewWarningLogArrived();
  439. }
  440. else
  441. {
  442. errorEntryCount++;
  443. errorEntryCountText.text = errorEntryCount.ToString();
  444. // If debug popup is visible, notify it of the new debug entry
  445. if( !isLogWindowVisible )
  446. popupManager.NewErrorLogArrived();
  447. }
  448. }
  449. // Value of snapToBottom is changed (user scrolled the list manually)
  450. public void SetSnapToBottom( bool snapToBottom )
  451. {
  452. this.snapToBottom = snapToBottom;
  453. }
  454. // Make sure the scroll bar of the scroll rect is adjusted properly
  455. public void ValidateScrollPosition()
  456. {
  457. logItemsScrollRect.OnScroll( nullPointerEventData );
  458. }
  459. // Hide button is clicked
  460. public void HideButtonPressed()
  461. {
  462. ShowPopup();
  463. }
  464. // Clear button is clicked
  465. public void ClearButtonPressed()
  466. {
  467. snapToBottom = true;
  468. infoEntryCount = 0;
  469. warningEntryCount = 0;
  470. errorEntryCount = 0;
  471. infoEntryCountText.text = "0";
  472. warningEntryCountText.text = "0";
  473. errorEntryCountText.text = "0";
  474. collapsedLogEntries.Clear();
  475. collapsedLogEntriesMap.Clear();
  476. uncollapsedLogEntriesIndices.Clear();
  477. indicesOfListEntriesToShow.Clear();
  478. recycledListView.DeselectSelectedLogItem();
  479. recycledListView.OnLogEntriesUpdated( true );
  480. }
  481. // Collapse button is clicked
  482. public void CollapseButtonPressed()
  483. {
  484. // Swap the value of collapse mode
  485. isCollapseOn = !isCollapseOn;
  486. snapToBottom = true;
  487. collapseButton.color = isCollapseOn ? collapseButtonSelectedColor : collapseButtonNormalColor;
  488. recycledListView.SetCollapseMode( isCollapseOn );
  489. // Determine the new list of debug entries to show
  490. FilterLogs();
  491. }
  492. // Filtering mode of info logs has been changed
  493. public void FilterLogButtonPressed()
  494. {
  495. logFilter = logFilter ^ DebugLogFilter.Info;
  496. if( ( logFilter & DebugLogFilter.Info ) == DebugLogFilter.Info )
  497. filterInfoButton.color = filterButtonsSelectedColor;
  498. else
  499. filterInfoButton.color = filterButtonsNormalColor;
  500. FilterLogs();
  501. }
  502. // Filtering mode of warning logs has been changed
  503. public void FilterWarningButtonPressed()
  504. {
  505. logFilter = logFilter ^ DebugLogFilter.Warning;
  506. if( ( logFilter & DebugLogFilter.Warning ) == DebugLogFilter.Warning )
  507. filterWarningButton.color = filterButtonsSelectedColor;
  508. else
  509. filterWarningButton.color = filterButtonsNormalColor;
  510. FilterLogs();
  511. }
  512. // Filtering mode of error logs has been changed
  513. public void FilterErrorButtonPressed()
  514. {
  515. logFilter = logFilter ^ DebugLogFilter.Error;
  516. if( ( logFilter & DebugLogFilter.Error ) == DebugLogFilter.Error )
  517. filterErrorButton.color = filterButtonsSelectedColor;
  518. else
  519. filterErrorButton.color = filterButtonsNormalColor;
  520. FilterLogs();
  521. }
  522. // Debug window is being resized,
  523. // Set the sizeDelta property of the window accordingly while
  524. // preventing window dimensions from going below the minimum dimensions
  525. public void Resize( BaseEventData dat )
  526. {
  527. PointerEventData eventData = (PointerEventData) dat;
  528. // Grab the resize button from top; 36f is the height of the resize button
  529. float newHeight = ( eventData.position.y - logWindowTR.position.y ) / -canvasTR.localScale.y + 36f;
  530. if( newHeight < minimumHeight )
  531. newHeight = minimumHeight;
  532. Vector2 anchorMin = logWindowTR.anchorMin;
  533. anchorMin.y = Mathf.Max( 0f, 1f - newHeight / canvasTR.sizeDelta.y );
  534. logWindowTR.anchorMin = anchorMin;
  535. // Update the recycled list view
  536. recycledListView.OnViewportDimensionsChanged();
  537. }
  538. // Determine the filtered list of debug entries to show on screen
  539. private void FilterLogs()
  540. {
  541. if( logFilter == DebugLogFilter.None )
  542. {
  543. // Show no entry
  544. indicesOfListEntriesToShow.Clear();
  545. }
  546. else if( logFilter == DebugLogFilter.All )
  547. {
  548. if( isCollapseOn )
  549. {
  550. // All the unique debug entries will be listed just once.
  551. // So, list of debug entries to show is the same as the
  552. // order these unique debug entries are added to collapsedLogEntries
  553. indicesOfListEntriesToShow.Clear();
  554. for( int i = 0; i < collapsedLogEntries.Count; i++ )
  555. indicesOfListEntriesToShow.Add( i );
  556. }
  557. else
  558. {
  559. indicesOfListEntriesToShow.Clear();
  560. for( int i = 0; i < uncollapsedLogEntriesIndices.Count; i++ )
  561. indicesOfListEntriesToShow.Add( uncollapsedLogEntriesIndices[i] );
  562. }
  563. }
  564. else
  565. {
  566. // Show only the debug entries that match the current filter
  567. bool isInfoEnabled = ( logFilter & DebugLogFilter.Info ) == DebugLogFilter.Info;
  568. bool isWarningEnabled = ( logFilter & DebugLogFilter.Warning ) == DebugLogFilter.Warning;
  569. bool isErrorEnabled = ( logFilter & DebugLogFilter.Error ) == DebugLogFilter.Error;
  570. if( isCollapseOn )
  571. {
  572. indicesOfListEntriesToShow.Clear();
  573. for( int i = 0; i < collapsedLogEntries.Count; i++ )
  574. {
  575. DebugLogEntry logEntry = collapsedLogEntries[i];
  576. if( logEntry.logTypeSpriteRepresentation == infoLog && isInfoEnabled )
  577. indicesOfListEntriesToShow.Add( i );
  578. else if( logEntry.logTypeSpriteRepresentation == warningLog && isWarningEnabled )
  579. indicesOfListEntriesToShow.Add( i );
  580. else if( logEntry.logTypeSpriteRepresentation == errorLog && isErrorEnabled )
  581. indicesOfListEntriesToShow.Add( i );
  582. }
  583. }
  584. else
  585. {
  586. indicesOfListEntriesToShow.Clear();
  587. for( int i = 0; i < uncollapsedLogEntriesIndices.Count; i++ )
  588. {
  589. DebugLogEntry logEntry = collapsedLogEntries[uncollapsedLogEntriesIndices[i]];
  590. if( logEntry.logTypeSpriteRepresentation == infoLog && isInfoEnabled )
  591. indicesOfListEntriesToShow.Add( uncollapsedLogEntriesIndices[i] );
  592. else if( logEntry.logTypeSpriteRepresentation == warningLog && isWarningEnabled )
  593. indicesOfListEntriesToShow.Add( uncollapsedLogEntriesIndices[i] );
  594. else if( logEntry.logTypeSpriteRepresentation == errorLog && isErrorEnabled )
  595. indicesOfListEntriesToShow.Add( uncollapsedLogEntriesIndices[i] );
  596. }
  597. }
  598. }
  599. // Update the recycled list view
  600. recycledListView.DeselectSelectedLogItem();
  601. recycledListView.OnLogEntriesUpdated( true );
  602. ValidateScrollPosition();
  603. }
  604. public string GetAllLogs()
  605. {
  606. int count = uncollapsedLogEntriesIndices.Count;
  607. int length = 0;
  608. int newLineLength = System.Environment.NewLine.Length;
  609. for( int i = 0; i < count; i++ )
  610. {
  611. DebugLogEntry entry = collapsedLogEntries[uncollapsedLogEntriesIndices[i]];
  612. length += entry.logString.Length + entry.stackTrace.Length + newLineLength * 3;
  613. }
  614. length += 100; // Just in case...
  615. System.Text.StringBuilder sb = new System.Text.StringBuilder( length );
  616. for( int i = 0; i < count; i++ )
  617. {
  618. DebugLogEntry entry = collapsedLogEntries[uncollapsedLogEntriesIndices[i]];
  619. sb.AppendLine( entry.logString ).AppendLine( entry.stackTrace ).AppendLine();
  620. }
  621. return sb.ToString();
  622. }
  623. private void SaveLogsToFile()
  624. {
  625. string path = Path.Combine( Application.persistentDataPath, System.DateTime.Now.ToString( "dd-MM-yyyy--HH-mm-ss" ) + ".txt" );
  626. File.WriteAllText( path, instance.GetAllLogs() );
  627. Debug.Log( "Logs saved to: " + path );
  628. }
  629. // Pool an unused log item
  630. public void PoolLogItem( DebugLogItem logItem )
  631. {
  632. logItem.gameObject.SetActive( false );
  633. pooledLogItems.Add( logItem );
  634. }
  635. // Fetch a log item from the pool
  636. public DebugLogItem PopLogItem()
  637. {
  638. DebugLogItem newLogItem;
  639. // If pool is not empty, fetch a log item from the pool,
  640. // create a new log item otherwise
  641. if( pooledLogItems.Count > 0 )
  642. {
  643. newLogItem = pooledLogItems[pooledLogItems.Count - 1];
  644. pooledLogItems.RemoveAt( pooledLogItems.Count - 1 );
  645. newLogItem.gameObject.SetActive( true );
  646. }
  647. else
  648. {
  649. newLogItem = (DebugLogItem) Instantiate( logItemPrefab, logItemsContainer, false );
  650. newLogItem.Initialize( recycledListView );
  651. }
  652. return newLogItem;
  653. }
  654. }
  655. }