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.
 
 

105 lines
3.2 KiB

using System;
using gaemstone.ECS;
using static gaemstone.Flecs.Core;
using Module = gaemstone.Flecs.Core.Module;
namespace gaemstone;
public class ModuleManager<TContext>
{
public Universe<TContext> Universe { get; }
public World<TContext> World => Universe.World;
private readonly Rule<TContext> _findDisabledDeps;
private readonly Rule<TContext> _findDependents;
private readonly ECS.Variable _findDisabledDepsThisVar;
private readonly ECS.Variable _findDependentsModuleVar;
public ModuleManager(Universe<TContext> universe)
{
Universe = universe;
World.New("/gaemstone/ModuleInfo").Symbol("ModuleInfo")
.Build().InitComponent<IModuleInfo>();
_findDisabledDeps = World.Rule(new("(DependsOn, $dep), Disabled($dep)"));
_findDependents = World.Rule(new("ModuleInfo, Disabled, (DependsOn, $module)"));
_findDisabledDepsThisVar = _findDisabledDeps.ThisVar
?? throw new InvalidOperationException($"Could not find $this of {nameof(_findDisabledDeps)}");
_findDependentsModuleVar = _findDependents.Variables["module"]
?? throw new InvalidOperationException($"Could not find $module of {nameof(_findDependents)}");
}
public Entity<TContext> Import<T>()
where T : IModule, IModuleImport
{
foreach (var dep in T.Dependencies)
if (World.LookupPathOrNull(dep) == null) throw new InvalidOperationException(
$"Missing required dependency {dep} for built-in module {T.Path}");
Console.WriteLine($"Importing built-in module {T.Path}");
var entity = T.Import(World);
entity.Set<IModuleInfo>(new ModuleInfo<T>());
T.OnEnable(entity);
return entity;
}
public Entity<TContext> Register<T>()
where T : IModule
{
if (T.IsBuiltIn) throw new ArgumentException(
$"Unexpected operation, {T.Path} is a built-in module");
var builder = World.New(T.Path)
.Add<Module>().Add<Disabled>()
.Set<IModuleInfo>(new ModuleInfo<T>());
foreach (var depPath in T.Dependencies) {
var dependency = World.LookupPathOrNull(depPath) ??
World.New(depPath).Add<Module>().Add<Disabled>().Build();
builder.Add<DependsOn>(dependency);
}
var module = builder.Build().CreateLookup<T>();
// Ensure all parent entities have the Module tag set.
for (var p = module.Parent; p is Entity<TContext> parent; p = parent.Parent)
parent.Add<Module>();
Console.WriteLine($"Registered module {module.Path}");
TryEnableModule(module);
return module;
}
private void TryEnableModule(Entity<TContext> module)
{
// Skip if module is already enabled.
if (module.IsEnabled) return;
// Skip if module has any not-yet-enabled dependencies.
if (_findDisabledDeps.Iter().SetVar(_findDisabledDepsThisVar, module).Any()) return;
Console.WriteLine($"Enabling module {module.Path}");
module.GetOrThrow<IModuleInfo>().OnEnable(module);
module.Enable();
// Get all modules that depend on this one and try to enabled them if they now have their dependencies met.
foreach (var dependent in _findDependents.Iter().SetVar(_findDependentsModuleVar, module).GetAllEntities())
TryEnableModule(dependent);
}
public interface IModuleInfo
{
void OnEnable(Entity<TContext> entity);
}
internal class ModuleInfo<T> : IModuleInfo
where T : IModule
{
public void OnEnable(Entity<TContext> entity)
=> T.OnEnable(entity);
}
}