using Ghost.Entities.Query; using Ghost.Entities.Utilities; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; namespace Ghost.Entities; [SkipLocalsInit] public struct Entity : IEquatable, IComparable { public const int WORLD_INDEX_BITS = 4; public const int GENERATION_BITS = 8; public const int INDEX_BITS = sizeof(EntityID) * 8 - WORLD_INDEX_BITS - GENERATION_BITS; private const int _WORLD_INDEX_MASK = (1 << WORLD_INDEX_BITS) - 1; private const int _GENERATION_MASK = (1 << GENERATION_BITS) - 1; private const int _INDEX_MASK = (1 << INDEX_BITS) - 1; public const EntityID INVALID_ID = -1; private EntityID _id; public readonly bool IsValid { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => ID != INVALID_ID; } public readonly EntityID ID { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => _id & _INDEX_MASK; } public readonly GenerationID Generation { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => (GenerationID)(_id >> INDEX_BITS & _GENERATION_MASK); } public readonly WorldID WorldID { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => (WorldID)(_id >> (INDEX_BITS + GENERATION_BITS) & _WORLD_INDEX_MASK); } public static Entity Invalid { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new(INVALID_ID, 0, 0); } internal Entity(EntityID id, GenerationID generation, WorldID worldID) { _id = worldID << (INDEX_BITS + GENERATION_BITS) | generation << INDEX_BITS | id; } public void IncrementGeneration() { var generation = Generation + 1; if (generation >= _GENERATION_MASK) { throw new InvalidOperationException("Generation overflow"); } _id = _id & ~(_GENERATION_MASK << INDEX_BITS) | generation << INDEX_BITS; } public readonly bool Equals(Entity other) { return _id == other._id; } public readonly int CompareTo(Entity other) { return _id.CompareTo(other._id); } public override readonly bool Equals(object? obj) { return obj is Entity other && Equals(other); } public override readonly int GetHashCode() { return _id.GetHashCode(); } public static bool operator ==(Entity left, Entity right) { return left.Equals(right); } public static bool operator !=(Entity left, Entity right) { return !(left == right); } public override readonly string ToString() { return $"Entity {{ Index: {ID}, Generation: {Generation}, WorldIndex: {WorldID} }}"; } } public class EntityManager : IDisposable { private readonly List _entities; private readonly Queue _freeEntitySlots; private readonly World _world; public int EntityCount => _entities.Count; public ReadOnlySpan Entities => CollectionsMarshal.AsSpan(_entities); public event Action? OnComponentAdded; public event Action? OnComponentRemoved; public event Action? OnEntityCreated; public event Action? OnEntityRemoved; internal EntityManager(World world, int initialCapacity) { _entities = new(initialCapacity); _freeEntitySlots = new(initialCapacity); _world = world; } /// /// Adds a new to the world. /// /// The created . public Entity CreateEntity() { Entity entity; if (_freeEntitySlots.TryDequeue(out var id)) { entity = _entities[id]; } else { id = _entities.Count; entity = new Entity(id, 0, _world.ID); _entities.Add(entity); } OnEntityCreated?.Invoke(entity); return entity; } /// /// Removes the specified from the world. /// /// public void RemoveEntity(ref Entity entity) { if (entity.ID >= _entities.Count || _entities[entity.ID].Generation != entity.Generation) { return; } _world.ComponentStorage.Remove(entity); var slot = _entities[entity.ID]; slot.IncrementGeneration(); _entities[entity.ID] = slot; _freeEntitySlots.Enqueue(entity.ID); OnEntityRemoved?.Invoke(entity.ID); entity = Entity.Invalid; } /// /// Checks if the given is valid and belongs to this . /// /// The entity to check. /// True if the entity is valid and belongs to this world; otherwise, false. [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool HasEntity(Entity entity) { if (!entity.IsValid || entity.WorldID != _world.ID || entity.ID >= _entities.Count) { return false; } return _entities[entity.ID].Generation == entity.Generation; } /// /// Adds a component of type to the given . /// /// The type of the component to set. /// The entity for which the component is to be add. /// The component value to add. [MethodImpl(MethodImplOptions.AggressiveInlining)] public void AddComponent(Entity entity, T component) where T : struct, IComponentData { _world.ComponentStorage.GetOrCreateComponentPool().Add(entity, component); _world.ComponentStorage.GetOrCreateMask(TypeHandle.Value).SetBit(entity.ID); OnComponentAdded?.Invoke(entity, typeof(T)); } /// /// Removes a component of type from the given . /// /// The type of the component to remove. /// The entity for which the component is to be remove. [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool RemoveComponent(Entity entity) where T : struct, IComponentData { if (!_world.ComponentStorage.TryGetPool(out var pool) || !pool.Has(entity)) { return false; } if (!pool.Remove(entity)) { return false; } _world.ComponentStorage.GetOrCreateMask(TypeHandle.Value).ClearBit(entity.ID); OnComponentRemoved?.Invoke(entity, typeof(T)); return true; } /// /// Sets a component of type for the given . /// /// The type of the component to set. /// The entity for which the component is to be set. /// The component value to set. [MethodImpl(MethodImplOptions.AggressiveInlining)] public void SetComponent(Entity entity, T component) where T : struct, IComponentData { _world.ComponentStorage.GetOrCreateComponentPool().Set(entity, component); } /// /// Checks if the given has a component of the specified type. /// /// The entity to check. /// The handle of the component type. /// True if the entity has the component; otherwise, false. [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool HasComponent(Entity entity, nint typeHandle) { return _world.ComponentStorage.TryGetMask(typeHandle, out var bitSet) && bitSet.IsSet(entity.ID); } /// /// Retrieves a reference to a component of type associated with the given . /// /// The type of the component to retrieve. /// The entity whose component is to be retrieved. /// A to the component, or a null reference if the component does not exist. [MethodImpl(MethodImplOptions.AggressiveInlining)] public Ref GetComponent(Entity entity) where T : struct, IComponentData { if (_world.ComponentStorage.TryGetPool(out var pool) && pool.Has(entity)) { return new Ref(ref pool.GetRef(entity)); } else { return new Ref(ref Unsafe.NullRef(), false); } } /// /// Adds a script of type to the given . /// /// The type of the script to add. /// The entity to which the script is to be added. [MethodImpl(MethodImplOptions.AggressiveInlining)] public void AddScript(Entity entity) where T : ScriptComponent, new() { _world.ComponentStorage.ScriptComponentPool.Add(entity, new T()); OnComponentAdded?.Invoke(entity, typeof(ScriptComponent)); } /// /// Adds a script of the specified type to the given . /// /// The entity to which the script is to be added. /// The type of the script to add. /// Thrown if the specified type does not inherit from . /// Thrown if the script instance could not be created. [MethodImpl(MethodImplOptions.AggressiveInlining)] public void AddScript(Entity entity, Type type) { if (!typeof(ScriptComponent).IsAssignableFrom(type)) { throw new ArgumentException($"Type {type} must inherit from ScriptComponent.", nameof(type)); } var instance = (ScriptComponent?)Activator.CreateInstance(type) ?? throw new InvalidOperationException($"Failed to create instance of {type}."); _world.ComponentStorage.ScriptComponentPool.Add(entity, instance); OnComponentAdded?.Invoke(entity, typeof(ScriptComponent)); } public bool RemoveScript(Entity entity) where T : ScriptComponent { if (!_world.ComponentStorage.ScriptComponentPool.Remove(entity)) { return false; } OnComponentRemoved?.Invoke(entity, typeof(ScriptComponent)); return true; } public bool RemoveScriptAt(Entity entity, int index) { if (!_world.ComponentStorage.ScriptComponentPool.RemoveAt(entity, index)) { return false; } OnComponentRemoved?.Invoke(entity, typeof(ScriptComponent)); return true; } /// /// Retrieves the first script of type associated with the given . /// /// The type of the script to retrieve. /// The entity whose script is to be retrieved. /// The script of type , or null if no such script exists. public T? GetScript(Entity entity) where T : ScriptComponent { return (T?)_world.ComponentStorage.ScriptComponentPool.Get(entity)? .FirstOrDefault(script => script is T tScript); } /// /// Retrieves all scripts of type associated with the given . /// /// The type of the scripts to retrieve. /// The entity whose scripts are to be retrieved. /// An enumerable of scripts of type . public IEnumerable GetScripts(Entity entity) where T : ScriptComponent { return (IEnumerable?)_world.ComponentStorage.ScriptComponentPool.Get(entity)?.Where(script => script is T tScript) ?? Enumerable.Empty(); } public void Dispose() { _entities.Clear(); _freeEntitySlots.Clear(); } }