using System; using System.Collections.Generic; using System.Linq; using gaemstone.ECS.Internal; using static flecs_hub.flecs; namespace gaemstone.ECS; public unsafe readonly partial struct Entity : IEquatable> { public static readonly Entity None = default; public readonly World World; public readonly Entity Value; public uint NumericId => Value.NumericId; public bool IsNone => Value.IsNone; public bool IsSome => Value.IsSome; public bool IsValid => EntityAccess.IsValid(World, this); public bool IsAlive => IsSome && EntityAccess.IsAlive(World, this); public string? Name { get => EntityAccess.GetName(World, this); set => EntityAccess.SetName(World, this, value); } public string? Symbol { get => EntityAccess.GetSymbol(World, this); set => EntityAccess.SetSymbol(World, this, value); } public EntityPath Path => EntityPath.From(World, this); public EntityType Type => new(World, ecs_get_type(World, this)); public Entity? Parent => GetOrNull(World, GetTargets(FlecsBuiltIn.ChildOf).FirstOrDefault()); public IEnumerable> Children => World.Term(new(FlecsBuiltIn.ChildOf, this)).GetAllEntities(); private Entity(World world, Entity value) { World = world; Value = value; } public static Entity GetOrInvalid(World world, Entity value) => new(world, value); public static Entity? GetOrNull(World world, Entity value) => new Entity(world, value).ValidOrNull(); public static Entity GetOrThrow(World world, Entity value) => new Entity(world, value).ValidOrThrow(); public Entity? ValidOrNull() => IsValid ? this : null; public Entity ValidOrThrow() => IsValid ? this : throw new InvalidOperationException($"The entity {this} is not valid"); public Entity? AliveOrNull() => IsAlive ? this : null; public Entity AliveOrThrow() => IsAlive ? this : throw new InvalidOperationException($"The entity {this} is not alive"); public void Delete() => ecs_delete(World, this); public Entity CreateLookup() { ref var lookup = ref Lookup.Entity.Value; if (lookup == this) { /* Don't throw if lookup already has the same entity set. */ } else if (lookup.IsSome) throw new InvalidOperationException( $"The lookup for type {typeof(T)} in context {typeof(TContext)} is already in use by {lookup}"); lookup = this; return this; } public EntityBuilder NewChild(EntityPath? path = null) => World.New(path?.ThrowIfAbsolute(), this); public Entity? LookupChildOrNull(EntityPath path) => World.LookupPathOrNull(path.ThrowIfAbsolute(), this); public Entity LookupChildOrThrow(EntityPath path) => World.LookupPathOrThrow(path.ThrowIfAbsolute()!, this); public Entity ChildOf(Entity parent) => Add(FlecsBuiltIn.ChildOf, parent); public bool IsDisabled => Has(FlecsBuiltIn.Disabled); public bool IsEnabled => !Has(FlecsBuiltIn.Disabled); public Entity Disable() => Add(FlecsBuiltIn.Disabled); public Entity Enable() => Remove(FlecsBuiltIn.Disabled); public Entity Add(Id id) { EntityAccess.Add(World, this, id); return this; } public Entity Add(string symbol) => Add(World.LookupSymbolOrThrow(symbol)); public Entity Add(Entity relation, Entity target) => Add(Id.Pair(relation, target)); public Entity Add() => Add(World.Entity()); public Entity Add(Entity target) => Add(World.Entity(), target); public Entity Add() => Add(World.Entity(), World.Entity()); public Entity Remove(Id id) { EntityAccess.Remove(World, this, id); return this; } public Entity Remove(string symbol) => Remove(World.LookupSymbolOrThrow(symbol)); public Entity Remove(Entity relation, Entity target) => Remove(Id.Pair(relation, target)); public Entity Remove() => Remove(World.Entity()); public Entity Remove(Entity target) => Remove(World.Entity(), target); public Entity Remove() => Remove(World.Entity(), World.Entity()); public bool Has(Id id) => EntityAccess.Has(World, this, id); public bool Has(string symbol) => Has(World.LookupSymbolOrThrow(symbol)); public bool Has(Entity relation, Entity target) => Has(Id.Pair(relation, target)); public bool Has() => Has(World.Entity()); public bool Has(Entity target) => Has(World.Entity(), target); public bool Has() => Has(World.Entity(), World.Entity()); public T? GetOrNull(Id id) where T : unmanaged => EntityAccess.GetOrNull(World, this, id); public T? GetOrNull(Id id, T _ = null!) where T : class => EntityAccess.GetOrNull(World, this, id); public T GetOrThrow(Id id) => EntityAccess.GetOrThrow(World, this, id); public ref T GetMut(Id id) where T : unmanaged => ref EntityAccess.GetMut(World, this, id); public ref T GetRefOrNull(Id id) where T : unmanaged => ref EntityAccess.GetRefOrNull(World, this, id); public ref T GetRefOrThrow(Id id) where T : unmanaged => ref EntityAccess.GetRefOrThrow(World, this, id); public Entity Modified(Id id) { EntityAccess.Modified(World, this, id); return this; } public Entity Set(Id id, in T value) where T : unmanaged { EntityAccess.Set(World, this, id, value); return this; } public Entity Set(Id id, T value) where T : class { EntityAccess.Set(World, this, id, value); return this; } public T? GetOrNull() where T : unmanaged => GetOrNull(World.Entity()); public T? GetOrNull(T _ = null!) where T : class => GetOrNull(World.Entity()); public T GetOrThrow() => GetOrThrow(World.Entity()); public ref T GetMut() where T : unmanaged => ref GetMut(World.Entity()); public ref T GetRefOrNull() where T : unmanaged => ref GetRefOrNull(World.Entity()); public ref T GetRefOrThrow() where T : unmanaged => ref GetRefOrThrow(World.Entity()); public Entity Modified() => Modified(World.Entity()); public Entity Set(in T value) where T : unmanaged => Set(World.Entity(), value); public Entity Set(T value) where T : class => Set(World.Entity(), value); public IEnumerable> GetTargets(Entity relation) { foreach (var entity in EntityAccess.GetTargets(World, this, relation)) yield return new(World, entity); } public IEnumerable> GetTargets(string symbol) => GetTargets(World.LookupSymbolOrThrow(symbol)); public IEnumerable> GetTargets() => GetTargets(World.Entity()); public bool Equals(Entity other) { #if DEBUG // In DEBUG mode, we additionally check if the worlds the two compared // values are from the same world. This accounts for the world being a // stage, hence why it might not be the cheapest operation. if (World != other.World) throw new ArgumentException( "The specified values are not from the same world"); #endif return Value == other.Value; } public override bool Equals(object? obj) => (obj is Entity other) && Equals(other); public override int GetHashCode() => Value.GetHashCode(); public override string? ToString() => Value.ToString(); public static bool operator ==(Entity left, Entity right) => left.Equals(right); public static bool operator !=(Entity left, Entity right) => !left.Equals(right); public static implicit operator Entity (Entity entity) => entity.Value; public static implicit operator ecs_entity_t(Entity entity) => entity.Value.Value; public static implicit operator Id(Entity entity) => Id.GetUnsafe(entity.World, entity); public static implicit operator Id (Entity entity) => new(entity.Value.Value.Data); public static implicit operator ecs_id_t (Entity entity) => entity.Value.Value.Data; public static implicit operator Term (Entity entity) => new(entity.Value); public static implicit operator TermId(Entity entity) => new(entity.Value); }