using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using UnityEditor.SceneManagement; using UnityEngine; using UnityEditor.Search; using UnityEditor.UIElements; using UnityEngine.UIElements; using UnityEngine.Assertions; using UnityEngine.Rendering; using UnityEngine.SceneManagement; using UnityEditor.Rendering.Analytics; namespace UnityEditor.Rendering.Universal { // Status for each row item to say in which state they are in. // This will make sure they are showing the correct icon [Serializable] enum Status { Pending, Warning, Error, Success } // This is the serialized class that stores the state of each item in the list of items to convert [Serializable] class ConverterItemState { public bool isActive; // Message that will be displayed on the icon if warning or failed. public string message; // Status of the converted item, Pending, Warning, Error or Success public Status status; internal bool hasConverted = false; } // Each converter uses the active bool // Each converter has a list of active items/assets // We do this so that we can use the binding system of the UI Elements [Serializable] class ConverterState { // This is the enabled state of the whole converter public bool isEnabled; public bool isActive; public bool isLoading; // to name public bool isInitialized; public List<ConverterItemState> items = new List<ConverterItemState>(); public int pending; public int warnings; public int errors; public int success; internal int index; public bool isActiveAndEnabled => isEnabled && isActive; public bool requiresInitialization => !isInitialized && isActiveAndEnabled; } [Serializable] internal struct ConverterItems { public List<ConverterItemDescriptor> itemDescriptors; } [Serializable] [EditorWindowTitle(title = "Render Pipeline Converters")] internal class RenderPipelineConvertersEditor : EditorWindow { Tuple<string, Texture2D> converterStateInfoDisabled; Tuple<string, Texture2D> converterStateInfoPendingInitialization; Tuple<string, Texture2D> converterStateInfoPendingConversion; Tuple<string, Texture2D> converterStateInfoPendingConversionWarning; Tuple<string, Texture2D> converterStateInfoCompleteErrors; Tuple<string, Texture2D> converterStateInfoComplete; public VisualTreeAsset converterEditorAsset; public VisualTreeAsset converterItem; public VisualTreeAsset converterWidgetMainAsset; ScrollView m_ScrollView; VisualElement m_ConverterSelectedVE; Button m_ConvertButton; Button m_InitButton; Button m_InitAnConvertButton; Button m_ContainerHelpButton; bool m_InitAndConvert; List<RenderPipelineConverter> m_CoreConvertersList = new List<RenderPipelineConverter>(); List<VisualElement> m_VEList = new List<VisualElement>(); // This list needs to be as long as the amount of converters List<ConverterItems> m_ItemsToConvert = new List<ConverterItems>(); //List<List<ConverterItemDescriptor>> m_ItemsToConvert = new List<List<ConverterItemDescriptor>>(); SerializedObject m_SerializedObject; List<string> m_ContainerChoices = new List<string>(); List<RenderPipelineConverterContainer> m_Containers = new List<RenderPipelineConverterContainer>(); int m_ContainerChoiceIndex = 0; int m_WorkerCount; // This is a list of Converter States which holds a list of which converter items/assets are active // There is one for each Converter. [SerializeField] List<ConverterState> m_ConverterStates = new List<ConverterState>(); TypeCache.TypeCollection m_ConverterContainers; RenderPipelineConverterContainer currentContainer => m_Containers[m_ContainerChoiceIndex]; // Name of the index file string m_URPConverterIndex = "URPConverterIndex"; [MenuItem("Window/Rendering/Render Pipeline Converter", false, 50)] public static void ShowWindow() { RenderPipelineConvertersEditor wnd = GetWindow<RenderPipelineConvertersEditor>(); wnd.titleContent = new GUIContent("Render Pipeline Converter"); DontSaveToLayout(wnd); wnd.minSize = new Vector2(650f, 400f); wnd.Show(); } internal static void DontSaveToLayout(EditorWindow wnd) { // Making sure that the window is not saved in layouts. Assembly assembly = typeof(EditorWindow).Assembly; var editorWindowType = typeof(EditorWindow); var hostViewType = assembly.GetType("UnityEditor.HostView"); var containerWindowType = assembly.GetType("UnityEditor.ContainerWindow"); var parentViewField = editorWindowType.GetField("m_Parent", BindingFlags.Instance | BindingFlags.NonPublic); var parentViewValue = parentViewField.GetValue(wnd); // window should not be saved to layout var containerWindowProperty = hostViewType.GetProperty("window", BindingFlags.Instance | BindingFlags.Public); var parentContainerWindowValue = containerWindowProperty.GetValue(parentViewValue); var dontSaveToLayoutField = containerWindowType.GetField("m_DontSaveToLayout", BindingFlags.Instance | BindingFlags.NonPublic); dontSaveToLayoutField.SetValue(parentContainerWindowValue, true); } void OnEnable() { InitIfNeeded(); GraphicsToolLifetimeAnalytic.WindowOpened<RenderPipelineConvertersEditor>(); } private void OnDisable() { GraphicsToolLifetimeAnalytic.WindowClosed<RenderPipelineConvertersEditor>(); } void InitIfNeeded() { if (m_CoreConvertersList.Any()) return; m_CoreConvertersList = new List<RenderPipelineConverter>(); // This is the drop down choices. m_ConverterContainers = TypeCache.GetTypesDerivedFrom<RenderPipelineConverterContainer>(); foreach (var containerType in m_ConverterContainers) { var container = (RenderPipelineConverterContainer)Activator.CreateInstance(containerType); m_Containers.Add(container); } // this need to be sorted by Priority property m_Containers = m_Containers .OrderBy(o => o.priority).ToList(); foreach (var container in m_Containers) { m_ContainerChoices.Add(container.name); } if (m_ConverterContainers.Any()) { GetConverters(); } else { ClearConverterStates(); } } void ClearConverterStates() { m_CoreConvertersList.Clear(); m_ConverterStates.Clear(); m_ItemsToConvert.Clear(); m_VEList.Clear(); } void GetConverters() { ClearConverterStates(); var converterList = TypeCache.GetTypesDerivedFrom<RenderPipelineConverter>(); for (int i = 0; i < converterList.Count; ++i) { // Iterate over the converters that are used by the current container RenderPipelineConverter conv = (RenderPipelineConverter)Activator.CreateInstance(converterList[i]); m_CoreConvertersList.Add(conv); } // this need to be sorted by Priority property m_CoreConvertersList = m_CoreConvertersList .OrderBy(o => o.priority).ToList(); for (int i = 0; i < m_CoreConvertersList.Count; i++) { // Create a new ConvertState which holds the active state of the converter var converterState = new ConverterState { isEnabled = m_CoreConvertersList[i].isEnabled, isActive = false, isInitialized = false, items = new List<ConverterItemState>(), index = i, }; m_ConverterStates.Add(converterState); // This just creates empty entries in the m_ItemsToConvert. // This list need to have the same amount of entries as the converters List<ConverterItemDescriptor> converterItemInfos = new List<ConverterItemDescriptor>(); m_ItemsToConvert.Add(new ConverterItems { itemDescriptors = converterItemInfos }); } } public void CreateGUI() { converterStateInfoDisabled = new("Converter Disabled", null); converterStateInfoPendingInitialization = new("Pending Initialization", CoreEditorStyles.iconPending); converterStateInfoPendingConversion = new("Pending Conversion", CoreEditorStyles.iconPending); converterStateInfoPendingConversionWarning = new("Pending Conversion with Warnings", CoreEditorStyles.iconWarn); converterStateInfoCompleteErrors = new("Conversion Complete with Errors", CoreEditorStyles.iconFail); converterStateInfoComplete = new("Conversion Complete", CoreEditorStyles.iconComplete); string theme = EditorGUIUtility.isProSkin ? "dark" : "light"; InitIfNeeded(); if (m_ConverterContainers.Any()) { m_SerializedObject = new SerializedObject(this); converterEditorAsset.CloneTree(rootVisualElement); rootVisualElement.Q<DropdownField>("conversionsDropDown").choices = m_ContainerChoices; rootVisualElement.Q<DropdownField>("conversionsDropDown").index = m_ContainerChoiceIndex; // Getting the scrollview where the converters should be added m_ScrollView = rootVisualElement.Q<ScrollView>("convertersScrollView"); m_ConvertButton = rootVisualElement.Q<Button>("convertButton"); m_ConvertButton.RegisterCallback<ClickEvent>(Convert); m_InitButton = rootVisualElement.Q<Button>("initializeButton"); m_InitButton.RegisterCallback<ClickEvent>(InitializeAllActiveConverters); m_InitAnConvertButton = rootVisualElement.Q<Button>("initializeAndConvert"); m_InitAnConvertButton.RegisterCallback<ClickEvent>(InitializeAndConvert); m_ContainerHelpButton = rootVisualElement.Q<Button>("containerHelpButton"); m_ContainerHelpButton.RegisterCallback<ClickEvent>(GotoHelpURL); m_ContainerHelpButton.Q<Image>("containerHelpImage").image = CoreEditorStyles.iconHelp; m_ContainerHelpButton.RemoveFromClassList("unity-button"); m_ContainerHelpButton.AddToClassList(theme); RecreateUI(); } } void GotoHelpURL(ClickEvent evt) { if (DocumentationUtils.TryGetHelpURL(currentContainer.GetType(), out var url)) { Help.BrowseURL(url); } } void InitOrConvert() { bool allSelectedHasInitialized = true; // Check if all ticked ones have been initialized. // If not then Init Button should be active // Get all active converters if (m_ConverterStates.Any()) { foreach (ConverterState state in m_ConverterStates) { if (state.isActiveAndEnabled && !state.isInitialized) { allSelectedHasInitialized = false; break; } } } else { // If no converters is active. // we should make the text somewhat disabled allSelectedHasInitialized = false; } if (allSelectedHasInitialized) { m_ConvertButton.style.display = DisplayStyle.Flex; m_InitButton.style.display = DisplayStyle.None; } else { m_ConvertButton.style.display = DisplayStyle.None; m_InitButton.style.display = DisplayStyle.Flex; } } void UpdateSelectedConverterItems(int index, VisualElement element) { int count = 0; foreach (ConverterItemState state in m_ConverterStates[index].items) { if (state.isActive) { count++; } } element.Q<Label>("converterStats").text = $"{count}/{m_ItemsToConvert[index].itemDescriptors.Count} selected"; } void ShowConverterLayout(VisualElement element) { m_ConverterSelectedVE = element; rootVisualElement.Q<VisualElement>("converterEditorMainVE").style.display = DisplayStyle.None; rootVisualElement.Q<VisualElement>("singleConverterVE").style.display = DisplayStyle.Flex; rootVisualElement.Q<VisualElement>("singleConverterVE").Add(element); element.Q<VisualElement>("converterItems").style.display = DisplayStyle.Flex; element.Q<VisualElement>("informationVE").style.display = DisplayStyle.Flex; rootVisualElement.Q<Button>("backButton").RegisterCallback<ClickEvent>(BackToConverters); } void HideConverterLayout(VisualElement element) { rootVisualElement.Q<VisualElement>("converterEditorMainVE").style.display = DisplayStyle.Flex; rootVisualElement.Q<VisualElement>("singleConverterVE").style.display = DisplayStyle.None; rootVisualElement.Q<VisualElement>("singleConverterVE").Remove(element); element.Q<VisualElement>("converterItems").style.display = DisplayStyle.None; element.Q<VisualElement>("informationVE").style.display = DisplayStyle.None; RecreateUI(); m_ConverterSelectedVE = null; } void ToggleAllNone(ClickEvent evt, int index, bool value, VisualElement item) { void ToggleSelection(Label labelSelected, Label labelNotSelected) { labelSelected.AddToClassList("selected"); labelSelected.RemoveFromClassList("not_selected"); labelNotSelected.AddToClassList("not_selected"); labelNotSelected.RemoveFromClassList("selected"); } var conv = m_ConverterStates[index]; if (conv.items.Count > 0) { foreach (var convItem in conv.items) { convItem.isActive = value; } UpdateSelectedConverterItems(index, item); var allLabel = item.Q<Label>("all"); var noneLabel = item.Q<Label>("none"); // Changing the look of the labels if (value) { ToggleSelection(allLabel, noneLabel); } else { ToggleSelection(noneLabel, allLabel); } } } void ConverterStatusInfo(int index, VisualElement item) { Tuple<string, Texture2D> info = converterStateInfoDisabled;; // Check if it is active if (m_ConverterStates[index].isActive) { info = converterStateInfoPendingInitialization; } if (m_ConverterStates[index].isInitialized) { info = converterStateInfoPendingConversion; } if (m_ConverterStates[index].warnings > 0) { info = converterStateInfoPendingConversionWarning; } if (m_ConverterStates[index].errors > 0) { info = converterStateInfoCompleteErrors; } if (m_ConverterStates[index].errors == 0 && m_ConverterStates[index].warnings == 0 && m_ConverterStates[index].success > 0) { info = converterStateInfoComplete; } if (!m_ConverterStates[index].isActive) { info = converterStateInfoDisabled; } item.Q<Label>("converterStateInfoL").text = info.Item1; item.Q<Image>("converterStateInfoIcon").image = info.Item2; } void BackToConverters(ClickEvent evt) { HideConverterLayout(m_ConverterSelectedVE); } void RecreateUI() { m_SerializedObject.Update(); // This is temp now to get the information filled in rootVisualElement.Q<DropdownField>("conversionsDropDown").RegisterCallback<ChangeEvent<string>>((evt) => { m_ContainerChoiceIndex = rootVisualElement.Q<DropdownField>("conversionsDropDown").index; rootVisualElement.Q<TextElement>("conversionInfo").text = currentContainer.info; HideUnhideConverters(); }); rootVisualElement.Q<TextElement>("conversionInfo").text = currentContainer.info; m_ScrollView.Clear(); for (int i = 0; i < m_CoreConvertersList.Count; ++i) { // Making an item using the converterListAsset as a template. // Then adding the information needed for each converter VisualElement item = new VisualElement(); converterWidgetMainAsset.CloneTree(item); // Adding the VE so that we can hide it and unhide it when needed m_VEList.Add(item); RenderPipelineConverter conv = m_CoreConvertersList[i]; item.name = $"{conv.name}#{conv.container.AssemblyQualifiedName}"; item.SetEnabled(conv.isEnabled); item.Q<Label>("converterName").text = conv.name; item.Q<Label>("converterInfo").text = conv.info; int id = i; var converterEnabledToggle = item.Q<Toggle>("converterEnabled"); converterEnabledToggle.RegisterCallback<ClickEvent>((evt) => { ConverterStatusInfo(id, item); InitOrConvert(); // This toggle needs to stop propagation since it is inside another clickable element evt.StopPropagation(); }); var topElement = item.Q<VisualElement>("converterTopVisualElement"); topElement.RegisterCallback<ClickEvent>((evt) => { ShowConverterLayout(item); item.Q<VisualElement>("allNoneVE").style.display = DisplayStyle.Flex; item.Q<Label>("all").RegisterCallback<ClickEvent>(evt => { ToggleAllNone(evt, id, true, item); }); item.Q<Label>("none").RegisterCallback<ClickEvent>(evt => { ToggleAllNone(evt, id, false, item); }); }); // setup the images item.Q<Image>("pendingImage").image = CoreEditorStyles.iconPending; item.Q<Image>("pendingImage").tooltip = "Pending"; var pendingLabel = item.Q<Label>("pendingLabel"); item.Q<Image>("warningImage").image = CoreEditorStyles.iconWarn; item.Q<Image>("warningImage").tooltip = "Warnings"; var warningLabel = item.Q<Label>("warningLabel"); item.Q<Image>("errorImage").image = CoreEditorStyles.iconFail; item.Q<Image>("errorImage").tooltip = "Failed"; var errorLabel = item.Q<Label>("errorLabel"); item.Q<Image>("successImage").image = CoreEditorStyles.iconComplete; item.Q<Image>("successImage").tooltip = "Success"; var successLabel = item.Q<Label>("successLabel"); converterEnabledToggle.bindingPath = $"{nameof(m_ConverterStates)}.Array.data[{i}].{nameof(ConverterState.isActive)}"; pendingLabel.bindingPath = $"{nameof(m_ConverterStates)}.Array.data[{i}].{nameof(ConverterState.pending)}"; warningLabel.bindingPath = $"{nameof(m_ConverterStates)}.Array.data[{i}].{nameof(ConverterState.warnings)}"; errorLabel.bindingPath = $"{nameof(m_ConverterStates)}.Array.data[{i}].{nameof(ConverterState.errors)}"; successLabel.bindingPath = $"{nameof(m_ConverterStates)}.Array.data[{i}].{nameof(ConverterState.success)}"; ConverterStatusInfo(id, item); VisualElement child = item; ListView listView = child.Q<ListView>("converterItems"); listView.showBoundCollectionSize = false; listView.bindingPath = $"{nameof(m_ConverterStates)}.Array.data[{i}].{nameof(ConverterState.items)}"; // Update the amount of things to convert UpdateSelectedConverterItems(id, child); listView.makeItem = () => { var convertItem = converterItem.CloneTree(); // Adding the contextual menu for each item convertItem.AddManipulator(new ContextualMenuManipulator(evt => AddToContextMenu(evt, id))); return convertItem; }; listView.bindItem = (element, index) => { m_SerializedObject.Update(); var property = m_SerializedObject.FindProperty($"{listView.bindingPath}.Array.data[{index}]"); // ListView doesn't bind the child elements for us properly, so we do that for it // In the UXML our root is a BindableElement, as we can't bind otherwise. var bindable = (BindableElement) element; bindable.BindProperty(property); // Adding index here to userData so it can be retrieved later element.userData = index; Status status = (Status) property.FindPropertyRelative("status").enumValueIndex; string info = property.FindPropertyRelative("message").stringValue; element.Q<Toggle>("converterItemActive").RegisterCallback<ClickEvent>((evt) => { UpdateSelectedConverterItems(id, child); DeselectAllNoneLabels(item); }); ConverterItemDescriptor convItemDesc = m_ItemsToConvert[id].itemDescriptors[index]; element.Q<Label>("converterItemName").text = convItemDesc.name; element.Q<Label>("converterItemPath").text = convItemDesc.info; if (!string.IsNullOrEmpty(convItemDesc.helpLink)) { element.Q<Image>("converterItemHelpIcon").image = CoreEditorStyles.iconHelp; element.Q<Image>("converterItemHelpIcon").tooltip = convItemDesc.helpLink; } // Changing the icon here depending on the status. Texture2D icon = null; switch (status) { case Status.Pending: icon = CoreEditorStyles.iconPending; break; case Status.Error: icon = CoreEditorStyles.iconFail; break; case Status.Warning: icon = CoreEditorStyles.iconWarn; break; case Status.Success: icon = CoreEditorStyles.iconComplete; break; } element.Q<Image>("converterItemStatusIcon").image = icon; element.Q<Image>("converterItemStatusIcon").tooltip = info; }; #if UNITY_2022_2_OR_NEWER listView.selectionChanged += obj => { m_CoreConvertersList[id].OnClicked(listView.selectedIndex); }; #else listView.onSelectionChange += obj => { m_CoreConvertersList[id].OnClicked(listView.selectedIndex); }; #endif listView.unbindItem = (element, index) => { var bindable = (BindableElement)element; bindable.Unbind(); }; m_ScrollView.Add(item); } InitOrConvert(); HideUnhideConverters(); rootVisualElement.Bind(m_SerializedObject); } private void HideUnhideConverters() { var type = currentContainer.GetType(); if (DocumentationUtils.TryGetHelpURL(type, out var url)) { m_ContainerHelpButton.style.display = DisplayStyle.Flex; } else { m_ContainerHelpButton.style.display = DisplayStyle.None; } foreach (VisualElement childElement in m_ScrollView.Q<VisualElement>().Children()) { var container = Type.GetType(childElement.name.Split('#').Last()); if (container == type) { childElement.style.display = DisplayStyle.Flex; } else { childElement.style.display = DisplayStyle.None; } } } void DeselectAllNoneLabels(VisualElement item) { item.Q<Label>("all").AddToClassList("not_selected"); item.Q<Label>("all").RemoveFromClassList("selected"); item.Q<Label>("none").AddToClassList("not_selected"); item.Q<Label>("none").RemoveFromClassList("selected"); } void GetAndSetData(int i, Action onAllConvertersCompleted = null) { // This need to be in Init method // Need to get the assets that this converter is converting. // Need to return Name, Path, Initial info, Help link. // New empty list of ConverterItemInfos List<ConverterItemDescriptor> converterItemInfos = new List<ConverterItemDescriptor>(); var initCtx = new InitializeConverterContext { items = converterItemInfos }; var conv = m_CoreConvertersList[i]; m_ConverterStates[i].isLoading = true; // This should also go to the init method // This will fill out the converter item infos list int id = i; conv.OnInitialize(initCtx, OnConverterCompleteDataCollection); void OnConverterCompleteDataCollection() { // Set the item infos list to to the right index m_ItemsToConvert[id] = new ConverterItems { itemDescriptors = converterItemInfos }; m_ConverterStates[id].items = new List<ConverterItemState>(converterItemInfos.Count); // Default all the entries to true for (var j = 0; j < converterItemInfos.Count; j++) { string message = string.Empty; Status status; bool active = true; // If this data hasn't been filled in from the init phase then we can assume that there are no issues / warnings if (string.IsNullOrEmpty(converterItemInfos[j].warningMessage)) { status = Status.Pending; } else { status = Status.Warning; message = converterItemInfos[j].warningMessage; active = false; m_ConverterStates[id].warnings++; } m_ConverterStates[id].items.Add(new ConverterItemState { isActive = active, message = message, status = status, hasConverted = false, }); } m_ConverterStates[id].isLoading = false; m_ConverterStates[id].isInitialized = true; // Making sure that the pending amount is set to the amount of items needs converting m_ConverterStates[id].pending = m_ConverterStates[id].items.Count; EditorUtility.SetDirty(this); m_SerializedObject.ApplyModifiedProperties(); CheckAllConvertersCompleted(); InitOrConvert(); } void CheckAllConvertersCompleted() { int convertersToInitialize = 0; int convertersInitialized = 0; for (var j = 0; j < m_ConverterStates.Count; j++) { var converter = m_ConverterStates[j]; // Skip inactive converters if (!converter.isActiveAndEnabled) continue; if (converter.isInitialized) convertersInitialized++; else convertersToInitialize++; } var sum = convertersToInitialize + convertersInitialized; Assert.IsFalse(sum == 0); // Show our progress so far EditorUtility.ClearProgressBar(); EditorUtility.DisplayProgressBar($"Initializing converters", $"Initializing converters ({convertersInitialized}/{sum})...", (float)convertersInitialized / sum); // If all converters are initialized call the complete callback if (convertersToInitialize == 0) { onAllConvertersCompleted?.Invoke(); } } } void InitializeAndConvert(ClickEvent evt) { m_InitAndConvert = ShouldCreateSearchIndex(); InitializeAllActiveConverters(evt); if (!m_InitAndConvert) { Convert(evt); } } void InitializeAllActiveConverters(ClickEvent evt) { if (!SaveCurrentSceneAndContinue()) return; // If we use search index, go async if (ShouldCreateSearchIndex()) { // Save the current worker count. So it can be reset after the index file has been created. m_WorkerCount = AssetDatabase.DesiredWorkerCount; AssetDatabase.ForceToDesiredWorkerCount(); AssetDatabase.DesiredWorkerCount = System.Convert.ToInt32(Math.Ceiling(Environment.ProcessorCount * 0.8)); CreateSearchIndex(m_URPConverterIndex); } // Otherwise do everything directly else { ConverterCollectData(() => { EditorUtility.ClearProgressBar(); }); } void CreateSearchIndex(string name) { // Create <guid>.index in the project var title = $"Building {name} search index"; EditorUtility.DisplayProgressBar(title, "Creating search index...", -1f); Search.SearchService.CreateIndex(name, IndexingOptions.Temporary | IndexingOptions.Extended, new[] { "Assets" }, new[] { ".prefab", ".unity", ".asset" }, null, OnSearchIndexCreated); } void OnSearchIndexCreated(string name, string path, Action onComplete) { EditorUtility.ClearProgressBar(); ConverterCollectData(() => { if (m_InitAndConvert) { Convert(null); m_InitAndConvert = false; } EditorUtility.ClearProgressBar(); AssetDatabase.DesiredWorkerCount = m_WorkerCount; AssetDatabase.ForceToDesiredWorkerCount(); RecreateUI(); onComplete(); }); } void ConverterCollectData(Action onConverterDataCollectionComplete) { EditorUtility.DisplayProgressBar($"Initializing converters", $"Initializing converters...", -1f); int convertersToConvert = 0; for (int i = 0; i < m_ConverterStates.Count; ++i) { if (m_ConverterStates[i].requiresInitialization) { convertersToConvert++; GetAndSetData(i, onConverterDataCollectionComplete); } } // If we did not kick off any converter initialization // We can complete everything immediately if (convertersToConvert == 0) { onConverterDataCollectionComplete?.Invoke(); } } RecreateUI(); } private bool SaveCurrentSceneAndContinue() { Scene currentScene = SceneManager.GetActiveScene(); if (currentScene.isDirty) { if (EditorUtility.DisplayDialog("Scene is not saved.", "Current scene is not saved. Please save the scene before continuing.", "Save and Continue", "Cancel")) { EditorSceneManager.SaveScene(currentScene); } else { return false; } } return true; } bool ShouldCreateSearchIndex() { for (int i = 0; i < m_ConverterStates.Count; ++i) { if (m_ConverterStates[i].requiresInitialization) { var converter = m_CoreConvertersList[i]; if (converter.needsIndexing) { return true; } } } return false; } void AddToContextMenu(ContextualMenuPopulateEvent evt, int coreConverterIndex) { var ve = (VisualElement)evt.target; // Checking if this context menu should be enabled or not var isActive = m_ConverterStates[coreConverterIndex].items[(int)ve.userData].isActive && !m_ConverterStates[coreConverterIndex].items[(int)ve.userData].hasConverted; evt.menu.AppendAction("Run converter for this asset", e => { ConvertIndex(coreConverterIndex, (int)ve.userData); // Refreshing the list to show the new state m_ConverterSelectedVE.Q<ListView>("converterItems").Rebuild(); }, isActive ? DropdownMenuAction.AlwaysEnabled : DropdownMenuAction.AlwaysDisabled); } void UpdateInfo(int stateIndex, RunItemContext ctx) { if (ctx.didFail) { m_ConverterStates[stateIndex].items[ctx.item.index].message = ctx.info; m_ConverterStates[stateIndex].items[ctx.item.index].status = Status.Error; m_ConverterStates[stateIndex].errors++; } else { m_ConverterStates[stateIndex].items[ctx.item.index].status = Status.Success; m_ConverterStates[stateIndex].success++; } m_ConverterStates[stateIndex].pending--; // Making sure that this is set here so that if user is clicking Convert again it will not run again. ctx.hasConverted = true; VisualElement child = m_ScrollView[stateIndex]; child.Q<ListView>("converterItems").Rebuild(); } struct AnalyticContextInfo { public string converter_id; public int items_count; } void Convert(ClickEvent evt) { // Ask to save save the current open scene and after the conversion is done reload the same scene. if (!SaveCurrentSceneAndContinue()) return; string currentScenePath = SceneManager.GetActiveScene().path; List<ConverterState> activeConverterStates = new List<ConverterState>(); // Getting all the active converters to use in the cancelable progressbar foreach (ConverterState state in m_ConverterStates) { if (state.isActive && state.isInitialized) { activeConverterStates.Add(state); } } List<AnalyticContextInfo> contextInfo = new (); int currentCount = 0; int activeConvertersCount = activeConverterStates.Count; foreach (ConverterState activeConverterState in activeConverterStates) { currentCount++; var index = activeConverterState.index; m_CoreConvertersList[index].OnPreRun(); var converterName = m_CoreConvertersList[index].name; var itemCount = m_ItemsToConvert[index].itemDescriptors.Count; AnalyticContextInfo converterInfo = new () { converter_id = converterName, items_count = 0 }; string progressTitle = $"{converterName} Converter : {currentCount}/{activeConvertersCount}"; for (var j = 0; j < itemCount; j++) { if (activeConverterState.items[j].isActive) { converterInfo.items_count++; if (EditorUtility.DisplayCancelableProgressBar(progressTitle, string.Format("({0} of {1}) {2}", j, itemCount, m_ItemsToConvert[index].itemDescriptors[j].info), (float)j / (float)itemCount)) break; ConvertIndex(index, j); } } contextInfo.Add(converterInfo); m_CoreConvertersList[index].OnPostRun(); AssetDatabase.SaveAssets(); EditorUtility.ClearProgressBar(); } // Checking if we have changed current scene. If we have we reload the old scene we started from if (!string.IsNullOrEmpty(currentScenePath) && currentScenePath != SceneManager.GetActiveScene().path) { EditorSceneManager.OpenScene(currentScenePath); } RecreateUI(); GraphicsToolUsageAnalytic.ActionPerformed<RenderPipelineConvertersEditor>(nameof(Convert), contextInfo.ToNestedColumn()); } void ConvertIndex(int coreConverterIndex, int index) { if (!m_ConverterStates[coreConverterIndex].items[index].hasConverted) { m_ConverterStates[coreConverterIndex].items[index].hasConverted = true; var item = new ConverterItemInfo() { index = index, descriptor = m_ItemsToConvert[coreConverterIndex].itemDescriptors[index], }; var ctx = new RunItemContext(item); m_CoreConvertersList[coreConverterIndex].OnRun(ref ctx); UpdateInfo(coreConverterIndex, ctx); } } } }