using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using System.Reflection; using System.Runtime.Serialization; using System.Text; using UnityEngine; using UnityObject = UnityEngine.Object; namespace Unity.VisualScripting { public static class Codebase { static Codebase() { using (ProfilingUtility.SampleBlock("Codebase initialization")) { _assemblies = new List(); _runtimeAssemblies = new List(); _editorAssemblies = new List(); _ludiqAssemblies = new List(); _ludiqRuntimeAssemblies = new List(); _ludiqEditorAssemblies = new List(); _types = new List(); _runtimeTypes = new List(); _editorTypes = new List(); _ludiqTypes = new List(); _ludiqRuntimeTypes = new List(); _ludiqEditorTypes = new List(); foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies()) { try { #if NET_4_6 if (assembly.IsDynamic) { continue; } #endif _assemblies.Add(assembly); var isRuntimeAssembly = IsRuntimeAssembly(assembly); var isEditorAssembly = IsEditorAssembly(assembly, new HashSet()); var isLudiqRuntimeDependentAssembly = IsLudiqRuntimeDependentAssembly(assembly); var isLudiqEditorDependentAssembly = IsLudiqEditorDependentAssembly(assembly); var isLudiqAssembly = isLudiqRuntimeDependentAssembly || isLudiqEditorDependentAssembly; var isLudiqEditorAssembly = isLudiqEditorDependentAssembly; var isLudiqRuntimeAssembly = isLudiqRuntimeDependentAssembly && !isLudiqEditorDependentAssembly; if (isRuntimeAssembly) { _runtimeAssemblies.Add(assembly); } if (isEditorAssembly) { _editorAssemblies.Add(assembly); } if (isLudiqAssembly) { _ludiqAssemblies.Add(assembly); } if (isLudiqEditorAssembly) { _ludiqEditorAssemblies.Add(assembly); } if (isLudiqRuntimeAssembly) { _ludiqRuntimeAssemblies.Add(assembly); } foreach (var type in assembly.GetTypesSafely()) { _types.Add(type); if (isRuntimeAssembly) { _runtimeTypes.Add(type); } if (isEditorAssembly) { _editorTypes.Add(type); } if (isLudiqAssembly) { _ludiqTypes.Add(type); } if (isLudiqEditorAssembly) { _ludiqEditorTypes.Add(type); } if (isLudiqRuntimeAssembly) { _ludiqRuntimeTypes.Add(type); } } } catch (Exception ex) { Debug.LogWarning($"Failed to analyze assembly '{assembly}':\n{ex}"); } } assemblies = _assemblies.AsReadOnly(); runtimeAssemblies = _runtimeAssemblies.AsReadOnly(); editorAssemblies = _editorAssemblies.AsReadOnly(); ludiqAssemblies = _ludiqAssemblies.AsReadOnly(); ludiqRuntimeAssemblies = _ludiqRuntimeAssemblies.AsReadOnly(); ludiqEditorAssemblies = _ludiqEditorAssemblies.AsReadOnly(); types = _types.AsReadOnly(); runtimeTypes = _runtimeTypes.AsReadOnly(); editorTypes = _editorTypes.AsReadOnly(); ludiqTypes = _ludiqTypes.AsReadOnly(); ludiqRuntimeTypes = _ludiqRuntimeTypes.AsReadOnly(); ludiqEditorTypes = _ludiqEditorTypes.AsReadOnly(); } } private static readonly List _assemblies; private static readonly List _runtimeAssemblies; private static readonly List _editorAssemblies; private static readonly List _ludiqAssemblies; private static readonly List _ludiqRuntimeAssemblies; private static readonly List _ludiqEditorAssemblies; private static readonly List _types; private static readonly List _runtimeTypes; private static readonly List _editorTypes; private static readonly List _ludiqTypes; private static readonly List _ludiqRuntimeTypes; private static readonly List _ludiqEditorTypes; private static List _settingsAssemblies; private static List _settingsAssembliesTypes; private static List _settingsTypes; private static readonly Dictionary _editorAssemblyCache = new Dictionary(); private static readonly Dictionary _assemblyNameCache = new Dictionary(); #region Serialization public static string SerializeType(Type type) { return RuntimeCodebase.SerializeType(type); } public static bool TryDeserializeType(string typeName, out Type type) { return RuntimeCodebase.TryDeserializeType(typeName, out type); } public static Type DeserializeType(string typeName) { return RuntimeCodebase.DeserializeType(typeName); } private const char memberDataSeparator = ';'; public static string SerializeMember(Member member) { // Format: "targetType;name;paramType1;paramType2" // Example: "UnityEngine.Transform;rotate;UnityEngine.Vector3" var sb = new StringBuilder(); sb.Append(SerializeType(member.targetType)); sb.Append(memberDataSeparator); sb.Append(member.name); if (member.parameterTypes != null) { sb.Append(memberDataSeparator); for (int i = 0; i < member.parameterTypes.Length; i++) { sb.Append(SerializeType(member.parameterTypes[i])); if (i < member.parameterTypes.Length - 1) { sb.Append(memberDataSeparator); } } } return sb.ToString(); } public static Member DeserializeMember(string memberData) { try { Ensure.That(nameof(memberData)).IsNotNullOrEmpty(memberData); var parts = memberData.Split(memberDataSeparator); if (parts.Length < 2) { throw new SerializationException("Malformed member data string."); } var targetType = DeserializeType(parts[0]); var name = parts[1]; Type[] parameterTypes; if (parts.Length == 2) { parameterTypes = null; } else if (parts.Length == 3 && string.IsNullOrEmpty(parts[2])) { parameterTypes = Empty.array; } else { parameterTypes = new Type[parts.Length - 2]; for (int i = 2; i < parts.Length; i++) { parameterTypes[i - 2] = DeserializeType(parts[i]); } } return new Member(targetType, name, parameterTypes); } catch (Exception ex) { throw new SerializationException($"Unable to find member: '{memberData}'.", ex); } } #endregion public static event Action settingsChanged; // NETUP: IReadOnlyCollection public static ReadOnlyCollection assemblies { get; private set; } // not used public static ReadOnlyCollection runtimeAssemblies { get; private set; } // not used public static ReadOnlyCollection editorAssemblies { get; private set; } public static ReadOnlyCollection ludiqAssemblies { get; private set; } public static ReadOnlyCollection ludiqRuntimeAssemblies { get; private set; } public static ReadOnlyCollection ludiqEditorAssemblies { get; private set; } public static ReadOnlyCollection settingsAssemblies { get; private set; } public static ReadOnlyCollection types { get; private set; } public static ReadOnlyCollection runtimeTypes { get; private set; } public static ReadOnlyCollection editorTypes { get; private set; } // not used public static ReadOnlyCollection ludiqTypes { get; private set; } public static ReadOnlyCollection ludiqRuntimeTypes { get; private set; } public static ReadOnlyCollection ludiqEditorTypes { get; private set; } public static ReadOnlyCollection settingsAssembliesTypes { get; private set; } public static ReadOnlyCollection settingsTypes { get; private set; } public static ReadOnlyCollection GetTypeSet(TypeSet typeSet) { switch (typeSet) { case TypeSet.AllTypes: return types; case TypeSet.RuntimeTypes: return runtimeTypes; case TypeSet.SettingsTypes: return settingsTypes; case TypeSet.SettingsAssembliesTypes: return settingsAssembliesTypes; default: throw new UnexpectedEnumValueException(typeSet); } } public static ReadOnlyCollection GetTypeSetFromAttribute(IAttributeProvider attributeProvider, TypeSet fallback = TypeSet.SettingsTypes) { var typeset = GetTypeSet(attributeProvider.GetAttribute()?.typeSet ?? fallback); return typeset; } private static bool IsUnityEditorAssembly(Assembly assembly) { // Cache because .GetName() is expensive if (!_assemblyNameCache.TryGetValue(assembly, out var name)) { name = assembly.GetName().Name; _assemblyNameCache.Add(assembly, name); } return IsUnityEditorAssembly(name); } private static bool IsUnityEditorAssembly(string name) { return name == "Assembly-CSharp-Editor" || name == "Assembly-CSharp-Editor-firstpass" || name == "UnityEditor" || name == "UnityEditor.CoreModule"; } private static bool IsSpecialCaseRuntimeAssembly(string assemblyName) { return assemblyName == "UnityEngine.UI" || // has a reference to UnityEditor.CoreModule assemblyName == "Unity.TextMeshPro"; // has a reference to UnityEditor.TextCoreFontEngineModule } private static bool IsEditorAssembly(Assembly assembly, HashSet visited) { // assembly.GetName() is surprisingly expensive, keep a cache if (_editorAssemblyCache.TryGetValue(assembly, out var isEditor)) { return isEditor; } var name = assembly.GetName().Name; if (visited.Contains(name)) { return false; } visited.Add(name); if (IsSpecialCaseRuntimeAssembly(name)) { _editorAssemblyCache.Add(assembly, false); return false; } if (Attribute.IsDefined(assembly, typeof(AssemblyIsEditorAssembly))) { _editorAssemblyCache.Add(assembly, true); return true; } if (IsUserAssembly(name)) { _editorAssemblyCache.Add(assembly, false); return false; } if (IsUnityEditorAssembly(name)) { _editorAssemblyCache.Add(assembly, true); return true; } AssemblyName[] listOfAssemblyNames = assembly.GetReferencedAssemblies(); foreach (var dependencyName in listOfAssemblyNames) { try { Assembly dependency = Assembly.Load(dependencyName); if (IsEditorAssembly(dependency, visited)) { _editorAssemblyCache.Add(assembly, true); return true; } } catch (Exception e) { Debug.LogWarning(e.Message); } } _editorAssemblyCache.Add(assembly, false); return false; } private static bool IsUserAssembly(string name) { return name == "Assembly-CSharp" || name == "Assembly-CSharp-firstpass"; } private static bool IsRuntimeAssembly(Assembly assembly) { // User assemblies refer to the editor when they include // a using UnityEditor / #if UNITY_EDITOR, but they should still // be considered runtime. return !IsEditorAssembly(assembly, new HashSet()); } private static bool IsLudiqRuntimeDependentAssembly(Assembly assembly) { if (assembly.GetName().Name == "Unity.VisualScripting.Core") { return true; } foreach (var dependency in assembly.GetReferencedAssemblies()) { if (dependency.Name == "Unity.VisualScripting.Core" || dependency.Name == "Unity.VisualScripting.Flow" || dependency.Name == "Unity.VisualScripting.State") { return true; } } return false; } private static bool IsLudiqEditorDependentAssembly(Assembly assembly) { if (assembly.GetName().Name == "Unity.VisualScripting.Core.Editor") { return true; } foreach (var dependency in assembly.GetReferencedAssemblies()) { if (dependency.Name == "Unity.VisualScripting.Core.Editor") { return true; } } return false; } // Need to keep this to avoid breaking the public API public static bool IsEditorType(Type type) { var rootNamespace = type.RootNamespace(); return IsEditorAssembly(type.Assembly, new HashSet()) || rootNamespace == "UnityEditor" || rootNamespace == "UnityEditorInternal"; } // Need to keep this to avoid breaking the public API public static bool IsRuntimeType(Type type) { return !IsEditorType(type) && !IsInternalType(type); } internal static bool IsUnityEditorType(Type type) { var rootNamespace = type.RootNamespace(); return rootNamespace == "UnityEditor" || rootNamespace == "UnityEditorInternal" || Attribute.IsDefined(type.Assembly, typeof(AssemblyIsEditorAssembly)) || IsUnityEditorAssembly(type.Assembly); } public static bool IsInternalType(Type type) { var rootNamespace = type.RootNamespace(); return rootNamespace == "UnityEngineInternal" || rootNamespace == "UnityEditorInternal"; } private static string RootNamespace(this Type type) { return type.Namespace?.PartBefore('.'); } public static void UpdateSettings() { using (ProfilingUtility.SampleBlock("Codebase settings update")) { var typeOptionsHashSet = new HashSet(BoltCore.Configuration.typeOptions); var assemblyOptionsHashSet = new HashSet(BoltCore.Configuration.assemblyOptions); _settingsAssemblies = new List(); _settingsAssembliesTypes = new List(); _settingsTypes = new List(); foreach (var assembly in _assemblies) { var couldHaveIncludeInSettingsAttribute = ludiqAssemblies.Contains(assembly); // It's important not to provide types outside the settings assemblies, // because only those assemblies will be added to the linker to preserve stripping. if (IncludeInSettings(assembly, assemblyOptionsHashSet)) { _settingsAssemblies.Add(assembly); foreach (var type in assembly.GetTypesSafely()) { // Apparently void can be returned somehow: // http://support.ludiq.io/topics/483 if (type == typeof(void)) { continue; } _settingsAssembliesTypes.Add(type); // For optimization, we bypass [IncludeInSettings] for assemblies // that could logically never have it. if (IncludeInSettings(type, couldHaveIncludeInSettingsAttribute, typeOptionsHashSet)) { _settingsTypes.Add(type); } } } } settingsAssemblies = _settingsAssemblies.AsReadOnly(); settingsAssembliesTypes = _settingsAssembliesTypes.AsReadOnly(); settingsTypes = _settingsTypes.AsReadOnly(); settingsChanged?.Invoke(); } } private static bool IncludeInSettings(Assembly a, HashSet assemblyOptionsHashSet) { var includeInSettings = assemblyOptionsHashSet.Contains(a.GetName().Name); return includeInSettings; } private static bool IncludeInSettings(Type t, bool couldHaveAttribute, HashSet typeOptionsHashSet) { // User-defined settings types if (typeOptionsHashSet.Contains(t)) { return true; } // Include non-runtime, non-internal enum and class deriving from UnityObject // check the attribute last, attempt to early-out if ((t.IsEnum || typeof(UnityObject).IsAssignableFrom(t)) && (GetAttributeInclude() ?? true)) { return !IsUnityEditorType(t) && !IsInternalType(t); } return GetAttributeInclude() ?? false; bool? GetAttributeInclude() { // Attribute.IsDefined is way faster than GetAttribute return couldHaveAttribute && Attribute.IsDefined(t, typeof(IncludeInSettingsAttribute)) ? (bool?)t.GetAttribute().include : null; } } public static CodebaseSubset Subset(IEnumerable types, MemberFilter memberFilter, TypeFilter memberTypeFilter = null) { return CodebaseSubset.Get(types, memberFilter, memberTypeFilter); } public static CodebaseSubset Subset(IEnumerable typeSet, TypeFilter typeFilter, MemberFilter memberFilter, TypeFilter memberTypeFilter = null) { return CodebaseSubset.Get(typeSet, typeFilter, memberFilter, memberTypeFilter); } } }