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.
 
 

158 lines
5.0 KiB

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using gaemstone.ECS;
using gaemstone.Utility;
using static gaemstone.Flecs.Core;
using Module = gaemstone.Flecs.Core.Module;
namespace gaemstone;
public class ModuleManager
{
private readonly Dictionary<Entity, ModuleInfo> _modules = new();
public Universe Universe { get; }
public ModuleManager(Universe universe)
=> Universe = universe;
internal ModuleInfo? Lookup(Entity entity)
=> _modules.GetValueOrDefault(entity);
public EntityRef Register<T>()
where T : class, new()
{
var moduleType = typeof(T);
if (moduleType.IsGenericType) throw new Exception(
$"Module {moduleType} must be a non-generic class");
if (!moduleType.Has<ModuleAttribute>()) throw new Exception(
$"Module {moduleType} must be marked with ModuleAttribute");
var path = GetModulePath(moduleType);
var module = new ModuleInfo(Universe, moduleType, path);
_modules.Add(module.Entity, module);
TryEnableModule(module);
return module.Entity;
}
private void TryEnableModule(ModuleInfo module)
{
if (module.UnmetDependencies.Count > 0) return;
Console.WriteLine($"Enabling module {module.Path} ...");
module.Enable();
// Find other modules that might be missing this module as a dependency.
foreach (var other in _modules.Values) {
if (other.IsActive) continue;
if (!other.UnmetDependencies.Contains(module.Entity)) continue;
// Move the just enabled module from unmet to met depedencies.
other.UnmetDependencies.Remove(module.Entity);
other.MetDependencies.Add(module);
TryEnableModule(other);
}
}
public static EntityPath GetModulePath(Type type)
{
var attr = type.Get<ModuleAttribute>();
if (attr == null) throw new ArgumentException(
$"Module {type} must be marked with ModuleAttribute", nameof(type));
var path = EntityPath.Parse(
(type.Get<PathAttribute>() is PathAttribute pathAttr)
? pathAttr.Value : type.Name);
// If specified path is absolute, return it now.
if (path.IsAbsolute) return path;
// Otherwise, create it based on the type's assembly, namespace and name.
var assemblyName = type.Assembly.GetName().Name!;
if (!type.FullName!.StartsWith(assemblyName + '.')) throw new InvalidOperationException(
$"Module {type} must be defined under namespace {assemblyName}");
var fullNameWithoutAssembly = type.FullName![(assemblyName.Length + 1)..];
var parts = fullNameWithoutAssembly.Split('.')[..^1];
return new(true, parts.Prepend(assemblyName).Concat(path.GetParts()).ToArray());
}
internal class ModuleInfo
{
public Universe Universe { get; }
public Type Type { get; }
public EntityPath Path { get; }
public EntityRef Entity { get; }
public object? Instance { get; internal set; }
public bool IsActive => Instance != null;
public HashSet<ModuleInfo> MetDependencies { get; } = new();
public HashSet<Entity> UnmetDependencies { get; } = new();
public ModuleInfo(Universe universe, Type type, EntityPath path)
{
Universe = universe;
Type = type;
Path = path;
if (Type.IsAbstract || Type.IsSealed) throw new Exception(
$"Module {Type} must not be abstract, sealed or static");
if (Type.GetConstructor(Type.EmptyTypes) == null) throw new Exception(
$"Module {Type} must define public parameterless constructor");
var module = Universe.New(Path).Add<Module>();
// Add module dependencies from [DependsOn<>] attributes.
foreach (var attr in Type.GetCustomAttributes()) {
// TODO: Do this without reflection.
var attrType = attr.GetType();
if (!attrType.IsGenericType) continue;
if (attrType.GetGenericTypeDefinition() != typeof(DependsOnAttribute<>)) continue;
var dependsTarget = attrType.GenericTypeArguments[0];
var dependsPath = GetModulePath(dependsTarget);
var dependency = Universe.LookupByPath(dependsPath) ??
Universe.New(dependsPath).Add<Module>().Disable().Build();
var depModule = Universe.Modules.Lookup(dependency);
if (depModule?.IsActive == true) MetDependencies.Add(depModule);
else { UnmetDependencies.Add(dependency); module.Disable(); }
module.Add<DependsOn>(dependency);
}
Entity = module.Build().CreateLookup(Type);
// Ensure all parent entities have Module set.
for (var p = Entity.Parent; p != null; p = p.Parent)
p.Add<Module>();
}
public void Enable()
{
Entity.Enable();
Instance = Activator.CreateInstance(Type)!; // TODO: Replace with generic new() somehow.
if (Instance is IModuleAutoRegisterComponents generatedComponents)
generatedComponents.RegisterComponents(Entity);
(Instance as IModuleInitializer)?.Initialize(Entity);
RegisterMethods(Instance);
}
private void RegisterMethods(object? instance)
{
foreach (var method in Type.GetMethods(
BindingFlags.Public | BindingFlags.NonPublic |
BindingFlags.Static | BindingFlags.Instance
)) {
if (method.Has<SystemAttribute>())
Universe.InitSystem(instance, method).ChildOf(Entity);
if (method.Has<ObserverAttribute>())
Universe.InitObserver(instance, method).ChildOf(Entity);
}
}
}
}