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.
169 lines
5.7 KiB
169 lines
5.7 KiB
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(); |
|
|
|
// 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 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 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; |
|
}
|
|
|