Add attributes for entity documentation

main
copygirl 12 months ago
parent 902ff21e48
commit 47530de27e
  1. 6
      src/gaemstone.SourceGen/ModuleGenerator.cs
  2. 21
      src/gaemstone.SourceGen/RelevantSymbolReceiver.cs
  3. 13
      src/gaemstone.SourceGen/Structure/BaseEntityInfo.cs
  4. 20
      src/gaemstone.SourceGen/Utility/SymbolExtensions.cs
  5. 66
      src/gaemstone/Doc+Attributes.cs
  6. 50
      src/gaemstone/Doc+Extensions.cs
  7. 98
      src/gaemstone/Doc.cs
  8. 8
      src/gaemstone/Flecs/Core.cs
  9. 44
      src/gaemstone/Flecs/Doc.cs
  10. 4
      src/gaemstone/Universe+Modules.cs

@ -197,6 +197,12 @@ public class ModuleGenerator
else sb.Append($".CreateLookup<{e.FullName}>()");
sb.AppendLine();
if (e.DocName != null) sb.AppendLine($"\t\t\t.SetDocName({e.DocName.ToStringLiteral()})");
if (e.DocBrief != null) sb.AppendLine($"\t\t\t.SetDocBrief({e.DocBrief.ToStringLiteral()})");
if (e.DocDetail != null) sb.AppendLine($"\t\t\t.SetDocDetail({e.DocDetail.ToStringLiteral()})");
if (e.DocLink != null) sb.AppendLine($"\t\t\t.SetDocLink({e.DocLink.ToStringLiteral()})");
if (e.DocColor != null) sb.AppendLine($"\t\t\t.SetDocColor({e.DocColor.ToStringLiteral()})");
// I don't think it makes sense to have singletons pre-initialized to zero.
// Especially for singletons that are reference types, which would default to null.
// if (e.IsSingleton) sb.AppendLine($"\t\t\t.Add<{e.FullName}>()");

@ -12,6 +12,7 @@ namespace gaemstone.SourceGen;
public class RelevantSymbolReceiver
: ISyntaxContextReceiver
{
// Attributes from gaemstone.ECS and gaemstone.Doc are considered.
private static readonly HashSet<string> RelevantAttributeNames = new(){
// Base entity attributes
"Module", // Can also be [Singleton]
@ -24,7 +25,7 @@ public class RelevantSymbolReceiver
"Observer",
// Entity properties that specify additional info / behavior
"Path", // TODO: When referring to a pre-existing entity, only [Path] should be necessary, right?
"Path",
"Symbol",
"Add",
"Set",
@ -37,6 +38,13 @@ public class RelevantSymbolReceiver
"Has",
"Not",
"Or",
// Documentation attributes
"Name",
"Brief",
"Detail",
"Link",
"Color",
};
public Dictionary<ISymbol, BaseInfo> Symbols { get; } = new(SymbolEqualityComparer.Default);
@ -75,8 +83,13 @@ public class RelevantSymbolReceiver
public static string? ToRelevantAttributeName(INamedTypeSymbol symbol)
{
if (symbol.GetNamespace() != "gaemstone.ECS") return null;
var name = symbol.MetadataName.Split('`')[0];
return name.EndsWith("Attribute") ? name[..^"Attribute".Length] : null;
var name = symbol.GetFullName(FullNameStyle.NoGeneric);
if (!name.EndsWith("Attribute")) return null;
var sep = name.LastIndexOf('.');
if (sep < 0) return null;
if (name.AsSpan()[..sep] is not ("gaemstone.ECS" or "gaemstone.Doc")) return null;
return name[(sep+1)..^"Attribute".Length];
}
}

@ -13,6 +13,12 @@ public abstract class BaseEntityInfo : BaseInfo
public string? EntityPath { get; }
public string? EntitySymbol { get; }
public string? DocName { get; }
public string? DocBrief { get; }
public string? DocDetail { get; }
public string? DocLink { get; }
public string? DocColor { get; }
public List<INamedTypeSymbol> EntitiesToAdd { get; } = new();
public List<(INamedTypeSymbol Relation, INamedTypeSymbol Target)> RelationsToAdd { get; } = new();
public List<(INamedTypeSymbol Component, ImmutableArray<TypedConstant> Arguments)> ComponentsToAdd { get; } = new();
@ -32,6 +38,13 @@ public abstract class BaseEntityInfo : BaseInfo
?? EntityPath?.Split('/')[^1] // .. otherwise default to the name in [Path], ..
?? Name // .. or just use the default: The symbol's name.
: null;
// TODO: Get Detail from <summary> + <details> xml documentation.
DocName = Get("Name" )?.ConstructorArguments.FirstOrDefault().Value as string;
DocBrief = Get("Brief" )?.ConstructorArguments.FirstOrDefault().Value as string;
DocDetail = Get("Detail")?.ConstructorArguments.FirstOrDefault().Value as string;
DocLink = Get("Link" )?.ConstructorArguments.FirstOrDefault().Value as string;
DocColor = Get("Color" )?.ConstructorArguments.FirstOrDefault().Value as string;
}
protected override IEnumerable<Diagnostic> ValidateSelf()

@ -96,23 +96,3 @@ public enum FullNameStyle
Metadata, // Namespace.Foo+Bar`1
NoGeneric, // Namespace.Foo.Bar
}
public struct StringifyOptions
{
public static readonly StringifyOptions Default = new();
public static readonly StringifyOptions StripGeneric = new(){ Generic = GenericDisplayMode.None };
public static readonly StringifyOptions MetadataGeneric = new(){ Generic = GenericDisplayMode.Metadata };
public bool Namespace { get; set; } = true;
// TODO: public bool FriendlyNames { get; set; } = true;
public GenericDisplayMode Generic { get; set; } = GenericDisplayMode.Full;
public StringifyOptions() { }
public enum GenericDisplayMode
{
None, // Foo
Metadata, // Foo`1
Full, // Foo<Bar<Baz>>
}
}

@ -0,0 +1,66 @@
using System;
namespace gaemstone;
public partial class Doc
{
/// <summary>
/// A display name for this entity.
/// Names in the entity hierarchy must be unique within the parent entity,
/// This doesn't apply to display names - they are mostly informational.
/// Displayed in the Entity Inspector.
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum)]
public class NameAttribute : Attribute
{
public string Value { get; }
public NameAttribute(string value) => Value = value;
}
/// <summary>
/// A brief description of this entity.
/// Displayed in the Entity Inspector.
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum)]
public class BriefAttribute : Attribute
{
public string Value { get; }
public BriefAttribute(string value) => Value = value;
}
/// <summary>
/// A detailed description, or full documentation, of this entity's purpose and behaviors.
/// It's encouraged to use multiple paragraphs and markdown formatting if necessary.
/// Displayed in the Entity Inspector.
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum)]
public class DetailAttribute : Attribute
{
public string Value { get; }
public DetailAttribute(string value) => Value = value;
}
/// <summary>
/// A link to a website relating to this entity, such as
/// a module's repository, or further documentation.
/// Displayed in the Entity Inspector.
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum)]
public class LinkAttribute : Attribute
{
public string Value { get; }
public LinkAttribute(string value) => Value = value;
}
/// <summary>
/// A custom color to represent this entity.
/// Displayed in the Entity Inspector.
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum)]
public class ColorAttribute : Attribute
{
// TODO: Should we be passing a string?
public string Value { get; }
public ColorAttribute(string value) => Value = value;
}
}

@ -0,0 +1,50 @@
using gaemstone.ECS;
using gaemstone.ECS.Utility;
using gaemstone.Flecs;
using static flecs_hub.flecs;
namespace gaemstone;
public static unsafe class DocExtensions
{
private static Entity<TContext> Set<TContext, T>(Entity<TContext> entity, string? value)
{
var id = entity.World.Pair<Flecs.Doc.Description, T>();
if (value != null) {
var str = GlobalHeapAllocator.Instance.AllocateCString(value);
var desc = new Flecs.Doc.Description { Value = (void*)(nint)str };
entity.Set(id, desc);
} else {
entity.Remove(id);
}
return entity;
}
public static string? GetDocName<TContext>(this Entity<TContext> entity, bool fallbackToEntityName = true)
=> fallbackToEntityName || entity.Has<Flecs.Doc.Description, Core.Name>()
? ecs_doc_get_name(entity.World, entity).FlecsToString() : null;
public static Entity<TContext> SetDocName<TContext>(this Entity<TContext> entity, string? value)
=> Set<TContext, Core.Name>(entity, value);
public static string? GetDocBrief<TContext>(this Entity<TContext> entity)
=> ecs_doc_get_brief(entity.World, entity).FlecsToString()!;
public static Entity<TContext> SetDocBrief<TContext>(this Entity<TContext> entity, string? value)
=> Set<TContext, Flecs.Doc.Brief>(entity, value);
public static string? GetDocDetail<TContext>(this Entity<TContext> entity)
=> ecs_doc_get_detail(entity.World, entity).FlecsToString()!;
public static Entity<TContext> SetDocDetail<TContext>(this Entity<TContext> entity, string? value)
=> Set<TContext, Flecs.Doc.Detail>(entity, value);
public static string? GetDocLink<TContext>(this Entity<TContext> entity)
=> ecs_doc_get_link(entity.World, entity).FlecsToString()!;
public static Entity<TContext> SetDocLink<TContext>(this Entity<TContext> entity, string? value)
=> Set<TContext, Flecs.Doc.Link>(entity, value);
public static string? GetDocColor<TContext>(this Entity<TContext> entity)
=> ecs_doc_get_color(entity.World, entity).FlecsToString()!;
public static Entity<TContext> SetDocColor<TContext>(this Entity<TContext> entity, string? value)
=> Set<TContext, Flecs.Doc.Color>(entity, value);
}

@ -1,4 +1,3 @@
using System;
using gaemstone.ECS;
using static gaemstone.Flecs.Core;
@ -7,88 +6,41 @@ namespace gaemstone;
[Module]
public partial class Doc
{
[Tag]
public struct DisplayType { }
[Tag]
public struct Relation { }
/// <summary>
/// Tags are just <see cref="Flecs.Core.Component"/>s with 0 size.
/// This functions as a special entity that holds the appearance for tags.
/// This functions as a special entity that holds the appearance for tags
/// used by the entity inspector, and is not actually added to any entity.
/// Not related to <see cref="Flecs.Core.Tag"/> in any way.
/// </summary>
[Tag]
public struct Tag { }
// TODO: These need to actually be read at some point.
/// <summary>
/// A display name for this entity.
/// Names in the entity hierarchy must be unique within the parent entity,
/// This doesn't apply to display names - they are mostly informational.
/// Displayed in the Entity Inspector.
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum)]
public class Name : Attribute
{
public string Value { get; }
public Name(string value) => Value = value;
}
/// <summary>
/// A brief description of this entity.
/// Displayed in the Entity Inspector.
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum)]
public class Brief : Attribute
{
public string Value { get; }
public Brief(string value) => Value = value;
}
/// <summary>
/// A detailed description, or full documentation, of this entity's purpose and behaviors.
/// It's encouraged to use multiple paragraphs and markdown formatting if necessary.
/// Displayed in the Entity Inspector.
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum)]
public class Detail : Attribute
{
public string Value { get; }
public Detail(string value) => Value = value;
}
/// <summary>
/// A link to a website relating to this entity, such as
/// a module's repository, or further documentation.
/// Displayed in the Entity Inspector.
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum)]
public class Link : Attribute
{
public string Value { get; }
public Link(string value) => Value = value;
}
/// <summary>
/// A custom color to represent this entity.
/// Displayed in the Entity Inspector.
/// Added to entities that represent a unique type of entity that should
/// be displayed uniquely in the entity inspector. If an entity is tagged
/// with such a display type entity, it too will be shown differently.
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum)]
public class Color : Attribute
{
public float Red { get; }
public float Green { get; }
public float Blue { get; }
public Color(float red, float green, float blue)
{
if ((red < 0.0f) || (red > 1.0f)) throw new ArgumentOutOfRangeException(nameof(red ));
if ((green < 0.0f) || (green > 1.0f)) throw new ArgumentOutOfRangeException(nameof(green));
if ((blue < 0.0f) || (blue > 1.0f)) throw new ArgumentOutOfRangeException(nameof(blue ));
Red = red; Green = green; Blue = blue;
}
}
/// <details>
/// <p>
/// Examples of such display types include:
/// <list>
/// <item> <see cref="Flecs.Core.Module"/> </item>
/// <item> <see cref="Flecs.Core.Component"/> </item>
/// <item> <see cref="Flecs.Core.Observer"/> </item>
/// <item> <see cref="Flecs.System.System"/> </item>
/// <item> <see cref="gaemstone.Doc.Relation"/> </item>
/// <item> <see cref="gaemstone.Doc.Tag"/> </item>
/// </list>
/// </p>
/// <p>
/// Used in conjuction with components and relations such as
/// (<see cref="Flecs.Doc.Description"/>, <see cref="Flecs.Doc.Color"/>)
/// to specify the appearance of this display type in the entity inspector.
/// </p>
/// </details>
[Tag]
public struct DisplayType { }
}

@ -8,6 +8,14 @@ public partial class Core
{
// Entity Tags
// TODO:
// [Tag] public struct Observer { }
// // Technically, this type should be in the Flecs.System addon,
// // but unfortunately due to language limitations, it has to reside here.
// [Tag, Path("/flecs/system/System")]
// public struct System { }
[Tag] public struct Module { }
[Tag] public struct Private { }
[Tag] public struct Prefab { }

@ -38,47 +38,3 @@ public unsafe partial class Doc
private static void DocImport(ecs_world_t* world)
=> FlecsDocImport(world);
}
public static unsafe class DocExtensions
{
private static Entity<TContext> Set<TContext, T>(Entity<TContext> entity, string? value)
{
var id = entity.World.Pair<Doc.Description, T>();
if (value != null) {
var str = GlobalHeapAllocator.Instance.AllocateCString(value);
var desc = new Doc.Description { Value = (void*)(nint)str };
entity.Set(id, desc);
} else {
entity.Remove(id);
}
return entity;
}
public static string? GetDocName<TContext>(this Entity<TContext> entity, bool fallbackToEntityName = true)
=> fallbackToEntityName || entity.Has<Doc.Description, Core.Name>()
? ecs_doc_get_name(entity.World, entity).FlecsToString() : null;
public static Entity<TContext> SetDocName<TContext>(this Entity<TContext> entity, string? value)
=> Set<TContext, Core.Name>(entity, value);
public static string? GetDocBrief<TContext>(this Entity<TContext> entity)
=> ecs_doc_get_brief(entity.World, entity).FlecsToString()!;
public static Entity<TContext> SetDocBrief<TContext>(this Entity<TContext> entity, string? value)
=> Set<TContext, Doc.Brief>(entity, value);
public static string? GetDocDetail<TContext>(this Entity<TContext> entity)
=> ecs_doc_get_detail(entity.World, entity).FlecsToString()!;
public static Entity<TContext> SetDocDetail<TContext>(this Entity<TContext> entity, string? value)
=> Set<TContext, Doc.Detail>(entity, value);
public static string? GetDocLink<TContext>(this Entity<TContext> entity)
=> ecs_doc_get_link(entity.World, entity).FlecsToString()!;
public static Entity<TContext> SetDocLink<TContext>(this Entity<TContext> entity, string? value)
=> Set<TContext, Doc.Link>(entity, value);
public static string? GetDocColor<TContext>(this Entity<TContext> entity)
=> ecs_doc_get_color(entity.World, entity).FlecsToString()!;
public static Entity<TContext> SetDocColor<TContext>(this Entity<TContext> entity, string? value)
=> Set<TContext, Doc.Color>(entity, value);
}

@ -23,8 +23,8 @@ public class ModuleManager<TContext>
World.New("/gaemstone/ModuleInfo").Symbol("ModuleInfo")
.Build().InitComponent<IModuleInfo>();
_findDisabledDeps = new(World, new("(DependsOn, $dep), Disabled($dep)"));
_findDependents = new(World, new("ModuleInfo, Disabled, (DependsOn, $module)"));
_findDisabledDeps = World.Rule(new("(DependsOn, $dep), Disabled($dep)"));
_findDependents = World.Rule(new("ModuleInfo, Disabled, (DependsOn, $module)"));
_findDisabledDepsThisVar = _findDisabledDeps.ThisVar
?? throw new InvalidOperationException($"Could not find $this of {nameof(_findDisabledDeps)}");

Loading…
Cancel
Save