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.
118 lines
3.3 KiB
118 lines
3.3 KiB
using System; |
|
using System.Collections.Generic; |
|
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, IModule, new() |
|
{ |
|
var moduleType = typeof(T); |
|
if (moduleType.IsGenericType) throw new Exception( |
|
$"Module {moduleType} must be a non-generic class"); |
|
|
|
var module = new ModuleInfo<T>(Universe); |
|
_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.Entity.GetFullPath()} ..."); |
|
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); |
|
} |
|
} |
|
|
|
internal abstract class ModuleInfo |
|
{ |
|
public abstract EntityRef Entity { get; } |
|
public abstract bool IsActive { get; } |
|
|
|
public HashSet<ModuleInfo> MetDependencies { get; } = new(); |
|
public HashSet<Entity> UnmetDependencies { get; } = new(); |
|
|
|
public abstract void Enable(); |
|
} |
|
|
|
internal class ModuleInfo<T> : ModuleInfo |
|
where T : IModule, new() |
|
{ |
|
public override EntityRef Entity { get; } |
|
public override bool IsActive => (Instance != null); |
|
public T? Instance { get; internal set; } |
|
|
|
public ModuleInfo(Universe universe) |
|
{ |
|
var builder = universe.New(T.ModulePath).Add<Module>(); |
|
|
|
foreach (var dependsPath in T.Dependencies) { |
|
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); builder.Disable(); } |
|
|
|
builder.Add<DependsOn>(dependency); |
|
} |
|
|
|
Entity = builder.Build().CreateLookup<T>(); |
|
|
|
// Ensure all parent entities have Module set. |
|
for (var p = Entity.Parent; p != null; p = p.Parent) |
|
p.Add<Module>(); |
|
} |
|
|
|
public override void Enable() |
|
{ |
|
Entity.Enable(); |
|
Instance = new T(); |
|
(Instance as IModuleAutoRegisterComponents)?.RegisterComponents(Entity); |
|
(Instance as IModuleInitializer)?.Initialize(Entity); |
|
RegisterMethods(Instance); |
|
} |
|
|
|
private void RegisterMethods(object? instance) |
|
{ |
|
var world = Entity.World; |
|
foreach (var method in typeof(T).GetMethods( |
|
BindingFlags.Public | BindingFlags.NonPublic | |
|
BindingFlags.Static | BindingFlags.Instance |
|
)) { |
|
if (method.Has<SystemAttribute>()) |
|
world.InitSystem(instance, method).ChildOf(Entity); |
|
if (method.Has<ObserverAttribute>()) |
|
world.InitObserver(instance, method).ChildOf(Entity); |
|
} |
|
} |
|
} |
|
}
|
|
|