parent
1d1ba4fe4d
commit
948268e9ba
31 changed files with 990 additions and 729 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.Collections.Generic; |
||||
using System.Linq; |
||||
using gaemstone.ECS.Internal; |
||||
using static flecs_hub.flecs; |
||||
|
||||
namespace gaemstone.ECS; |
||||
|
||||
public readonly struct Entity |
||||
: IEquatable<Entity> |
||||
public unsafe readonly partial struct Entity<TContext> |
||||
: IEquatable<Entity<TContext>> |
||||
{ |
||||
public static readonly Entity None = default; |
||||
public static readonly Entity<TContext> None = default; |
||||
|
||||
public readonly ecs_entity_t Value; |
||||
public uint Id => (uint)Value.Data; |
||||
public readonly World<TContext> World; |
||||
public readonly Entity Value; |
||||
|
||||
public bool IsSome => Value.Data != 0; |
||||
public bool IsNone => Value.Data == 0; |
||||
public uint NumericId => Value.NumericId; |
||||
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 override bool Equals(object? obj) => (obj is Entity other) && Equals(other); |
||||
public override int GetHashCode() => Value.Data.GetHashCode(); |
||||
public string? Name { get => EntityAccess.GetName(World, this); set => EntityAccess.SetName(World, this, value); } |
||||
public string? Symbol { get => EntityAccess.GetSymbol(World, this); set => EntityAccess.SetSymbol(World, this, value); } |
||||
|
||||
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() |
||||
=> IsSome ? $"Entity({Value.Data.Data})" |
||||
: "Entity.None"; |
||||
=> Value.ToString(); |
||||
|
||||
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 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 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 Id(Entity e) => new(e.Value.Data); |
||||
public static implicit operator ecs_id_t(Entity e) => e.Value.Data; |
||||
public static implicit operator Term (Entity<TContext> entity) => new(entity.Value); |
||||
public static implicit operator TermId(Entity<TContext> entity) => new(entity.Value); |
||||
} |
||||
|
@ -1,193 +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 |
||||
: IEquatable<EntityRef> |
||||
{ |
||||
public 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 EntityRef ChildOf(Entity parent) |
||||
=> Add(World.ChildOf, parent); |
||||
|
||||
|
||||
public bool IsDisabled => Has(World.Disabled); |
||||
public EntityRef Disable() => Add(World.Disabled); |
||||
public EntityRef Enable() => Remove(World.Disabled); |
||||
|
||||
|
||||
public EntityRef Add(Id id) { ecs_add_id(World, this, id); return this; } |
||||
public EntityRef Add(string symbol) => Add(World.LookupBySymbolOrThrow(symbol)); |
||||
public EntityRef Add(Entity relation, Entity target) => Add(ECS.Id.Pair(relation, target)); |
||||
|
||||
public EntityRef Remove(Id id) { ecs_remove_id(World, this, id); return this; } |
||||
public EntityRef Remove(string symbol) => Remove(World.LookupBySymbolOrThrow(symbol)); |
||||
public EntityRef Remove(Entity relation, Entity target) => Remove(ECS.Id.Pair(relation, target)); |
||||
|
||||
public bool Has(Id id) => ecs_has_id(World, this, id); |
||||
public bool Has(string symbol) => Has(World.LookupBySymbolOrThrow(symbol)); |
||||
public bool Has(Entity relation, Entity target) => Has(ECS.Id.Pair(relation, target)); |
||||
|
||||
|
||||
public T? GetOrNull<T>(Id id) |
||||
where T : unmanaged |
||||
{ |
||||
var ptr = ecs_get_id(World, this, id); |
||||
return (ptr != null) ? Unsafe.Read<T>(ptr) : null; |
||||
} |
||||
|
||||
public 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 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 ref T GetMut<T>(Id id) |
||||
where T : unmanaged |
||||
{ |
||||
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 ref T GetRefOrNull<T>(Id id) |
||||
where T : unmanaged |
||||
{ |
||||
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 ref T GetRefOrThrow<T>(Id id) |
||||
where T : unmanaged |
||||
{ |
||||
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 void Modified<T>(Id id) |
||||
=> ecs_modified_id(World, this, id); |
||||
|
||||
public EntityRef Set<T>(Id id, in T value) |
||||
where T : unmanaged |
||||
{ |
||||
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 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.Collections; |
||||
using System.Collections.Generic; |
||||
using System.Runtime.CompilerServices; |
||||
using gaemstone.Utility; |
||||
using System.Linq; |
||||
using gaemstone.ECS.Internal; |
||||
using gaemstone.ECS.Utility; |
||||
using static flecs_hub.flecs; |
||||
|
||||
namespace gaemstone.ECS; |
||||
|
||||
public unsafe class Iterator |
||||
: IEnumerable<Iterator> |
||||
, IDisposable |
||||
public unsafe class Iterator<TContext> : Iterator |
||||
, IEnumerable<Iterator<TContext>> |
||||
{ |
||||
public World World { get; } |
||||
public IteratorType? Type { get; } |
||||
public ecs_iter_t Value; |
||||
public new World<TContext> World => new(base.World); |
||||
|
||||
public bool Completed { get; private set; } |
||||
public int Count => Value.count; |
||||
public TimeSpan DeltaTime => TimeSpan.FromSeconds(Value.delta_time); |
||||
public TimeSpan DeltaSystemTime => TimeSpan.FromSeconds(Value.delta_system_time); |
||||
public Iterator(ecs_iter_t* handle) : base(handle) { } |
||||
public Iterator(ecs_iter_t value) : base(value) { } |
||||
|
||||
public Iterator(World world, IteratorType? type, ecs_iter_t value) |
||||
{ World = world; Type = type; Value = value; } |
||||
|
||||
public static Iterator FromTerm(World world, Term term) |
||||
public static TermIterator<TContext> FromTerm(World world, Term term) |
||||
{ |
||||
using var alloc = TempAllocator.Use(); |
||||
var flecsTerm = term.ToFlecs(alloc); |
||||
var flecsIter = ecs_term_iter(world, &flecsTerm); |
||||
return new(world, IteratorType.Term, 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))); |
||||
return new(flecsIter); |
||||
} |
||||
|
||||
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) |
||||
=> new(World, new(Value.entities[index])); |
||||
public new IEnumerable<Entity<TContext>> GetAllEntities() |
||||
=> base.GetAllEntities().Select(e => Entity<TContext>.GetOrInvalid(World, e)); |
||||
|
||||
public Span<T> Field<T>(int index) |
||||
where T : unmanaged |
||||
{ |
||||
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 new Id<TContext> FieldId(int index) |
||||
=> Id<TContext>.GetUnsafe(World, base.FieldId(index)); |
||||
|
||||
public SpanToRef<T> Field<T>(int index, T _ = null!) where T : class
|
||||
=> new(Field<ReferenceHandle>(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()!; |
||||
} |
||||
public new Entity<TContext> Entity(int index) |
||||
=> Entity<TContext>.GetOrInvalid(World, base.Entity(index)); |
||||
|
||||
// IEnumerable implementation |
||||
public IEnumerator<Iterator> GetEnumerator() { while (Next()) yield return this; } |
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); |
||||
|
||||
public virtual IEnumerator<Iterator<TContext>> GetEnumerator() |
||||
{ while (Next()) yield return this; } |
||||
IEnumerator IEnumerable.GetEnumerator() |
||||
=> GetEnumerator(); |
||||
} |
||||
|
||||
public class Variable |
||||
{ |
||||
public int Index { get; } |
||||
public string Name { get; } |
||||
public Variable(int index, string name) |
||||
{ Index = index; Name = name; } |
||||
} |
||||
[Flags] |
||||
public enum IteratorFlags : uint |
||||
{ |
||||
/// <summary> Does iterator contain valid result. </summary> |
||||
IsValid = EcsIterIsValid, |
||||
/// <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> |
||||
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; |
||||
} |
||||
public class Variable |
||||
{ |
||||
public int Index { get; } |
||||
public string Name { get; } |
||||
public Variable(int index, string name) |
||||
{ Index = index; Name = name; } |
||||
} |
||||
|
||||
public enum IteratorType |
||||
public unsafe class TermIterator<TContext> |
||||
: Iterator<TContext> |
||||
{ |
||||
Term, |
||||
Filter, |
||||
Query, |
||||
Rule, |
||||
internal TermIterator(ecs_iter_t value) |
||||
: base(value) { } |
||||
public override bool Next() |
||||
=> ecs_term_next(Handle); |
||||
} |
||||
|
@ -1,6 +1,6 @@ |
||||
using System.Collections.Generic; |
||||
|
||||
namespace gaemstone.Utility; |
||||
namespace gaemstone.ECS.Utility; |
||||
|
||||
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; |
||||
|
||||
namespace gaemstone.Utility; |
||||
namespace gaemstone.ECS.Utility; |
||||
|
||||
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,38 +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,51 +1,69 @@ |
||||
using System; |
||||
using gaemstone.Utility; |
||||
using gaemstone.ECS.Internal; |
||||
using static flecs_hub.flecs; |
||||
|
||||
namespace gaemstone.ECS; |
||||
|
||||
public unsafe partial class World |
||||
: IDisposable |
||||
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. |
||||
internal EntityRef ChildOf { get; } |
||||
internal EntityRef Disabled { get; } |
||||
|
||||
public bool IsDeferred => ecs_is_deferred(this); |
||||
public bool IsQuitRequested => ecs_should_quit(this); |
||||
public bool IsDeferred => Value.IsDeferred; |
||||
public bool IsQuitRequested => Value.IsQuitRequested; |
||||
|
||||
public World(World value) |
||||
=> Value = value; |
||||
public World(params string[] args) |
||||
{ |
||||
ecs_os_set_api_defaults(); |
||||
var api = ecs_os_get_api(); |
||||
api.abort_ = new FnPtr_Void { Pointer = &FlecsAbortException.Callback }; |
||||
ecs_os_set_api(&api); |
||||
: this(new World(args)) { } |
||||
|
||||
Handle = ecs_init_w_args(args.Length, null); |
||||
public bool Progress(TimeSpan delta) => Value.Progress(delta); |
||||
public void Quit() => Value.Quit(); |
||||
public void Dispose() => Value.Dispose(); |
||||
|
||||
ChildOf = LookupByPathOrThrow("/flecs/core/ChildOf"); |
||||
Disabled = LookupByPathOrThrow("/flecs/core/Disabled"); |
||||
} |
||||
|
||||
public void Dispose() |
||||
=> ecs_fini(this); |
||||
public EntityBuilder<TContext> New(EntityPath? path = null, Entity parent = default) |
||||
=> new(this, parent, path); |
||||
|
||||
|
||||
public EntityBuilder New(EntityPath? path = null) |
||||
=> new(this, default, path); |
||||
public Entity<TContext>? LookupAliveOrNull(Entity value) |
||||
=> 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 EntityBuilder New(EntityRef? parent, EntityPath? path = null) |
||||
=> new(this, parent, path); |
||||
public Entity<TContext>? LookupPathOrNull(EntityPath path, Entity parent = default) |
||||
=> 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 Entity<TContext> Entity<T>() |
||||
=> ECS.Entity<TContext>.GetOrThrow(this, |
||||
Lookup<TContext>.Entity<T>.Value); |
||||
|
||||
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() |
||||
=> ecs_quit(this); |
||||
public bool Equals(World<TContext> other) => Value == other.Value; |
||||
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