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.
231 lines
8.1 KiB
231 lines
8.1 KiB
2 years ago
|
using System;
|
||
|
using System.Collections.Generic;
|
||
2 years ago
|
using System.Linq;
|
||
1 year ago
|
using gaemstone.ECS;
|
||
2 years ago
|
using gaemstone.Utility;
|
||
2 years ago
|
using static gaemstone.Flecs.Core;
|
||
1 year ago
|
using BindingFlags = System.Reflection.BindingFlags;
|
||
2 years ago
|
|
||
1 year ago
|
namespace gaemstone;
|
||
2 years ago
|
|
||
|
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);
|
||
|
|
||
2 years ago
|
public void RegisterAllFrom(System.Reflection.Assembly assembly)
|
||
|
=> RegisterAll(assembly.GetTypes());
|
||
|
public void RegisterAll(IEnumerable<Type> types)
|
||
|
{
|
||
|
foreach (var type in types)
|
||
|
if (type.Has<ModuleAttribute>())
|
||
|
Register(type);
|
||
|
}
|
||
|
|
||
2 years ago
|
public EntityRef Register<T>()
|
||
|
where T : class => Register(typeof(T));
|
||
1 year ago
|
public EntityRef Register(Type moduleType)
|
||
2 years ago
|
{
|
||
1 year ago
|
if (!moduleType.IsClass || moduleType.IsGenericType || moduleType.IsGenericTypeDefinition) throw new Exception(
|
||
|
$"Module {moduleType} must be a non-generic class");
|
||
1 year ago
|
if (moduleType.Get<ModuleAttribute>() is not ModuleAttribute moduleAttr) throw new Exception(
|
||
1 year ago
|
$"Module {moduleType} must be marked with ModuleAttribute");
|
||
2 years ago
|
|
||
|
// Check if module type is static.
|
||
1 year ago
|
if (moduleType.IsAbstract && moduleType.IsSealed) {
|
||
2 years ago
|
|
||
|
// Static modules represent existing modules, as such they don't
|
||
|
// create entities, only look up existing ones to add type lookups
|
||
|
// for use with the Lookup(Type) method.
|
||
|
|
||
1 year ago
|
if (moduleType.Get<PathAttribute>()?.Value is not string modulePathStr) throw new Exception(
|
||
|
$"Existing module {moduleType} must have {nameof(PathAttribute)} set");
|
||
|
var modulePath = EntityPath.Parse(modulePathStr);
|
||
|
var moduleEntity = Universe.LookupByPath(modulePath) ?? throw new Exception(
|
||
|
$"Existing module {moduleType} with name '{modulePath}' not found");
|
||
2 years ago
|
|
||
|
// This implementation is pretty naive. It simply gets all nested
|
||
2 years ago
|
// types which are tagged with an ICreateEntityAttribute base
|
||
|
// attribute and creates a lookup mapping. No sanity checking.
|
||
2 years ago
|
|
||
1 year ago
|
foreach (var type in moduleType.GetNestedTypes()) {
|
||
|
if (!type.GetCustomAttributes(true).OfType<ICreateEntityAttribute>().Any()) continue;
|
||
1 year ago
|
|
||
|
var attr = type.Get<PathAttribute>();
|
||
|
var path = EntityPath.Parse(attr?.Value ?? type.Name);
|
||
|
var entity = Universe.LookupByPathOrThrow(moduleEntity, path);
|
||
1 year ago
|
entity.CreateLookup(type);
|
||
1 year ago
|
|
||
|
if (type.Has<RelationAttribute>()) entity.Add<Doc.Relation>();
|
||
2 years ago
|
}
|
||
2 years ago
|
|
||
1 year ago
|
return moduleEntity;
|
||
2 years ago
|
|
||
|
} else {
|
||
|
|
||
1 year ago
|
var path = GetModulePath(moduleType);
|
||
|
var module = new ModuleInfo(Universe, moduleType, path);
|
||
2 years ago
|
_modules.Add(module.Entity, module);
|
||
2 years ago
|
TryEnableModule(module);
|
||
2 years ago
|
return module.Entity;
|
||
2 years ago
|
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private void TryEnableModule(ModuleInfo module)
|
||
|
{
|
||
|
if (module.UnmetDependencies.Count > 0) return;
|
||
1 year ago
|
|
||
|
Console.WriteLine($"Enabling module {module.Path} ...");
|
||
2 years ago
|
module.Enable();
|
||
|
|
||
|
// Find other modules that might be missing this module as a dependency.
|
||
|
foreach (var other in _modules.Values) {
|
||
2 years ago
|
if (other.IsActive) continue;
|
||
2 years ago
|
if (!other.UnmetDependencies.Contains(module.Entity)) continue;
|
||
2 years ago
|
|
||
|
// Move the just enabled module from unmet to met depedencies.
|
||
2 years ago
|
other.UnmetDependencies.Remove(module.Entity);
|
||
2 years ago
|
other.MetDependencies.Add(module);
|
||
|
|
||
|
TryEnableModule(other);
|
||
|
}
|
||
|
}
|
||
|
|
||
2 years ago
|
public static EntityPath GetModulePath(Type type)
|
||
2 years ago
|
{
|
||
|
var attr = type.Get<ModuleAttribute>();
|
||
|
if (attr == null) throw new ArgumentException(
|
||
|
$"Module {type} must be marked with ModuleAttribute", nameof(type));
|
||
2 years ago
|
|
||
1 year ago
|
var path = EntityPath.Parse(
|
||
|
(type.Get<PathAttribute>() is PathAttribute pathAttr)
|
||
|
? pathAttr.Value : type.Name);
|
||
2 years ago
|
|
||
1 year ago
|
// If specified path is absolute, return it now.
|
||
|
if (path.IsAbsolute) return path;
|
||
2 years ago
|
|
||
2 years ago
|
// 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)..];
|
||
2 years ago
|
|
||
1 year ago
|
var parts = fullNameWithoutAssembly.Split('.')[..^1];
|
||
|
return new(true, parts.Prepend(assemblyName).Concat(path.GetParts()).ToArray());
|
||
2 years ago
|
}
|
||
|
|
||
1 year ago
|
internal class ModuleInfo
|
||
2 years ago
|
{
|
||
1 year ago
|
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 dependsAttr in Type.GetMultiple<AddRelationAttribute>().Where(attr =>
|
||
|
attr.GetType().GetGenericTypeDefinition() == typeof(DependsOnAttribute<>))) {
|
||
|
var dependsPath = GetModulePath(dependsAttr.Target);
|
||
|
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);
|
||
|
}
|
||
2 years ago
|
|
||
1 year ago
|
Entity = module.Build().CreateLookup(Type);
|
||
2 years ago
|
|
||
1 year ago
|
// Ensure all parent entities have Module set.
|
||
|
for (var p = Entity.Parent; p != null; p = p.Parent)
|
||
|
p.Add<Module>();
|
||
2 years ago
|
}
|
||
|
|
||
1 year ago
|
public void Enable()
|
||
|
{
|
||
|
Entity.Enable();
|
||
|
Instance = Activator.CreateInstance(Type)!;
|
||
|
foreach (var type in Type.GetNestedTypes(BindingFlags.Public | BindingFlags.NonPublic))
|
||
|
RegisterNestedType(type);
|
||
|
(Instance as IModuleInitializer)?.Initialize(Entity);
|
||
|
RegisterMethods(Instance);
|
||
|
}
|
||
2 years ago
|
|
||
1 year ago
|
private void RegisterNestedType(Type type)
|
||
|
{
|
||
|
if (!type.GetCustomAttributes(true).OfType<ICreateEntityAttribute>().Any()) return;
|
||
2 years ago
|
|
||
|
// If proxied type is specified, use it instead of the marked type.
|
||
|
// Attributes are still read from the original type.
|
||
|
var proxyType = type.Get<ProxyAttribute>()?.Type ?? type;
|
||
2 years ago
|
|
||
1 year ago
|
if (!type.Has<ComponentAttribute>() && (!proxyType.IsValueType || (proxyType.GetFields().Length > 0))) {
|
||
2 years ago
|
var typeHint = (proxyType != type) ? $"{proxyType.Name} (proxied by {type})" : type.ToString();
|
||
|
throw new Exception($"Type {typeHint} must be an empty, used-defined struct.");
|
||
2 years ago
|
}
|
||
|
|
||
1 year ago
|
var path = EntityPath.Parse(type.Get<PathAttribute>()?.Value ?? proxyType.Name);
|
||
2 years ago
|
var builder = path.IsAbsolute ? Universe.New(path) : Entity.NewChild(path);
|
||
2 years ago
|
|
||
1 year ago
|
if (type.Get<SymbolAttribute>() is SymbolAttribute symbolAttr)
|
||
|
builder.Symbol(symbolAttr.Value ?? path.Name);
|
||
1 year ago
|
|
||
2 years ago
|
var entity = builder.Build();
|
||
2 years ago
|
|
||
2 years ago
|
EntityRef Lookup(Type toLookup)
|
||
1 year ago
|
=> (type != toLookup) ? Universe.LookupByTypeOrThrow(toLookup) : entity;
|
||
2 years ago
|
foreach (var attr in type.GetMultiple<AddEntityAttribute>())
|
||
|
entity.Add(Lookup(attr.Entity));
|
||
|
foreach (var attr in type.GetMultiple<AddRelationAttribute>())
|
||
|
entity.Add(Lookup(attr.Relation), Lookup(attr.Target));
|
||
2 years ago
|
|
||
1 year ago
|
if (type.Get<SingletonAttribute>()?.AutoAdd == true) entity.Add(entity);
|
||
1 year ago
|
if (type.Has<ComponentAttribute>()) entity.InitComponent(proxyType);
|
||
2 years ago
|
else entity.CreateLookup(proxyType);
|
||
1 year ago
|
if (type.Has<RelationAttribute>()) entity.Add<Doc.Relation>();
|
||
|
if (type.Has<TagAttribute>()) entity.Add<Tag>();
|
||
2 years ago
|
}
|
||
|
|
||
1 year ago
|
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);
|
||
|
}
|
||
2 years ago
|
}
|
||
|
}
|
||
|
}
|