Compare commits

...

4 Commits

  1. 8
      README.md
  2. 44
      src/gaemstone.ECS/EntityBase.cs
  3. 27
      src/gaemstone.ECS/EntityPath.cs
  4. 12
      src/gaemstone.ECS/EntityRef.cs
  5. 2
      src/gaemstone.ECS/Identifier.cs
  6. 6
      src/gaemstone.ECS/IdentifierRef.cs
  7. 11
      src/gaemstone.ECS/Iterator.cs
  8. 54
      src/gaemstone.ECS/World+Lookup.cs
  9. 6
      src/gaemstone.ECS/World.cs

@ -17,15 +17,16 @@ These classes have only recently been split from the main **gæmstone** project.
- Simple wrapper structs such as [Entity] and [Identifier].
- Classes with convenience functions like [EntityRef] and [EntityType].
- [EntityPath] uses a unix-like path, for example `/Game/Players/copygirl`.
- 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.
- [EntityPath] uses a unix-like path: `/Game/Players/copygirl`
[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
[Components]: ./src/gaemstone.ECS/Component.cs
[Iterators]: ./src/gaemstone.ECS/Iterator.cs
[Filters]: ./src/gaemstone.ECS/Filter.cs
@ -33,7 +34,6 @@ These classes have only recently been split from the main **gæmstone** project.
[Rules]: ./src/gaemstone.ECS/Rule.cs
[Systems]: ./src/gaemstone.ECS/System.cs
[Observers]: ./src/gaemstone.ECS/Observer.cs
[EntityPath]: ./src/gaemstone.ECS/EntityPath.cs
## Example
@ -45,7 +45,7 @@ var position = world
.Symbol("Position") // Set entity's symbol. (Used in query expression.)
.Build() // Actually create the entity in-world.
// Finally, create a component from this entity.
// The "Position" struct is defined at the bottom of this example.
// "Position" is defined at the bottom of this example.
.InitComponent<Position>();
// Create an "Entities" parent with two positioned entities inside.
@ -59,7 +59,7 @@ foreach (var child in entities.GetChildren()) {
pos = new(pos.X * 10, pos.Y * 10);
}
var onUpdate = world.LookupOrThrow("/flecs/pipeline/OnUpdate");
var onUpdate = world.LookupByPathOrThrow("/flecs/pipeline/OnUpdate");
// Create a system that will move all entities with
// the "Position" component downwards by 2 every frame.

@ -9,23 +9,23 @@ public abstract class EntityBase<TReturn>
public abstract TReturn Remove(Identifier id);
public abstract bool Has(Identifier id);
public TReturn Add(string symbol) => Add(World.LookupSymbolOrThrow(symbol));
public TReturn Add<T>() => Add(World.LookupOrThrow(typeof(T)));
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(Identifier.Pair(relation, target));
public TReturn Add<TRelation>(Entity target) => Add(World.LookupOrThrow<TRelation>(), target);
public TReturn Add<TRelation, TTarget>() => Add(World.LookupOrThrow<TRelation>(), World.LookupOrThrow<TTarget>());
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.LookupSymbolOrThrow(symbol));
public TReturn Remove<T>() => Remove(World.LookupOrThrow(typeof(T)));
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(Identifier.Pair(relation, target));
public TReturn Remove<TRelation>(Entity target) => Remove(World.LookupOrThrow<TRelation>(), target);
public TReturn Remove<TRelation, TTarget>() => Remove(World.LookupOrThrow<TRelation>(), World.LookupOrThrow<TTarget>());
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.LookupSymbolOrThrow(symbol));
public bool Has<T>() => Has(World.LookupOrThrow(typeof(T)));
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(Identifier.Pair(relation, target));
public bool Has<TRelation>(Entity target) => Has(World.LookupOrThrow<TRelation>(), target);
public bool Has<TRelation, TTarget>() => Has(World.LookupOrThrow<TRelation>(), World.LookupOrThrow<TTarget>());
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 Get<T>(Identifier id);
@ -36,24 +36,24 @@ public abstract class EntityBase<TReturn>
public abstract ref T GetRefOrThrow<T>(Identifier id) where T : unmanaged;
public abstract void Modified<T>(Identifier id);
public T Get<T>() => Get<T>(World.LookupOrThrow<T>());
public T? GetOrNull<T>() where T : unmanaged => GetOrNull<T>(World.LookupOrThrow<T>());
public T? GetOrNull<T>(T _ = null!) where T : class => GetOrNull<T>(World.LookupOrThrow<T>());
public ref T GetMut<T>() where T : unmanaged => ref GetMut<T>(World.LookupOrThrow<T>());
public ref T GetRefOrNull<T>() where T : unmanaged => ref GetRefOrNull<T>(World.LookupOrThrow<T>());
public ref T GetRefOrThrow<T>() where T : unmanaged => ref GetRefOrThrow<T>(World.LookupOrThrow<T>());
public void Modified<T>() => Modified<T>(World.LookupOrThrow<T>());
public T Get<T>() => Get<T>(World.LookupByTypeOrThrow<T>());
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 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>(Identifier id, in T value) where T : unmanaged;
public abstract TReturn Set<T>(Identifier id, T obj) where T : class;
public TReturn Set<T>(in T value) where T : unmanaged => Set(World.LookupOrThrow<T>(), value);
public TReturn Set<T>(T obj) where T : class => Set(World.LookupOrThrow<T>(), obj);
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.LookupOrThrow<TParent>());
public TReturn ChildOf<TParent>() => Add(World.ChildOf, World.LookupByTypeOrThrow<TParent>());
public TReturn Disable() => Add(World.Disabled);
public TReturn Enable() => Remove(World.Disabled);

@ -134,22 +134,29 @@ public class EntityPath
}
internal static unsafe Entity Lookup(World world, Entity parent, EntityPath path)
internal static unsafe Entity Lookup(
World world, Entity parent, EntityPath path, bool throwOnNotFound)
{
// If path is absolute, ignore parent and start at root.
if (path.IsAbsolute) parent = default;
// Otherwise, if no parent is specified, use the current scope.
else if (parent.IsNone) parent = new(ecs_get_scope(world));
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.
: parent; // Otherwise just use the specified parent.
var current = start;
foreach (var part in path)
fixed (byte* ptr = part.AsSpan()) {
// FIXME: This breaks when using large entity Ids.
parent = new(ecs_lookup_child(world, parent, ptr));
if (parent.IsNone || !ecs_is_alive(world, parent))
return Entity.None;
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 == parent) ? $"Child entity of '{startStr}' at '{path}' not found"
: start.IsSome ? $"Entity at scope '{startStr}' at '{path}' not found"
: $"Entity at '{path}' not found"
);
}
return parent;
return current;
}
/// <summary> Used by <see cref="EntityBuilder.Build"/>. </summary>

@ -50,10 +50,10 @@ public unsafe class EntityRef
public EntityBuilder NewChild(EntityPath? path = null)
=> World.New(EnsureRelativePath(path)).ChildOf(this);
public EntityRef? Lookup(EntityPath path)
=> World.Lookup(this, EnsureRelativePath(path)!);
public EntityRef LookupOrThrow(EntityPath path)
=> World.LookupOrThrow(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 must not be absolute", nameof(path)); return path; }
@ -142,9 +142,9 @@ public unsafe class EntityRef
public EntityRef? GetTarget(Entity relation, int index = 0)
=> CreateOrNull(World, new(ecs_get_target(World, this, relation, index)));
public EntityRef? GetTarget(string symbol, int index = 0)
=> GetTarget(World.LookupSymbolOrThrow(symbol), index);
=> GetTarget(World.LookupBySymbolOrThrow(symbol), index);
public EntityRef? GetTarget<T>(int index = 0)
=> GetTarget(World.LookupOrThrow(typeof(T)), index);
=> GetTarget(World.LookupByTypeOrThrow(typeof(T)), index);
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);

@ -26,6 +26,8 @@ public readonly struct Identifier
((relation.Value.Data << 32) & ECS_COMPONENT_MASK) |
( target.Value.Data & ECS_ENTITY_MASK )));
public EntityRef? AsEntity(World world)
=> new IdentifierRef(world, this).AsEntity();
public (EntityRef Relation, EntityRef Target)? AsPair(World world)
=> new IdentifierRef(world, this).AsPair();

@ -27,10 +27,10 @@ public unsafe class IdentifierRef
=> new(target.World, Identifier.Pair(relation, target));
public EntityRef? AsEntity()
=> (Flags == default) ? World.Lookup(new Entity(new() { Data = Id })) : null;
=> (Flags == default) ? World.LookupAlive(new Entity(new() { Data = Id })) : null;
public (EntityRef Relation, EntityRef Target)? AsPair()
=> IsPair && (World.Lookup(Id.RelationUnsafe) is EntityRef relation) &&
(World.Lookup(Id.TargetUnsafe ) is EntityRef target )
=> IsPair && (World.LookupAlive(Id.RelationUnsafe) is EntityRef relation) &&
(World.LookupAlive(Id.TargetUnsafe ) is EntityRef target )
? (relation, target) : null;
public bool Equals(IdentifierRef? other) => (other is not null) && (World == other.World) && (Id == other.Id);

@ -94,13 +94,14 @@ public unsafe class Iterator
return ecs_field_is_set(ptr, index);
}
// TODO: Potentially misleading, doesn't check the field's backing data type.
// The id might be "(Identifier, Name)", but its data type "Identifier".
public bool FieldIs<T>(int index)
=> FieldIs(index, World.LookupByType<T>());
public bool FieldIs(int index, Identifier id)
{
fixed (ecs_iter_t* ptr = &Value) {
var id = ecs_field_id(ptr, index);
var comp = World.LookupOrThrow<T>();
return id == comp.Entity.Value.Data;
}
fixed (ecs_iter_t* ptr = &Value)
return ecs_field_id(ptr, index) == id.Value;
}
public override string ToString()

@ -20,38 +20,40 @@ public unsafe partial class World
$"Lookup for {type} does not exist"); }
private EntityRef? CreateOrNull(Entity entity)
=> EntityRef.CreateOrNull(this, entity);
public EntityRef? LookupByType<T>()
=> LookupByType(typeof(T));
public EntityRef? LookupByType(Type type)
=> LookupAlive(_lookupByType.GetValueOrDefault(type));
public EntityRef LookupByTypeOrThrow<T>()
=> LookupByTypeOrThrow(typeof(T));
public EntityRef LookupByTypeOrThrow(Type type)
=> LookupByType(type) ?? throw new EntityNotFoundException(
$"Entity of type {type} not found");
public EntityRef? Lookup<T>()
=> Lookup(typeof(T));
public EntityRef? Lookup(Type type)
=> Lookup(_lookupByType.GetValueOrDefault(type));
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");
public EntityRef? Lookup(Entity value)
=> CreateOrNull(new(ecs_get_alive(this, value)));
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));
public EntityRef? Lookup(EntityPath path)
=> Lookup(default, path);
public EntityRef? Lookup(Entity parent, EntityPath path)
=> CreateOrNull(EntityPath.Lookup(this, parent, path));
public EntityRef? LookupSymbol(string symbol)
public EntityRef? LookupBySymbol(string symbol)
{
using var alloc = TempAllocator.Use();
return CreateOrNull(new(ecs_lookup_symbol(this, alloc.AllocateCString(symbol), false)));
var entity = ecs_lookup_symbol(this, alloc.AllocateCString(symbol), false);
return EntityRef.CreateOrNull(this, new(entity));
}
public EntityRef LookupOrThrow<T>() => LookupOrThrow(typeof(T));
public EntityRef LookupOrThrow(Type type) => Lookup(type)
?? throw new EntityNotFoundException($"Entity of type {type} not found");
public EntityRef LookupOrThrow(Entity entity) => Lookup(entity)
?? throw new EntityNotFoundException($"Entity {entity} not alive");
public EntityRef LookupOrThrow(EntityPath path) => Lookup(default, path)
?? throw new EntityNotFoundException($"Entity '{path}' not found");
public EntityRef LookupOrThrow(Entity parent, EntityPath path) => Lookup(parent, path)
?? throw new EntityNotFoundException($"Child entity of {parent} '{path}' not found");
public EntityRef LookupSymbolOrThrow(string symbol) => LookupSymbol(symbol)
?? throw new EntityNotFoundException($"Entity with symbol '{symbol}' not found");
public EntityRef LookupBySymbolOrThrow(string symbol)
=> LookupBySymbol(symbol) ?? throw new EntityNotFoundException(
$"Entity with symbol '{symbol}' not found");
public class EntityNotFoundException : Exception

@ -19,9 +19,9 @@ public unsafe partial class World
{
Handle = ecs_init_w_args(args.Length, null);
ChildOf = LookupOrThrow("/flecs/core/ChildOf");
Disabled = LookupOrThrow("/flecs/core/Disabled");
DependsOn = LookupOrThrow("/flecs/core/DependsOn");
ChildOf = LookupByPathOrThrow("/flecs/core/ChildOf");
Disabled = LookupByPathOrThrow("/flecs/core/Disabled");
DependsOn = LookupByPathOrThrow("/flecs/core/DependsOn");
}
public void Dispose()

Loading…
Cancel
Save