Improve built-in entities

- Allow specifying just [Path] for entity, making it built-in
- Built-in entities error if the entity doesn't already exist
wip/source-generators
copygirl 12 months ago
parent 03b7e5ce42
commit c97c4a530f
  1. 12
      src/gaemstone.SourceGen/Descriptors.cs
  2. 17
      src/gaemstone.SourceGen/ModuleGenerator.cs
  3. 3
      src/gaemstone.SourceGen/Structure/MethodEntityInfo.cs
  4. 5
      src/gaemstone.SourceGen/Structure/ModuleEntityInfo.cs
  5. 22
      src/gaemstone.SourceGen/Structure/TypeEntityInfo.cs
  6. 22
      src/gaemstone/Universe+Modules.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).",

@ -107,6 +107,7 @@ public class ModuleGenerator
""");
// TODO: Built-in modules should not have dependencies.
var dependencies = module.GetDependencies().ToList();
sb.Append("\tstatic IReadOnlyList<string> 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<gaemstone.Doc.Relation>()");
// 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<gaemstone.Flecs.Core.Component>()");
if (e.IsTag && e.IsRelation) sb.AppendLine("\t\t\t.Add<gaemstone.Flecs.Core.Tag>()");
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();

@ -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(

@ -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<Diagnostic> ValidateSelf()

@ -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<Diagnostic> 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;
}

@ -91,11 +91,18 @@ public class ModuleManager<TContext>
public ModuleInfo(Universe<TContext> universe)
{
var world = universe.World;
var builder = world.New(T.Path);
var deps = new List<ModuleDependency>();
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>();
if (!T.IsBuiltIn) {
builder.Add<Module>();
foreach (var dependsPath in T.Dependencies) {
var dependency = world.LookupPathOrNull(dependsPath) ??
@ -108,15 +115,14 @@ public class ModuleManager<TContext>
if (!isDepInit) builder.Add<Disabled>();
builder.Add<DependsOn>(dependency);
}
}
Entity = builder.Build().CreateLookup<T>();
Dependencies = deps.AsReadOnly();
Entity = builder.Build().CreateLookup<T>();
Dependencies = deps.AsReadOnly();
if (!T.IsBuiltIn)
// 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()

Loading…
Cancel
Save