No Description
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

RenderPipelineConvertersEditor.cs 39KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Reflection;
  5. using UnityEditor.SceneManagement;
  6. using UnityEngine;
  7. using UnityEditor.Search;
  8. using UnityEditor.UIElements;
  9. using UnityEngine.UIElements;
  10. using UnityEngine.Assertions;
  11. using UnityEngine.Rendering;
  12. using UnityEngine.SceneManagement;
  13. namespace UnityEditor.Rendering.Universal
  14. {
  15. // Status for each row item to say in which state they are in.
  16. // This will make sure they are showing the correct icon
  17. [Serializable]
  18. enum Status
  19. {
  20. Pending,
  21. Warning,
  22. Error,
  23. Success
  24. }
  25. // This is the serialized class that stores the state of each item in the list of items to convert
  26. [Serializable]
  27. class ConverterItemState
  28. {
  29. public bool isActive;
  30. // Message that will be displayed on the icon if warning or failed.
  31. public string message;
  32. // Status of the converted item, Pending, Warning, Error or Success
  33. public Status status;
  34. internal bool hasConverted = false;
  35. }
  36. // Each converter uses the active bool
  37. // Each converter has a list of active items/assets
  38. // We do this so that we can use the binding system of the UI Elements
  39. [Serializable]
  40. class ConverterState
  41. {
  42. // This is the enabled state of the whole converter
  43. public bool isEnabled;
  44. public bool isActive;
  45. public bool isLoading; // to name
  46. public bool isInitialized;
  47. public List<ConverterItemState> items = new List<ConverterItemState>();
  48. public int pending;
  49. public int warnings;
  50. public int errors;
  51. public int success;
  52. internal int index;
  53. public bool isActiveAndEnabled => isEnabled && isActive;
  54. public bool requiresInitialization => !isInitialized && isActiveAndEnabled;
  55. }
  56. [Serializable]
  57. internal struct ConverterItems
  58. {
  59. public List<ConverterItemDescriptor> itemDescriptors;
  60. }
  61. [Serializable]
  62. [EditorWindowTitle(title = "Render Pipeline Converters")]
  63. internal class RenderPipelineConvertersEditor : EditorWindow
  64. {
  65. Tuple<string, Texture2D> converterStateInfoDisabled;
  66. Tuple<string, Texture2D> converterStateInfoPendingInitialization;
  67. Tuple<string, Texture2D> converterStateInfoPendingConversion;
  68. Tuple<string, Texture2D> converterStateInfoPendingConversionWarning;
  69. Tuple<string, Texture2D> converterStateInfoCompleteErrors;
  70. Tuple<string, Texture2D> converterStateInfoComplete;
  71. public VisualTreeAsset converterEditorAsset;
  72. public VisualTreeAsset converterItem;
  73. public VisualTreeAsset converterWidgetMainAsset;
  74. ScrollView m_ScrollView;
  75. VisualElement m_ConverterSelectedVE;
  76. Button m_ConvertButton;
  77. Button m_InitButton;
  78. Button m_InitAnConvertButton;
  79. Button m_ContainerHelpButton;
  80. bool m_InitAndConvert;
  81. List<RenderPipelineConverter> m_CoreConvertersList = new List<RenderPipelineConverter>();
  82. List<VisualElement> m_VEList = new List<VisualElement>();
  83. // This list needs to be as long as the amount of converters
  84. List<ConverterItems> m_ItemsToConvert = new List<ConverterItems>();
  85. //List<List<ConverterItemDescriptor>> m_ItemsToConvert = new List<List<ConverterItemDescriptor>>();
  86. SerializedObject m_SerializedObject;
  87. List<string> m_ContainerChoices = new List<string>();
  88. List<RenderPipelineConverterContainer> m_Containers = new List<RenderPipelineConverterContainer>();
  89. int m_ContainerChoiceIndex = 0;
  90. int m_WorkerCount;
  91. // This is a list of Converter States which holds a list of which converter items/assets are active
  92. // There is one for each Converter.
  93. [SerializeField] List<ConverterState> m_ConverterStates = new List<ConverterState>();
  94. TypeCache.TypeCollection m_ConverterContainers;
  95. RenderPipelineConverterContainer currentContainer => m_Containers[m_ContainerChoiceIndex];
  96. // Name of the index file
  97. string m_URPConverterIndex = "URPConverterIndex";
  98. [MenuItem("Window/Rendering/Render Pipeline Converter", false, 50)]
  99. public static void ShowWindow()
  100. {
  101. RenderPipelineConvertersEditor wnd = GetWindow<RenderPipelineConvertersEditor>();
  102. wnd.titleContent = new GUIContent("Render Pipeline Converter");
  103. DontSaveToLayout(wnd);
  104. wnd.minSize = new Vector2(650f, 400f);
  105. wnd.Show();
  106. }
  107. internal static void DontSaveToLayout(EditorWindow wnd)
  108. {
  109. // Making sure that the window is not saved in layouts.
  110. Assembly assembly = typeof(EditorWindow).Assembly;
  111. var editorWindowType = typeof(EditorWindow);
  112. var hostViewType = assembly.GetType("UnityEditor.HostView");
  113. var containerWindowType = assembly.GetType("UnityEditor.ContainerWindow");
  114. var parentViewField = editorWindowType.GetField("m_Parent", BindingFlags.Instance | BindingFlags.NonPublic);
  115. var parentViewValue = parentViewField.GetValue(wnd);
  116. // window should not be saved to layout
  117. var containerWindowProperty =
  118. hostViewType.GetProperty("window", BindingFlags.Instance | BindingFlags.Public);
  119. var parentContainerWindowValue = containerWindowProperty.GetValue(parentViewValue);
  120. var dontSaveToLayoutField =
  121. containerWindowType.GetField("m_DontSaveToLayout", BindingFlags.Instance | BindingFlags.NonPublic);
  122. dontSaveToLayoutField.SetValue(parentContainerWindowValue, true);
  123. }
  124. void OnEnable()
  125. {
  126. InitIfNeeded();
  127. }
  128. void InitIfNeeded()
  129. {
  130. if (m_CoreConvertersList.Any())
  131. return;
  132. m_CoreConvertersList = new List<RenderPipelineConverter>();
  133. // This is the drop down choices.
  134. m_ConverterContainers = TypeCache.GetTypesDerivedFrom<RenderPipelineConverterContainer>();
  135. foreach (var containerType in m_ConverterContainers)
  136. {
  137. var container = (RenderPipelineConverterContainer)Activator.CreateInstance(containerType);
  138. m_Containers.Add(container);
  139. }
  140. // this need to be sorted by Priority property
  141. m_Containers = m_Containers
  142. .OrderBy(o => o.priority).ToList();
  143. foreach (var container in m_Containers)
  144. {
  145. m_ContainerChoices.Add(container.name);
  146. }
  147. if (m_ConverterContainers.Any())
  148. {
  149. GetConverters();
  150. }
  151. else
  152. {
  153. ClearConverterStates();
  154. }
  155. }
  156. void ClearConverterStates()
  157. {
  158. m_CoreConvertersList.Clear();
  159. m_ConverterStates.Clear();
  160. m_ItemsToConvert.Clear();
  161. m_VEList.Clear();
  162. }
  163. void GetConverters()
  164. {
  165. ClearConverterStates();
  166. var converterList = TypeCache.GetTypesDerivedFrom<RenderPipelineConverter>();
  167. for (int i = 0; i < converterList.Count; ++i)
  168. {
  169. // Iterate over the converters that are used by the current container
  170. RenderPipelineConverter conv = (RenderPipelineConverter)Activator.CreateInstance(converterList[i]);
  171. m_CoreConvertersList.Add(conv);
  172. }
  173. // this need to be sorted by Priority property
  174. m_CoreConvertersList = m_CoreConvertersList
  175. .OrderBy(o => o.priority).ToList();
  176. for (int i = 0; i < m_CoreConvertersList.Count; i++)
  177. {
  178. // Create a new ConvertState which holds the active state of the converter
  179. var converterState = new ConverterState
  180. {
  181. isEnabled = m_CoreConvertersList[i].isEnabled,
  182. isActive = false,
  183. isInitialized = false,
  184. items = new List<ConverterItemState>(),
  185. index = i,
  186. };
  187. m_ConverterStates.Add(converterState);
  188. // This just creates empty entries in the m_ItemsToConvert.
  189. // This list need to have the same amount of entries as the converters
  190. List<ConverterItemDescriptor> converterItemInfos = new List<ConverterItemDescriptor>();
  191. m_ItemsToConvert.Add(new ConverterItems { itemDescriptors = converterItemInfos });
  192. }
  193. }
  194. public void CreateGUI()
  195. {
  196. converterStateInfoDisabled = new("Converter Disabled", null);
  197. converterStateInfoPendingInitialization = new("Pending Initialization", CoreEditorStyles.iconPending);
  198. converterStateInfoPendingConversion = new("Pending Conversion", CoreEditorStyles.iconPending);
  199. converterStateInfoPendingConversionWarning = new("Pending Conversion with Warnings", CoreEditorStyles.iconWarn);
  200. converterStateInfoCompleteErrors = new("Conversion Complete with Errors", CoreEditorStyles.iconFail);
  201. converterStateInfoComplete = new("Conversion Complete", CoreEditorStyles.iconComplete);
  202. string theme = EditorGUIUtility.isProSkin ? "dark" : "light";
  203. InitIfNeeded();
  204. if (m_ConverterContainers.Any())
  205. {
  206. m_SerializedObject = new SerializedObject(this);
  207. converterEditorAsset.CloneTree(rootVisualElement);
  208. rootVisualElement.Q<DropdownField>("conversionsDropDown").choices = m_ContainerChoices;
  209. rootVisualElement.Q<DropdownField>("conversionsDropDown").index = m_ContainerChoiceIndex;
  210. // Getting the scrollview where the converters should be added
  211. m_ScrollView = rootVisualElement.Q<ScrollView>("convertersScrollView");
  212. m_ConvertButton = rootVisualElement.Q<Button>("convertButton");
  213. m_ConvertButton.RegisterCallback<ClickEvent>(Convert);
  214. m_InitButton = rootVisualElement.Q<Button>("initializeButton");
  215. m_InitButton.RegisterCallback<ClickEvent>(InitializeAllActiveConverters);
  216. m_InitAnConvertButton = rootVisualElement.Q<Button>("initializeAndConvert");
  217. m_InitAnConvertButton.RegisterCallback<ClickEvent>(InitializeAndConvert);
  218. m_ContainerHelpButton = rootVisualElement.Q<Button>("containerHelpButton");
  219. m_ContainerHelpButton.RegisterCallback<ClickEvent>(GotoHelpURL);
  220. m_ContainerHelpButton.Q<Image>("containerHelpImage").image = CoreEditorStyles.iconHelp;
  221. m_ContainerHelpButton.RemoveFromClassList("unity-button");
  222. m_ContainerHelpButton.AddToClassList(theme);
  223. RecreateUI();
  224. }
  225. }
  226. void GotoHelpURL(ClickEvent evt)
  227. {
  228. if (DocumentationUtils.TryGetHelpURL(currentContainer.GetType(), out var url))
  229. {
  230. Help.BrowseURL(url);
  231. }
  232. }
  233. void InitOrConvert()
  234. {
  235. bool allSelectedHasInitialized = true;
  236. // Check if all ticked ones have been initialized.
  237. // If not then Init Button should be active
  238. // Get all active converters
  239. if (m_ConverterStates.Any())
  240. {
  241. foreach (ConverterState state in m_ConverterStates)
  242. {
  243. if (state.isActiveAndEnabled && !state.isInitialized)
  244. {
  245. allSelectedHasInitialized = false;
  246. break;
  247. }
  248. }
  249. }
  250. else
  251. {
  252. // If no converters is active.
  253. // we should make the text somewhat disabled
  254. allSelectedHasInitialized = false;
  255. }
  256. if (allSelectedHasInitialized)
  257. {
  258. m_ConvertButton.style.display = DisplayStyle.Flex;
  259. m_InitButton.style.display = DisplayStyle.None;
  260. }
  261. else
  262. {
  263. m_ConvertButton.style.display = DisplayStyle.None;
  264. m_InitButton.style.display = DisplayStyle.Flex;
  265. }
  266. }
  267. void UpdateSelectedConverterItems(int index, VisualElement element)
  268. {
  269. int count = 0;
  270. foreach (ConverterItemState state in m_ConverterStates[index].items)
  271. {
  272. if (state.isActive)
  273. {
  274. count++;
  275. }
  276. }
  277. element.Q<Label>("converterStats").text = $"{count}/{m_ItemsToConvert[index].itemDescriptors.Count} selected";
  278. }
  279. void ShowConverterLayout(VisualElement element)
  280. {
  281. m_ConverterSelectedVE = element;
  282. rootVisualElement.Q<VisualElement>("converterEditorMainVE").style.display = DisplayStyle.None;
  283. rootVisualElement.Q<VisualElement>("singleConverterVE").style.display = DisplayStyle.Flex;
  284. rootVisualElement.Q<VisualElement>("singleConverterVE").Add(element);
  285. element.Q<VisualElement>("converterItems").style.display = DisplayStyle.Flex;
  286. element.Q<VisualElement>("informationVE").style.display = DisplayStyle.Flex;
  287. rootVisualElement.Q<Button>("backButton").RegisterCallback<ClickEvent>(BackToConverters);
  288. }
  289. void HideConverterLayout(VisualElement element)
  290. {
  291. rootVisualElement.Q<VisualElement>("converterEditorMainVE").style.display = DisplayStyle.Flex;
  292. rootVisualElement.Q<VisualElement>("singleConverterVE").style.display = DisplayStyle.None;
  293. rootVisualElement.Q<VisualElement>("singleConverterVE").Remove(element);
  294. element.Q<VisualElement>("converterItems").style.display = DisplayStyle.None;
  295. element.Q<VisualElement>("informationVE").style.display = DisplayStyle.None;
  296. RecreateUI();
  297. m_ConverterSelectedVE = null;
  298. }
  299. void ToggleAllNone(ClickEvent evt, int index, bool value, VisualElement item)
  300. {
  301. void ToggleSelection(Label labelSelected, Label labelNotSelected)
  302. {
  303. labelSelected.AddToClassList("selected");
  304. labelSelected.RemoveFromClassList("not_selected");
  305. labelNotSelected.AddToClassList("not_selected");
  306. labelNotSelected.RemoveFromClassList("selected");
  307. }
  308. var conv = m_ConverterStates[index];
  309. if (conv.items.Count > 0)
  310. {
  311. foreach (var convItem in conv.items)
  312. {
  313. convItem.isActive = value;
  314. }
  315. UpdateSelectedConverterItems(index, item);
  316. var allLabel = item.Q<Label>("all");
  317. var noneLabel = item.Q<Label>("none");
  318. // Changing the look of the labels
  319. if (value)
  320. {
  321. ToggleSelection(allLabel, noneLabel);
  322. }
  323. else
  324. {
  325. ToggleSelection(noneLabel, allLabel);
  326. }
  327. }
  328. }
  329. void ConverterStatusInfo(int index, VisualElement item)
  330. {
  331. Tuple<string, Texture2D> info = converterStateInfoDisabled;;
  332. // Check if it is active
  333. if (m_ConverterStates[index].isActive)
  334. {
  335. info = converterStateInfoPendingInitialization;
  336. }
  337. if (m_ConverterStates[index].isInitialized)
  338. {
  339. info = converterStateInfoPendingConversion;
  340. }
  341. if (m_ConverterStates[index].warnings > 0)
  342. {
  343. info = converterStateInfoPendingConversionWarning;
  344. }
  345. if (m_ConverterStates[index].errors > 0)
  346. {
  347. info = converterStateInfoCompleteErrors;
  348. }
  349. if (m_ConverterStates[index].errors == 0 && m_ConverterStates[index].warnings == 0 && m_ConverterStates[index].success > 0)
  350. {
  351. info = converterStateInfoComplete;
  352. }
  353. if (!m_ConverterStates[index].isActive)
  354. {
  355. info = converterStateInfoDisabled;
  356. }
  357. item.Q<Label>("converterStateInfoL").text = info.Item1;
  358. item.Q<Image>("converterStateInfoIcon").image = info.Item2;
  359. }
  360. void BackToConverters(ClickEvent evt)
  361. {
  362. HideConverterLayout(m_ConverterSelectedVE);
  363. }
  364. void RecreateUI()
  365. {
  366. m_SerializedObject.Update();
  367. // This is temp now to get the information filled in
  368. rootVisualElement.Q<DropdownField>("conversionsDropDown").RegisterCallback<ChangeEvent<string>>((evt) =>
  369. {
  370. m_ContainerChoiceIndex = rootVisualElement.Q<DropdownField>("conversionsDropDown").index;
  371. rootVisualElement.Q<TextElement>("conversionInfo").text = currentContainer.info;
  372. HideUnhideConverters();
  373. });
  374. rootVisualElement.Q<TextElement>("conversionInfo").text = currentContainer.info;
  375. m_ScrollView.Clear();
  376. for (int i = 0; i < m_CoreConvertersList.Count; ++i)
  377. {
  378. // Making an item using the converterListAsset as a template.
  379. // Then adding the information needed for each converter
  380. VisualElement item = new VisualElement();
  381. converterWidgetMainAsset.CloneTree(item);
  382. // Adding the VE so that we can hide it and unhide it when needed
  383. m_VEList.Add(item);
  384. RenderPipelineConverter conv = m_CoreConvertersList[i];
  385. item.name = $"{conv.name}#{conv.container.AssemblyQualifiedName}";
  386. item.SetEnabled(conv.isEnabled);
  387. item.Q<Label>("converterName").text = conv.name;
  388. item.Q<Label>("converterInfo").text = conv.info;
  389. int id = i;
  390. var converterEnabledToggle = item.Q<Toggle>("converterEnabled");
  391. converterEnabledToggle.RegisterCallback<ClickEvent>((evt) =>
  392. {
  393. ConverterStatusInfo(id, item);
  394. InitOrConvert();
  395. // This toggle needs to stop propagation since it is inside another clickable element
  396. evt.StopPropagation();
  397. });
  398. var topElement = item.Q<VisualElement>("converterTopVisualElement");
  399. topElement.RegisterCallback<ClickEvent>((evt) =>
  400. {
  401. ShowConverterLayout(item);
  402. item.Q<VisualElement>("allNoneVE").style.display = DisplayStyle.Flex;
  403. item.Q<Label>("all").RegisterCallback<ClickEvent>(evt =>
  404. {
  405. ToggleAllNone(evt, id, true, item);
  406. });
  407. item.Q<Label>("none").RegisterCallback<ClickEvent>(evt =>
  408. {
  409. ToggleAllNone(evt, id, false, item);
  410. });
  411. });
  412. // setup the images
  413. item.Q<Image>("pendingImage").image = CoreEditorStyles.iconPending;
  414. item.Q<Image>("pendingImage").tooltip = "Pending";
  415. var pendingLabel = item.Q<Label>("pendingLabel");
  416. item.Q<Image>("warningImage").image = CoreEditorStyles.iconWarn;
  417. item.Q<Image>("warningImage").tooltip = "Warnings";
  418. var warningLabel = item.Q<Label>("warningLabel");
  419. item.Q<Image>("errorImage").image = CoreEditorStyles.iconFail;
  420. item.Q<Image>("errorImage").tooltip = "Failed";
  421. var errorLabel = item.Q<Label>("errorLabel");
  422. item.Q<Image>("successImage").image = CoreEditorStyles.iconComplete;
  423. item.Q<Image>("successImage").tooltip = "Success";
  424. var successLabel = item.Q<Label>("successLabel");
  425. converterEnabledToggle.bindingPath =
  426. $"{nameof(m_ConverterStates)}.Array.data[{i}].{nameof(ConverterState.isActive)}";
  427. pendingLabel.bindingPath =
  428. $"{nameof(m_ConverterStates)}.Array.data[{i}].{nameof(ConverterState.pending)}";
  429. warningLabel.bindingPath =
  430. $"{nameof(m_ConverterStates)}.Array.data[{i}].{nameof(ConverterState.warnings)}";
  431. errorLabel.bindingPath =
  432. $"{nameof(m_ConverterStates)}.Array.data[{i}].{nameof(ConverterState.errors)}";
  433. successLabel.bindingPath =
  434. $"{nameof(m_ConverterStates)}.Array.data[{i}].{nameof(ConverterState.success)}";
  435. ConverterStatusInfo(id, item);
  436. VisualElement child = item;
  437. ListView listView = child.Q<ListView>("converterItems");
  438. listView.showBoundCollectionSize = false;
  439. listView.bindingPath = $"{nameof(m_ConverterStates)}.Array.data[{i}].{nameof(ConverterState.items)}";
  440. // Update the amount of things to convert
  441. UpdateSelectedConverterItems(id, child);
  442. listView.makeItem = () =>
  443. {
  444. var convertItem = converterItem.CloneTree();
  445. // Adding the contextual menu for each item
  446. convertItem.AddManipulator(new ContextualMenuManipulator(evt => AddToContextMenu(evt, id)));
  447. return convertItem;
  448. };
  449. listView.bindItem = (element, index) =>
  450. {
  451. m_SerializedObject.Update();
  452. var property = m_SerializedObject.FindProperty($"{listView.bindingPath}.Array.data[{index}]");
  453. // ListView doesn't bind the child elements for us properly, so we do that for it
  454. // In the UXML our root is a BindableElement, as we can't bind otherwise.
  455. var bindable = (BindableElement) element;
  456. bindable.BindProperty(property);
  457. // Adding index here to userData so it can be retrieved later
  458. element.userData = index;
  459. Status status = (Status) property.FindPropertyRelative("status").enumValueIndex;
  460. string info = property.FindPropertyRelative("message").stringValue;
  461. element.Q<Toggle>("converterItemActive").RegisterCallback<ClickEvent>((evt) =>
  462. {
  463. UpdateSelectedConverterItems(id, child);
  464. DeselectAllNoneLabels(item);
  465. });
  466. ConverterItemDescriptor convItemDesc = m_ItemsToConvert[id].itemDescriptors[index];
  467. element.Q<Label>("converterItemName").text = convItemDesc.name;
  468. element.Q<Label>("converterItemPath").text = convItemDesc.info;
  469. if (!string.IsNullOrEmpty(convItemDesc.helpLink))
  470. {
  471. element.Q<Image>("converterItemHelpIcon").image = CoreEditorStyles.iconHelp;
  472. element.Q<Image>("converterItemHelpIcon").tooltip = convItemDesc.helpLink;
  473. }
  474. // Changing the icon here depending on the status.
  475. Texture2D icon = null;
  476. switch (status)
  477. {
  478. case Status.Pending:
  479. icon = CoreEditorStyles.iconPending;
  480. break;
  481. case Status.Error:
  482. icon = CoreEditorStyles.iconFail;
  483. break;
  484. case Status.Warning:
  485. icon = CoreEditorStyles.iconWarn;
  486. break;
  487. case Status.Success:
  488. icon = CoreEditorStyles.iconComplete;
  489. break;
  490. }
  491. element.Q<Image>("converterItemStatusIcon").image = icon;
  492. element.Q<Image>("converterItemStatusIcon").tooltip = info;
  493. };
  494. #if UNITY_2022_2_OR_NEWER
  495. listView.selectionChanged += obj => { m_CoreConvertersList[id].OnClicked(listView.selectedIndex); };
  496. #else
  497. listView.onSelectionChange += obj => { m_CoreConvertersList[id].OnClicked(listView.selectedIndex); };
  498. #endif
  499. listView.unbindItem = (element, index) =>
  500. {
  501. var bindable = (BindableElement)element;
  502. bindable.Unbind();
  503. };
  504. m_ScrollView.Add(item);
  505. }
  506. InitOrConvert();
  507. HideUnhideConverters();
  508. rootVisualElement.Bind(m_SerializedObject);
  509. }
  510. private void HideUnhideConverters()
  511. {
  512. var type = currentContainer.GetType();
  513. if (DocumentationUtils.TryGetHelpURL(type, out var url))
  514. {
  515. m_ContainerHelpButton.style.display = DisplayStyle.Flex;
  516. }
  517. else
  518. {
  519. m_ContainerHelpButton.style.display = DisplayStyle.None;
  520. }
  521. foreach (VisualElement childElement in m_ScrollView.Q<VisualElement>().Children())
  522. {
  523. var container = Type.GetType(childElement.name.Split('#').Last());
  524. if (container == type)
  525. {
  526. childElement.style.display = DisplayStyle.Flex;
  527. }
  528. else
  529. {
  530. childElement.style.display = DisplayStyle.None;
  531. }
  532. }
  533. }
  534. void DeselectAllNoneLabels(VisualElement item)
  535. {
  536. item.Q<Label>("all").AddToClassList("not_selected");
  537. item.Q<Label>("all").RemoveFromClassList("selected");
  538. item.Q<Label>("none").AddToClassList("not_selected");
  539. item.Q<Label>("none").RemoveFromClassList("selected");
  540. }
  541. void GetAndSetData(int i, Action onAllConvertersCompleted = null)
  542. {
  543. // This need to be in Init method
  544. // Need to get the assets that this converter is converting.
  545. // Need to return Name, Path, Initial info, Help link.
  546. // New empty list of ConverterItemInfos
  547. List<ConverterItemDescriptor> converterItemInfos = new List<ConverterItemDescriptor>();
  548. var initCtx = new InitializeConverterContext { items = converterItemInfos };
  549. var conv = m_CoreConvertersList[i];
  550. m_ConverterStates[i].isLoading = true;
  551. // This should also go to the init method
  552. // This will fill out the converter item infos list
  553. int id = i;
  554. conv.OnInitialize(initCtx, OnConverterCompleteDataCollection);
  555. void OnConverterCompleteDataCollection()
  556. {
  557. // Set the item infos list to to the right index
  558. m_ItemsToConvert[id] = new ConverterItems { itemDescriptors = converterItemInfos };
  559. m_ConverterStates[id].items = new List<ConverterItemState>(converterItemInfos.Count);
  560. // Default all the entries to true
  561. for (var j = 0; j < converterItemInfos.Count; j++)
  562. {
  563. string message = string.Empty;
  564. Status status;
  565. bool active = true;
  566. // If this data hasn't been filled in from the init phase then we can assume that there are no issues / warnings
  567. if (string.IsNullOrEmpty(converterItemInfos[j].warningMessage))
  568. {
  569. status = Status.Pending;
  570. }
  571. else
  572. {
  573. status = Status.Warning;
  574. message = converterItemInfos[j].warningMessage;
  575. active = false;
  576. m_ConverterStates[id].warnings++;
  577. }
  578. m_ConverterStates[id].items.Add(new ConverterItemState
  579. {
  580. isActive = active,
  581. message = message,
  582. status = status,
  583. hasConverted = false,
  584. });
  585. }
  586. m_ConverterStates[id].isLoading = false;
  587. m_ConverterStates[id].isInitialized = true;
  588. // Making sure that the pending amount is set to the amount of items needs converting
  589. m_ConverterStates[id].pending = m_ConverterStates[id].items.Count;
  590. EditorUtility.SetDirty(this);
  591. m_SerializedObject.ApplyModifiedProperties();
  592. CheckAllConvertersCompleted();
  593. InitOrConvert();
  594. }
  595. void CheckAllConvertersCompleted()
  596. {
  597. int convertersToInitialize = 0;
  598. int convertersInitialized = 0;
  599. for (var j = 0; j < m_ConverterStates.Count; j++)
  600. {
  601. var converter = m_ConverterStates[j];
  602. // Skip inactive converters
  603. if (!converter.isActiveAndEnabled)
  604. continue;
  605. if (converter.isInitialized)
  606. convertersInitialized++;
  607. else
  608. convertersToInitialize++;
  609. }
  610. var sum = convertersToInitialize + convertersInitialized;
  611. Assert.IsFalse(sum == 0);
  612. // Show our progress so far
  613. EditorUtility.ClearProgressBar();
  614. EditorUtility.DisplayProgressBar($"Initializing converters", $"Initializing converters ({convertersInitialized}/{sum})...", (float)convertersInitialized / sum);
  615. // If all converters are initialized call the complete callback
  616. if (convertersToInitialize == 0)
  617. {
  618. onAllConvertersCompleted?.Invoke();
  619. }
  620. }
  621. }
  622. void InitializeAndConvert(ClickEvent evt)
  623. {
  624. m_InitAndConvert = ShouldCreateSearchIndex();
  625. InitializeAllActiveConverters(evt);
  626. if (!m_InitAndConvert)
  627. {
  628. Convert(evt);
  629. }
  630. }
  631. void InitializeAllActiveConverters(ClickEvent evt)
  632. {
  633. if (!SaveCurrentSceneAndContinue()) return;
  634. // If we use search index, go async
  635. if (ShouldCreateSearchIndex())
  636. {
  637. // Save the current worker count. So it can be reset after the index file has been created.
  638. m_WorkerCount = AssetDatabase.DesiredWorkerCount;
  639. AssetDatabase.ForceToDesiredWorkerCount();
  640. AssetDatabase.DesiredWorkerCount = System.Convert.ToInt32(Math.Ceiling(Environment.ProcessorCount * 0.8));
  641. CreateSearchIndex(m_URPConverterIndex);
  642. }
  643. // Otherwise do everything directly
  644. else
  645. {
  646. ConverterCollectData(() => { EditorUtility.ClearProgressBar(); });
  647. }
  648. void CreateSearchIndex(string name)
  649. {
  650. // Create <guid>.index in the project
  651. var title = $"Building {name} search index";
  652. EditorUtility.DisplayProgressBar(title, "Creating search index...", -1f);
  653. Search.SearchService.CreateIndex(name, IndexingOptions.Temporary | IndexingOptions.Extended,
  654. new[] { "Assets" },
  655. new[] { ".prefab", ".unity", ".asset" },
  656. null, OnSearchIndexCreated);
  657. }
  658. void OnSearchIndexCreated(string name, string path, Action onComplete)
  659. {
  660. EditorUtility.ClearProgressBar();
  661. ConverterCollectData(() =>
  662. {
  663. if (m_InitAndConvert)
  664. {
  665. Convert(null);
  666. m_InitAndConvert = false;
  667. }
  668. EditorUtility.ClearProgressBar();
  669. AssetDatabase.DesiredWorkerCount = m_WorkerCount;
  670. AssetDatabase.ForceToDesiredWorkerCount();
  671. RecreateUI();
  672. onComplete();
  673. });
  674. }
  675. void ConverterCollectData(Action onConverterDataCollectionComplete)
  676. {
  677. EditorUtility.DisplayProgressBar($"Initializing converters", $"Initializing converters...", -1f);
  678. int convertersToConvert = 0;
  679. for (int i = 0; i < m_ConverterStates.Count; ++i)
  680. {
  681. if (m_ConverterStates[i].requiresInitialization)
  682. {
  683. convertersToConvert++;
  684. GetAndSetData(i, onConverterDataCollectionComplete);
  685. }
  686. }
  687. // If we did not kick off any converter initialization
  688. // We can complete everything immediately
  689. if (convertersToConvert == 0)
  690. {
  691. onConverterDataCollectionComplete?.Invoke();
  692. }
  693. }
  694. RecreateUI();
  695. }
  696. private bool SaveCurrentSceneAndContinue()
  697. {
  698. Scene currentScene = SceneManager.GetActiveScene();
  699. if (currentScene.isDirty)
  700. {
  701. if (EditorUtility.DisplayDialog("Scene is not saved.",
  702. "Current scene is not saved. Please save the scene before continuing.", "Save and Continue",
  703. "Cancel"))
  704. {
  705. EditorSceneManager.SaveScene(currentScene);
  706. }
  707. else
  708. {
  709. return false;
  710. }
  711. }
  712. return true;
  713. }
  714. bool ShouldCreateSearchIndex()
  715. {
  716. for (int i = 0; i < m_ConverterStates.Count; ++i)
  717. {
  718. if (m_ConverterStates[i].requiresInitialization)
  719. {
  720. var converter = m_CoreConvertersList[i];
  721. if (converter.needsIndexing)
  722. {
  723. return true;
  724. }
  725. }
  726. }
  727. return false;
  728. }
  729. void AddToContextMenu(ContextualMenuPopulateEvent evt, int coreConverterIndex)
  730. {
  731. var ve = (VisualElement)evt.target;
  732. // Checking if this context menu should be enabled or not
  733. var isActive = m_ConverterStates[coreConverterIndex].items[(int)ve.userData].isActive &&
  734. !m_ConverterStates[coreConverterIndex].items[(int)ve.userData].hasConverted;
  735. evt.menu.AppendAction("Run converter for this asset",
  736. e =>
  737. {
  738. ConvertIndex(coreConverterIndex, (int)ve.userData);
  739. // Refreshing the list to show the new state
  740. m_ConverterSelectedVE.Q<ListView>("converterItems").Rebuild();
  741. },
  742. isActive ? DropdownMenuAction.AlwaysEnabled : DropdownMenuAction.AlwaysDisabled);
  743. }
  744. void UpdateInfo(int stateIndex, RunItemContext ctx)
  745. {
  746. if (ctx.didFail)
  747. {
  748. m_ConverterStates[stateIndex].items[ctx.item.index].message = ctx.info;
  749. m_ConverterStates[stateIndex].items[ctx.item.index].status = Status.Error;
  750. m_ConverterStates[stateIndex].errors++;
  751. }
  752. else
  753. {
  754. m_ConverterStates[stateIndex].items[ctx.item.index].status = Status.Success;
  755. m_ConverterStates[stateIndex].success++;
  756. }
  757. m_ConverterStates[stateIndex].pending--;
  758. // Making sure that this is set here so that if user is clicking Convert again it will not run again.
  759. ctx.hasConverted = true;
  760. VisualElement child = m_ScrollView[stateIndex];
  761. child.Q<ListView>("converterItems").Rebuild();
  762. }
  763. void Convert(ClickEvent evt)
  764. {
  765. // Ask to save save the current open scene and after the conversion is done reload the same scene.
  766. if (!SaveCurrentSceneAndContinue()) return;
  767. string currentScenePath = SceneManager.GetActiveScene().path;
  768. List<ConverterState> activeConverterStates = new List<ConverterState>();
  769. // Getting all the active converters to use in the cancelable progressbar
  770. foreach (ConverterState state in m_ConverterStates)
  771. {
  772. if (state.isActive && state.isInitialized)
  773. {
  774. activeConverterStates.Add(state);
  775. }
  776. }
  777. int currentCount = 0;
  778. int activeConvertersCount = activeConverterStates.Count;
  779. foreach (ConverterState activeConverterState in activeConverterStates)
  780. {
  781. currentCount++;
  782. var index = activeConverterState.index;
  783. m_CoreConvertersList[index].OnPreRun();
  784. var converterName = m_CoreConvertersList[index].name;
  785. var itemCount = m_ItemsToConvert[index].itemDescriptors.Count;
  786. string progressTitle = $"{converterName} Converter : {currentCount}/{activeConvertersCount}";
  787. for (var j = 0; j < itemCount; j++)
  788. {
  789. if (activeConverterState.items[j].isActive)
  790. {
  791. if (EditorUtility.DisplayCancelableProgressBar(progressTitle,
  792. string.Format("({0} of {1}) {2}", j, itemCount, m_ItemsToConvert[index].itemDescriptors[j].info),
  793. (float)j / (float)itemCount))
  794. break;
  795. ConvertIndex(index, j);
  796. }
  797. }
  798. m_CoreConvertersList[index].OnPostRun();
  799. AssetDatabase.SaveAssets();
  800. EditorUtility.ClearProgressBar();
  801. }
  802. // Checking if we have changed current scene. If we have we reload the old scene we started from
  803. if (!string.IsNullOrEmpty(currentScenePath) && currentScenePath != SceneManager.GetActiveScene().path)
  804. {
  805. EditorSceneManager.OpenScene(currentScenePath);
  806. }
  807. RecreateUI();
  808. }
  809. void ConvertIndex(int coreConverterIndex, int index)
  810. {
  811. if (!m_ConverterStates[coreConverterIndex].items[index].hasConverted)
  812. {
  813. m_ConverterStates[coreConverterIndex].items[index].hasConverted = true;
  814. var item = new ConverterItemInfo()
  815. {
  816. index = index,
  817. descriptor = m_ItemsToConvert[coreConverterIndex].itemDescriptors[index],
  818. };
  819. var ctx = new RunItemContext(item);
  820. m_CoreConvertersList[coreConverterIndex].OnRun(ref ctx);
  821. UpdateInfo(coreConverterIndex, ctx);
  822. }
  823. }
  824. }
  825. }