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;
}
}
}
}