using System; using System.Collections.Generic; using Uigenx.Binding; using Uigenx.Protocol; using UnityEditor; using UnityEditor.Compilation; using UnityEngine; namespace Uigenx.Editor { /// /// Reflects the project's data sources into the schema the studio binds against. Uses the same /// walker the runtime uses for , so the studio never sees a /// path the runtime cannot resolve. /// public static class UgxSchemaReflector { public static UgxDataSchemaResult Reflect(string typeName = null, string requestId = null) { var result = new UgxDataSchemaResult { RequestId = requestId }; if (!string.IsNullOrEmpty(typeName)) { var type = FindType(typeName); if (type == null) { Debug.LogWarning("[ugx] no type named '" + typeName + "' was found in the player assemblies"); return result; } result.Sources.Add(Describe(type)); return result; } foreach (var type in FindDataSourceTypes()) result.Sources.Add(Describe(type)); return result; } public static UgxDataSchemaSource Describe(Type type) { var attribute = (UgxDataSourceAttribute)Attribute.GetCustomAttribute(type, typeof(UgxDataSourceAttribute)); var source = new UgxDataSchemaSource { Name = attribute != null && !string.IsNullOrEmpty(attribute.Name) ? attribute.Name : type.Name, TypeName = type.FullName, }; foreach (var entry in UgxSchemaWalker.Collect(type)) source.Fields.Add(new UgxDataSchemaField { Path = entry.Path, Type = entry.Type, Writable = entry.Writable, }); return source; } /// Every type carrying in a player assembly. public static List FindDataSourceTypes() { var found = new List(8); foreach (var assembly in LoadPlayerAssemblies()) { foreach (var type in SafeGetTypes(assembly)) { if (type == null || type.IsAbstract) continue; if (!Attribute.IsDefined(type, typeof(UgxDataSourceAttribute))) continue; found.Add(type); } } found.Sort((a, b) => string.CompareOrdinal(a.FullName, b.FullName)); return found; } /// /// Resolves a type the studio named. Player assemblies are searched first because that is /// where a shipping data source lives; editor assemblies are a fallback so an editor side /// tool or a test type can be inspected too. /// public static Type FindType(string typeName) { var direct = Type.GetType(typeName, false); if (direct != null) return direct; return FindIn(typeName, AssembliesType.Player) ?? FindIn(typeName, AssembliesType.Editor); } static Type FindIn(string typeName, AssembliesType assemblies) { foreach (var assembly in LoadAssemblies(assemblies)) { var type = assembly.GetType(typeName, false); if (type != null) return type; } // A name match so the studio can ask for `PlayerData` without the namespace. foreach (var assembly in LoadAssemblies(assemblies)) { foreach (var type in SafeGetTypes(assembly)) if (type != null && type.Name == typeName) return type; } return null; } /// A player assembly can reference an editor only type, which makes GetTypes partial. static Type[] SafeGetTypes(System.Reflection.Assembly assembly) { try { return assembly.GetTypes(); } catch (System.Reflection.ReflectionTypeLoadException e) { return e.Types ?? Array.Empty(); } } static IEnumerable LoadPlayerAssemblies() => LoadAssemblies(AssembliesType.Player); static IEnumerable LoadAssemblies(AssembliesType assemblies) { var wanted = new HashSet(StringComparer.Ordinal); foreach (var assembly in CompilationPipeline.GetAssemblies(assemblies)) wanted.Add(assembly.name); foreach (var loaded in AppDomain.CurrentDomain.GetAssemblies()) { var name = loaded.GetName().Name; if (wanted.Contains(name)) yield return loaded; } } } }