using System; using System.Collections.Generic; using System.Reflection; using Godot; public static class SyncRegistry { private static readonly List _byID = new List(); private static readonly Dictionary _byType = new Dictionary(); static SyncRegistry() { foreach (var type in typeof(SyncRegistry).Assembly.GetTypes()) { var objAttr = type.GetCustomAttribute(); if (objAttr == null) continue; if (!typeof(Node).IsAssignableFrom(type)) throw new Exception( $"Type {type} with {nameof(SyncObjectAttribute)} must be a subclass of {nameof(Node)}"); var objInfo = new SyncObjectInfo((ushort)_byID.Count, type); foreach (var property in type.GetProperties()) { if (property.GetCustomAttribute() == null) continue; var propType = typeof(SyncPropertyInfo<,>).MakeGenericType(type, property.PropertyType); var propInfo = (SyncPropertyInfo)Activator.CreateInstance(propType, (byte)objInfo.PropertiesByID.Count, property); objInfo.PropertiesByID.Add(propInfo); objInfo.PropertiesByName.Add(propInfo.Name, propInfo); // Ensure that the de/serializer for this type has been generated. DeSerializerRegistry.Get(propInfo.Type, true); } _byID.Add(objInfo); _byType.Add(objInfo.Type, objInfo); } } public static SyncObjectInfo Get(ushort id) => (id < _byID.Count) ? _byID[id] : throw new Exception( $"Unknown {nameof(SyncObjectInfo)} with ID {id}"); public static SyncObjectInfo Get() => Get(typeof(T)); public static SyncObjectInfo Get(Type type) => _byType.TryGetValue(type, out var value) ? value : throw new Exception( $"No {nameof(SyncObjectInfo)} found for type {type} (missing {nameof(SyncObjectAttribute)}?)"); } public class SyncObjectInfo { public ushort ID { get; } public Type Type { get; } public string Name => Type.Name; public PackedScene InstanceScene { get; } public string ContainerNodePath { get; } public List PropertiesByID { get; } = new List(); public Dictionary PropertiesByName { get; } = new Dictionary(); public SyncObjectInfo(ushort id, Type type) { ID = id; Type = type; var attr = type.GetCustomAttribute(); InstanceScene = GD.Load($"res://scene/{attr.Scene}.tscn"); ContainerNodePath = attr.Container; } } public abstract class SyncPropertyInfo { public byte ID { get; } public PropertyInfo Property { get; } public string Name => Property.Name; public Type Type => Property.PropertyType; public Func Getter { get; } public Action Setter { get; } public SyncPropertyInfo(byte id, PropertyInfo property, Func getter, Action setter) { ID = id; Property = property; Getter = getter; Setter = setter; } } public class SyncPropertyInfo : SyncPropertyInfo { public SyncPropertyInfo(byte id, PropertyInfo property) : base(id, property, obj => ((Func)property.GetMethod.CreateDelegate(typeof(Func))).Invoke((TObject)obj), (obj, value) => ((Action)property.SetMethod.CreateDelegate(typeof(Action))).Invoke((TObject)obj, (TValue)value) ) { } } [AttributeUsage(AttributeTargets.Class)] public class SyncObjectAttribute : Attribute { public string Scene { get; } public string Container { get; } public SyncObjectAttribute(string scene, string container) { Scene = scene; Container = container; } } [AttributeUsage(AttributeTargets.Property)] public class SyncPropertyAttribute : Attribute { }