diff --git a/src/gaemstone.SourceGen/Generators/AutoRegisterComponentsGenerator.cs b/src/gaemstone.SourceGen/Generators/AutoRegisterComponentsGenerator.cs
index e744a70..75b4fc9 100644
--- a/src/gaemstone.SourceGen/Generators/AutoRegisterComponentsGenerator.cs
+++ b/src/gaemstone.SourceGen/Generators/AutoRegisterComponentsGenerator.cs
@@ -123,7 +123,7 @@ public partial class AutoRegisterComponentsGenerator
//
using gaemstone.ECS;
- namespace {{module.Namespace}};
+ namespace {{ module.Namespace }};
""");
@@ -146,7 +146,7 @@ public partial class AutoRegisterComponentsGenerator
}
} else {
sb.AppendLine($$"""
- public partial class {{module.Name}}
+ public partial class {{ module.Name }}
: IModuleAutoRegisterComponents
{
public void RegisterComponents(EntityRef module)
diff --git a/src/gaemstone.SourceGen/Generators/ModuleGenerator.cs b/src/gaemstone.SourceGen/Generators/ModuleGenerator.cs
index f3b8f30..880f4e8 100644
--- a/src/gaemstone.SourceGen/Generators/ModuleGenerator.cs
+++ b/src/gaemstone.SourceGen/Generators/ModuleGenerator.cs
@@ -1,5 +1,6 @@
using System.Collections.Generic;
using System.Linq;
+using System.Text;
using gaemstone.SourceGen.Utility;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
@@ -71,6 +72,70 @@ public class ModuleGenerator
.ConstructorArguments.FirstOrDefault().Value == null))
context.ReportDiagnostic(Diagnostic.Create(ModuleBuiltInMustHavePath,
symbol.Locations.FirstOrDefault(), symbol.GetFullName()));
+
+ // Static classes can't implement interfaces.
+ if (symbol.IsStatic) continue;
+
+ var modulePath = GetModulePath(symbol).ToStringLiteral();
+ var dependencies = new List();
+ foreach (var attr in symbol.GetAttributes())
+ for (var type = attr.AttributeClass; type != null; type = type.BaseType)
+ if ((type.GetFullName(true) == "gaemstone.ECS.AddAttribute`2")
+ && type.TypeArguments[0].GetFullName() == "gaemstone.Flecs.Core.DependsOn")
+ dependencies.Add(GetModulePath(type.TypeArguments[1]));
+
+ var sb = new StringBuilder();
+ sb.AppendLine($$"""
+ //
+ using System.Collections.Generic;
+ using System.Linq;
+ using gaemstone.ECS;
+
+ namespace {{ symbol.GetNamespace() }};
+
+ public partial class {{ symbol.Name }}
+ : IModule
+ {
+ public static string ModulePath { get; }
+ = {{ modulePath }};
+
+ """);
+
+ if (dependencies.Count > 0) {
+ sb.AppendLine($$"""
+ public static IEnumerable Dependencies { get; } = new[] {
+ """);
+ foreach (var dependency in dependencies)
+ sb.AppendLine($$""" {{ dependency.ToStringLiteral() }},""");
+ sb.AppendLine($$"""
+ };
+ """);
+ } else sb.AppendLine($$"""
+ public static IEnumerable Dependencies { get; } = Enumerable.Empty();
+ """);
+
+
+ sb.AppendLine($$"""
+ }
+
+ """);
+
+ context.AddSource($"{symbol.GetFullName()}_Module.g.cs", sb.ToString());
+ }
+ }
+
+ private static string GetModulePath(ISymbol module)
+ {
+ var path = module.GetAttribute("gaemstone.ECS.PathAttribute")?
+ .ConstructorArguments.FirstOrDefault().Value as string;
+ var isAbsolute = (path?.FirstOrDefault() == '/');
+ if (isAbsolute) return path!;
+
+ var fullPath = module.GetFullName().Replace('.', '/');
+ if (path != null) {
+ var index = fullPath.LastIndexOf('/');
+ fullPath = $"{fullPath.Substring(0, index)}/{path}";
}
+ return $"/{fullPath}";
}
}
diff --git a/src/gaemstone/ECS/Module.cs b/src/gaemstone/ECS/Module.cs
index 77671be..3e01260 100644
--- a/src/gaemstone/ECS/Module.cs
+++ b/src/gaemstone/ECS/Module.cs
@@ -1,16 +1,27 @@
using System;
+using System.Collections.Generic;
namespace gaemstone.ECS;
[AttributeUsage(AttributeTargets.Class)]
public class ModuleAttribute : Attribute { }
-public interface IModuleAutoRegisterComponents
+public interface IModuleInitializer
{
- void RegisterComponents(EntityRef module);
+ void Initialize(EntityRef module);
}
-public interface IModuleInitializer
+// The following will be implemented on [Module]
+// types automatically for you by source generators.
+
+public interface IModule
{
- void Initialize(EntityRef module);
+ static abstract string ModulePath { get; }
+
+ static abstract IEnumerable Dependencies { get; }
+}
+
+public interface IModuleAutoRegisterComponents
+{
+ void RegisterComponents(EntityRef module);
}
diff --git a/src/gaemstone/Universe+Modules.cs b/src/gaemstone/Universe+Modules.cs
index f247866..b0ea253 100644
--- a/src/gaemstone/Universe+Modules.cs
+++ b/src/gaemstone/Universe+Modules.cs
@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
-using System.Linq;
using System.Reflection;
using gaemstone.ECS;
using gaemstone.Utility;
@@ -21,16 +20,13 @@ public class ModuleManager
=> _modules.GetValueOrDefault(entity);
public EntityRef Register()
- where T : class, new()
+ where T : class, IModule, new()
{
var moduleType = typeof(T);
if (moduleType.IsGenericType) throw new Exception(
$"Module {moduleType} must be a non-generic class");
- if (!moduleType.Has()) throw new Exception(
- $"Module {moduleType} must be marked with ModuleAttribute");
- var path = GetModulePath(moduleType);
- var module = new ModuleInfo(Universe, moduleType, path);
+ var module = new ModuleInfo(Universe);
_modules.Add(module.Entity, module);
TryEnableModule(module);
return module.Entity;
@@ -40,7 +36,7 @@ public class ModuleManager
{
if (module.UnmetDependencies.Count > 0) return;
- Console.WriteLine($"Enabling module {module.Path} ...");
+ Console.WriteLine($"Enabling module {module.Entity.GetFullPath()} ...");
module.Enable();
// Find other modules that might be missing this module as a dependency.
@@ -56,102 +52,66 @@ public class ModuleManager
}
}
- public static EntityPath GetModulePath(Type type)
+ internal abstract class ModuleInfo
{
- var attr = type.Get();
- if (attr == null) throw new ArgumentException(
- $"Module {type} must be marked with ModuleAttribute", nameof(type));
+ public abstract EntityRef Entity { get; }
+ public abstract bool IsActive { get; }
- var path = EntityPath.Parse(
- (type.Get() is PathAttribute pathAttr)
- ? pathAttr.Value : type.Name);
-
- // If specified path is absolute, return it now.
- if (path.IsAbsolute) return path;
-
- // 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)..];
+ public HashSet MetDependencies { get; } = new();
+ public HashSet UnmetDependencies { get; } = new();
- var parts = fullNameWithoutAssembly.Split('.')[..^1];
- return new(true, parts.Prepend(assemblyName).Concat(path.GetParts()).ToArray());
+ public abstract void Enable();
}
- internal class ModuleInfo
+ internal class ModuleInfo : ModuleInfo
+ where T : IModule, new()
{
- public Universe Universe { get; }
- public Type Type { get; }
- public EntityPath Path { get; }
+ public override EntityRef Entity { get; }
+ public override bool IsActive => (Instance != null);
+ public T? Instance { get; internal set; }
- public EntityRef Entity { get; }
- public object? Instance { get; internal set; }
- public bool IsActive => Instance != null;
+ public ModuleInfo(Universe universe)
+ {
+ var builder = universe.New(T.ModulePath).Add();
- public HashSet MetDependencies { get; } = new();
- public HashSet UnmetDependencies { get; } = new();
+ foreach (var dependsPath in T.Dependencies) {
+ var dependency = universe.LookupByPath(dependsPath) ??
+ universe.New(dependsPath).Add().Disable().Build();
- 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();
-
- // Add module dependencies from [DependsOn<>] attributes.
- foreach (var attr in Type.GetCustomAttributes()) {
- // TODO: Do this without reflection.
- var attrType = attr.GetType();
- if (!attrType.IsGenericType) continue;
- if (attrType.GetGenericTypeDefinition() != typeof(DependsOnAttribute<>)) continue;
-
- var dependsTarget = attrType.GenericTypeArguments[0];
- var dependsPath = GetModulePath(dependsTarget);
- var dependency = Universe.LookupByPath(dependsPath) ??
- Universe.New(dependsPath).Add().Disable().Build();
-
- var depModule = Universe.Modules.Lookup(dependency);
+ var depModule = universe.Modules.Lookup(dependency);
if (depModule?.IsActive == true) MetDependencies.Add(depModule);
- else { UnmetDependencies.Add(dependency); module.Disable(); }
+ else { UnmetDependencies.Add(dependency); builder.Disable(); }
- module.Add(dependency);
+ builder.Add(dependency);
}
- Entity = module.Build().CreateLookup(Type);
+ Entity = builder.Build().CreateLookup();
// Ensure all parent entities have Module set.
for (var p = Entity.Parent; p != null; p = p.Parent)
p.Add();
}
- public void Enable()
+ public override void Enable()
{
Entity.Enable();
- Instance = Activator.CreateInstance(Type)!; // TODO: Replace with generic new() somehow.
- if (Instance is IModuleAutoRegisterComponents generatedComponents)
- generatedComponents.RegisterComponents(Entity);
+ Instance = new T();
+ (Instance as IModuleAutoRegisterComponents)?.RegisterComponents(Entity);
(Instance as IModuleInitializer)?.Initialize(Entity);
RegisterMethods(Instance);
}
private void RegisterMethods(object? instance)
{
- foreach (var method in Type.GetMethods(
+ var world = Entity.World;
+ foreach (var method in typeof(T).GetMethods(
BindingFlags.Public | BindingFlags.NonPublic |
BindingFlags.Static | BindingFlags.Instance
)) {
if (method.Has())
- Universe.InitSystem(instance, method).ChildOf(Entity);
+ world.InitSystem(instance, method).ChildOf(Entity);
if (method.Has())
- Universe.InitObserver(instance, method).ChildOf(Entity);
+ world.InitObserver(instance, method).ChildOf(Entity);
}
}
}