Add [Set] attribute to set entity's component

wip/source-generators
copygirl 12 months 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.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 })

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

@ -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<INamedTypeSymbol> 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<TypedConstant> 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");
}
}
}

@ -40,6 +40,22 @@ public class AddAttribute<TEntity> : 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"/>
public class IsAAttribute<TTarget> : AddAttribute<IsA, TTarget> { }
/// <seealso cref="ChildOf"/>

Loading…
Cancel
Save