|
|
|
using System;
|
|
|
|
using System.Collections.Generic;
|
|
|
|
using System.Linq;
|
|
|
|
using System.Reflection;
|
|
|
|
using System.Text.RegularExpressions;
|
|
|
|
using gaemstone.Utility;
|
|
|
|
|
|
|
|
namespace gaemstone.ECS;
|
|
|
|
|
|
|
|
[AttributeUsage(AttributeTargets.Class)]
|
|
|
|
public class ModuleAttribute : Attribute { }
|
|
|
|
|
|
|
|
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
|
|
|
|
public class DependsOnAttribute : Attribute
|
|
|
|
{
|
|
|
|
public Type Target { get; } // TODO: Should probably move to string.
|
|
|
|
public DependsOnAttribute(Type target) => Target = target;
|
|
|
|
}
|
|
|
|
|
|
|
|
[Component]
|
|
|
|
public class Module
|
|
|
|
{
|
|
|
|
public Type Type { get; }
|
|
|
|
public object? Instance { get; internal set; }
|
|
|
|
public Module(Type type) => Type = type;
|
|
|
|
}
|
|
|
|
|
|
|
|
public static class ModuleExtensions
|
|
|
|
{
|
|
|
|
private static readonly Regex _removeCommon = new("(Module|Components)$");
|
|
|
|
// TODO: This wouldn't work with multiple universes. Find a different way to do it.
|
|
|
|
private static Query? _disabledModulesQuery;
|
|
|
|
private static Rule? _disabledModulesWithDisabledDepsRule;
|
|
|
|
|
|
|
|
public static EntityRef RegisterModule<T>(this Universe universe)
|
|
|
|
where T : class => universe.RegisterModule(typeof(T));
|
|
|
|
public static EntityRef RegisterModule(this Universe universe, Type type)
|
|
|
|
{
|
|
|
|
// TODO: Would be nice to make this more straight-forward, especially for in-module code.
|
|
|
|
var Module = universe.Lookup<Module>();
|
|
|
|
var Disabled = universe.Lookup<Flecs.Disabled>();
|
|
|
|
var DependsOn = universe.Lookup<Flecs.DependsOn>();
|
|
|
|
|
|
|
|
if (!type.IsClass || type.IsAbstract) throw new Exception(
|
|
|
|
"Module must be a non-abstract and non-static class");
|
|
|
|
if (!type.Has<ModuleAttribute>()) throw new Exception(
|
|
|
|
"Module must be marked with ModuleAttribute");
|
|
|
|
|
|
|
|
var hasSimpleCtor = type.GetConstructor(Type.EmptyTypes) != null;
|
|
|
|
var hasUniverseCtor = type.GetConstructor(new[] { typeof(Universe) }) != null;
|
|
|
|
if (!hasSimpleCtor && !hasUniverseCtor) throw new Exception(
|
|
|
|
$"Module {type} must define public new() or new(Universe)");
|
|
|
|
|
|
|
|
static EntityRef LookupOrCreateModule(Universe universe, Type type)
|
|
|
|
{
|
|
|
|
var entity = universe.TryLookup(type);
|
|
|
|
if (entity.IsValid) return new(universe, entity);
|
|
|
|
|
|
|
|
// Using startat: 1, regex shouldn't match strings whose entire name matches.
|
|
|
|
if (type.Namespace == null) throw new NotSupportedException("Module must have a namespace");
|
|
|
|
var name = _removeCommon.Replace(type.GetFriendlyName(), "", 1, startat: 1);
|
|
|
|
var newEntity = new EntityBuilder(universe, $"{type.Namespace}.{name}")
|
|
|
|
// NOTE: Modules should not be accessed by symbol.
|
|
|
|
// (Could collide with component symbols.)
|
|
|
|
// { Symbol = symbol }
|
|
|
|
.Disabled().Build();
|
|
|
|
universe.RegisterLookup(type, newEntity);
|
|
|
|
return newEntity;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create an entity for the module that is being registered.
|
|
|
|
var entity = LookupOrCreateModule(universe, type);
|
|
|
|
var dependencies = type.GetMultiple<DependsOnAttribute>()
|
|
|
|
.Select(attr => LookupOrCreateModule(universe, attr.Target));
|
|
|
|
foreach (var dep in dependencies) entity.Add(DependsOn, dep);
|
|
|
|
entity.Set(new Module(type));
|
|
|
|
|
|
|
|
// Collect all currently disabled modules.
|
|
|
|
var disabledModules = new Dictionary<Entity, Module>();
|
|
|
|
_disabledModulesQuery ??= new Query(universe, new(
|
|
|
|
"DisabledModulesQuery",
|
|
|
|
Module,
|
|
|
|
Disabled
|
|
|
|
));
|
|
|
|
foreach (var iter in _disabledModulesQuery) {
|
|
|
|
var modules = iter.FieldRef<Module>(1);
|
|
|
|
for (var i = 0; i < iter.Count; i++)
|
|
|
|
disabledModules.Add(iter.Entity(i), modules[i]);
|
|
|
|
}
|
|
|
|
|
|
|
|
while (true) {
|
|
|
|
|
|
|
|
// Collect all modules that can now be enabled.
|
|
|
|
var modulesToEnable = new Dictionary<Entity, Module>(disabledModules);
|
|
|
|
_disabledModulesWithDisabledDepsRule ??= new Rule(universe, new(
|
|
|
|
"DisabledModulesWithDisabledDepsQuery",
|
|
|
|
Module,
|
|
|
|
Disabled,
|
|
|
|
new(DependsOn, "$Dependency"),
|
|
|
|
new(Disabled) { Source = "$Dependency" }
|
|
|
|
));
|
|
|
|
foreach (var iter in _disabledModulesWithDisabledDepsRule)
|
|
|
|
for (var i = 0; i < iter.Count; i++)
|
|
|
|
modulesToEnable.Remove(iter.Entity(i));
|
|
|
|
|
|
|
|
if (modulesToEnable.Count == 0) break;
|
|
|
|
|
|
|
|
foreach (var (e, c) in modulesToEnable) {
|
|
|
|
var module = new EntityRef(universe, e);
|
|
|
|
c.Instance = EnableModule(module, c.Type);
|
|
|
|
disabledModules.Remove(e);
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return entity;
|
|
|
|
}
|
|
|
|
|
|
|
|
private static object EnableModule(EntityRef module, Type type)
|
|
|
|
{
|
|
|
|
var instance = (type.GetConstructor(Type.EmptyTypes) != null)
|
|
|
|
? Activator.CreateInstance(type)!
|
|
|
|
: Activator.CreateInstance(type, module.Universe)!;
|
|
|
|
|
|
|
|
foreach (var member in type.GetNestedTypes().Concat<MemberInfo>(type.GetMethods()))
|
|
|
|
member.GetRegisterableInfo(out var _)?
|
|
|
|
.Register(module.Universe, instance, member)
|
|
|
|
.Add<Flecs.ChildOf>(module);
|
|
|
|
|
|
|
|
module.Remove<Flecs.Disabled>();
|
|
|
|
return instance;
|
|
|
|
}
|
|
|
|
}
|