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

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