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.
 
 

135 lines
3.6 KiB

using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using gaemstone.ECS;
using static gaemstone.Flecs.Core;
using Module = gaemstone.Flecs.Core.Module;
namespace gaemstone;
public class ModuleManager<TContext>
: IEnumerable<ModuleManager<TContext>.IModuleInfo>
{
private readonly Dictionary<Entity<TContext>, IModuleInfo> _modules = new();
public Universe<TContext> Universe { get; }
public ModuleManager(Universe<TContext> universe)
=> Universe = universe;
internal IModuleInfo? Lookup(Entity<TContext> entity)
=> _modules.GetValueOrDefault(entity);
public Entity<TContext> Register<T>()
where T : IModule
{
// if (!typeof(T).IsAssignableTo(typeof(IModule))) throw new ArgumentException(
// $"The specified type {typeof(T)} does not implement IModule", nameof(T));
var module = new ModuleInfo<T>(Universe);
_modules.Add(module.Entity, module);
TryEnableModule(module);
return module.Entity;
}
private void TryEnableModule(IModuleInfo module)
{
if (!module.Dependencies.All(dep => dep.IsDependencyMet)) return;
Console.WriteLine($"Enabling module {module.Entity.Path}");
module.Enable();
// Find other modules that might be missing this module as a dependency.
foreach (var other in _modules.Values) {
if (other.IsInitialized) continue;
var dependency = other.Dependencies.FirstOrDefault(dep => dep.Entity == module.Entity);
if (dependency == null) continue;
dependency.Info = module;
dependency.IsDependencyMet = true;
TryEnableModule(other);
}
}
public interface IModuleInfo
{
Entity<TContext> Entity { get; }
IReadOnlyCollection<ModuleDependency> Dependencies { get; }
bool IsInitialized { get; }
void Enable();
}
// IEnumerable implementation
public IEnumerator<IModuleInfo> GetEnumerator() => _modules.Values.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
public class ModuleDependency
{
public Entity<TContext> Entity { get; }
public IModuleInfo? Info { get; internal set; }
public bool IsDependencyMet { get; internal set; }
public ModuleDependency(Entity<TContext> entity,
IModuleInfo? info = null, bool isDependencyMet = false)
{
Entity = entity;
Info = info;
IsDependencyMet = isDependencyMet;
}
}
internal class ModuleInfo<T> : IModuleInfo
where T : IModule
{
public Entity<TContext> Entity { get; }
public IReadOnlyCollection<ModuleDependency> Dependencies { get; }
public bool IsInitialized { get; private set; }
public ModuleInfo(Universe<TContext> universe)
{
var world = universe.World;
if (T.IsBuiltIn)
{
Entity = world.LookupPathOrThrow(T.Path);
Dependencies = Array.Empty<ModuleDependency>();
}
else
{
var builder = world.New(T.Path);
var deps = new List<ModuleDependency>();
builder.Add<Module>();
foreach (var dependsPath in T.Dependencies) {
var dependency = world.LookupPathOrNull(dependsPath) ??
world.New(dependsPath).Add<Module>().Add<Disabled>().Build();
var depModule = universe.Modules.Lookup(dependency);
var isDepInit = (depModule?.IsInitialized == true);
deps.Add(new(dependency, depModule, isDepInit));
if (!isDepInit) builder.Add<Disabled>();
builder.Add<DependsOn>(dependency);
}
Entity = builder.Build().CreateLookup<T>();
Dependencies = deps.AsReadOnly();
// Ensure all parent entities have the Module tag set.
for (var p = Entity.Parent; p is Entity<TContext> parent; p = parent.Parent)
parent.Add<Module>();
}
}
public void Enable()
{
Entity.Enable();
T.Initialize(Entity);
IsInitialized = true;
}
}
}