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
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); |
|
} |
|
}
|
|
|