initial commit
This commit is contained in:
222
unity/Assets/Guildlib/Runtime/SchemaExporter.cs
Normal file
222
unity/Assets/Guildlib/Runtime/SchemaExporter.cs
Normal file
@@ -0,0 +1,222 @@
|
||||
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 "";
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user