Add [Set] attribute to set entity's component

wip/source-generators
copygirl 1 year ago
parent fe54cc3637
commit 03b7e5ce42
  1. 3
      src/gaemstone.SourceGen/ModuleGenerator.cs
  2. 3
      src/gaemstone.SourceGen/RelevantSymbolReceiver.cs
  3. 61
      src/gaemstone.SourceGen/Structure/BaseEntityInfo.cs
  4. 16
      src/gaemstone/ECS/Module+Attributes.cs

@ -5,6 +5,7 @@ using System.Text;
using gaemstone.SourceGen.Structure; using gaemstone.SourceGen.Structure;
using gaemstone.SourceGen.Utility; using gaemstone.SourceGen.Utility;
using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
namespace gaemstone.SourceGen; namespace gaemstone.SourceGen;
@ -292,6 +293,8 @@ public class ModuleGenerator
sb.AppendLine($"\t\t{@var}.Add<{a.GetFullName()}>();"); sb.AppendLine($"\t\t{@var}.Add<{a.GetFullName()}>();");
foreach (var (r, t) in e.RelationsToAdd) foreach (var (r, t) in e.RelationsToAdd)
sb.AppendLine($"\t\t{@var}.Add<{r.GetFullName()}, {t.GetFullName()}>();"); 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 system doesn't have an explicit phase set, default to OnUpdate.
if (e is MethodEntityInfo { IsSystem: true, HasPhaseSet: false }) if (e is MethodEntityInfo { IsSystem: true, HasPhaseSet: false })

@ -24,9 +24,10 @@ public class RelevantSymbolReceiver
"Observer", "Observer",
// Entity properties that specify additional info / behavior // Entity properties that specify additional info / behavior
"Path", "Path", // TODO: When referring to a pre-existing entity, only [Path] should be necessary, right?
"Symbol", "Symbol",
"Add", "Add",
"Set",
"BuiltIn", // Valid on [Module] "BuiltIn", // Valid on [Module]
"Expression", // Valid on [System] and [Observer] "Expression", // Valid on [System] and [Observer]

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq; using System.Linq;
using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis;
@ -14,7 +15,11 @@ public abstract class BaseEntityInfo : BaseInfo
public List<INamedTypeSymbol> EntitiesToAdd { get; } = new(); public List<INamedTypeSymbol> EntitiesToAdd { get; } = new();
public List<(INamedTypeSymbol Relation, INamedTypeSymbol Target)> RelationsToAdd { 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<TypedConstant> Arguments)> ComponentsToAdd { get; } = new();
public virtual bool HasEntitiesToAdd => (EntitiesToAdd.Count > 0)
|| (RelationsToAdd.Count > 0)
|| (ComponentsToAdd.Count > 0);
public BaseEntityInfo(ISymbol symbol) public BaseEntityInfo(ISymbol symbol)
: base(symbol) : base(symbol)
@ -41,33 +46,49 @@ public abstract class BaseEntityInfo : BaseInfo
Descriptors.EntityMustBeInModule, Location); Descriptors.EntityMustBeInModule, Location);
} }
// Add entities and relationships specified using [Add<...>] attributes.
foreach (var attr in Symbol.GetAttributes()) { foreach (var attr in Symbol.GetAttributes()) {
// Add entities and relationships specified using [Add<...>] attributes.
for (var attrType = attr.AttributeClass; attrType != null; attrType = attrType.BaseType) { 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; var allTypeArgumentsValid = true;
for (var i = 0; i < attrType.TypeArguments.Length; i++) { for (var i = 0; i < attrType.TypeArguments.Length; i++) {
var arg = attrType.TypeArguments[i]; var arg = attrType.TypeArguments[i];
var param = attrType.TypeParameters[i]; var param = attrType.TypeParameters[i];
if (arg is not INamedTypeSymbol) { if (arg is not INamedTypeSymbol) {
yield return Diagnostic.Create( yield return Diagnostic.Create(
Descriptors.InvalidTypeArgument, param.Locations.Single()); Descriptors.InvalidTypeArgument, param.Locations.Single());
allTypeArgumentsValid = false; 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 (attrName) {
switch (attrType.TypeArguments) { case "Add":
case [ INamedTypeSymbol entity ]: switch (attrType.TypeArguments) {
EntitiesToAdd.Add(entity); 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; 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; break;
default: throw new InvalidOperationException( default: throw new InvalidOperationException(
"Type argument pattern matching failed"); "Invalid relevant attribute name");
} }
} }
} }

@ -40,6 +40,22 @@ public class AddAttribute<TEntity> : Attribute { }
public class AddAttribute<TRelation, TTarget> : Attribute { } public class AddAttribute<TRelation, TTarget> : Attribute { }
/// <summary>
/// Marked entity automatically has the specified component added to it when
/// automatically registered. Equivalent to <see cref="EntityBuilder.Set{TComponent}"/>.
/// Arguments are passed to a valid constructor with matching arguments. Only
/// attribute-safe primitives are allowed for the specified arguments.
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct
| AttributeTargets.Enum | AttributeTargets.Method,
AllowMultiple = true)]
public class SetAttribute<TComponent> : Attribute
{
public object[] Arguments { get; }
public SetAttribute(params object[] args) => Arguments = args;
}
/// <seealso cref="IsA"/> /// <seealso cref="IsA"/>
public class IsAAttribute<TTarget> : AddAttribute<IsA, TTarget> { } public class IsAAttribute<TTarget> : AddAttribute<IsA, TTarget> { }
/// <seealso cref="ChildOf"/> /// <seealso cref="ChildOf"/>

Loading…
Cancel
Save