Compare commits

...

34 Commits

Author SHA1 Message Date
copygirl 28d2b3e077 Fix link in README and some whitespace 1 year ago
copygirl 99174ece5c Use ecs_iter_is_true in Iterator.Any 1 year ago
copygirl 1607506e32 Remove ETX as Flecs now supports Empty 1 year ago
copygirl 5600a6f0ee Fix compile errors due to changes in flecs-cs 1 year ago
copygirl 063fb40c5c Update to newest flecs-cs 1 year ago
copygirl 3bea2a6ba0 Add Term, Filter, Query, Rule shorthands to World 1 year ago
copygirl ef00efd4bc Add Iterator<>.First() 1 year ago
copygirl 2c5d7f6c65 Add Iterator.Any() 1 year ago
copygirl 720a90549c Change $This variable to $this 1 year ago
copygirl 1dca1e7988 Add Entity<>.IsEnabled 1 year ago
copygirl c575046a61 Minimal world init, remove args constructor 1 year ago
copygirl 6cd699efbc Update README 1 year ago
copygirl eec968d636 Update flecs-cs and Flecs to fix missing functions 1 year ago
copygirl 5264ffdc5f Fix World not handling args correctly 1 year ago
copygirl 9eade0adb1 Add Entity<T>.Valid/AliveOrNull/Throw 1 year ago
copygirl 7830e72c29 Fix IsAlive throwing if entity is None 1 year ago
copygirl b85a533ca4 Throw in EntityPath.From if entity is None 1 year ago
copygirl 42f843e690 Update to newest flecs-cs 1 year ago
copygirl f7d17d46ab Fix Observer not using new generic Iterator 1 year ago
copygirl b93beed5ec Fix CreateLookup throwing on same lookup creation 1 year ago
copygirl 948268e9ba WIP 1 year ago
copygirl 1d1ba4fe4d Remove EntityBase 1 year ago
copygirl 60919ef1f6 Add EntityBuilder constructor with parent arg 1 year ago
copygirl 0527919d47 Make World implement IDisposable 1 year ago
copygirl c24052cfa7 Move EntityNotFoundException out of World class 1 year ago
copygirl dc776ace1f Remove LookupByType and related methods 1 year ago
copygirl e4f3020a87 Exclude flecs-cs from compile items 1 year ago
copygirl 2d72e87290 Many minor changes 1 year ago
copygirl 411f94c412 Fix IdFlags not being up-to-date with Flecs 1 year ago
copygirl 0ff026d639 Cleanup 1 year ago
copygirl 33bbd75fbc Rename Term.InOut and .Oper, add InOut property 1 year ago
copygirl 60c031a119 Don't add phase in InitSystem 1 year ago
copygirl d6c8129676 Allow passing multiple events to InitObserver 1 year ago
copygirl 6bc3ab8bfb Add SpanToRef.GetOrNull 1 year ago
  1. 57
      README.md
  2. 7
      gaemstone.ECS.csproj
  3. 2
      src/flecs-cs
  4. 87
      src/gaemstone.ECS/Component.cs
  5. 35
      src/gaemstone.ECS/Entity+Bare.cs
  6. 176
      src/gaemstone.ECS/Entity.cs
  7. 61
      src/gaemstone.ECS/EntityBase.cs
  8. 91
      src/gaemstone.ECS/EntityBuilder.cs
  9. 78
      src/gaemstone.ECS/EntityPath.cs
  10. 169
      src/gaemstone.ECS/EntityRef.cs
  11. 18
      src/gaemstone.ECS/EntityType.cs
  12. 31
      src/gaemstone.ECS/Exceptions.cs
  13. 29
      src/gaemstone.ECS/Filter.cs
  14. 36
      src/gaemstone.ECS/Id+Bare.cs
  15. 81
      src/gaemstone.ECS/Id.cs
  16. 61
      src/gaemstone.ECS/IdRef.cs
  17. 117
      src/gaemstone.ECS/Internal/EntityAccess.cs
  18. 11
      src/gaemstone.ECS/Internal/FlecsBuiltIn.cs
  19. 113
      src/gaemstone.ECS/Internal/Iterator.cs
  20. 9
      src/gaemstone.ECS/Internal/Lookup.cs
  21. 196
      src/gaemstone.ECS/Iterator.cs
  22. 28
      src/gaemstone.ECS/Observer.cs
  23. 33
      src/gaemstone.ECS/Query.cs
  24. 55
      src/gaemstone.ECS/Rule.cs
  25. 43
      src/gaemstone.ECS/System.cs
  26. 62
      src/gaemstone.ECS/Term.cs
  27. 17
      src/gaemstone.ECS/Utility/Allocators.cs
  28. 3
      src/gaemstone.ECS/Utility/CStringExtensions.cs
  29. 2
      src/gaemstone.ECS/Utility/CallbackContextHelper.cs
  30. 71
      src/gaemstone.ECS/Utility/ReferenceHandle.cs
  31. 4
      src/gaemstone.ECS/Utility/SpanExtensions.cs
  32. 75
      src/gaemstone.ECS/World+Bare.cs
  33. 69
      src/gaemstone.ECS/World+Lookup.cs
  34. 96
      src/gaemstone.ECS/World.cs
  35. 26
      src/gaemstone.Utility/FlecsException.cs

@ -1,10 +1,8 @@
# gaemstone.ECS
.. is a medium-level managed wrapper library around the [flecs-cs] bindings
for the amazing [Entity Component System (ECS)][ECS] framework [Flecs]. It is
used as part of the [gæmstone] game engine, but may be used in other projects
as well. To efficiently use this library, a thorough understanding of [Flecs]
is required.
.. is a medium-level managed wrapper library around the [flecs-cs] bindings for the amazing [Entity Component System (ECS)][ECS] framework [Flecs]. It is used as part of the [gæmstone] game engine, but may be used in other projects as well. To efficiently use this library, a thorough understanding of [Flecs] is required.
These classes have been split from the main [gæmstone] project. It is still a little unclear what functionality belongs where, and structural changes may occur. In its current state, I recommend to use this repository simply as a reference for building similar projects.
[ECS]: https://en.wikipedia.org/wiki/Entity_component_system
[Flecs]: https://github.com/SanderMertens/flecs
@ -13,20 +11,18 @@ is required.
## Features
These classes have only recently been split from the main **gæmstone** project. It is currently unclear what functionality belongs where, and structural changes are still very likely. In its current state, feel free to use **gæmstone.ECS** as a reference for building similar projects, or be aware that things are in flux, and in general nowhere near stable.
- Simple wrapper structs such as [Entity] and [Identifier].
- Classes with convenience functions like [EntityRef] and [EntityType].
- Convenient wrapper types such as [Id], [Entity] and [EntityType].
- [EntityPath] uses a unix-like path, for example `/Game/Players/copygirl`.
- Fast type-to-entity lookup using generic context on [World]. (See below.)
- Define your own [Components] as both value or reference types.
- Query the ECS with [Iterators], [Filters], [Queries] and [Rules].
- Create [Systems] for game logic and [Observers] to act on changes.
[Id]: ./src/gaemstone.ECS/Id.cs
[Entity]: ./src/gaemstone.ECS/Entity.cs
[Identifier]: ./src/gaemstone.ECS/Identifier.cs
[EntityRef]: ./src/gaemstone.ECS/EntityRef.cs
[EntityType]: ./src/gaemstone.ECS/EntityType.cs
[EntityPath]: ./src/gaemstone.ECS/EntityPath.cs
[World]: ./src/gaemstone.ECS/World.cs
[Components]: ./src/gaemstone.ECS/Component.cs
[Iterators]: ./src/gaemstone.ECS/Iterator.cs
[Filters]: ./src/gaemstone.ECS/Filter.cs
@ -38,7 +34,7 @@ These classes have only recently been split from the main **gæmstone** project.
## Example
```cs
var world = new World();
var world = new World<Program>();
var position = world
.New("Position") // Create a new EntityBuilder, and set its name.
@ -55,26 +51,31 @@ entities.NewChild("Two").Set(new Position(10, 20)).Build();
// Changed my mind: Let's multiply each entity's position by 10.
foreach (var child in entities.GetChildren()) {
ref var pos = ref child.GetRefOrThrow<Position>();
pos = new(pos.X * 10, pos.Y * 10);
ref var pos = ref child.GetRefOrThrow<Position>();
pos = new(pos.X * 10, pos.Y * 10);
}
var onUpdate = world.LookupByPathOrThrow("/flecs/pipeline/OnUpdate");
// The following systems run in the "OnUpdate"
// phase of the default pipeline provided by Flecs.
var dependsOn = world.LookupPathOrThrow("/flecs/core/DependsOn");
var onUpdate = world.LookupPathOrThrow("/flecs/pipeline/OnUpdate");
// Create a system that will move all entities with
// the "Position" component downwards by 2 every frame.
world.New("FallSystem").Build()
.InitSystem(onUpdate, new("Position"), iter => {
world.New("FallSystem")
.Add(dependsOn, onUpdate)
.Build().InitSystem(new("Position"), iter => {
var posColumn = iter.Field<Position>(1);
for (var i = 0; i < iter.Count; i++) {
ref var pos = ref posColumn[i];
pos = new(pos.X, pos.Y + 2);
pos = new(pos.X, pos.Y - 2);
}
});
// Create a system that will print out entities' positions.
world.New("PrintPositionSystem").Build()
.InitSystem(onUpdate, new("[in] Position"), iter => {
.Add(dependsOn, onUpdate)
.InitSystem(new("[in] Position"), iter => {
var posColumn = iter.Field<Position>(1);
for (var i = 0; i < iter.Count; i++) {
var entity = iter.Entity(i);
@ -109,3 +110,21 @@ dotnet add reference gaemstone.ECS/gaemstone.ECS.csproj
# To generate flecs-cs' bindings:
./gaemstone.ECS/src/flecs-cs/library.sh
```
## On the `TContext` type parameter
Entities may be looked up simply by their type, once they're registered with `CreateLookup` or `InitComponent`. Under the hood, this is made possible using a nested static generic class that hold onto an `Entity` field. Theoretically this could be compiled into a simple field lookup and therefore be faster than dictionary lookups.
To support scenarios where multiple worlds may be used simultaneously, each with their own unique type lookups, we specify a generic type as that context.
In cases where only a single world is used, the amount of typing can be reduced by including a file similar to the following, defining global aliases:
```cs
global using Entity = gaemstone.ECS.Entity<Context>;
global using Id = gaemstone.ECS.Id<Context>;
global using Iterator = gaemstone.ECS.Iterator<Context>;
global using World = gaemstone.ECS.World<Context>;
// Add more aliases as you feel they are needed.
public struct Context { }
```

@ -7,13 +7,8 @@
<Nullable>enable</Nullable>
</PropertyGroup>
<PropertyGroup>
<EnableDefaultItems>false</EnableDefaultItems>
</PropertyGroup>
<ItemGroup>
<Compile Include="src/gaemstone.ECS/**/*.cs" />
<Compile Include="src/gaemstone.Utility/**/*.cs" />
<Compile Remove="src/flecs-cs/**" />
</ItemGroup>
<ItemGroup>

@ -1 +1 @@
Subproject commit a2047983917aa462a8c2f34d5315aea48502f4d8
Subproject commit 1ae8ffade56a279d450dbf42545afbf873bdbafe

@ -1,18 +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(this EntityRef entity, Type type)
=> (EntityRef)typeof(ComponentExtensions)
.GetMethod(nameof(InitComponent), new[] { typeof(EntityRef) })!
.MakeGenericMethod(type).Invoke(null, new[]{ entity })!;
public static EntityRef InitComponent<T>(this EntityRef entity)
public Entity<TContext> InitComponent<T>()
{
if (typeof(T).IsPrimitive) throw new ArgumentException(
"Must not be primitive");
@ -22,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.
@ -34,77 +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.CreateLookup(typeof(T));
}
}
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,172 @@
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 => IsSome && 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
=> World.Term(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)
=> new Entity<TContext>(world, value).ValidOrNull();
public static Entity<TContext> GetOrThrow(World<TContext> world, Entity value)
=> new Entity<TContext>(world, value).ValidOrThrow();
public Entity<TContext>? ValidOrNull() => IsValid ? this : null;
public Entity<TContext> ValidOrThrow() => IsValid ? this
: throw new InvalidOperationException($"The entity {this} is not valid");
public Entity<TContext>? AliveOrNull() => IsAlive ? this : null;
public Entity<TContext> AliveOrThrow() => IsAlive ? this
: throw new InvalidOperationException($"The entity {this} is not alive");
public void Delete()
=> ecs_delete(World, this);
public Entity<TContext> CreateLookup<T>()
{
ref var lookup = ref Lookup<TContext>.Entity<T>.Value;
if (lookup == this) { /* Don't throw if lookup already has the same entity set. */ }
else 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 bool IsEnabled => !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(0x{Value.Data.Data:X})"
: "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,61 +0,0 @@
namespace gaemstone.ECS;
public abstract class EntityBase<TReturn>
{
public abstract World World { get; }
public abstract TReturn Add(Id id);
public abstract TReturn Remove(Id id);
public abstract bool Has(Id id);
public TReturn Add(string symbol) => Add(World.LookupBySymbolOrThrow(symbol));
public TReturn Add<T>() => Add(World.LookupByTypeOrThrow(typeof(T)));
public TReturn Add(Entity relation, Entity target) => Add(Id.Pair(relation, target));
public TReturn Add<TRelation>(Entity target) => Add(World.LookupByTypeOrThrow<TRelation>(), target);
public TReturn Add<TRelation, TTarget>() => Add(World.LookupByTypeOrThrow<TRelation>(), World.LookupByTypeOrThrow<TTarget>());
public TReturn Remove(string symbol) => Remove(World.LookupBySymbolOrThrow(symbol));
public TReturn Remove<T>() => Remove(World.LookupByTypeOrThrow(typeof(T)));
public TReturn Remove(Entity relation, Entity target) => Remove(Id.Pair(relation, target));
public TReturn Remove<TRelation>(Entity target) => Remove(World.LookupByTypeOrThrow<TRelation>(), target);
public TReturn Remove<TRelation, TTarget>() => Remove(World.LookupByTypeOrThrow<TRelation>(), World.LookupByTypeOrThrow<TTarget>());
public bool Has(string symbol) => Has(World.LookupBySymbolOrThrow(symbol));
public bool Has<T>() => Has(World.LookupByTypeOrThrow(typeof(T)));
public bool Has(Entity relation, Entity target) => Has(Id.Pair(relation, target));
public bool Has<TRelation>(Entity target) => Has(World.LookupByTypeOrThrow<TRelation>(), target);
public bool Has<TRelation, TTarget>() => Has(World.LookupByTypeOrThrow<TRelation>(), World.LookupByTypeOrThrow<TTarget>());
public abstract T? GetOrNull<T>(Id id) where T : unmanaged;
public abstract T? GetOrNull<T>(Id id, T _ = null!) where T : class;
public abstract T GetOrThrow<T>(Id id);
public abstract ref T GetMut<T>(Id id) where T : unmanaged;
public abstract ref T GetRefOrNull<T>(Id id) where T : unmanaged;
public abstract ref T GetRefOrThrow<T>(Id id) where T : unmanaged;
public abstract void Modified<T>(Id id);
public T? GetOrNull<T>() where T : unmanaged => GetOrNull<T>(World.LookupByTypeOrThrow<T>());
public T? GetOrNull<T>(T _ = null!) where T : class => GetOrNull<T>(World.LookupByTypeOrThrow<T>());
public T GetOrThrow<T>() => GetOrThrow<T>(World.LookupByTypeOrThrow<T>());
public ref T GetMut<T>() where T : unmanaged => ref GetMut<T>(World.LookupByTypeOrThrow<T>());
public ref T GetRefOrNull<T>() where T : unmanaged => ref GetRefOrNull<T>(World.LookupByTypeOrThrow<T>());
public ref T GetRefOrThrow<T>() where T : unmanaged => ref GetRefOrThrow<T>(World.LookupByTypeOrThrow<T>());
public void Modified<T>() => Modified<T>(World.LookupByTypeOrThrow<T>());
public abstract TReturn Set<T>(Id id, in T value) where T : unmanaged;
public abstract TReturn Set<T>(Id id, T obj) where T : class;
public TReturn Set<T>(in T value) where T : unmanaged => Set(World.LookupByTypeOrThrow<T>(), value);
public TReturn Set<T>(T obj) where T : class => Set(World.LookupByTypeOrThrow<T>(), obj);
public TReturn ChildOf(Entity parent) => Add(World.ChildOf, parent);
public TReturn ChildOf<TParent>() => Add(World.ChildOf, World.LookupByTypeOrThrow<TParent>());
public TReturn Disable() => Add(World.Disabled);
public TReturn Enable() => Remove(World.Disabled);
public bool IsDisabled => Has(World.Disabled);
}

@ -1,17 +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
: EntityBase<EntityBuilder>
public class EntityBuilder<TContext>
{
public override 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
@ -27,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>
@ -38,51 +39,63 @@ public class EntityBuilder
/// <summary> Ids to add to the new or existing entity. </summary>
private readonly HashSet<Id> _toAdd = new();
// (ChildOf, *) is handled explicitly, it won't be added to _toAdd.
private Entity _parent = Entity.None;
/// <summary> String expression with components to add. </summary>
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 override EntityBuilder Add(Id id)
public EntityBuilder(World<TContext> world, Entity parent, EntityPath? path = null)
: this(world, path)
{
// If adding a ChildOf relation, store the parent separately.
if (id.AsPair(World) is (EntityRef relation, EntityRef target) &&
(relation == World.ChildOf)) { _parent = target; return this; }
// 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(ChildOf, parent);
}
public EntityBuilder<TContext> Add(Id id)
{
// If adding a ChildOf relation, store the parent separately.
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 override EntityBuilder Remove(Id id)
=> throw new NotSupportedException();
public override bool Has(Id id)
=> !id.IsWildcard ? _toAdd.Contains(id)
: throw new NotSupportedException(); // TODO: Support wildcard.
public override T? GetOrNull<T>(Id id) => throw new NotSupportedException();
public override T? GetOrNull<T>(Id id, T _ = null!) where T : class => throw new NotSupportedException();
public override T GetOrThrow<T>(Id id) => throw new NotSupportedException();
public override ref T GetMut<T>(Id id) => throw new NotSupportedException();
public override ref T GetRefOrNull<T>(Id id) => throw new NotSupportedException();
public override ref T GetRefOrThrow<T>(Id id) => throw new NotSupportedException();
public override void Modified<T>(Id id) => throw new NotImplementedException();
public override EntityBuilder Set<T>(Id id, in T value)
public EntityBuilder<TContext> Add(string symbol)
=> Add(World.LookupSymbolOrThrow(symbol));
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 override EntityBuilder Set<T>(Id id, T obj)
{ _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;
@ -90,25 +103,25 @@ 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();
var desc = new ecs_entity_desc_t {
id = Id,
name = (Path != null) ? alloc.AllocateCString(Path.Name.AsSpan()) : default,
symbol = alloc.AllocateCString(_symbol),
add_expr = alloc.AllocateCString(Expression),
_name = (Path != null) ? alloc.AllocateCString(Path.Name.AsSpan()) : default,
_symbol = alloc.AllocateCString(_symbol),
_add_expr = alloc.AllocateCString(Expression),
use_low_id = UseLowId,
sep = CStringExtensions.ETX,
_sep = CStringExtensions.Empty,
};
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,29 @@ public class EntityPath
return bytes;
}).ToArray()) { }
public static unsafe EntityPath From(World world, Entity entity)
{
if (entity.IsNone) throw new ArgumentException(
"entity is Entity.None", nameof(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 +131,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 +164,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 +174,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 World.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 +188,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));
@ -169,8 +197,8 @@ public class EntityPath
foreach (var part in path)
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);
var desc = new ecs_entity_desc_t { _name = ptr, _sep = CStringExtensions.Empty };
if (parent.IsSome) desc.add[0] = Id.Pair(FlecsBuiltIn.ChildOf, parent);
parent = new(ecs_entity_init(world, &desc));
skipLookup = true;
}
@ -179,30 +207,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,169 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using gaemstone.Utility;
using static flecs_hub.flecs;
namespace gaemstone.ECS;
public unsafe class EntityRef
: EntityBase<EntityRef>
, IEquatable<EntityRef>
{
public override World World { get; }
public Entity Entity { get; }
public uint Id => Entity.Id;
public bool IsAlive => ecs_is_alive(World, this);
public EntityType Type => new(World, ecs_get_type(World, this));
public string? Name {
get => ecs_get_name(World, this).FlecsToString()!;
set { using var alloc = TempAllocator.Use();
ecs_set_name(World, this, alloc.AllocateCString(value)); }
}
public string? Symbol {
get => ecs_get_symbol(World, this).FlecsToString()!;
set { using var alloc = TempAllocator.Use();
ecs_set_symbol(World, this, alloc.AllocateCString(value)); }
}
private EntityRef(World world, Entity entity, bool throwOnInvalid)
{
if (throwOnInvalid && !ecs_is_valid(world, entity))
throw new InvalidOperationException($"The entity {entity} is not valid");
World = world;
Entity = entity;
}
public EntityRef(World world, Entity entity)
: this(world, entity, true) { }
public static EntityRef? CreateOrNull(World world, Entity entity)
=> ecs_is_valid(world, entity) ? new(world, entity) : null;
public void Delete()
=> ecs_delete(World, this);
public EntityBuilder NewChild(EntityPath? path = null)
=> World.New(this, EnsureRelativePath(path));
public EntityRef? LookupChild(EntityPath path)
=> World.LookupByPath(this, EnsureRelativePath(path)!);
public EntityRef LookupChildOrThrow(EntityPath path)
=> World.LookupByPathOrThrow(this, EnsureRelativePath(path)!);
private static EntityPath? EnsureRelativePath(EntityPath? path)
{
if (path?.IsAbsolute == true) throw new ArgumentException(
$"Path '{path}' must not be absolute", nameof(path)); return path;
}
public EntityRef? Parent
=> GetTargets(World.ChildOf).FirstOrDefault();
public IEnumerable<EntityRef> GetChildren()
{
foreach (var iter in Iterator.FromTerm(World, new(World.ChildOf, this)))
for (var i = 0; i < iter.Count; i++)
yield return iter.Entity(i);
}
public override EntityRef Add(Id id) { ecs_add_id(World, this, id); return this; }
public override EntityRef Remove(Id id) { ecs_remove_id(World, this, id); return this; }
public override bool Has(Id id) => ecs_has_id(World, this, id);
public override T? GetOrNull<T>(Id id)
{
var ptr = ecs_get_id(World, this, id);
return (ptr != null) ? Unsafe.Read<T>(ptr) : null;
}
public override T? GetOrNull<T>(Id id, T _ = null!)
where T : class
{
var ptr = ecs_get_id(World, this, id);
return (T?)Unsafe.Read<ReferenceHandle>(ptr).Target;
}
public override T GetOrThrow<T>(Id id)
{
var ptr = ecs_get_id(World, this, id);
if (ptr == null) throw new Exception($"Component {typeof(T)} not found on {this}");
if (typeof(T).IsValueType) return Unsafe.Read<T>(ptr);
else return (T)Unsafe.Read<ReferenceHandle>(ptr).Target!;
}
public override ref T GetRefOrNull<T>(Id id)
{
var @ref = ecs_ref_init_id(World, this, id);
var ptr = ecs_ref_get_id(World, &@ref, id);
return ref (ptr != null) ? ref Unsafe.AsRef<T>(ptr)
: ref Unsafe.NullRef<T>();
}
public override ref T GetRefOrThrow<T>(Id id)
{
ref var ptr = ref GetRefOrNull<T>(id);
if (Unsafe.IsNullRef(ref ptr)) throw new Exception(
$"Component {typeof(T)} not found on {this}");
return ref ptr;
}
public override ref T GetMut<T>(Id id)
{
var ptr = ecs_get_mut_id(World, this, id);
// NOTE: Value is added if it doesn't exist on the entity.
return ref Unsafe.AsRef<T>(ptr);
}
public override void Modified<T>(Id id)
=> ecs_modified_id(World, this, id);
public override EntityRef Set<T>(Id id, in T value)
{
var size = (ulong)Unsafe.SizeOf<T>();
fixed (T* ptr = &value)
if (ecs_set_id(World, this, id, size, ptr).Data == 0)
throw new InvalidOperationException();
return this;
}
public override EntityRef Set<T>(Id id, T obj) where T : class
{
if (obj == null) throw new ArgumentNullException(nameof(obj));
var size = (ulong)sizeof(ReferenceHandle);
// Dispose this handle afterwards, since Flecs clones it.
using var handle = ReferenceHandle.Alloc(obj);
if (ecs_set_id(World, this, id, size, &handle).Data == 0)
throw new InvalidOperationException();
return this;
}
private EntityRef? GetTarget(Entity relation, int index)
=> CreateOrNull(World, new(ecs_get_target(World, this, relation, index)));
public IEnumerable<EntityRef> GetTargets(Entity relation)
{ var index = 0; while (GetTarget(relation, index++) is EntityRef target) yield return target; }
public IEnumerable<EntityRef> GetTargets(string symbol)
=> GetTargets(World.LookupBySymbolOrThrow(symbol));
public IEnumerable<EntityRef> GetTargets<T>()
=> GetTargets(World.LookupByTypeOrThrow(typeof(T)));
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)