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 sealed class EntityRef : EntityBase , IEquatable { public override Universe Universe { get; } public Entity Entity { get; } public uint ID => Entity.ID; public bool IsAlive => ecs_is_alive(Universe, this); public EntityType Type => new(Universe, ecs_get_type(Universe, this)); public string? Name { get => ecs_get_name(Universe, this).FlecsToString()!; set { using var alloc = TempAllocator.Use(); ecs_set_name(Universe, this, alloc.AllocateCString(value)); } } public string? Symbol { get => ecs_get_symbol(Universe, this).FlecsToString()!; set { using var alloc = TempAllocator.Use(); ecs_set_symbol(Universe, this, alloc.AllocateCString(value)); } } private EntityRef(Universe universe, Entity entity, bool throwOnInvalid) { if (throwOnInvalid && !ecs_is_valid(universe, entity)) throw new InvalidOperationException($"The entity {entity} is not valid"); Universe = universe; Entity = entity; } public EntityRef(Universe universe, Entity entity) : this(universe, entity, true) { } public static EntityRef? CreateOrNull(Universe universe, Entity entity) => ecs_is_valid(universe, entity) ? new(universe, entity) : null; public void Delete() => ecs_delete(Universe, this); public EntityBuilder NewChild(EntityPath? path = null) => Universe.New(EnsureRelativePath(path)).ChildOf(this); public EntityRef? Lookup(EntityPath path) => Universe.Lookup(this, EnsureRelativePath(path)!); public EntityRef LookupOrThrow(EntityPath path) => Universe.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(Universe.ChildOf); public IEnumerable GetChildren() { foreach (var iter in Iterator.FromTerm(Universe, new(Universe.ChildOf, this))) for (var i = 0; i < iter.Count; i++) yield return iter.Entity(i); } public override EntityRef Add(Identifier id) { ecs_add_id(Universe, this, id); return this; } public override EntityRef Remove(Identifier id) { ecs_remove_id(Universe, this, id); return this; } public override bool Has(Identifier id) => ecs_has_id(Universe, this, id); public override T Get() { var comp = Universe.LookupOrThrow(); var ptr = ecs_get_id(Universe, this, comp); 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? MaybeGet() { var comp = Universe.LookupOrThrow(); var ptr = ecs_get_id(Universe, this, comp); return (ptr != null) ? Unsafe.Read(ptr) : null; } public override T? MaybeGet(T _ = null!) where T : class { var comp = Universe.LookupOrThrow(); var ptr = ecs_get_id(Universe, this, comp); return (ptr != null) ? (T)((GCHandle)Unsafe.Read(ptr)).Target! : null; } public override ref T GetRefOrNull() { var comp = Universe.LookupOrThrow(); var @ref = ecs_ref_init_id(Universe, this, comp); var ptr = ecs_ref_get_id(Universe, &@ref, comp); return ref (ptr != null) ? ref Unsafe.AsRef(ptr) : ref Unsafe.NullRef(); } public override ref T GetRefOrThrow() { ref var ptr = ref GetRefOrNull(); if (Unsafe.IsNullRef(ref ptr)) throw new Exception( $"Component {typeof(T)} not found on {this}"); return ref ptr; } public override ref T GetMut() { var comp = Universe.LookupOrThrow(); var ptr = ecs_get_mut_id(Universe, this, comp); // NOTE: Value is added if it doesn't exist on the entity. return ref Unsafe.AsRef(ptr); } public override void Modified() => ecs_modified_id(Universe, this, Universe.LookupOrThrow()); public override EntityRef Set(in T value) { var comp = Universe.LookupOrThrow(); var size = (ulong)Unsafe.SizeOf(); fixed (T* ptr = &value) if (ecs_set_id(Universe, this, comp, size, ptr).Data == 0) throw new InvalidOperationException(); return this; } public override EntityRef Set(T obj) where T : class { var comp = Universe.LookupOrThrow(); var handle = (nint)GCHandle.Alloc(obj); // FIXME: Previous handle needs to be freed. if (ecs_set_id(Universe, this, comp, (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(Universe, new(ecs_get_target(Universe, this, relation, index))); public EntityRef? GetTarget(string symbol, int index = 0) => GetTarget(Universe.LookupSymbolOrThrow(symbol), index); public EntityRef? GetTarget(int index = 0) => GetTarget(Universe.LookupOrThrow(typeof(T)), index); public bool Equals(EntityRef? other) => (other is not null) && (Universe == other.Universe) && (Entity == other.Entity); public override bool Equals(object? obj) => Equals(obj as EntityRef); public override int GetHashCode() => HashCode.Combine(Universe, Entity); public override string? ToString() => ecs_entity_str(Universe, 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; }