using System; using System.Collections; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using System.Reflection; using gaemstone.Utility; namespace gaemstone.ECS; public unsafe partial class Universe { public void RegisterModule() where T : class => RegisterModule(typeof(T)); public void RegisterModule(Type type) { var builder = new ModuleBuilder(this, type); builder.UnmetDependencies.ExceptWith(Modules._modules.Keys); if (builder.UnmetDependencies.Count > 0) { // If builder has unmet dependencies, defer the registration. Modules._deferred.Add(type, builder); } else { // Otherwise register it right away, .. Modules._modules.Add(type, new ModuleInfo(builder)); // .. and tell other deferred modules this one is now loaded. RemoveDependency(builder.Type); } } private void RemoveDependency(Type type) { var resolved = Modules._deferred.Values .Where(d => d.UnmetDependencies.Remove(type) && d.UnmetDependencies.Count == 0) .ToArray(); foreach (var builder in resolved) { Modules._deferred.Remove(builder.Type); Modules._modules.Add(type, new ModuleInfo(builder)); RemoveDependency(builder.Type); } } public class UniverseModules { internal readonly Dictionary _modules = new(); internal readonly Dictionary _deferred = new(); internal UniverseModules(Universe universe) { } } public class ModuleInfo { public Universe Universe { get; } public object Instance { get; } public IReadOnlyList Relations { get; } public IReadOnlyList Components { get; } public IReadOnlyList Tags { get; } public IReadOnlyList Entities { get; } public IReadOnlyList Systems { get; } internal ModuleInfo(ModuleBuilder builder) { Universe = builder.Universe; Instance = builder.HasSimpleConstructor ? Activator.CreateInstance(builder.Type)! : Activator.CreateInstance(builder.Type, Universe)!; Relations = builder.Relations .Select(Universe.RegisterRelation ).ToImmutableList(); Components = builder.Components.Select(Universe.RegisterComponent).ToImmutableList(); Tags = builder.Tags .Select(Universe.RegisterTag ).ToImmutableList(); Entities = builder.Entities .Select(Universe.RegisterEntity ).ToImmutableList(); Systems = builder.Systems.Select(s => Universe.RegisterSystem(Instance, s)).ToImmutableList(); } } public class ModuleBuilder { public Universe Universe { get; } public Type Type { get; } public IReadOnlyList DependsOn { get; } public bool HasSimpleConstructor { get; } public IReadOnlyList Relations { get; } public IReadOnlyList Components { get; } public IReadOnlyList Tags { get; } public IReadOnlyList Entities { get; } public IReadOnlyList Systems { get; } public HashSet UnmetDependencies { get; } internal ModuleBuilder(Universe universe, Type type) { if (!type.IsClass || type.IsAbstract) throw new Exception( "Module must be a non-abstract class"); if (!type.Has()) throw new Exception( "Module must be marked with ModuleAttribute"); Universe = universe; Type = type; DependsOn = type.GetMultiple() .Select(d => d.Target).ToImmutableList(); HasSimpleConstructor = type.GetConstructor(Type.EmptyTypes) != null; var hasUniverseConstructor = type.GetConstructor(new[] { typeof(Universe) }) != null; if (!HasSimpleConstructor && !hasUniverseConstructor) throw new Exception( $"Module {Type} must define a public constructor with either no parameters, or a single {nameof(Universe)} parameter"); var relations = new List(); var components = new List(); var tags = new List(); var entities = new List(); var systems = new List(); foreach (var nested in Type.GetNestedTypes()) { if (nested.Has()) relations.Add(nested); else if (nested.Has()) components.Add(nested); else if (nested.Has()) tags.Add(nested); else if (nested.Has()) entities.Add(nested); } foreach (var method in Type.GetMethods()) if (method.Has()) systems.Add(method); var elements = new IList[] { relations, components, tags, entities, systems }; if (elements.Sum(l => l.Count) == 0) throw new Exception( "Module must define at least one ECS related type or method"); Relations = relations.AsReadOnly(); Components = components.AsReadOnly(); Tags = tags.AsReadOnly(); Entities = entities.AsReadOnly(); Systems = systems.AsReadOnly(); UnmetDependencies = DependsOn.ToHashSet(); } } }