wip/no-type-lookup
copygirl 1 year ago
parent 1d1ba4fe4d
commit 948268e9ba
  1. 80
      src/gaemstone.ECS/Component.cs
  2. 35
      src/gaemstone.ECS/Entity+Bare.cs
  3. 166
      src/gaemstone.ECS/Entity.cs
  4. 61
      src/gaemstone.ECS/EntityBuilder.cs
  5. 73
      src/gaemstone.ECS/EntityPath.cs
  6. 193
      src/gaemstone.ECS/EntityRef.cs
  7. 18
      src/gaemstone.ECS/EntityType.cs
  8. 31
      src/gaemstone.ECS/Exceptions.cs
  9. 27
      src/gaemstone.ECS/Filter.cs
  10. 36
      src/gaemstone.ECS/Id+Bare.cs
  11. 71
      src/gaemstone.ECS/Id.cs
  12. 53
      src/gaemstone.ECS/IdRef.cs
  13. 117
      src/gaemstone.ECS/Internal/EntityAccess.cs
  14. 11
      src/gaemstone.ECS/Internal/FlecsBuiltIn.cs
  15. 110
      src/gaemstone.ECS/Internal/Iterator.cs
  16. 9
      src/gaemstone.ECS/Internal/Lookup.cs
  17. 186
      src/gaemstone.ECS/Iterator.cs
  18. 13
      src/gaemstone.ECS/Observer.cs
  19. 31
      src/gaemstone.ECS/Query.cs
  20. 51
      src/gaemstone.ECS/Rule.cs
  21. 23
      src/gaemstone.ECS/System.cs
  22. 15
      src/gaemstone.ECS/Term.cs
  23. 17
      src/gaemstone.ECS/Utility/Allocators.cs
  24. 2
      src/gaemstone.ECS/Utility/CStringExtensions.cs
  25. 2
      src/gaemstone.ECS/Utility/CallbackContextHelper.cs
  26. 71
      src/gaemstone.ECS/Utility/ReferenceHandle.cs
  27. 2
      src/gaemstone.ECS/Utility/SpanExtensions.cs
  28. 73
      src/gaemstone.ECS/World+Bare.cs
  29. 38
      src/gaemstone.ECS/World+Lookup.cs
  30. 78
      src/gaemstone.ECS/World.cs
  31. 26
      src/gaemstone.Utility/FlecsException.cs

@ -1,13 +1,13 @@
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using gaemstone.ECS.Utility;
using static flecs_hub.flecs;
namespace gaemstone.ECS;
public static unsafe class ComponentExtensions
public unsafe partial struct Entity<TContext>
{
public static EntityRef InitComponent<T>(this EntityRef entity)
public Entity<TContext> InitComponent<T>()
{
if (typeof(T).IsPrimitive) throw new ArgumentException(
"Must not be primitive");
@ -17,8 +17,8 @@ public static unsafe class ComponentExtensions
var size = typeof(T).IsValueType ? Unsafe.SizeOf<T>() : sizeof(ReferenceHandle);
var typeInfo = new ecs_type_info_t { size = size, alignment = size };
var componentDesc = new ecs_component_desc_t { entity = entity, type = typeInfo };
ecs_component_init(entity.World, &componentDesc);
var componentDesc = new ecs_component_desc_t { entity = this, type = typeInfo };
ecs_component_init(World, &componentDesc);
if (!typeof(T).IsValueType) {
// Set up component hooks for proper freeing of GCHandles.
@ -29,75 +29,9 @@ public static unsafe class ComponentExtensions
move = new() { Data = new() { Pointer = &ReferenceHandle.Move } },
copy = new() { Data = new() { Pointer = &ReferenceHandle.Copy } },
};
ecs_set_hooks_id(entity.World, entity, &typeHooks);
ecs_set_hooks_id(World, this, &typeHooks);
}
return entity;
}
}
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();
}
return CreateLookup<T>();
}
}

@ -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,16 +1,18 @@
using System;
using System.Collections.Generic;
using gaemstone.Utility;
using gaemstone.ECS.Internal;
using gaemstone.ECS.Utility;
using static flecs_hub.flecs;
using static gaemstone.ECS.Internal.FlecsBuiltIn;
namespace gaemstone.ECS;
public class EntityBuilder
public class EntityBuilder<TContext>
{
public World World { get; }
public World<TContext> World { get; }
/// <summary> Set to modify existing entity (optional). </summary>
public Entity Id { get; set; }
public Entity<TContext> Id { get; set; }
/// <summary>
/// Path of the entity. If no entity is provided, an entity with this path
@ -26,7 +28,7 @@ public class EntityBuilder
/// function name, where these identifiers differ from the name they are
/// registered with in flecs.
/// </summary>
public EntityBuilder Symbol(string symbol) { _symbol = symbol; return this; }
public EntityBuilder<TContext> Symbol(string symbol) { _symbol = symbol; return this; }
private string? _symbol = null;
/// <summary>
@ -44,43 +46,56 @@ public class EntityBuilder
public string? Expression { get; }
/// <summary> Actions to run once the entity has been created. </summary>
private readonly List<Action<EntityRef>> _toSet = new();
private readonly List<Action<Entity>> _toSet = new();
public EntityBuilder(World world, EntityPath? path = null)
public EntityBuilder(World<TContext> world, EntityPath? path = null)
{ World = world; Path = path; }
public EntityBuilder(World world, Entity parent, EntityPath? path = null)
public EntityBuilder(World<TContext> world, Entity parent, EntityPath? path = null)
: this(world, path)
{
// 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.IsSome) Add(World.ChildOf, parent);
if ((path?.IsRelative != false) && parent.IsSome) Add(ChildOf, parent);
}
public EntityBuilder Add(Id id)
public EntityBuilder<TContext> Add(Id id)
{
// If adding a ChildOf relation, store the parent separately.
if (id.RelationUnsafe == World.ChildOf)
if (id.RelationUnsafe == ChildOf)
{ _parent = id.TargetUnsafe; return this; }
if (_toAdd.Count == 31) throw new NotSupportedException(
"Must not add more than 31 Ids at once with EntityBuilder");
_toAdd.Add(id);
return this;
}
public EntityBuilder Add(string symbol)
=> Add(World.LookupBySymbolOrThrow(symbol));
public EntityBuilder Add(Entity relation, Entity target)
=> Add(ECS.Id.Pair(relation, target));
public EntityBuilder<TContext> Add(string symbol)
=> Add(World.LookupSymbolOrThrow(symbol));
public EntityBuilder Set<T>(Id id, in T value) where T : unmanaged
public EntityBuilder<TContext> Add<TEntity>()
=> Add(World.Entity<TEntity>());
public EntityBuilder<TContext> Add(Entity relation, Entity target)
=> Add(World.Pair(relation, target));
public EntityBuilder<TContext> Add<TRelation>(Entity target)
=> Add(World.Pair<TRelation>(target));
public EntityBuilder<TContext> Add<TRelation, TTarget>()
=> Add(World.Pair<TRelation, TTarget>());
public EntityBuilder<TContext> Set<T>(Id id, in T value) where T : unmanaged
// "in" can't be used with lambdas, so we make a local copy.
{ var copy = value; _toSet.Add(e => e.Set(id, copy)); return this; }
{ var copy = value; _toSet.Add(e => EntityAccess.Set(World, e, id, copy)); return this; }
public EntityBuilder<TContext> Set<T>(Id id, T value) where T : class
{ _toSet.Add(e => EntityAccess.Set(World, e, id, value)); return this; }
public EntityBuilder Set<T>(Id id, T obj) where T : class
{ _toSet.Add(e => e.Set(id, obj)); return this; }
public EntityBuilder<TContext> Set<T>(in T value) where T : unmanaged
=> Set(World.Entity<T>(), value);
public EntityBuilder<TContext> Set<T>(T value) where T : class
=> Set(World.Entity<T>(), value);
public unsafe EntityRef Build()
public unsafe Entity<TContext> Build()
{
var parent = _parent;
@ -88,7 +103,7 @@ public class EntityBuilder
if (parent.IsSome && Path.IsAbsolute) throw new InvalidOperationException(
"Entity already has parent set (via ChildOf), so path must not be absolute");
// If path specifies more than just a name, ensure the parent entity exists.
if (Path.Count > 1) parent = EntityPath.EnsureEntityExists(World, parent, Path.Parent!);
if (Path.Count > 1) parent = EntityPath.EnsureEntityExists(World, Path.Parent!, parent);
}
using var alloc = TempAllocator.Use();
@ -102,11 +117,11 @@ public class EntityBuilder
};
var add = desc.add; var index = 0;
if (parent.IsSome) add[index++] = ECS.Id.Pair(World.ChildOf, parent);
if (parent.IsSome) add[index++] = World.Pair(ChildOf, parent);
foreach (var id in _toAdd) add[index++] = id;
var entityId = ecs_entity_init(World, &desc);
var entity = new EntityRef(World, new(entityId));
var entity = Entity<TContext>.GetOrInvalid(World, new(entityId));
foreach (var action in _toSet) action(entity);
return entity;

@ -4,11 +4,13 @@ using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Text;
using gaemstone.Utility;
using gaemstone.ECS.Internal;
using gaemstone.ECS.Utility;
using static flecs_hub.flecs;
namespace gaemstone.ECS;
// TODO: Redo this with a single UTF8 byte array.
public class EntityPath
{
private readonly byte[][] _parts;
@ -48,6 +50,26 @@ public class EntityPath
return bytes;
}).ToArray()) { }
public static unsafe EntityPath From(World world, Entity entity)
{
var parts = new List<byte[]>(32);
do {
var name = ecs_get_name(world, entity).FlecsToBytes();
if (name != null) parts.Add(name);
else {
// If name is not set, use the numeric Id, instead.
var id = entity.NumericId.ToString();
var bytes = new byte[Encoding.UTF8.GetByteCount(id) + 1];
Encoding.UTF8.GetBytes(id, bytes);
parts.Add(bytes);
}
} while ((entity = new(ecs_get_target(world, entity, EcsChildOf, 0))).IsSome);
parts.Reverse();
return new(true, parts.ToArray());
}
public static bool TryParse(string str, [NotNullWhen(true)] out EntityPath? result)
{
result = null;
@ -106,6 +128,10 @@ public class EntityPath
// throw new ArgumentException($"Must not contain {Rune.GetUnicodeCategory(rune)} character");
// }
public EntityPath ThrowIfAbsolute()
=> IsRelative ? this : throw new InvalidOperationException(
$"Path '{this}' must not be absolute");
public string[] GetParts()
{
var result = new string[Count];
@ -135,7 +161,7 @@ public class EntityPath
internal static unsafe Entity Lookup(
World world, Entity parent, EntityPath path, bool throwOnNotFound)
World world, EntityPath path, Entity parent, bool throwOnNotFound)
{
var start = path.IsAbsolute ? Entity.None // If path is absolute, ignore parent and use root.
: parent.IsNone ? new(ecs_get_scope(world)) // If no parent is specified, use the current scope.
@ -145,14 +171,13 @@ public class EntityPath
foreach (var part in path)
fixed (byte* ptr = part.AsSpan()) {
current = new(ecs_lookup_child(world, current, ptr));
if (current.IsSome && ecs_is_alive(world, current)) continue;
if (!throwOnNotFound) return Entity.None;
var startStr = EntityRef.CreateOrNull(world, start)?.GetFullPath().ToString() ?? start.ToString();
throw new EntityNotFoundException(
start.IsNone ? $"Entity at '{path}' not found"
: (start == parent) ? $"Child entity of '{startStr}' at '{path}' not found"
: $"Entity at scope '{startStr}' at '{path}' not found");
if (current.IsNone || !ecs_is_alive(world, current)) {
if (!throwOnNotFound) return Entity.None;
else throw new EntityNotFoundException(
start.IsNone ? $"Entity at '{path}' not found"
: (start == parent) ? $"Child entity of '{From(world, start)}' at '{path}' not found"
: $"Entity at scope '{From(world, start)}' at '{path}' not found");
}
}
return current;
@ -160,7 +185,7 @@ public class EntityPath
/// <summary> Used by <see cref="EntityBuilder.Build"/>. </summary>
internal static unsafe Entity EnsureEntityExists(
World world, Entity parent, EntityPath path)
World world, EntityPath path, Entity parent)
{
// If no parent is specified and path is relative, use the current scope.
if (parent.IsNone && path.IsRelative) parent = new(ecs_get_scope(world));
@ -170,7 +195,7 @@ public class EntityPath
fixed (byte* ptr = part.AsSpan())
if (skipLookup || (parent = new(ecs_lookup_child(world, parent, ptr))).IsNone) {
var desc = new ecs_entity_desc_t { name = ptr, sep = CStringExtensions.ETX };
if (parent.IsSome) desc.add[0] = Id.Pair(world.ChildOf, parent);
if (parent.IsSome) desc.add[0] = Id.Pair(FlecsBuiltIn.ChildOf, parent);
parent = new(ecs_entity_init(world, &desc));
skipLookup = true;
}
@ -179,30 +204,6 @@ public class EntityPath
}
}
public static class EntityPathExtensions
{
public static unsafe EntityPath GetFullPath(this EntityRef entity)
{
var current = (Entity)entity;
var parts = new List<byte[]>(32);
do {
var name = ecs_get_name(entity.World, current).FlecsToBytes();
if (name != null) parts.Add(name);
else {
// If name is not set, use the numeric Id, instead.
var id = current.Id.ToString();
var bytes = new byte[Encoding.UTF8.GetByteCount(id) + 1];
Encoding.UTF8.GetBytes(id, bytes);
parts.Add(bytes);
}
} while ((current = new(ecs_get_target(entity.World, current, EcsChildOf, 0))).IsSome);
parts.Reverse();
return new(true, parts.ToArray());
}
}
public readonly ref struct UTF8View
{
private readonly ReadOnlySpan<byte> _bytes;

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

@ -1,17 +1,17 @@
using System.Collections;
using System.Collections.Generic;
using gaemstone.Utility;
using gaemstone.ECS.Utility;
using static flecs_hub.flecs;
namespace gaemstone.ECS;
public unsafe readonly struct EntityType
: IReadOnlyList<IdRef>
public unsafe readonly struct EntityType<TContext>
: IReadOnlyList<Id<TContext>>
{
public World World { get; }
public ecs_type_t* Handle { get; }
public readonly World<TContext> World;
public readonly ecs_type_t* Handle;
public EntityType(World world, ecs_type_t* handle)
public EntityType(World<TContext> world, ecs_type_t* handle)
{ World = world; Handle = handle; }
public override string ToString()
@ -19,7 +19,9 @@ public unsafe readonly struct EntityType
// IReadOnlyList implementation
public int Count => Handle->count;
public IdRef this[int index] => new(World, new(Handle->array[index]));
public IEnumerator<IdRef> GetEnumerator() { for (var i = 0; i < Count; i++) yield return this[i]; }
public Id<TContext> this[int index]
=> Id<TContext>.GetUnsafe(World, new(Handle->array[index]));
public IEnumerator<Id<TContext>> GetEnumerator()
{ for (var i = 0; i < Count; i++) yield return this[i]; }
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}

@ -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") { }
}

@ -1,26 +1,26 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using gaemstone.Utility;
using gaemstone.ECS.Utility;
using static flecs_hub.flecs;
namespace gaemstone.ECS;
public unsafe class Filter
public unsafe class Filter<TContext>
: IDisposable
{
public World World { get; }
public World<TContext> World { get; }
public ecs_filter_t* Handle { get; }
public Iterator.Variable? ThisVar { get {
public Variable? ThisVar { get {
var index = ecs_filter_find_this_var(this);
return (index >= 0) ? new(index, "This") : null;
} }
internal Filter(World world, ecs_filter_t* handle)
internal Filter(World<TContext> world, ecs_filter_t* handle)
{ World = world; Handle = handle; }
public Filter(World world, FilterDesc desc)
public Filter(World<TContext> world, FilterDesc desc)
{
using var alloc = TempAllocator.Use();
var flecsDesc = desc.ToFlecs(alloc);
@ -31,13 +31,22 @@ public unsafe class Filter
public void Dispose()
=> ecs_filter_fini(this);
public Iterator Iter()
=> new(World, IteratorType.Filter, ecs_filter_iter(World, this));
public FilterIterator<TContext> Iter()
=> new(ecs_filter_iter(World, this));
public override string ToString()
=> ecs_filter_str(World, this).FlecsToStringAndFree()!;
public static implicit operator ecs_filter_t*(Filter q) => q.Handle;
public static implicit operator ecs_filter_t*(Filter<TContext> filter) => filter.Handle;
}
public unsafe class FilterIterator<TContext>
: Iterator<TContext>
{
internal FilterIterator(ecs_iter_t value)
: base(value) { }
public override bool Next()
=> ecs_filter_next(Handle);
}
public class FilterDesc

@ -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,41 +1,66 @@
using System;
using gaemstone.ECS.Utility;
using static flecs_hub.flecs;
namespace gaemstone.ECS;
public readonly struct Id
: IEquatable<Id>
public unsafe struct Id<TContext>
: IEquatable<Id<TContext>>
{
public readonly ecs_id_t Value;
public IdFlags Flags => (IdFlags)(Value & ECS_ID_FLAGS_MASK);
public readonly World<TContext> World;
public readonly Id Value;
public bool IsPair => ecs_id_is_pair(this);
public bool IsWildcard => ecs_id_is_wildcard(this);
public IdFlags Flags => Value.Flags;
public bool IsPair => Value.IsPair;
public bool IsWildcard => Value.IsWildcard;
public Entity RelationUnsafe => new(new() { Data = (Value & ECS_COMPONENT_MASK) >> 32 });
public Entity TargetUnsafe => new(new() { Data = Value & ECS_ENTITY_MASK });
public bool IsValid => ecs_id_is_valid(World, this);
public bool IsTag => ecs_id_is_tag(World, this).Data != default;
public bool IsInUse => ecs_id_in_use(World, this);
public int Count => ecs_count_id(World, this);
public Id(ecs_id_t value) => Value = value;
private Id(World<TContext> world, Id id)
{ World = world; Value = id; }
public static Id Combine(IdFlags flags, Id id)
=> new((ulong)flags | id.Value);
public static Id<TContext> GetUnsafe(World<TContext> world, Id value)
=> new(world, value);
public static Id<TContext>? GetOrNull(World<TContext> world, Id value)
=> ecs_id_is_valid(world, value) ? new(world, value) : null;
public static Id<TContext> GetOrThrow(World<TContext> world, Id value)
=> ecs_id_is_valid(world, value) ? new(world, value) : throw new InvalidOperationException($"The id {value} is not valid");
public static Id Pair(Entity relation, Entity target)
=> Combine(IdFlags.Pair, new(
((relation.Value.Data << 32) & ECS_COMPONENT_MASK) |
( target.Value.Data & ECS_ENTITY_MASK )));
public Entity<TContext>? AsEntity()
=> !IsPair ? World.LookupAliveOrNull(new Entity(new() { Data = Value })) : null;
public (Entity<TContext> Relation, Entity<TContext> Target)? AsPair()
=> IsPair && (World.LookupAliveOrNull(Value.RelationUnsafe) is Entity<TContext> relation) &&
(World.LookupAliveOrNull(Value.TargetUnsafe ) is Entity<TContext> target )
? (relation, target) : null;
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 bool Equals(Id<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 Id<TContext> other) && Equals(other);
public override int GetHashCode()
=> Value.GetHashCode();
public override string? ToString()
=> (Flags != default) ? $"Id({Value.Data}, Flags={Flags})"
: $"Id({Value.Data})";
=> ecs_id_str(World, this).FlecsToStringAndFree()!;
public static bool operator ==(Id left, Id right) => left.Equals(right);
public static bool operator !=(Id left, Id right) => !left.Equals(right);
public static bool operator ==(Id<TContext> left, Id<TContext> right) => left.Equals(right);
public static bool operator !=(Id<TContext> left, Id<TContext> right) => !left.Equals(right);
public static implicit operator ecs_id_t(Id i) => i.Value;
public static implicit operator Id (Id<TContext> id) => id.Value;
public static implicit operator ecs_id_t(Id<TContext> id) => id.Value.Value;
public static implicit operator Term(Id<TContext> id) => new(id.Value);
}
[Flags]

@ -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,14 +1,15 @@
using System;
using System.Runtime.InteropServices;
using gaemstone.Utility;
using gaemstone.ECS.Internal;
using gaemstone.ECS.Utility;
using static flecs_hub.flecs;
namespace gaemstone.ECS;
public static class ObserverExtensions
{
public static unsafe EntityRef InitObserver(this EntityRef entity,
FilterDesc filter, Action<Iterator> callback, params Entity[] events)
public static unsafe Entity<TContext> InitObserver<TContext>(this Entity<TContext> entity,
FilterDesc filter, Action<Iterator<TContext>> callback, params Entity[] events)
{
if (events.Length == 0) throw new ArgumentException("Must specify at least 1 event", nameof(events));
if (events.Length > 8) throw new ArgumentException("Must specify at most 8 events", nameof(events));
@ -25,7 +26,9 @@ public static class ObserverExtensions
var span = desc.events;
for (var i = 0; i < events.Length; i++)
span[i] = events[i];
return new(world, new(ecs_observer_init(world, &desc)));
if (ecs_observer_init(world, &desc).Data == default)
throw new InvalidOperationException();
return entity;
}
[UnmanagedCallersOnly]
@ -33,7 +36,7 @@ public static class ObserverExtensions
{
var (world, callback) = CallbackContextHelper
.Get<(World, Action<Iterator>)>((nint)iter->binding_ctx);
callback(new Iterator(world, null, *iter));
callback(new Iterator(iter));
}
[UnmanagedCallersOnly]

@ -1,19 +1,19 @@
using System;
using gaemstone.Utility;
using gaemstone.ECS.Utility;
using static flecs_hub.flecs;
namespace gaemstone.ECS;
public unsafe class Query
public unsafe class Query<TContext>
: IDisposable
{
public World World { get; }
public World<TContext> World { get; }
public ecs_query_t* Handle { get; }
public Filter Filter { get; }
public Filter<TContext> Filter { get; }
public Iterator.Variable? ThisVar => Filter.ThisVar;
public Variable? ThisVar => Filter.ThisVar;
public Query(World world, QueryDesc desc)
public Query(World<TContext> world, QueryDesc desc)
{
using var alloc = TempAllocator.Use();
var flecsDesc = desc.ToFlecs(alloc);
@ -25,13 +25,22 @@ public unsafe class Query
public void Dispose()
=> ecs_query_fini(this);
public Iterator Iter()
=> new(World, IteratorType.Query, ecs_query_iter(World, this));
public QueryIterator<TContext> Iter()
=> new(ecs_query_iter(World, this));
public override string ToString()
=> ecs_query_str(Handle).FlecsToStringAndFree()!;
public static implicit operator ecs_query_t*(Query q) => q.Handle;
public static implicit operator ecs_query_t*(Query<TContext> query) => query.Handle;
}
public unsafe class QueryIterator<TContext>
: Iterator<TContext>
{
internal QueryIterator(ecs_iter_t value)
: base(value) { }
public override bool Next()
=> ecs_query_next(Handle);
}
public class QueryDesc : FilterDesc
@ -39,10 +48,10 @@ public class QueryDesc : FilterDesc
public QueryDesc(params Term[] terms) : base(terms) { }
public QueryDesc(string expression) : base(expression) { }
public new unsafe ecs_query_desc_t ToFlecs(IAllocator allocator)
public new unsafe ecs_query_desc_t ToFlecs(IAllocator alloc)
{
var desc = new ecs_query_desc_t {
filter = base.ToFlecs(allocator),
filter = base.ToFlecs(alloc),
// TODO: Implement more Query features.
};
return desc;

@ -1,23 +1,23 @@
using System;
using System.Collections;
using System.Collections.Generic;
using gaemstone.Utility;
using gaemstone.ECS.Utility;
using static flecs_hub.flecs;
namespace gaemstone.ECS;
public unsafe class Rule
public unsafe class Rule<TContext>
: IDisposable
{
public World World { get; }
public World<TContext> World { get; }
public ecs_rule_t* Handle { get; }
public Filter Filter { get; }
public Filter<TContext> Filter { get; }
private VariableCollection? _variables;
public VariableCollection Variables => _variables ??= new(this);
public Iterator.Variable? ThisVar => Filter.ThisVar;
public Variable? ThisVar => Filter.ThisVar;
public Rule(World world, FilterDesc desc)
public Rule(World<TContext> world, FilterDesc desc)
{
using var alloc = TempAllocator.Use();
var flecsDesc = desc.ToFlecs(alloc);
@ -29,48 +29,53 @@ public unsafe class Rule
public void Dispose()
=> ecs_rule_fini(this);
public Iterator Iter()
=> new(World, IteratorType.Rule, ecs_rule_iter(World, this));
public RuleIterator<TContext> Iter()
=> new(ecs_rule_iter(World, this));
public override string ToString()
=> ecs_rule_str(Handle).FlecsToStringAndFree()!;
public static implicit operator ecs_rule_t*(Rule q) => q.Handle;
public static implicit operator ecs_rule_t*(Rule<TContext> rule) => rule.Handle;
public unsafe class VariableCollection
: IReadOnlyCollection<Iterator.Variable>
: IReadOnlyCollection<Variable>
{
private readonly List<Iterator.Variable> _variables = new();
public Rule Rule { get; }
private readonly List<Variable> _variables = new();
public int Count => _variables.Count;
public Iterator.Variable? this[int index]
public Variable? this[int index]
=> _variables.Find(v => v.Index == index);
public Iterator.Variable? this[string name]
public Variable? this[string name]
=> _variables.Find(v => string.Equals(
v.Name, name, StringComparison.OrdinalIgnoreCase));
internal VariableCollection(Rule rule)
internal VariableCollection(ecs_rule_t* handle)
{
Rule = rule;
// Find the $This variable, if the rule has one.
var thisIndex = ecs_filter_find_this_var(ecs_rule_get_filter(Rule));
var thisIndex = ecs_filter_find_this_var(ecs_rule_get_filter(handle));
if (thisIndex >= 0) _variables.Add(new(thisIndex, "This"));
// Find all the other "accessible" variables.
var count = ecs_rule_var_count(Rule);
var count = ecs_rule_var_count(handle);
for (var i = 0; i < count; i++) {
if ((i == thisIndex) || !ecs_rule_var_is_entity(Rule, i)) continue;
var name = ecs_rule_var_name(Rule, i).FlecsToString()!;
if ((i == thisIndex) || !ecs_rule_var_is_entity(handle, i)) continue;
var name = ecs_rule_var_name(handle, i).FlecsToString()!;
_variables.Add(new(i, name));
}
}
// IEnumerable implementation
public IEnumerator<Iterator.Variable> GetEnumerator() => _variables.GetEnumerator();
public IEnumerator<Variable> GetEnumerator() => _variables.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => _variables.GetEnumerator();
}
}
public unsafe class RuleIterator<TContext>
: Iterator<TContext>
{
internal RuleIterator(ecs_iter_t value)
: base(value) { }
public override bool Next()
=> ecs_rule_next(Handle);
}

@ -1,34 +1,37 @@
using System;
using System.Runtime.InteropServices;
using gaemstone.Utility;
using gaemstone.ECS.Utility;
using static flecs_hub.flecs;
namespace gaemstone.ECS;
public static class SystemExtensions
{
public static unsafe EntityRef InitSystem(this EntityRef entity,
QueryDesc query, Action<Iterator> callback)
public static unsafe Entity<TContext> InitSystem<TContext>(this Entity<TContext> entity,
QueryDesc query, Action<Iterator<TContext>> callback)
{
var world = entity.World;
var internalCallback = (nint iter) =>
callback(new Iterator<TContext>((ecs_iter_t*)iter));
using var alloc = TempAllocator.Use();
var desc = new ecs_system_desc_t {
entity = entity,
query = query.ToFlecs(alloc),
binding_ctx = (void*)CallbackContextHelper.Create((world, callback)),
binding_ctx = (void*)CallbackContextHelper.Create(internalCallback),
binding_ctx_free = new() { Data = new() { Pointer = &FreeContext } },
callback = new() { Data = new() { Pointer = &Callback } },
};
return new(world, new(ecs_system_init(world, &desc)));
if (ecs_system_init(world, &desc).Data == default)
throw new InvalidOperationException();
return entity;
}
[UnmanagedCallersOnly]
private static unsafe void Callback(ecs_iter_t* iter)
{
var (world, callback) = CallbackContextHelper
.Get<(World, Action<Iterator>)>((nint)iter->binding_ctx);
callback(new Iterator(world, null, *iter));
}
=> CallbackContextHelper
.Get<Action<nint>>((nint)iter->binding_ctx)
.Invoke((nint)iter);
[UnmanagedCallersOnly]
private static unsafe void FreeContext(void* context)

@ -1,5 +1,5 @@
using System;
using gaemstone.Utility;
using gaemstone.ECS.Utility;
using static flecs_hub.flecs;
namespace gaemstone.ECS;
@ -19,11 +19,6 @@ public class Term
public Term(TermId relation, TermId target)
{ Relation = relation; Target = target; }
public static implicit operator Term(EntityRef entity) => new(entity);
public static implicit operator Term(Entity entity) => new(entity);
public static implicit operator Term(IdRef id) => new(id);
public static implicit operator Term(Id id) => new(id);
public Term None { get { InOutKind = TermInOutKind.None; return this; } }
public Term In { get { InOutKind = TermInOutKind.In; return this; } }
public Term Out { get { InOutKind = TermInOutKind.Out; return this; } }
@ -35,9 +30,9 @@ public class Term
public ecs_term_t ToFlecs(IAllocator allocator) => new() {
id = Id,
src = Source?.ToFlecs(allocator) ?? default,
src = Source ?.ToFlecs(allocator) ?? default,
first = Relation?.ToFlecs(allocator) ?? default,
second = Target?.ToFlecs(allocator) ?? default,
second = Target ?.ToFlecs(allocator) ?? default,
inout = (ecs_inout_kind_t)InOutKind,
oper = (ecs_oper_kind_t)OperKind,
id_flags = (ecs_id_t)(ulong)Flags,
@ -83,10 +78,6 @@ public class TermId
} else Name = name;
}
public static implicit operator TermId(EntityRef entity) => new(entity);
public static implicit operator TermId(Entity entity) => new(entity);
public static implicit operator TermId(string name) => new(name);
public ecs_term_id_t ToFlecs(IAllocator allocator) => new() {
id = Id,
name = allocator.AllocateCString(Name),

@ -5,7 +5,7 @@ using System.Text;
using System.Threading;
using static flecs_hub.flecs.Runtime;
namespace gaemstone.Utility;
namespace gaemstone.ECS.Utility;
public interface IAllocator
{
@ -15,18 +15,19 @@ public interface IAllocator
public unsafe static class AllocatorExtensions
{
public static Span<T> AllocateCopy<T>(this IAllocator allocator, ReadOnlySpan<T> orig) where T : unmanaged
{ var copy = allocator.Allocate<T>(orig.Length); orig.CopyTo(copy); return copy; }
public static Span<T> Allocate<T>(this IAllocator allocator, int count) where T : unmanaged
=> new((void*)allocator.Allocate(sizeof(T) * count), count);
public static void Free<T>(this IAllocator allocator, Span<T> span) where T : unmanaged
=> allocator.Free((nint)Unsafe.AsPointer(ref span[0]));
public static Span<T> AllocateCopy<T>(this IAllocator allocator, ReadOnlySpan<T> orig) where T : unmanaged
{ var copy = allocator.Allocate<T>(orig.Length); orig.CopyTo(copy); return copy; }
public static ref T Allocate<T>(this IAllocator allocator) where T : unmanaged
=> ref Unsafe.AsRef<T>((void*)allocator.Allocate(sizeof(T)));
public static void Free<T>(this IAllocator allocator, ref T value) where T : unmanaged
=> allocator.Free((nint)Unsafe.AsPointer(ref value));
public static T* AllocateCopy<T>(this IAllocator allocator, T orig) where T : unmanaged
{ var ptr = allocator.Allocate<T>(); *ptr = orig; return ptr; }
public static T* Allocate<T>(this IAllocator allocator) where T : unmanaged
=> (T*)allocator.Allocate(sizeof(T));
public static void Free<T>(this IAllocator allocator, T* ptr) where T : unmanaged
=> allocator.Free((nint)ptr);
public static CString AllocateCString(this IAllocator allocator, string? value)
{

@ -3,7 +3,7 @@ using System.Runtime.InteropServices;
using static flecs_hub.flecs;
using static flecs_hub.flecs.Runtime;
namespace gaemstone.Utility;
namespace gaemstone.ECS.Utility;
public unsafe static class CStringExtensions
{

@ -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…
Cancel
Save