Allow specifying path in EntityAttribute

- [Module] now based on [Entity]
- Entities can be registered as global
wip/source-generators
copygirl 2 years ago
parent 18615fbb3f
commit db73b54d53
  1. 6
      src/gaemstone/ECS/Component.cs
  2. 24
      src/gaemstone/ECS/Entity.cs
  3. 11
      src/gaemstone/ECS/Module.cs
  4. 39
      src/gaemstone/ECS/Universe+Modules.cs
  5. 12
      src/gaemstone/Flecs/Core.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
{

@ -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; } }
{
/// <summary> If specified, uses this path instead of the default name. </summary>
public string[]? Path { get; }
/// <summary> If true, the path will be absolute instead of relative. </summary>
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;
}
}
/// <summary>
/// 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 <see cref="SourceAttribute{}"/> with itself as the generic type parameter.
/// </summary>
[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<Entity>

@ -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

@ -54,7 +54,7 @@ public class ModuleManager
foreach (var nested in type.GetNestedTypes()) {
if (!nested.GetCustomAttributes(true).OfType<ICreateEntityAttribute>().Any()) continue;
var name = nested.Get<EntityAttribute>()?.Name ?? nested.Name;
var name = nested.Get<EntityAttribute>()?.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<EntityAttribute>()?.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<EntityAttribute>() 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<PrivateAttribute>()) builder.Symbol(name);
var builder = path.IsAbsolute ? Universe.New(path) : Entity.NewChild(path);
if (!type.Has<PrivateAttribute>()) builder.Symbol(path.Name);
foreach (var attr in type.GetMultiple<AddEntityAttribute>())
builder.Add(Universe.LookupOrThrow(attr.Entity));

@ -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

Loading…
Cancel
Save