diff --git a/src/gaemstone.SourceGen/ModuleGenerator.cs b/src/gaemstone.SourceGen/ModuleGenerator.cs index bfb0eb2..d8809c1 100644 --- a/src/gaemstone.SourceGen/ModuleGenerator.cs +++ b/src/gaemstone.SourceGen/ModuleGenerator.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}>()"); diff --git a/src/gaemstone.SourceGen/RelevantSymbolReceiver.cs b/src/gaemstone.SourceGen/RelevantSymbolReceiver.cs index e498d3a..cc8ce79 100644 --- a/src/gaemstone.SourceGen/RelevantSymbolReceiver.cs +++ b/src/gaemstone.SourceGen/RelevantSymbolReceiver.cs @@ -12,6 +12,7 @@ namespace gaemstone.SourceGen; public class RelevantSymbolReceiver : ISyntaxContextReceiver { + // Attributes from gaemstone.ECS and gaemstone.Doc are considered. private static readonly HashSet 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 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]; } } diff --git a/src/gaemstone.SourceGen/Structure/BaseEntityInfo.cs b/src/gaemstone.SourceGen/Structure/BaseEntityInfo.cs index f18537e..4b7254b 100644 --- a/src/gaemstone.SourceGen/Structure/BaseEntityInfo.cs +++ b/src/gaemstone.SourceGen/Structure/BaseEntityInfo.cs @@ -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 EntitiesToAdd { get; } = new(); public List<(INamedTypeSymbol Relation, INamedTypeSymbol Target)> RelationsToAdd { get; } = new(); public List<(INamedTypeSymbol Component, ImmutableArray 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 +
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 ValidateSelf() diff --git a/src/gaemstone.SourceGen/Utility/SymbolExtensions.cs b/src/gaemstone.SourceGen/Utility/SymbolExtensions.cs index 0409433..3605e43 100644 --- a/src/gaemstone.SourceGen/Utility/SymbolExtensions.cs +++ b/src/gaemstone.SourceGen/Utility/SymbolExtensions.cs @@ -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> - } -} diff --git a/src/gaemstone/Doc+Attributes.cs b/src/gaemstone/Doc+Attributes.cs new file mode 100644 index 0000000..d5749a0 --- /dev/null +++ b/src/gaemstone/Doc+Attributes.cs @@ -0,0 +1,66 @@ +using System; + +namespace gaemstone; + +public partial class Doc +{ + /// + /// 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. + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum)] + public class NameAttribute : Attribute + { + public string Value { get; } + public NameAttribute(string value) => Value = value; + } + + /// + /// A brief description of this entity. + /// Displayed in the Entity Inspector. + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum)] + public class BriefAttribute : Attribute + { + public string Value { get; } + public BriefAttribute(string value) => Value = value; + } + + /// + /// 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. + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum)] + public class DetailAttribute : Attribute + { + public string Value { get; } + public DetailAttribute(string value) => Value = value; + } + + /// + /// A link to a website relating to this entity, such as + /// a module's repository, or further documentation. + /// Displayed in the Entity Inspector. + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum)] + public class LinkAttribute : Attribute + { + public string Value { get; } + public LinkAttribute(string value) => Value = value; + } + + /// + /// A custom color to represent this entity. + /// Displayed in the Entity Inspector. + /// + [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; + } +} diff --git a/src/gaemstone/Doc+Extensions.cs b/src/gaemstone/Doc+Extensions.cs new file mode 100644 index 0000000..c1db2af --- /dev/null +++ b/src/gaemstone/Doc+Extensions.cs @@ -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 Set(Entity entity, string? value) + { + var id = entity.World.Pair(); + + 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(this Entity entity, bool fallbackToEntityName = true) + => fallbackToEntityName || entity.Has() + ? ecs_doc_get_name(entity.World, entity).FlecsToString() : null; + public static Entity SetDocName(this Entity entity, string? value) + => Set(entity, value); + + public static string? GetDocBrief(this Entity entity) + => ecs_doc_get_brief(entity.World, entity).FlecsToString()!; + public static Entity SetDocBrief(this Entity entity, string? value) + => Set(entity, value); + + public static string? GetDocDetail(this Entity entity) + => ecs_doc_get_detail(entity.World, entity).FlecsToString()!; + public static Entity SetDocDetail(this Entity entity, string? value) + => Set(entity, value); + + public static string? GetDocLink(this Entity entity) + => ecs_doc_get_link(entity.World, entity).FlecsToString()!; + public static Entity SetDocLink(this Entity entity, string? value) + => Set(entity, value); + + public static string? GetDocColor(this Entity entity) + => ecs_doc_get_color(entity.World, entity).FlecsToString()!; + public static Entity SetDocColor(this Entity entity, string? value) + => Set(entity, value); +} diff --git a/src/gaemstone/Doc.cs b/src/gaemstone/Doc.cs index 1d85fdc..f919906 100644 --- a/src/gaemstone/Doc.cs +++ b/src/gaemstone/Doc.cs @@ -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 { } /// /// Tags are just 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 in any way. /// [Tag] public struct Tag { } - - // TODO: These need to actually be read at some point. - - /// - /// 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. - /// - [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum)] - public class Name : Attribute - { - public string Value { get; } - public Name(string value) => Value = value; - } - - /// - /// A brief description of this entity. - /// Displayed in the Entity Inspector. - /// - [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum)] - public class Brief : Attribute - { - public string Value { get; } - public Brief(string value) => Value = value; - } - - /// - /// 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. - /// - [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum)] - public class Detail : Attribute - { - public string Value { get; } - public Detail(string value) => Value = value; - } - - /// - /// A link to a website relating to this entity, such as - /// a module's repository, or further documentation. - /// Displayed in the Entity Inspector. - /// - [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum)] - public class Link : Attribute - { - public string Value { get; } - public Link(string value) => Value = value; - } - /// - /// 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. /// - [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; - } - } + ///
+ ///

+ /// Examples of such display types include: + /// + /// + /// + /// + /// + /// + /// + /// + ///

+ ///

+ /// Used in conjuction with components and relations such as + /// (, ) + /// to specify the appearance of this display type in the entity inspector. + ///

+ ///
+ [Tag] + public struct DisplayType { } } diff --git a/src/gaemstone/Flecs/Core.cs b/src/gaemstone/Flecs/Core.cs index 6864683..aeca2ba 100644 --- a/src/gaemstone/Flecs/Core.cs +++ b/src/gaemstone/Flecs/Core.cs @@ -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 { } diff --git a/src/gaemstone/Flecs/Doc.cs b/src/gaemstone/Flecs/Doc.cs index 8e83fd6..b12deae 100644 --- a/src/gaemstone/Flecs/Doc.cs +++ b/src/gaemstone/Flecs/Doc.cs @@ -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 Set(Entity entity, string? value) - { - var id = entity.World.Pair(); - - 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(this Entity entity, bool fallbackToEntityName = true) - => fallbackToEntityName || entity.Has() - ? ecs_doc_get_name(entity.World, entity).FlecsToString() : null; - public static Entity SetDocName(this Entity entity, string? value) - => Set(entity, value); - - public static string? GetDocBrief(this Entity entity) - => ecs_doc_get_brief(entity.World, entity).FlecsToString()!; - public static Entity SetDocBrief(this Entity entity, string? value) - => Set(entity, value); - - public static string? GetDocDetail(this Entity entity) - => ecs_doc_get_detail(entity.World, entity).FlecsToString()!; - public static Entity SetDocDetail(this Entity entity, string? value) - => Set(entity, value); - - public static string? GetDocLink(this Entity entity) - => ecs_doc_get_link(entity.World, entity).FlecsToString()!; - public static Entity SetDocLink(this Entity entity, string? value) - => Set(entity, value); - - public static string? GetDocColor(this Entity entity) - => ecs_doc_get_color(entity.World, entity).FlecsToString()!; - public static Entity SetDocColor(this Entity entity, string? value) - => Set(entity, value); -} diff --git a/src/gaemstone/Universe+Modules.cs b/src/gaemstone/Universe+Modules.cs index ce4aba6..80db2cf 100644 --- a/src/gaemstone/Universe+Modules.cs +++ b/src/gaemstone/Universe+Modules.cs @@ -23,8 +23,8 @@ public class ModuleManager World.New("/gaemstone/ModuleInfo").Symbol("ModuleInfo") .Build().InitComponent(); - _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)}");