DebugLogManager.cs 57 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743
  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using System.IO;
  4. using System.Text;
  5. using UnityEngine;
  6. using UnityEngine.UI;
  7. using UnityEngine.EventSystems;
  8. #if ENABLE_INPUT_SYSTEM && !ENABLE_LEGACY_INPUT_MANAGER
  9. using UnityEngine.InputSystem;
  10. #endif
  11. #if UNITY_EDITOR && UNITY_2021_1_OR_NEWER
  12. using Screen = UnityEngine.Device.Screen; // To support Device Simulator on Unity 2021.1+
  13. #endif
  14. // Receives debug entries and custom events (e.g. Clear, Collapse, Filter by Type)
  15. // and notifies the recycled list view of changes to the list of debug entries
  16. //
  17. // - Vocabulary -
  18. // Debug/Log entry: a Debug.Log/LogError/LogWarning/LogException/LogAssertion request made by
  19. // the client and intercepted by this manager object
  20. // Debug/Log item: a visual (uGUI) representation of a debug entry
  21. //
  22. // There can be a lot of debug entries in the system but there will only be a handful of log items
  23. // to show their properties on screen (these log items are recycled as the list is scrolled)
  24. // An enum to represent filtered log types
  25. namespace IngameDebugConsole
  26. {
  27. public enum DebugLogFilter
  28. {
  29. None = 0,
  30. Info = 1,
  31. Warning = 2,
  32. Error = 4,
  33. All = 7
  34. }
  35. public class DebugLogManager : MonoBehaviour
  36. {
  37. public static DebugLogManager Instance { get; private set; }
  38. #pragma warning disable 0649
  39. [Header( "Properties" )]
  40. [SerializeField]
  41. [HideInInspector]
  42. [Tooltip( "If enabled, console window will persist between scenes (i.e. not be destroyed when scene changes)" )]
  43. private bool singleton = true;
  44. [SerializeField]
  45. [HideInInspector]
  46. [Tooltip( "Minimum height of the console window" )]
  47. private float minimumHeight = 200f;
  48. [SerializeField]
  49. [HideInInspector]
  50. [Tooltip( "If enabled, console window can be resized horizontally, as well" )]
  51. private bool enableHorizontalResizing = false;
  52. [SerializeField]
  53. [HideInInspector]
  54. [Tooltip( "If enabled, console window's resize button will be located at bottom-right corner. Otherwise, it will be located at bottom-left corner" )]
  55. private bool resizeFromRight = true;
  56. [SerializeField]
  57. [HideInInspector]
  58. [Tooltip( "Minimum width of the console window" )]
  59. private float minimumWidth = 240f;
  60. [SerializeField]
  61. [HideInInspector]
  62. [Tooltip( "If disabled, no popup will be shown when the console window is hidden" )]
  63. private bool enablePopup = true;
  64. [SerializeField]
  65. [HideInInspector]
  66. [Tooltip( "If enabled, console will be initialized as a popup" )]
  67. private bool startInPopupMode = true;
  68. [SerializeField]
  69. [HideInInspector]
  70. [Tooltip( "If enabled, console window will initially be invisible" )]
  71. private bool startMinimized = false;
  72. [SerializeField]
  73. [HideInInspector]
  74. [Tooltip( "If enabled, pressing the Toggle Key will show/hide (i.e. toggle) the console window at runtime" )]
  75. private bool toggleWithKey = false;
  76. #if ENABLE_INPUT_SYSTEM && !ENABLE_LEGACY_INPUT_MANAGER
  77. [SerializeField]
  78. [HideInInspector]
  79. public InputAction toggleBinding = new InputAction( "Toggle Binding", type: InputActionType.Button, binding: "<Keyboard>/backquote", expectedControlType: "Button" );
  80. #else
  81. [SerializeField]
  82. [HideInInspector]
  83. private KeyCode toggleKey = KeyCode.BackQuote;
  84. #endif
  85. [SerializeField]
  86. [HideInInspector]
  87. [Tooltip( "If enabled, the console window will have a searchbar" )]
  88. private bool enableSearchbar = true;
  89. [SerializeField]
  90. [HideInInspector]
  91. [Tooltip( "Width of the canvas determines whether the searchbar will be located inside the menu bar or underneath the menu bar. This way, the menu bar doesn't get too crowded on narrow screens. This value determines the minimum width of the canvas for the searchbar to appear inside the menu bar" )]
  92. private float topSearchbarMinWidth = 360f;
  93. [SerializeField]
  94. [HideInInspector]
  95. [Tooltip( "If enabled, the console window will continue receiving logs in the background even if its GameObject is inactive. But the console window's GameObject needs to be activated at least once because its Awake function must be triggered for this to work" )]
  96. private bool receiveLogsWhileInactive = false;
  97. [SerializeField]
  98. [HideInInspector]
  99. private bool receiveInfoLogs = true, receiveWarningLogs = true, receiveErrorLogs = true, receiveExceptionLogs = true;
  100. [SerializeField]
  101. [HideInInspector]
  102. [Tooltip( "If enabled, the arrival times of logs will be recorded and displayed when a log is expanded" )]
  103. private bool captureLogTimestamps = false;
  104. [SerializeField]
  105. [HideInInspector]
  106. [Tooltip( "If enabled, timestamps will be displayed for logs even if they aren't expanded" )]
  107. internal bool alwaysDisplayTimestamps = false;
  108. [SerializeField]
  109. [HideInInspector]
  110. [Tooltip( "While the console window is hidden, incoming logs will be queued but not immediately processed until the console window is opened (to avoid wasting CPU resources). When the log queue exceeds this limit, the first logs in the queue will be processed to enforce this limit. Processed logs won't increase RAM usage if they've been seen before (i.e. collapsible logs) but this is not the case for queued logs, so if a log is spammed every frame, it will fill the whole queue in an instant. Which is why there is a queue limit" )]
  111. private int queuedLogLimit = 256;
  112. [SerializeField]
  113. [HideInInspector]
  114. [Tooltip( "If enabled, the command input field at the bottom of the console window will automatically be cleared after entering a command" )]
  115. private bool clearCommandAfterExecution = true;
  116. [SerializeField]
  117. [HideInInspector]
  118. [Tooltip( "Console keeps track of the previously entered commands. This value determines the capacity of the command history (you can scroll through the history via up and down arrow keys while the command input field is focused)" )]
  119. private int commandHistorySize = 15;
  120. [SerializeField]
  121. [HideInInspector]
  122. [Tooltip( "If enabled, while typing a command, all of the matching commands' signatures will be displayed in a popup" )]
  123. private bool showCommandSuggestions = true;
  124. [SerializeField]
  125. [HideInInspector]
  126. [Tooltip( "If enabled, on Android platform, logcat entries of the application will also be logged to the console with the prefix \"LOGCAT: \". This may come in handy especially if you want to access the native logs of your Android plugins (like Admob)" )]
  127. private bool receiveLogcatLogsInAndroid = false;
  128. #pragma warning disable 0414
  129. #if UNITY_2018_3_OR_NEWER // On older Unity versions, disabling CS0169 is problematic: "Cannot restore warning 'CS0169' because it was disabled globally"
  130. #pragma warning disable 0169
  131. #endif
  132. [SerializeField]
  133. [HideInInspector]
  134. [Tooltip( "Native logs will be filtered using these arguments. If left blank, all native logs of the application will be logged to the console. But if you want to e.g. see Admob's logs only, you can enter \"-s Ads\" (without quotes) here" )]
  135. private string logcatArguments;
  136. #if UNITY_2018_3_OR_NEWER
  137. #pragma warning restore 0169
  138. #endif
  139. #pragma warning restore 0414
  140. [SerializeField]
  141. [HideInInspector]
  142. [Tooltip( "If enabled, on Android and iOS devices with notch screens, the console window will be repositioned so that the cutout(s) don't obscure it" )]
  143. private bool avoidScreenCutout = true;
  144. [SerializeField]
  145. [Tooltip( "If a log is longer than this limit, it will be truncated. This helps avoid reaching Unity's 65000 vertex limit for UI canvases" )]
  146. private int maxLogLength = 10000;
  147. #if UNITY_EDITOR || UNITY_STANDALONE || UNITY_WEBGL
  148. [SerializeField]
  149. [HideInInspector]
  150. [Tooltip( "If enabled, on standalone platforms, command input field will automatically be focused (start receiving keyboard input) after opening the console window" )]
  151. private bool autoFocusOnCommandInputField = true;
  152. #endif
  153. [Header( "Visuals" )]
  154. [SerializeField]
  155. private DebugLogItem logItemPrefab;
  156. [SerializeField]
  157. private Text commandSuggestionPrefab;
  158. // Visuals for different log types
  159. [SerializeField]
  160. private Sprite infoLog;
  161. [SerializeField]
  162. private Sprite warningLog;
  163. [SerializeField]
  164. private Sprite errorLog;
  165. // Visuals for resize button
  166. [SerializeField]
  167. private Sprite resizeIconAllDirections;
  168. [SerializeField]
  169. private Sprite resizeIconVerticalOnly;
  170. private Dictionary<LogType, Sprite> logSpriteRepresentations;
  171. [SerializeField]
  172. private Color collapseButtonNormalColor;
  173. [SerializeField]
  174. private Color collapseButtonSelectedColor;
  175. [SerializeField]
  176. private Color filterButtonsNormalColor;
  177. [SerializeField]
  178. private Color filterButtonsSelectedColor;
  179. [SerializeField]
  180. private string commandSuggestionHighlightStart = "<color=orange>";
  181. [SerializeField]
  182. private string commandSuggestionHighlightEnd = "</color>";
  183. [Header( "Internal References" )]
  184. [SerializeField]
  185. private RectTransform logWindowTR;
  186. internal RectTransform canvasTR;
  187. [SerializeField]
  188. private RectTransform logItemsContainer;
  189. [SerializeField]
  190. private RectTransform commandSuggestionsContainer;
  191. [SerializeField]
  192. private InputField commandInputField;
  193. [SerializeField]
  194. private Button hideButton;
  195. [SerializeField]
  196. private Button clearButton;
  197. [SerializeField]
  198. private Image collapseButton;
  199. [SerializeField]
  200. private Image filterInfoButton;
  201. [SerializeField]
  202. private Image filterWarningButton;
  203. [SerializeField]
  204. private Image filterErrorButton;
  205. [SerializeField]
  206. private Text infoEntryCountText;
  207. [SerializeField]
  208. private Text warningEntryCountText;
  209. [SerializeField]
  210. private Text errorEntryCountText;
  211. [SerializeField]
  212. private RectTransform searchbar;
  213. [SerializeField]
  214. private RectTransform searchbarSlotTop;
  215. [SerializeField]
  216. private RectTransform searchbarSlotBottom;
  217. [SerializeField]
  218. private Image resizeButton;
  219. [SerializeField]
  220. private GameObject snapToBottomButton;
  221. // Canvas group to modify visibility of the log window
  222. [SerializeField]
  223. private CanvasGroup logWindowCanvasGroup;
  224. [SerializeField]
  225. private DebugLogPopup popupManager;
  226. [SerializeField]
  227. private ScrollRect logItemsScrollRect;
  228. private RectTransform logItemsScrollRectTR;
  229. private Vector2 logItemsScrollRectOriginalSize;
  230. // Recycled list view to handle the log items efficiently
  231. [SerializeField]
  232. private DebugLogRecycledListView recycledListView;
  233. #pragma warning restore 0649
  234. private bool isLogWindowVisible = true;
  235. public bool IsLogWindowVisible { get { return isLogWindowVisible; } }
  236. public bool PopupEnabled
  237. {
  238. get { return popupManager.gameObject.activeSelf; }
  239. set { popupManager.gameObject.SetActive( value ); }
  240. }
  241. private bool screenDimensionsChanged = true;
  242. private float logWindowPreviousWidth;
  243. // Number of entries filtered by their types
  244. private int infoEntryCount = 0, warningEntryCount = 0, errorEntryCount = 0;
  245. private bool entryCountTextsDirty;
  246. // Number of new entries received this frame
  247. private int newInfoEntryCount = 0, newWarningEntryCount = 0, newErrorEntryCount = 0;
  248. // Filters to apply to the list of debug entries to show
  249. private bool isCollapseOn = false;
  250. private DebugLogFilter logFilter = DebugLogFilter.All;
  251. // Search filter
  252. private string searchTerm;
  253. private bool isInSearchMode;
  254. // If the last log item is completely visible (scrollbar is at the bottom),
  255. // scrollbar will remain at the bottom when new debug entries are received
  256. private bool snapToBottom = true;
  257. // List of unique debug entries (duplicates of entries are not kept)
  258. private List<DebugLogEntry> collapsedLogEntries;
  259. private List<DebugLogEntryTimestamp> collapsedLogEntriesTimestamps;
  260. // Dictionary to quickly find if a log already exists in collapsedLogEntries
  261. private Dictionary<DebugLogEntry, int> collapsedLogEntriesMap;
  262. // The order the collapsedLogEntries are received
  263. // (duplicate entries have the same index (value))
  264. private DebugLogIndexList<int> uncollapsedLogEntriesIndices;
  265. private DebugLogIndexList<DebugLogEntryTimestamp> uncollapsedLogEntriesTimestamps;
  266. // Filtered list of debug entries to show
  267. private DebugLogIndexList<int> indicesOfListEntriesToShow;
  268. private DebugLogIndexList<DebugLogEntryTimestamp> timestampsOfListEntriesToShow;
  269. // The log entry that must be focused this frame
  270. private int indexOfLogEntryToSelectAndFocus = -1;
  271. // Whether or not logs list view should be updated this frame
  272. private bool shouldUpdateRecycledListView = false;
  273. // Logs that should be registered in Update-loop
  274. private DynamicCircularBuffer<QueuedDebugLogEntry> queuedLogEntries;
  275. private DynamicCircularBuffer<DebugLogEntryTimestamp> queuedLogEntriesTimestamps;
  276. private object logEntriesLock;
  277. private int pendingLogToAutoExpand;
  278. // Command suggestions that match the currently entered command
  279. private List<Text> commandSuggestionInstances;
  280. private int visibleCommandSuggestionInstances = 0;
  281. private List<ConsoleMethodInfo> matchingCommandSuggestions;
  282. private List<int> commandCaretIndexIncrements;
  283. private string commandInputFieldPrevCommand;
  284. private string commandInputFieldPrevCommandName;
  285. private int commandInputFieldPrevParamCount = -1;
  286. private int commandInputFieldPrevCaretPos = -1;
  287. private int commandInputFieldPrevCaretArgumentIndex = -1;
  288. // Value of the command input field when autocomplete was first requested
  289. private string commandInputFieldAutoCompleteBase;
  290. private bool commandInputFieldAutoCompletedNow;
  291. // Pools for memory efficiency
  292. private List<DebugLogEntry> pooledLogEntries;
  293. private List<DebugLogItem> pooledLogItems;
  294. // History of the previously entered commands
  295. private CircularBuffer<string> commandHistory;
  296. private int commandHistoryIndex = -1;
  297. private string unfinishedCommand;
  298. // StringBuilder used by various functions
  299. internal StringBuilder sharedStringBuilder;
  300. // Offset of DateTime.Now from DateTime.UtcNow
  301. private System.TimeSpan localTimeUtcOffset;
  302. // Last recorded values of Time.realtimeSinceStartup and Time.frameCount on the main thread (because these Time properties can't be accessed from other threads)
  303. #if !IDG_OMIT_ELAPSED_TIME
  304. private float lastElapsedSeconds;
  305. #endif
  306. #if !IDG_OMIT_FRAMECOUNT
  307. private int lastFrameCount;
  308. #endif
  309. private DebugLogEntryTimestamp dummyLogEntryTimestamp;
  310. // Required in ValidateScrollPosition() function
  311. private PointerEventData nullPointerEventData;
  312. // Callbacks for log window show/hide events
  313. public System.Action OnLogWindowShown, OnLogWindowHidden;
  314. #if UNITY_EDITOR
  315. private bool isQuittingApplication;
  316. #endif
  317. #if !UNITY_EDITOR && UNITY_ANDROID
  318. private DebugLogLogcatListener logcatListener;
  319. #endif
  320. private void Awake()
  321. {
  322. // Only one instance of debug console is allowed
  323. if( !Instance )
  324. {
  325. Instance = this;
  326. // If it is a singleton object, don't destroy it between scene changes
  327. if( singleton )
  328. DontDestroyOnLoad( gameObject );
  329. }
  330. else if( Instance != this )
  331. {
  332. Destroy( gameObject );
  333. return;
  334. }
  335. pooledLogEntries = new List<DebugLogEntry>( 16 );
  336. pooledLogItems = new List<DebugLogItem>( 16 );
  337. commandSuggestionInstances = new List<Text>( 8 );
  338. matchingCommandSuggestions = new List<ConsoleMethodInfo>( 8 );
  339. commandCaretIndexIncrements = new List<int>( 8 );
  340. queuedLogEntries = new DynamicCircularBuffer<QueuedDebugLogEntry>( Mathf.Clamp( queuedLogLimit, 16, 4096 ) );
  341. commandHistory = new CircularBuffer<string>( commandHistorySize );
  342. logEntriesLock = new object();
  343. sharedStringBuilder = new StringBuilder( 1024 );
  344. canvasTR = (RectTransform) transform;
  345. logItemsScrollRectTR = (RectTransform) logItemsScrollRect.transform;
  346. logItemsScrollRectOriginalSize = logItemsScrollRectTR.sizeDelta;
  347. // Associate sprites with log types
  348. logSpriteRepresentations = new Dictionary<LogType, Sprite>()
  349. {
  350. { LogType.Log, infoLog },
  351. { LogType.Warning, warningLog },
  352. { LogType.Error, errorLog },
  353. { LogType.Exception, errorLog },
  354. { LogType.Assert, errorLog }
  355. };
  356. // Initially, all log types are visible
  357. filterInfoButton.color = filterButtonsSelectedColor;
  358. filterWarningButton.color = filterButtonsSelectedColor;
  359. filterErrorButton.color = filterButtonsSelectedColor;
  360. resizeButton.sprite = enableHorizontalResizing ? resizeIconAllDirections : resizeIconVerticalOnly;
  361. collapsedLogEntries = new List<DebugLogEntry>( 128 );
  362. collapsedLogEntriesMap = new Dictionary<DebugLogEntry, int>( 128 );
  363. uncollapsedLogEntriesIndices = new DebugLogIndexList<int>();
  364. indicesOfListEntriesToShow = new DebugLogIndexList<int>();
  365. if( captureLogTimestamps )
  366. {
  367. collapsedLogEntriesTimestamps = new List<DebugLogEntryTimestamp>( 128 );
  368. uncollapsedLogEntriesTimestamps = new DebugLogIndexList<DebugLogEntryTimestamp>();
  369. timestampsOfListEntriesToShow = new DebugLogIndexList<DebugLogEntryTimestamp>();
  370. queuedLogEntriesTimestamps = new DynamicCircularBuffer<DebugLogEntryTimestamp>( queuedLogEntries.Capacity );
  371. }
  372. recycledListView.Initialize( this, collapsedLogEntries, indicesOfListEntriesToShow, timestampsOfListEntriesToShow, logItemPrefab.Transform.sizeDelta.y );
  373. recycledListView.UpdateItemsInTheList( true );
  374. if( minimumWidth < 100f )
  375. minimumWidth = 100f;
  376. if( minimumHeight < 200f )
  377. minimumHeight = 200f;
  378. if( !resizeFromRight )
  379. {
  380. RectTransform resizeButtonTR = (RectTransform) resizeButton.GetComponentInParent<DebugLogResizeListener>().transform;
  381. resizeButtonTR.anchorMin = new Vector2( 0f, resizeButtonTR.anchorMin.y );
  382. resizeButtonTR.anchorMax = new Vector2( 0f, resizeButtonTR.anchorMax.y );
  383. resizeButtonTR.pivot = new Vector2( 0f, resizeButtonTR.pivot.y );
  384. ( (RectTransform) commandInputField.transform ).anchoredPosition += new Vector2( resizeButtonTR.sizeDelta.x, 0f );
  385. }
  386. if( enableSearchbar )
  387. searchbar.GetComponent<InputField>().onValueChanged.AddListener( SearchTermChanged );
  388. else
  389. {
  390. searchbar = null;
  391. searchbarSlotTop.gameObject.SetActive( false );
  392. searchbarSlotBottom.gameObject.SetActive( false );
  393. }
  394. filterInfoButton.gameObject.SetActive( receiveInfoLogs );
  395. filterWarningButton.gameObject.SetActive( receiveWarningLogs );
  396. filterErrorButton.gameObject.SetActive( receiveErrorLogs || receiveExceptionLogs );
  397. if( commandSuggestionsContainer.gameObject.activeSelf )
  398. commandSuggestionsContainer.gameObject.SetActive( false );
  399. // Register to UI events
  400. commandInputField.onValidateInput += OnValidateCommand;
  401. commandInputField.onValueChanged.AddListener( OnEditCommand );
  402. commandInputField.onEndEdit.AddListener( OnEndEditCommand );
  403. hideButton.onClick.AddListener( HideLogWindow );
  404. clearButton.onClick.AddListener( ClearLogs );
  405. collapseButton.GetComponent<Button>().onClick.AddListener( CollapseButtonPressed );
  406. filterInfoButton.GetComponent<Button>().onClick.AddListener( FilterLogButtonPressed );
  407. filterWarningButton.GetComponent<Button>().onClick.AddListener( FilterWarningButtonPressed );
  408. filterErrorButton.GetComponent<Button>().onClick.AddListener( FilterErrorButtonPressed );
  409. snapToBottomButton.GetComponent<Button>().onClick.AddListener( () => SetSnapToBottom( true ) );
  410. localTimeUtcOffset = System.DateTime.Now - System.DateTime.UtcNow;
  411. dummyLogEntryTimestamp = new DebugLogEntryTimestamp();
  412. nullPointerEventData = new PointerEventData( null );
  413. if( receiveLogsWhileInactive )
  414. {
  415. Application.logMessageReceivedThreaded -= ReceivedLog;
  416. Application.logMessageReceivedThreaded += ReceivedLog;
  417. }
  418. #if UNITY_EDITOR && UNITY_2018_1_OR_NEWER
  419. // OnApplicationQuit isn't reliable on some Unity versions when Application.wantsToQuit is used; Application.quitting is the only reliable solution on those versions
  420. // https://issuetracker.unity3d.com/issues/onapplicationquit-method-is-called-before-application-dot-wantstoquit-event-is-raised
  421. Application.quitting -= OnApplicationQuitting;
  422. Application.quitting += OnApplicationQuitting;
  423. #endif
  424. #if ENABLE_INPUT_SYSTEM && !ENABLE_LEGACY_INPUT_MANAGER
  425. toggleBinding.performed += ( context ) =>
  426. {
  427. if( toggleWithKey )
  428. {
  429. if( isLogWindowVisible )
  430. HideLogWindow();
  431. else
  432. ShowLogWindow();
  433. }
  434. };
  435. // On new Input System, scroll sensitivity is much higher than legacy Input system
  436. logItemsScrollRect.scrollSensitivity *= 0.25f;
  437. #endif
  438. }
  439. private void OnEnable()
  440. {
  441. if( Instance != this )
  442. return;
  443. if( !receiveLogsWhileInactive )
  444. {
  445. Application.logMessageReceivedThreaded -= ReceivedLog;
  446. Application.logMessageReceivedThreaded += ReceivedLog;
  447. }
  448. if( receiveLogcatLogsInAndroid )
  449. {
  450. #if !UNITY_EDITOR && UNITY_ANDROID
  451. if( logcatListener == null )
  452. logcatListener = new DebugLogLogcatListener();
  453. logcatListener.Start( logcatArguments );
  454. #endif
  455. }
  456. DebugLogConsole.AddCommand( "logs.save", "Saves logs to persistentDataPath", SaveLogsToFile );
  457. DebugLogConsole.AddCommand<string>( "logs.save", "Saves logs to the specified file", SaveLogsToFile );
  458. #if ENABLE_INPUT_SYSTEM && !ENABLE_LEGACY_INPUT_MANAGER
  459. if( toggleWithKey )
  460. toggleBinding.Enable();
  461. #endif
  462. //Debug.LogAssertion( "assert" );
  463. //Debug.LogError( "error" );
  464. //Debug.LogException( new System.IO.EndOfStreamException() );
  465. //Debug.LogWarning( "warning" );
  466. //Debug.Log( "log" );
  467. }
  468. private void OnDisable()
  469. {
  470. if( Instance != this )
  471. return;
  472. if( !receiveLogsWhileInactive )
  473. Application.logMessageReceivedThreaded -= ReceivedLog;
  474. #if !UNITY_EDITOR && UNITY_ANDROID
  475. if( logcatListener != null )
  476. logcatListener.Stop();
  477. #endif
  478. DebugLogConsole.RemoveCommand( "logs.save" );
  479. #if ENABLE_INPUT_SYSTEM && !ENABLE_LEGACY_INPUT_MANAGER
  480. if( toggleBinding.enabled )
  481. toggleBinding.Disable();
  482. #endif
  483. }
  484. private void Start()
  485. {
  486. if( ( enablePopup && startInPopupMode ) || ( !enablePopup && startMinimized ) )
  487. HideLogWindow();
  488. else
  489. ShowLogWindow();
  490. PopupEnabled = enablePopup;
  491. }
  492. private void OnDestroy()
  493. {
  494. if( receiveLogsWhileInactive )
  495. Application.logMessageReceivedThreaded -= ReceivedLog;
  496. #if UNITY_EDITOR && UNITY_2018_1_OR_NEWER
  497. Application.quitting -= OnApplicationQuitting;
  498. #endif
  499. }
  500. #if UNITY_EDITOR
  501. private void OnValidate()
  502. {
  503. queuedLogLimit = Mathf.Max( 0, queuedLogLimit );
  504. if( UnityEditor.EditorApplication.isPlaying )
  505. {
  506. resizeButton.sprite = enableHorizontalResizing ? resizeIconAllDirections : resizeIconVerticalOnly;
  507. filterInfoButton.gameObject.SetActive( receiveInfoLogs );
  508. filterWarningButton.gameObject.SetActive( receiveWarningLogs );
  509. filterErrorButton.gameObject.SetActive( receiveErrorLogs || receiveExceptionLogs );
  510. }
  511. }
  512. #if UNITY_2018_1_OR_NEWER
  513. private void OnApplicationQuitting()
  514. #else
  515. private void OnApplicationQuit()
  516. #endif
  517. {
  518. isQuittingApplication = true;
  519. }
  520. #endif
  521. // Window is resized, update the list
  522. private void OnRectTransformDimensionsChange()
  523. {
  524. screenDimensionsChanged = true;
  525. }
  526. private void Update()
  527. {
  528. #if !IDG_OMIT_ELAPSED_TIME
  529. lastElapsedSeconds = Time.realtimeSinceStartup;
  530. #endif
  531. #if !IDG_OMIT_FRAMECOUNT
  532. lastFrameCount = Time.frameCount;
  533. #endif
  534. #if !UNITY_EDITOR && UNITY_ANDROID
  535. if( logcatListener != null )
  536. {
  537. string log;
  538. while( ( log = logcatListener.GetLog() ) != null )
  539. ReceivedLog( "LOGCAT: " + log, string.Empty, LogType.Log );
  540. }
  541. #endif
  542. #if !ENABLE_INPUT_SYSTEM || ENABLE_LEGACY_INPUT_MANAGER
  543. // Toggling the console with toggleKey is handled in Update instead of LateUpdate because
  544. // when we hide the console, we don't want the commandInputField to capture the toggleKey.
  545. // InputField captures input in LateUpdate so deactivating it in Update ensures that
  546. // no further input is captured
  547. if( toggleWithKey )
  548. {
  549. if( Input.GetKeyDown( toggleKey ) )
  550. {
  551. if( isLogWindowVisible )
  552. HideLogWindow();
  553. else
  554. ShowLogWindow();
  555. }
  556. }
  557. #endif
  558. }
  559. private void LateUpdate()
  560. {
  561. #if UNITY_EDITOR
  562. if( isQuittingApplication )
  563. return;
  564. #endif
  565. int numberOfLogsToProcess = isLogWindowVisible ? queuedLogEntries.Count : ( queuedLogEntries.Count - queuedLogLimit );
  566. if( numberOfLogsToProcess > 0 )
  567. {
  568. for( int i = 0; i < numberOfLogsToProcess; i++ )
  569. {
  570. QueuedDebugLogEntry logEntry;
  571. DebugLogEntryTimestamp timestamp;
  572. lock( logEntriesLock )
  573. {
  574. logEntry = queuedLogEntries.RemoveFirst();
  575. timestamp = queuedLogEntriesTimestamps != null ? queuedLogEntriesTimestamps.RemoveFirst() : dummyLogEntryTimestamp;
  576. }
  577. ProcessLog( logEntry, timestamp );
  578. }
  579. }
  580. // Don't perform CPU heavy tasks if neither the log window nor the popup is visible
  581. if( !isLogWindowVisible && !PopupEnabled )
  582. return;
  583. int newInfoEntryCount, newWarningEntryCount, newErrorEntryCount;
  584. lock( logEntriesLock )
  585. {
  586. newInfoEntryCount = this.newInfoEntryCount;
  587. newWarningEntryCount = this.newWarningEntryCount;
  588. newErrorEntryCount = this.newErrorEntryCount;
  589. this.newInfoEntryCount = 0;
  590. this.newWarningEntryCount = 0;
  591. this.newErrorEntryCount = 0;
  592. }
  593. // Update entry count texts in a single batch
  594. if( newInfoEntryCount > 0 || newWarningEntryCount > 0 || newErrorEntryCount > 0 )
  595. {
  596. if( newInfoEntryCount > 0 )
  597. {
  598. infoEntryCount += newInfoEntryCount;
  599. if( isLogWindowVisible )
  600. infoEntryCountText.text = infoEntryCount.ToString();
  601. }
  602. if( newWarningEntryCount > 0 )
  603. {
  604. warningEntryCount += newWarningEntryCount;
  605. if( isLogWindowVisible )
  606. warningEntryCountText.text = warningEntryCount.ToString();
  607. }
  608. if( newErrorEntryCount > 0 )
  609. {
  610. errorEntryCount += newErrorEntryCount;
  611. if( isLogWindowVisible )
  612. errorEntryCountText.text = errorEntryCount.ToString();
  613. }
  614. // If debug popup is visible, notify it of the new debug entries
  615. if( !isLogWindowVisible )
  616. {
  617. entryCountTextsDirty = true;
  618. popupManager.NewLogsArrived( newInfoEntryCount, newWarningEntryCount, newErrorEntryCount );
  619. }
  620. }
  621. if( isLogWindowVisible )
  622. {
  623. // Update visible logs if necessary
  624. if( shouldUpdateRecycledListView )
  625. {
  626. recycledListView.OnLogEntriesUpdated( false );
  627. shouldUpdateRecycledListView = false;
  628. }
  629. // Automatically expand the target log (if any)
  630. if( indexOfLogEntryToSelectAndFocus >= 0 )
  631. {
  632. if( indexOfLogEntryToSelectAndFocus < indicesOfListEntriesToShow.Count )
  633. recycledListView.SelectAndFocusOnLogItemAtIndex( indexOfLogEntryToSelectAndFocus );
  634. indexOfLogEntryToSelectAndFocus = -1;
  635. }
  636. float logWindowWidth = logWindowTR.rect.width;
  637. if( !Mathf.Approximately( logWindowWidth, logWindowPreviousWidth ) )
  638. {
  639. logWindowPreviousWidth = logWindowWidth;
  640. if( searchbar )
  641. {
  642. if( logWindowWidth >= topSearchbarMinWidth )
  643. {
  644. if( searchbar.parent == searchbarSlotBottom )
  645. {
  646. searchbarSlotTop.gameObject.SetActive( true );
  647. searchbar.SetParent( searchbarSlotTop, false );
  648. searchbarSlotBottom.gameObject.SetActive( false );
  649. logItemsScrollRectTR.anchoredPosition = Vector2.zero;
  650. logItemsScrollRectTR.sizeDelta = logItemsScrollRectOriginalSize;
  651. }
  652. }
  653. else
  654. {
  655. if( searchbar.parent == searchbarSlotTop )
  656. {
  657. searchbarSlotBottom.gameObject.SetActive( true );
  658. searchbar.SetParent( searchbarSlotBottom, false );
  659. searchbarSlotTop.gameObject.SetActive( false );
  660. float searchbarHeight = searchbarSlotBottom.sizeDelta.y;
  661. logItemsScrollRectTR.anchoredPosition = new Vector2( 0f, searchbarHeight * -0.5f );
  662. logItemsScrollRectTR.sizeDelta = logItemsScrollRectOriginalSize - new Vector2( 0f, searchbarHeight );
  663. }
  664. }
  665. }
  666. recycledListView.OnViewportWidthChanged();
  667. }
  668. // If snapToBottom is enabled, force the scrollbar to the bottom
  669. if( snapToBottom )
  670. {
  671. logItemsScrollRect.verticalNormalizedPosition = 0f;
  672. if( snapToBottomButton.activeSelf )
  673. snapToBottomButton.SetActive( false );
  674. }
  675. else
  676. {
  677. float scrollPos = logItemsScrollRect.verticalNormalizedPosition;
  678. if( snapToBottomButton.activeSelf != ( scrollPos > 1E-6f && scrollPos < 0.9999f ) )
  679. snapToBottomButton.SetActive( !snapToBottomButton.activeSelf );
  680. }
  681. if( showCommandSuggestions && commandInputField.isFocused && commandInputField.caretPosition != commandInputFieldPrevCaretPos )
  682. RefreshCommandSuggestions( commandInputField.text );
  683. if( commandInputField.isFocused && commandHistory.Count > 0 )
  684. {
  685. #if ENABLE_INPUT_SYSTEM && !ENABLE_LEGACY_INPUT_MANAGER
  686. if( Keyboard.current != null )
  687. #endif
  688. {
  689. #if ENABLE_INPUT_SYSTEM && !ENABLE_LEGACY_INPUT_MANAGER
  690. if( Keyboard.current[Key.UpArrow].wasPressedThisFrame )
  691. #else
  692. if( Input.GetKeyDown( KeyCode.UpArrow ) )
  693. #endif
  694. {
  695. if( commandHistoryIndex == -1 )
  696. {
  697. commandHistoryIndex = commandHistory.Count - 1;
  698. unfinishedCommand = commandInputField.text;
  699. }
  700. else if( --commandHistoryIndex < 0 )
  701. commandHistoryIndex = 0;
  702. commandInputField.text = commandHistory[commandHistoryIndex];
  703. commandInputField.caretPosition = commandInputField.text.Length;
  704. }
  705. #if ENABLE_INPUT_SYSTEM && !ENABLE_LEGACY_INPUT_MANAGER
  706. else if( Keyboard.current[Key.DownArrow].wasPressedThisFrame && commandHistoryIndex != -1 )
  707. #else
  708. else if( Input.GetKeyDown( KeyCode.DownArrow ) && commandHistoryIndex != -1 )
  709. #endif
  710. {
  711. if( ++commandHistoryIndex < commandHistory.Count )
  712. commandInputField.text = commandHistory[commandHistoryIndex];
  713. else
  714. {
  715. commandHistoryIndex = -1;
  716. commandInputField.text = unfinishedCommand ?? string.Empty;
  717. }
  718. }
  719. }
  720. }
  721. }
  722. if( screenDimensionsChanged )
  723. {
  724. // Update the recycled list view
  725. if( isLogWindowVisible )
  726. recycledListView.OnViewportHeightChanged();
  727. else
  728. popupManager.UpdatePosition( true );
  729. #if UNITY_EDITOR || UNITY_ANDROID || UNITY_IOS
  730. CheckScreenCutout();
  731. #endif
  732. screenDimensionsChanged = false;
  733. }
  734. }
  735. public void ShowLogWindow()
  736. {
  737. // Show the log window
  738. logWindowCanvasGroup.blocksRaycasts = true;
  739. logWindowCanvasGroup.alpha = 1f;
  740. popupManager.Hide();
  741. // Update the recycled list view
  742. // (in case new entries were intercepted while log window was hidden)
  743. recycledListView.OnLogEntriesUpdated( true );
  744. #if UNITY_EDITOR || UNITY_STANDALONE || UNITY_WEBGL
  745. // Focus on the command input field on standalone platforms when the console is opened
  746. if( autoFocusOnCommandInputField )
  747. StartCoroutine( ActivateCommandInputFieldCoroutine() );
  748. #endif
  749. if( entryCountTextsDirty )
  750. {
  751. infoEntryCountText.text = infoEntryCount.ToString();
  752. warningEntryCountText.text = warningEntryCount.ToString();
  753. errorEntryCountText.text = errorEntryCount.ToString();
  754. entryCountTextsDirty = false;
  755. }
  756. isLogWindowVisible = true;
  757. if( OnLogWindowShown != null )
  758. OnLogWindowShown();
  759. }
  760. public void HideLogWindow()
  761. {
  762. // Hide the log window
  763. logWindowCanvasGroup.blocksRaycasts = false;
  764. logWindowCanvasGroup.alpha = 0f;
  765. if( commandInputField.isFocused )
  766. commandInputField.DeactivateInputField();
  767. popupManager.Show();
  768. isLogWindowVisible = false;
  769. if( OnLogWindowHidden != null )
  770. OnLogWindowHidden();
  771. }
  772. // Command field input is changed, check if command is submitted
  773. private char OnValidateCommand( string text, int charIndex, char addedChar )
  774. {
  775. if( addedChar == '\t' ) // Autocomplete attempt
  776. {
  777. if( !string.IsNullOrEmpty( text ) )
  778. {
  779. if( string.IsNullOrEmpty( commandInputFieldAutoCompleteBase ) )
  780. commandInputFieldAutoCompleteBase = text;
  781. string autoCompletedCommand = DebugLogConsole.GetAutoCompleteCommand( commandInputFieldAutoCompleteBase, text );
  782. if( !string.IsNullOrEmpty( autoCompletedCommand ) && autoCompletedCommand != text )
  783. {
  784. commandInputFieldAutoCompletedNow = true;
  785. commandInputField.text = autoCompletedCommand;
  786. }
  787. }
  788. return '\0';
  789. }
  790. else if( addedChar == '\n' ) // Command is submitted
  791. {
  792. // Clear the command field
  793. if( clearCommandAfterExecution )
  794. commandInputField.text = string.Empty;
  795. if( text.Length > 0 )
  796. {
  797. if( commandHistory.Count == 0 || commandHistory[commandHistory.Count - 1] != text )
  798. commandHistory.Add( text );
  799. commandHistoryIndex = -1;
  800. unfinishedCommand = null;
  801. // Execute the command
  802. DebugLogConsole.ExecuteCommand( text );
  803. // Snap to bottom and select the latest entry
  804. SetSnapToBottom( true );
  805. }
  806. return '\0';
  807. }
  808. return addedChar;
  809. }
  810. // A debug entry is received
  811. public void ReceivedLog( string logString, string stackTrace, LogType logType )
  812. {
  813. #if UNITY_EDITOR
  814. if( isQuittingApplication )
  815. return;
  816. #endif
  817. switch( logType )
  818. {
  819. case LogType.Log: if( !receiveInfoLogs ) return; break;
  820. case LogType.Warning: if( !receiveWarningLogs ) return; break;
  821. case LogType.Error: if( !receiveErrorLogs ) return; break;
  822. case LogType.Assert:
  823. case LogType.Exception: if( !receiveExceptionLogs ) return; break;
  824. }
  825. // Truncate the log if it is longer than maxLogLength
  826. int logLength = logString.Length;
  827. if( stackTrace == null )
  828. {
  829. if( logLength > maxLogLength )
  830. logString = logString.Substring( 0, maxLogLength - 11 ) + "<truncated>";
  831. }
  832. else
  833. {
  834. logLength += stackTrace.Length;
  835. if( logLength > maxLogLength )
  836. {
  837. // Decide which log component(s) to truncate
  838. int halfMaxLogLength = maxLogLength / 2;
  839. if( logString.Length >= halfMaxLogLength )
  840. {
  841. if( stackTrace.Length >= halfMaxLogLength )
  842. {
  843. // Truncate both logString and stackTrace
  844. logString = logString.Substring( 0, halfMaxLogLength - 11 ) + "<truncated>";
  845. // If stackTrace doesn't end with a blank line, its last line won't be visible in the console for some reason
  846. stackTrace = stackTrace.Substring( 0, halfMaxLogLength - 12 ) + "<truncated>\n";
  847. }
  848. else
  849. {
  850. // Truncate logString
  851. logString = logString.Substring( 0, maxLogLength - stackTrace.Length - 11 ) + "<truncated>";
  852. }
  853. }
  854. else
  855. {
  856. // Truncate stackTrace
  857. stackTrace = stackTrace.Substring( 0, maxLogLength - logString.Length - 12 ) + "<truncated>\n";
  858. }
  859. }
  860. }
  861. QueuedDebugLogEntry queuedLogEntry = new QueuedDebugLogEntry( logString, stackTrace, logType );
  862. DebugLogEntryTimestamp queuedLogEntryTimestamp;
  863. if( queuedLogEntriesTimestamps != null )
  864. {
  865. // It is 10 times faster to cache local time's offset from UtcNow and add it to UtcNow to get local time at any time
  866. System.DateTime dateTime = System.DateTime.UtcNow + localTimeUtcOffset;
  867. #if !IDG_OMIT_ELAPSED_TIME && !IDG_OMIT_FRAMECOUNT
  868. queuedLogEntryTimestamp = new DebugLogEntryTimestamp( dateTime, lastElapsedSeconds, lastFrameCount );
  869. #elif !IDG_OMIT_ELAPSED_TIME
  870. queuedLogEntryTimestamp = new DebugLogEntryTimestamp( dateTime, lastElapsedSeconds );
  871. #elif !IDG_OMIT_FRAMECOUNT
  872. queuedLogEntryTimestamp = new DebugLogEntryTimestamp( dateTime, lastFrameCount );
  873. #else
  874. queuedLogEntryTimestamp = new DebugLogEntryTimestamp( dateTime );
  875. #endif
  876. }
  877. else
  878. queuedLogEntryTimestamp = dummyLogEntryTimestamp;
  879. lock( logEntriesLock )
  880. {
  881. queuedLogEntries.Add( queuedLogEntry );
  882. if( queuedLogEntriesTimestamps != null )
  883. queuedLogEntriesTimestamps.Add( queuedLogEntryTimestamp );
  884. if( logType == LogType.Log )
  885. newInfoEntryCount++;
  886. else if( logType == LogType.Warning )
  887. newWarningEntryCount++;
  888. else
  889. newErrorEntryCount++;
  890. }
  891. }
  892. // Present the log entry in the console
  893. private void ProcessLog( QueuedDebugLogEntry queuedLogEntry, DebugLogEntryTimestamp timestamp )
  894. {
  895. LogType logType = queuedLogEntry.logType;
  896. DebugLogEntry logEntry;
  897. if( pooledLogEntries.Count > 0 )
  898. {
  899. logEntry = pooledLogEntries[pooledLogEntries.Count - 1];
  900. pooledLogEntries.RemoveAt( pooledLogEntries.Count - 1 );
  901. }
  902. else
  903. logEntry = new DebugLogEntry();
  904. logEntry.Initialize( queuedLogEntry.logString, queuedLogEntry.stackTrace );
  905. // Check if this entry is a duplicate (i.e. has been received before)
  906. int logEntryIndex;
  907. bool isEntryInCollapsedEntryList = collapsedLogEntriesMap.TryGetValue( logEntry, out logEntryIndex );
  908. if( !isEntryInCollapsedEntryList )
  909. {
  910. // It is not a duplicate,
  911. // add it to the list of unique debug entries
  912. logEntry.logTypeSpriteRepresentation = logSpriteRepresentations[logType];
  913. logEntryIndex = collapsedLogEntries.Count;
  914. collapsedLogEntries.Add( logEntry );
  915. collapsedLogEntriesMap[logEntry] = logEntryIndex;
  916. if( collapsedLogEntriesTimestamps != null )
  917. collapsedLogEntriesTimestamps.Add( timestamp );
  918. }
  919. else
  920. {
  921. // It is a duplicate, pool the duplicate log entry and
  922. // increment the original debug item's collapsed count
  923. pooledLogEntries.Add( logEntry );
  924. logEntry = collapsedLogEntries[logEntryIndex];
  925. logEntry.count++;
  926. if( collapsedLogEntriesTimestamps != null )
  927. collapsedLogEntriesTimestamps[logEntryIndex] = timestamp;
  928. }
  929. // Add the index of the unique debug entry to the list
  930. // that stores the order the debug entries are received
  931. uncollapsedLogEntriesIndices.Add( logEntryIndex );
  932. // Record log's timestamp if desired
  933. if( uncollapsedLogEntriesTimestamps != null )
  934. uncollapsedLogEntriesTimestamps.Add( timestamp );
  935. // If this debug entry matches the current filters,
  936. // add it to the list of debug entries to show
  937. int logEntryIndexInEntriesToShow = -1;
  938. Sprite logTypeSpriteRepresentation = logEntry.logTypeSpriteRepresentation;
  939. if( isCollapseOn && isEntryInCollapsedEntryList )
  940. {
  941. if( isLogWindowVisible || timestampsOfListEntriesToShow != null )
  942. {
  943. if( !isInSearchMode && logFilter == DebugLogFilter.All )
  944. logEntryIndexInEntriesToShow = logEntryIndex;
  945. else
  946. logEntryIndexInEntriesToShow = indicesOfListEntriesToShow.IndexOf( logEntryIndex );
  947. if( logEntryIndexInEntriesToShow >= 0 )
  948. {
  949. if( timestampsOfListEntriesToShow != null )
  950. timestampsOfListEntriesToShow[logEntryIndexInEntriesToShow] = timestamp;
  951. if( isLogWindowVisible )
  952. recycledListView.OnCollapsedLogEntryAtIndexUpdated( logEntryIndexInEntriesToShow );
  953. }
  954. }
  955. }
  956. else if( ( !isInSearchMode || queuedLogEntry.MatchesSearchTerm( searchTerm ) ) && ( logFilter == DebugLogFilter.All ||
  957. ( logTypeSpriteRepresentation == infoLog && ( ( logFilter & DebugLogFilter.Info ) == DebugLogFilter.Info ) ) ||
  958. ( logTypeSpriteRepresentation == warningLog && ( ( logFilter & DebugLogFilter.Warning ) == DebugLogFilter.Warning ) ) ||
  959. ( logTypeSpriteRepresentation == errorLog && ( ( logFilter & DebugLogFilter.Error ) == DebugLogFilter.Error ) ) ) )
  960. {
  961. indicesOfListEntriesToShow.Add( logEntryIndex );
  962. logEntryIndexInEntriesToShow = indicesOfListEntriesToShow.Count - 1;
  963. if( timestampsOfListEntriesToShow != null )
  964. timestampsOfListEntriesToShow.Add( timestamp );
  965. shouldUpdateRecycledListView = true;
  966. }
  967. // Automatically expand this log if necessary
  968. if( pendingLogToAutoExpand > 0 && --pendingLogToAutoExpand <= 0 && logEntryIndexInEntriesToShow >= 0 )
  969. indexOfLogEntryToSelectAndFocus = logEntryIndexInEntriesToShow;
  970. }
  971. // Value of snapToBottom is changed (user scrolled the list manually)
  972. public void SetSnapToBottom( bool snapToBottom )
  973. {
  974. this.snapToBottom = snapToBottom;
  975. }
  976. // Make sure the scroll bar of the scroll rect is adjusted properly
  977. internal void ValidateScrollPosition()
  978. {
  979. // When scrollbar is snapped to the very bottom of the scroll view, sometimes OnScroll alone doesn't work
  980. if( logItemsScrollRect.verticalNormalizedPosition <= Mathf.Epsilon )
  981. logItemsScrollRect.verticalNormalizedPosition = 0.0001f;
  982. logItemsScrollRect.OnScroll( nullPointerEventData );
  983. }
  984. // Modifies certain properties of the most recently received log
  985. public void AdjustLatestPendingLog( bool autoExpand, bool stripStackTrace )
  986. {
  987. lock( logEntriesLock )
  988. {
  989. if( queuedLogEntries.Count == 0 )
  990. return;
  991. if( autoExpand ) // Automatically expand the latest log in queuedLogEntries
  992. pendingLogToAutoExpand = queuedLogEntries.Count;
  993. if( stripStackTrace ) // Omit the latest log's stack trace
  994. {
  995. QueuedDebugLogEntry log = queuedLogEntries[queuedLogEntries.Count - 1];
  996. queuedLogEntries[queuedLogEntries.Count - 1] = new QueuedDebugLogEntry( log.logString, string.Empty, log.logType );
  997. }
  998. }
  999. }
  1000. // Clear all the logs
  1001. public void ClearLogs()
  1002. {
  1003. snapToBottom = true;
  1004. infoEntryCount = 0;
  1005. warningEntryCount = 0;
  1006. errorEntryCount = 0;
  1007. infoEntryCountText.text = "0";
  1008. warningEntryCountText.text = "0";
  1009. errorEntryCountText.text = "0";
  1010. collapsedLogEntries.Clear();
  1011. collapsedLogEntriesMap.Clear();
  1012. uncollapsedLogEntriesIndices.Clear();
  1013. indicesOfListEntriesToShow.Clear();
  1014. if( collapsedLogEntriesTimestamps != null )
  1015. {
  1016. collapsedLogEntriesTimestamps.Clear();
  1017. uncollapsedLogEntriesTimestamps.Clear();
  1018. timestampsOfListEntriesToShow.Clear();
  1019. }
  1020. recycledListView.DeselectSelectedLogItem();
  1021. recycledListView.OnLogEntriesUpdated( true );
  1022. }
  1023. // Collapse button is clicked
  1024. private void CollapseButtonPressed()
  1025. {
  1026. // Swap the value of collapse mode
  1027. isCollapseOn = !isCollapseOn;
  1028. snapToBottom = true;
  1029. collapseButton.color = isCollapseOn ? collapseButtonSelectedColor : collapseButtonNormalColor;
  1030. recycledListView.SetCollapseMode( isCollapseOn );
  1031. // Determine the new list of debug entries to show
  1032. FilterLogs();
  1033. }
  1034. // Filtering mode of info logs has changed
  1035. private void FilterLogButtonPressed()
  1036. {
  1037. logFilter = logFilter ^ DebugLogFilter.Info;
  1038. if( ( logFilter & DebugLogFilter.Info ) == DebugLogFilter.Info )
  1039. filterInfoButton.color = filterButtonsSelectedColor;
  1040. else
  1041. filterInfoButton.color = filterButtonsNormalColor;
  1042. FilterLogs();
  1043. }
  1044. // Filtering mode of warning logs has changed
  1045. private void FilterWarningButtonPressed()
  1046. {
  1047. logFilter = logFilter ^ DebugLogFilter.Warning;
  1048. if( ( logFilter & DebugLogFilter.Warning ) == DebugLogFilter.Warning )
  1049. filterWarningButton.color = filterButtonsSelectedColor;
  1050. else
  1051. filterWarningButton.color = filterButtonsNormalColor;
  1052. FilterLogs();
  1053. }
  1054. // Filtering mode of error logs has changed
  1055. private void FilterErrorButtonPressed()
  1056. {
  1057. logFilter = logFilter ^ DebugLogFilter.Error;
  1058. if( ( logFilter & DebugLogFilter.Error ) == DebugLogFilter.Error )
  1059. filterErrorButton.color = filterButtonsSelectedColor;
  1060. else
  1061. filterErrorButton.color = filterButtonsNormalColor;
  1062. FilterLogs();
  1063. }
  1064. // Search term has changed
  1065. private void SearchTermChanged( string searchTerm )
  1066. {
  1067. if( searchTerm != null )
  1068. searchTerm = searchTerm.Trim();
  1069. this.searchTerm = searchTerm;
  1070. bool isInSearchMode = !string.IsNullOrEmpty( searchTerm );
  1071. if( isInSearchMode || this.isInSearchMode )
  1072. {
  1073. this.isInSearchMode = isInSearchMode;
  1074. FilterLogs();
  1075. }
  1076. }
  1077. // Show suggestions for the currently entered command
  1078. private void RefreshCommandSuggestions( string command )
  1079. {
  1080. if( !showCommandSuggestions )
  1081. return;
  1082. commandInputFieldPrevCaretPos = commandInputField.caretPosition;
  1083. // Don't recalculate the command suggestions if the input command hasn't changed (i.e. only caret's position has changed)
  1084. bool commandChanged = command != commandInputFieldPrevCommand;
  1085. bool commandNameOrParametersChanged = false;
  1086. if( commandChanged )
  1087. {
  1088. commandInputFieldPrevCommand = command;
  1089. matchingCommandSuggestions.Clear();
  1090. commandCaretIndexIncrements.Clear();
  1091. string prevCommandName = commandInputFieldPrevCommandName;
  1092. int numberOfParameters;
  1093. DebugLogConsole.GetCommandSuggestions( command, matchingCommandSuggestions, commandCaretIndexIncrements, ref commandInputFieldPrevCommandName, out numberOfParameters );
  1094. if( prevCommandName != commandInputFieldPrevCommandName || numberOfParameters != commandInputFieldPrevParamCount )
  1095. {
  1096. commandInputFieldPrevParamCount = numberOfParameters;
  1097. commandNameOrParametersChanged = true;
  1098. }
  1099. }
  1100. int caretArgumentIndex = 0;
  1101. int caretPos = commandInputField.caretPosition;
  1102. for( int i = 0; i < commandCaretIndexIncrements.Count && caretPos > commandCaretIndexIncrements[i]; i++ )
  1103. caretArgumentIndex++;
  1104. if( caretArgumentIndex != commandInputFieldPrevCaretArgumentIndex )
  1105. commandInputFieldPrevCaretArgumentIndex = caretArgumentIndex;
  1106. else if( !commandChanged || !commandNameOrParametersChanged )
  1107. {
  1108. // Command suggestions don't need to be updated if:
  1109. // a) neither the entered command nor the argument that the caret is hovering has changed
  1110. // b) entered command has changed but command's name hasn't changed, parameter count hasn't changed and the argument
  1111. // that the caret is hovering hasn't changed (i.e. user has continued typing a parameter's value)
  1112. return;
  1113. }
  1114. if( matchingCommandSuggestions.Count == 0 )
  1115. OnEndEditCommand( command );
  1116. else
  1117. {
  1118. if( !commandSuggestionsContainer.gameObject.activeSelf )
  1119. commandSuggestionsContainer.gameObject.SetActive( true );
  1120. int suggestionInstancesCount = commandSuggestionInstances.Count;
  1121. int suggestionsCount = matchingCommandSuggestions.Count;
  1122. for( int i = 0; i < suggestionsCount; i++ )
  1123. {
  1124. if( i >= visibleCommandSuggestionInstances )
  1125. {
  1126. if( i >= suggestionInstancesCount )
  1127. commandSuggestionInstances.Add( (Text) Instantiate( commandSuggestionPrefab, commandSuggestionsContainer, false ) );
  1128. else
  1129. commandSuggestionInstances[i].gameObject.SetActive( true );
  1130. visibleCommandSuggestionInstances++;
  1131. }
  1132. ConsoleMethodInfo suggestedCommand = matchingCommandSuggestions[i];
  1133. sharedStringBuilder.Length = 0;
  1134. if( caretArgumentIndex > 0 )
  1135. sharedStringBuilder.Append( suggestedCommand.command );
  1136. else
  1137. sharedStringBuilder.Append( commandSuggestionHighlightStart ).Append( matchingCommandSuggestions[i].command ).Append( commandSuggestionHighlightEnd );
  1138. if( suggestedCommand.parameters.Length > 0 )
  1139. {
  1140. sharedStringBuilder.Append( " " );
  1141. // If the command name wasn't highlighted, a parameter must always be highlighted
  1142. int caretParameterIndex = caretArgumentIndex - 1;
  1143. if( caretParameterIndex >= suggestedCommand.parameters.Length )
  1144. caretParameterIndex = suggestedCommand.parameters.Length - 1;
  1145. for( int j = 0; j < suggestedCommand.parameters.Length; j++ )
  1146. {
  1147. if( caretParameterIndex != j )
  1148. sharedStringBuilder.Append( suggestedCommand.parameters[j] );
  1149. else
  1150. sharedStringBuilder.Append( commandSuggestionHighlightStart ).Append( suggestedCommand.parameters[j] ).Append( commandSuggestionHighlightEnd );
  1151. }
  1152. }
  1153. commandSuggestionInstances[i].text = sharedStringBuilder.ToString();
  1154. }
  1155. for( int i = visibleCommandSuggestionInstances - 1; i >= suggestionsCount; i-- )
  1156. commandSuggestionInstances[i].gameObject.SetActive( false );
  1157. visibleCommandSuggestionInstances = suggestionsCount;
  1158. }
  1159. }
  1160. // Command input field's text has changed
  1161. private void OnEditCommand( string command )
  1162. {
  1163. RefreshCommandSuggestions( command );
  1164. if( !commandInputFieldAutoCompletedNow )
  1165. commandInputFieldAutoCompleteBase = null;
  1166. else // This change was caused by autocomplete
  1167. commandInputFieldAutoCompletedNow = false;
  1168. }
  1169. // Command input field has lost focus
  1170. private void OnEndEditCommand( string command )
  1171. {
  1172. if( commandSuggestionsContainer.gameObject.activeSelf )
  1173. commandSuggestionsContainer.gameObject.SetActive( false );
  1174. }
  1175. // Debug window is being resized,
  1176. // Set the sizeDelta property of the window accordingly while
  1177. // preventing window dimensions from going below the minimum dimensions
  1178. internal void Resize( PointerEventData eventData )
  1179. {
  1180. Vector2 localPoint;
  1181. if( !RectTransformUtility.ScreenPointToLocalPointInRectangle( canvasTR, eventData.position, eventData.pressEventCamera, out localPoint ) )
  1182. return;
  1183. // To be able to maximize the log window easily:
  1184. // - When enableHorizontalResizing is true and resizing horizontally, resize button will be grabbed from its left edge (if resizeFromRight is true) or its right edge
  1185. // - While resizing vertically, resize button will be grabbed from its top edge
  1186. const float resizeButtonWidth = 64f;
  1187. const float resizeButtonHeight = 36f;
  1188. Vector2 canvasPivot = canvasTR.pivot;
  1189. Vector2 canvasSize = canvasTR.rect.size;
  1190. Vector2 anchorMin = logWindowTR.anchorMin;
  1191. // Horizontal resizing
  1192. if( enableHorizontalResizing )
  1193. {
  1194. if( resizeFromRight )
  1195. {
  1196. localPoint.x += canvasPivot.x * canvasSize.x + resizeButtonWidth;
  1197. if( localPoint.x < minimumWidth )
  1198. localPoint.x = minimumWidth;
  1199. Vector2 anchorMax = logWindowTR.anchorMax;
  1200. anchorMax.x = Mathf.Clamp01( localPoint.x / canvasSize.x );
  1201. logWindowTR.anchorMax = anchorMax;
  1202. }
  1203. else
  1204. {
  1205. localPoint.x += canvasPivot.x * canvasSize.x - resizeButtonWidth;
  1206. if( localPoint.x > canvasSize.x - minimumWidth )
  1207. localPoint.x = canvasSize.x - minimumWidth;
  1208. anchorMin.x = Mathf.Clamp01( localPoint.x / canvasSize.x );
  1209. }
  1210. }
  1211. // Vertical resizing
  1212. float notchHeight = -logWindowTR.sizeDelta.y; // Size of notch screen cutouts at the top of the screen
  1213. localPoint.y += canvasPivot.y * canvasSize.y - resizeButtonHeight;
  1214. if( localPoint.y > canvasSize.y - minimumHeight - notchHeight )
  1215. localPoint.y = canvasSize.y - minimumHeight - notchHeight;
  1216. anchorMin.y = Mathf.Clamp01( localPoint.y / canvasSize.y );
  1217. logWindowTR.anchorMin = anchorMin;
  1218. // Update the recycled list view
  1219. recycledListView.OnViewportHeightChanged();
  1220. }
  1221. // Determine the filtered list of debug entries to show on screen
  1222. private void FilterLogs()
  1223. {
  1224. indicesOfListEntriesToShow.Clear();
  1225. if( timestampsOfListEntriesToShow != null )
  1226. timestampsOfListEntriesToShow.Clear();
  1227. if( logFilter != DebugLogFilter.None )
  1228. {
  1229. if( logFilter == DebugLogFilter.All )
  1230. {
  1231. if( isCollapseOn )
  1232. {
  1233. if( !isInSearchMode )
  1234. {
  1235. // All the unique debug entries will be listed just once.
  1236. // So, list of debug entries to show is the same as the
  1237. // order these unique debug entries are added to collapsedLogEntries
  1238. for( int i = 0, count = collapsedLogEntries.Count; i < count; i++ )
  1239. {
  1240. indicesOfListEntriesToShow.Add( i );
  1241. if( timestampsOfListEntriesToShow != null )
  1242. timestampsOfListEntriesToShow.Add( collapsedLogEntriesTimestamps[i] );
  1243. }
  1244. }
  1245. else
  1246. {
  1247. for( int i = 0, count = collapsedLogEntries.Count; i < count; i++ )
  1248. {
  1249. if( collapsedLogEntries[i].MatchesSearchTerm( searchTerm ) )
  1250. {
  1251. indicesOfListEntriesToShow.Add( i );
  1252. if( timestampsOfListEntriesToShow != null )
  1253. timestampsOfListEntriesToShow.Add( collapsedLogEntriesTimestamps[i] );
  1254. }
  1255. }
  1256. }
  1257. }
  1258. else
  1259. {
  1260. if( !isInSearchMode )
  1261. {
  1262. for( int i = 0, count = uncollapsedLogEntriesIndices.Count; i < count; i++ )
  1263. {
  1264. indicesOfListEntriesToShow.Add( uncollapsedLogEntriesIndices[i] );
  1265. if( timestampsOfListEntriesToShow != null )
  1266. timestampsOfListEntriesToShow.Add( uncollapsedLogEntriesTimestamps[i] );
  1267. }
  1268. }
  1269. else
  1270. {
  1271. for( int i = 0, count = uncollapsedLogEntriesIndices.Count; i < count; i++ )
  1272. {
  1273. if( collapsedLogEntries[uncollapsedLogEntriesIndices[i]].MatchesSearchTerm( searchTerm ) )
  1274. {
  1275. indicesOfListEntriesToShow.Add( uncollapsedLogEntriesIndices[i] );
  1276. if( timestampsOfListEntriesToShow != null )
  1277. timestampsOfListEntriesToShow.Add( uncollapsedLogEntriesTimestamps[i] );
  1278. }
  1279. }
  1280. }
  1281. }
  1282. }
  1283. else
  1284. {
  1285. // Show only the debug entries that match the current filter
  1286. bool isInfoEnabled = ( logFilter & DebugLogFilter.Info ) == DebugLogFilter.Info;
  1287. bool isWarningEnabled = ( logFilter & DebugLogFilter.Warning ) == DebugLogFilter.Warning;
  1288. bool isErrorEnabled = ( logFilter & DebugLogFilter.Error ) == DebugLogFilter.Error;
  1289. if( isCollapseOn )
  1290. {
  1291. for( int i = 0, count = collapsedLogEntries.Count; i < count; i++ )
  1292. {
  1293. DebugLogEntry logEntry = collapsedLogEntries[i];
  1294. if( isInSearchMode && !logEntry.MatchesSearchTerm( searchTerm ) )
  1295. continue;
  1296. bool shouldShowLog = false;
  1297. if( logEntry.logTypeSpriteRepresentation == infoLog )
  1298. {
  1299. if( isInfoEnabled )
  1300. shouldShowLog = true;
  1301. }
  1302. else if( logEntry.logTypeSpriteRepresentation == warningLog )
  1303. {
  1304. if( isWarningEnabled )
  1305. shouldShowLog = true;
  1306. }
  1307. else if( isErrorEnabled )
  1308. shouldShowLog = true;
  1309. if( shouldShowLog )
  1310. {
  1311. indicesOfListEntriesToShow.Add( i );
  1312. if( timestampsOfListEntriesToShow != null )
  1313. timestampsOfListEntriesToShow.Add( collapsedLogEntriesTimestamps[i] );
  1314. }
  1315. }
  1316. }
  1317. else
  1318. {
  1319. for( int i = 0, count = uncollapsedLogEntriesIndices.Count; i < count; i++ )
  1320. {
  1321. DebugLogEntry logEntry = collapsedLogEntries[uncollapsedLogEntriesIndices[i]];
  1322. if( isInSearchMode && !logEntry.MatchesSearchTerm( searchTerm ) )
  1323. continue;
  1324. bool shouldShowLog = false;
  1325. if( logEntry.logTypeSpriteRepresentation == infoLog )
  1326. {
  1327. if( isInfoEnabled )
  1328. shouldShowLog = true;
  1329. }
  1330. else if( logEntry.logTypeSpriteRepresentation == warningLog )
  1331. {
  1332. if( isWarningEnabled )
  1333. shouldShowLog = true;
  1334. }
  1335. else if( isErrorEnabled )
  1336. shouldShowLog = true;
  1337. if( shouldShowLog )
  1338. {
  1339. indicesOfListEntriesToShow.Add( uncollapsedLogEntriesIndices[i] );
  1340. if( timestampsOfListEntriesToShow != null )
  1341. timestampsOfListEntriesToShow.Add( uncollapsedLogEntriesTimestamps[i] );
  1342. }
  1343. }
  1344. }
  1345. }
  1346. }
  1347. // Update the recycled list view
  1348. recycledListView.DeselectSelectedLogItem();
  1349. recycledListView.OnLogEntriesUpdated( true );
  1350. ValidateScrollPosition();
  1351. }
  1352. public string GetAllLogs()
  1353. {
  1354. int count = uncollapsedLogEntriesIndices.Count;
  1355. int length = 0;
  1356. int newLineLength = System.Environment.NewLine.Length;
  1357. for( int i = 0; i < count; i++ )
  1358. {
  1359. DebugLogEntry entry = collapsedLogEntries[uncollapsedLogEntriesIndices[i]];
  1360. length += entry.logString.Length + entry.stackTrace.Length + newLineLength * 3;
  1361. }
  1362. if( uncollapsedLogEntriesTimestamps != null )
  1363. length += count * 12; // Timestamp: "[HH:mm:ss]: "
  1364. length += 100; // Just in case...
  1365. StringBuilder sb = new StringBuilder( length );
  1366. for( int i = 0; i < count; i++ )
  1367. {
  1368. DebugLogEntry entry = collapsedLogEntries[uncollapsedLogEntriesIndices[i]];
  1369. if( uncollapsedLogEntriesTimestamps != null )
  1370. {
  1371. uncollapsedLogEntriesTimestamps[i].AppendTime( sb );
  1372. sb.Append( ": " );
  1373. }
  1374. sb.AppendLine( entry.logString ).AppendLine( entry.stackTrace ).AppendLine();
  1375. }
  1376. return sb.ToString();
  1377. }
  1378. private void SaveLogsToFile()
  1379. {
  1380. SaveLogsToFile( Path.Combine( Application.persistentDataPath, System.DateTime.Now.ToString( "dd-MM-yyyy--HH-mm-ss" ) + ".txt" ) );
  1381. }
  1382. private void SaveLogsToFile( string filePath )
  1383. {
  1384. File.WriteAllText( filePath, GetAllLogs() );
  1385. Debug.Log( "Logs saved to: " + filePath );
  1386. }
  1387. // If a cutout is intersecting with debug window on notch screens, shift the window downwards
  1388. private void CheckScreenCutout()
  1389. {
  1390. if( !avoidScreenCutout )
  1391. return;
  1392. #if UNITY_2017_2_OR_NEWER && ( UNITY_EDITOR || UNITY_ANDROID || UNITY_IOS )
  1393. // Check if there is a cutout at the top of the screen
  1394. int screenHeight = Screen.height;
  1395. float safeYMax = Screen.safeArea.yMax;
  1396. if( safeYMax < screenHeight - 1 ) // 1: a small threshold
  1397. {
  1398. // There is a cutout, shift the log window downwards
  1399. float cutoutPercentage = ( screenHeight - safeYMax ) / Screen.height;
  1400. float cutoutLocalSize = cutoutPercentage * canvasTR.rect.height;
  1401. logWindowTR.anchoredPosition = new Vector2( 0f, -cutoutLocalSize );
  1402. logWindowTR.sizeDelta = new Vector2( 0f, -cutoutLocalSize );
  1403. }
  1404. else
  1405. {
  1406. logWindowTR.anchoredPosition = Vector2.zero;
  1407. logWindowTR.sizeDelta = Vector2.zero;
  1408. }
  1409. #endif
  1410. }
  1411. #if UNITY_EDITOR || UNITY_STANDALONE || UNITY_WEBGL
  1412. private IEnumerator ActivateCommandInputFieldCoroutine()
  1413. {
  1414. // Waiting 1 frame before activating commandInputField ensures that the toggleKey isn't captured by it
  1415. yield return null;
  1416. commandInputField.ActivateInputField();
  1417. yield return null;
  1418. commandInputField.MoveTextEnd( false );
  1419. }
  1420. #endif
  1421. // Pool an unused log item
  1422. internal void PoolLogItem( DebugLogItem logItem )
  1423. {
  1424. logItem.CanvasGroup.alpha = 0f;
  1425. logItem.CanvasGroup.blocksRaycasts = false;
  1426. pooledLogItems.Add( logItem );
  1427. }
  1428. // Fetch a log item from the pool
  1429. internal DebugLogItem PopLogItem()
  1430. {
  1431. DebugLogItem newLogItem;
  1432. // If pool is not empty, fetch a log item from the pool,
  1433. // create a new log item otherwise
  1434. if( pooledLogItems.Count > 0 )
  1435. {
  1436. newLogItem = pooledLogItems[pooledLogItems.Count - 1];
  1437. pooledLogItems.RemoveAt( pooledLogItems.Count - 1 );
  1438. newLogItem.CanvasGroup.alpha = 1f;
  1439. newLogItem.CanvasGroup.blocksRaycasts = true;
  1440. }
  1441. else
  1442. {
  1443. newLogItem = (DebugLogItem) Instantiate( logItemPrefab, logItemsContainer, false );
  1444. newLogItem.Initialize( recycledListView );
  1445. }
  1446. return newLogItem;
  1447. }
  1448. }
  1449. }