- 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 { } |
Loading…
Reference in new issue