You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
140 lines
4.7 KiB
140 lines
4.7 KiB
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<T>() 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<Type, ModuleInfo> _modules = new(); |
|
internal readonly Dictionary<Type, ModuleBuilder> _deferred = new(); |
|
|
|
internal UniverseModules(Universe universe) { } |
|
} |
|
|
|
public class ModuleInfo |
|
{ |
|
public Universe Universe { get; } |
|
public object Instance { get; } |
|
|
|
public IReadOnlyList<Entity> Relations { get; } |
|
public IReadOnlyList<Entity> Components { get; } |
|
public IReadOnlyList<Entity> Tags { get; } |
|
public IReadOnlyList<Entity> Entities { get; } |
|
public IReadOnlyList<SystemInfo> 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<Type> DependsOn { get; } |
|
public bool HasSimpleConstructor { get; } |
|
|
|
public IReadOnlyList<Type> Relations { get; } |
|
public IReadOnlyList<Type> Components { get; } |
|
public IReadOnlyList<Type> Tags { get; } |
|
public IReadOnlyList<Type> Entities { get; } |
|
public IReadOnlyList<MethodInfo> Systems { get; } |
|
|
|
public HashSet<Type> 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<ModuleAttribute>()) throw new Exception( |
|
"Module must be marked with ModuleAttribute"); |
|
|
|
Universe = universe; |
|
Type = type; |
|
DependsOn = type.GetMultiple<DependsOnAttribute>() |
|
.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<Type>(); |
|
var components = new List<Type>(); |
|
var tags = new List<Type>(); |
|
var entities = new List<Type>(); |
|
var systems = new List<MethodInfo>(); |
|
|
|
foreach (var nested in Type.GetNestedTypes()) { |
|
if (nested.Has<RelationAttribute>()) relations.Add(nested); |
|
else if (nested.Has<ComponentAttribute>()) components.Add(nested); |
|
else if (nested.Has<TagAttribute>()) tags.Add(nested); |
|
else if (nested.Has<EntityAttribute>()) entities.Add(nested); |
|
} |
|
foreach (var method in Type.GetMethods()) |
|
if (method.Has<SystemAttribute>()) |
|
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(); |
|
} |
|
} |
|
}
|
|
|