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 , IEquatable { 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 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(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(ptr) : (T)((GCHandle)Unsafe.Read(ptr)).Target!; } public override T? GetOrNull(Identifier id) { var ptr = ecs_get_id(World, this, id); return (ptr != null) ? Unsafe.Read(ptr) : null; } public override T? GetOrNull(Identifier id, T _ = null!) where T : class { var ptr = ecs_get_id(World, this, id); return (ptr != null) ? (T)((GCHandle)Unsafe.Read(ptr)).Target! : null; } public override ref T GetRefOrNull(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(ptr) : ref Unsafe.NullRef(); } public override ref T GetRefOrThrow(Identifier id) { ref var ptr = ref GetRefOrNull(id); if (Unsafe.IsNullRef(ref ptr)) throw new Exception( $"Component {typeof(T)} not found on {this}"); return ref ptr; } public override ref T GetMut(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(ptr); } public override void Modified(Identifier id) => ecs_modified_id(World, this, id); public override EntityRef Set(Identifier id, in T value) { var size = (ulong)Unsafe.SizeOf(); fixed (T* ptr = &value) if (ecs_set_id(World, this, id, size, ptr).Data == 0) throw new InvalidOperationException(); return this; } public override EntityRef Set(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(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; }