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.

134 lines
4.6 KiB

2 years ago
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;
using gaemstone.Utility;
2 years ago
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.
2 years ago
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;
}
}