diff --git a/src/gaemstone.SourceGen/Descriptors.cs b/src/gaemstone.SourceGen/Descriptors.cs index 9e11d24..6632ccb 100644 --- a/src/gaemstone.SourceGen/Descriptors.cs +++ b/src/gaemstone.SourceGen/Descriptors.cs @@ -36,6 +36,12 @@ public static class Descriptors // Diagnostics relating to the combined usage of attributes. + public static readonly DiagnosticDescriptor AttributesRequireTypeOrPath = new( + $"gSG{_idCounter++:00}", "Entity attributes require type or path", + "Entity attributes require a type (such as [Entity]) or [Path].", + DiagnosticCategory, DiagnosticSeverity.Error, true); + + public static readonly DiagnosticDescriptor InvalidAttributeCombination = new( $"gSG{_idCounter++:00}", "Invalid combination of entity attributes", "The combination of entity attributes {0} is not valid.", @@ -61,7 +67,10 @@ public static class Descriptors "A [BuiltIn, Module] must also have a [Path] set.", DiagnosticCategory, DiagnosticSeverity.Info, true); - + public static readonly DiagnosticDescriptor BuiltInModuleMustNotHaveMethods = new( + $"gSG{_idCounter++:00}", "BuiltIn Module must not have System / Observer", + "A [BuiltIn, Module] must not have [System] or [Observer] methods.", + DiagnosticCategory, DiagnosticSeverity.Info, true); // Diagnostics relating to keywords / modifiers used with the symbol. @@ -182,6 +191,7 @@ public static class Descriptors DiagnosticCategory, DiagnosticSeverity.Error, true); + // TODO: Make this more descriptive. (Special = Has<>, Not<> and [Tag]?) public static readonly DiagnosticDescriptor SpecialMustNotBeByRef = new( $"gSG{_idCounter++:00}", "Special parameter must not be ByRef", "Special parameter must not be ByRef (in/out/ref).", diff --git a/src/gaemstone.SourceGen/ModuleGenerator.cs b/src/gaemstone.SourceGen/ModuleGenerator.cs index ac3a335..af13570 100644 --- a/src/gaemstone.SourceGen/ModuleGenerator.cs +++ b/src/gaemstone.SourceGen/ModuleGenerator.cs @@ -107,6 +107,7 @@ public class ModuleGenerator """); + // TODO: Built-in modules should not have dependencies. var dependencies = module.GetDependencies().ToList(); sb.Append("\tstatic IReadOnlyList IModule.Dependencies { get; } = "); if (dependencies.Count > 0) { @@ -155,26 +156,30 @@ public class ModuleGenerator foreach (var e in entities) { var @var = $"_{e.Name}_Entity"; var path = (e.EntityPath ?? e.Name).ToStringLiteral(); - sb.AppendLine($"\t\tvar {@var} = world.New({path}, module)"); - if (e.EntitySymbol != null) - sb.AppendLine($"\t\t\t.Symbol({e.EntitySymbol.ToStringLiteral()})"); + + // When looking for a BuiltIn entity, error if it doesn't exist. + var lookupFunc = (e.IsBuiltIn ? "LookupPathOrThrow" : "New"); + sb.AppendLine($"\t\tvar {@var} = world.{lookupFunc}({path}, module)"); // Since this is a custom gaemstone tag, we want to add it even for [BuiltIn] modules. if (e.IsRelation) sb.AppendLine("\t\t\t.Add()"); + // Other than [Relation], we don't add anything to entities defined in [BuiltIn] modules. if (module.IsBuiltIn) { - sb.AppendLine($"\t\t\t.Build().CreateLookup<{e.FullName}>()"); + sb.AppendLine($"\t\t\t.CreateLookup<{e.FullName}>()"); } else { - // TODO: if (e.IsPublic) sb.AppendLine($"\t\t\t.Symbol(\"{e.Name}\")"); + if (e.EntitySymbol != null) + sb.AppendLine($"\t\t\t.Symbol({e.EntitySymbol.ToStringLiteral()})"); // Tags and relations in Flecs are marked as empty components. if (e.IsTag || e.IsRelation) sb.AppendLine("\t\t\t.Add()"); if (e.IsTag && e.IsRelation) sb.AppendLine("\t\t\t.Add()"); - sb.Append( "\t\t\t.Build()"); + sb.Append( "\t\t\t"); + if (!e.IsBuiltIn) sb.Append(".Build()"); if (e.IsComponent) sb.Append($".InitComponent<{e.FullName}>()"); else sb.Append($".CreateLookup<{e.FullName}>()"); sb.AppendLine(); diff --git a/src/gaemstone.SourceGen/Structure/MethodEntityInfo.cs b/src/gaemstone.SourceGen/Structure/MethodEntityInfo.cs index 43a0067..e9e1e85 100644 --- a/src/gaemstone.SourceGen/Structure/MethodEntityInfo.cs +++ b/src/gaemstone.SourceGen/Structure/MethodEntityInfo.cs @@ -45,6 +45,9 @@ public class MethodEntityInfo : BaseEntityInfo if (IsSystem && IsObserver) yield return Diagnostic.Create( Descriptors.InvalidAttributeCombination, Location, "[System, Observer]"); + if (Parent is ModuleEntityInfo { IsBuiltIn: true }) yield return Diagnostic.Create( + Descriptors.BuiltInModuleMustNotHaveMethods, Location); + if (Symbol.IsAbstract) yield return Diagnostic.Create( Descriptors.MethodMustNotBeAbstract, Location); if (Symbol.IsAsync) yield return Diagnostic.Create( diff --git a/src/gaemstone.SourceGen/Structure/ModuleEntityInfo.cs b/src/gaemstone.SourceGen/Structure/ModuleEntityInfo.cs index 6bee8aa..172f13e 100644 --- a/src/gaemstone.SourceGen/Structure/ModuleEntityInfo.cs +++ b/src/gaemstone.SourceGen/Structure/ModuleEntityInfo.cs @@ -10,7 +10,6 @@ namespace gaemstone.SourceGen.Structure; public class ModuleEntityInfo : TypeEntityInfo { public bool IsPartial { get; } - public bool IsBuiltIn { get; } public bool HasInitializer { get; } public ModuleEntityInfo(ISymbol symbol) @@ -19,10 +18,10 @@ public class ModuleEntityInfo : TypeEntityInfo var classDecl = (TypeDeclarationSyntax)Symbol.DeclaringSyntaxReferences.First().GetSyntax(); IsPartial = classDecl.Modifiers.Any(t => t.IsKind(SyntaxKind.PartialKeyword)); - IsBuiltIn = Has("BuiltIn"); - HasInitializer = Symbol.AllInterfaces.Any(i => i.GetFullName(FullNameStyle.NoGeneric) == "gaemstone.ECS.IModuleInitializer"); + + IsBuiltIn = Has("BuiltIn"); } protected override IEnumerable ValidateSelf() diff --git a/src/gaemstone.SourceGen/Structure/TypeEntityInfo.cs b/src/gaemstone.SourceGen/Structure/TypeEntityInfo.cs index e28dc65..0534588 100644 --- a/src/gaemstone.SourceGen/Structure/TypeEntityInfo.cs +++ b/src/gaemstone.SourceGen/Structure/TypeEntityInfo.cs @@ -17,6 +17,11 @@ public class TypeEntityInfo : BaseEntityInfo public bool IsSingleton { get; } public bool IsRelation { get; } + // Built-in entities error if they don't exist. + // When a module is built-in, most attributes become descriptive, + // and don't change or affect the entity they're attached to. + public bool IsBuiltIn { get; protected set; } + public bool IsValueType => Symbol.IsValueType; public bool IsReferenceType => Symbol.IsReferenceType; // TODO: Make sure that component has (instance) fields, non-component entity does not have fields. @@ -30,6 +35,10 @@ public class TypeEntityInfo : BaseEntityInfo IsComponent = Has("Component"); IsSingleton = Has("Singleton"); IsRelation = Has("Relation"); + + IsBuiltIn = !IsModule && !IsEntity && !IsTag + && !IsComponent && !IsSingleton && !IsRelation + && (EntityPath != null); } protected override IEnumerable ValidateSelf() @@ -49,7 +58,14 @@ public class TypeEntityInfo : BaseEntityInfo if (IsComponent) attributeList.Add("Component"); if (IsSingleton) attributeList.Add("Singleton"); - switch (attributeList) { + switch (attributeList) + { + // No attributes are only valid if [Path] is set. + case [ ]: + if (!IsBuiltIn) yield return Diagnostic.Create( + Descriptors.AttributesRequireTypeOrPath, Location, + $"[{string.Join(", ", attributeList)}]"); + break; // A single attribute is valid. case [ _ ]: // The following are valid attribute combinations. @@ -70,6 +86,10 @@ public class TypeEntityInfo : BaseEntityInfo break; } + // In in a built-in module, this entity is built-in, too. + if (Parent is ModuleEntityInfo { IsBuiltIn: true }) + IsBuiltIn = true; + // Singletons are special kinds of components. if (IsSingleton) IsComponent = true; } diff --git a/src/gaemstone/Universe+Modules.cs b/src/gaemstone/Universe+Modules.cs index 799f33c..906ab18 100644 --- a/src/gaemstone/Universe+Modules.cs +++ b/src/gaemstone/Universe+Modules.cs @@ -91,11 +91,18 @@ public class ModuleManager public ModuleInfo(Universe universe) { - var world = universe.World; - var builder = world.New(T.Path); - var deps = new List(); + var world = universe.World; + + if (T.IsBuiltIn) + { + Entity = world.LookupPathOrThrow(T.Path); + Dependencies = Array.Empty(); + } + else + { + var builder = world.New(T.Path); + var deps = new List(); - if (!T.IsBuiltIn) { builder.Add(); foreach (var dependsPath in T.Dependencies) { var dependency = world.LookupPathOrNull(dependsPath) ?? @@ -108,15 +115,14 @@ public class ModuleManager if (!isDepInit) builder.Add(); builder.Add(dependency); } - } - Entity = builder.Build().CreateLookup(); - Dependencies = deps.AsReadOnly(); + Entity = builder.Build().CreateLookup(); + Dependencies = deps.AsReadOnly(); - if (!T.IsBuiltIn) // Ensure all parent entities have the Module tag set. for (var p = Entity.Parent; p is Entity parent; p = parent.Parent) parent.Add(); + } } public void Enable()