Compare commits
	
		
			5 Commits 
		
	
	
		
			dc776ace1f
			...
			948268e9ba
		
	
	| Author | SHA1 | Date | 
|---|---|---|
| 
							
							
								
								 | 
						948268e9ba | 3 years ago | 
| 
							
							
								
								 | 
						1d1ba4fe4d | 3 years ago | 
| 
							
							
								
								 | 
						60919ef1f6 | 3 years ago | 
| 
							
							
								
								 | 
						0527919d47 | 3 years ago | 
| 
							
							
								
								 | 
						c24052cfa7 | 3 years ago | 
				 32 changed files with 999 additions and 762 deletions
			
			
		@ -0,0 +1,35 @@ | 
				
			|||||||
 | 
					using System; | 
				
			||||||
 | 
					using static flecs_hub.flecs; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace gaemstone.ECS; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public readonly partial struct Entity | 
				
			||||||
 | 
						: IEquatable<Entity> | 
				
			||||||
 | 
					{ | 
				
			||||||
 | 
						public static readonly Entity None = default; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public readonly ecs_entity_t Value; | 
				
			||||||
 | 
						public uint NumericId => (uint)Value.Data; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public bool IsSome => Value.Data != 0; | 
				
			||||||
 | 
						public bool IsNone => Value.Data == 0; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public Entity(ecs_entity_t value) => Value = value; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public bool Equals(Entity other) => Value.Data == other.Value.Data; | 
				
			||||||
 | 
						public override bool Equals(object? obj) => (obj is Entity other) && Equals(other); | 
				
			||||||
 | 
						public override int GetHashCode() => Value.Data.GetHashCode(); | 
				
			||||||
 | 
						public override string? ToString() | 
				
			||||||
 | 
							=> IsSome ? $"Entity({Value.Data.Data})" | 
				
			||||||
 | 
							          :  "Entity.None"; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public static bool operator ==(Entity left, Entity right) =>  left.Equals(right); | 
				
			||||||
 | 
						public static bool operator !=(Entity left, Entity right) => !left.Equals(right); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public static implicit operator Id          (Entity e) => new(e.Value.Data); | 
				
			||||||
 | 
						public static implicit operator ecs_entity_t(Entity e) => e.Value; | 
				
			||||||
 | 
						public static implicit operator ecs_id_t    (Entity e) => e.Value.Data; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public static implicit operator Term  (Entity entity) => new(entity); | 
				
			||||||
 | 
						public static implicit operator TermId(Entity entity) => new(entity); | 
				
			||||||
 | 
					} | 
				
			||||||
@ -1,32 +1,162 @@ | 
				
			|||||||
using System; | 
					using System; | 
				
			||||||
 | 
					using System.Collections.Generic; | 
				
			||||||
 | 
					using System.Linq; | 
				
			||||||
 | 
					using gaemstone.ECS.Internal; | 
				
			||||||
using static flecs_hub.flecs; | 
					using static flecs_hub.flecs; | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace gaemstone.ECS; | 
					namespace gaemstone.ECS; | 
				
			||||||
 | 
					
 | 
				
			||||||
public readonly struct Entity | 
					public unsafe readonly partial struct Entity<TContext> | 
				
			||||||
	: IEquatable<Entity> | 
						: IEquatable<Entity<TContext>> | 
				
			||||||
{ | 
					{ | 
				
			||||||
	public static readonly Entity None = default; | 
						public static readonly Entity<TContext> None = default; | 
				
			||||||
 | 
					
 | 
				
			||||||
	public readonly ecs_entity_t Value; | 
						public readonly World<TContext> World; | 
				
			||||||
	public uint Id => (uint)Value.Data; | 
						public readonly Entity Value; | 
				
			||||||
 | 
					
 | 
				
			||||||
	public bool IsSome => Value.Data != 0; | 
						public uint NumericId => Value.NumericId; | 
				
			||||||
	public bool IsNone => Value.Data == 0; | 
						public bool IsNone    => Value.IsNone; | 
				
			||||||
 | 
						public bool IsSome    => Value.IsSome; | 
				
			||||||
 | 
					
 | 
				
			||||||
	public Entity(ecs_entity_t value) => Value = value; | 
						public bool IsValid => EntityAccess.IsValid(World, this); | 
				
			||||||
 | 
						public bool IsAlive => EntityAccess.IsAlive(World, this); | 
				
			||||||
 | 
					
 | 
				
			||||||
	public bool Equals(Entity other) => Value.Data == other.Value.Data; | 
						public string? Name	  { get => EntityAccess.GetName(World, this);   set => EntityAccess.SetName(World, this, value); } | 
				
			||||||
	public override bool Equals(object? obj) => (obj is Entity other) && Equals(other); | 
						public string? Symbol { get => EntityAccess.GetSymbol(World, this); set => EntityAccess.SetSymbol(World, this, value); } | 
				
			||||||
	public override int GetHashCode() => Value.Data.GetHashCode(); | 
					
 | 
				
			||||||
 | 
						public EntityPath Path => EntityPath.From(World, this); | 
				
			||||||
 | 
						public EntityType<TContext> Type => new(World, ecs_get_type(World, this)); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public Entity<TContext>? Parent | 
				
			||||||
 | 
							=> GetOrNull(World, GetTargets(FlecsBuiltIn.ChildOf).FirstOrDefault()); | 
				
			||||||
 | 
						public IEnumerable<Entity<TContext>> Children | 
				
			||||||
 | 
							=> Iterator<TContext>.FromTerm(World, new(FlecsBuiltIn.ChildOf, this)).GetAllEntities(); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private Entity(World<TContext> world, Entity value) | 
				
			||||||
 | 
							{ World = world; Value = value; } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public static Entity<TContext> GetOrInvalid(World<TContext> world, Entity value) | 
				
			||||||
 | 
							=> new(world, value); | 
				
			||||||
 | 
						public static Entity<TContext>? GetOrNull(World<TContext> world, Entity value) | 
				
			||||||
 | 
							=> ecs_is_valid(world, value) ? new(world, value) : null; | 
				
			||||||
 | 
						public static Entity<TContext> GetOrThrow(World<TContext> world, Entity value) | 
				
			||||||
 | 
							=> ecs_is_valid(world, value) ? new(world, value) : throw new InvalidOperationException($"The entity {value} is not valid"); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public void Delete() | 
				
			||||||
 | 
							=> ecs_delete(World, this); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public Entity<TContext> CreateLookup<T>() | 
				
			||||||
 | 
						{ | 
				
			||||||
 | 
							ref var lookup = ref Lookup<TContext>.Entity<T>.Value; | 
				
			||||||
 | 
							if (lookup.IsSome) throw new InvalidOperationException( | 
				
			||||||
 | 
								$"The lookup for type {typeof(T)} in context {typeof(TContext)} is already in use by {lookup}"); | 
				
			||||||
 | 
							lookup = this; | 
				
			||||||
 | 
							return this; | 
				
			||||||
 | 
						} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public EntityBuilder<TContext> NewChild(EntityPath? path = null) | 
				
			||||||
 | 
							=> World.New(path?.ThrowIfAbsolute(), this); | 
				
			||||||
 | 
						public Entity<TContext>? LookupChildOrNull(EntityPath path) | 
				
			||||||
 | 
							=> World.LookupPathOrNull(path.ThrowIfAbsolute(), this); | 
				
			||||||
 | 
						public Entity<TContext> LookupChildOrThrow(EntityPath path) | 
				
			||||||
 | 
							=> World.LookupPathOrThrow(path.ThrowIfAbsolute()!, this); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public Entity<TContext> ChildOf(Entity parent) | 
				
			||||||
 | 
							=> Add(FlecsBuiltIn.ChildOf, parent); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public bool IsDisabled => Has(FlecsBuiltIn.Disabled); | 
				
			||||||
 | 
						public Entity<TContext> Disable() => Add(FlecsBuiltIn.Disabled); | 
				
			||||||
 | 
						public Entity<TContext> Enable()  => Remove(FlecsBuiltIn.Disabled); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public Entity<TContext> Add(Id id) { EntityAccess.Add(World, this, id); return this; } | 
				
			||||||
 | 
						public Entity<TContext> Add(string symbol) => Add(World.LookupSymbolOrThrow(symbol)); | 
				
			||||||
 | 
						public Entity<TContext> Add(Entity relation, Entity target) => Add(Id.Pair(relation, target)); | 
				
			||||||
 | 
						public Entity<TContext> Add<TEntity>() => Add(World.Entity<TEntity>()); | 
				
			||||||
 | 
						public Entity<TContext> Add<TRelation>(Entity target) => Add(World.Entity<TRelation>(), target); | 
				
			||||||
 | 
						public Entity<TContext> Add<TRelation, TTarget>() => Add(World.Entity<TRelation>(), World.Entity<TTarget>()); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public Entity<TContext> Remove(Id id) { EntityAccess.Remove(World, this, id); return this; } | 
				
			||||||
 | 
						public Entity<TContext> Remove(string symbol) => Remove(World.LookupSymbolOrThrow(symbol)); | 
				
			||||||
 | 
						public Entity<TContext> Remove(Entity relation, Entity target) => Remove(Id.Pair(relation, target)); | 
				
			||||||
 | 
						public Entity<TContext> Remove<TEntity>() => Remove(World.Entity<TEntity>()); | 
				
			||||||
 | 
						public Entity<TContext> Remove<TRelation>(Entity target) => Remove(World.Entity<TRelation>(), target); | 
				
			||||||
 | 
						public Entity<TContext> Remove<TRelation, TTarget>() => Remove(World.Entity<TRelation>(), World.Entity<TTarget>()); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public bool Has(Id id) => EntityAccess.Has(World, this, id); | 
				
			||||||
 | 
						public bool Has(string symbol) => Has(World.LookupSymbolOrThrow(symbol)); | 
				
			||||||
 | 
						public bool Has(Entity relation, Entity target) => Has(Id.Pair(relation, target)); | 
				
			||||||
 | 
						public bool Has<TEntity>() => Has(World.Entity<TEntity>()); | 
				
			||||||
 | 
						public bool Has<TRelation>(Entity target) => Has(World.Entity<TRelation>(), target); | 
				
			||||||
 | 
						public bool Has<TRelation, TTarget>() => Has(World.Entity<TRelation>(), World.Entity<TTarget>()); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public T? GetOrNull<T>(Id id) where T : unmanaged => EntityAccess.GetOrNull<T>(World, this, id); | 
				
			||||||
 | 
						public T? GetOrNull<T>(Id id, T _ = null!) where T : class => EntityAccess.GetOrNull<T>(World, this, id); | 
				
			||||||
 | 
						public T GetOrThrow<T>(Id id) => EntityAccess.GetOrThrow<T>(World, this, id); | 
				
			||||||
 | 
						public ref T GetMut<T>(Id id) where T : unmanaged => ref EntityAccess.GetMut<T>(World, this, id); | 
				
			||||||
 | 
						public ref T GetRefOrNull<T>(Id id) where T : unmanaged => ref EntityAccess.GetRefOrNull<T>(World, this, id); | 
				
			||||||
 | 
						public ref T GetRefOrThrow<T>(Id id) where T : unmanaged => ref EntityAccess.GetRefOrThrow<T>(World, this, id); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public Entity<TContext> Modified(Id id) { EntityAccess.Modified(World, this, id); return this; } | 
				
			||||||
 | 
						public Entity<TContext> Set<T>(Id id, in T value) where T : unmanaged { EntityAccess.Set(World, this, id, value); return this; } | 
				
			||||||
 | 
						public Entity<TContext> Set<T>(Id id, T value) where T : class { EntityAccess.Set(World, this, id, value); return this; } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public T? GetOrNull<T>() where T : unmanaged => GetOrNull<T>(World.Entity<T>()); | 
				
			||||||
 | 
						public T? GetOrNull<T>(T _ = null!) where T : class => GetOrNull<T>(World.Entity<T>()); | 
				
			||||||
 | 
						public T GetOrThrow<T>() => GetOrThrow<T>(World.Entity<T>()); | 
				
			||||||
 | 
						public ref T GetMut<T>() where T : unmanaged => ref GetMut<T>(World.Entity<T>()); | 
				
			||||||
 | 
						public ref T GetRefOrNull<T>() where T : unmanaged => ref GetRefOrNull<T>(World.Entity<T>()); | 
				
			||||||
 | 
						public ref T GetRefOrThrow<T>() where T : unmanaged => ref GetRefOrThrow<T>(World.Entity<T>()); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public Entity<TContext> Modified<T>() => Modified(World.Entity<T>()); | 
				
			||||||
 | 
						public Entity<TContext> Set<T>(in T value) where T : unmanaged => Set(World.Entity<T>(), value); | 
				
			||||||
 | 
						public Entity<TContext> Set<T>(T value) where T : class => Set(World.Entity<T>(), value); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public IEnumerable<Entity<TContext>> GetTargets(Entity relation) | 
				
			||||||
 | 
						{ | 
				
			||||||
 | 
							foreach (var entity in EntityAccess.GetTargets(World, this, relation)) | 
				
			||||||
 | 
								yield return new(World, entity); | 
				
			||||||
 | 
						} | 
				
			||||||
 | 
						public IEnumerable<Entity<TContext>> GetTargets(string symbol) | 
				
			||||||
 | 
							=> GetTargets(World.LookupSymbolOrThrow(symbol)); | 
				
			||||||
 | 
						public IEnumerable<Entity<TContext>> GetTargets<TRelation>() | 
				
			||||||
 | 
							=> GetTargets(World.Entity<TRelation>()); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public bool Equals(Entity<TContext> other) | 
				
			||||||
 | 
						{ | 
				
			||||||
 | 
					#if DEBUG | 
				
			||||||
 | 
							// In DEBUG mode, we additionally check if the worlds the two compared | 
				
			||||||
 | 
							// values are from the same world. This accounts for the world being a | 
				
			||||||
 | 
							// stage, hence why it might not be the cheapest operation. | 
				
			||||||
 | 
							if (World != other.World) throw new ArgumentException( | 
				
			||||||
 | 
								"The specified values are not from the same world"); | 
				
			||||||
 | 
					#endif | 
				
			||||||
 | 
							return Value == other.Value; | 
				
			||||||
 | 
						} | 
				
			||||||
 | 
						public override bool Equals(object? obj) | 
				
			||||||
 | 
							=> (obj is Entity<TContext> other) && Equals(other); | 
				
			||||||
 | 
						public override int GetHashCode() | 
				
			||||||
 | 
							=> Value.GetHashCode(); | 
				
			||||||
	public override string? ToString() | 
						public override string? ToString() | 
				
			||||||
		=> IsSome ? $"Entity({Value.Data.Data})" | 
							=> Value.ToString(); | 
				
			||||||
		          :  "Entity.None"; | 
					
 | 
				
			||||||
 | 
						public static bool operator ==(Entity<TContext> left, Entity<TContext> right) =>  left.Equals(right); | 
				
			||||||
 | 
						public static bool operator !=(Entity<TContext> left, Entity<TContext> right) => !left.Equals(right); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public static implicit operator Entity      (Entity<TContext> entity) => entity.Value; | 
				
			||||||
 | 
						public static implicit operator ecs_entity_t(Entity<TContext> entity) => entity.Value.Value; | 
				
			||||||
 | 
					
 | 
				
			||||||
	public static bool operator ==(Entity left, Entity right) =>  left.Equals(right); | 
						public static implicit operator Id<TContext>(Entity<TContext> entity) => Id<TContext>.GetUnsafe(entity.World, entity); | 
				
			||||||
	public static bool operator !=(Entity left, Entity right) => !left.Equals(right); | 
						public static implicit operator Id          (Entity<TContext> entity) => new(entity.Value.Value.Data); | 
				
			||||||
 | 
						public static implicit operator ecs_id_t    (Entity<TContext> entity) => entity.Value.Value.Data; | 
				
			||||||
 | 
					
 | 
				
			||||||
	public static implicit operator ecs_entity_t(Entity e) => e.Value; | 
						public static implicit operator Term  (Entity<TContext> entity) => new(entity.Value); | 
				
			||||||
	public static implicit operator Id(Entity e) => new(e.Value.Data); | 
						public static implicit operator TermId(Entity<TContext> entity) => new(entity.Value); | 
				
			||||||
	public static implicit operator ecs_id_t(Entity e) => e.Value.Data; | 
					 | 
				
			||||||
} | 
					} | 
				
			||||||
 | 
				
			|||||||
@ -1,37 +0,0 @@ | 
				
			|||||||
namespace gaemstone.ECS; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// TODO: Turn this into an interface? | 
					 | 
				
			||||||
public abstract class EntityBase<TReturn> | 
					 | 
				
			||||||
{ | 
					 | 
				
			||||||
	public abstract World World { get; } | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	public abstract TReturn Add(Id id); | 
					 | 
				
			||||||
	public abstract TReturn Remove(Id id); | 
					 | 
				
			||||||
	public abstract bool Has(Id id); | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	public TReturn Add(string symbol) => Add(World.LookupBySymbolOrThrow(symbol)); | 
					 | 
				
			||||||
	public TReturn Add(Entity relation, Entity target) => Add(Id.Pair(relation, target)); | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	public TReturn Remove(string symbol) => Remove(World.LookupBySymbolOrThrow(symbol)); | 
					 | 
				
			||||||
	public TReturn Remove(Entity relation, Entity target) => Remove(Id.Pair(relation, target)); | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	public bool Has(string symbol) => Has(World.LookupBySymbolOrThrow(symbol)); | 
					 | 
				
			||||||
	public bool Has(Entity relation, Entity target) => Has(Id.Pair(relation, target)); | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	public abstract T? GetOrNull<T>(Id id) where T : unmanaged; | 
					 | 
				
			||||||
	public abstract T? GetOrNull<T>(Id id, T _ = null!) where T : class; | 
					 | 
				
			||||||
	public abstract T GetOrThrow<T>(Id id); | 
					 | 
				
			||||||
	public abstract ref T GetMut<T>(Id id) where T : unmanaged; | 
					 | 
				
			||||||
	public abstract ref T GetRefOrNull<T>(Id id) where T : unmanaged; | 
					 | 
				
			||||||
	public abstract ref T GetRefOrThrow<T>(Id id) where T : unmanaged; | 
					 | 
				
			||||||
	public abstract void Modified<T>(Id id); | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	public abstract TReturn Set<T>(Id id, in T value) where T : unmanaged; | 
					 | 
				
			||||||
	public abstract TReturn Set<T>(Id id, T obj) where T : class; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	public TReturn ChildOf(Entity parent) => Add(World.ChildOf, parent); | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	public TReturn Disable() => Add(World.Disabled); | 
					 | 
				
			||||||
	public TReturn Enable() => Remove(World.Disabled); | 
					 | 
				
			||||||
	public bool IsDisabled => Has(World.Disabled); | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
@ -1,169 +0,0 @@ | 
				
			|||||||
using System; | 
					 | 
				
			||||||
using System.Collections.Generic; | 
					 | 
				
			||||||
using System.Linq; | 
					 | 
				
			||||||
using System.Runtime.CompilerServices; | 
					 | 
				
			||||||
using gaemstone.Utility; | 
					 | 
				
			||||||
using static flecs_hub.flecs; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
namespace gaemstone.ECS; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
public unsafe class EntityRef | 
					 | 
				
			||||||
	: EntityBase<EntityRef> | 
					 | 
				
			||||||
	, IEquatable<EntityRef> | 
					 | 
				
			||||||
{ | 
					 | 
				
			||||||
	public override World World { get; } | 
					 | 
				
			||||||
	public Entity Entity { get; } | 
					 | 
				
			||||||
	public uint Id => Entity.Id; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	public bool IsAlive => ecs_is_alive(World, this); | 
					 | 
				
			||||||
	public EntityType Type => new(World, ecs_get_type(World, this)); | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	public string? Name { | 
					 | 
				
			||||||
		get => ecs_get_name(World, this).FlecsToString()!; | 
					 | 
				
			||||||
		set { using var alloc = TempAllocator.Use(); | 
					 | 
				
			||||||
		      ecs_set_name(World, this, alloc.AllocateCString(value)); } | 
					 | 
				
			||||||
	} | 
					 | 
				
			||||||
	public string? Symbol { | 
					 | 
				
			||||||
		get => ecs_get_symbol(World, this).FlecsToString()!; | 
					 | 
				
			||||||
		set { using var alloc = TempAllocator.Use(); | 
					 | 
				
			||||||
		      ecs_set_symbol(World, this, alloc.AllocateCString(value)); } | 
					 | 
				
			||||||
	} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	private EntityRef(World world, Entity entity, bool throwOnInvalid) | 
					 | 
				
			||||||
	{ | 
					 | 
				
			||||||
		if (throwOnInvalid && !ecs_is_valid(world, entity)) | 
					 | 
				
			||||||
			throw new InvalidOperationException($"The entity {entity} is not valid"); | 
					 | 
				
			||||||
		World  = world; | 
					 | 
				
			||||||
		Entity = entity; | 
					 | 
				
			||||||
	} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	public EntityRef(World world, Entity entity) | 
					 | 
				
			||||||
		: this(world, entity, true) {  } | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	public static EntityRef? CreateOrNull(World world, Entity entity) | 
					 | 
				
			||||||
		=> ecs_is_valid(world, entity) ? new(world, entity) : null; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	public void Delete() | 
					 | 
				
			||||||
		=> ecs_delete(World, this); | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	public EntityBuilder NewChild(EntityPath? path = null) | 
					 | 
				
			||||||
		=> World.New(this, EnsureRelativePath(path)); | 
					 | 
				
			||||||
	public EntityRef? LookupChild(EntityPath path) | 
					 | 
				
			||||||
		=> World.LookupByPath(this, EnsureRelativePath(path)!); | 
					 | 
				
			||||||
	public EntityRef LookupChildOrThrow(EntityPath path) | 
					 | 
				
			||||||
		=> World.LookupByPathOrThrow(this, EnsureRelativePath(path)!); | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	private static EntityPath? EnsureRelativePath(EntityPath? path) | 
					 | 
				
			||||||
	{ | 
					 | 
				
			||||||
		if (path?.IsAbsolute == true) throw new ArgumentException( | 
					 | 
				
			||||||
			$"Path '{path}' must not be absolute", nameof(path)); | 
					 | 
				
			||||||
		return path; | 
					 | 
				
			||||||
	} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	public EntityRef? Parent | 
					 | 
				
			||||||
		=> GetTargets(World.ChildOf).FirstOrDefault(); | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// TODO: Change to property after all? | 
					 | 
				
			||||||
	public IEnumerable<EntityRef> GetChildren() | 
					 | 
				
			||||||
	{ | 
					 | 
				
			||||||
		foreach (var iter in Iterator.FromTerm(World, new(World.ChildOf, this))) | 
					 | 
				
			||||||
			for (var i = 0; i < iter.Count; i++) | 
					 | 
				
			||||||
				yield return iter.Entity(i); | 
					 | 
				
			||||||
	} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	public override EntityRef Add(Id id) { ecs_add_id(World, this, id); return this; } | 
					 | 
				
			||||||
	public override EntityRef Remove(Id id) { ecs_remove_id(World, this, id); return this; } | 
					 | 
				
			||||||
	public override bool Has(Id id) => ecs_has_id(World, this, id); | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	public override T? GetOrNull<T>(Id id) | 
					 | 
				
			||||||
	{ | 
					 | 
				
			||||||
		var ptr = ecs_get_id(World, this, id); | 
					 | 
				
			||||||
		return (ptr != null) ? Unsafe.Read<T>(ptr) : null; | 
					 | 
				
			||||||
	} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	public override T? GetOrNull<T>(Id id, T _ = null!) | 
					 | 
				
			||||||
		where T : class
 | 
					 | 
				
			||||||
	{ | 
					 | 
				
			||||||
		var ptr = ecs_get_id(World, this, id); | 
					 | 
				
			||||||
		return (T?)Unsafe.Read<ReferenceHandle>(ptr).Target; | 
					 | 
				
			||||||
	} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	public override T GetOrThrow<T>(Id id) | 
					 | 
				
			||||||
	{ | 
					 | 
				
			||||||
		var ptr = ecs_get_id(World, this, id); | 
					 | 
				
			||||||
		if (ptr == null) throw new Exception($"Component {typeof(T)} not found on {this}"); | 
					 | 
				
			||||||
		if (typeof(T).IsValueType) return Unsafe.Read<T>(ptr); | 
					 | 
				
			||||||
		else return (T)Unsafe.Read<ReferenceHandle>(ptr).Target!; | 
					 | 
				
			||||||
	} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	public override ref T GetRefOrNull<T>(Id id) | 
					 | 
				
			||||||
	{ | 
					 | 
				
			||||||
		var @ref = ecs_ref_init_id(World, this, id); | 
					 | 
				
			||||||
		var ptr  = ecs_ref_get_id(World, &@ref, id); | 
					 | 
				
			||||||
		return ref (ptr != null) ? ref Unsafe.AsRef<T>(ptr) | 
					 | 
				
			||||||
		                         : ref Unsafe.NullRef<T>(); | 
					 | 
				
			||||||
	} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	public override ref T GetRefOrThrow<T>(Id id) | 
					 | 
				
			||||||
	{ | 
					 | 
				
			||||||
		ref var ptr = ref GetRefOrNull<T>(id); | 
					 | 
				
			||||||
		if (Unsafe.IsNullRef(ref ptr)) throw new Exception( | 
					 | 
				
			||||||
			$"Component {typeof(T)} not found on {this}"); | 
					 | 
				
			||||||
		return ref ptr; | 
					 | 
				
			||||||
	} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	public override ref T GetMut<T>(Id id) | 
					 | 
				
			||||||
	{ | 
					 | 
				
			||||||
		var ptr = ecs_get_mut_id(World, this, id); | 
					 | 
				
			||||||
		// NOTE: Value is added if it doesn't exist on the entity. | 
					 | 
				
			||||||
		return ref Unsafe.AsRef<T>(ptr); | 
					 | 
				
			||||||
	} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	public override void Modified<T>(Id id) | 
					 | 
				
			||||||
		=> ecs_modified_id(World, this, id); | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	public override EntityRef Set<T>(Id id, in T value) | 
					 | 
				
			||||||
	{ | 
					 | 
				
			||||||
		var size = (ulong)Unsafe.SizeOf<T>(); | 
					 | 
				
			||||||
		fixed (T* ptr = &value) | 
					 | 
				
			||||||
			if (ecs_set_id(World, this, id, size, ptr).Data == 0) | 
					 | 
				
			||||||
				throw new InvalidOperationException(); | 
					 | 
				
			||||||
		return this; | 
					 | 
				
			||||||
	} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	public override EntityRef Set<T>(Id id, T obj) where T : class
 | 
					 | 
				
			||||||
	{ | 
					 | 
				
			||||||
		if (obj == null) throw new ArgumentNullException(nameof(obj)); | 
					 | 
				
			||||||
		var size = (ulong)sizeof(ReferenceHandle); | 
					 | 
				
			||||||
		// Dispose this handle afterwards, since Flecs clones it. | 
					 | 
				
			||||||
		using var handle = ReferenceHandle.Alloc(obj); | 
					 | 
				
			||||||
		if (ecs_set_id(World, this, id, size, &handle).Data == 0) | 
					 | 
				
			||||||
			throw new InvalidOperationException(); | 
					 | 
				
			||||||
		return this; | 
					 | 
				
			||||||
	} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	private EntityRef? GetTarget(Entity relation, int index) | 
					 | 
				
			||||||
		=> CreateOrNull(World, new(ecs_get_target(World, this, relation, index))); | 
					 | 
				
			||||||
	public IEnumerable<EntityRef> GetTargets(Entity relation) | 
					 | 
				
			||||||
		{ var index = 0; while (GetTarget(relation, index++) is EntityRef target) yield return target; } | 
					 | 
				
			||||||
	public IEnumerable<EntityRef> GetTargets(string symbol) | 
					 | 
				
			||||||
		=> GetTargets(World.LookupBySymbolOrThrow(symbol)); | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	public bool Equals(EntityRef? other) => (other is not null) && (World == other.World) && (Entity == other.Entity); | 
					 | 
				
			||||||
	public override bool Equals(object? obj) => Equals(obj as EntityRef); | 
					 | 
				
			||||||
	public override int GetHashCode() => HashCode.Combine(World, Entity); | 
					 | 
				
			||||||
	public override string? ToString() => ecs_entity_str(World, this).FlecsToStringAndFree()!; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	public static bool operator ==(EntityRef? left, EntityRef? right) => ReferenceEquals(left, right) || (left?.Equals(right) ?? false); | 
					 | 
				
			||||||
	public static bool operator !=(EntityRef? left, EntityRef? right) => !(left == right); | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	public static implicit operator Entity(EntityRef? e) => e?.Entity ?? default; | 
					 | 
				
			||||||
	public static implicit operator ecs_entity_t(EntityRef? e) => e?.Entity.Value ?? default; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	public static implicit operator Id(EntityRef? e) => new(e?.Entity.Value.Data ?? default); | 
					 | 
				
			||||||
	public static implicit operator ecs_id_t(EntityRef? e) => e?.Entity.Value.Data ?? default; | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
@ -0,0 +1,31 @@ | 
				
			|||||||
 | 
					using System; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace gaemstone.ECS; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public class EntityNotFoundException | 
				
			||||||
 | 
						: Exception | 
				
			||||||
 | 
					{ | 
				
			||||||
 | 
						public EntityNotFoundException(string message) | 
				
			||||||
 | 
							: base(message) {  } | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public class ComponentNotFoundException | 
				
			||||||
 | 
						: Exception | 
				
			||||||
 | 
					{ | 
				
			||||||
 | 
						public ComponentNotFoundException(string message) | 
				
			||||||
 | 
							: base(message) {  } | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public class FlecsException | 
				
			||||||
 | 
						: Exception | 
				
			||||||
 | 
					{ | 
				
			||||||
 | 
						public FlecsException() : base() {  } | 
				
			||||||
 | 
						public FlecsException(string message) : base(message) {  } | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public class FlecsAbortException | 
				
			||||||
 | 
						: FlecsException | 
				
			||||||
 | 
					{ | 
				
			||||||
 | 
						internal FlecsAbortException() | 
				
			||||||
 | 
							: base("Abort was called by flecs") {  } | 
				
			||||||
 | 
					} | 
				
			||||||
@ -0,0 +1,36 @@ | 
				
			|||||||
 | 
					using System; | 
				
			||||||
 | 
					using static flecs_hub.flecs; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace gaemstone.ECS; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public readonly struct Id | 
				
			||||||
 | 
						: IEquatable<Id> | 
				
			||||||
 | 
					{ | 
				
			||||||
 | 
						public readonly ecs_id_t Value; | 
				
			||||||
 | 
						public IdFlags Flags => (IdFlags)(Value & ECS_ID_FLAGS_MASK); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public bool IsPair     => ecs_id_is_pair(this); | 
				
			||||||
 | 
						public bool IsWildcard => ecs_id_is_wildcard(this); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public Entity RelationUnsafe => new(new() { Data = (Value & ECS_COMPONENT_MASK) >> 32 }); | 
				
			||||||
 | 
						public Entity TargetUnsafe   => new(new() { Data =  Value & ECS_ENTITY_MASK }); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public Id(ecs_id_t value) => Value = value; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public static Id Pair(Entity relation, Entity target) | 
				
			||||||
 | 
							=> new(ecs_make_pair(relation, target)); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public bool Equals(Id other) => Value.Data == other.Value.Data; | 
				
			||||||
 | 
						public override bool Equals(object? obj) => (obj is Id other) && Equals(other); | 
				
			||||||
 | 
						public override int GetHashCode() => Value.Data.GetHashCode(); | 
				
			||||||
 | 
						public override string? ToString() | 
				
			||||||
 | 
							=> (Flags != default) ? $"Id({Value.Data}, Flags={Flags})" | 
				
			||||||
 | 
							                      : $"Id({Value.Data})"; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public static bool operator ==(Id left, Id right) =>  left.Equals(right); | 
				
			||||||
 | 
						public static bool operator !=(Id left, Id right) => !left.Equals(right); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public static implicit operator ecs_id_t(Id id) => id.Value; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public static implicit operator Term(Id id) => new(id); | 
				
			||||||
 | 
					} | 
				
			||||||
@ -1,53 +0,0 @@ | 
				
			|||||||
using System; | 
					 | 
				
			||||||
using gaemstone.Utility; | 
					 | 
				
			||||||
using static flecs_hub.flecs; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
namespace gaemstone.ECS; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
public unsafe class IdRef | 
					 | 
				
			||||||
	: IEquatable<IdRef> | 
					 | 
				
			||||||
{ | 
					 | 
				
			||||||
	public World World { get; } | 
					 | 
				
			||||||
	public Id Id { get; } | 
					 | 
				
			||||||
	public IdFlags Flags => Id.Flags; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	public bool IsPair     => Id.IsPair; | 
					 | 
				
			||||||
	public bool IsWildcard => Id.IsWildcard; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	public bool IsValid => ecs_id_is_valid(World, this); | 
					 | 
				
			||||||
	public bool IsInUse => ecs_id_in_use(World, this); | 
					 | 
				
			||||||
	public int  Count   => ecs_count_id(World, this); | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	public IdRef(World world, Id id) | 
					 | 
				
			||||||
		{ World = world; Id = id; } | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	public static IdRef Combine(IdFlags flags, IdRef id) | 
					 | 
				
			||||||
		=> new(id.World, Id.Combine(flags, id)); | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	public static IdRef Pair(World world, Entity relation, Entity target) | 
					 | 
				
			||||||
		=> new(world, Id.Pair(relation, target)); | 
					 | 
				
			||||||
	public static IdRef Pair(EntityRef relation, Entity target) | 
					 | 
				
			||||||
		=> Pair(relation.World, relation, target); | 
					 | 
				
			||||||
	public static IdRef Pair(Entity relation, EntityRef target) | 
					 | 
				
			||||||
		=> Pair(target.World, relation, target); | 
					 | 
				
			||||||
	public static IdRef Pair(EntityRef relation, EntityRef target) | 
					 | 
				
			||||||
		=> Pair(relation.World, relation, target); | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	public EntityRef? AsEntity() | 
					 | 
				
			||||||
		=> (Flags == default) ? World.LookupAlive(new Entity(new() { Data = Id })) : null; | 
					 | 
				
			||||||
	public (EntityRef Relation, EntityRef Target)? AsPair() | 
					 | 
				
			||||||
		=> IsPair && (World.LookupAlive(Id.RelationUnsafe) is EntityRef relation) && | 
					 | 
				
			||||||
		             (World.LookupAlive(Id.TargetUnsafe  ) is EntityRef target  ) | 
					 | 
				
			||||||
			? (relation, target) : null; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	public bool Equals(IdRef? other) => (other is not null) && (World == other.World) && (Id == other.Id); | 
					 | 
				
			||||||
	public override bool Equals(object? obj) => Equals(obj as IdRef); | 
					 | 
				
			||||||
	public override int GetHashCode() => HashCode.Combine(World, Id); | 
					 | 
				
			||||||
	public override string? ToString() => ecs_id_str(World, this).FlecsToStringAndFree()!; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	public static bool operator ==(IdRef? left, IdRef? right) => ReferenceEquals(left, right) || (left?.Equals(right) ?? false); | 
					 | 
				
			||||||
	public static bool operator !=(IdRef? left, IdRef? right) => !(left == right); | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	public static implicit operator Id(IdRef i) => i.Id; | 
					 | 
				
			||||||
	public static implicit operator ecs_id_t(IdRef i) => i.Id.Value; | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
@ -0,0 +1,117 @@ | 
				
			|||||||
 | 
					using System; | 
				
			||||||
 | 
					using System.Collections.Generic; | 
				
			||||||
 | 
					using System.Runtime.CompilerServices; | 
				
			||||||
 | 
					using gaemstone.ECS.Utility; | 
				
			||||||
 | 
					using static flecs_hub.flecs; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace gaemstone.ECS.Internal; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public unsafe static class EntityAccess | 
				
			||||||
 | 
					{ | 
				
			||||||
 | 
						public static bool IsValid(World world, Entity entity) | 
				
			||||||
 | 
							=> ecs_is_valid(world, entity); | 
				
			||||||
 | 
						public static bool IsAlive(World world, Entity entity) | 
				
			||||||
 | 
							=> ecs_is_alive(world, entity); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public static string? GetName(World world, Entity entity) | 
				
			||||||
 | 
							=> ecs_get_name(world, entity).FlecsToString()!; | 
				
			||||||
 | 
						public static void SetName(World world, Entity entity, string? value) | 
				
			||||||
 | 
							{ using var alloc = TempAllocator.Use(); ecs_set_name(world, entity, alloc.AllocateCString(value)); } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public static string? GetSymbol(World world, Entity entity) | 
				
			||||||
 | 
							=> ecs_get_symbol(world, entity).FlecsToString()!; | 
				
			||||||
 | 
						public static void SetSymbol(World world, Entity entity, string? value) | 
				
			||||||
 | 
							{ using var alloc = TempAllocator.Use(); ecs_set_symbol(world, entity, alloc.AllocateCString(value)); } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public static void Add(World world, Entity entity, Id id) | 
				
			||||||
 | 
							=> ecs_add_id(world, entity, id); | 
				
			||||||
 | 
						public static void Remove(World world, Entity entity, Id id) | 
				
			||||||
 | 
							=> ecs_remove_id(world, entity, id); | 
				
			||||||
 | 
						public static bool Has(World world, Entity entity, Id id) | 
				
			||||||
 | 
							=> ecs_has_id(world, entity, id); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public static IEnumerable<Entity> GetTargets(World world, Entity entity, Entity relation) | 
				
			||||||
 | 
						{ | 
				
			||||||
 | 
							Entity GetTarget(int index) | 
				
			||||||
 | 
								=> new(ecs_get_target(world, entity, relation, index)); | 
				
			||||||
 | 
							for (var i = 0; GetTarget(i) is { IsSome: true } target; i++) | 
				
			||||||
 | 
								yield return target; | 
				
			||||||
 | 
						} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public static T? GetOrNull<T>(World world, Entity entity, Id id) | 
				
			||||||
 | 
							where T : unmanaged | 
				
			||||||
 | 
						{ | 
				
			||||||
 | 
							var ptr = ecs_get_id(world, entity, id); | 
				
			||||||
 | 
							return (ptr != null) ? Unsafe.Read<T>(ptr) : null; | 
				
			||||||
 | 
						} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public static T? GetOrNull<T>(World world, Entity entity, Id id, T _ = null!) | 
				
			||||||
 | 
							where T : class
 | 
				
			||||||
 | 
						{ | 
				
			||||||
 | 
							var ptr = ecs_get_id(world, entity, id); | 
				
			||||||
 | 
							return (T?)Unsafe.Read<ReferenceHandle>(ptr).Target; | 
				
			||||||
 | 
						} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public static T GetOrThrow<T>(World world, Entity entity, Id id) | 
				
			||||||
 | 
						{ | 
				
			||||||
 | 
							var ptr = ecs_get_id(world, entity, id); | 
				
			||||||
 | 
							if (ptr == null) throw new ComponentNotFoundException($"Component {id} not found on {entity}"); | 
				
			||||||
 | 
							if (typeof(T).IsValueType) return Unsafe.Read<T>(ptr); | 
				
			||||||
 | 
							else return (T)Unsafe.Read<ReferenceHandle>(ptr).Target!; | 
				
			||||||
 | 
						} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public static ref T GetMut<T>(World world, Entity entity, Id id) | 
				
			||||||
 | 
							where T : unmanaged | 
				
			||||||
 | 
						{ | 
				
			||||||
 | 
							var ptr = ecs_get_mut_id(world, entity, id); | 
				
			||||||
 | 
							// NOTE: Value is added if it doesn't exist on the entity. | 
				
			||||||
 | 
							return ref Unsafe.AsRef<T>(ptr); | 
				
			||||||
 | 
						} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public static ref T GetRefOrNull<T>(World world, Entity entity, Id id) | 
				
			||||||
 | 
							where T : unmanaged | 
				
			||||||
 | 
						{ | 
				
			||||||
 | 
							var @ref = ecs_ref_init_id(world, entity, id); | 
				
			||||||
 | 
							var ptr  = ecs_ref_get_id(world, &@ref, id); | 
				
			||||||
 | 
							return ref (ptr != null) ? ref Unsafe.AsRef<T>(ptr) | 
				
			||||||
 | 
							                         : ref Unsafe.NullRef<T>(); | 
				
			||||||
 | 
						} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public static ref T GetRefOrThrow<T>(World world, Entity entity, Id id) | 
				
			||||||
 | 
							where T : unmanaged | 
				
			||||||
 | 
						{ | 
				
			||||||
 | 
							ref var ptr = ref GetRefOrNull<T>(world, entity, id); | 
				
			||||||
 | 
							if (Unsafe.IsNullRef(ref ptr)) throw new Exception( | 
				
			||||||
 | 
								$"Component {typeof(T)} not found on {entity}"); | 
				
			||||||
 | 
							return ref ptr; | 
				
			||||||
 | 
						} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public static void Modified(World world, Entity entity, Id id) | 
				
			||||||
 | 
							=> ecs_modified_id(world, entity, id); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public static Entity Set<T>(World world, Entity entity, Id id, in T value) | 
				
			||||||
 | 
							where T : unmanaged | 
				
			||||||
 | 
						{ | 
				
			||||||
 | 
							var size = (ulong)Unsafe.SizeOf<T>(); | 
				
			||||||
 | 
							fixed (T* ptr = &value) | 
				
			||||||
 | 
								if (ecs_set_id(world, entity, id, size, ptr).Data == 0) | 
				
			||||||
 | 
									throw new InvalidOperationException(); | 
				
			||||||
 | 
							return entity; | 
				
			||||||
 | 
						} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public static Entity Set<T>(World world, Entity entity, Id id, T value) | 
				
			||||||
 | 
							where T : class
 | 
				
			||||||
 | 
						{ | 
				
			||||||
 | 
							if (value == null) throw new ArgumentNullException(nameof(value)); | 
				
			||||||
 | 
							var size = (ulong)sizeof(ReferenceHandle); | 
				
			||||||
 | 
							// Dispose entity handle afterwards, since Flecs clones it. | 
				
			||||||
 | 
							using var handle = ReferenceHandle.Alloc(value); | 
				
			||||||
 | 
							if (ecs_set_id(world, entity, id, size, &handle).Data == 0) | 
				
			||||||
 | 
								throw new InvalidOperationException(); | 
				
			||||||
 | 
							return entity; | 
				
			||||||
 | 
						} | 
				
			||||||
 | 
					} | 
				
			||||||
@ -0,0 +1,11 @@ | 
				
			|||||||
 | 
					using static flecs_hub.flecs; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace gaemstone.ECS.Internal; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public static class FlecsBuiltIn | 
				
			||||||
 | 
					{ | 
				
			||||||
 | 
						public static Entity ChildOf { get; } = new(pinvoke_EcsChildOf()); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// FIXME: Hopefully flecs-cs will expose this one day. | 
				
			||||||
 | 
						public static Entity Disabled { get; } = new(new() { Data = ECS_HI_COMPONENT_ID + 7 }); | 
				
			||||||
 | 
					} | 
				
			||||||
@ -0,0 +1,110 @@ | 
				
			|||||||
 | 
					using System; | 
				
			||||||
 | 
					using System.Collections.Generic; | 
				
			||||||
 | 
					using System.Runtime.CompilerServices; | 
				
			||||||
 | 
					using gaemstone.ECS.Utility; | 
				
			||||||
 | 
					using static flecs_hub.flecs; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace gaemstone.ECS.Internal; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public unsafe class Iterator | 
				
			||||||
 | 
						: IDisposable | 
				
			||||||
 | 
					{ | 
				
			||||||
 | 
						public ecs_iter_t* Handle; | 
				
			||||||
 | 
						private readonly bool _handleIsOwned; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public World World => new(Handle->world); | 
				
			||||||
 | 
						public int Count => Handle->count; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public IteratorFlags Flags => (IteratorFlags)(uint)Handle->flags; | 
				
			||||||
 | 
						public bool IsValid => (Flags & IteratorFlags.IsValid) != 0; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public TimeSpan DeltaTime       => TimeSpan.FromSeconds(Handle->delta_time); | 
				
			||||||
 | 
						public TimeSpan DeltaSystemTime => TimeSpan.FromSeconds(Handle->delta_system_time); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public Iterator(ecs_iter_t* handle) | 
				
			||||||
 | 
						{ | 
				
			||||||
 | 
							Handle = handle; | 
				
			||||||
 | 
							_handleIsOwned = false; | 
				
			||||||
 | 
						} | 
				
			||||||
 | 
						public Iterator(ecs_iter_t value) | 
				
			||||||
 | 
						{ | 
				
			||||||
 | 
							Handle = GlobalHeapAllocator.Instance.AllocateCopy(value); | 
				
			||||||
 | 
							_handleIsOwned = true; | 
				
			||||||
 | 
						} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public void Dispose() | 
				
			||||||
 | 
						{ | 
				
			||||||
 | 
							if (Handle == null) return; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// When an iterator is iterated until completion, | 
				
			||||||
 | 
							// ecs_iter_fini will be called automatically. | 
				
			||||||
 | 
							if (IsValid) ecs_iter_fini(Handle); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (_handleIsOwned) | 
				
			||||||
 | 
								GlobalHeapAllocator.Instance.Free(Handle); | 
				
			||||||
 | 
							Handle = null; | 
				
			||||||
 | 
						} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public Entity GetVar(Variable var) | 
				
			||||||
 | 
							=> new(ecs_iter_get_var(Handle, var.Index)); | 
				
			||||||
 | 
						public Iterator SetVar(Variable var, Entity entity) | 
				
			||||||
 | 
							{ ecs_iter_set_var(Handle, var.Index, entity); return this; } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public virtual bool Next() | 
				
			||||||
 | 
							=> ecs_iter_next(Handle); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public Entity First() | 
				
			||||||
 | 
							=> new(ecs_iter_first(Handle)); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public IEnumerable<Entity> GetAllEntities() | 
				
			||||||
 | 
						{ | 
				
			||||||
 | 
							while (Next()) | 
				
			||||||
 | 
								for (var i = 0; i < Count; i++) | 
				
			||||||
 | 
									yield return Entity(i); | 
				
			||||||
 | 
						} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public bool FieldIsSet(int index) | 
				
			||||||
 | 
							=> ecs_field_is_set(Handle, index); | 
				
			||||||
 | 
						public Id FieldId(int index) | 
				
			||||||
 | 
							=> new(ecs_field_id(Handle, index)); | 
				
			||||||
 | 
						public bool FieldIs(int index, Id id) | 
				
			||||||
 | 
							=> ecs_field_id(Handle, index) == id.Value; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public Entity Entity(int index) | 
				
			||||||
 | 
							=> new(Handle->entities[index]); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public Span<T> Field<T>(int index) | 
				
			||||||
 | 
							where T : unmanaged | 
				
			||||||
 | 
						{ | 
				
			||||||
 | 
							var size    = (ulong)Unsafe.SizeOf<T>(); | 
				
			||||||
 | 
							var isSelf  = ecs_field_is_self(Handle, index); | 
				
			||||||
 | 
							var pointer = ecs_field_w_size(Handle, size, index); | 
				
			||||||
 | 
							return new(pointer, isSelf ? Count : 1); | 
				
			||||||
 | 
						} | 
				
			||||||
 | 
						public Span<T> FieldOrEmpty<T>(int index) where T : unmanaged | 
				
			||||||
 | 
							=> FieldIsSet(index) ? Field<T>(index) : Span<T>.Empty; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public SpanToRef<T> Field<T>(int index, T _ = null!) where T : class
 | 
				
			||||||
 | 
							=> new(Field<ReferenceHandle>(index)); | 
				
			||||||
 | 
						public SpanToRef<T> FieldOrEmpty<T>(int index, T _ = null!) where T : class
 | 
				
			||||||
 | 
							=> FieldIsSet(index) ? Field<T>(index) : SpanToRef<T>.Empty; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public override string ToString() | 
				
			||||||
 | 
							=> ecs_iter_str(Handle).FlecsToStringAndFree()!; | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public readonly ref struct SpanToRef<T> | 
				
			||||||
 | 
						where T : class
 | 
				
			||||||
 | 
					{ | 
				
			||||||
 | 
						public static SpanToRef<T> Empty => default; | 
				
			||||||
 | 
						private readonly Span<ReferenceHandle> _span; | 
				
			||||||
 | 
						public int Length => _span.Length; | 
				
			||||||
 | 
						public T this[int index] => (T)_span[index].Target!; | 
				
			||||||
 | 
						public T? GetOrNull(int index) => ((index >= 0) && (index < Length)) ? this[index] : null; | 
				
			||||||
 | 
						internal SpanToRef(Span<ReferenceHandle> span) => _span = span; | 
				
			||||||
 | 
					} | 
				
			||||||
@ -0,0 +1,9 @@ | 
				
			|||||||
 | 
					namespace gaemstone.ECS.Internal; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					internal static class Lookup<TContext> | 
				
			||||||
 | 
					{ | 
				
			||||||
 | 
						internal static class Entity<T> | 
				
			||||||
 | 
						{ | 
				
			||||||
 | 
							public static Entity Value; | 
				
			||||||
 | 
						} | 
				
			||||||
 | 
					} | 
				
			||||||
@ -1,154 +1,86 @@ | 
				
			|||||||
using System; | 
					using System; | 
				
			||||||
using System.Collections; | 
					using System.Collections; | 
				
			||||||
using System.Collections.Generic; | 
					using System.Collections.Generic; | 
				
			||||||
using System.Runtime.CompilerServices; | 
					using System.Linq; | 
				
			||||||
using gaemstone.Utility; | 
					using gaemstone.ECS.Internal; | 
				
			||||||
 | 
					using gaemstone.ECS.Utility; | 
				
			||||||
using static flecs_hub.flecs; | 
					using static flecs_hub.flecs; | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace gaemstone.ECS; | 
					namespace gaemstone.ECS; | 
				
			||||||
 | 
					
 | 
				
			||||||
public unsafe class Iterator | 
					public unsafe class Iterator<TContext> : Iterator | 
				
			||||||
	: IEnumerable<Iterator> | 
						, IEnumerable<Iterator<TContext>> | 
				
			||||||
	, IDisposable | 
					 | 
				
			||||||
{ | 
					{ | 
				
			||||||
	public World World { get; } | 
						public new World<TContext> World => new(base.World); | 
				
			||||||
	public IteratorType? Type { get; } | 
					 | 
				
			||||||
	public ecs_iter_t Value; | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	public bool Completed { get; private set; } | 
						public Iterator(ecs_iter_t* handle) : base(handle) {  } | 
				
			||||||
	public int Count => Value.count; | 
						public Iterator(ecs_iter_t value)   : base(value) {  } | 
				
			||||||
	public TimeSpan DeltaTime       => TimeSpan.FromSeconds(Value.delta_time); | 
					 | 
				
			||||||
	public TimeSpan DeltaSystemTime => TimeSpan.FromSeconds(Value.delta_system_time); | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	public Iterator(World world, IteratorType? type, ecs_iter_t value) | 
						public static TermIterator<TContext> FromTerm(World world, Term term) | 
				
			||||||
		{ World = world; Type = type; Value = value; } | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	public static Iterator FromTerm(World world, Term term) | 
					 | 
				
			||||||
	{ | 
						{ | 
				
			||||||
		using var alloc = TempAllocator.Use(); | 
							using var alloc = TempAllocator.Use(); | 
				
			||||||
		var flecsTerm = term.ToFlecs(alloc); | 
							var flecsTerm = term.ToFlecs(alloc); | 
				
			||||||
		var flecsIter = ecs_term_iter(world, &flecsTerm); | 
							var flecsIter = ecs_term_iter(world, &flecsTerm); | 
				
			||||||
		return new(world, IteratorType.Term, flecsIter); | 
							return new(flecsIter); | 
				
			||||||
	} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	public void Dispose() | 
					 | 
				
			||||||
	{ | 
					 | 
				
			||||||
		// When an iterator is iterated until completion, | 
					 | 
				
			||||||
		// ecs_iter_fini will be called automatically. | 
					 | 
				
			||||||
		if (!Completed) | 
					 | 
				
			||||||
			fixed (ecs_iter_t* ptr = &Value) | 
					 | 
				
			||||||
				ecs_iter_fini(ptr); | 
					 | 
				
			||||||
	} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	public EntityRef GetVar(Variable var) | 
					 | 
				
			||||||
	{ | 
					 | 
				
			||||||
		fixed (ecs_iter_t* ptr = &Value) | 
					 | 
				
			||||||
			return new(World, new(ecs_iter_get_var(ptr, var.Index))); | 
					 | 
				
			||||||
	} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	public Iterator SetVar(Variable var, Entity entity) | 
					 | 
				
			||||||
	{ | 
					 | 
				
			||||||
		fixed (ecs_iter_t* ptr = &Value) | 
					 | 
				
			||||||
			ecs_iter_set_var(ptr, var.Index, entity); | 
					 | 
				
			||||||
		return this; | 
					 | 
				
			||||||
	} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	public bool Next() | 
					 | 
				
			||||||
	{ | 
					 | 
				
			||||||
		fixed (ecs_iter_t* ptr = &Value) { | 
					 | 
				
			||||||
			var result = Type switch { | 
					 | 
				
			||||||
				IteratorType.Term   => ecs_term_next(ptr), | 
					 | 
				
			||||||
				IteratorType.Filter => ecs_filter_next(ptr), | 
					 | 
				
			||||||
				IteratorType.Query  => ecs_query_next(ptr), | 
					 | 
				
			||||||
				IteratorType.Rule   => ecs_rule_next(ptr), | 
					 | 
				
			||||||
				_ => ecs_iter_next(ptr), | 
					 | 
				
			||||||
			}; | 
					 | 
				
			||||||
			Completed = !result; | 
					 | 
				
			||||||
			return result; | 
					 | 
				
			||||||
		} | 
					 | 
				
			||||||
	} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	public bool FieldIsSet(int index) | 
					 | 
				
			||||||
	{ | 
					 | 
				
			||||||
		fixed (ecs_iter_t* ptr = &Value) | 
					 | 
				
			||||||
			return ecs_field_is_set(ptr, index); | 
					 | 
				
			||||||
	} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	public bool FieldIs(int index, Id id) | 
					 | 
				
			||||||
	{ | 
					 | 
				
			||||||
		fixed (ecs_iter_t* ptr = &Value) | 
					 | 
				
			||||||
			return ecs_field_id(ptr, index) == id.Value; | 
					 | 
				
			||||||
	} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	public IdRef FieldId(int index) | 
					 | 
				
			||||||
	{ | 
					 | 
				
			||||||
		fixed (ecs_iter_t* ptr = &Value) | 
					 | 
				
			||||||
			return new(World, new(ecs_field_id(ptr, index))); | 
					 | 
				
			||||||
	} | 
						} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public new Entity<TContext>? GetVar(Variable var) | 
				
			||||||
 | 
							=> Entity<TContext>.GetOrNull(World, base.GetVar(var)); | 
				
			||||||
 | 
						public new Iterator<TContext> SetVar(Variable var, Entity entity) | 
				
			||||||
 | 
							=> (Iterator<TContext>)base.SetVar(var, entity); | 
				
			||||||
 | 
					
 | 
				
			||||||
	public EntityRef Entity(int index) | 
						public new IEnumerable<Entity<TContext>> GetAllEntities() | 
				
			||||||
		=> new(World, new(Value.entities[index])); | 
							=> base.GetAllEntities().Select(e => Entity<TContext>.GetOrInvalid(World, e)); | 
				
			||||||
 | 
					
 | 
				
			||||||
	public Span<T> Field<T>(int index) | 
						public new Id<TContext> FieldId(int index) | 
				
			||||||
		where T : unmanaged | 
							=> Id<TContext>.GetUnsafe(World, base.FieldId(index)); | 
				
			||||||
	{ | 
					 | 
				
			||||||
		fixed (ecs_iter_t* ptr = &Value) { | 
					 | 
				
			||||||
			var size    = (ulong)Unsafe.SizeOf<T>(); | 
					 | 
				
			||||||
			var isSelf  = ecs_field_is_self(ptr, index); | 
					 | 
				
			||||||
			var pointer = ecs_field_w_size(ptr, size, index); | 
					 | 
				
			||||||
			return new(pointer, isSelf ? Count : 1); | 
					 | 
				
			||||||
		} | 
					 | 
				
			||||||
	} | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	public SpanToRef<T> Field<T>(int index, T _ = null!) where T : class
 | 
						public new Entity<TContext> Entity(int index) | 
				
			||||||
		=> new(Field<ReferenceHandle>(index)); | 
							=> Entity<TContext>.GetOrInvalid(World, base.Entity(index)); | 
				
			||||||
 | 
					 | 
				
			||||||
	public Span<T> FieldOrEmpty<T>(int index) where T : unmanaged | 
					 | 
				
			||||||
		=> FieldIsSet(index) ? Field<T>(index) : Span<T>.Empty; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	public SpanToRef<T> FieldOrEmpty<T>(int index, T _ = null!) where T : class
 | 
					 | 
				
			||||||
		=> FieldIsSet(index) ? Field(index, _) : SpanToRef<T>.Empty; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	public override string ToString() | 
					 | 
				
			||||||
	{ | 
					 | 
				
			||||||
		fixed (ecs_iter_t* ptr = &Value) | 
					 | 
				
			||||||
			return ecs_iter_str(ptr).FlecsToStringAndFree()!; | 
					 | 
				
			||||||
	} | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// IEnumerable implementation | 
						// IEnumerable implementation | 
				
			||||||
	public IEnumerator<Iterator> GetEnumerator() { while (Next()) yield return this; } | 
						public virtual IEnumerator<Iterator<TContext>> GetEnumerator() | 
				
			||||||
	IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); | 
							{ while (Next()) yield return this; } | 
				
			||||||
 | 
						IEnumerator IEnumerable.GetEnumerator() | 
				
			||||||
 | 
							=> GetEnumerator(); | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
	public class Variable | 
					[Flags] | 
				
			||||||
	{ | 
					public enum IteratorFlags : uint | 
				
			||||||
		public int Index { get; } | 
					{ | 
				
			||||||
		public string Name { get; } | 
						/// <summary> Does iterator contain valid result. </summary> | 
				
			||||||
		public Variable(int index, string name) | 
						IsValid        = EcsIterIsValid, | 
				
			||||||
			{ Index = index; Name = name; } | 
						/// <summary> Is iterator filter (metadata only). </summary> | 
				
			||||||
	} | 
						IsFilter       = EcsIterIsFilter, | 
				
			||||||
 | 
						/// <summary> Is iterator instanced. </summary> | 
				
			||||||
 | 
						IsInstanced    = EcsIterIsInstanced, | 
				
			||||||
 | 
						/// <summary> Does result have shared terms. </summary> | 
				
			||||||
 | 
						HasShared      = EcsIterHasShared, | 
				
			||||||
 | 
						/// <summary> Result only populates table. </summary> | 
				
			||||||
 | 
						TableOnly      = EcsIterTableOnly, | 
				
			||||||
 | 
						/// <summary> Treat terms with entity subject as optional. </summary> | 
				
			||||||
 | 
						EntityOptional = EcsIterEntityOptional, | 
				
			||||||
 | 
						/// <summary> Iterator has no results. </summary> | 
				
			||||||
 | 
						NoResults      = EcsIterNoResults, | 
				
			||||||
 | 
						/// <summary> Only evaluate non-this terms. </summary> | 
				
			||||||
 | 
						IgnoreThis     = EcsIterIgnoreThis, | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						MatchVar       = EcsIterMatchVar, | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
	public readonly ref struct SpanToRef<T> | 
					public class Variable | 
				
			||||||
		where T : class
 | 
					{ | 
				
			||||||
	{ | 
						public int Index { get; } | 
				
			||||||
		public static SpanToRef<T> Empty => default; | 
						public string Name { get; } | 
				
			||||||
		private readonly Span<ReferenceHandle> _span; | 
						public Variable(int index, string name) | 
				
			||||||
		public int Length => _span.Length; | 
							{ Index = index; Name = name; } | 
				
			||||||
		public T this[int index] => (T)_span[index].Target!; | 
					 | 
				
			||||||
		public T? GetOrNull(int index) => ((index >= 0) && (index < Length)) ? this[index] : null; | 
					 | 
				
			||||||
		internal SpanToRef(Span<ReferenceHandle> span) => _span = span; | 
					 | 
				
			||||||
	} | 
					 | 
				
			||||||
} | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
public enum IteratorType | 
					public unsafe class TermIterator<TContext> | 
				
			||||||
 | 
						: Iterator<TContext> | 
				
			||||||
{ | 
					{ | 
				
			||||||
	Term, | 
						internal TermIterator(ecs_iter_t value) | 
				
			||||||
	Filter, | 
							: base(value) {  } | 
				
			||||||
	Query, | 
						public override bool Next() | 
				
			||||||
	Rule, | 
							=> ecs_term_next(Handle); | 
				
			||||||
} | 
					} | 
				
			||||||
 | 
				
			|||||||
@ -1,6 +1,6 @@ | 
				
			|||||||
using System.Collections.Generic; | 
					using System.Collections.Generic; | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace gaemstone.Utility; | 
					namespace gaemstone.ECS.Utility; | 
				
			||||||
 | 
					
 | 
				
			||||||
public static class CallbackContextHelper | 
					public static class CallbackContextHelper | 
				
			||||||
{ | 
					{ | 
				
			||||||
@ -0,0 +1,71 @@ | 
				
			|||||||
 | 
					using System; | 
				
			||||||
 | 
					using System.Runtime.InteropServices; | 
				
			||||||
 | 
					using static flecs_hub.flecs; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace gaemstone.ECS.Utility; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public unsafe readonly struct ReferenceHandle | 
				
			||||||
 | 
						: IDisposable | 
				
			||||||
 | 
					{ | 
				
			||||||
 | 
						public static int NumActiveHandles { get; private set; } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private readonly nint _value; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public object? Target => (_value != default) | 
				
			||||||
 | 
							? ((GCHandle)_value).Target : null; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private ReferenceHandle(nint value) | 
				
			||||||
 | 
							=> _value = value; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public static ReferenceHandle Alloc(object? target) | 
				
			||||||
 | 
						{ | 
				
			||||||
 | 
							if (target == null) return default; | 
				
			||||||
 | 
							NumActiveHandles++; | 
				
			||||||
 | 
							return new((nint)GCHandle.Alloc(target)); | 
				
			||||||
 | 
						} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public ReferenceHandle Clone() | 
				
			||||||
 | 
							=> Alloc(Target); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public void Dispose() | 
				
			||||||
 | 
						{ | 
				
			||||||
 | 
							if (_value == default) return; | 
				
			||||||
 | 
							NumActiveHandles--; | 
				
			||||||
 | 
							((GCHandle)_value).Free(); | 
				
			||||||
 | 
						} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						[UnmanagedCallersOnly] | 
				
			||||||
 | 
						internal static void Construct(void* ptr, int count, ecs_type_info_t* _) | 
				
			||||||
 | 
							=> new Span<ReferenceHandle>(ptr, count).Clear(); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						[UnmanagedCallersOnly] | 
				
			||||||
 | 
						internal static void Destruct(void* ptr, int count, ecs_type_info_t* _) | 
				
			||||||
 | 
						{ | 
				
			||||||
 | 
							var span = new Span<ReferenceHandle>(ptr, count); | 
				
			||||||
 | 
							foreach (var handle in span) handle.Dispose(); | 
				
			||||||
 | 
							span.Clear(); | 
				
			||||||
 | 
						} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						[UnmanagedCallersOnly] | 
				
			||||||
 | 
						internal static void Move(void* dstPtr, void* srcPtr, int count, ecs_type_info_t* _) | 
				
			||||||
 | 
						{ | 
				
			||||||
 | 
							var dst = new Span<ReferenceHandle>(dstPtr, count); | 
				
			||||||
 | 
							var src = new Span<ReferenceHandle>(srcPtr, count); | 
				
			||||||
 | 
							foreach (var handle in dst) handle.Dispose(); | 
				
			||||||
 | 
							src.CopyTo(dst); | 
				
			||||||
 | 
							src.Clear(); | 
				
			||||||
 | 
						} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						[UnmanagedCallersOnly] | 
				
			||||||
 | 
						internal static void Copy(void* dstPtr, void* srcPtr, int count, ecs_type_info_t* _) | 
				
			||||||
 | 
						{ | 
				
			||||||
 | 
							var dst = new Span<ReferenceHandle>(dstPtr, count); | 
				
			||||||
 | 
							var src = new Span<ReferenceHandle>(srcPtr, count); | 
				
			||||||
 | 
							for (var i = 0; i < count; i++) { | 
				
			||||||
 | 
								dst[i].Dispose(); | 
				
			||||||
 | 
								dst[i] = src[i].Clone(); | 
				
			||||||
 | 
							} | 
				
			||||||
 | 
						} | 
				
			||||||
 | 
					} | 
				
			||||||
@ -1,6 +1,6 @@ | 
				
			|||||||
using System; | 
					using System; | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace gaemstone.Utility; | 
					namespace gaemstone.ECS.Utility; | 
				
			||||||
 | 
					
 | 
				
			||||||
public static class SpanExtensions | 
					public static class SpanExtensions | 
				
			||||||
{ | 
					{ | 
				
			||||||
@ -0,0 +1,73 @@ | 
				
			|||||||
 | 
					using System; | 
				
			||||||
 | 
					using System.Runtime.InteropServices; | 
				
			||||||
 | 
					using gaemstone.ECS.Utility; | 
				
			||||||
 | 
					using static flecs_hub.flecs; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace gaemstone.ECS; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public unsafe struct World | 
				
			||||||
 | 
						: IEquatable<World> | 
				
			||||||
 | 
						, IDisposable | 
				
			||||||
 | 
					{ | 
				
			||||||
 | 
						public ecs_world_t* Handle { get; } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public bool IsDeferred      => ecs_is_deferred(this); | 
				
			||||||
 | 
						public bool IsQuitRequested => ecs_should_quit(this); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public World(ecs_world_t* handle) | 
				
			||||||
 | 
							=> Handle = handle; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public World(params string[] args) | 
				
			||||||
 | 
						{ | 
				
			||||||
 | 
							[UnmanagedCallersOnly] | 
				
			||||||
 | 
							static void Abort() => throw new FlecsAbortException(); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							ecs_os_set_api_defaults(); | 
				
			||||||
 | 
							var api = ecs_os_get_api(); | 
				
			||||||
 | 
							api.abort_ = new FnPtr_Void { Pointer = &Abort }; | 
				
			||||||
 | 
							ecs_os_set_api(&api); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							Handle = ecs_init_w_args(args.Length, null); | 
				
			||||||
 | 
						} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public bool Progress(TimeSpan delta) | 
				
			||||||
 | 
							=> ecs_progress(this, (float)delta.TotalSeconds); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public void Quit() | 
				
			||||||
 | 
							=> ecs_quit(this); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public void Dispose() | 
				
			||||||
 | 
							=> ecs_fini(this); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public Entity LookupAlive(Entity value) | 
				
			||||||
 | 
							=> new(ecs_get_alive(this, value)); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public Entity LookupPath(EntityPath path, Entity parent = default, | 
				
			||||||
 | 
						                         bool throwOnNotFound = false) | 
				
			||||||
 | 
							=> new(EntityPath.Lookup(this, path, parent, throwOnNotFound)); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// TODO: Provide overload that uses a UTF-8 byte span? | 
				
			||||||
 | 
						public Entity LookupSymbol(string symbol) | 
				
			||||||
 | 
						{ | 
				
			||||||
 | 
							using var alloc = TempAllocator.Use(); | 
				
			||||||
 | 
							return new(ecs_lookup_symbol(this, alloc.AllocateCString(symbol), false)); | 
				
			||||||
 | 
						} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public bool Equals(World other) | 
				
			||||||
 | 
						{ | 
				
			||||||
 | 
							if (Handle == other.Handle) return true; | 
				
			||||||
 | 
							return ecs_get_world((ecs_poly_t*)Handle) | 
				
			||||||
 | 
								== ecs_get_world((ecs_poly_t*)other.Handle); | 
				
			||||||
 | 
						} | 
				
			||||||
 | 
						public override bool Equals(object? obj) | 
				
			||||||
 | 
							=> (obj is World other) && Equals(other); | 
				
			||||||
 | 
						public override int GetHashCode() | 
				
			||||||
 | 
							=> ((nint)ecs_get_world((ecs_poly_t*)Handle)).GetHashCode(); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public static bool operator ==(World left, World right) =>  left.Equals(right); | 
				
			||||||
 | 
						public static bool operator !=(World left, World right) => !left.Equals(right); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public static implicit operator ecs_world_t*(World w) => w.Handle; | 
				
			||||||
 | 
					} | 
				
			||||||
@ -1,39 +0,0 @@ | 
				
			|||||||
using System; | 
					 | 
				
			||||||
using gaemstone.Utility; | 
					 | 
				
			||||||
using static flecs_hub.flecs; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
namespace gaemstone.ECS; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
public unsafe partial class World | 
					 | 
				
			||||||
{ | 
					 | 
				
			||||||
	public EntityRef? LookupAlive(Entity value) | 
					 | 
				
			||||||
		=> EntityRef.CreateOrNull(this, new(ecs_get_alive(this, value))); | 
					 | 
				
			||||||
	public EntityRef LookupAliveOrThrow(Entity entity) | 
					 | 
				
			||||||
		=> LookupAlive(entity) ?? throw new EntityNotFoundException( | 
					 | 
				
			||||||
			$"Entity {entity} is not alive"); | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// TODO: Simplify method names? | 
					 | 
				
			||||||
	public EntityRef? LookupByPath(EntityPath path) | 
					 | 
				
			||||||
		=> LookupByPath(default, path); | 
					 | 
				
			||||||
	public EntityRef? LookupByPath(Entity parent, EntityPath path) | 
					 | 
				
			||||||
		=> EntityRef.CreateOrNull(this, EntityPath.Lookup(this, parent, path, false)); | 
					 | 
				
			||||||
	public EntityRef LookupByPathOrThrow(EntityPath path) | 
					 | 
				
			||||||
		=> LookupByPathOrThrow(default, path); | 
					 | 
				
			||||||
	public EntityRef LookupByPathOrThrow(Entity parent, EntityPath path) | 
					 | 
				
			||||||
		=> new(this, EntityPath.Lookup(this, parent, path, true)); | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// TODO: Provide overload that uses a UTF-8 byte span? | 
					 | 
				
			||||||
	public EntityRef? LookupBySymbol(string symbol) | 
					 | 
				
			||||||
	{ | 
					 | 
				
			||||||
		using var alloc = TempAllocator.Use(); | 
					 | 
				
			||||||
		var entity = ecs_lookup_symbol(this, alloc.AllocateCString(symbol), false); | 
					 | 
				
			||||||
		return EntityRef.CreateOrNull(this, new(entity)); | 
					 | 
				
			||||||
	} | 
					 | 
				
			||||||
	public EntityRef LookupBySymbolOrThrow(string symbol) | 
					 | 
				
			||||||
		=> LookupBySymbol(symbol) ?? throw new EntityNotFoundException( | 
					 | 
				
			||||||
			$"Entity with symbol '{symbol}' not found"); | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	public class EntityNotFoundException : Exception | 
					 | 
				
			||||||
		{ public EntityNotFoundException(string message) : base(message) {  } } | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
@ -1,58 +1,69 @@ | 
				
			|||||||
using System; | 
					using System; | 
				
			||||||
using gaemstone.Utility; | 
					using gaemstone.ECS.Internal; | 
				
			||||||
using static flecs_hub.flecs; | 
					using static flecs_hub.flecs; | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace gaemstone.ECS; | 
					namespace gaemstone.ECS; | 
				
			||||||
 | 
					
 | 
				
			||||||
public unsafe partial class World | 
					public unsafe struct World<TContext> | 
				
			||||||
 | 
						: IEquatable<World<TContext>> | 
				
			||||||
 | 
						, IDisposable | 
				
			||||||
{ | 
					{ | 
				
			||||||
	public ecs_world_t* Handle { get; } | 
						public readonly World Value; | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Flecs built-ins that are important to internals. | 
						public bool IsDeferred      => Value.IsDeferred; | 
				
			||||||
	internal EntityRef ChildOf { get; } | 
						public bool IsQuitRequested => Value.IsQuitRequested; | 
				
			||||||
	internal EntityRef Disabled { get; } | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	public bool IsDeferred => ecs_is_deferred(this); | 
					 | 
				
			||||||
	public bool IsQuitRequested => ecs_should_quit(this); | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public World(World value) | 
				
			||||||
 | 
							=> Value = value; | 
				
			||||||
	public World(params string[] args) | 
						public World(params string[] args) | 
				
			||||||
	{ | 
							: this(new World(args)) {  } | 
				
			||||||
		ecs_os_set_api_defaults(); | 
					
 | 
				
			||||||
		var api = ecs_os_get_api(); | 
						public bool Progress(TimeSpan delta) => Value.Progress(delta); | 
				
			||||||
		api.abort_ = new FnPtr_Void { Pointer = &FlecsAbortException.Callback }; | 
						public void Quit()    => Value.Quit(); | 
				
			||||||
		ecs_os_set_api(&api); | 
						public void Dispose() => Value.Dispose(); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public EntityBuilder<TContext> New(EntityPath? path = null, Entity parent = default) | 
				
			||||||
 | 
							=> new(this, parent, path); | 
				
			||||||
 | 
					
 | 
				
			||||||
		Handle = ecs_init_w_args(args.Length, null); | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
		ChildOf  = LookupByPathOrThrow("/flecs/core/ChildOf"); | 
						public Entity<TContext>? LookupAliveOrNull(Entity value) | 
				
			||||||
		Disabled = LookupByPathOrThrow("/flecs/core/Disabled"); | 
							=> ECS.Entity<TContext>.GetOrNull(this, Value.LookupAlive(value)); | 
				
			||||||
	} | 
						public Entity<TContext> LookupAliveOrThrow(Entity value) | 
				
			||||||
 | 
							=> LookupAliveOrNull(value) ?? throw new EntityNotFoundException( | 
				
			||||||
 | 
								$"Entity '{value}' could not be found"); | 
				
			||||||
 | 
					
 | 
				
			||||||
	public void Dispose() | 
						public Entity<TContext>? LookupPathOrNull(EntityPath path, Entity parent = default) | 
				
			||||||
		=> ecs_fini(this); | 
							=> ECS.Entity<TContext>.GetOrNull(this, Value.LookupPath(path, parent, false)); | 
				
			||||||
 | 
						public Entity<TContext> LookupPathOrThrow(EntityPath path, Entity parent = default) | 
				
			||||||
 | 
							=> ECS.Entity<TContext>.GetOrInvalid(this, Value.LookupPath(path, parent, true)); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public Entity<TContext>? LookupSymbolOrNull(string symbol) | 
				
			||||||
 | 
							=> ECS.Entity<TContext>.GetOrNull(this, Value.LookupSymbol(symbol)); | 
				
			||||||
 | 
						public Entity<TContext> LookupSymbolOrThrow(string symbol) | 
				
			||||||
 | 
							=> LookupSymbolOrNull(symbol) ?? throw new EntityNotFoundException( | 
				
			||||||
 | 
								$"Entity with symbol '{symbol}' could not be found"); | 
				
			||||||
 | 
					
 | 
				
			||||||
	public EntityBuilder New(EntityPath? path = null) | 
					 | 
				
			||||||
		=> new(this, path); | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	public EntityBuilder New(EntityRef? parent, EntityPath? path = null) | 
						public Entity<TContext> Entity<T>() | 
				
			||||||
	{ | 
							=> ECS.Entity<TContext>.GetOrThrow(this, | 
				
			||||||
		var entity = New(path); | 
								Lookup<TContext>.Entity<T>.Value); | 
				
			||||||
		// If given path is absolute, the new entity won't be created as a | 
					 | 
				
			||||||
		// child of the specified parent. Alternatively, EntityRef.NewChild | 
					 | 
				
			||||||
		// can be used, which will throw when an absolute path is given. | 
					 | 
				
			||||||
		if ((path?.IsRelative != false) && (parent != null)) | 
					 | 
				
			||||||
			entity.ChildOf(parent); | 
					 | 
				
			||||||
		return entity; | 
					 | 
				
			||||||
	} | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public Id<TContext> Pair(Entity relation, Entity target) | 
				
			||||||
 | 
							=> Id<TContext>.GetOrThrow(this, Id.Pair(relation, target)); | 
				
			||||||
 | 
						public Id<TContext> Pair<TRelation>(Entity target) | 
				
			||||||
 | 
							=> Pair(Entity<TRelation>(), target); | 
				
			||||||
 | 
						public Id<TContext> Pair<TRelation, TTarget>() | 
				
			||||||
 | 
							=> Pair(Entity<TRelation>(), Entity<TTarget>()); | 
				
			||||||
 | 
					
 | 
				
			||||||
	public bool Progress(TimeSpan delta) | 
					 | 
				
			||||||
		=> ecs_progress(this, (float)delta.TotalSeconds); | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	public void Quit() | 
						public bool Equals(World<TContext> other) => Value == other.Value; | 
				
			||||||
		=> ecs_quit(this); | 
						public override bool Equals(object? obj) => (obj is World<TContext> other) && Equals(other); | 
				
			||||||
 | 
						public override int GetHashCode() => Value.GetHashCode(); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public static bool operator ==(World<TContext> left, World<TContext> right) =>  left.Equals(right); | 
				
			||||||
 | 
						public static bool operator !=(World<TContext> left, World<TContext> right) => !left.Equals(right); | 
				
			||||||
 | 
					
 | 
				
			||||||
	public static implicit operator ecs_world_t*(World w) => w.Handle; | 
						public static implicit operator World       (World<TContext> world) => world.Value; | 
				
			||||||
 | 
						public static implicit operator ecs_world_t*(World<TContext> world) => world.Value.Handle; | 
				
			||||||
} | 
					} | 
				
			||||||
 | 
				
			|||||||
@ -1,26 +0,0 @@ | 
				
			|||||||
using System; | 
					 | 
				
			||||||
using System.Diagnostics; | 
					 | 
				
			||||||
using System.Runtime.InteropServices; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
namespace gaemstone.Utility; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
public class FlecsException | 
					 | 
				
			||||||
	: Exception | 
					 | 
				
			||||||
{ | 
					 | 
				
			||||||
	public FlecsException() : base() {  } | 
					 | 
				
			||||||
	public FlecsException(string message) : base(message) {  } | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
public class FlecsAbortException | 
					 | 
				
			||||||
	: FlecsException | 
					 | 
				
			||||||
{ | 
					 | 
				
			||||||
	private readonly string _stackTrace = new StackTrace(2, true).ToString(); | 
					 | 
				
			||||||
	public override string? StackTrace => _stackTrace; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	private FlecsAbortException() | 
					 | 
				
			||||||
		: base("Abort was called by flecs") {  } | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	[UnmanagedCallersOnly] | 
					 | 
				
			||||||
	internal static void Callback() | 
					 | 
				
			||||||
		=> throw new FlecsAbortException(); | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
					Loading…
					
					
				
		Reference in new issue