using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Runtime.Serialization; using Unity.VisualScripting.AssemblyQualifiedNameParser; using UnityEngine; using Exception = System.Exception; using Unity.VisualScripting; namespace Unity.VisualScripting { public static class RuntimeCodebase { private static readonly object @lock = new object(); private static readonly List _types = new List(); public static IEnumerable types => _types; private static readonly List _assemblies = new List(); public static IEnumerable assemblies => _assemblies; private static readonly Dictionary typeSerializations = new Dictionary(); private static Dictionary _renamedTypes = null; private static Dictionary _renamedNamespaces = null; private static Dictionary _renamedAssemblies = null; private static readonly Dictionary> _renamedMembers = new Dictionary>(); static RuntimeCodebase() { lock (@lock) { foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies()) { _assemblies.Add(assembly); foreach (var assemblyType in assembly.GetTypesSafely()) { _types.Add(assemblyType); } } } } #region Assembly Attributes public static IEnumerable GetAssemblyAttributes(Type attributeType) { return GetAssemblyAttributes(attributeType, assemblies); } public static IEnumerable GetAssemblyAttributes(Type attributeType, IEnumerable assemblies) { Ensure.That(nameof(attributeType)).IsNotNull(attributeType); Ensure.That(nameof(assemblies)).IsNotNull(assemblies); foreach (var assembly in assemblies) { foreach (var attribute in assembly.GetCustomAttributes(attributeType)) { if (attributeType.IsInstanceOfType(attribute)) { yield return attribute; } } } } public static IEnumerable GetAssemblyAttributes(IEnumerable assemblies) where TAttribute : Attribute { return GetAssemblyAttributes(typeof(TAttribute), assemblies).Cast(); } public static IEnumerable GetAssemblyAttributes() where TAttribute : Attribute { return GetAssemblyAttributes(typeof(TAttribute)).Cast(); } #endregion #region Serialization public static void PrewarmTypeDeserialization(Type type) { Ensure.That(nameof(type)).IsNotNull(type); var serialization = SerializeType(type); if (typeSerializations.ContainsKey(serialization)) { // Some are duplicates, but almost always compiler generated stuff. // Safe to ignore, and anyway what would we even do to deserialize them properly? } else { typeSerializations.Add(serialization, type); } } public static string SerializeType(Type type) { Ensure.That(nameof(type)).IsNotNull(type); return type?.FullName; } public static bool TryDeserializeType(string typeName, out Type type) { if (string.IsNullOrEmpty(typeName)) { type = null; return false; } lock (@lock) { if (!TryCachedTypeLookup(typeName, out type)) { if (!TrySystemTypeLookup(typeName, out type)) { if (!TryRenamedTypeLookup(typeName, out type)) { return false; } } typeSerializations.Add(typeName, type); } return true; } } public static Type DeserializeType(string typeName) { if (!TryDeserializeType(typeName, out var type)) { throw new SerializationException($"Unable to find type: '{typeName ?? "(null)"}'."); } return type; } private static bool TryCachedTypeLookup(string typeName, out Type type) { return typeSerializations.TryGetValue(typeName, out type); } private static bool TrySystemTypeLookup(string typeName, out Type type) { foreach (var assembly in _assemblies) { type = assembly.GetType(typeName); if (type != null) { return true; } } type = null; return false; } private static bool TrySystemTypeLookup(TypeName typeName, out Type type) { // Can't retrieve an array with the ToLooseString format so use the type Name and compare Assemblies if (typeName.IsArray) { foreach (var assembly in _assemblies.Where(a => typeName.AssemblyName == a.GetName().Name)) { type = assembly.GetType(typeName.Name); if (type != null) { return true; } } type = null; return false; } return TrySystemTypeLookup(typeName.ToLooseString(), out type); } private static bool TryRenamedTypeLookup(string previousTypeName, out Type type) { // Try for an exact match in our renamed types dictionary. // That should work for every non-generic type. if (renamedTypes.TryGetValue(previousTypeName, out var newType)) { type = newType; return true; } // If we can't get an exact match, we'll try parsing the previous type name, // replacing all the renamed types we can find, then reconstructing it. else { var parsedTypeName = TypeName.Parse(previousTypeName); foreach (var renamedType in renamedTypes) { parsedTypeName.ReplaceName(renamedType.Key, renamedType.Value); } foreach (var renamedNamespace in renamedNamespaces) { parsedTypeName.ReplaceNamespace(renamedNamespace.Key, renamedNamespace.Value); } foreach (var renamedAssembly in renamedAssemblies) { parsedTypeName.ReplaceAssembly(renamedAssembly.Key, renamedAssembly.Value); } // Run the system lookup if (TrySystemTypeLookup(parsedTypeName, out type)) { return true; } type = null; return false; } } #endregion #region Renaming // Can't use AttributeUtility here, because the caching system will // try to load all attributes of the type for efficiency, which is // not allowed on the serialization thread because some of Unity's // attribute constructors use Unity API methods (ugh!). public static Dictionary renamedNamespaces { get { if (_renamedNamespaces == null) { _renamedNamespaces = FetchRenamedNamespaces(); } return _renamedNamespaces; } } public static Dictionary renamedAssemblies { get { if (_renamedAssemblies == null) { _renamedAssemblies = FetchRenamedAssemblies(); } return _renamedAssemblies; } } public static Dictionary renamedTypes { get { if (_renamedTypes == null) { // Fetch only on demand because attribute lookups are expensive _renamedTypes = FetchRenamedTypes(); } return _renamedTypes; } } public static Dictionary RenamedMembers(Type type) { Dictionary renamedMembers; if (!_renamedMembers.TryGetValue(type, out renamedMembers)) { renamedMembers = FetchRenamedMembers(type); _renamedMembers.Add(type, renamedMembers); } return renamedMembers; } private static Dictionary FetchRenamedMembers(Type type) { Ensure.That(nameof(type)).IsNotNull(type); var renamedMembers = new Dictionary(); var members = type.GetExtendedMembers(Member.SupportedBindingFlags); foreach (var member in members) { IEnumerable renamedFromAttributes; try { renamedFromAttributes = Attribute.GetCustomAttributes(member, typeof(RenamedFromAttribute), false).Cast(); } catch (Exception ex) { Debug.LogWarning($"Failed to fetch RenamedFrom attributes for member '{member}':\n{ex}"); continue; } var newMemberName = member.Name; foreach (var renamedFromAttribute in renamedFromAttributes) { var previousMemberName = renamedFromAttribute.previousName; if (renamedMembers.ContainsKey(previousMemberName)) { Debug.LogWarning($"Multiple members on '{type}' indicate having been renamed from '{previousMemberName}'.\nIgnoring renamed attributes for '{member}'."); continue; } renamedMembers.Add(previousMemberName, newMemberName); } } return renamedMembers; } private static Dictionary FetchRenamedNamespaces() { var renamedNamespaces = new Dictionary(); foreach (var renamedNamespaceAttribute in GetAssemblyAttributes()) { var previousNamespaceName = renamedNamespaceAttribute.previousName; var newNamespaceName = renamedNamespaceAttribute.newName; if (renamedNamespaces.ContainsKey(previousNamespaceName)) { Debug.LogWarning($"Multiple new names have been provided for namespace '{previousNamespaceName}'.\nIgnoring new name '{newNamespaceName}'."); continue; } renamedNamespaces.Add(previousNamespaceName, newNamespaceName); } return renamedNamespaces; } private static Dictionary FetchRenamedAssemblies() { var renamedAssemblies = new Dictionary(); foreach (var renamedAssemblyAttribute in GetAssemblyAttributes()) { var previousAssemblyName = renamedAssemblyAttribute.previousName; var newAssemblyName = renamedAssemblyAttribute.newName; if (renamedAssemblies.ContainsKey(previousAssemblyName)) { Debug.LogWarning($"Multiple new names have been provided for assembly '{previousAssemblyName}'.\nIgnoring new name '{newAssemblyName}'."); continue; } renamedAssemblies.Add(previousAssemblyName, newAssemblyName); } return renamedAssemblies; } private static Dictionary FetchRenamedTypes() { var renamedTypes = new Dictionary(); foreach (var assembly in assemblies) { foreach (var type in assembly.GetTypesSafely()) { IEnumerable renamedFromAttributes; try { renamedFromAttributes = Attribute.GetCustomAttributes(type, typeof(RenamedFromAttribute), false).Cast(); } catch (Exception ex) { Debug.LogWarning($"Failed to fetch RenamedFrom attributes for type '{type}':\n{ex}"); continue; } var newTypeName = type.FullName; foreach (var renamedFromAttribute in renamedFromAttributes) { var previousTypeName = renamedFromAttribute.previousName; if (renamedTypes.ContainsKey(previousTypeName)) { Debug.LogWarning($"Multiple types indicate having been renamed from '{previousTypeName}'.\nIgnoring renamed attributes for '{type}'."); continue; } renamedTypes.Add(previousTypeName, type); } } } return renamedTypes; } #endregion } }