diff --git a/src/gaemstone/ECS/Component.cs b/src/gaemstone/ECS/Component.cs
index 5dcc260..b2c1a87 100644
--- a/src/gaemstone/ECS/Component.cs
+++ b/src/gaemstone/ECS/Component.cs
@@ -6,7 +6,11 @@ using static flecs_hub.flecs;
namespace gaemstone.ECS;
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum)]
-public class ComponentAttribute : EntityAttribute { }
+public class ComponentAttribute : EntityAttribute
+{
+ public ComponentAttribute() { }
+ public ComponentAttribute(params string[] path) : base(path) { }
+}
public static class ComponentExtensions
{
diff --git a/src/gaemstone/ECS/Entity.cs b/src/gaemstone/ECS/Entity.cs
index f974882..f06cc15 100644
--- a/src/gaemstone/ECS/Entity.cs
+++ b/src/gaemstone/ECS/Entity.cs
@@ -3,9 +3,23 @@ using static flecs_hub.flecs;
namespace gaemstone.ECS;
-[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)]
+[AttributeUsage(AttributeTargets.Struct)]
public class EntityAttribute : Attribute, ICreateEntityAttribute
- { public string? Name { get; init; } }
+{
+ /// If specified, uses this path instead of the default name.
+ public string[]? Path { get; }
+
+ /// If true, the path will be absolute instead of relative.
+ public bool Global { get; init; }
+
+ public EntityAttribute() { }
+ public EntityAttribute(params string[] path)
+ {
+ if (path.Length == 0) throw new ArgumentException(
+ "Path must not be empty", nameof(path));
+ Path = path;
+ }
+}
///
/// A singleton is a single instance of a tag or component that can be retrieved
@@ -13,7 +27,11 @@ public class EntityAttribute : Attribute, ICreateEntityAttribute
/// to with itself as the generic type parameter.
///
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum)]
-public class SingletonAttribute : EntityAttribute { }
+public class SingletonAttribute : EntityAttribute
+{
+ public SingletonAttribute() { }
+ public SingletonAttribute(params string[] path) : base(path) { }
+}
public readonly struct Entity
: IEquatable
diff --git a/src/gaemstone/ECS/Module.cs b/src/gaemstone/ECS/Module.cs
index c2aabf4..7644352 100644
--- a/src/gaemstone/ECS/Module.cs
+++ b/src/gaemstone/ECS/Module.cs
@@ -3,17 +3,10 @@ using System;
namespace gaemstone.ECS;
[AttributeUsage(AttributeTargets.Class)]
-public class ModuleAttribute : Attribute
+public class ModuleAttribute : EntityAttribute
{
- public string[]? Path { get; set; }
-
public ModuleAttribute() { }
- public ModuleAttribute(params string[] path)
- {
- if (path.Length == 0) throw new ArgumentException(
- "Path must not be empty", nameof(path));
- Path = path;
- }
+ public ModuleAttribute(params string[] path) : base(path) { }
}
public interface IModuleInitializer
diff --git a/src/gaemstone/ECS/Universe+Modules.cs b/src/gaemstone/ECS/Universe+Modules.cs
index 3186ffa..71f0da6 100644
--- a/src/gaemstone/ECS/Universe+Modules.cs
+++ b/src/gaemstone/ECS/Universe+Modules.cs
@@ -54,7 +54,7 @@ public class ModuleManager
foreach (var nested in type.GetNestedTypes()) {
if (!nested.GetCustomAttributes(true).OfType().Any()) continue;
- var name = nested.Get()?.Name ?? nested.Name;
+ var name = nested.Get()?.Path?.Single() ?? nested.Name;
Universe.LookupOrThrow(entity, name).CreateLookup(nested);
}
@@ -97,22 +97,22 @@ public class ModuleManager
if (attr == null) throw new ArgumentException(
$"Module {type} must be marked with ModuleAttribute", nameof(type));
- // If path is not specified in the attribute, return the type's name.
- if (attr.Path == null) {
- var assemblyName = type.Assembly.GetName().Name!;
+ // If module is static, its path will be implictly global.
+ var global = (type.IsAbstract && type.IsSealed) || attr.Global;
- if (!type.FullName!.StartsWith(assemblyName + '.')) throw new InvalidOperationException(
- $"Module {type} must be defined under namespace {assemblyName}");
- var fullNameWithoutAssembly = type.FullName![(assemblyName.Length + 1)..];
+ // If global or path are specified in the attribute, return an absolute path.
+ if (global || attr.Path != null)
+ return new(global, attr.Path ?? new[] { type.Name });
- var parts = fullNameWithoutAssembly.Split('.');
- return new(true, parts.Prepend(assemblyName).ToArray());
- }
+ // Otherwise, create it based on the type's assembly, namespace and name.
+ var assemblyName = type.Assembly.GetName().Name!;
+
+ if (!type.FullName!.StartsWith(assemblyName + '.')) throw new InvalidOperationException(
+ $"Module {type} must be defined under namespace {assemblyName}");
+ var fullNameWithoutAssembly = type.FullName![(assemblyName.Length + 1)..];
- var fullPath = new EntityPath(true, attr.Path);
- if (!fullPath.IsAbsolute) throw new ArgumentException(
- $"Module {type} must have an absolute path (if specified)", nameof(type));
- return fullPath;
+ var parts = fullNameWithoutAssembly.Split('.');
+ return new(true, parts.Prepend(assemblyName).ToArray());
}
}
@@ -182,13 +182,12 @@ internal class ModuleInfo
throw new Exception($"Type {typeHint} must be an empty, used-defined struct.");
}
- var name = type.Get()?.Name ?? proxyType.Name;
- try { EntityPath.ValidateName(name); }
- catch (Exception ex) { throw new Exception(
- $"{type} has invalid entity name '{name}: {ex.Message}'", ex); }
+ var path = (type.Get() is EntityAttribute entityAttr)
+ ? new EntityPath(entityAttr.Global, entityAttr.Path ?? new[] { proxyType.Name })
+ : new EntityPath(false, proxyType.Name);
- var builder = Entity.NewChild(name);
- if (!type.Has()) builder.Symbol(name);
+ var builder = path.IsAbsolute ? Universe.New(path) : Entity.NewChild(path);
+ if (!type.Has()) builder.Symbol(path.Name);
foreach (var attr in type.GetMultiple())
builder.Add(Universe.LookupOrThrow(attr.Entity));
diff --git a/src/gaemstone/Flecs/Core.cs b/src/gaemstone/Flecs/Core.cs
index 38c036a..887fc81 100644
--- a/src/gaemstone/Flecs/Core.cs
+++ b/src/gaemstone/Flecs/Core.cs
@@ -15,12 +15,12 @@ public static class Core
// Entities
- [Entity] public struct World { }
- [Entity(Name = "*")] public struct Wildcard { }
- [Entity(Name = "_")] public struct Any { }
- [Entity] public struct This { }
- [Entity(Name = "$")] public struct Variable { }
- [Entity] public struct Flag { }
+ [Entity] public struct World { }
+ [Entity("*")] public struct Wildcard { }
+ [Entity("_")] public struct Any { }
+ [Entity] public struct This { }
+ [Entity("$")] public struct Variable { }
+ [Entity] public struct Flag { }
// Entity Relationships