using System; using System.Collections.Generic; using System.Reflection; using System.Text; using UnityEditor.Rendering.Converter; using UnityEngine; using UnityEngine.Rendering; namespace UnityEditor.Rendering.Universal { [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] internal class BatchModeConverterClassInfo : Attribute { public string converterType { get; } public string containerName { get; } public BatchModeConverterClassInfo(string containerName, string converterType) { this.converterType = converterType; this.containerName = containerName; } } /// /// Class for the converter framework. /// public static partial class Converters { // Commands const string k_BatchmodeCommand = "-batchmode"; const string k_HelpCommand = "--help"; const string k_ListCommand = "--list"; const string k_ContainerCommand = "-container"; const string k_TypesFilterCommand = "-typesFilter"; const string k_InclusiveFlag = "--inclusive"; const string k_ExclusiveFlag = "--exclusive"; // List all available containers static void ListAvailableConverters() { // Get all converters and group them by container var containersDict = DictionaryPool>.Get(); foreach (var container in TypeCache.GetTypesWithAttribute()) { if (container.IsAbstract || container.IsInterface) continue; var info = container.GetCustomAttribute(); if (!containersDict.ContainsKey(info.containerName)) { containersDict[info.containerName] = new List(); } containersDict[info.containerName].Add(info.converterType); } // Organize the information StringBuilder convertersMessage = StringBuilderPool.Get(); foreach (var converter in containersDict) { convertersMessage.AppendLine($"Container: {converter.Key}"); convertersMessage.AppendLine("Available converter types:"); convertersMessage.AppendLine(String.Join($"\n\t- ", converter.Value)); convertersMessage.AppendLine("\n"); } Debug.Log($"Available containers and their converter types\n{convertersMessage}"); } static void LogHelp() { StringBuilder helpMessage = StringBuilderPool.Get(); // Description helpMessage.AppendLine("\n"); helpMessage.AppendLine( "The batchmode converter is a tool to help you upgrade your projects from one scriptable render pipeline\n" + "to another. Using this API can lead to incomplete or unpredictable conversion outcomes.\n" + "For reliable results, please perform the conversion via the dedicated window: Window > Rendering > Render Pipeline Converter."); helpMessage.AppendLine("\n"); // Usage helpMessage.AppendLine($"usage: \t -projectPath {k_BatchmodeCommand} -executeMethod UnityEditor.Rendering.Universal.Converters.RunInBatchModeCmdLine\n" + $"\t \t[{k_HelpCommand}] [{k_ListCommand}]\n" + $"\t \t[{k_ContainerCommand} ] [{k_TypesFilterCommand} ] [{k_InclusiveFlag}|{k_ExclusiveFlag}]"); helpMessage.AppendLine("\n"); // Commands helpMessage.AppendLine("Commands"); helpMessage.AppendLine($"\t{k_HelpCommand} \t \t Show this help and exit."); helpMessage.AppendLine($"\t{k_ListCommand} \t \t List all available converters and exit."); helpMessage.AppendLine("\n"); // Options helpMessage.AppendLine("Options"); helpMessage.AppendLine($"\t{k_ContainerCommand} \t \t \t The name of the container which will be batched (required)."); helpMessage.AppendLine($"\t{k_TypesFilterCommand} \t The list of converters types that will be either included or excluded from batching. These converters need to be part of the passed in container for them to run."); helpMessage.AppendLine($"\t{k_InclusiveFlag}|{k_ExclusiveFlag} \t \t \t Whether the list of converters specified with {k_TypesFilterCommand} will be included or excluded when batching."); helpMessage.AppendLine("\n"); helpMessage.AppendLine("Notes"); helpMessage.AppendLine($"\t Use either {k_InclusiveFlag} or {k_ExclusiveFlag}, not both."); helpMessage.AppendLine($"\t When using {k_InclusiveFlag}, you must specify values for {k_TypesFilterCommand}."); helpMessage.AppendLine($"\t Values for {k_TypesFilterCommand} must be provided as a space-separated list: {k_TypesFilterCommand} typeA typeB typeC"); helpMessage.AppendLine("\nOnline documentation: https://docs.unity3d.com/6000.5/Documentation/Manual/urp/convert-assets-to-urp.html\n"); Debug.Log(helpMessage); } internal static void SuggestUpdatedCommand(string container, List converters, bool isInclusive) { var containerParameter = $" {k_ContainerCommand} {container}"; var converterParameter = converters.Count == 0 ? "" : $" {k_TypesFilterCommand} {string.Join(" ", converters)}"; var filterModeFlag = isInclusive ? $" {k_InclusiveFlag}" : $" {k_ExclusiveFlag}"; Debug.Log("The method you're trying to use is deprecated. Try running the following command in the command line:\n" + $" -projectPath {k_BatchmodeCommand} -executeMethod UnityEditor.Rendering.Universal.Converters.RunInBatchModeCmdLine{filterModeFlag}{containerParameter}{converterParameter}"); } // Return all converters we will be running internal static List FilterConverters(string containerName, List convertedTypesFilter, bool isInclusive = false) { var allConverters = TypeCache.GetTypesWithAttribute(); var filteredList = new List(allConverters.Count); // early return if (isInclusive && convertedTypesFilter.Count == 0) return filteredList; // nothing included in the list foreach (var converterType in allConverters) { var converterInfo = converterType.GetCustomAttribute(); if (containerName != converterInfo.containerName) continue; var isTypeInFilteredList = convertedTypesFilter.Contains(converterInfo.converterType); // add if inclusive and in the included list, add if exclusive and not in the excluded list if (isInclusive == isTypeInFilteredList) filteredList.Add(converterType); } return filteredList; } internal static Dictionary> ParseArgs(string[] rawArgs) { int batchmodeArgIndex = Array.FindIndex(rawArgs, arg => arg == k_BatchmodeCommand); if (batchmodeArgIndex == -1) throw new ArgumentException($"No {k_BatchmodeCommand} argument found. Exiting."); var parsedArgs = DictionaryPool>.Get(); parsedArgs["Flags"] = new List(); string currentKey = null; // are we collecting values for a key? for(int i = batchmodeArgIndex + 1; i < rawArgs.Length; i++) { if (rawArgs[i].StartsWith("--")) // new flag { parsedArgs["Flags"].Add(rawArgs[i]); currentKey = ""; } else if (rawArgs[i].StartsWith("-")) // new argument { parsedArgs.Add(rawArgs[i], new List()); currentKey = rawArgs[i]; } else // adding to the last argument { if (String.IsNullOrEmpty(currentKey)) { throw new ArgumentException($"Unrecognized argument: {rawArgs[i]}"); } parsedArgs[currentKey].Add(rawArgs[i]); } } return parsedArgs; } /// /// Call this method to run all the converters in a specific container in batch mode. /// /// The name of the container which will be batched. All Converters in this Container will run if prerequisites are met. public static void RunInBatchMode(string containerName) { RunInBatchMode(containerName, null, isInclusive: false); } /// /// Call this method to run a specific list of converters in a specific container in batch mode. /// /// The name of the container which will be batched. /// The list of converters that will be either included or excluded from batching. These converters need to be part of the passed in container for them to run. /// Whether the list of converters will be included or excluded when batching. public static void RunInBatchMode(string containerName, List convertedTypes, bool isInclusive) { var types = FilterConverters(containerName, convertedTypes, isInclusive); RunInBatchMode(types); } /// /// Call this method to run a specific list of converters in a specific container in batch mode. /// Entry point for: -executeMethod UnityEditor.Rendering.Universal.Converters.RunInBatchModeCmdLine /// public static void RunInBatchModeCmdLine() { Debug.Log("BATCH MODE COMMAND LINE\n"); var exitCode = 0; try { var args = ParseArgs(Environment.GetCommandLineArgs()); // If help requested, print and exit if (args["Flags"].Contains(k_HelpCommand)) { LogHelp(); EditorApplication.Exit(0); return; } if (args["Flags"].Contains(k_ListCommand)) { ListAvailableConverters(); EditorApplication.Exit(0); return; } // ContainerType ----- if (!args.TryGetValue(k_ContainerCommand, out var converter)) throw new ArgumentException($"Missing required {k_ContainerCommand} . Use {k_ListCommand} to see available converter."); if(converter.Count != 1) throw new ArgumentException($"Please specify only one container. Use {k_ListCommand} to see available converters."); // Filter + Include/Exclude ------ var hasInclusive = args["Flags"].Contains(k_InclusiveFlag); var hasExclusive = args["Flags"].Contains(k_ExclusiveFlag); var hasTypesFilter = args.TryGetValue(k_TypesFilterCommand, out var filteredTypes); if (hasTypesFilter && hasExclusive == hasInclusive) { throw new ArgumentException($"When using {k_TypesFilterCommand}, please specify exactly one of {k_InclusiveFlag} or {k_ExclusiveFlag}. Use {k_HelpCommand} for usage."); } if (hasInclusive && !hasTypesFilter) throw new ArgumentException($"When using {k_InclusiveFlag} mode, please specify types to include using {k_TypesFilterCommand} otherwise nothing will be converted. " + $"Use {k_ListCommand} to see available types."); RunInBatchMode(converter[0], filteredTypes, hasInclusive); } catch (Exception ex) { Debug.LogError($"ConverterCli failed: {ex.Message}\n{ex}"); exitCode = 1; } finally { EditorApplication.Exit(exitCode); } } /// /// Call this method to run a specific list of converters in batch mode. /// /// The list of converters to run /// False if there were errors. internal static bool RunInBatchMode(List converterTypes) { Debug.LogWarning($"Using this API can lead to incomplete or unpredictable conversion outcomes. For reliable results, please perform the conversion via the dedicated window: Window > Rendering > Render Pipeline Converter."); List convertersToExecute = new(); bool errors = false; foreach (var type in converterTypes) { try { var instance = Activator.CreateInstance(type) as IRenderPipelineConverter; if (instance == null) { Debug.LogWarning($"{type} is not a converter type."); errors = true; } else convertersToExecute.Add(instance); } catch { Debug.LogWarning($"Unable to create instance of type {type}."); errors = true; } } if (errors) { Debug.LogWarning($"Please use any of the given Converter Types."); ListAvailableConverters(); } BatchConverters(convertersToExecute); return !errors; } static void BatchConverters(List converters) { foreach (var converter in converters) { var sb = StringBuilderPool.Get(); converter.Scan(OnConverterCompleteDataCollection); void OnConverterCompleteDataCollection(List items) { // Flatten folders to leaf items for batch conversion var leafItems = new List(); RenderPipelineConverterUtility.CollectLeafItems(items, leafItems); converter.BeforeConvert(); foreach (var item in leafItems) { var status = converter.Convert(item, out var message); switch (status) { case Status.Pending: throw new InvalidOperationException("Converter returned a pending status when converting. This is not supported."); case Status.Error: case Status.Warning: sb.AppendLine($"- {item.name} ({status}) ({message})"); break; case Status.Success: { sb.AppendLine($"- {item.name} ({status})"); } break; } } converter.AfterConvert(); var conversionResult = sb.ToString(); if (!string.IsNullOrEmpty(conversionResult)) Debug.Log(sb.ToString()); } } AssetDatabase.SaveAssets(); } } }