Alternative managed wrapper around flecs-cs bindings for using the ECS framework Flecs in modern .NET.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

162 lines
5.6 KiB

using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
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(EnsureRelativePath(path)).ChildOf(this);
public EntityRef? Lookup(EntityPath path)
=> World.Lookup(this, EnsureRelativePath(path)!);
public EntityRef LookupOrThrow(EntityPath path)
=> World.LookupOrThrow(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; }
public EntityRef? Parent
=> GetTarget(World.ChildOf);
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(Identifier id) { ecs_add_id(World, this, id); return this; }
public override EntityRef Remove(Identifier id) { ecs_remove_id(World, this, id); return this; }
public override bool Has(Identifier id) => ecs_has_id(World, this, id);
public override T Get<T>(Identifier id)
{
var ptr = ecs_get_id(World, this, id);
if (ptr == null) throw new Exception($"Component {typeof(T)} not found on {this}");
return typeof(T).IsValueType ? Unsafe.Read<T>(ptr)
: (T)((GCHandle)Unsafe.Read<nint>(ptr)).Target!;
}
public override T? GetOrNull<T>(Identifier id)
{
var ptr = ecs_get_id(World, this, id);
return (ptr != null) ? Unsafe.Read<T>(ptr) : null;
}
public override T? GetOrNull<T>(Identifier id, T _ = null!)
where T : class
{
var ptr = ecs_get_id(World, this, id);
return (ptr != null) ? (T)((GCHandle)Unsafe.Read<nint>(ptr)).Target! : null;
}
public override ref T GetRefOrNull<T>(Identifier 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>(Identifier 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>(Identifier 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>(Identifier id)
=> ecs_modified_id(World, this, id);
public override EntityRef Set<T>(Identifier 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>(Identifier id, T obj) where T : class
{
var handle = (nint)GCHandle.Alloc(obj);
// FIXME: Previous handle needs to be freed.
if (ecs_set_id(World, this, id, (ulong)sizeof(nint), &handle).Data == 0)
throw new InvalidOperationException();
// FIXME: Handle needs to be freed when component is removed!
return this;
}
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);
public EntityRef? GetTarget<T>(int index = 0)
=> GetTarget(World.LookupOrThrow(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);
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 Identifier(EntityRef? e) => new(e?.Entity.Value.Data ?? default);
public static implicit operator ecs_id_t(EntityRef? e) => e?.Entity.Value.Data ?? default;
}