- Add IModuleAutoRegisterComponents interface - Add gaemstone.SourceGen project which (currently) generates a method to automatically register a module's compiles. - Get rid of RegisterNestedType (which has been replaced) and code to handle static (Flecs built-in) modules. - Split Attributes into Module+Attributes and +Components "Components" is for attributes that define components "Attributes" are for other attributes that can be addedwip/source-generators
parent
13f69a0856
commit
2b877e0c5c
48 changed files with 638 additions and 277 deletions
@ -0,0 +1,24 @@ |
||||
using System; |
||||
using gaemstone.ECS; |
||||
|
||||
namespace Immersion; |
||||
|
||||
[Module] |
||||
public partial class ManagedComponentTest |
||||
{ |
||||
[Component] |
||||
public class BigManagedData |
||||
{ |
||||
public readonly byte[] BigArray = new byte[1024 * 1024]; |
||||
} |
||||
|
||||
[System] |
||||
public static void CreateLotsOfGarbageData(World world) |
||||
{ |
||||
var game = world.LookupByPathOrThrow("/Game"); |
||||
game.Remove<BigManagedData>(); |
||||
game.Set(new BigManagedData()); |
||||
// This is to make sure the number of objects kept alive stays stable. |
||||
Console.WriteLine(ReferenceHandle.NumActiveHandles); |
||||
} |
||||
} |
@ -0,0 +1,256 @@ |
||||
using System; |
||||
using System.Collections.Generic; |
||||
using System.Linq; |
||||
using System.Text; |
||||
using gaemstone.SourceGen.Utility; |
||||
using Microsoft.CodeAnalysis; |
||||
using Microsoft.CodeAnalysis.CSharp; |
||||
using Microsoft.CodeAnalysis.CSharp.Syntax; |
||||
|
||||
namespace gaemstone.SourceGen.Generators; |
||||
|
||||
[Generator] |
||||
public partial class AutoRegisterComponentsGenerator |
||||
: ISourceGenerator |
||||
{ |
||||
private static readonly DiagnosticDescriptor ComponentNotPartOfModule = new( |
||||
"gaem0101", "Components must be part of a module", |
||||
"Type {0} isn't defined as part of a [Module]", |
||||
nameof(AutoRegisterComponentsGenerator), DiagnosticSeverity.Error, true); |
||||
|
||||
private static readonly DiagnosticDescriptor ComponentMultipleTypes = new( |
||||
"gaem0102", "Components may not have multiple component types", |
||||
"Type {0} is marked with multiple component types ({1})", |
||||
nameof(AutoRegisterComponentsGenerator), DiagnosticSeverity.Error, true); |
||||
|
||||
private static readonly DiagnosticDescriptor ComponentRelationInvalidType = new( |
||||
"gaem0103", "Relations may only be marked with [Component] or [Tag]", |
||||
"Type {0} marked as [Relation] may not be marked with [{1}], only [Component] or [Tag] are valid", |
||||
nameof(AutoRegisterComponentsGenerator), DiagnosticSeverity.Error, true); |
||||
|
||||
|
||||
public void Initialize(GeneratorInitializationContext context) |
||||
=> context.RegisterForSyntaxNotifications(() => new SyntaxReceiver()); |
||||
|
||||
private class SyntaxReceiver |
||||
: ISyntaxContextReceiver |
||||
{ |
||||
public Dictionary<INamedTypeSymbol, HashSet<INamedTypeSymbol>> Modules { get; } |
||||
= new(SymbolEqualityComparer.Default); |
||||
public HashSet<INamedTypeSymbol> ComponentsNotInModule { get; } |
||||
= new(SymbolEqualityComparer.Default); |
||||
|
||||
public void OnVisitSyntaxNode(GeneratorSyntaxContext context) |
||||
{ |
||||
if (context.Node is not AttributeSyntax attrNode) return; |
||||
var model = context.SemanticModel; |
||||
|
||||
var attrType = model.GetTypeInfo(attrNode).Type!; |
||||
if ((attrType.GetNamespace() != "gaemstone.ECS") || !attrType.Name.EndsWith("Attribute")) return; |
||||
var attrName = attrType.Name.Substring(0, attrType.Name.Length - "Attribute".Length); |
||||
if (!Enum.TryParse<RegisterType>(attrName, out _)) return; |
||||
|
||||
var symbol = (model.GetDeclaredSymbol(attrNode.Parent?.Parent!) as INamedTypeSymbol)!; |
||||
if ((symbol.ContainingSymbol is INamedTypeSymbol module) |
||||
&& module.HasAttribute("gaemstone.ECS.ModuleAttribute")) |
||||
{ |
||||
if (!Modules.TryGetValue(module, out var components)) |
||||
Modules.Add(module, components = new(SymbolEqualityComparer.Default)); |
||||
components.Add(symbol); |
||||
} |
||||
else ComponentsNotInModule.Add(symbol); |
||||
} |
||||
} |
||||
|
||||
public void Execute(GeneratorExecutionContext context) |
||||
{ |
||||
if (context.SyntaxContextReceiver is not SyntaxReceiver receiver) return; |
||||
|
||||
foreach (var symbol in receiver.ComponentsNotInModule) |
||||
context.ReportDiagnostic(Diagnostic.Create(ComponentNotPartOfModule, |
||||
symbol.Locations.FirstOrDefault(), symbol.GetFullName())); |
||||
|
||||
var modules = new Dictionary<INamedTypeSymbol, ModuleInfo>( |
||||
SymbolEqualityComparer.Default); |
||||
foreach (var pair in receiver.Modules) { |
||||
var moduleSymbol = pair.Key; |
||||
if (!modules.TryGetValue(moduleSymbol, out var module)) |
||||
modules.Add(moduleSymbol, module = new(moduleSymbol)); |
||||
|
||||
foreach (var symbol in pair.Value) { |
||||
var componentTypes = symbol.GetAttributes() |
||||
.Where (attr => (attr.AttributeClass?.GetNamespace() == "gaemstone.ECS")) |
||||
.Select(attr => attr.AttributeClass!.Name) |
||||
.Where (name => name.EndsWith("Attribute")) |
||||
.Select(name => name.Substring(0, name.Length - "Attribute".Length)) |
||||
.SelectMany(name => Enum.TryParse<RegisterType>(name, out var type) |
||||
? new[] { type } : Enumerable.Empty<RegisterType>()) |
||||
.ToList(); |
||||
if (componentTypes.Count == 0) continue; |
||||
|
||||
var isRelation = componentTypes.Contains(RegisterType.Relation); |
||||
if (isRelation && (componentTypes.Count == 2)) { |
||||
var other = componentTypes.Where(type => (type != RegisterType.Relation)).Single(); |
||||
if (other is RegisterType.Component or RegisterType.Tag) |
||||
componentTypes.Remove(RegisterType.Relation); |
||||
else context.ReportDiagnostic(Diagnostic.Create(ComponentRelationInvalidType, |
||||
symbol.Locations.FirstOrDefault(), symbol.GetFullName(), other.ToString())); |
||||
} |
||||
|
||||
if (componentTypes.Count >= 2) |
||||
context.ReportDiagnostic(Diagnostic.Create(ComponentMultipleTypes, |
||||
symbol.Locations.FirstOrDefault(), symbol.GetFullName(), |
||||
string.Join(", ", componentTypes.Select(s => $"[{s}]")))); |
||||
var componentType = componentTypes[0]; |
||||
|
||||
var addedEntities = new List<ITypeSymbol>(); |
||||
var addedRelations = new List<(ITypeSymbol, ITypeSymbol)>(); |
||||
foreach (var attr in symbol.GetAttributes()) |
||||
for (var type = attr.AttributeClass; type != null; type = type.BaseType) |
||||
switch (type.GetFullName(true)) { |
||||
case "gaemstone.ECS.AddAttribute`1": addedEntities.Add(type.TypeArguments[0]); break; |
||||
case "gaemstone.ECS.AddAttribute`2": addedRelations.Add((type.TypeArguments[0], type.TypeArguments[1])); break; |
||||
} |
||||
|
||||
module.Components.Add(new(symbol, module, componentType, |
||||
addedEntities, addedRelations)); |
||||
} |
||||
} |
||||
|
||||
foreach (var module in modules.Values) { |
||||
var sb = new StringBuilder(); |
||||
sb.AppendLine($$"""
|
||||
// <auto-generated/> |
||||
using gaemstone.ECS; |
||||
|
||||
namespace {{module.Namespace}}; |
||||
|
||||
""");
|
||||
|
||||
if (module.IsStatic) { |
||||
sb.AppendLine($$"""
|
||||
public static partial class {{ module.Name }} |
||||
{ |
||||
public static void RegisterComponents(World world) |
||||
{ |
||||
var module = world.LookupByPathOrThrow({{ module.Path.ToStringLiteral() }}); |
||||
""");
|
||||
|
||||
foreach (var c in module.Components) { |
||||
var @var = $"entity{c.Name}"; |
||||
var path = (c.Path ?? c.Name).ToStringLiteral(); |
||||
sb.AppendLine($$""" var {{ @var }} = world.LookupByPathOrThrow(module, {{ path }})"""); |
||||
if (c.IsRelation) sb.AppendLine($$""" .Add<gaemstone.Doc.Relation>()"""); |
||||
sb.AppendLine($$""" .CreateLookup<{{ c.Name }}>()"""); |
||||
sb.Insert(sb.Length - 1, ";"); |
||||
} |
||||
} else { |
||||
sb.AppendLine($$"""
|
||||
public partial class {{module.Name}} |
||||
: IModuleAutoRegisterComponents |
||||
{ |
||||
public void RegisterComponents(EntityRef module) |
||||
{ |
||||
var world = module.World; |
||||
""");
|
||||
|
||||
foreach (var c in module.Components) { |
||||
var @var = $"entity{c.Name}"; |
||||
var path = (c.Path ?? c.Name).ToStringLiteral(); |
||||
sb.AppendLine($$""" var {{ @var }} = world.New(module, {{ path }})"""); |
||||
if (c.IsSymbol) sb.AppendLine($$""" .Symbol("{{ c.Name }}")"""); |
||||
if (c.IsRelation) sb.AppendLine($$""" .Add<gaemstone.Doc.Relation>()"""); |
||||
if (c.IsTag) sb.AppendLine($$""" .Add<gaemstone.Flecs.Core.Tag>()"""); |
||||
if (c.IsComponent) sb.AppendLine($$""" .Build().InitComponent<{{ c.Name }}>()"""); |
||||
else sb.AppendLine($$""" .Build().CreateLookup<{{ c.Name }}>()"""); |
||||
if (c.SingletonAddSelf) sb.AppendLine($$""" .Add<{{ c.Name }}>()"""); |
||||
sb.Insert(sb.Length - 1, ";"); |
||||
} |
||||
|
||||
foreach (var c in module.Components) { |
||||
var @var = $"entity{c.Name}"; |
||||
foreach (var e in c.EntitiesToAdd) |
||||
sb.AppendLine($$""" {{ @var }}.Add<{{ e.GetFullName() }}>();"""); |
||||
foreach (var (r, t) in c.RelationsToAdd) |
||||
sb.AppendLine($$""" {{ @var }}.Add<{{ r.GetFullName() }}, {{ t.GetFullName() }}>();"""); |
||||
} |
||||
} |
||||
|
||||
sb.AppendLine($$"""
|
||||
} |
||||
} |
||||
""");
|
||||
|
||||
context.AddSource($"{module.FullName}_Components.g.cs", sb.ToString()); |
||||
} |
||||
} |
||||
|
||||
private class ModuleInfo |
||||
{ |
||||
public INamedTypeSymbol Symbol { get; } |
||||
public string Name => Symbol.Name; |
||||
public string FullName => Symbol.GetFullName(); |
||||
public string Namespace => Symbol.GetNamespace()!; |
||||
|
||||
public List<ComponentInfo> Components { get; } = new(); |
||||
public string? Path { get; } |
||||
public bool IsStatic => Symbol.IsStatic; |
||||
|
||||
public ModuleInfo(INamedTypeSymbol symbol) |
||||
{ |
||||
Symbol = symbol; |
||||
Path = symbol.GetAttribute("gaemstone.ECS.PathAttribute")? |
||||
.ConstructorArguments.FirstOrDefault().Value as string; |
||||
} |
||||
} |
||||
|
||||
private class ComponentInfo |
||||
{ |
||||
public INamedTypeSymbol Symbol { get; } |
||||
public string Name => Symbol.Name; |
||||
|
||||
public ModuleInfo Module { get; } |
||||
public RegisterType Type { get; } |
||||
public List<ITypeSymbol> EntitiesToAdd { get; } |
||||
public List<(ITypeSymbol, ITypeSymbol)> RelationsToAdd { get; } |
||||
|
||||
public string? Path { get; } |
||||
public bool IsSymbol { get; } // TODO: Get rid of this. |
||||
public bool IsRelation { get; } |
||||
public bool IsTag => (Type is RegisterType.Tag); |
||||
public bool IsComponent => (Type is RegisterType.Component |
||||
or RegisterType.Singleton); |
||||
public bool IsSingleton => (Type is RegisterType.Singleton); |
||||
public bool SingletonAddSelf { get; } |
||||
|
||||
public ComponentInfo(INamedTypeSymbol symbol, ModuleInfo module, RegisterType type, |
||||
List<ITypeSymbol> entitiesToAdd, List<(ITypeSymbol, ITypeSymbol)> relationsToAdd) |
||||
{ |
||||
Symbol = symbol; |
||||
Module = module; |
||||
Type = type; |
||||
|
||||
EntitiesToAdd = entitiesToAdd; |
||||
RelationsToAdd = relationsToAdd; |
||||
|
||||
Path = symbol.GetAttribute("gaemstone.ECS.PathAttribute")? |
||||
.ConstructorArguments.FirstOrDefault().Value as string; |
||||
IsSymbol = symbol.HasAttribute("gaemstone.ECS.SymbolAttribute"); |
||||
IsRelation = symbol.HasAttribute("gaemstone.ECS.RelationAttribute"); |
||||
|
||||
SingletonAddSelf = IsSingleton |
||||
&& (symbol.GetAttribute("gaemstone.ECS.SingletonAttribute")! |
||||
.NamedArguments.FirstOrDefault() is not ("AutoAdd", TypedConstant typedConst) |
||||
|| (bool)typedConst.Value!); |
||||
} |
||||
} |
||||
|
||||
private enum RegisterType |
||||
{ |
||||
Entity, |
||||
Singleton, |
||||
Relation, |
||||
Component, |
||||
Tag, |
||||
} |
||||
} |
@ -0,0 +1,76 @@ |
||||
using System.Collections.Generic; |
||||
using System.Linq; |
||||
using gaemstone.SourceGen.Utility; |
||||
using Microsoft.CodeAnalysis; |
||||
using Microsoft.CodeAnalysis.CSharp; |
||||
using Microsoft.CodeAnalysis.CSharp.Syntax; |
||||
|
||||
namespace gaemstone.SourceGen.Generators; |
||||
|
||||
[Generator] |
||||
public class ModuleGenerator |
||||
: ISourceGenerator |
||||
{ |
||||
private static readonly DiagnosticDescriptor ModuleMayNotBeNested = new( |
||||
"gaem0001", "Module may not be nested", |
||||
"Type {0} marked with [Module] may not be a nested type", |
||||
nameof(ModuleGenerator), DiagnosticSeverity.Error, true); |
||||
|
||||
private static readonly DiagnosticDescriptor ModuleMustBePartial = new( |
||||
"gaem0002", "Module must be partial", |
||||
"Type {0} marked with [Module] must be a partial type", |
||||
nameof(ModuleGenerator), DiagnosticSeverity.Error, true); |
||||
|
||||
private static readonly DiagnosticDescriptor ModuleBuiltInMustHavePath = new( |
||||
"gaem0003", "Built-in module must have [Path]", |
||||
"Type {0} marked with [Module] is a built-in module (static), and therefore must have [Path] set", |
||||
nameof(ModuleGenerator), DiagnosticSeverity.Error, true); |
||||
|
||||
|
||||
public void Initialize(GeneratorInitializationContext context) |
||||
=> context.RegisterForSyntaxNotifications(() => new SyntaxReceiver()); |
||||
|
||||
private class SyntaxReceiver |
||||
: ISyntaxContextReceiver |
||||
{ |
||||
public HashSet<INamedTypeSymbol> Symbols { get; } |
||||
= new(SymbolEqualityComparer.Default); |
||||
|
||||
public void OnVisitSyntaxNode(GeneratorSyntaxContext context) |
||||
{ |
||||
if (context.Node is not AttributeSyntax attrNode) return; |
||||
var model = context.SemanticModel; |
||||
|
||||
var attrType = model.GetTypeInfo(attrNode).Type!; |
||||
if (attrType.GetFullName(true) != "gaemstone.ECS.ModuleAttribute") return; |
||||
|
||||
var memberNode = attrNode.Parent?.Parent!; |
||||
var memberSymbol = model.GetDeclaredSymbol(memberNode) as INamedTypeSymbol; |
||||
Symbols.Add(memberSymbol!); |
||||
} |
||||
} |
||||
|
||||
public void Execute(GeneratorExecutionContext context) |
||||
{ |
||||
if (context.SyntaxContextReceiver is not SyntaxReceiver receiver) return; |
||||
|
||||
foreach (var symbol in receiver.Symbols) { |
||||
var isNested = (symbol.ContainingType != null); |
||||
if (isNested) |
||||
context.ReportDiagnostic(Diagnostic.Create(ModuleMayNotBeNested, |
||||
symbol.Locations.FirstOrDefault(), symbol.GetFullName())); |
||||
|
||||
var isPartial = symbol.DeclaringSyntaxReferences |
||||
.Any(r => (r.GetSyntax() as ClassDeclarationSyntax)?.Modifiers |
||||
.Any(t => t.IsKind(SyntaxKind.PartialKeyword)) ?? false); |
||||
if (!isPartial) |
||||
context.ReportDiagnostic(Diagnostic.Create(ModuleMustBePartial, |
||||
symbol.Locations.FirstOrDefault(), symbol.GetFullName())); |
||||
|
||||
if (symbol.IsStatic && (symbol.GetAttribute("gaemstone.ECS.PathAttribute")? |
||||
.ConstructorArguments.FirstOrDefault().Value == null)) |
||||
context.ReportDiagnostic(Diagnostic.Create(ModuleBuiltInMustHavePath, |
||||
symbol.Locations.FirstOrDefault(), symbol.GetFullName())); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,10 @@ |
||||
using System.Collections.Generic; |
||||
|
||||
namespace gaemstone.SourceGen.Utility; |
||||
|
||||
public static class CollectionExtensions |
||||
{ |
||||
public static void Deconstruct<TKey, TValue>( |
||||
this KeyValuePair<TKey, TValue> kvp, out TKey key, out TValue value) |
||||
{ key = kvp.Key; value = kvp.Value; } |
||||
} |
@ -0,0 +1,51 @@ |
||||
using System.Linq; |
||||
using System.Text; |
||||
using Microsoft.CodeAnalysis; |
||||
using Microsoft.CodeAnalysis.CSharp; |
||||
|
||||
namespace gaemstone.SourceGen.Utility; |
||||
|
||||
public static class SymbolExtensions |
||||
{ |
||||
public static string GetFullName(this ISymbol symbol, bool metadata = false) |
||||
{ |
||||
var builder = new StringBuilder(); |
||||
AppendFullName(symbol, builder, metadata); |
||||
return builder.ToString(); |
||||
} |
||||
|
||||
public static void AppendFullName(this ISymbol symbol, StringBuilder builder, bool metadata = false) |
||||
{ |
||||
if ((symbol.ContainingSymbol is ISymbol parent) |
||||
&& ((parent as INamespaceSymbol)?.IsGlobalNamespace != true)) |
||||
{ |
||||
AppendFullName(parent, builder, metadata); |
||||
builder.Append(((parent is ITypeSymbol) && metadata) ? '+' : '.'); |
||||
} |
||||
|
||||
if (!metadata && (symbol is INamedTypeSymbol typeSymbol) && typeSymbol.IsGenericType) { |
||||
var length = symbol.MetadataName.IndexOf('`'); |
||||
builder.Append(symbol.MetadataName, 0, length); |
||||
builder.Append('<'); |
||||
foreach (var genericType in typeSymbol.TypeArguments) { |
||||
AppendFullName(genericType, builder, true); |
||||
builder.Append(','); |
||||
} |
||||
builder.Length--; // Remove the last ',' character. |
||||
builder.Append('>'); |
||||
} else builder.Append(symbol.MetadataName); |
||||
} |
||||
|
||||
public static string? GetNamespace(this ISymbol symbol) |
||||
=> symbol.ContainingNamespace?.GetFullName(); |
||||
|
||||
public static bool HasAttribute(this ISymbol symbol, string attrMetadataName) |
||||
=> symbol.GetAttributes().Any(attr => attr.AttributeClass!.GetFullName(true) == attrMetadataName); |
||||
|
||||
public static AttributeData? GetAttribute(this ISymbol symbol, string attrMetadataName) |
||||
=> symbol.GetAttributes().FirstOrDefault(attr => attr.AttributeClass!.GetFullName(true) == attrMetadataName); |
||||
|
||||
public static string ToStringLiteral(this string? input) |
||||
=> (input != null) ? SyntaxFactory.LiteralExpression(SyntaxKind.StringLiteralExpression, |
||||
SyntaxFactory.Literal(input)).ToFullString() : "null"; |
||||
} |
@ -0,0 +1,19 @@ |
||||
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
||||
<PropertyGroup> |
||||
<LangVersion>preview</LangVersion> |
||||
<TargetFramework>netstandard2.0</TargetFramework> |
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> |
||||
<ImplicitUsings>disable</ImplicitUsings> |
||||
<Nullable>enable</Nullable> |
||||
</PropertyGroup> |
||||
|
||||
<ItemGroup> |
||||
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.3"> |
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> |
||||
<PrivateAssets>all</PrivateAssets> |
||||
</PackageReference> |
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.4.0" /> |
||||
</ItemGroup> |
||||
|
||||
</Project> |
@ -1,121 +0,0 @@ |
||||
using System; |
||||
using static gaemstone.Flecs.Core; |
||||
|
||||
namespace gaemstone.ECS; |
||||
|
||||
/// <summary> |
||||
/// When present on an attribute attached to a type that's part of a module |
||||
/// being registered automatically through <see cref="ModuleManager.Register"/>, |
||||
/// an entity is automatically created and <see cref="LookupExtensions.CreateLookup"/> |
||||
/// called on it, meaning it can be looked up using <see cref="World.LookupByType(Type)"/>. |
||||
/// </summary> |
||||
public interface ICreateEntityAttribute { } |
||||
|
||||
[AttributeUsage(AttributeTargets.Struct)] |
||||
public class EntityAttribute : Attribute, ICreateEntityAttribute { } |
||||
|
||||
/// <summary> |
||||
/// Use a custom name or path for this entity instead of the type's name. |
||||
/// </summary> |
||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum)] |
||||
public class PathAttribute : Attribute, ICreateEntityAttribute |
||||
{ |
||||
public string Value { get; } |
||||
public PathAttribute(string value) => Value = value; |
||||
} |
||||
|
||||
/// <summary> |
||||
/// Register the entity under a globally unique symbol. |
||||
/// Uses the type's name by default. |
||||
/// </summary> |
||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum)] |
||||
public class SymbolAttribute : Attribute, ICreateEntityAttribute |
||||
{ |
||||
public string? Value { get; } |
||||
public SymbolAttribute() { } |
||||
public SymbolAttribute(string value) => Value = value; |
||||
} |
||||
|
||||
/// <summary> |
||||
/// A singleton is a single instance of a tag or component that can be retrieved |
||||
/// without explicitly specifying an entity in a query, where it is equivalent |
||||
/// to <see cref="SourceAttribute{}"/> with itself as the generic type parameter. |
||||
/// </summary> |
||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum)] |
||||
public class SingletonAttribute : Attribute, ICreateEntityAttribute |
||||
{ public bool AutoAdd { get; init; } = true; } |
||||
|
||||
|
||||
/// <summary> |
||||
/// Marked entity automatically has the specified entity added to it when |
||||
/// automatically registered. Equivalent to <see cref="EntityBase.Add{T}"/>. |
||||
/// </summary> |
||||
public class AddAttribute<TEntity> : AddEntityAttribute |
||||
{ public AddAttribute() : base(typeof(TEntity)) { } } |
||||
|
||||
/// <summary> |
||||
/// Marked entity automatically has the specified relationship pair added to it when |
||||
/// automatically registered, Equivalent to <see cref="EntityBase.Add{TRelation, TTarget}"/>. |
||||
/// </summary> |
||||
public class AddAttribute<TRelation, TTarget> : AddRelationAttribute |
||||
{ public AddAttribute() : base(typeof(TRelation), typeof(TTarget)) { } } |
||||
|
||||
/// <summary> |
||||
/// Marked entity represents a relationship type. |
||||
/// It may be used as the "relation" in a pair. |
||||
/// </summary> |
||||
/// <remarks> |
||||
/// The relationship may have component data associated with |
||||
/// it when added to an entity under these circumstances: |
||||
/// <list type="bullet"> |
||||
/// <item>If marked as a <see cref="TagAttribute"/>, does not carry data.</item> |
||||
/// <item>If marked as a <see cref="ComponentAttribute"/>, carries the relation's data.</item> |
||||
/// <item>If marked with neither, will carry the target's data, if it's a component.</item> |
||||
/// </list> |
||||
/// </remarks> |
||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum)] |
||||
public class RelationAttribute : Attribute, ICreateEntityAttribute { } |
||||
|
||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum)] |
||||
public class ComponentAttribute : Attribute, ICreateEntityAttribute { } |
||||
|
||||
/// <seealso cref="Tag"/> |
||||
[AttributeUsage(AttributeTargets.Struct)] |
||||
public class TagAttribute : AddAttribute<Tag>, ICreateEntityAttribute { } |
||||
|
||||
|
||||
/// <seealso cref="IsA"/> |
||||
public class IsAAttribute<TTarget> : AddAttribute<IsA, TTarget> { } |
||||
|
||||
/// <seealso cref="ChildOf"/> |
||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum)] |
||||
public class ChildOfAttribute<TTarget> : AddAttribute<ChildOf, TTarget> { } |
||||
|
||||
/// <seealso cref="DependsOn"/> |
||||
public class DependsOnAttribute<TTarget> : AddAttribute<DependsOn, TTarget> { } |
||||
|
||||
|
||||
/// <seealso cref="Exclusive"/> |
||||
public class ExclusiveAttribute : AddAttribute<Exclusive> { } |
||||
|
||||
/// <seealso cref="With"/> |
||||
public class WithAttribute<TTarget> : AddAttribute<With, TTarget> { } |
||||
|
||||
|
||||
// Base attributes for other attributes. |
||||
|
||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum, AllowMultiple = true)] |
||||
public class AddEntityAttribute : Attribute |
||||
{ |
||||
public Type Entity { get; } |
||||
internal AddEntityAttribute(Type entity) => Entity = entity; |
||||
} |
||||
|
||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum, AllowMultiple = true)] |
||||
public class AddRelationAttribute : Attribute |
||||
{ |
||||
public Type Relation { get; } |
||||
public Type Target { get; } |
||||
internal AddRelationAttribute(Type relation, Type target) |
||||
{ Relation = relation; Target = target; } |
||||
} |
@ -0,0 +1,53 @@ |
||||
using System; |
||||
using static gaemstone.Flecs.Core; |
||||
|
||||
namespace gaemstone.ECS; |
||||
|
||||
/// <summary> |
||||
/// Use a custom name or path for this entity instead of the type's name. |
||||
/// </summary> |
||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum)] |
||||
public class PathAttribute : Attribute |
||||
{ |
||||
public string Value { get; } |
||||
public PathAttribute(string value) => Value = value; |
||||
} |
||||
|
||||
/// <summary> |
||||
/// Register the entity under a globally unique symbol. |
||||
/// Uses the type's name by default. |
||||
/// </summary> |
||||
// TODO: Remove [Symbol], introduce [Public] for modules and [Private] or so. |
||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum)] |
||||
public class SymbolAttribute : Attribute { } |
||||
|
||||
|
||||
/// <summary> |
||||
/// Marked entity automatically has the specified entity added to it when |
||||
/// automatically registered. Equivalent to <see cref="EntityBase.Add{T}"/>. |
||||
/// </summary> |
||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum, AllowMultiple = true)] |
||||
public class AddAttribute<TEntity> : Attribute { } |
||||
|
||||
/// <summary> |
||||
/// Marked entity automatically has the specified relationship pair added to it when |
||||
/// automatically registered. Equivalent to <see cref="EntityBase.Add{TRelation, TTarget}"/>. |
||||
/// </summary> |
||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum, AllowMultiple = true)] |
||||
public class AddAttribute<TRelation, TTarget> : Attribute { } |
||||
|
||||
|
||||
/// <seealso cref="IsA"/> |
||||
public class IsAAttribute<TTarget> : AddAttribute<IsA, TTarget> { } |
||||
|
||||
/// <seealso cref="ChildOf"/> |
||||
public class ChildOfAttribute<TTarget> : AddAttribute<ChildOf, TTarget> { } |
||||
|
||||
/// <seealso cref="DependsOn"/> |
||||
public class DependsOnAttribute<TTarget> : AddAttribute<DependsOn, TTarget> { } |
||||
|
||||
/// <seealso cref="Exclusive"/> |
||||
public class ExclusiveAttribute : AddAttribute<Exclusive> { } |
||||
|
||||
/// <seealso cref="With"/> |
||||
public class WithAttribute<TTarget> : AddAttribute<With, TTarget> { } |
@ -0,0 +1,40 @@ |
||||
using System; |
||||
using static gaemstone.Flecs.Core; |
||||
|
||||
namespace gaemstone.ECS; |
||||
|
||||
[AttributeUsage(AttributeTargets.Struct)] |
||||
public class EntityAttribute : Attribute { } |
||||
|
||||
/// <seealso cref="Tag"/> |
||||
[AttributeUsage(AttributeTargets.Struct)] |
||||
public class TagAttribute : Attribute { } |
||||
|
||||
/// <seealso cref="Component"/> |
||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum)] |
||||
public class ComponentAttribute : Attribute { } |
||||
|
||||
/// <summary> |
||||
/// A singleton is a single instance of a tag or component that can be retrieved |
||||
/// without explicitly specifying an entity in a query, where it is equivalent |
||||
/// to <see cref="SourceAttribute{}"/> with itself as the generic type parameter. |
||||
/// </summary> |
||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum)] |
||||
public class SingletonAttribute : Attribute |
||||
{ public bool AutoAdd { get; init; } = true; } |
||||
|
||||
/// <summary> |
||||
/// Marked entity represents a relationship type. |
||||
/// It may be used as the "relation" in a pair. |
||||
/// </summary> |
||||
/// <remarks> |
||||
/// The relationship may have component data associated with |
||||
/// it when added to an entity under these circumstances: |
||||
/// <list type="bullet"> |
||||
/// <item>If marked as a <see cref="TagAttribute"/>, does not carry data.</item> |
||||
/// <item>If marked as a <see cref="ComponentAttribute"/>, carries the relation's data.</item> |
||||
/// <item>If marked with neither, will carry the target's data, if it's a component.</item> |
||||
/// </list> |
||||
/// </remarks> |
||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum)] |
||||
public class RelationAttribute : Attribute { } |