Changed project name

This commit is contained in:
2025-12-04 16:55:26 +09:00
parent 3bbf485fce
commit 93bc8e55a3
54 changed files with 1819 additions and 613 deletions

417
Ghost.Entities/Archetype.cs Normal file
View File

@@ -0,0 +1,417 @@
using Ghost.Core;
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections;
using Misaki.HighPerformance.LowLevel.Utilities;
using System.Runtime.CompilerServices;
namespace Ghost.Entities;
internal unsafe struct Chuck : IDisposable
{
public const int CHUNK_SIZE = 16384; // 16 KB
private UnsafeArray<byte> _data;
private int _count;
private int _capacity;
public int Count
{
get => _count;
set => _count = value;
}
public int Capacity => _capacity;
public Chuck(int size, int capacity)
{
_data = new UnsafeArray<byte>(size, Allocator.Persistent);
_capacity = capacity;
_count = 0;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public byte* GetUnsafePtr()
{
return (byte*)_data.GetUnsafePtr();
}
public void Dispose()
{
_data.Dispose();
}
}
internal struct Edge
{
public Identifier<IComponent> componentID;
public int targetArchetype; // can't use Identifier<Archetype> because cycle causer
}
internal struct ComponentMemoryLayout
{
public int offset;
public int size;
public Identifier<IComponent> componentID;
}
internal unsafe struct Archetype : IIdentifierType, IDisposable
{
private readonly Identifier<Archetype> _id;
private readonly Identifier<World> _worldID;
private UnsafeBitSet _signature;
private UnsafeList<Chuck> _chunks;
private UnsafeArray<ComponentMemoryLayout> _layouts;
private UnsafeArray<int> _componentIDToOffset;
// TODO: Is hash map better?
private UnsafeList<Edge> _edgesAdd;
private UnsafeList<Edge> _edgesRemove;
private int _hash;
private int _entityCapacity;
private int _maxComponentID;
private int _entityIdsOffset;
public Identifier<Archetype> ID => _id;
public UnsafeBitSet Signature => _signature;
public UnsafeList<Chuck> Chunks => _chunks;
public UnsafeArray<ComponentMemoryLayout> Layouts => _layouts;
public int EntityCapacity => _entityCapacity;
public int ChunkCount => _chunks.Count;
public Archetype(Identifier<Archetype> id, Identifier<World> worldID, ReadOnlySpan<Identifier<IComponent>> componentIds)
{
_id = id;
_worldID = worldID;
if (componentIds.IsEmpty)
{
_signature = new UnsafeBitSet(1, Allocator.Persistent, AllocationOption.Clear);
_chunks = new UnsafeList<Chuck>(4, Allocator.Persistent);
_edgesAdd = new UnsafeList<Edge>(4, Allocator.Persistent);
_edgesRemove = new UnsafeList<Edge>(4, Allocator.Persistent);
_signature.ClearAll();
_entityCapacity = Chuck.CHUNK_SIZE / sizeof(Entity);
return;
}
var highestComponentID = 0;
for (var i = 0; i < componentIds.Length; i++)
{
if (componentIds[i] > highestComponentID)
{
highestComponentID = componentIds[i];
}
}
_signature = new UnsafeBitSet(highestComponentID + 1, Allocator.Persistent, AllocationOption.Clear);
_chunks = new UnsafeList<Chuck>(4, Allocator.Persistent);
_edgesAdd = new UnsafeList<Edge>(4, Allocator.Persistent);
_edgesRemove = new UnsafeList<Edge>(4, Allocator.Persistent);
_hash = _signature.GetHashCode();
var pComponents = stackalloc ComponentInfo[componentIds.Length];
for (var i = 0; i < componentIds.Length; i++)
{
_signature.SetBit(componentIds[i]);
pComponents[i] = ComponentRegister.GetComponentInfo(componentIds[i]);
}
CalculateLayout(new Span<ComponentInfo>(pComponents, componentIds.Length));
}
private void CalculateLayout(Span<ComponentInfo> components)
{
var entitySize = sizeof(Entity);
var entityAlign = (int)MemoryUtility.AlignOf<Entity>();
// Calculate total size per entity to get an initial capacity estimate
var bytesPerEntity = entitySize;
var maxComponentID = 0;
for (var i = 0; i < components.Length; i++)
{
var comp = components[i];
bytesPerEntity += comp.size;
if (comp.id > maxComponentID)
{
maxComponentID = comp.id;
}
}
_maxComponentID = maxComponentID;
_entityCapacity = Chuck.CHUNK_SIZE / bytesPerEntity;
_layouts = new UnsafeArray<ComponentMemoryLayout>(components.Length, Allocator.Persistent);
_componentIDToOffset = new UnsafeArray<int>(_maxComponentID + 1, Allocator.Persistent);
_componentIDToOffset.AsSpan().Fill(-1);
components.Sort((a, b) => b.alignment.CompareTo(a.alignment));
var tempOffsets = stackalloc int[components.Length];
while (_entityCapacity > 0)
{
var currentOffset = 0;
var fits = true;
currentOffset = (currentOffset + entityAlign - 1) & ~(entityAlign - 1);
_entityIdsOffset = currentOffset;
currentOffset += _entityCapacity * entitySize;
for (var i = 0; i < components.Length; i++)
{
var size = components[i].size;
var align = components[i].alignment;
currentOffset = (currentOffset + align - 1) & ~(align - 1);
tempOffsets[i] = currentOffset;
currentOffset += _entityCapacity * size;
if (currentOffset > Chuck.CHUNK_SIZE)
{
fits = false;
break;
}
}
if (fits)
{
for (var i = 0; i < components.Length; i++)
{
_layouts[i] = new ComponentMemoryLayout
{
offset = tempOffsets[i],
size = components[i].size,
componentID = components[i].id
};
_componentIDToOffset[components[i].id] = tempOffsets[i];
}
return;
}
_entityCapacity--;
}
}
public void AllocateEntity(out int chunkIndex, out int rowIndex)
{
for (var i = 0; i < _chunks.Count; i++)
{
var chunk = _chunks[i];
if (chunk.Count < _entityCapacity)
{
rowIndex = chunk.Count;
chunk.Count++;
chunkIndex = i;
return;
}
}
// Need to allocate a new chunk
var newChunk = new Chuck(Chuck.CHUNK_SIZE, _entityCapacity);
rowIndex = 0;
newChunk.Count++;
chunkIndex = _chunks.Count;
_chunks.Add(newChunk);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetEntity(int chunkIndex, int rowIndex, Entity entity)
{
var chunk = _chunks[chunkIndex];
var chunkBase = chunk.GetUnsafePtr();
var pEntity = chunkBase + _entityIdsOffset + (sizeof(Entity) * rowIndex);
MemoryUtility.MemCpy(&entity, pEntity, (nuint)sizeof(Entity));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetComponentData(int chunkIndex, int rowIndex, Identifier<IComponent> componentID, void* pComponent)
{
var offset = _componentIDToOffset[componentID];
var chunk = _chunks[chunkIndex];
var chunkBase = chunk.GetUnsafePtr();
var size = ComponentRegister.GetComponentInfo(componentID).size;
var dst = chunkBase + offset + (size * rowIndex);
MemoryUtility.MemCpy(pComponent, dst, (nuint)size);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ref Chuck GetChunkReference(int index)
{
return ref _chunks[index];
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int GetOffset(int componentId)
{
if (componentId >= _componentIDToOffset.Count)
{
return -1;
}
return _componentIDToOffset[componentId];
}
public ResultStatus RemoveEntity(int chunkIndex, int rowIndex)
{
if (chunkIndex < 0 || chunkIndex >= _chunks.Count)
{
return ResultStatus.InvalidArgument;
}
ref var chunk = ref _chunks[chunkIndex];
var lastIndex = chunk.Count - 1;
// If we are NOT removing the very last entity, we must swap.
if (rowIndex != lastIndex)
{
var chunkBase = chunk.GetUnsafePtr();
var pLastEntity = chunkBase + _entityIdsOffset + (sizeof(Entity) * lastIndex);
var pRowEntity = chunkBase + _entityIdsOffset + (sizeof(Entity) * rowIndex);
var wroldResult = World.GetWorld(_worldID);
if (wroldResult.Status != ResultStatus.Success)
{
return wroldResult.Status;
}
var result = wroldResult.Value.EntityManager.UpdateEntityLocation(*(Entity*)pLastEntity, _id, chunkIndex, rowIndex);
if (result != ResultStatus.Success)
{
return result;
}
// Only operate the swap back after the update is succeed.
MemoryUtility.MemCpy(pLastEntity, pRowEntity, (nuint)sizeof(Entity));
for (var i = 0; i <= _layouts.Count; i++)
{
var layout = _layouts[i];
var pRow = chunk.GetUnsafePtr() + layout.offset + (layout.size * rowIndex);
var pLast = chunk.GetUnsafePtr() + layout.offset + (layout.size * lastIndex);
MemoryUtility.MemCpy(pLast, pRow, (nuint)layout.size);
}
}
chunk.Count--;
return ResultStatus.Success;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool HasComponent(Identifier<IComponent> componentID)
{
return _signature.IsSet(componentID);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddEdgeAdd(Identifier<IComponent> componentID, Identifier<Archetype> targetArchetype)
{
_edgesAdd.Add(new Edge
{
componentID = componentID,
targetArchetype = targetArchetype
});
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Identifier<Archetype> GetEdgeAdd(Identifier<IComponent> componentID)
{
for (var i = 0; i < _edgesAdd.Count; i++)
{
var edge = _edgesAdd[i];
if (edge.componentID == componentID)
{
return edge.targetArchetype;
}
}
return Identifier<Archetype>.Invalid;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddEdgeRemove(Identifier<IComponent> componentID, Identifier<Archetype> targetArchetype)
{
_edgesRemove.Add(new Edge
{
componentID = componentID,
targetArchetype = targetArchetype
});
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Identifier<Archetype> GetEdgeRemove(Identifier<IComponent> componentID)
{
for (var i = 0; i < _edgesRemove.Count; i++)
{
var edge = _edgesRemove[i];
if (edge.componentID == componentID)
{
return edge.targetArchetype;
}
}
return Identifier<Archetype>.Invalid;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Span<T> GetComponentArray<T>(int chunkIndex)
where T : unmanaged, IComponent
{
var id = ComponentTypeID<T>.value;
if (id >= _componentIDToOffset.Count)
{
return default;
}
var offset = _componentIDToOffset[id];
if (offset == -1)
{
return default;
}
var chunk = _chunks[chunkIndex];
return new Span<T>((T*)((byte*)chunk.GetUnsafePtr() + offset), chunk.Count);
}
public override int GetHashCode()
{
return _hash;
}
public void Dispose()
{
if (_chunks.IsCreated)
{
foreach (ref var chunk in _chunks)
{
chunk.Dispose();
}
}
_signature.Dispose();
_chunks.Dispose();
_componentIDToOffset.Dispose();
_layouts.Dispose();
_edgesAdd.Dispose();
_edgesRemove.Dispose();
}
}

View File

@@ -1,12 +1,3 @@
global using EntityID = System.Int32;
global using GenerationID = System.UInt16;
global using WorldID = System.UInt16;
global using GenerationID = System.Int32;
using Ghost.Core.Attributes;
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("Ghost.Engine")]
[assembly: InternalsVisibleTo("Ghost.Editor.Core")]
[assembly: InternalsVisibleTo("Ghost.Entities.Test")]
[assembly: EngineAssembly]

View File

@@ -0,0 +1,96 @@
using Ghost.Core;
using Misaki.HighPerformance.LowLevel.Collections;
using Misaki.HighPerformance.LowLevel.Utilities;
namespace Ghost.Entities;
public interface IComponent : IIdentifierType
{
}
public struct ComponentInfo
{
// public FixedText64 stableName; // Do we actually need this?
public int size;
public int alignment;
public Identifier<IComponent> id;
}
public static unsafe class ComponentTypeID<T>
where T : unmanaged, IComponent
{
public static readonly Identifier<IComponent> value = ComponentRegister.GetOrRegisterComponent<T>();
}
internal static class ComponentRegister
{
private static int s_nextComponentTypeID = 0;
private static Dictionary<IntPtr, Identifier<IComponent>> s_typeHandleToID = new();
private static List<ComponentInfo> s_registeredComponents = new();
private static Dictionary<string, Identifier<IComponent>> s_nameToRuntimeID = new();
public unsafe static Identifier<IComponent> GetOrRegisterComponent<T>()
where T : unmanaged, IComponent
{
var typeHandle = typeof(T).TypeHandle.Value;
lock (s_registeredComponents)
{
if (s_typeHandleToID.TryGetValue(typeHandle, out var existingID))
{
return existingID;
}
var newID = new Identifier<IComponent>(s_nextComponentTypeID);
s_nextComponentTypeID++;
var stableName = typeof(T).FullName ?? typeof(T).Name;
var info = new ComponentInfo
{
// stableName = new FixedText64(stableName),
size = sizeof(T),
alignment = (int)MemoryUtility.AlignOf<T>(),
id = newID,
};
while (s_registeredComponents.Count <= newID.value) s_registeredComponents.Add(default);
s_registeredComponents[newID.value] = info;
s_typeHandleToID[typeHandle] = newID;
s_nameToRuntimeID[stableName] = newID;
return newID;
}
}
public static ComponentInfo GetComponentInfo(Identifier<IComponent> typeId)
{
return s_registeredComponents[typeId];
}
public static int GetHashCode(ReadOnlySpan<Identifier<IComponent>> componentTypeIDs)
{
var largestID = 0;
foreach (var id in componentTypeIDs)
{
if (id.value > largestID)
{
largestID = id.value;
}
}
var length = UnsafeBitSet.RequiredLength(largestID + 1);
var bits = (Span<uint>)stackalloc uint[length];
bits.Clear();
var bitSet = new SpanBitSet(bits);
foreach (var id in componentTypeIDs)
{
bitSet.SetBit(id.value);
}
return bitSet.GetHashCode();
}
}

View File

@@ -1,715 +0,0 @@
using Ghost.Core;
using Misaki.HighPerformance.LowLevel.Collections;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Ghost.Entities.Components;
internal static class SingletonContainer<T>
where T : unmanaged, IComponentData
{
public static readonly Dictionary<int, T> container = new();
}
internal interface IComponentPool : IDisposable
{
public EntityID Count
{
get;
}
public void Add(Entity entity, IComponentData component);
public bool Remove(Entity entity);
public bool Has(Entity entity);
public IComponentData Get(Entity entity);
public IntPtr GetUnsafe(Entity entity);
public void Set(Entity entity, in IComponentData component);
public IEnumerable<(Entity entity, IComponentData component)> Enumerate();
}
internal interface IComponentPool<T> : IComponentPool
where T : IComponentData
{
public void Add(Entity entity, T Component);
public void Set(Entity entity, in T component);
}
internal class ComponentPool<T> : IComponentPool<T>
where T : unmanaged, IComponentData
{
private struct ComponentMetadata
{
public T data;
public Entity owner;
}
private EntityID _nextId;
private EntityID _capacity;
private ComponentMetadata[] _components;
private EntityID[] _lookup;
public EntityID Count => _nextId;
public ComponentPool(int initialSize)
{
_nextId = 0;
_capacity = initialSize;
_components = new ComponentMetadata[initialSize];
_lookup = new EntityID[initialSize];
_lookup.AsSpan().Fill(Entity.INVALID_ID);
}
public ComponentPool()
: this(16)
{
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static EntityID GetLookupIndex(Entity entity)
{
return entity.ID;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private EntityID GetComponentIndex(Entity entity)
{
return _lookup[GetLookupIndex(entity)];
}
public void Add(Entity entity, IComponentData component)
{
if (component is not T typedComponent)
{
throw new ArgumentException($"Component type mismatch. Expected {typeof(T)}, but got {component.GetType()}.");
}
Add(entity, typedComponent);
}
public void Add(Entity entity, T component)
{
if (!entity.IsValid)
{
return;
}
var lookupIndex = GetLookupIndex(entity);
var componentIndex = GetComponentIndex(entity);
if (componentIndex != Entity.INVALID_ID)
{
// Overwrite the old data if generation is larger
if (entity.Generation > _components[componentIndex].owner.Generation)
{
var index = _lookup[lookupIndex];
_components[index].data = component;
_components[index].owner = entity;
}
return;
}
if (_nextId >= _capacity)
{
var newCapacity = _capacity * 2;
Array.Resize(ref _components, newCapacity);
Array.Resize(ref _lookup, newCapacity);
_lookup.AsSpan(_capacity, newCapacity - _capacity).Fill(Entity.INVALID_ID);
_capacity = newCapacity;
}
_lookup[lookupIndex] = _nextId;
_components[_nextId] = new ComponentMetadata
{
data = component,
owner = entity
};
_nextId++;
}
public bool Remove(Entity entity)
{
// We do not remove anything here, the generation of the entity will be used to determine if the component is valid.
return true;
}
public IComponentData Get(Entity entity)
{
return GetRef(entity);
}
public unsafe IntPtr GetUnsafe(Entity entity)
{
return (IntPtr)Unsafe.AsPointer(ref GetRef(entity));
}
public ref T GetRef(Entity entity)
{
if (!entity.IsValid)
{
return ref Unsafe.NullRef<T>();
}
var index = GetComponentIndex(entity);
return ref _components[index].data;
}
public bool Has(Entity entity)
{
if (entity.ID >= _lookup.Length)
{
return false;
}
var index = GetComponentIndex(entity);
return index != Entity.INVALID_ID && _components[index].owner.Generation == entity.Generation;
}
public void Set(Entity entity, in IComponentData component)
{
if (component is not T typedComponent)
{
throw new ArgumentException($"Component type mismatch. Expected {typeof(T)}, but got {component.GetType()}.");
}
Set(entity, typedComponent);
}
public void Set(Entity entity, in T component)
{
if (!entity.IsValid || entity.ID >= _lookup.Length || GetComponentIndex(entity) == Entity.INVALID_ID)
{
return;
}
var index = GetComponentIndex(entity);
_components[index].data = component;
_components[index].owner = entity;
}
public IEnumerable<(Entity entity, IComponentData component)> Enumerate()
{
for (var i = 0; i < _nextId; i++)
{
if (_components[i].owner.IsValid)
{
yield return (_components[i].owner, _components[i].data);
}
}
}
public void Dispose()
{
_components = Array.Empty<ComponentMetadata>();
_lookup = Array.Empty<EntityID>();
_nextId = 0;
_capacity = 0;
}
}
internal class ScriptComponentPool : IComponentPool<ScriptComponent>
{
private Dictionary<Entity, List<ScriptComponent>>? _scriptComponents;
private List<ScriptComponent>? _executionList;
private readonly World _world;
internal IReadOnlyDictionary<Entity, List<ScriptComponent>>? ScriptComponents => _scriptComponents;
internal IReadOnlyList<ScriptComponent>? ExecutionList => _executionList;
public bool IsInitialized => _scriptComponents != null;
public int Count => _scriptComponents?.Keys.Count ?? 0;
public ScriptComponentPool(World world)
{
_world = world;
}
internal void Initialize(int capacity = 16)
{
_scriptComponents ??= new(capacity);
}
internal void RebuildExecutionList()
{
if (_scriptComponents == null)
{
return;
}
_executionList ??= new List<ScriptComponent>(_scriptComponents.Count);
_executionList.Clear();
foreach (var kvp in _scriptComponents)
{
_executionList.AddRange(kvp.Value);
}
_executionList.Sort((a, b) => a.ExecutionOrder.CompareTo(b.ExecutionOrder));
}
public void Add(Entity entity, IComponentData component)
{
if (component is not ScriptComponent scriptComponent)
{
throw new ArgumentException($"Component type mismatch. Expected {typeof(ScriptComponent)}, but got {component.GetType()}.");
}
Add(entity, scriptComponent);
}
public void Add(Entity entity, ScriptComponent component)
{
if (!IsInitialized)
{
Initialize();
}
ref var scriptList = ref CollectionsMarshal.GetValueRefOrAddDefault(_scriptComponents!, entity, out var exists);
scriptList ??= new List<ScriptComponent>();
scriptList.Add(component);
component.Owner = entity;
component._world = _world;
component.Enable = true;
component.Initialize();
}
public bool Remove<T>(Entity entity)
where T : ScriptComponent
{
if (!Has(entity)
|| !_scriptComponents!.TryGetValue(entity, out var scriptList)
|| scriptList == null)
{
return false;
}
var scriptToRemove = scriptList.FirstOrDefault(script => script is T);
if (scriptToRemove == null)
{
return false;
}
scriptToRemove.OnDestroy();
scriptList.Remove(scriptToRemove);
if (scriptList.Count == 0)
{
_scriptComponents.Remove(entity);
}
return true;
}
public bool RemoveAt(Entity entity, int index)
{
if (!Has(entity)
|| !_scriptComponents!.TryGetValue(entity, out var scriptList)
|| scriptList == null)
{
return false;
}
if (index < 0 || index > scriptList.Count)
{
return false;
}
scriptList.RemoveAt(index);
if (scriptList.Count == 0)
{
_scriptComponents.Remove(entity);
}
return true;
}
public bool Remove(Entity entity)
{
if (!Has(entity)
|| !_scriptComponents!.TryGetValue(entity, out var scriptList)
|| scriptList == null)
{
return false;
}
foreach (var script in scriptList)
{
script.OnDestroy();
}
_scriptComponents.Remove(entity);
return true;
}
public bool Has(Entity entity)
{
return _scriptComponents?.ContainsKey(entity) ?? false;
}
[Obsolete("Use GetAll instead of Get for ScriptComponentPool.")]
public IComponentData Get(Entity entity)
{
if (!Has(entity)
|| !_scriptComponents!.TryGetValue(entity, out var scriptList)
|| scriptList == null
|| scriptList.Count == 0)
{
return null!;
}
return scriptList[0];
}
[Obsolete("Use GetAll instead of GetUnsafe for ScriptComponentPool.")]
public unsafe IntPtr GetUnsafe(Entity entity)
{
if (!Has(entity)
|| !_scriptComponents!.TryGetValue(entity, out var scriptList)
|| scriptList == null
|| scriptList.Count == 0)
{
return IntPtr.Zero;
}
return (IntPtr)Unsafe.AsPointer(ref CollectionsMarshal.AsSpan(scriptList)[0]);
}
public void Set(Entity entity, in IComponentData component)
{
if (component is not ScriptComponent scriptComponent)
{
throw new ArgumentException($"Component type mismatch. Expected {typeof(ScriptComponent)}, but got {component.GetType()}.");
}
Set(entity, scriptComponent);
}
public void Set(Entity entity, in ScriptComponent component)
{
if (!Has(entity)
|| !_scriptComponents!.TryGetValue(entity, out var scriptList)
|| scriptList == null)
{
return;
}
var index = scriptList.IndexOf(component);
if (index >= 0)
{
scriptList[index] = component;
component.Owner = entity;
}
}
public IEnumerable<(Entity entity, IComponentData component)> Enumerate()
{
if (_scriptComponents == null)
{
yield break;
}
foreach (var kvp in _scriptComponents)
{
foreach (var script in kvp.Value)
{
yield return (kvp.Key, script);
}
}
}
public IReadOnlyList<IComponentData>? GetAll(Entity entity)
{
if (_scriptComponents == null
|| !_scriptComponents.TryGetValue(entity, out var scriptList))
{
return null;
}
return scriptList;
}
public void Dispose()
{
if (_scriptComponents != null)
{
if (_executionList != null)
{
foreach (var script in _executionList)
{
script.OnDestroy();
}
_executionList.Clear();
}
else
{
foreach (var scriptList in _scriptComponents.Values)
{
if (scriptList == null)
{
continue;
}
foreach (var script in scriptList)
{
script.OnDestroy();
}
}
}
_scriptComponents.Clear();
}
}
}
[SkipLocalsInit]
internal struct ComponentStorage : IDisposable
{
private static int s_nextId = 0;
private static class TypeID<T>
{
public static readonly int value = s_nextId++;
}
private int _currentCapacity = 16;
private IComponentPool?[] _componentPools = new IComponentPool[16];
private UnsafeBitSet[] _componentEntityMasks = new UnsafeBitSet[16];
private readonly Dictionary<TypeHandle, int> _typeIDMap = new(16);
private readonly Dictionary<int, TypeHandle> _typeHandleMap = new(16);
private readonly ScriptComponentPool _scriptComponentPool;
private readonly World _world;
internal ComponentStorage(World world)
{
_world = world;
_scriptComponentPool = new ScriptComponentPool(world);
}
internal readonly IReadOnlyList<IComponentPool?> ComponentPools => _componentPools;
internal readonly IReadOnlyList<UnsafeBitSet> ComponentEntityMasks => _componentEntityMasks;
internal readonly ScriptComponentPool ScriptComponentPool => _scriptComponentPool;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private readonly int GetTypeID(TypeHandle typeHandle)
{
if (_typeIDMap.TryGetValue(typeHandle, out var id))
{
return id;
}
return typeof(TypeID<>).MakeGenericType(typeHandle!)
.GetField("value", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public)
?.GetValue(null) as int? ?? -1;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private readonly int GetTypeID<T>()
{
return TypeID<T>.value;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void Resize(int newCapacity)
{
Array.Resize(ref _componentPools, newCapacity);
Array.Resize(ref _componentEntityMasks, newCapacity);
_currentCapacity = newCapacity;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal readonly TypeHandle GetComponentPoolType(int poolIndex)
{
if (poolIndex < 0 || poolIndex >= _currentCapacity)
{
throw new ArgumentOutOfRangeException(nameof(poolIndex), "Invalid pool index.");
}
return _typeHandleMap[poolIndex];
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly bool TryGetPool(TypeHandle typeHandle, [NotNullWhen(true)] out IComponentPool? pool)
{
var result = _typeIDMap.TryGetValue(typeHandle, out var id);
if (!result || id >= _currentCapacity)
{
pool = null;
return false;
}
pool = _componentPools[id];
return pool != null;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly bool TryGetPool(Type type, [NotNullWhen(true)] out IComponentPool? pool)
{
return TryGetPool(TypeHandle.Get(type), out pool);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly bool TryGetPool<T>([NotNullWhen(true)] out ComponentPool<T>? pool)
where T : unmanaged, IComponentData
{
var id = TypeID<T>.value;
if (id >= _currentCapacity)
{
pool = null;
return false;
}
pool = (ComponentPool<T>?)_componentPools[id];
return pool != null;
}
public IComponentPool GetOrCreateComponentPool(Type type)
{
var typeHandle = TypeHandle.Get(type);
if (_typeIDMap.TryGetValue(typeHandle, out var id))
{
if (id < _currentCapacity && _componentPools[id] is IComponentPool existingPool)
{
return existingPool;
}
}
else
{
id = GetTypeID(typeHandle);
}
if (id >= _currentCapacity)
{
Resize(_currentCapacity * 2);
_typeIDMap[typeHandle] = id;
_typeHandleMap[id] = typeHandle;
}
else if (_componentPools[id] is IComponentPool existingPool)
{
return existingPool;
}
var pool = Activator.CreateInstance(typeof(ComponentPool<>).MakeGenericType(type)) as IComponentPool
?? throw new InvalidOperationException($"Failed to create component pool for type {type.FullName}");
_componentPools[id] = pool;
return pool;
}
public ComponentPool<T> GetOrCreateComponentPool<T>()
where T : unmanaged, IComponentData
{
var id = TypeID<T>.value;
var typeHandle = TypeHandle.Get<T>();
if (id >= _currentCapacity)
{
Resize(_currentCapacity * 2);
_typeIDMap[typeHandle] = id;
_typeHandleMap[id] = typeHandle;
}
else if (_componentPools[id] is ComponentPool<T> existingPool)
{
return existingPool;
}
var pool = new ComponentPool<T>();
_componentPools[id] = pool;
return pool;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly bool TryGetMask(TypeHandle typeHandle, [NotNullWhen(true)] out UnsafeBitSet? bitSet)
{
if (!_typeIDMap.TryGetValue(typeHandle, out var id)
|| id >= _currentCapacity)
{
bitSet = null;
return false;
}
bitSet = _componentEntityMasks[id];
return bitSet.HasValue;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly bool TryGetMask<T>([NotNullWhen(true)] out UnsafeBitSet? bitSet)
where T : unmanaged, IComponentData
{
return TryGetMask(TypeHandle.Get<T>(), out bitSet);
}
public ref UnsafeBitSet GetOrCreateMask(TypeHandle typeHandle)
{
if (!_typeIDMap.TryGetValue(typeHandle, out var id))
{
id = GetTypeID(typeHandle);
_typeIDMap[typeHandle] = id;
_typeHandleMap[id] = typeHandle;
}
if (id >= _currentCapacity)
{
Resize(_currentCapacity * 2);
}
ref var set = ref _componentEntityMasks[id];
if (!set.IsCreated)
{
set = new UnsafeBitSet();
}
return ref set;
}
public ref UnsafeBitSet GetOrCreateMask<T>()
where T : unmanaged, IComponentData
{
return ref GetOrCreateMask(TypeHandle.Get<T>());
}
public ref UnsafeBitSet GetOrCreateMask(Type type)
{
return ref GetOrCreateMask(TypeHandle.Get(type));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly void RebuildExecutionList()
{
_scriptComponentPool.RebuildExecutionList();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly void Remove(Entity entity)
{
_scriptComponentPool.Remove(entity);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly void Dispose()
{
foreach (var pool in _componentPools)
{
pool?.Dispose();
}
foreach (var bitSet in _componentEntityMasks)
{
bitSet.Dispose();
}
_scriptComponentPool.Dispose();
}
}

View File

@@ -1,5 +0,0 @@
namespace Ghost.Entities.Components;
public interface IComponentData
{
}

View File

@@ -1,110 +0,0 @@
namespace Ghost.Entities.Components;
public abstract class ScriptComponent : IComponentData
{
private bool _enable;
internal World _world = null!;
/// <summary>
/// Gets or sets a Value indicating whether this script component is enabled.
/// </summary>
public bool Enable
{
get => _enable;
set
{
if (_enable == value)
{
return;
}
_enable = value;
if (_enable)
{
OnEnable();
}
else
{
OnDisable();
}
}
}
/// <summary>
/// Gets the entity that owns this script component.
/// </summary>
public Entity Owner
{
get;
internal set;
}
/// <summary>
/// Gets the EntityManager instance associated with the current world.
/// </summary>
protected EntityManager EntityManager => _world.EntityManager;
/// <summary>
/// Gets or sets the priority of the script component.
/// Change this during runtime does not affect the execution order.
/// </summary>
public virtual int ExecutionOrder => 0;
/// <summary>
/// Called when the script component is enabled.
/// </summary>
public virtual void OnEnable()
{
}
/// <summary>
/// Called when the script component is disabled.
/// </summary>
public virtual void OnDisable()
{
}
/// <summary>
/// Called when the script component is initialized.
/// </summary>
public virtual void Initialize()
{
}
/// <summary>
/// Called when the script component is started.
/// </summary>
public virtual void Start()
{
}
/// <summary>
/// Called every frame.
/// </summary>
public virtual void Update()
{
}
/// <summary>
/// Called every frame after all Update methods have been called.
/// </summary>
public virtual void LateUpdate()
{
}
/// <summary>
/// Called at a fixed interval.
/// This method is called at a fixed time step, independent of the frame rate.
/// </summary>
public virtual void FixedUpdate()
{
}
/// <summary>
/// Called when the script component is destroyed.
/// </summary>
public virtual void OnDestroy()
{
}
}

View File

@@ -1,30 +1,29 @@
using System.Runtime.CompilerServices;
using System.Text.Json.Serialization;
using System.Runtime.InteropServices;
namespace Ghost.Entities;
[SkipLocalsInit]
public struct Entity : IEquatable<Entity>, IComparable<Entity>
[StructLayout(LayoutKind.Sequential, Size = 8)]
public readonly struct Entity : IEquatable<Entity>, IComparable<Entity>
{
public const EntityID INVALID_ID = -1;
[JsonInclude]
private EntityID _id;
private GenerationID _generation;
private readonly EntityID _id;
private readonly GenerationID _generation;
public readonly EntityID ID
public EntityID ID
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => _id;
}
public readonly GenerationID Generation
public GenerationID Generation
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => _generation;
}
public readonly bool IsValid
public bool IsValid
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => ID != INVALID_ID;
@@ -42,27 +41,24 @@ public struct Entity : IEquatable<Entity>, IComparable<Entity>
_generation = generation;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void IncrementGeneration() => _generation++;
public readonly bool Equals(Entity other)
public bool Equals(Entity other)
{
return _id == other._id && _generation == other._generation;
}
public readonly int CompareTo(Entity other)
public int CompareTo(Entity other)
{
return _id.CompareTo(other._id);
}
public override readonly bool Equals(object? obj)
public override bool Equals(object? obj)
{
return obj is Entity other && Equals(other);
}
public override readonly int GetHashCode()
public override int GetHashCode()
{
return _id.GetHashCode();
return _id ^ _generation << 16;
}
public static bool operator ==(Entity left, Entity right)
@@ -75,8 +71,8 @@ public struct Entity : IEquatable<Entity>, IComparable<Entity>
return !(left == right);
}
public override readonly string ToString()
public override string ToString()
{
return $"Entity {{ Index: {ID}, Generation: {Generation} }}";
}
}
}

View File

@@ -0,0 +1,110 @@
using Misaki.HighPerformance.LowLevel.Collections;
using Misaki.HighPerformance.LowLevel.Utilities;
namespace Ghost.Entities;
public unsafe class EntityCommandBuffer : IDisposable
{
private enum CommandType
{
CreateEntity,
DestroyEntity,
AddComponent,
RemoveComponent,
SetComponent,
}
private struct Command
{
public UnsafeArray<byte> data;
public CommandType type;
public Entity entity;
public int componentTypeID;
}
private readonly EntityManager _entityManager;
private UnsafeList<Command> _commands; // TODO: Maybe use UnsafeArray<byte> directly?
public EntityCommandBuffer(EntityManager entityManager)
{
_entityManager = entityManager;
_commands = new UnsafeList<Command>(32, Misaki.HighPerformance.LowLevel.Buffer.Allocator.Persistent);
}
public void CreateEntity()
{
var command = new Command
{
type = CommandType.CreateEntity,
data = default,
entity = default,
componentTypeID = -1
};
_commands.Add(command);
}
public void DestroyEntity(Entity entity)
{
var command = new Command
{
type = CommandType.DestroyEntity,
data = default,
entity = entity,
componentTypeID = -1
};
_commands.Add(command);
}
public void AddComponent<T>(Entity entity, T component)
where T : unmanaged, IComponent
{
var data = new UnsafeArray<byte>(sizeof(T), Misaki.HighPerformance.LowLevel.Buffer.Allocator.Persistent);
MemoryUtility.MemCpy(&component, data.GetUnsafePtr(), (nuint)sizeof(T));
var command = new Command
{
type = CommandType.AddComponent,
data = data,
entity = entity,
componentTypeID = ComponentTypeID<T>.value
};
_commands.Add(command);
}
public void RemoveComponent<T>(Entity entity)
where T : unmanaged, IComponent
{
var command = new Command
{
type = CommandType.RemoveComponent,
data = default,
entity = entity,
componentTypeID = ComponentTypeID<T>.value
};
_commands.Add(command);
}
public void Reset()
{
foreach (ref var command in _commands)
{
command.data.Dispose();
}
_commands.Clear();
}
public void Dispose()
{
foreach (ref var command in _commands)
{
command.data.Dispose();
}
_commands.Dispose();
}
}

View File

@@ -1,360 +1,266 @@
using Ghost.Core;
using Ghost.Entities.Components;
using Ghost.Entities.Query;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections;
using Misaki.HighPerformance.LowLevel.Utilities;
using System.Diagnostics;
namespace Ghost.Entities;
public readonly struct EntityManager : IDisposable
public unsafe class EntityManager : IDisposable
{
private readonly List<Entity> _entities;
private readonly Queue<EntityID> _freeEntitySlots;
private struct EntityLocation
{
public Identifier<Archetype> archetypeID;
public int chunkIndex;
public int rowIndex;
}
private readonly World _world;
public readonly int EntityCount => _entities.Count;
public readonly ReadOnlySpan<Entity> Entities => CollectionsMarshal.AsSpan(_entities);
private World _world;
private UnsafeSlotMap<EntityLocation> _entityLocations;
internal EntityManager(World world, int initialCapacity)
{
_entities = new(initialCapacity);
_freeEntitySlots = new(initialCapacity);
_world = world;
_entityLocations = new UnsafeSlotMap<EntityLocation>(initialCapacity, Allocator.Persistent);
}
/// <summary>
/// Adds a new <see cref="Entity"/> to the world.
/// </summary>
/// <returns>The created <see cref="Entity"/>.</returns>
public readonly Entity CreateEntity()
internal ResultStatus UpdateEntityLocation(Entity entity, Identifier<Archetype> newArchetypeID, int newChunkIndex, int newRowIndex)
{
Entity entity;
if (_freeEntitySlots.TryDequeue(out var id))
ref var location = ref _entityLocations.GetElementReferenceAt(entity.ID, entity.Generation, out var exist);
if (!exist)
{
entity = _entities[id];
return ResultStatus.NotFound;
}
else
location.archetypeID = newArchetypeID;
location.chunkIndex = newChunkIndex;
location.rowIndex = newRowIndex;
return ResultStatus.Success;
}
public Entity CreateEntity(params ReadOnlySpan<Identifier<IComponent>> componentTypeIDs)
{
var signatureHash = ComponentRegister.GetHashCode(componentTypeIDs);
var arcID = _world.GetArchetypeIDBySignatureHash(signatureHash);
if (arcID.IsNotValid)
{
id = _entities.Count;
entity = new Entity(id, 0);
_entities.Add(entity);
arcID = _world.CreateArchetype(componentTypeIDs, signatureHash);
}
ref var archetype = ref _world.GetArchetypeReference(arcID);
archetype.AllocateEntity(out var chunkIndex, out var rowIndex);
var id = _entityLocations.Add(new EntityLocation
{
archetypeID = arcID,
chunkIndex = chunkIndex,
rowIndex = rowIndex
}, out var generation);
var entity = new Entity(id, generation);
archetype.SetEntity(chunkIndex, rowIndex, entity);
return entity;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal readonly void AddEntityInternal(Entity entity)
public Entity CreateEntity()
{
_entities.Add(entity);
// Put into empty archetype
ref var emptyArchetype = ref _world.GetArchetypeReference(World.EmptyArchetypeID);
emptyArchetype.AllocateEntity(out var chunkIndex, out var rowIndex);
var id = _entityLocations.Add(new EntityLocation
{
archetypeID = World.EmptyArchetypeID,
chunkIndex = chunkIndex,
rowIndex = rowIndex
}, out var generation);
var entity = new Entity(id, generation);
emptyArchetype.SetEntity(chunkIndex, rowIndex, entity);
return entity;
}
/// <summary>
/// Removes the specified <see cref="Entity"/> from the world.
/// </summary>
/// <param name="entity"></param>
public readonly void RemoveEntity(ref Entity entity)
public ResultStatus DestoryEntity(Entity entity)
{
if (entity.ID >= _entities.Count || _entities[entity.ID].Generation != entity.Generation)
if (!_entityLocations.TryGetElementAt(entity.ID, entity.Generation, out var location))
{
return;
return ResultStatus.NotFound;
}
_world.ComponentStorage.Remove(entity);
var slot = _entities[entity.ID];
slot.IncrementGeneration();
_entities[entity.ID] = slot;
_freeEntitySlots.Enqueue(entity.ID);
entity = Entity.Invalid;
}
/// <summary>
/// Checks if the given <see cref="Entity"/> is valid and belongs to this <see cref="World"/>.
/// </summary>
/// <param name="entity">The entity to check.</param>
/// <returns>True if the entity is valid and belongs to this world; otherwise, false.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly bool HasEntity(Entity entity)
{
if (!entity.IsValid
|| entity.ID >= _entities.Count)
ref var archetype = ref _world.GetArchetypeReference(location.archetypeID);
var r = archetype.RemoveEntity(location.chunkIndex, location.rowIndex);
if (r != ResultStatus.Success)
{
return false;
return r;
}
return _entities[entity.ID].Generation == entity.Generation;
}
/// <summary>
/// Adds a component of the specified type to the given entity.
/// </summary>
/// <remarks>
/// This method use reflection to determine the type of the component being added. Use generic as much as possible.
/// </remarks>
/// <param name="entity">The entity to which the component will be added.</param>
/// <param name="component">The component data to associate with the entity.</param>
/// <param name="componentType">The type of the component being added. This must match the type of <paramref name="component"/>.</param>
public readonly void AddComponent(Entity entity, IComponentData component, Type componentType)
{
_world.ComponentStorage.GetOrCreateComponentPool(componentType).Add(entity, component);
_world.ComponentStorage.GetOrCreateMask(componentType).SetBit(entity.ID);
}
/// <summary>
/// Adds a component of type <typeparamref name="T"/> to the given <see cref="Entity"/>.
/// </summary>
/// <typeparam name="T">The type of the component to set.</typeparam>
/// <param name="entity">The entity for which the component is to be add.</param>
/// <param name="component">The component Value to add.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly void AddComponent<T>(Entity entity, T component)
where T : unmanaged, IComponentData
{
_world.ComponentStorage.GetOrCreateComponentPool<T>().Add(entity, component);
_world.ComponentStorage.GetOrCreateMask<T>().SetBit(entity.ID);
}
/// <summary>
/// Removes a component of type <typeparamref name="T"/> from the given <see cref="Entity"/>.
/// </summary>
/// <typeparam name="T">The type of the component to remove.</typeparam>
/// <param name="entity">The entity for which the component is to be remove.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly bool RemoveComponent<T>(Entity entity)
where T : unmanaged, IComponentData
{
if (!_world.ComponentStorage.TryGetPool<T>(out var pool) || !pool.Has(entity))
if (!_entityLocations.Remove(entity.ID, entity.Generation))
{
return false;
return ResultStatus.NotFound;
}
if (!pool.Remove(entity))
{
return false;
}
_world.ComponentStorage.GetOrCreateMask<T>().ClearBit(entity.ID);
return true;
return ResultStatus.Success;
}
/// <summary>
/// Sets a component of the specified type for the given <see cref="Entity"/>.
/// </summary>
/// <param name="entity">The entity for which the component is to be set.</param>
/// <param name="component">The component Value to set.</param>
/// <param name="type">The type of the component to set.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly void SetComponent(Entity entity, IComponentData component, Type type)
private static void CopyData(ref Archetype oldArch, int oldChunk, int oldRow,
ref Archetype newArch, int newChunk, int newRow)
{
var typeHandle = TypeHandle.Get(type);
if (!_world.ComponentStorage.TryGetPool(typeHandle, out var pool))
// Iterate every component type in the OLD archetype
for (var i = 0; i < oldArch.Layouts.Count; i++)
{
return;
}
var layout = oldArch.Layouts[i];
if (!pool.Has(entity))
{
return;
}
var src = oldArch.Chunks[oldChunk].GetUnsafePtr() + layout.offset + (layout.size * oldRow);
var newOffset = newArch.GetOffset(layout.componentID); // O(1) Lookup
var dst = oldArch.Chunks[oldChunk].GetUnsafePtr() + newOffset + (layout.size * newRow);
pool.Set(entity, component);
}
/// <summary>
/// Sets a component of type <typeparamref name="T"/> for the given <see cref="Entity"/>.
/// </summary>
/// <typeparam name="T">The type of the component to set.</typeparam>
/// <param name="entity">The entity for which the component is to be set.</param>
/// <param name="component">The component Value to set.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly void SetComponent<T>(Entity entity, in T component)
where T : unmanaged, IComponentData
{
_world.ComponentStorage.GetOrCreateComponentPool<T>().Set(entity, in component);
}
/// <summary>
/// Checks if the given <see cref="Entity"/> has a component of the specified type.
/// </summary>
/// <param name="entity">The entity to check.</param>
/// <param name="typeHandle">The handle of the component type.</param>
/// <returns>True if the entity has the component; otherwise, false.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly bool HasComponent(Entity entity, TypeHandle typeHandle)
{
return _world.ComponentStorage.TryGetMask(typeHandle, out var bitSet) && bitSet.Value.IsSet(entity.ID);
}
/// <summary>
/// Retrieves a reference to a component of type <typeparamref name="T"/> associated with the given <see cref="Entity"/>.
/// </summary>
/// <typeparam name="T">The type of the component to retrieve.</typeparam>
/// <param name="entity">The entity whose component is to be retrieved.</param>
/// <returns>A <see cref="CompRef{T}"/> to the component, or a null reference if the component does not exist.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly CompRef<T> GetComponent<T>(Entity entity)
where T : unmanaged, IComponentData
{
if (_world.ComponentStorage.TryGetPool<T>(out var pool) && pool.Has(entity))
{
return new CompRef<T>(ref pool.GetRef(entity));
}
else
{
return new CompRef<T>(ref Unsafe.NullRef<T>(), false);
MemoryUtility.MemCpy(src, dst, (nuint)layout.size);
}
}
/// <summary>
/// Retrieves all components associated with the specified entity.
/// </summary>
/// <remarks>This method iterates through all available component pools to find components associated
/// with the given entity. It is designed to lazily yield components, making it efficient for scenarios where only
/// a subset of components may be needed.</remarks>
/// <param name="entity">The entity for which components are to be retrieved.</param>
/// <returns>An enumerable collection of components associated with the specified entity. If the entity has no components,
/// the collection will be empty.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly IEnumerable<IComponentData> GetComponents(Entity entity)
public ResultStatus AddComponent(Entity entity, Identifier<IComponent> componentID, void* component)
{
foreach (var pool in _world.ComponentStorage.ComponentPools)
// Find current location
ref var location = ref _entityLocations.GetElementReferenceAt(entity.ID, entity.Generation, out var exist);
if (!exist)
{
if (pool == null)
return ResultStatus.NotFound;
}
// Build new archetype signature
ref var oldArchetype = ref _world.GetArchetypeReference(location.archetypeID);
var oldSignature = oldArchetype.Signature;
// TODO: Check edge cache first.
var newArcID = oldArchetype.GetEdgeAdd(componentID);
if (newArcID.IsNotValid)
{
var largestComponentID = Math.Max(oldSignature.Count, componentID);
var length = UnsafeBitSet.RequiredLength(largestComponentID + 1);
Span<uint> bits = stackalloc uint[length];
bits.Clear();
var newSignature = new SpanBitSet(bits);
var iterator = 0;
var compCount = 0;
while (true)
{
continue;
var bit = oldSignature.NextSetBit(iterator);
if (bit == -1)
{
break;
}
newSignature.SetBit(bit);
iterator = bit + 1;
compCount++;
}
if (pool.Has(entity))
compCount++;
newSignature.SetBit(componentID);
// Find or create new archetype
var newSignatureHash = newSignature.GetHashCode();
newArcID = _world.GetArchetypeIDBySignatureHash(newSignatureHash);
if (newArcID.IsNotValid)
{
yield return pool.Get(entity);
// Create new archetype
Span<Identifier<IComponent>> componentTypeIDs = stackalloc Identifier<IComponent>[compCount];
componentTypeIDs[0] = componentID;
iterator = 0;
while (true)
{
var bit = oldSignature.NextSetBit(iterator);
if (bit == -1)
{
break;
}
componentTypeIDs[--compCount] = bit;
iterator = bit + 1;
}
newArcID = _world.CreateArchetype(componentTypeIDs, newSignatureHash);
}
oldArchetype.AddEdgeAdd(componentID, newArcID);
}
// Move entity data
ref var newArchetype = ref _world.GetArchetypeReference(newArcID);
newArchetype.AllocateEntity(out var newChunkIndex, out var newRowIndex);
CopyData(ref oldArchetype, location.chunkIndex, location.rowIndex,
ref newArchetype, newChunkIndex, newRowIndex);
newArchetype.SetEntity(newChunkIndex, newRowIndex, entity);
newArchetype.SetComponentData(newChunkIndex, newRowIndex, componentID, component);
var r = oldArchetype.RemoveEntity(location.chunkIndex, location.rowIndex);
Debug.Assert(r == ResultStatus.Success); // We assert it because the entity should exist if the whole system is consistent.
// if (r != ResultStatus.Success)
// {
// return r;
// }
// Update location
location.archetypeID = newArcID;
location.chunkIndex = newChunkIndex;
location.rowIndex = newRowIndex;
return ResultStatus.Success;
}
/// <summary>
/// Retrieves an enumerable collection of raw pointers to the components associated with the specified entity.
/// </summary>
/// <remarks>This method provides direct access to the memory locations of components, bypassing type
/// safety. Use with caution, as improper handling of raw pointers can lead to undefined behavior or memory
/// corruption. Ensure that the entity is valid and exists within the current world context before calling this
/// method.</remarks>
/// <param name="entity">The entity whose components are to be retrieved.</param>
/// <returns>An enumerable collection of <see cref="IntPtr"/> representing the memory addresses of the components associated
/// with the specified entity. The collection will be empty if the entity has no associated components.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly IEnumerable<(TypeHandle, IntPtr)> GetComponentsUnsafe(Entity entity)
public ResultStatus AddComponent<T>(Entity entity, ref T component)
where T : unmanaged, IComponent
{
for (var i = 0; i < _world.ComponentStorage.ComponentPools.Count; i++)
return AddComponent(entity, ComponentTypeID<T>.value, UnsafeUtility.AddressOf(ref component));
}
public ResultStatus SetComponentData(Entity entity, Identifier<IComponent> componentID, void* pComponent)
{
if (!_entityLocations.TryGetElementAt(entity.ID, entity.Generation, out var location))
{
var pool = _world.ComponentStorage.ComponentPools[i];
if (pool == null)
{
continue;
}
if (pool.Has(entity))
{
yield return (_world.ComponentStorage.GetComponentPoolType(i), pool.GetUnsafe(entity));
}
}
}
/// <summary>
/// Adds a script of type <typeparamref name="T"/> to the given <see cref="Entity"/>.
/// </summary>
/// <typeparam name="T">The type of the script to add.</typeparam>
/// <param name="entity">The entity to which the script is to be added.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly void AddScript<T>(Entity entity)
where T : ScriptComponent, new()
{
_world.ComponentStorage.ScriptComponentPool.Add(entity, new T());
}
/// <summary>
/// Adds a script of the specified type to the given <see cref="Entity"/>.
/// </summary>
/// <param name="entity">The entity to which the script is to be added.</param>
/// <param name="type">The type of the script to add.</param>
/// <exception cref="ArgumentException">Thrown if the specified type does not inherit from <see cref="ScriptComponent"/>.</exception>
/// <exception cref="InvalidOperationException">Thrown if the script instance could not be created.</exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly void AddScript(Entity entity, Type type)
{
if (!typeof(ScriptComponent).IsAssignableFrom(type))
{
throw new ArgumentException($"Type {type} must inherit from ScriptComponent.", nameof(type));
return ResultStatus.NotFound;
}
var instance = (ScriptComponent?)Activator.CreateInstance(type) ?? throw new InvalidOperationException($"Failed to create instance of {type}.");
_world.ComponentStorage.ScriptComponentPool.Add(entity, instance);
ref var archetype = ref _world.GetArchetypeReference(location.archetypeID);
archetype.SetComponentData(location.chunkIndex, location.rowIndex, componentID, pComponent);
return ResultStatus.Success;
}
/// <summary>
/// Removes a script of type <typeparamref name="T"/> from the given <see cref="Entity"/>.
/// </summary>
/// <typeparam name="T">The type of the script to remove.</typeparam>
/// <param name="entity">The entity from which the script is to be removed.</param>
/// <returns>True if the script was successfully removed; otherwise, false.</returns>
public readonly bool RemoveScript<T>(Entity entity)
where T : ScriptComponent
public ResultStatus SetComponentData<T>(Entity entity, ref T component)
where T : unmanaged, IComponent
{
if (!_world.ComponentStorage.ScriptComponentPool.Remove<T>(entity))
return SetComponentData(entity, ComponentTypeID<T>.value, UnsafeUtility.AddressOf(ref component));
}
public bool HasComponent(Entity entity, Identifier<IComponent> componentID)
{
if (!_entityLocations.TryGetElementAt(entity.ID, entity.Generation, out var location))
{
return false;
}
return true;
ref var archetype = ref _world.GetArchetypeReference(location.archetypeID);
return archetype.HasComponent(componentID);
}
/// <summary>
/// Removes a script at the specified index from the given <see cref="Entity"/>.
/// </summary>
/// <param name="entity">The entity from which the script is to be removed.</param>
/// <param name="index">The index of the script to remove.</param>
/// <returns>True if the script was successfully removed; otherwise, false.</returns>
public readonly bool RemoveScriptAt(Entity entity, int index)
public bool HasComponent<T>(Entity entity)
where T : unmanaged, IComponent
{
if (!_world.ComponentStorage.ScriptComponentPool.RemoveAt(entity, index))
{
return false;
}
return true;
return HasComponent(entity, ComponentTypeID<T>.value);
}
/// <summary>
/// Retrieves the first script of type <typeparamref name="T"/> associated with the given <see cref="Entity"/>.
/// </summary>
/// <typeparam name="T">The type of the script to retrieve.</typeparam>
/// <param name="entity">The entity whose script is to be retrieved.</param>
/// <returns>The script of type <typeparamref name="T"/>, or null if no such script exists.</returns>
public readonly T? GetScript<T>(Entity entity)
where T : ScriptComponent
public void Dispose()
{
return (T?)_world.ComponentStorage.ScriptComponentPool.GetAll(entity)?
.FirstOrDefault(script => script is T tScript);
_entityLocations.Dispose();
}
/// <summary>
/// Retrieves all scripts of type <typeparamref name="T"/> associated with the given <see cref="Entity"/>.
/// </summary>
/// <typeparam name="T">The type of the scripts to retrieve.</typeparam>
/// <param name="entity">The entity whose scripts are to be retrieved.</param>
/// <returns>An enumerable of scripts of type <typeparamref name="T"/>.</returns>
public readonly IEnumerable<T> GetScripts<T>(Entity entity)
where T : ScriptComponent
{
return (IEnumerable<T>?)_world.ComponentStorage.ScriptComponentPool.GetAll(entity)?.Where(script => script is T tScript) ?? Enumerable.Empty<T>();
}
public readonly void Dispose()
{
_entities.Clear();
_freeEntitySlots.Clear();
}
}
}

View File

@@ -0,0 +1,60 @@
namespace Ghost.Entities;
public unsafe class EntityQuery<T1, T2>
where T1 : unmanaged, IComponent
where T2 : unmanaged, IComponent
{
// The Cache Struct
struct ArchetypeCache
{
public Archetype Archetype;
public int Offset1; // Offset for T1
public int Offset2; // Offset for T2
}
private List<ArchetypeCache> _cache = new();
internal void AddMatchingArchetype(Archetype archetype)
{
// We look up the offsets ONCE when the archetype is registered
int off1 = archetype.GetOffset(ComponentTypeID<T1>.value);
int off2 = archetype.GetOffset(ComponentTypeID<T2>.value);
_cache.Add(new ArchetypeCache
{
Archetype = archetype,
Offset1 = off1,
Offset2 = off2
});
}
// The Optimized Iteration Loop
public void ForEach(delegate*<ref T1, ref T2, void> action)
{
foreach (var cache in _cache)
{
var archetype = cache.Archetype;
var offset1 = cache.Offset1;
var offset2 = cache.Offset2;
// Iterate Chunks
for (int i = 0; i < archetype.ChunkCount; i++)
{
var chunk = archetype.GetChunkReference(i);
var chunkPtr = chunk.GetUnsafePtr();
var count = chunk.Count;
// POINTER MATH ONLY - NO LOOKUPS
// We use the pre-calculated integer offsets
T1* ptr1 = (T1*)(chunkPtr + offset1);
T2* ptr2 = (T2*)(chunkPtr + offset2);
// The hot loop
for (int k = 0; k < count; k++)
{
action(ref ptr1[k], ref ptr2[k]);
}
}
}
}
}

View File

@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
@@ -7,105 +7,6 @@
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<IsAotCompatible>True</IsAotCompatible>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<IsAotCompatible>True</IsAotCompatible>
</PropertyGroup>
<ItemGroup>
<None Include="Template\ForEach.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>ForEach.tt</DependentUpon>
</None>
<None Include="Template\QueryItem.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>QueryItem.tt</DependentUpon>
</None>
<None Include="Template\QueryRefComponent.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>QueryRefComponent.tt</DependentUpon>
</None>
<None Include="Template\World.Query.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>World.Query.tt</DependentUpon>
</None>
</ItemGroup>
<ItemGroup>
<None Update="Template\ForEach.tt">
<Generator>TextTemplatingFileGenerator</Generator>
<LastGenOutput>ForEach.cs</LastGenOutput>
</None>
<None Update="Template\QueryEnumerable.tt">
<Generator>TextTemplatingFileGenerator</Generator>
<LastGenOutput>QueryEnumerable.cs</LastGenOutput>
</None>
<None Update="Template\Helpers.tt">
<Generator>TextTemplatingFilePreprocessor</Generator>
<LastGenOutput>Helpers.cs</LastGenOutput>
</None>
<None Update="Template\QueryItem.tt">
<Generator>TextTemplatingFileGenerator</Generator>
<LastGenOutput>QueryItem.cs</LastGenOutput>
</None>
<None Update="Template\QueryRefComponent.tt">
<Generator>TextTemplatingFileGenerator</Generator>
<LastGenOutput>QueryRefComponent.cs</LastGenOutput>
</None>
<None Update="Template\World.Query.tt">
<Generator>TextTemplatingFileGenerator</Generator>
<LastGenOutput>World.Query.cs</LastGenOutput>
</None>
</ItemGroup>
<ItemGroup>
<Service Include="{508349b6-6b84-4df5-91f0-309beebad82d}" />
</ItemGroup>
<ItemGroup>
<Compile Update="Template\ForEach.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>ForEach.tt</DependentUpon>
</Compile>
<Compile Update="Template\Helpers.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>Helpers.tt</DependentUpon>
</Compile>
<Compile Update="Template\QueryEnumerable.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>QueryEnumerable.tt</DependentUpon>
</Compile>
<Compile Update="Template\QueryItem.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>QueryItem.tt</DependentUpon>
</Compile>
<Compile Update="Template\QueryRefComponent.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>QueryRefComponent.tt</DependentUpon>
</Compile>
<Compile Update="Template\World.Query.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>World.Query.tt</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<Folder Include="Helpers\" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Ghost.Core\Ghost.Core.csproj" />
</ItemGroup>

View File

@@ -1,42 +0,0 @@
using Ghost.Core;
namespace Ghost.Entities.Query;
public struct QueryBuilder
{
private QueryFilter _filter;
public QueryBuilder()
{
_filter = new QueryFilter();
}
public QueryBuilder WithAll<T>()
{
_filter._all.Add(TypeHandle.Get<T>());
return this;
}
public QueryBuilder WithAny<T>()
{
_filter._any.Add(TypeHandle.Get<T>());
return this;
}
public QueryBuilder WithAbsent<T>()
{
_filter._absent.Add(TypeHandle.Get<T>());
return this;
}
public QueryBuilder WithDisabled<T>()
{
_filter._disabled.Add(TypeHandle.Get<T>());
return this;
}
public readonly QueryFilter Build()
{
return _filter;
}
}

View File

@@ -1,97 +0,0 @@
using Ghost.Core;
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections;
namespace Ghost.Entities.Query;
public struct QueryFilter : IDisposable
{
//private readonly Stack.Scope _scope;
internal UnsafeList<TypeHandle> _all;
internal UnsafeList<TypeHandle> _any;
internal UnsafeList<TypeHandle> _absent;
internal UnsafeList<TypeHandle> _disabled;
public QueryFilter()
{
//_scope = AllocationManager.CreateStackScope();
_all = new UnsafeList<TypeHandle>(4, Allocator.Stack);
_any = new UnsafeList<TypeHandle>(4, Allocator.Stack);
_absent = new UnsafeList<TypeHandle>(4, Allocator.Stack);
_disabled = new UnsafeList<TypeHandle>(4, Allocator.Stack);
}
public readonly UnsafeBitSet ComputeFilterBitMask(World world, Allocator allocator)
{
UnsafeBitSet allMask = default;
UnsafeBitSet anyMask = default;
UnsafeBitSet absentMask = default;
var result = new UnsafeBitSet(world.EntityManager.EntityCount, allocator);
result.ClearAll();
using (AllocationManager.CreateStackScope())
{
foreach (var typeHandle in _all)
{
var mask = world.ComponentStorage.GetOrCreateMask(typeHandle);
if (!allMask.IsCreated)
{
allMask = new UnsafeBitSet(mask.Count, Allocator.Stack, AllocationOption.None);
allMask.SetAll();
}
allMask.And(mask);
}
foreach (var typeHandle in _any)
{
var mask = world.ComponentStorage.GetOrCreateMask(typeHandle);
if (!anyMask.IsCreated)
{
anyMask = new UnsafeBitSet(mask.Count, Allocator.Stack);
}
anyMask.And(mask);
}
foreach (var typeHandle in _absent)
{
var mask = world.ComponentStorage.GetOrCreateMask(typeHandle);
if (!absentMask.IsCreated)
{
absentMask = new UnsafeBitSet(mask.Count, Allocator.Stack);
}
absentMask.Or(mask);
}
if (allMask.IsCreated)
{
result.And(allMask);
}
if (anyMask.IsCreated)
{
result.And(anyMask);
}
if (absentMask.IsCreated)
{
result.ANDC(absentMask);
}
}
return result;
}
public readonly void Dispose()
{
//_scope.Dispose();
}
}

View File

@@ -1,73 +0,0 @@
using Ghost.Entities.Components;
using System.Runtime.CompilerServices;
namespace Ghost.Entities.Query;
public interface IQueryTypeParameter<T>
where T : IComponentData
{
}
public ref struct CompRef<T> : IQueryTypeParameter<T>
where T : IComponentData
{
internal ref T _value;
public ref T ValueRW
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => ref _value;
}
public readonly ref T ValueRO
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => ref _value;
}
public readonly bool IsValid
{
get;
init;
}
public CompRef(ref T value, bool isValid)
{
_value = ref value;
IsValid = isValid;
}
public CompRef(ref T value)
: this(ref value, true)
{
}
}
public readonly ref struct CompRO<T> : IQueryTypeParameter<T>
where T : IComponentData
{
internal readonly ref T _value;
public readonly ref T ValueRO
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => ref _value;
}
public readonly bool IsValid
{
get;
init;
}
public CompRO(ref T value, bool isValid)
{
_value = ref value;
IsValid = isValid;
}
public CompRO(ref T value)
: this(ref value, true)
{
}
}

View File

@@ -1,27 +0,0 @@
namespace Ghost.Entities.Systems;
/// <summary>
/// Attribute to declare that a system depends on one or more other systems.
/// </summary>
[AttributeUsage(AttributeTargets.Struct | AttributeTargets.Class, AllowMultiple = false)]
public class DependsOnAttribute : Attribute
{
public Type[] Prerequisites
{
get;
}
public DependsOnAttribute(params Type[] prerequisites)
{
Prerequisites = prerequisites;
}
}
public interface ISystem
{
public void OnCreate(in SystemState state);
public void OnUpdate(in SystemState state);
public void OnDestroy(in SystemState state);
}

View File

@@ -1,105 +0,0 @@
using System.Reflection;
namespace Ghost.Entities.Systems;
internal class SystemDependencyBuilder
{
private readonly Dictionary<Type, List<Type>> _dependencies = new();
private readonly List<Type> _systemTypes;
public SystemDependencyBuilder(List<Type> allSystemTypes)
{
_systemTypes = allSystemTypes;
}
/// <summary>
/// Builds a dependency graph for all system types that implement the <see cref="ISystem"/> interface.
/// </summary>
/// <remarks>This method analyzes all system types and their dependencies, as defined by the <see
/// cref="DependsOnAttribute"/>. It validates that each system type is a concrete implementation of <see
/// cref="ISystem"/> and constructs a mapping of each system type to its direct dependencies.</remarks>
/// <exception cref="ArgumentException">Thrown if a type in <c>allSystemTypes</c> is not a concrete implementation of <see cref="ISystem"/>.</exception>
public void BuildDependencyGraph()
{
foreach (var systemType in _systemTypes)
{
if (!typeof(ISystem).IsAssignableFrom(systemType) || systemType.IsAbstract || systemType.IsInterface)
{
throw new ArgumentException($"{systemType.Name} is not a concrete ISystem type.");
}
var directDependencies = new List<Type>();
var dependsOnAttributes = systemType.GetCustomAttributes<DependsOnAttribute>(false);
foreach (var attr in dependsOnAttributes)
{
directDependencies.AddRange(attr.Prerequisites);
}
_dependencies[systemType] = directDependencies;
}
}
private void Visit(Type systemType, HashSet<Type> visited, HashSet<Type> permanentMark, List<Type> executionOrder)
{
if (permanentMark.Contains(systemType))
{
return;
}
if (visited.Contains(systemType))
{
throw new InvalidOperationException($"Circular dependency detected involving system: {systemType.Name}");
}
visited.Add(systemType); // Mark as currently visiting
if (_dependencies.TryGetValue(systemType, out var directDependencies))
{
foreach (var dependencyType in directDependencies)
{
// Ensure the dependency is a registered system type
if (!_systemTypes.Contains(dependencyType))
{
throw new InvalidOperationException($"System {systemType.Name} depends on unregistered system {dependencyType.Name}.");
}
Visit(dependencyType, visited, permanentMark, executionOrder);
}
}
visited.Remove(systemType); // Done visiting this node in the current path
permanentMark.Add(systemType); // Mark as permanently processed
executionOrder.Add(systemType); // Add to the sorted list (this will be reversed later for correct order)
}
/// <summary>
/// Builds the topological order of systems.
/// </summary>
/// <returns>A list of system types in the order they should be executed.</returns>
/// <exception cref="InvalidOperationException">Thrown if a circular dependency is detected.</exception>"
public List<Type> BuildExecutionOrder()
{
var executionOrder = new List<Type>(_systemTypes.Count);
var visited = new HashSet<Type>(); // Tracks visited nodes in the current DFS path (for cycle detection)
var permanentMark = new HashSet<Type>(); // Tracks nodes whose dependencies have been fully resolved
// Initialize dependencies for all registered systems, even those without explicit attributes
foreach (var sysType in _systemTypes)
{
if (!_dependencies.ContainsKey(sysType))
{
_dependencies[sysType] = new();
}
}
foreach (var systemType in _systemTypes)
{
if (!permanentMark.Contains(systemType))
{
Visit(systemType, visited, permanentMark, executionOrder);
}
}
return executionOrder;
}
}

View File

@@ -1,10 +0,0 @@
namespace Ghost.Entities.Systems;
public struct SystemState
{
public World World
{
get;
init;
}
}

View File

@@ -1,93 +0,0 @@
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Ghost.Entities.Systems;
[SkipLocalsInit]
public readonly struct SystemStorage
{
private readonly List<Type> _systems = new();
private readonly List<ISystem> _executionList = new();
private readonly World _world;
internal ReadOnlySpan<Type> Systems => CollectionsMarshal.AsSpan(_systems);
internal SystemStorage(World world)
{
_world = world;
}
public readonly void AddSystem(Type systemType)
{
_systems.Add(systemType);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly void AddSystem<T>()
where T : ISystem, new()
{
AddSystem(typeof(T));
}
public readonly void RemoveSystem(Type systemType)
{
_systems.Remove(systemType);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly void RemoveSystem<T>()
where T : ISystem, new()
{
RemoveSystem(typeof(T));
}
internal void CreateSystems()
{
var builder = new SystemDependencyBuilder(_systems);
builder.BuildDependencyGraph();
var executionOrder = builder.BuildExecutionOrder();
var state = new SystemState()
{
World = _world,
};
foreach (var systemType in executionOrder)
{
var system = (ISystem?)Activator.CreateInstance(systemType) ?? throw new InvalidOperationException($"Failed to create instance of system type {systemType.Name}.");
_executionList.Add(system);
system.OnCreate(in state);
}
}
internal void UpdateSystems()
{
var state = new SystemState()
{
World = _world,
};
foreach (var system in _executionList)
{
system.OnUpdate(in state);
}
}
internal void Dispose()
{
var state = new SystemState()
{
World = _world,
};
foreach (var system in _executionList)
{
system.OnDestroy(in state);
}
_systems.Clear();
_executionList.Clear();
}
}

View File

@@ -1,12 +0,0 @@
namespace Ghost.Entities;
public delegate void ForEach<T0>(ref T0 t0Component);
public delegate void ForEach<T0, T1>(ref T0 t0Component, ref T1 t1Component);
public delegate void ForEach<T0, T1, T2>(ref T0 t0Component, ref T1 t1Component, ref T2 t2Component);
public delegate void ForEach<T0, T1, T2, T3>(ref T0 t0Component, ref T1 t1Component, ref T2 t2Component, ref T3 t3Component);
public delegate void ForEach<T0, T1, T2, T3, T4>(ref T0 t0Component, ref T1 t1Component, ref T2 t2Component, ref T3 t3Component, ref T4 t4Component);
public delegate void ForEach<T0, T1, T2, T3, T4, T5>(ref T0 t0Component, ref T1 t1Component, ref T2 t2Component, ref T3 t3Component, ref T4 t4Component, ref T5 t5Component);
public delegate void ForEach<T0, T1, T2, T3, T4, T5, T6>(ref T0 t0Component, ref T1 t1Component, ref T2 t2Component, ref T3 t3Component, ref T4 t4Component, ref T5 t5Component, ref T6 t6Component);
public delegate void ForEach<T0, T1, T2, T3, T4, T5, T6, T7>(ref T0 t0Component, ref T1 t1Component, ref T2 t2Component, ref T3 t3Component, ref T4 t4Component, ref T5 t5Component, ref T6 t6Component, ref T7 t7Component);

View File

@@ -1,16 +0,0 @@
<#@ template language="C#" #>
<#@ output extension=".cs" #>
<#@ assembly name="System.Core" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text" #>
<#@ include file="Helpers.ttinclude" #>
namespace Ghost.Entities;
<# for (var i = 1; i <= Amount; i++)
{
var generics = AppendGenerics(i);
var compGenerics = AppendGenericRefParameters(i);
#>
public delegate void ForEach<<#= generics #>>(<#= compGenerics #>);
<# } #>

View File

@@ -1,128 +0,0 @@
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Collections.Generic" #>
<#+
public int Amount = 8;
public int ExtensionAmount = 3;
public string Indent(StringBuilder sb, int spaces)
{
var indent = new string(' ', spaces);
return sb.ToString().Replace("\n", "\n" + indent);
}
string AppendGenerics(int amount, string template)
{
var sb = new StringBuilder();
for (var i = 0; i < amount; i++)
{
if (i > 0) sb.Append(", ");
sb.Append($"{template}{i}");
}
return sb.ToString();
}
string AppendGenerics(int amount)
{
return AppendGenerics(amount, "T");
}
public StringBuilder AppendGenericRefParameters(int amount)
{
var sb = new StringBuilder();
for (var localIndex = 0; localIndex < amount; localIndex++)
{
sb.Append($"ref T{localIndex} t{localIndex}Component,");
}
sb.Length--;
return sb;
}
public StringBuilder AppendRefParameters(int amount)
{
var sb = new StringBuilder();
for (var localIndex = 0; localIndex < amount; localIndex++)
{
sb.Append($"ref component{localIndex},");
}
sb.Length--;
return sb;
}
public StringBuilder AppendGenericRestrictions(int amount, string Ttemplate, string template)
{
var sb = new StringBuilder();
for (var localIndex = 0; localIndex < amount; localIndex++)
{
sb.Append($"where {Ttemplate}{localIndex} : {template}");
if (localIndex < amount - 1)
{
sb.Append(' ');
}
}
return sb;
}
public StringBuilder AppendGenericRestrictions(int amount, string template)
{
return AppendGenericRestrictions(amount, "T", template);
}
public StringBuilder TryGetComponentPools(int amount)
{
var sb = new StringBuilder();
for (var localIndex = 0; localIndex < amount; localIndex++)
{
sb.Append($"_componentStorage.TryGetPool<T{localIndex}>(out var pool{localIndex})");
if (localIndex < amount - 1)
{
sb.Append(" && ");
}
}
return sb;
}
public StringBuilder HasEntity(int amount)
{
var sb = new StringBuilder();
for (var localIndex = 1; localIndex < amount; localIndex++)
{
sb.Append($"pool{localIndex}.Has(entity)");
if (localIndex < amount - 1)
{
sb.Append(" && ");
}
}
return sb;
}
public StringBuilder GetComponent(int amount)
{
var sb = new StringBuilder();
for (var localIndex = 0; localIndex < amount; localIndex++)
{
sb.Append($"pool{localIndex}.GetRef(entity)");
if (localIndex < amount - 1)
{
sb.Append(", ");
}
}
return sb;
}
public StringBuilder GetComponentRef(int amount)
{
var sb = new StringBuilder();
for (var localIndex = 0; localIndex < amount; localIndex++)
{
sb.Append($"ref pool{localIndex}.GetRef(entity)");
if (localIndex < amount - 1)
{
sb.Append(", ");
}
}
return sb;
}
#>

File diff suppressed because it is too large Load Diff

View File

@@ -1,175 +0,0 @@
<#@ template language="C#" #>
<#@ output extension=".cs" #>
<#@ assembly name="System.Core" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Linq" #>
<#@ include file="Helpers.ttinclude" #>
using Ghost.Core;
using Ghost.Entities.Components;
using Ghost.Entities.Query;
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections;
namespace Ghost.Entities;
<# for (int arity = 1; arity <= Amount; arity++) {
var generics = AppendGenerics(arity);
var restrictions = AppendGenericRestrictions(arity, "unmanaged, IComponentData");
var poolParams = Enumerable.Range(0, arity)
.Select(i => $"ComponentPool<T{i}> pool{i}")
.Aggregate((a, b) => a + ", " + b);
var constructorParams = Enumerable.Range(0, arity)
.Select(i => $"_pool{i}")
.Aggregate((a, b) => a + ", " + b);
#>
public unsafe ref struct QueryEnumerable<<#= generics #>>
<#= restrictions #>
{
private QueryFilter _filter;
private readonly World _world;
<# for (int i = 0; i < arity; i++){ #>
private readonly ComponentPool<T<#= i #>> _pool<#= i #>;
<# } #>
private readonly int _count;
internal QueryEnumerable(World world, <#= poolParams #>, int count)
{
_filter = new();
<# for (int i = 0; i < arity; i++) {#>
_filter._all.Add(TypeHandle.Get<T<#= i #>>());
<# } #>
_world = world;
<# for (int i = 0; i < arity; i++) { #>
_pool<#= i #> = pool<#= i #>;
<# } #>
_count = count;
}
internal QueryEnumerable(World world, <#= poolParams #>, int count, ref readonly QueryFilter filter)
{
_filter = filter;
_world = world;
<# for (int i = 0; i < arity; i++) { #>
_pool<#= i #> = pool<#= i #>;
<# } #>
_count = count;
}
#pragma warning disable CS9084 // Struct member returns 'this' or other instance members by reference
public Enumerator GetEnumerator() => new(_world, <#= constructorParams #>, _count, ref _filter);
#pragma warning restore CS9084 // Struct member returns 'this' or other instance members by reference
public ref struct Enumerator
{
private ref QueryFilter _filter;
private UnsafeBitSet _filterMask;
private readonly ReadOnlySpan<Entity> _entities;
private readonly Stack.Scope _stackScope;
<# for (int i = 0; i < arity; i++){ #>
private readonly ComponentPool<T<#= i #>> _pool<#= i #>;
<# } #>
private int _index;
private int _count;
public QueryItem<<#= generics #>> Current
{
get;
private set;
}
internal Enumerator(World world, <#= poolParams #>, int count, ref QueryFilter filter)
{
_stackScope = AllocationManager.CreateStackScope();
_filter = ref filter;
_filterMask = _filter.ComputeFilterBitMask(world, Allocator.Stack);
_entities = world.EntityManager.Entities;
<# for (int i = 0; i < arity; i++){ #>
_pool<#= i #> = pool<#= i #>;
<# } #>
_count = count;
_index = -1;
Current = default;
}
public bool MoveNext()
{
_index = _filterMask.NextSetBit(_index + 1);
if (_index < 0 || _count <= 0)
{
return false;
}
_count--;
Current = new QueryItem<<#= generics #>>(_entities[_index], <#= constructorParams #>);
return true;
}
public readonly void Dispose()
{
_stackScope.Dispose();
_filter.Dispose();
}
}
<# for (int i = 1; i <= ExtensionAmount; i++) {
var compGenerics = AppendGenerics(i, "TComponent");
var compRestrictions = AppendGenericRestrictions(i, "TComponent", "unmanaged, IComponentData");
#>
public QueryEnumerable<<#= generics #>> WithAll<<#= compGenerics #>>()
<#= compRestrictions #>
{
<# for (int j = 0; j < i; j++) {#>
_filter._all.Add(TypeHandle.Get<TComponent<#= j #>>());
<# } #>
return this;
}
public QueryEnumerable<<#= generics #>> WithAny<<#= compGenerics #>>()
<#= compRestrictions #>
{
<# for (int j = 0; j < i; j++) {#>
_filter._any.Add(TypeHandle.Get<TComponent<#= j #>>());
<# } #>
return this;
}
public QueryEnumerable<<#= generics #>> WithAbsent<<#= compGenerics #>>()
<#= compRestrictions #>
{
<# for (int j = 0; j < i; j++) {#>
_filter._absent.Add(TypeHandle.Get<TComponent<#= j #>>());
<# } #>
return this;
}
public QueryEnumerable<<#= generics #>> WithDisabled<<#= compGenerics #>>()
<#= compRestrictions #>
{
<# for (int j = 0; j < i; j++) {#>
_filter._disabled.Add(TypeHandle.Get<TComponent<#= j #>>());
<# } #>
return this;
}
<# } #>
}
<# } #>

View File

@@ -1,319 +0,0 @@
using Ghost.Entities.Components;
using Ghost.Entities.Query;
namespace Ghost.Entities;
public readonly struct QueryItem<T0>
where T0 : unmanaged, IComponentData
{
private readonly Entity _entity;
private readonly ComponentPool<T0> _pool0;
internal QueryItem(Entity entity, ComponentPool<T0> pool0)
{
_entity = entity;
_pool0 = pool0;
}
public Entity Entity => _entity;
public ref T0 Component0 => ref _pool0.GetRef(_entity);
// Deconstruct into tuple-like values
public void Deconstruct(out Entity entity, out CompRef<T0> c0)
{
entity = _entity;
c0 = new(ref _pool0.GetRef(_entity));
}
}
public readonly struct QueryItem<T0, T1>
where T0 : unmanaged, IComponentData where T1 : unmanaged, IComponentData
{
private readonly Entity _entity;
private readonly ComponentPool<T0> _pool0;
private readonly ComponentPool<T1> _pool1;
internal QueryItem(Entity entity, ComponentPool<T0> pool0, ComponentPool<T1> pool1)
{
_entity = entity;
_pool0 = pool0;
_pool1 = pool1;
}
public Entity Entity => _entity;
public ref T0 Component0 => ref _pool0.GetRef(_entity);
public ref T1 Component1 => ref _pool1.GetRef(_entity);
// Deconstruct into tuple-like values
public void Deconstruct(out Entity entity, out CompRef<T0> c0, out CompRef<T1> c1)
{
entity = _entity;
c0 = new(ref _pool0.GetRef(_entity));
c1 = new(ref _pool1.GetRef(_entity));
}
}
public readonly struct QueryItem<T0, T1, T2>
where T0 : unmanaged, IComponentData where T1 : unmanaged, IComponentData where T2 : unmanaged, IComponentData
{
private readonly Entity _entity;
private readonly ComponentPool<T0> _pool0;
private readonly ComponentPool<T1> _pool1;
private readonly ComponentPool<T2> _pool2;
internal QueryItem(Entity entity, ComponentPool<T0> pool0, ComponentPool<T1> pool1, ComponentPool<T2> pool2)
{
_entity = entity;
_pool0 = pool0;
_pool1 = pool1;
_pool2 = pool2;
}
public Entity Entity => _entity;
public ref T0 Component0 => ref _pool0.GetRef(_entity);
public ref T1 Component1 => ref _pool1.GetRef(_entity);
public ref T2 Component2 => ref _pool2.GetRef(_entity);
// Deconstruct into tuple-like values
public void Deconstruct(out Entity entity, out CompRef<T0> c0, out CompRef<T1> c1, out CompRef<T2> c2)
{
entity = _entity;
c0 = new(ref _pool0.GetRef(_entity));
c1 = new(ref _pool1.GetRef(_entity));
c2 = new(ref _pool2.GetRef(_entity));
}
}
public readonly struct QueryItem<T0, T1, T2, T3>
where T0 : unmanaged, IComponentData where T1 : unmanaged, IComponentData where T2 : unmanaged, IComponentData where T3 : unmanaged, IComponentData
{
private readonly Entity _entity;
private readonly ComponentPool<T0> _pool0;
private readonly ComponentPool<T1> _pool1;
private readonly ComponentPool<T2> _pool2;
private readonly ComponentPool<T3> _pool3;
internal QueryItem(Entity entity, ComponentPool<T0> pool0, ComponentPool<T1> pool1, ComponentPool<T2> pool2, ComponentPool<T3> pool3)
{
_entity = entity;
_pool0 = pool0;
_pool1 = pool1;
_pool2 = pool2;
_pool3 = pool3;
}
public Entity Entity => _entity;
public ref T0 Component0 => ref _pool0.GetRef(_entity);
public ref T1 Component1 => ref _pool1.GetRef(_entity);
public ref T2 Component2 => ref _pool2.GetRef(_entity);
public ref T3 Component3 => ref _pool3.GetRef(_entity);
// Deconstruct into tuple-like values
public void Deconstruct(out Entity entity, out CompRef<T0> c0, out CompRef<T1> c1, out CompRef<T2> c2, out CompRef<T3> c3)
{
entity = _entity;
c0 = new(ref _pool0.GetRef(_entity));
c1 = new(ref _pool1.GetRef(_entity));
c2 = new(ref _pool2.GetRef(_entity));
c3 = new(ref _pool3.GetRef(_entity));
}
}
public readonly struct QueryItem<T0, T1, T2, T3, T4>
where T0 : unmanaged, IComponentData where T1 : unmanaged, IComponentData where T2 : unmanaged, IComponentData where T3 : unmanaged, IComponentData where T4 : unmanaged, IComponentData
{
private readonly Entity _entity;
private readonly ComponentPool<T0> _pool0;
private readonly ComponentPool<T1> _pool1;
private readonly ComponentPool<T2> _pool2;
private readonly ComponentPool<T3> _pool3;
private readonly ComponentPool<T4> _pool4;
internal QueryItem(Entity entity, ComponentPool<T0> pool0, ComponentPool<T1> pool1, ComponentPool<T2> pool2, ComponentPool<T3> pool3, ComponentPool<T4> pool4)
{
_entity = entity;
_pool0 = pool0;
_pool1 = pool1;
_pool2 = pool2;
_pool3 = pool3;
_pool4 = pool4;
}
public Entity Entity => _entity;
public ref T0 Component0 => ref _pool0.GetRef(_entity);
public ref T1 Component1 => ref _pool1.GetRef(_entity);
public ref T2 Component2 => ref _pool2.GetRef(_entity);
public ref T3 Component3 => ref _pool3.GetRef(_entity);
public ref T4 Component4 => ref _pool4.GetRef(_entity);
// Deconstruct into tuple-like values
public void Deconstruct(out Entity entity, out CompRef<T0> c0, out CompRef<T1> c1, out CompRef<T2> c2, out CompRef<T3> c3, out CompRef<T4> c4)
{
entity = _entity;
c0 = new(ref _pool0.GetRef(_entity));
c1 = new(ref _pool1.GetRef(_entity));
c2 = new(ref _pool2.GetRef(_entity));
c3 = new(ref _pool3.GetRef(_entity));
c4 = new(ref _pool4.GetRef(_entity));
}
}
public readonly struct QueryItem<T0, T1, T2, T3, T4, T5>
where T0 : unmanaged, IComponentData where T1 : unmanaged, IComponentData where T2 : unmanaged, IComponentData where T3 : unmanaged, IComponentData where T4 : unmanaged, IComponentData where T5 : unmanaged, IComponentData
{
private readonly Entity _entity;
private readonly ComponentPool<T0> _pool0;
private readonly ComponentPool<T1> _pool1;
private readonly ComponentPool<T2> _pool2;
private readonly ComponentPool<T3> _pool3;
private readonly ComponentPool<T4> _pool4;
private readonly ComponentPool<T5> _pool5;
internal QueryItem(Entity entity, ComponentPool<T0> pool0, ComponentPool<T1> pool1, ComponentPool<T2> pool2, ComponentPool<T3> pool3, ComponentPool<T4> pool4, ComponentPool<T5> pool5)
{
_entity = entity;
_pool0 = pool0;
_pool1 = pool1;
_pool2 = pool2;
_pool3 = pool3;
_pool4 = pool4;
_pool5 = pool5;
}
public Entity Entity => _entity;
public ref T0 Component0 => ref _pool0.GetRef(_entity);
public ref T1 Component1 => ref _pool1.GetRef(_entity);
public ref T2 Component2 => ref _pool2.GetRef(_entity);
public ref T3 Component3 => ref _pool3.GetRef(_entity);
public ref T4 Component4 => ref _pool4.GetRef(_entity);
public ref T5 Component5 => ref _pool5.GetRef(_entity);
// Deconstruct into tuple-like values
public void Deconstruct(out Entity entity, out CompRef<T0> c0, out CompRef<T1> c1, out CompRef<T2> c2, out CompRef<T3> c3, out CompRef<T4> c4, out CompRef<T5> c5)
{
entity = _entity;
c0 = new(ref _pool0.GetRef(_entity));
c1 = new(ref _pool1.GetRef(_entity));
c2 = new(ref _pool2.GetRef(_entity));
c3 = new(ref _pool3.GetRef(_entity));
c4 = new(ref _pool4.GetRef(_entity));
c5 = new(ref _pool5.GetRef(_entity));
}
}
public readonly struct QueryItem<T0, T1, T2, T3, T4, T5, T6>
where T0 : unmanaged, IComponentData where T1 : unmanaged, IComponentData where T2 : unmanaged, IComponentData where T3 : unmanaged, IComponentData where T4 : unmanaged, IComponentData where T5 : unmanaged, IComponentData where T6 : unmanaged, IComponentData
{
private readonly Entity _entity;
private readonly ComponentPool<T0> _pool0;
private readonly ComponentPool<T1> _pool1;
private readonly ComponentPool<T2> _pool2;
private readonly ComponentPool<T3> _pool3;
private readonly ComponentPool<T4> _pool4;
private readonly ComponentPool<T5> _pool5;
private readonly ComponentPool<T6> _pool6;
internal QueryItem(Entity entity, ComponentPool<T0> pool0, ComponentPool<T1> pool1, ComponentPool<T2> pool2, ComponentPool<T3> pool3, ComponentPool<T4> pool4, ComponentPool<T5> pool5, ComponentPool<T6> pool6)
{
_entity = entity;
_pool0 = pool0;
_pool1 = pool1;
_pool2 = pool2;
_pool3 = pool3;
_pool4 = pool4;
_pool5 = pool5;
_pool6 = pool6;
}
public Entity Entity => _entity;
public ref T0 Component0 => ref _pool0.GetRef(_entity);
public ref T1 Component1 => ref _pool1.GetRef(_entity);
public ref T2 Component2 => ref _pool2.GetRef(_entity);
public ref T3 Component3 => ref _pool3.GetRef(_entity);
public ref T4 Component4 => ref _pool4.GetRef(_entity);
public ref T5 Component5 => ref _pool5.GetRef(_entity);
public ref T6 Component6 => ref _pool6.GetRef(_entity);
// Deconstruct into tuple-like values
public void Deconstruct(out Entity entity, out CompRef<T0> c0, out CompRef<T1> c1, out CompRef<T2> c2, out CompRef<T3> c3, out CompRef<T4> c4, out CompRef<T5> c5, out CompRef<T6> c6)
{
entity = _entity;
c0 = new(ref _pool0.GetRef(_entity));
c1 = new(ref _pool1.GetRef(_entity));
c2 = new(ref _pool2.GetRef(_entity));
c3 = new(ref _pool3.GetRef(_entity));
c4 = new(ref _pool4.GetRef(_entity));
c5 = new(ref _pool5.GetRef(_entity));
c6 = new(ref _pool6.GetRef(_entity));
}
}
public readonly struct QueryItem<T0, T1, T2, T3, T4, T5, T6, T7>
where T0 : unmanaged, IComponentData where T1 : unmanaged, IComponentData where T2 : unmanaged, IComponentData where T3 : unmanaged, IComponentData where T4 : unmanaged, IComponentData where T5 : unmanaged, IComponentData where T6 : unmanaged, IComponentData where T7 : unmanaged, IComponentData
{
private readonly Entity _entity;
private readonly ComponentPool<T0> _pool0;
private readonly ComponentPool<T1> _pool1;
private readonly ComponentPool<T2> _pool2;
private readonly ComponentPool<T3> _pool3;
private readonly ComponentPool<T4> _pool4;
private readonly ComponentPool<T5> _pool5;
private readonly ComponentPool<T6> _pool6;
private readonly ComponentPool<T7> _pool7;
internal QueryItem(Entity entity, ComponentPool<T0> pool0, ComponentPool<T1> pool1, ComponentPool<T2> pool2, ComponentPool<T3> pool3, ComponentPool<T4> pool4, ComponentPool<T5> pool5, ComponentPool<T6> pool6, ComponentPool<T7> pool7)
{
_entity = entity;
_pool0 = pool0;
_pool1 = pool1;
_pool2 = pool2;
_pool3 = pool3;
_pool4 = pool4;
_pool5 = pool5;
_pool6 = pool6;
_pool7 = pool7;
}
public Entity Entity => _entity;
public ref T0 Component0 => ref _pool0.GetRef(_entity);
public ref T1 Component1 => ref _pool1.GetRef(_entity);
public ref T2 Component2 => ref _pool2.GetRef(_entity);
public ref T3 Component3 => ref _pool3.GetRef(_entity);
public ref T4 Component4 => ref _pool4.GetRef(_entity);
public ref T5 Component5 => ref _pool5.GetRef(_entity);
public ref T6 Component6 => ref _pool6.GetRef(_entity);
public ref T7 Component7 => ref _pool7.GetRef(_entity);
// Deconstruct into tuple-like values
public void Deconstruct(out Entity entity, out CompRef<T0> c0, out CompRef<T1> c1, out CompRef<T2> c2, out CompRef<T3> c3, out CompRef<T4> c4, out CompRef<T5> c5, out CompRef<T6> c6, out CompRef<T7> c7)
{
entity = _entity;
c0 = new(ref _pool0.GetRef(_entity));
c1 = new(ref _pool1.GetRef(_entity));
c2 = new(ref _pool2.GetRef(_entity));
c3 = new(ref _pool3.GetRef(_entity));
c4 = new(ref _pool4.GetRef(_entity));
c5 = new(ref _pool5.GetRef(_entity));
c6 = new(ref _pool6.GetRef(_entity));
c7 = new(ref _pool7.GetRef(_entity));
}
}

View File

@@ -1,61 +0,0 @@
<#@ template language="C#" debug="false" hostspecific="true" #>
<#@ assembly name="System.Core" #>
<#@ import namespace="System.Linq" #>
<#@ include file="Helpers.ttinclude" #>
<#@ output extension=".cs" #>
using Ghost.Entities.Components;
using Ghost.Entities.Query;
namespace Ghost.Entities;
<# for (int arity = 1; arity <= Amount; arity++)
{
var generics = AppendGenerics(arity);
var restrictions = AppendGenericRestrictions(arity, "unmanaged, IComponentData");
var constructorParams = Enumerable.Range(0, arity)
.Select(i => $"ComponentPool<T{i}> pool{i}")
.Aggregate((a, b) => a + ", " + b);
var deconstructParams = Enumerable.Range(0, arity)
.Select(i => {
var name = $"c{i}";
return arity == 1
? $"out CompRef<T0> {name}"
: $"out CompRef<T{i}> {name}";
})
.Aggregate((a, b) => a + ", " + b);
#>
public readonly struct QueryItem<<#= generics #>>
<#= restrictions #>
{
private readonly Entity _entity;
<# for (int i = 0; i < arity; i++){ #>
private readonly ComponentPool<T<#= i #>> _pool<#= i #>;
<# } #>
internal QueryItem(Entity entity, <#= constructorParams #>)
{
_entity = entity;
<# for (int i = 0; i < arity; i++){ #>
_pool<#= i #> = pool<#= i #>;
<# } #>
}
public Entity Entity => _entity;
<# for (int i = 0; i < arity; i++){ #>
public ref T<#= i #> Component<#= i #> => ref _pool<#= i #>.GetRef(_entity);
<# } #>
// Deconstruct into tuple-like values
public void Deconstruct(out Entity entity, <#= deconstructParams #>)
{
entity = _entity;
<# for (int i = 0; i < arity; i++){ #>
c<#= i #> = new (ref _pool<#= i #>.GetRef(_entity));
<# } #>
}
}
<# } #>

View File

@@ -1,22 +0,0 @@
using Ghost.Entities.Components;
namespace Ghost.Entities;
public delegate void QueryRefComponent<T0>(Entity entity, ref T0 t0Component)
where T0 : unmanaged, IComponentData;
public delegate void QueryRefComponent<T0, T1>(Entity entity, ref T0 t0Component, ref T1 t1Component)
where T0 : unmanaged, IComponentData where T1 : unmanaged, IComponentData;
public delegate void QueryRefComponent<T0, T1, T2>(Entity entity, ref T0 t0Component, ref T1 t1Component, ref T2 t2Component)
where T0 : unmanaged, IComponentData where T1 : unmanaged, IComponentData where T2 : unmanaged, IComponentData;
public delegate void QueryRefComponent<T0, T1, T2, T3>(Entity entity, ref T0 t0Component, ref T1 t1Component, ref T2 t2Component, ref T3 t3Component)
where T0 : unmanaged, IComponentData where T1 : unmanaged, IComponentData where T2 : unmanaged, IComponentData where T3 : unmanaged, IComponentData;
public delegate void QueryRefComponent<T0, T1, T2, T3, T4>(Entity entity, ref T0 t0Component, ref T1 t1Component, ref T2 t2Component, ref T3 t3Component, ref T4 t4Component)
where T0 : unmanaged, IComponentData where T1 : unmanaged, IComponentData where T2 : unmanaged, IComponentData where T3 : unmanaged, IComponentData where T4 : unmanaged, IComponentData;
public delegate void QueryRefComponent<T0, T1, T2, T3, T4, T5>(Entity entity, ref T0 t0Component, ref T1 t1Component, ref T2 t2Component, ref T3 t3Component, ref T4 t4Component, ref T5 t5Component)
where T0 : unmanaged, IComponentData where T1 : unmanaged, IComponentData where T2 : unmanaged, IComponentData where T3 : unmanaged, IComponentData where T4 : unmanaged, IComponentData where T5 : unmanaged, IComponentData;
public delegate void QueryRefComponent<T0, T1, T2, T3, T4, T5, T6>(Entity entity, ref T0 t0Component, ref T1 t1Component, ref T2 t2Component, ref T3 t3Component, ref T4 t4Component, ref T5 t5Component, ref T6 t6Component)
where T0 : unmanaged, IComponentData where T1 : unmanaged, IComponentData where T2 : unmanaged, IComponentData where T3 : unmanaged, IComponentData where T4 : unmanaged, IComponentData where T5 : unmanaged, IComponentData where T6 : unmanaged, IComponentData;
public delegate void QueryRefComponent<T0, T1, T2, T3, T4, T5, T6, T7>(Entity entity, ref T0 t0Component, ref T1 t1Component, ref T2 t2Component, ref T3 t3Component, ref T4 t4Component, ref T5 t5Component, ref T6 t6Component, ref T7 t7Component)
where T0 : unmanaged, IComponentData where T1 : unmanaged, IComponentData where T2 : unmanaged, IComponentData where T3 : unmanaged, IComponentData where T4 : unmanaged, IComponentData where T5 : unmanaged, IComponentData where T6 : unmanaged, IComponentData where T7 : unmanaged, IComponentData;

View File

@@ -1,21 +0,0 @@
<#@ template language="C#" #>
<#@ output extension=".cs" #>
<#@ import namespace="System.Text" #>
<#@ include file="Helpers.ttinclude" #>
using Ghost.Entities.Components;
namespace Ghost.Entities;
<#
for (var index = 1; index <= Amount; index++)
{
var generics = AppendGenerics(index);
var parameters = AppendGenericRefParameters(index);
var restrictions = AppendGenericRestrictions(index, "unmanaged, IComponentData");
#>
public delegate void QueryRefComponent<<#= generics #>>(Entity entity, <#= parameters.ToString() #>)
<#= restrictions.ToString() #>;
<#
}
#>

View File

@@ -1,242 +0,0 @@
using Ghost.Entities.Components;
using Ghost.Entities.Query;
namespace Ghost.Entities;
public partial class World
{
public QueryEnumerable<T0> Query<T0>()
where T0 : unmanaged, IComponentData
{
if (!(_componentStorage.TryGetPool<T0>(out var pool0)))
{
return default;
}
return new QueryEnumerable<T0>(
this,
pool0,
pool0.Count);
}
public QueryEnumerable<T0> QueryFilter<T0>(ref readonly QueryFilter filter)
where T0 : unmanaged, IComponentData
{
if (!(_componentStorage.TryGetPool<T0>(out var pool0)))
{
return default;
}
return new QueryEnumerable<T0>(
this,
pool0,
pool0.Count,
in filter);
}
public QueryEnumerable<T0, T1> Query<T0, T1>()
where T0 : unmanaged, IComponentData where T1 : unmanaged, IComponentData
{
if (!(_componentStorage.TryGetPool<T0>(out var pool0) && _componentStorage.TryGetPool<T1>(out var pool1)))
{
return default;
}
return new QueryEnumerable<T0, T1>(
this,
pool0, pool1,
pool0.Count);
}
public QueryEnumerable<T0, T1> QueryFilter<T0, T1>(ref readonly QueryFilter filter)
where T0 : unmanaged, IComponentData where T1 : unmanaged, IComponentData
{
if (!(_componentStorage.TryGetPool<T0>(out var pool0) && _componentStorage.TryGetPool<T1>(out var pool1)))
{
return default;
}
return new QueryEnumerable<T0, T1>(
this,
pool0, pool1,
pool0.Count,
in filter);
}
public QueryEnumerable<T0, T1, T2> Query<T0, T1, T2>()
where T0 : unmanaged, IComponentData where T1 : unmanaged, IComponentData where T2 : unmanaged, IComponentData
{
if (!(_componentStorage.TryGetPool<T0>(out var pool0) && _componentStorage.TryGetPool<T1>(out var pool1) && _componentStorage.TryGetPool<T2>(out var pool2)))
{
return default;
}
return new QueryEnumerable<T0, T1, T2>(
this,
pool0, pool1, pool2,
pool0.Count);
}
public QueryEnumerable<T0, T1, T2> QueryFilter<T0, T1, T2>(ref readonly QueryFilter filter)
where T0 : unmanaged, IComponentData where T1 : unmanaged, IComponentData where T2 : unmanaged, IComponentData
{
if (!(_componentStorage.TryGetPool<T0>(out var pool0) && _componentStorage.TryGetPool<T1>(out var pool1) && _componentStorage.TryGetPool<T2>(out var pool2)))
{
return default;
}
return new QueryEnumerable<T0, T1, T2>(
this,
pool0, pool1, pool2,
pool0.Count,
in filter);
}
public QueryEnumerable<T0, T1, T2, T3> Query<T0, T1, T2, T3>()
where T0 : unmanaged, IComponentData where T1 : unmanaged, IComponentData where T2 : unmanaged, IComponentData where T3 : unmanaged, IComponentData
{
if (!(_componentStorage.TryGetPool<T0>(out var pool0) && _componentStorage.TryGetPool<T1>(out var pool1) && _componentStorage.TryGetPool<T2>(out var pool2) && _componentStorage.TryGetPool<T3>(out var pool3)))
{
return default;
}
return new QueryEnumerable<T0, T1, T2, T3>(
this,
pool0, pool1, pool2, pool3,
pool0.Count);
}
public QueryEnumerable<T0, T1, T2, T3> QueryFilter<T0, T1, T2, T3>(ref readonly QueryFilter filter)
where T0 : unmanaged, IComponentData where T1 : unmanaged, IComponentData where T2 : unmanaged, IComponentData where T3 : unmanaged, IComponentData
{
if (!(_componentStorage.TryGetPool<T0>(out var pool0) && _componentStorage.TryGetPool<T1>(out var pool1) && _componentStorage.TryGetPool<T2>(out var pool2) && _componentStorage.TryGetPool<T3>(out var pool3)))
{
return default;
}
return new QueryEnumerable<T0, T1, T2, T3>(
this,
pool0, pool1, pool2, pool3,
pool0.Count,
in filter);
}
public QueryEnumerable<T0, T1, T2, T3, T4> Query<T0, T1, T2, T3, T4>()
where T0 : unmanaged, IComponentData where T1 : unmanaged, IComponentData where T2 : unmanaged, IComponentData where T3 : unmanaged, IComponentData where T4 : unmanaged, IComponentData
{
if (!(_componentStorage.TryGetPool<T0>(out var pool0) && _componentStorage.TryGetPool<T1>(out var pool1) && _componentStorage.TryGetPool<T2>(out var pool2) && _componentStorage.TryGetPool<T3>(out var pool3) && _componentStorage.TryGetPool<T4>(out var pool4)))
{
return default;
}
return new QueryEnumerable<T0, T1, T2, T3, T4>(
this,
pool0, pool1, pool2, pool3, pool4,
pool0.Count);
}
public QueryEnumerable<T0, T1, T2, T3, T4> QueryFilter<T0, T1, T2, T3, T4>(ref readonly QueryFilter filter)
where T0 : unmanaged, IComponentData where T1 : unmanaged, IComponentData where T2 : unmanaged, IComponentData where T3 : unmanaged, IComponentData where T4 : unmanaged, IComponentData
{
if (!(_componentStorage.TryGetPool<T0>(out var pool0) && _componentStorage.TryGetPool<T1>(out var pool1) && _componentStorage.TryGetPool<T2>(out var pool2) && _componentStorage.TryGetPool<T3>(out var pool3) && _componentStorage.TryGetPool<T4>(out var pool4)))
{
return default;
}
return new QueryEnumerable<T0, T1, T2, T3, T4>(
this,
pool0, pool1, pool2, pool3, pool4,
pool0.Count,
in filter);
}
public QueryEnumerable<T0, T1, T2, T3, T4, T5> Query<T0, T1, T2, T3, T4, T5>()
where T0 : unmanaged, IComponentData where T1 : unmanaged, IComponentData where T2 : unmanaged, IComponentData where T3 : unmanaged, IComponentData where T4 : unmanaged, IComponentData where T5 : unmanaged, IComponentData
{
if (!(_componentStorage.TryGetPool<T0>(out var pool0) && _componentStorage.TryGetPool<T1>(out var pool1) && _componentStorage.TryGetPool<T2>(out var pool2) && _componentStorage.TryGetPool<T3>(out var pool3) && _componentStorage.TryGetPool<T4>(out var pool4) && _componentStorage.TryGetPool<T5>(out var pool5)))
{
return default;
}
return new QueryEnumerable<T0, T1, T2, T3, T4, T5>(
this,
pool0, pool1, pool2, pool3, pool4, pool5,
pool0.Count);
}
public QueryEnumerable<T0, T1, T2, T3, T4, T5> QueryFilter<T0, T1, T2, T3, T4, T5>(ref readonly QueryFilter filter)
where T0 : unmanaged, IComponentData where T1 : unmanaged, IComponentData where T2 : unmanaged, IComponentData where T3 : unmanaged, IComponentData where T4 : unmanaged, IComponentData where T5 : unmanaged, IComponentData
{
if (!(_componentStorage.TryGetPool<T0>(out var pool0) && _componentStorage.TryGetPool<T1>(out var pool1) && _componentStorage.TryGetPool<T2>(out var pool2) && _componentStorage.TryGetPool<T3>(out var pool3) && _componentStorage.TryGetPool<T4>(out var pool4) && _componentStorage.TryGetPool<T5>(out var pool5)))
{
return default;
}
return new QueryEnumerable<T0, T1, T2, T3, T4, T5>(
this,
pool0, pool1, pool2, pool3, pool4, pool5,
pool0.Count,
in filter);
}
public QueryEnumerable<T0, T1, T2, T3, T4, T5, T6> Query<T0, T1, T2, T3, T4, T5, T6>()
where T0 : unmanaged, IComponentData where T1 : unmanaged, IComponentData where T2 : unmanaged, IComponentData where T3 : unmanaged, IComponentData where T4 : unmanaged, IComponentData where T5 : unmanaged, IComponentData where T6 : unmanaged, IComponentData
{
if (!(_componentStorage.TryGetPool<T0>(out var pool0) && _componentStorage.TryGetPool<T1>(out var pool1) && _componentStorage.TryGetPool<T2>(out var pool2) && _componentStorage.TryGetPool<T3>(out var pool3) && _componentStorage.TryGetPool<T4>(out var pool4) && _componentStorage.TryGetPool<T5>(out var pool5) && _componentStorage.TryGetPool<T6>(out var pool6)))
{
return default;
}
return new QueryEnumerable<T0, T1, T2, T3, T4, T5, T6>(
this,
pool0, pool1, pool2, pool3, pool4, pool5, pool6,
pool0.Count);
}
public QueryEnumerable<T0, T1, T2, T3, T4, T5, T6> QueryFilter<T0, T1, T2, T3, T4, T5, T6>(ref readonly QueryFilter filter)
where T0 : unmanaged, IComponentData where T1 : unmanaged, IComponentData where T2 : unmanaged, IComponentData where T3 : unmanaged, IComponentData where T4 : unmanaged, IComponentData where T5 : unmanaged, IComponentData where T6 : unmanaged, IComponentData
{
if (!(_componentStorage.TryGetPool<T0>(out var pool0) && _componentStorage.TryGetPool<T1>(out var pool1) && _componentStorage.TryGetPool<T2>(out var pool2) && _componentStorage.TryGetPool<T3>(out var pool3) && _componentStorage.TryGetPool<T4>(out var pool4) && _componentStorage.TryGetPool<T5>(out var pool5) && _componentStorage.TryGetPool<T6>(out var pool6)))
{
return default;
}
return new QueryEnumerable<T0, T1, T2, T3, T4, T5, T6>(
this,
pool0, pool1, pool2, pool3, pool4, pool5, pool6,
pool0.Count,
in filter);
}
public QueryEnumerable<T0, T1, T2, T3, T4, T5, T6, T7> Query<T0, T1, T2, T3, T4, T5, T6, T7>()
where T0 : unmanaged, IComponentData where T1 : unmanaged, IComponentData where T2 : unmanaged, IComponentData where T3 : unmanaged, IComponentData where T4 : unmanaged, IComponentData where T5 : unmanaged, IComponentData where T6 : unmanaged, IComponentData where T7 : unmanaged, IComponentData
{
if (!(_componentStorage.TryGetPool<T0>(out var pool0) && _componentStorage.TryGetPool<T1>(out var pool1) && _componentStorage.TryGetPool<T2>(out var pool2) && _componentStorage.TryGetPool<T3>(out var pool3) && _componentStorage.TryGetPool<T4>(out var pool4) && _componentStorage.TryGetPool<T5>(out var pool5) && _componentStorage.TryGetPool<T6>(out var pool6) && _componentStorage.TryGetPool<T7>(out var pool7)))
{
return default;
}
return new QueryEnumerable<T0, T1, T2, T3, T4, T5, T6, T7>(
this,
pool0, pool1, pool2, pool3, pool4, pool5, pool6, pool7,
pool0.Count);
}
public QueryEnumerable<T0, T1, T2, T3, T4, T5, T6, T7> QueryFilter<T0, T1, T2, T3, T4, T5, T6, T7>(ref readonly QueryFilter filter)
where T0 : unmanaged, IComponentData where T1 : unmanaged, IComponentData where T2 : unmanaged, IComponentData where T3 : unmanaged, IComponentData where T4 : unmanaged, IComponentData where T5 : unmanaged, IComponentData where T6 : unmanaged, IComponentData where T7 : unmanaged, IComponentData
{
if (!(_componentStorage.TryGetPool<T0>(out var pool0) && _componentStorage.TryGetPool<T1>(out var pool1) && _componentStorage.TryGetPool<T2>(out var pool2) && _componentStorage.TryGetPool<T3>(out var pool3) && _componentStorage.TryGetPool<T4>(out var pool4) && _componentStorage.TryGetPool<T5>(out var pool5) && _componentStorage.TryGetPool<T6>(out var pool6) && _componentStorage.TryGetPool<T7>(out var pool7)))
{
return default;
}
return new QueryEnumerable<T0, T1, T2, T3, T4, T5, T6, T7>(
this,
pool0, pool1, pool2, pool3, pool4, pool5, pool6, pool7,
pool0.Count,
in filter);
}
}

View File

@@ -1,54 +0,0 @@
<#@ template language="C#" #>
<#@ output extension=".cs" #>
<#@ assembly name="System.Core" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text" #>
<#@ include file="Helpers.ttinclude" #>
using Ghost.Entities.Components;
using Ghost.Entities.Query;
namespace Ghost.Entities;
public partial class World
{
<# for (var index = 1; index <= Amount; index++) {
var generics = AppendGenerics(index);
var restrictions = AppendGenericRestrictions(index, "unmanaged, IComponentData");
var tryGetPools = TryGetComponentPools(index);
var poolParams = Enumerable.Range(0, index)
.Select(i => $"pool{i}")
.Aggregate((a,b)=> a + ", " + b);
var countSource = "pool0.Count";
#>
public QueryEnumerable<<#= generics #>> Query<<#= generics #>>()
<#= restrictions #>
{
if (!(<#= tryGetPools #>))
{
return default;
}
return new QueryEnumerable<<#= generics #>>(
this,
<#= poolParams #>,
<#= countSource #>);
}
public QueryEnumerable<<#= generics #>> QueryFilter<<#= generics #>>(ref readonly QueryFilter filter)
<#= restrictions #>
{
if (!(<#= tryGetPools #>))
{
return default;
}
return new QueryEnumerable<<#= generics #>>(
this,
<#= poolParams #>,
<#= countSource #>,
in filter);
}
<# } #>
}

View File

@@ -1,17 +1,16 @@
using Ghost.Entities.Components;
using Ghost.Entities.Query;
using Ghost.Entities.Systems;
using System.Diagnostics;
using Ghost.Core;
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Ghost.Entities;
// TODO: Archetype system for better performance
public partial class World
{
private static List<World> s_worlds = new(4);
private static Queue<WorldID> s_freeWorldSlots = new();
private static List<World?> s_worlds = new(4);
private static Queue<Identifier<World>> s_freeWorldSlots = new();
internal static Identifier<Archetype> EmptyArchetypeID => new Identifier<Archetype>(0);
public static int WorldCount => s_worlds.Count - s_freeWorldSlots.Count;
@@ -21,53 +20,63 @@ public partial class World
{
if (s_freeWorldSlots.TryDequeue(out var index))
{
s_worlds[index] = new World(index, entityCapacity);
s_worlds[index.value] = new World(index, entityCapacity);
}
else
{
if (s_worlds.Count >= WorldID.MaxValue)
{
throw new InvalidOperationException("Maximum number of worlds reached");
}
index = (WorldID)s_worlds.Count;
index = new Identifier<World>(s_worlds.Count);
s_worlds.Add(new World(index, entityCapacity));
}
return s_worlds[index];
return s_worlds[index.value]!;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static World GetWorld(int index)
public static Result<World, ResultStatus> GetWorld(Identifier<World> id)
{
return s_worlds[index];
if (id.value < 0 || id.value >= s_worlds.Count)
{
return Result.Create(default(World)!, ResultStatus.NotFound);
}
var world = s_worlds[id.value];
if (world is null)
{
return Result.Create(default(World)!, ResultStatus.NotFound);
}
return Result.Create(world, ResultStatus.Success);
}
}
public partial class World : IDisposable, IEquatable<World>
public partial class World : IIdentifierType, IDisposable, IEquatable<World>
{
private readonly WorldID _id;
private readonly EntityManager _entityManager;
private readonly ComponentStorage _componentStorage;
private readonly SystemStorage _systemStorage;
private readonly Identifier<World> _id;
private bool _isDisposed = false;
private UnsafeList<Archetype> _archetypes;
private UnsafeHashMap<int, Identifier<Archetype>> _archetypeLookup; // Signature Hash to Archetype ID
private EntityManager _entityManager;
private EntityCommandBuffer _entityCommandBuffer;
internal ComponentStorage ComponentStorage => _componentStorage;
private bool _disposed = false;
public WorldID ID => _id;
public Identifier<World> ID => _id;
public EntityManager EntityManager => _entityManager;
public SystemStorage SystemStorage => _systemStorage;
public EntityCommandBuffer EntityCommandBuffer => _entityCommandBuffer;
public event Action<World, Entity, Type>? ComponentChanged;
private World(WorldID id, int entityCapacity)
private World(Identifier<World> id, int entityCapacity)
{
_id = id;
_archetypes = new UnsafeList<Archetype>(16, Allocator.Persistent);
_archetypeLookup = new UnsafeHashMap<int, Identifier<Archetype>>(16, Allocator.Persistent);
_entityManager = new EntityManager(this, entityCapacity);
_componentStorage = new ComponentStorage(this);
_systemStorage = new SystemStorage(this);
_entityCommandBuffer = new EntityCommandBuffer(_entityManager);
// Create the empty archetype
CreateArchetype(ReadOnlySpan<Identifier<IComponent>>.Empty, 0);
}
~World()
@@ -75,53 +84,33 @@ public partial class World : IDisposable, IEquatable<World>
Dispose();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public CompRef<T> GetSingleton<T>()
where T : unmanaged, IComponentData
internal Identifier<Archetype> CreateArchetype(ReadOnlySpan<Identifier<IComponent>> componentTypeIDs, int signatureHash)
{
ref var component = ref CollectionsMarshal.GetValueRefOrAddDefault(SingletonContainer<T>.container, _id, out _);
return new CompRef<T>(ref component);
var arcID = new Identifier<Archetype>(_archetypes.Count);
_archetypes.Add(new Archetype(arcID, _id, componentTypeIDs));
_archetypeLookup.Add(signatureHash, arcID);
return arcID;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public IEnumerable<ScriptComponent> QueryScript()
internal ref Archetype GetArchetypeReference(Identifier<Archetype> id)
{
if (_componentStorage.ScriptComponentPool.IsInitialized)
return ref _archetypes[id.value];
}
internal Identifier<Archetype> GetArchetypeIDBySignatureHash(int signatureHash)
{
if (_archetypeLookup.TryGetValue(signatureHash, out var arcID))
{
return _componentStorage.ScriptComponentPool.ExecutionList!;
return arcID;
}
return Enumerable.Empty<ScriptComponent>();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[Conditional("GHOST_EDITOR")]
public void NotifyComponentChanged(Entity entity, Type type)
{
ComponentChanged?.Invoke(this, entity, type);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[Conditional("GHOST_EDITOR")]
public void NotifyComponentChanged<T>(Entity entity)
where T : unmanaged, IComponentData
{
NotifyComponentChanged(entity, typeof(T));
return Identifier<Archetype>.Invalid;
}
public bool Equals(World? other)
{
if (other is null)
{
return false;
}
if (ReferenceEquals(this, other))
{
return true;
}
return _id == other._id;
return other is not null && _id == other._id;
}
public override int GetHashCode()
@@ -146,19 +135,21 @@ public partial class World : IDisposable, IEquatable<World>
public void Dispose()
{
if (_isDisposed)
if (_disposed)
{
return;
}
_entityManager.Dispose();
_componentStorage.Dispose();
_systemStorage.Dispose();
_archetypes.Dispose();
_archetypeLookup.Dispose();
s_freeWorldSlots.Enqueue(_id);
_isDisposed = true;
_disposed = true;
GC.SuppressFinalize(this);
}
}
}