From 03b7e5ce42e22d4dd9969411523bc9bd2d8d6b5c Mon Sep 17 00:00:00 2001 From: copygirl Date: Mon, 8 May 2023 22:07:54 +0200 Subject: [PATCH] Add [Set] attribute to set entity's component --- src/gaemstone.SourceGen/ModuleGenerator.cs | 3 + .../RelevantSymbolReceiver.cs | 3 +- .../Structure/BaseEntityInfo.cs | 61 +++++++++++++------ src/gaemstone/ECS/Module+Attributes.cs | 16 +++++ 4 files changed, 62 insertions(+), 21 deletions(-) diff --git a/src/gaemstone.SourceGen/ModuleGenerator.cs b/src/gaemstone.SourceGen/ModuleGenerator.cs index 1d85ee4..ac3a335 100644 --- a/src/gaemstone.SourceGen/ModuleGenerator.cs +++ b/src/gaemstone.SourceGen/ModuleGenerator.cs @@ -5,6 +5,7 @@ using System.Text; using gaemstone.SourceGen.Structure; using gaemstone.SourceGen.Utility; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; namespace gaemstone.SourceGen; @@ -292,6 +293,8 @@ public class ModuleGenerator sb.AppendLine($"\t\t{@var}.Add<{a.GetFullName()}>();"); foreach (var (r, t) in e.RelationsToAdd) sb.AppendLine($"\t\t{@var}.Add<{r.GetFullName()}, {t.GetFullName()}>();"); + foreach (var (c, args) in e.ComponentsToAdd) + sb.AppendLine($"\t\t{@var}.Set(new {c.GetFullName()}({string.Join(", ", args.Select(a => a.ToCSharpString()))}));"); // If system doesn't have an explicit phase set, default to OnUpdate. if (e is MethodEntityInfo { IsSystem: true, HasPhaseSet: false }) diff --git a/src/gaemstone.SourceGen/RelevantSymbolReceiver.cs b/src/gaemstone.SourceGen/RelevantSymbolReceiver.cs index 3d42dfd..e498d3a 100644 --- a/src/gaemstone.SourceGen/RelevantSymbolReceiver.cs +++ b/src/gaemstone.SourceGen/RelevantSymbolReceiver.cs @@ -24,9 +24,10 @@ public class RelevantSymbolReceiver "Observer", // Entity properties that specify additional info / behavior - "Path", + "Path", // TODO: When referring to a pre-existing entity, only [Path] should be necessary, right? "Symbol", "Add", + "Set", "BuiltIn", // Valid on [Module] "Expression", // Valid on [System] and [Observer] diff --git a/src/gaemstone.SourceGen/Structure/BaseEntityInfo.cs b/src/gaemstone.SourceGen/Structure/BaseEntityInfo.cs index 5add683..f18537e 100644 --- a/src/gaemstone.SourceGen/Structure/BaseEntityInfo.cs +++ b/src/gaemstone.SourceGen/Structure/BaseEntityInfo.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; using Microsoft.CodeAnalysis; @@ -14,7 +15,11 @@ public abstract class BaseEntityInfo : BaseInfo public List EntitiesToAdd { get; } = new(); public List<(INamedTypeSymbol Relation, INamedTypeSymbol Target)> RelationsToAdd { get; } = new(); - public virtual bool HasEntitiesToAdd => (EntitiesToAdd.Count > 0) || (RelationsToAdd.Count > 0); + public List<(INamedTypeSymbol Component, ImmutableArray Arguments)> ComponentsToAdd { get; } = new(); + + public virtual bool HasEntitiesToAdd => (EntitiesToAdd.Count > 0) + || (RelationsToAdd.Count > 0) + || (ComponentsToAdd.Count > 0); public BaseEntityInfo(ISymbol symbol) : base(symbol) @@ -41,33 +46,49 @@ public abstract class BaseEntityInfo : BaseInfo Descriptors.EntityMustBeInModule, Location); } - // Add entities and relationships specified using [Add<...>] attributes. foreach (var attr in Symbol.GetAttributes()) { + // Add entities and relationships specified using [Add<...>] attributes. for (var attrType = attr.AttributeClass; attrType != null; attrType = attrType.BaseType) { - if (RelevantSymbolReceiver.ToRelevantAttributeName(attrType) != "Add") continue; + var attrName = RelevantSymbolReceiver.ToRelevantAttributeName(attrType); + if (attrName is "Add" or "Set") { - var allTypeArgumentsValid = true; - for (var i = 0; i < attrType.TypeArguments.Length; i++) { - var arg = attrType.TypeArguments[i]; - var param = attrType.TypeParameters[i]; - if (arg is not INamedTypeSymbol) { - yield return Diagnostic.Create( - Descriptors.InvalidTypeArgument, param.Locations.Single()); - allTypeArgumentsValid = false; + var allTypeArgumentsValid = true; + for (var i = 0; i < attrType.TypeArguments.Length; i++) { + var arg = attrType.TypeArguments[i]; + var param = attrType.TypeParameters[i]; + if (arg is not INamedTypeSymbol) { + yield return Diagnostic.Create( + Descriptors.InvalidTypeArgument, param.Locations.Single()); + allTypeArgumentsValid = false; + } + // TODO: Make sure entities being added have appropriate attributes as well. } - // TODO: Make sure entities being added have appropriate attributes as well. - } + if (!allTypeArgumentsValid) continue; - if (allTypeArgumentsValid) { - switch (attrType.TypeArguments) { - case [ INamedTypeSymbol entity ]: - EntitiesToAdd.Add(entity); + switch (attrName) { + case "Add": + switch (attrType.TypeArguments) { + case [ INamedTypeSymbol entity ]: + EntitiesToAdd.Add(entity); + break; + case [ INamedTypeSymbol relation, INamedTypeSymbol target ]: + RelationsToAdd.Add((relation, target)); + break; + default: throw new InvalidOperationException( + "Type argument pattern matching failed"); + } break; - case [ INamedTypeSymbol relation, INamedTypeSymbol target ]: - RelationsToAdd.Add((relation, target)); + + case "Set": + var component = (INamedTypeSymbol)attrType.TypeArguments.Single(); + var arguments = attr.ConstructorArguments.Single().Values; + // TODO: Verify arguments actually match a constructor. + // Right now this will just error in the generated code. Probably good enough? + ComponentsToAdd.Add((component, arguments)); break; + default: throw new InvalidOperationException( - "Type argument pattern matching failed"); + "Invalid relevant attribute name"); } } } diff --git a/src/gaemstone/ECS/Module+Attributes.cs b/src/gaemstone/ECS/Module+Attributes.cs index b0b593e..5c38ca5 100644 --- a/src/gaemstone/ECS/Module+Attributes.cs +++ b/src/gaemstone/ECS/Module+Attributes.cs @@ -40,6 +40,22 @@ public class AddAttribute : Attribute { } public class AddAttribute : Attribute { } +/// +/// Marked entity automatically has the specified component added to it when +/// automatically registered. Equivalent to . +/// Arguments are passed to a valid constructor with matching arguments. Only +/// attribute-safe primitives are allowed for the specified arguments. +/// +[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct + | AttributeTargets.Enum | AttributeTargets.Method, + AllowMultiple = true)] +public class SetAttribute : Attribute +{ + public object[] Arguments { get; } + public SetAttribute(params object[] args) => Arguments = args; +} + + /// public class IsAAttribute : AddAttribute { } ///