ThreadSelectionWindow.cs 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965
  1. using UnityEngine;
  2. using System.IO;
  3. using System;
  4. using System.Collections.Generic;
  5. using UnityEditor.IMGUI.Controls;
  6. using UnityEngine.Assertions;
  7. using System.Linq;
  8. namespace UnityEditor.Performance.ProfileAnalyzer
  9. {
  10. class ThreadTreeViewItem : TreeViewItem
  11. {
  12. public readonly ThreadIdentifier threadIdentifier;
  13. public ThreadTreeViewItem(int id, int depth, string displayName, ThreadIdentifier threadIdentifier) : base(id, depth, displayName)
  14. {
  15. this.threadIdentifier = threadIdentifier;
  16. }
  17. }
  18. class ThreadTable : TreeView
  19. {
  20. const float kRowHeights = 20f;
  21. readonly List<TreeViewItem> m_Rows = new List<TreeViewItem>(100);
  22. List<string> m_ThreadNames;
  23. List<string> m_ThreadUINames;
  24. ThreadIdentifier m_AllThreadIdentifier;
  25. ThreadSelection m_ThreadSelection;
  26. GUIStyle activeLineStyle;
  27. public bool StateChanged { get; private set; }
  28. // All columns
  29. public enum MyColumns
  30. {
  31. GroupName,
  32. ThreadName,
  33. State,
  34. }
  35. public enum SortOption
  36. {
  37. GroupName,
  38. ThreadName,
  39. State,
  40. }
  41. // Sort options per column
  42. SortOption[] m_SortOptions =
  43. {
  44. SortOption.GroupName,
  45. SortOption.ThreadName,
  46. SortOption.State,
  47. };
  48. public enum ThreadSelected
  49. {
  50. Selected,
  51. Partial,
  52. NotSelected
  53. };
  54. public ThreadTable(TreeViewState state, MultiColumnHeader multicolumnHeader, List<string> threadNames, List<string> threadUINames, ThreadSelection threadSelection) : base(state, multicolumnHeader)
  55. {
  56. StateChanged = false;
  57. m_AllThreadIdentifier = new ThreadIdentifier();
  58. m_AllThreadIdentifier.SetName("All");
  59. m_AllThreadIdentifier.SetAll();
  60. Assert.AreEqual(m_SortOptions.Length, Enum.GetValues(typeof(MyColumns)).Length, "Ensure number of sort options are in sync with number of MyColumns enum values");
  61. // Custom setup
  62. rowHeight = kRowHeights;
  63. showAlternatingRowBackgrounds = true;
  64. columnIndexForTreeFoldouts = (int)(MyColumns.GroupName);
  65. showBorder = true;
  66. customFoldoutYOffset = (kRowHeights - EditorGUIUtility.singleLineHeight) * 0.5f; // center foldout in the row since we also center content. See RowGUI
  67. // extraSpaceBeforeIconAndLabel = 0;
  68. multicolumnHeader.sortingChanged += OnSortingChanged;
  69. multicolumnHeader.visibleColumnsChanged += OnVisibleColumnsChanged;
  70. m_ThreadNames = threadNames;
  71. m_ThreadUINames = threadUINames;
  72. m_ThreadSelection = new ThreadSelection(threadSelection);
  73. #if UNITY_2018_3_OR_NEWER
  74. this.foldoutOverride += DoFoldout;
  75. #endif
  76. Reload();
  77. }
  78. #if UNITY_2018_3_OR_NEWER
  79. bool DoFoldout(Rect position, bool expandedstate, GUIStyle style)
  80. {
  81. return !(position.y < rowHeight) && GUI.Toggle(position, expandedstate, GUIContent.none, style);
  82. }
  83. #endif
  84. public void ClearThreadSelection()
  85. {
  86. m_ThreadSelection.selection.Clear();
  87. m_ThreadSelection.groups.Clear();
  88. StateChanged = true;
  89. Reload();
  90. }
  91. public void SelectMain()
  92. {
  93. m_ThreadSelection.selection.Clear();
  94. m_ThreadSelection.groups.Clear();
  95. foreach (var threadName in m_ThreadNames)
  96. {
  97. if (threadName.StartsWith("All:"))
  98. continue;
  99. if (threadName.EndsWith(":Main Thread")) // Usually just starts with 1:
  100. m_ThreadSelection.selection.Add(threadName);
  101. }
  102. StateChanged = true;
  103. Reload();
  104. }
  105. public void SelectCommon()
  106. {
  107. m_ThreadSelection.selection.Clear();
  108. m_ThreadSelection.groups.Clear();
  109. foreach (var threadName in m_ThreadNames)
  110. {
  111. if (threadName.StartsWith("All:"))
  112. continue;
  113. if (threadName.EndsWith(":Render Thread")) // Usually just starts with 1:
  114. m_ThreadSelection.selection.Add(threadName);
  115. if (threadName.EndsWith(":Main Thread")) // Usually just starts with 1:
  116. m_ThreadSelection.selection.Add(threadName);
  117. if (threadName.EndsWith(":Job.Worker")) // Mulitple jobs, number depends on processor setup
  118. m_ThreadSelection.selection.Add(threadName);
  119. }
  120. StateChanged = true;
  121. Reload();
  122. }
  123. public ThreadSelection GetThreadSelection()
  124. {
  125. return m_ThreadSelection;
  126. }
  127. protected int GetChildCount(ThreadIdentifier selectedThreadIdentifier, out int selected)
  128. {
  129. int count = 0;
  130. int selectedCount = 0;
  131. if (selectedThreadIdentifier.index == ThreadIdentifier.kAll)
  132. {
  133. if (selectedThreadIdentifier.name == "All")
  134. {
  135. for (int index = 0; index < m_ThreadNames.Count; ++index)
  136. {
  137. var threadNameWithIndex = m_ThreadNames[index];
  138. var threadIdentifier = new ThreadIdentifier(threadNameWithIndex);
  139. if (threadIdentifier.index != ThreadIdentifier.kAll)
  140. {
  141. count++;
  142. if (m_ThreadSelection.selection.Contains(threadNameWithIndex))
  143. selectedCount++;
  144. }
  145. }
  146. }
  147. else
  148. {
  149. for (int index = 0; index < m_ThreadNames.Count; ++index)
  150. {
  151. var threadNameWithIndex = m_ThreadNames[index];
  152. var threadIdentifier = new ThreadIdentifier(threadNameWithIndex);
  153. if (selectedThreadIdentifier.name == threadIdentifier.name &&
  154. threadIdentifier.index != ThreadIdentifier.kAll)
  155. {
  156. count++;
  157. if (m_ThreadSelection.selection.Contains(threadNameWithIndex))
  158. selectedCount++;
  159. }
  160. }
  161. }
  162. }
  163. selected = selectedCount;
  164. return count;
  165. }
  166. protected override TreeViewItem BuildRoot()
  167. {
  168. int idForHiddenRoot = -1;
  169. int depthForHiddenRoot = -1;
  170. ProfileTreeViewItem root = new ProfileTreeViewItem(idForHiddenRoot, depthForHiddenRoot, "root", null);
  171. int depth = 0;
  172. var top = new ThreadTreeViewItem(-1, depth, m_AllThreadIdentifier.name, m_AllThreadIdentifier);
  173. root.AddChild(top);
  174. var expandList = new List<int>() {-1};
  175. string lastThreadName = "";
  176. TreeViewItem node = root;
  177. for (int index = 0; index < m_ThreadNames.Count; ++index)
  178. {
  179. var threadNameWithIndex = m_ThreadNames[index];
  180. if (threadNameWithIndex == m_AllThreadIdentifier.threadNameWithIndex)
  181. continue;
  182. var threadIdentifier = new ThreadIdentifier(threadNameWithIndex);
  183. var item = new ThreadTreeViewItem(index, depth, m_ThreadUINames[index], threadIdentifier);
  184. if (threadIdentifier.name != lastThreadName)
  185. {
  186. // New threads at root
  187. node = top;
  188. depth = 0;
  189. }
  190. node.AddChild(item);
  191. if (threadIdentifier.name != lastThreadName)
  192. {
  193. // Extra instances hang of the parent
  194. lastThreadName = threadIdentifier.name;
  195. node = item;
  196. depth = 1;
  197. }
  198. }
  199. SetExpanded(expandList);
  200. SetupDepthsFromParentsAndChildren(root);
  201. return root;
  202. }
  203. void BuildRowRecursive(IList<TreeViewItem> rows, TreeViewItem item)
  204. {
  205. //if (item.children == null)
  206. // return;
  207. if (!IsExpanded(item.id))
  208. return;
  209. foreach (ThreadTreeViewItem subNode in item.children)
  210. {
  211. rows.Add(subNode);
  212. if (subNode.children != null)
  213. BuildRowRecursive(rows, subNode);
  214. }
  215. }
  216. void BuildAllRows(IList<TreeViewItem> rows, TreeViewItem rootItem)
  217. {
  218. rows.Clear();
  219. if (rootItem == null)
  220. return;
  221. if (rootItem.children == null)
  222. return;
  223. foreach (ThreadTreeViewItem node in rootItem.children)
  224. {
  225. rows.Add(node);
  226. if (node.children != null)
  227. BuildRowRecursive(rows, node);
  228. }
  229. }
  230. protected override IList<TreeViewItem> BuildRows(TreeViewItem root)
  231. {
  232. BuildAllRows(m_Rows, root);
  233. SortIfNeeded(m_Rows);
  234. return m_Rows;
  235. }
  236. void OnSortingChanged(MultiColumnHeader _multiColumnHeader)
  237. {
  238. SortIfNeeded(GetRows());
  239. }
  240. void OnVisibleColumnsChanged(MultiColumnHeader _multiColumnHeader)
  241. {
  242. multiColumnHeader.ResizeToFit();
  243. }
  244. void SortIfNeeded(IList<TreeViewItem> rows)
  245. {
  246. if (rows.Count <= 1)
  247. {
  248. return;
  249. }
  250. if (multiColumnHeader.sortedColumnIndex == -1)
  251. {
  252. return; // No column to sort for (just use the order the data are in)
  253. }
  254. SortByMultipleColumns();
  255. BuildAllRows(rows, rootItem);
  256. Repaint();
  257. }
  258. string GetItemGroupName(ThreadTreeViewItem item)
  259. {
  260. string groupName;
  261. string threadName = item.threadIdentifier.name;
  262. threadName = ProfileData.GetThreadNameWithoutGroup(item.threadIdentifier.name, out groupName);
  263. return groupName;
  264. }
  265. List<TreeViewItem> SortChildrenByMultipleColumns(List<TreeViewItem> children)
  266. {
  267. int[] sortedColumns = multiColumnHeader.state.sortedColumns;
  268. if (sortedColumns.Length == 0)
  269. {
  270. return children;
  271. }
  272. var myTypes = children.Cast<ThreadTreeViewItem>();
  273. var orderedQuery = InitialOrder(myTypes, sortedColumns);
  274. for (int i = 0; i < sortedColumns.Length; i++)
  275. {
  276. SortOption sortOption = m_SortOptions[sortedColumns[i]];
  277. bool ascending = multiColumnHeader.IsSortedAscending(sortedColumns[i]);
  278. switch (sortOption)
  279. {
  280. case SortOption.GroupName:
  281. orderedQuery = orderedQuery.ThenBy(l => GetItemGroupName(l), ascending);
  282. break;
  283. case SortOption.ThreadName:
  284. orderedQuery = orderedQuery.ThenBy(l => GetItemDisplayText(l), ascending);
  285. break;
  286. case SortOption.State:
  287. orderedQuery = orderedQuery.ThenBy(l => GetStateSort(l), ascending);
  288. break;
  289. }
  290. }
  291. return orderedQuery.Cast<TreeViewItem>().ToList();
  292. }
  293. void SortByMultipleColumns()
  294. {
  295. rootItem.children = SortChildrenByMultipleColumns(rootItem.children);
  296. // Sort all the next level children too (As 'All' is the only item at the top)
  297. for (int i = 0; i < rootItem.children.Count; i++)
  298. {
  299. var child = rootItem.children[0];
  300. child.children = SortChildrenByMultipleColumns(child.children);
  301. }
  302. }
  303. IOrderedEnumerable<ThreadTreeViewItem> InitialOrder(IEnumerable<ThreadTreeViewItem> myTypes, int[] history)
  304. {
  305. SortOption sortOption = m_SortOptions[history[0]];
  306. bool ascending = multiColumnHeader.IsSortedAscending(history[0]);
  307. switch (sortOption)
  308. {
  309. case SortOption.GroupName:
  310. return myTypes.Order(l => GetItemGroupName(l), ascending);
  311. case SortOption.ThreadName:
  312. return myTypes.Order(l => GetItemDisplayText(l), ascending);
  313. case SortOption.State:
  314. return myTypes.Order(l => GetStateSort(l), ascending);
  315. default:
  316. Assert.IsTrue(false, "Unhandled enum");
  317. break;
  318. }
  319. // default
  320. return myTypes.Order(l => GetItemDisplayText(l), ascending);
  321. }
  322. protected override void RowGUI(RowGUIArgs args)
  323. {
  324. ThreadTreeViewItem item = (ThreadTreeViewItem)args.item;
  325. for (int i = 0; i < args.GetNumVisibleColumns(); ++i)
  326. {
  327. CellGUI(args.GetCellRect(i), item, (MyColumns)args.GetColumn(i), ref args);
  328. }
  329. }
  330. string GetStateSort(ThreadTreeViewItem item)
  331. {
  332. ThreadSelected threadSelected = GetThreadSelectedState(item.threadIdentifier);
  333. string sortString = ((int)threadSelected).ToString() + GetItemDisplayText(item);
  334. return sortString;
  335. }
  336. ThreadSelected GetThreadSelectedState(ThreadIdentifier selectedThreadIdentifier)
  337. {
  338. if (ProfileAnalyzer.MatchThreadFilter(selectedThreadIdentifier.threadNameWithIndex, m_ThreadSelection.selection))
  339. return ThreadSelected.Selected;
  340. // If querying the 'All' filter then check if all selected
  341. if (selectedThreadIdentifier.threadNameWithIndex == m_AllThreadIdentifier.threadNameWithIndex)
  342. {
  343. // Check all threads without All in the name are selected
  344. int count = 0;
  345. int selectedCount = 0;
  346. foreach (var threadNameWithIndex in m_ThreadNames)
  347. {
  348. var threadIdentifier = new ThreadIdentifier(threadNameWithIndex);
  349. if (threadIdentifier.index == ThreadIdentifier.kAll || threadIdentifier.index == ThreadIdentifier.kSingle)
  350. continue;
  351. if (m_ThreadSelection.selection.Contains(threadNameWithIndex))
  352. selectedCount++;
  353. count++;
  354. }
  355. if (selectedCount == count)
  356. return ThreadSelected.Selected;
  357. if (selectedCount > 0)
  358. return ThreadSelected.Partial;
  359. return ThreadSelected.NotSelected;
  360. }
  361. // Need to check 'All' and thread group All.
  362. if (selectedThreadIdentifier.index == ThreadIdentifier.kAll)
  363. {
  364. // Count all threads that match this thread group
  365. int count = 0;
  366. foreach (var threadNameWithIndex in m_ThreadNames)
  367. {
  368. var threadIdentifier = new ThreadIdentifier(threadNameWithIndex);
  369. if (threadIdentifier.index == ThreadIdentifier.kAll || threadIdentifier.index == ThreadIdentifier.kSingle)
  370. continue;
  371. if (selectedThreadIdentifier.name != threadIdentifier.name)
  372. continue;
  373. count++;
  374. }
  375. // Count all the threads we have selected that match this thread group
  376. int selectedCount = 0;
  377. foreach (var threadNameWithIndex in m_ThreadSelection.selection)
  378. {
  379. var threadIdentifier = new ThreadIdentifier(threadNameWithIndex);
  380. if (selectedThreadIdentifier.name != threadIdentifier.name)
  381. continue;
  382. if (threadIdentifier.index > count)
  383. continue;
  384. selectedCount++;
  385. }
  386. if (selectedCount == count)
  387. return ThreadSelected.Selected;
  388. if (selectedCount > 0)
  389. return ThreadSelected.Partial;
  390. }
  391. return ThreadSelected.NotSelected;
  392. }
  393. string GetItemDisplayText(ThreadTreeViewItem item)
  394. {
  395. int selectedChildren;
  396. int childCount = GetChildCount(item.threadIdentifier, out selectedChildren);
  397. string fullThreadName = item.threadIdentifier.name;
  398. string groupName;
  399. string threadName = ProfileData.GetThreadNameWithoutGroup(fullThreadName, out groupName);
  400. string displayThreadName = multiColumnHeader.IsColumnVisible((int)MyColumns.GroupName) ? threadName : fullThreadName;
  401. string text;
  402. if (childCount <= 1)
  403. {
  404. text = item.displayName;
  405. }
  406. else if (selectedChildren != childCount)
  407. {
  408. text = string.Format("{0} ({1} of {2})", displayThreadName, selectedChildren, childCount);
  409. }
  410. else
  411. {
  412. text = string.Format("{0} (All)", displayThreadName);
  413. }
  414. return text;
  415. }
  416. void GetThreadTreeViewItemInfo(ThreadTreeViewItem item, out string text, out string tooltip)
  417. {
  418. text = GetItemDisplayText(item);
  419. int selectedChildren;
  420. int childCount = GetChildCount(item.threadIdentifier, out selectedChildren);
  421. string groupName = GetItemGroupName(item);
  422. if (childCount <= 1)
  423. {
  424. tooltip = (groupName == "") ? text : string.Format("{0}\n{1}", text, groupName);
  425. }
  426. else if (selectedChildren != childCount)
  427. {
  428. tooltip = (groupName == "") ? text : string.Format("{0}\n{1}", text, groupName);
  429. }
  430. else
  431. {
  432. tooltip = (groupName == "") ? text : string.Format("{0}\n{1}", text, groupName);
  433. }
  434. }
  435. Rect DrawIndent(Rect rect, ThreadTreeViewItem item, ref RowGUIArgs args)
  436. {
  437. // The rect is assumed indented and sized after the content when pinging
  438. float indent = GetContentIndent(item) + extraSpaceBeforeIconAndLabel;
  439. rect.xMin += indent;
  440. int iconRectWidth = 16;
  441. int kSpaceBetweenIconAndText = 2;
  442. // Draw icon
  443. Rect iconRect = rect;
  444. iconRect.width = iconRectWidth;
  445. // iconRect.x += 7f;
  446. Texture icon = args.item.icon;
  447. if (icon != null)
  448. GUI.DrawTexture(iconRect, icon, ScaleMode.ScaleToFit);
  449. rect.xMin += icon == null ? 0 : iconRectWidth + kSpaceBetweenIconAndText;
  450. return rect;
  451. }
  452. void CellGUI(Rect cellRect, ThreadTreeViewItem item, MyColumns column, ref RowGUIArgs args)
  453. {
  454. // Center cell rect vertically (makes it easier to place controls, icons etc in the cells)
  455. CenterRectUsingSingleLineHeight(ref cellRect);
  456. switch (column)
  457. {
  458. case MyColumns.ThreadName:
  459. {
  460. args.rowRect = cellRect;
  461. // base.RowGUI(args); // Required to show tree indenting
  462. // Draw manually to keep indenting while add a tooltip
  463. Rect rect = cellRect;
  464. if (Event.current.rawType == EventType.Repaint)
  465. {
  466. string text;
  467. string tooltip;
  468. GetThreadTreeViewItemInfo(item, out text, out tooltip);
  469. var content = new GUIContent(text, tooltip);
  470. if (activeLineStyle == null)
  471. {
  472. // activeLineStyle = DefaultStyles.boldLabel;
  473. activeLineStyle = new GUIStyle(DefaultStyles.label);
  474. activeLineStyle.normal.textColor = DefaultStyles.boldLabel.onActive.textColor;
  475. }
  476. // rect = DrawIndent(rect, item, ref args);
  477. //bool mouseOver = rect.Contains(Event.current.mousePosition);
  478. //DefaultStyles.label.Draw(rect, content, mouseOver, false, args.selected, args.focused);
  479. // Must use this call to draw tooltip
  480. EditorGUI.LabelField(rect, content, args.selected ? activeLineStyle : DefaultStyles.label);
  481. }
  482. }
  483. break;
  484. case MyColumns.GroupName:
  485. {
  486. Rect rect = cellRect;
  487. if (Event.current.rawType == EventType.Repaint)
  488. {
  489. rect = DrawIndent(rect, item, ref args);
  490. string groupName = GetItemGroupName(item);
  491. var content = new GUIContent(groupName, groupName);
  492. EditorGUI.LabelField(rect, content);
  493. }
  494. }
  495. break;
  496. case MyColumns.State:
  497. bool oldState = GetThreadSelectedState(item.threadIdentifier) == ThreadSelected.Selected;
  498. bool newState = EditorGUI.Toggle(cellRect, oldState);
  499. if (newState != oldState)
  500. {
  501. if (item.threadIdentifier.threadNameWithIndex == m_AllThreadIdentifier.threadNameWithIndex)
  502. {
  503. // Record active groups
  504. m_ThreadSelection.groups.Clear();
  505. if (newState)
  506. {
  507. if (!m_ThreadSelection.groups.Contains(item.threadIdentifier.threadNameWithIndex))
  508. m_ThreadSelection.groups.Add(item.threadIdentifier.threadNameWithIndex);
  509. }
  510. // Update selection
  511. m_ThreadSelection.selection.Clear();
  512. if (newState)
  513. {
  514. foreach (string threadNameWithIndex in m_ThreadNames)
  515. {
  516. if (threadNameWithIndex != m_AllThreadIdentifier.threadNameWithIndex)
  517. {
  518. var threadIdentifier = new ThreadIdentifier(threadNameWithIndex);
  519. if (threadIdentifier.index != ThreadIdentifier.kAll)
  520. {
  521. m_ThreadSelection.selection.Add(threadNameWithIndex);
  522. }
  523. }
  524. }
  525. }
  526. }
  527. else if (item.threadIdentifier.index == ThreadIdentifier.kAll)
  528. {
  529. // Record active groups
  530. if (newState)
  531. {
  532. if (!m_ThreadSelection.groups.Contains(item.threadIdentifier.threadNameWithIndex))
  533. m_ThreadSelection.groups.Add(item.threadIdentifier.threadNameWithIndex);
  534. }
  535. else
  536. {
  537. m_ThreadSelection.groups.Remove(item.threadIdentifier.threadNameWithIndex);
  538. // When turning off a sub group, turn of the 'all' group too
  539. m_ThreadSelection.groups.Remove(m_AllThreadIdentifier.threadNameWithIndex);
  540. }
  541. // Update selection
  542. if (newState)
  543. {
  544. foreach (string threadNameWithIndex in m_ThreadNames)
  545. {
  546. var threadIdentifier = new ThreadIdentifier(threadNameWithIndex);
  547. if (threadIdentifier.name == item.threadIdentifier.name &&
  548. threadIdentifier.index != ThreadIdentifier.kAll)
  549. {
  550. if (!m_ThreadSelection.selection.Contains(threadNameWithIndex))
  551. m_ThreadSelection.selection.Add(threadNameWithIndex);
  552. }
  553. }
  554. }
  555. else
  556. {
  557. var removeSelection = new List<string>();
  558. foreach (string threadNameWithIndex in m_ThreadSelection.selection)
  559. {
  560. var threadIdentifier = new ThreadIdentifier(threadNameWithIndex);
  561. if (threadIdentifier.name == item.threadIdentifier.name &&
  562. threadIdentifier.index != ThreadIdentifier.kAll)
  563. {
  564. removeSelection.Add(threadNameWithIndex);
  565. }
  566. }
  567. foreach (string threadNameWithIndex in removeSelection)
  568. {
  569. m_ThreadSelection.selection.Remove(threadNameWithIndex);
  570. }
  571. }
  572. }
  573. else
  574. {
  575. if (newState)
  576. {
  577. m_ThreadSelection.selection.Add(item.threadIdentifier.threadNameWithIndex);
  578. }
  579. else
  580. {
  581. m_ThreadSelection.selection.Remove(item.threadIdentifier.threadNameWithIndex);
  582. // Turn off any group its in too
  583. var groupIdentifier = new ThreadIdentifier(item.threadIdentifier);
  584. groupIdentifier.SetAll();
  585. m_ThreadSelection.groups.Remove(groupIdentifier.threadNameWithIndex);
  586. // Turn of the 'all' group too
  587. m_ThreadSelection.groups.Remove(m_AllThreadIdentifier.threadNameWithIndex);
  588. }
  589. }
  590. StateChanged = true;
  591. // Re-sort
  592. SortIfNeeded(GetRows());
  593. }
  594. break;
  595. }
  596. }
  597. // Misc
  598. //--------
  599. protected override bool CanMultiSelect(TreeViewItem item)
  600. {
  601. return false;
  602. }
  603. struct HeaderData
  604. {
  605. public GUIContent content;
  606. public float width;
  607. public float minWidth;
  608. public bool autoResize;
  609. public bool allowToggleVisibility;
  610. public HeaderData(string name, string tooltip = "", float _width = 50, float _minWidth = 30, bool _autoResize = true, bool _allowToggleVisibility = true)
  611. {
  612. content = new GUIContent(name, tooltip);
  613. width = _width;
  614. minWidth = _minWidth;
  615. autoResize = _autoResize;
  616. allowToggleVisibility = _allowToggleVisibility;
  617. }
  618. }
  619. public static MultiColumnHeaderState CreateDefaultMultiColumnHeaderState(float treeViewWidth)
  620. {
  621. var columnList = new List<MultiColumnHeaderState.Column>();
  622. HeaderData[] headerData = new HeaderData[]
  623. {
  624. new HeaderData("Group", "Thread Group", 200, 100, true, false),
  625. new HeaderData("Thread", "Thread Name", 350, 100, true, false),
  626. new HeaderData("Show", "Check to show this thread in the analysis views", 40, 100, false, false),
  627. };
  628. foreach (var header in headerData)
  629. {
  630. columnList.Add(new MultiColumnHeaderState.Column
  631. {
  632. headerContent = header.content,
  633. headerTextAlignment = TextAlignment.Left,
  634. sortedAscending = true,
  635. sortingArrowAlignment = TextAlignment.Left,
  636. width = header.width,
  637. minWidth = header.minWidth,
  638. autoResize = header.autoResize,
  639. allowToggleVisibility = header.allowToggleVisibility
  640. });
  641. }
  642. ;
  643. var columns = columnList.ToArray();
  644. Assert.AreEqual(columns.Length, Enum.GetValues(typeof(MyColumns)).Length, "Number of columns should match number of enum values: You probably forgot to update one of them.");
  645. var state = new MultiColumnHeaderState(columns);
  646. state.visibleColumns = new int[]
  647. {
  648. (int)MyColumns.GroupName,
  649. (int)MyColumns.ThreadName,
  650. (int)MyColumns.State,
  651. };
  652. return state;
  653. }
  654. protected override void SelectionChanged(IList<int> selectedIds)
  655. {
  656. base.SelectionChanged(selectedIds);
  657. if (selectedIds.Count > 0)
  658. {
  659. }
  660. }
  661. }
  662. internal class ThreadSelectionWindow : EditorWindow
  663. {
  664. ProfileAnalyzerWindow m_ProfileAnalyzerWindow;
  665. TreeViewState m_ThreadTreeViewState;
  666. //Static to store state between open/close
  667. static MultiColumnHeaderState m_ThreadMulticolumnHeaderState;
  668. ThreadTable m_ThreadTable;
  669. List<string> m_ThreadNames;
  670. List<string> m_ThreadUINames;
  671. ThreadSelection m_OriginalThreadSelection;
  672. bool m_EnableApplyButton = false;
  673. bool m_EnableResetButton = false;
  674. bool m_RequestClose;
  675. internal static class Styles
  676. {
  677. public static readonly GUIContent reset = new GUIContent("Reset", "Reset selection to previous set");
  678. public static readonly GUIContent clear = new GUIContent("Clear", "Clear selection below");
  679. public static readonly GUIContent main = new GUIContent("Main Only", "Select Main Thread only");
  680. public static readonly GUIContent common = new GUIContent("Common Set", "Select Common threads : Main, Render and Jobs");
  681. public static readonly GUIContent apply = new GUIContent("Apply", "");
  682. }
  683. static public bool IsOpen()
  684. {
  685. #if UNITY_2019_3_OR_NEWER
  686. return HasOpenInstances<ThreadSelectionWindow>();
  687. #else
  688. UnityEngine.Object[] windows = Resources.FindObjectsOfTypeAll(typeof(ThreadSelectionWindow));
  689. if (windows != null && windows.Length > 0)
  690. return true;
  691. return false;
  692. #endif // UNITY_2019_3_OR_NEWER
  693. }
  694. static public ThreadSelectionWindow Open(float screenX, float screenY, ProfileAnalyzerWindow profileAnalyzerWindow, ThreadSelection threadSelection, List<string> threadNames, List<string> threadUINames)
  695. {
  696. ThreadSelectionWindow window = GetWindow<ThreadSelectionWindow>(true, "Threads");
  697. window.minSize = new Vector2(380, 200);
  698. window.position = new Rect(screenX, screenY, 500, 500);
  699. window.SetData(profileAnalyzerWindow, threadSelection, threadNames, threadUINames);
  700. window.Show();
  701. return window;
  702. }
  703. void OnEnable()
  704. {
  705. m_RequestClose = false;
  706. }
  707. void CreateTable(ProfileAnalyzerWindow profileAnalyzerWindow, List<string> threadNames, List<string> threadUINames, ThreadSelection threadSelection)
  708. {
  709. if (m_ThreadTreeViewState == null)
  710. m_ThreadTreeViewState = new TreeViewState();
  711. int sortedColumn;
  712. bool sortAscending;
  713. if (m_ThreadMulticolumnHeaderState == null)
  714. {
  715. m_ThreadMulticolumnHeaderState = ThreadTable.CreateDefaultMultiColumnHeaderState(700);
  716. sortedColumn = (int)ThreadTable.MyColumns.GroupName;
  717. sortAscending = true;
  718. }
  719. else
  720. {
  721. // Remember last sort key
  722. sortedColumn = m_ThreadMulticolumnHeaderState.sortedColumnIndex;
  723. sortAscending = m_ThreadMulticolumnHeaderState.columns[sortedColumn].sortedAscending;
  724. }
  725. var multiColumnHeader = new MultiColumnHeader(m_ThreadMulticolumnHeaderState);
  726. multiColumnHeader.SetSorting(sortedColumn, sortAscending);
  727. multiColumnHeader.ResizeToFit();
  728. m_ThreadTable = new ThreadTable(m_ThreadTreeViewState, multiColumnHeader, threadNames, threadUINames, threadSelection);
  729. }
  730. void SetData(ProfileAnalyzerWindow profileAnalyzerWindow, ThreadSelection threadSelection, List<string> threadNames, List<string> threadUINames)
  731. {
  732. m_ProfileAnalyzerWindow = profileAnalyzerWindow;
  733. m_OriginalThreadSelection = threadSelection;
  734. m_ThreadNames = threadNames;
  735. m_ThreadUINames = threadUINames;
  736. CreateTable(profileAnalyzerWindow, threadNames, threadUINames, threadSelection);
  737. }
  738. void OnDestroy()
  739. {
  740. // By design we now no longer apply the thread settings when closing the dialog.
  741. // Apply must be clicked to set them.
  742. // m_ProfileAnalyzerWindow.SetThreadSelection(m_ThreadTable.GetThreadSelection());
  743. }
  744. void OnGUI()
  745. {
  746. EditorGUILayout.BeginVertical(GUILayout.ExpandWidth(true));
  747. GUIStyle style = new GUIStyle(GUI.skin.label);
  748. style.alignment = TextAnchor.MiddleLeft;
  749. GUILayout.Label("Select Thread : ", style);
  750. EditorGUILayout.BeginHorizontal();
  751. bool lastEnabled = GUI.enabled;
  752. GUI.enabled = m_EnableResetButton;
  753. if (GUILayout.Button(Styles.reset, GUILayout.Width(50)))
  754. {
  755. // Reset the thread window contents only
  756. CreateTable(m_ProfileAnalyzerWindow, m_ThreadNames, m_ThreadUINames, m_OriginalThreadSelection);
  757. m_EnableApplyButton = true;
  758. m_EnableResetButton = false;
  759. }
  760. GUI.enabled = lastEnabled;
  761. if (GUILayout.Button(Styles.clear, GUILayout.Width(50)))
  762. {
  763. m_ThreadTable.ClearThreadSelection();
  764. }
  765. if (GUILayout.Button(Styles.main, GUILayout.Width(100)))
  766. {
  767. m_ThreadTable.SelectMain();
  768. }
  769. if (GUILayout.Button(Styles.common, GUILayout.Width(100)))
  770. {
  771. m_ThreadTable.SelectCommon();
  772. }
  773. GUI.enabled = m_EnableApplyButton && !m_ProfileAnalyzerWindow.IsAnalysisRunning();
  774. EditorGUILayout.Space();
  775. if (GUILayout.Button(Styles.apply, GUILayout.Width(50)))
  776. {
  777. m_ProfileAnalyzerWindow.SetThreadSelection(m_ThreadTable.GetThreadSelection());
  778. m_EnableApplyButton = false;
  779. m_EnableResetButton = true;
  780. }
  781. GUI.enabled = lastEnabled;
  782. EditorGUILayout.EndHorizontal();
  783. if (m_ThreadTable != null)
  784. {
  785. Rect r = EditorGUILayout.GetControlRect(GUILayout.ExpandHeight(true));
  786. m_ThreadTable.OnGUI(r);
  787. }
  788. EditorGUILayout.EndVertical();
  789. }
  790. void Update()
  791. {
  792. if (m_ThreadTable != null && m_ThreadTable.StateChanged)
  793. {
  794. m_EnableApplyButton = true;
  795. m_EnableResetButton = true;
  796. }
  797. if (m_RequestClose)
  798. Close();
  799. }
  800. void OnLostFocus()
  801. {
  802. m_RequestClose = true;
  803. }
  804. }
  805. }