223 lines
9.2 KiB
C#
223 lines
9.2 KiB
C#
|
|
using System;
|
||
|
|
using System.Collections.Generic;
|
||
|
|
using System.IO;
|
||
|
|
using System.Linq;
|
||
|
|
using System.Reflection;
|
||
|
|
using UnityEngine;
|
||
|
|
using Newtonsoft.Json;
|
||
|
|
|
||
|
|
namespace Guildlib.Runtime
|
||
|
|
{
|
||
|
|
// =========================================================================
|
||
|
|
// Schema wire types — serialised to JSON and sent to the server
|
||
|
|
// =========================================================================
|
||
|
|
|
||
|
|
[Serializable]
|
||
|
|
public class FieldSchema
|
||
|
|
{
|
||
|
|
public string name;
|
||
|
|
public string type; // "string" | "int" | "float" | "bool" | "string[]" …
|
||
|
|
public bool isArray;
|
||
|
|
public bool isInherited; // true = defined on a parent class
|
||
|
|
public bool isDisplayName; // true = [GuildDisplayName] present
|
||
|
|
public bool isMultiline; // true = [GuildMultiline] present
|
||
|
|
public string refShard; // non-null = [GuildRef("shard")] present
|
||
|
|
public string[] enumOptions; // non-null = [GuildEnum(...)] present
|
||
|
|
public string defaultValue;
|
||
|
|
}
|
||
|
|
|
||
|
|
[Serializable]
|
||
|
|
public class TypeSchema
|
||
|
|
{
|
||
|
|
public string typeName; // fully-qualified C# type name
|
||
|
|
public string displayName; // from [GuildLabel] or class name
|
||
|
|
public string description; // from [GuildDescription] or ""
|
||
|
|
public string shard; // from [ShardTarget]
|
||
|
|
public string parentType; // null if top-level DataEntry subclass
|
||
|
|
public List<string> childTypes = new();
|
||
|
|
public List<string> defaultPlatforms = new(); // from [GuildPlatforms]
|
||
|
|
public List<string> defaultTags = new(); // from [GuildTags]
|
||
|
|
public List<FieldSchema> fields = new();
|
||
|
|
}
|
||
|
|
|
||
|
|
[Serializable]
|
||
|
|
public class PlatformConfig
|
||
|
|
{
|
||
|
|
public List<string> platforms = new();
|
||
|
|
public List<string> tags = new();
|
||
|
|
}
|
||
|
|
|
||
|
|
[Serializable]
|
||
|
|
public class SchemaManifest
|
||
|
|
{
|
||
|
|
public string version = "2";
|
||
|
|
public long exportedAt;
|
||
|
|
public string unityProjectId;
|
||
|
|
public List<string> shards = new(); // distinct shard names
|
||
|
|
public PlatformConfig platformConfig = new(); // all valid platforms + tags
|
||
|
|
public List<TypeSchema> types = new();
|
||
|
|
}
|
||
|
|
|
||
|
|
// =========================================================================
|
||
|
|
// Exporter
|
||
|
|
// =========================================================================
|
||
|
|
|
||
|
|
public static class SchemaExporter
|
||
|
|
{
|
||
|
|
public static string Export(string projectId = "")
|
||
|
|
{
|
||
|
|
var manifest = new SchemaManifest
|
||
|
|
{
|
||
|
|
exportedAt = DateTimeOffset.UtcNow.ToUnixTimeSeconds(),
|
||
|
|
unityProjectId = projectId,
|
||
|
|
platformConfig = new PlatformConfig
|
||
|
|
{
|
||
|
|
platforms = GuildPlatformRegistry.All.ToList(),
|
||
|
|
tags = GuildTagRegistry.All.ToList(),
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
// Discover all concrete DataEntry subclasses in all loaded assemblies
|
||
|
|
var baseType = typeof(DataEntry);
|
||
|
|
var entryTypes = AppDomain.CurrentDomain.GetAssemblies()
|
||
|
|
.SelectMany(a => { try { return a.GetTypes(); } catch { return Array.Empty<Type>(); } })
|
||
|
|
.Where(t => t.IsClass && !t.IsAbstract && baseType.IsAssignableFrom(t))
|
||
|
|
.ToList();
|
||
|
|
|
||
|
|
var shardSet = new HashSet<string>();
|
||
|
|
var typeMap = new Dictionary<string, TypeSchema>();
|
||
|
|
|
||
|
|
foreach (var t in entryTypes)
|
||
|
|
{
|
||
|
|
var shardAttr = t.GetCustomAttribute<ShardTargetAttribute>(false);
|
||
|
|
var shard = shardAttr?.ShardName ?? "default";
|
||
|
|
shardSet.Add(shard);
|
||
|
|
|
||
|
|
// Display name
|
||
|
|
var labelAttr = t.GetCustomAttribute<GuildLabelAttribute>(false);
|
||
|
|
var displayName = labelAttr?.Label ?? t.Name;
|
||
|
|
|
||
|
|
// Description
|
||
|
|
var descAttr = t.GetCustomAttribute<GuildDescriptionAttribute>(false);
|
||
|
|
var desc = descAttr?.Description ?? "";
|
||
|
|
|
||
|
|
// Default platforms
|
||
|
|
var platformAttr = t.GetCustomAttribute<GuildPlatformsAttribute>(false);
|
||
|
|
var defaultPlats = platformAttr != null
|
||
|
|
? platformAttr.Platforms.ToList()
|
||
|
|
: new List<string> { GuildPlatformRegistry.AllPlatforms };
|
||
|
|
|
||
|
|
// Default tags
|
||
|
|
var tagsAttr = t.GetCustomAttribute<GuildTagsAttribute>(false);
|
||
|
|
var defaultTags = tagsAttr?.Tags.ToList() ?? new List<string>();
|
||
|
|
|
||
|
|
// Parent type (walk up skipping non-DataEntry classes)
|
||
|
|
string parentTypeName = null;
|
||
|
|
var parent = t.BaseType;
|
||
|
|
while (parent != null && parent != baseType &&
|
||
|
|
parent != typeof(ScriptableObject) &&
|
||
|
|
parent != typeof(UnityEngine.Object))
|
||
|
|
{
|
||
|
|
if (entryTypes.Contains(parent)) { parentTypeName = parent.FullName; break; }
|
||
|
|
parent = parent.BaseType;
|
||
|
|
}
|
||
|
|
|
||
|
|
var schema = new TypeSchema
|
||
|
|
{
|
||
|
|
typeName = t.FullName,
|
||
|
|
displayName = displayName,
|
||
|
|
description = desc,
|
||
|
|
shard = shard,
|
||
|
|
parentType = parentTypeName,
|
||
|
|
defaultPlatforms = defaultPlats,
|
||
|
|
defaultTags = defaultTags,
|
||
|
|
fields = BuildFields(t, entryTypes),
|
||
|
|
};
|
||
|
|
|
||
|
|
typeMap[t.FullName] = schema;
|
||
|
|
manifest.types.Add(schema);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Second pass — populate childTypes
|
||
|
|
foreach (var ts in manifest.types)
|
||
|
|
if (ts.parentType != null && typeMap.TryGetValue(ts.parentType, out var p))
|
||
|
|
p.childTypes.Add(ts.typeName);
|
||
|
|
|
||
|
|
manifest.shards = shardSet.OrderBy(s => s).ToList();
|
||
|
|
|
||
|
|
return JsonConvert.SerializeObject(manifest, Formatting.Indented);
|
||
|
|
}
|
||
|
|
|
||
|
|
// ── Field reflection ─────────────────────────────────────────────────
|
||
|
|
|
||
|
|
static List<FieldSchema> BuildFields(Type t, List<Type> allEntryTypes)
|
||
|
|
{
|
||
|
|
var result = new List<FieldSchema>();
|
||
|
|
var baseType = typeof(DataEntry);
|
||
|
|
var seen = new HashSet<string>();
|
||
|
|
|
||
|
|
// Walk up chain so parent fields appear first
|
||
|
|
var chain = new List<Type>();
|
||
|
|
var cur = t;
|
||
|
|
while (cur != null && cur != baseType && cur != typeof(ScriptableObject))
|
||
|
|
{
|
||
|
|
chain.Add(cur); cur = cur.BaseType;
|
||
|
|
}
|
||
|
|
chain.Reverse();
|
||
|
|
|
||
|
|
foreach (var type in chain)
|
||
|
|
{
|
||
|
|
bool inherited = type != t;
|
||
|
|
foreach (var f in type.GetFields(BindingFlags.Public | BindingFlags.Instance
|
||
|
|
| BindingFlags.DeclaredOnly))
|
||
|
|
{
|
||
|
|
if (f.GetCustomAttribute<GuildExcludeAttribute>() != null) continue;
|
||
|
|
if (f.GetCustomAttribute<HideInInspector>() != null) continue;
|
||
|
|
if (seen.Contains(f.Name)) continue;
|
||
|
|
seen.Add(f.Name);
|
||
|
|
|
||
|
|
var refAttr = f.GetCustomAttribute<GuildRefAttribute>();
|
||
|
|
var enumAttr = f.GetCustomAttribute<GuildEnumAttribute>();
|
||
|
|
|
||
|
|
result.Add(new FieldSchema
|
||
|
|
{
|
||
|
|
name = f.Name,
|
||
|
|
type = MapType(f.FieldType),
|
||
|
|
isArray = f.FieldType.IsArray,
|
||
|
|
isInherited = inherited,
|
||
|
|
isDisplayName = f.GetCustomAttribute<GuildDisplayNameAttribute>() != null,
|
||
|
|
isMultiline = f.GetCustomAttribute<GuildMultilineAttribute>() != null,
|
||
|
|
refShard = refAttr?.TargetShard,
|
||
|
|
enumOptions = enumAttr?.Options,
|
||
|
|
defaultValue = GetDefault(f.FieldType),
|
||
|
|
});
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return result;
|
||
|
|
}
|
||
|
|
|
||
|
|
static string MapType(Type t)
|
||
|
|
{
|
||
|
|
if (t == typeof(string)) return "string";
|
||
|
|
if (t == typeof(int)) return "int";
|
||
|
|
if (t == typeof(long)) return "long";
|
||
|
|
if (t == typeof(float)) return "float";
|
||
|
|
if (t == typeof(double)) return "double";
|
||
|
|
if (t == typeof(bool)) return "bool";
|
||
|
|
if (t == typeof(string[])) return "string[]";
|
||
|
|
if (t == typeof(int[])) return "int[]";
|
||
|
|
if (t == typeof(float[])) return "float[]";
|
||
|
|
return "string";
|
||
|
|
}
|
||
|
|
|
||
|
|
static string GetDefault(Type t)
|
||
|
|
{
|
||
|
|
if (t == typeof(bool)) return "false";
|
||
|
|
if (t == typeof(float) || t == typeof(double) ||
|
||
|
|
t == typeof(int) || t == typeof(long)) return "0";
|
||
|
|
return "";
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|