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.
141 lines
4.7 KiB
141 lines
4.7 KiB
2 years ago
|
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();
|
||
|
}
|
||
|
}
|
||
|
}
|