using System.Reflection;
using System.Runtime.CompilerServices;
using MessagePack;
namespace OECS;
///
/// Registry of component types used by .
/// Types are discovered via the OECS.SourceGen incremental generator
/// at compile time, with a runtime fallback that scans loaded assemblies
/// for [MessagePackObject] structs.
///
public static class ComponentRegistry
{
private static ComponentDescriptor[]? _descriptors;
private static Dictionary? _byTypeName;
private static bool _scanned;
///
/// All discovered component descriptors.
///
public static ComponentDescriptor[] Descriptors
{
get
{
EnsureScanned();
return _descriptors ?? Array.Empty();
}
}
///
/// Lookup by assembly-qualified type name.
///
public static IReadOnlyDictionary ByTypeName
{
get
{
EnsureScanned();
if (_byTypeName == null)
{
var dict = new Dictionary();
foreach (var desc in Descriptors)
dict[desc.TypeName] = desc;
_byTypeName = dict;
}
return _byTypeName;
}
}
///
/// Called by generated code to register discovered component types.
/// Multiple assemblies may call this. Descriptors are accumulated.
///
public static void Register(ComponentDescriptor[] descriptors)
{
if (_descriptors == null)
{
_descriptors = descriptors;
}
else
{
var existing = new HashSet(_descriptors.Select(d => d.TypeName));
var merged = new List(_descriptors);
foreach (var d in descriptors)
{
if (!existing.Contains(d.TypeName))
{
existing.Add(d.TypeName);
merged.Add(d);
}
}
_descriptors = merged.ToArray();
}
_byTypeName = null;
}
private static void EnsureScanned()
{
if (_scanned) return;
_scanned = true;
// Runtime fallback: scan loaded assemblies for [MessagePackObject] structs.
// This ensures serialization works even when the source generator
// doesn't run (e.g., in test projects referencing game DLLs).
var scanned = new List(_descriptors ?? Array.Empty());
var seen = new HashSet(scanned.Select(d => d.TypeName));
foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
{
try
{
foreach (var type in asm.GetTypes())
{
if (!type.IsValueType || type.IsAbstract || !type.IsPublic)
continue;
if (type.GetCustomAttribute() == null)
continue;
var aqn = $"{type.FullName}, {type.Assembly.GetName().Name}";
if (!seen.Add(aqn))
continue;
var desc = CreateDescriptor(type, aqn);
scanned.Add(desc);
}
}
catch
{
// Some assemblies may throw during reflection (e.g., mixed-mode).
}
}
_descriptors = scanned.ToArray();
}
private static ComponentDescriptor CreateDescriptor(Type type, string aqn)
{
// Build serialize/deserialize delegates via reflection.
var method = typeof(ComponentRegistry).GetMethod(
nameof(CreateTypedDescriptor),
BindingFlags.NonPublic | BindingFlags.Static)!;
var generic = method.MakeGenericMethod(type);
return (ComponentDescriptor)generic.Invoke(null, [aqn])!;
}
private static ComponentDescriptor CreateTypedDescriptor(string aqn) where T : struct
{
return new ComponentDescriptor(
typeName: aqn,
type: typeof(T),
serialize: obj => MessagePackSerializer.Serialize((T)obj),
deserializeAndAdd: (world, entity, data) =>
world.AddComponent(entity, MessagePackSerializer.Deserialize(data)),
deserialize: data => MessagePackSerializer.Deserialize(data));
}
}