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