Refactor folder structure

This commit is contained in:
2026-02-18 00:50:46 +09:00
parent 426786397c
commit db8ca971a8
413 changed files with 2885 additions and 3634 deletions

View File

@@ -0,0 +1,712 @@
using Ghost.Core;
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections;
using Misaki.HighPerformance.LowLevel.Utilities;
using System.Diagnostics;
using System.Runtime.CompilerServices;
namespace Ghost.Entities;
internal unsafe sealed class ChunkDebugView
{
[DebuggerDisplay("{Name,nq}: {Data}")]
internal class ComponentArrayView
{
public string Name { get; }
[DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
public object Data { get; }
public ComponentArrayView(string name, object data)
{
Name = name;
Data = data;
}
}
private Chunk _chunk;
public ChunkDebugView(Chunk chunk)
{
_chunk = chunk;
}
[DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
public object[] Items => GetItems(in _chunk);
private static T[] ReadComponentArray<T>(long pData, int offsetInChunk, int count)
where T : unmanaged
{
var result = new T[count];
unsafe
{
var basePtr = (byte*)pData + offsetInChunk;
var span = new Span<T>(basePtr, count);
span.CopyTo(result);
}
return result;
}
private static object[] GetItems(ref readonly Chunk chunk)
{
#if !(DEBUG || GHOST_EDITOR)
return [];
#else
var pData = chunk.GetUnsafePtr();
var count = chunk._count;
var capacity = chunk._capacity;
var worldID = chunk._worldID;
var archetypeID = chunk._archetypeID;
if (count == 0)
{
return [];
}
var views = new List<object>();
var world = World.GetWorld(worldID);
if (world is null)
{
return [];
}
ref var archetype = ref world.ComponentManager.GetArchetypeReference(archetypeID);
var it = archetype._signature.GetIterator();
while (it.Next(out var index))
{
var type = ComponentRegistry.s_runtimeIDToType[index];
if (type == null)
{
continue;
}
var layout = archetype.GetLayout(index).Value;
var readMethod = typeof(ChunkDebugView)
.GetMethod(nameof(ReadComponentArray), System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static)!
.MakeGenericMethod(type);
// 3. Invoke it to get a Position[] or Velocity[]
var array = readMethod.Invoke(null, [(long)pData, layout.offset, count]);
if (array == null)
{
continue;
}
// 4. Wrap it in a nice label so the debugger shows "Position[]"
views.Add(new ComponentArrayView(type.Name, array));
}
return [.. views];
#endif
}
}
[DebuggerTypeProxy(typeof(ChunkDebugView))]
internal unsafe struct Chunk : IDisposable
{
public const int CHUNK_BUFFER_SIZE = 16384; // 16 KB
public const int BIT_ALIGNMENT = 8;
public const int BIT_SHIFT = 3; // log2(BIT_ALIGNMENT)
public const int BIT_ALIGNMENT_MINUS_ONE = BIT_ALIGNMENT - 1;
private UnsafeArray<byte> _data;
private UnsafeArray<int> _versions;
// TODO: Add structual change versioning, similar to DidOrderChange in unity ecs.
internal int _structuralVersion;
internal int _count;
internal readonly int _capacity;
#if DEBUG || GHOST_EDITOR
// For debugging purpose
internal int _worldID;
internal int _archetypeID;
#endif
public Chunk(int bufferSize, int capacity, int componentCount, int globalVersion)
{
_data = new UnsafeArray<byte>(bufferSize, Allocator.Persistent, AllocationOption.Clear);
_versions = new UnsafeArray<int>(componentCount, Allocator.Persistent);
_capacity = capacity;
_count = 0;
_versions.AsSpan().Fill(globalVersion);
_structuralVersion = globalVersion;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly byte* GetUnsafePtr()
{
return (byte*)_data.GetUnsafePtr();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly int* GetVersionUnsafePtr()
{
return (int*)_versions.GetUnsafePtr();
}
public void Dispose()
{
_data.Dispose();
_versions.Dispose();
}
}
internal unsafe struct Archetype : IDisposable
{
internal struct ComponentMemoryLayout
{
public int componentID;
public int size;
public int offset;
public int enableBitsOffset;
public int versionIndex;
}
private struct Edge
{
public int componentID;
public int targetArchetype; // can't use Identifier<Archetype> because cycle causer
}
internal UnsafeBitSet _signature;
internal UnsafeList<Chunk> _chunks;
internal UnsafeArray<ComponentMemoryLayout> _layouts;
internal UnsafeArray<int> _componentIDToLayoutIndex;
// TODO: Is hash map better?
private UnsafeList<Edge> _edgesAdd;
private UnsafeList<Edge> _edgesRemove;
private readonly Identifier<Archetype> _id;
private readonly Identifier<World> _worldID;
private readonly int _hash;
private int _entityCapacity;
private int _maxComponentID;
private int _entityIdsOffset;
public readonly Identifier<Archetype> ID => _id;
public readonly Identifier<World> WorldID => _worldID;
public readonly int EntityCapacity => _entityCapacity;
public readonly int ChunkCount => _chunks.Count;
public readonly int EntityIDsOffset => _entityIdsOffset;
public Archetype(Identifier<Archetype> id, Identifier<World> worldID, ReadOnlySpan<Identifier<IComponent>> componentIds)
{
_id = id;
_worldID = worldID;
_chunks = new UnsafeList<Chunk>(4, Allocator.Persistent);
_edgesAdd = new UnsafeList<Edge>(4, Allocator.Persistent);
_edgesRemove = new UnsafeList<Edge>(4, Allocator.Persistent);
if (componentIds.IsEmpty)
{
_signature = new UnsafeBitSet(1, Allocator.Persistent, AllocationOption.Clear);
_hash = 0;
_signature.ClearAll();
_entityCapacity = Chunk.CHUNK_BUFFER_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);
_hash = _signature.GetHashCode();
CalculateLayout(componentIds);
}
private void CalculateLayout(ReadOnlySpan<Identifier<IComponent>> componentIds)
{
var entitySize = sizeof(Entity);
var entityAlign = (int)MemoryUtility.AlignOf<Entity>();
var components = (Span<ComponentInfo>)stackalloc ComponentInfo[componentIds.Length];
for (var i = 0; i < componentIds.Length; i++)
{
_signature.SetBit(componentIds[i]);
components[i] = ComponentRegistry.GetComponentInfo(componentIds[i]);
}
// 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 = Chunk.CHUNK_BUFFER_SIZE / bytesPerEntity;
_layouts = new UnsafeArray<ComponentMemoryLayout>(components.Length, Allocator.Persistent);
_componentIDToLayoutIndex = new UnsafeArray<int>(_maxComponentID + 1, Allocator.Persistent);
_componentIDToLayoutIndex.AsSpan().Fill(-1);
components.Sort((a, b) => b.alignment.CompareTo(a.alignment));
var tempOffsets = stackalloc int[components.Length];
var tempBitmaskOffsets = 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;
var bitmaskOffset = -1;
if (components[i].isEnableable)
{
var bitmaskSize = (_entityCapacity + Chunk.BIT_ALIGNMENT_MINUS_ONE) / Chunk.BIT_ALIGNMENT;
// Reserve space for the bitmask (1 bit per entity)
currentOffset = (currentOffset + Chunk.BIT_ALIGNMENT_MINUS_ONE) & ~Chunk.BIT_ALIGNMENT_MINUS_ONE; // Align
bitmaskOffset = currentOffset;
currentOffset += bitmaskSize;
}
tempBitmaskOffsets[i] = bitmaskOffset;
if (currentOffset > Chunk.CHUNK_BUFFER_SIZE)
{
fits = false;
break;
}
}
if (fits)
{
for (var i = 0; i < components.Length; i++)
{
_layouts[i] = new ComponentMemoryLayout
{
componentID = components[i].id,
offset = tempOffsets[i],
size = components[i].size,
enableBitsOffset = tempBitmaskOffsets[i],
versionIndex = i
};
_componentIDToLayoutIndex[components[i].id] = i;
}
return;
}
_entityCapacity--;
}
}
public void AllocateEntity(out int chunkIndex, out int rowIndex)
{
var world = World.GetWorldUncheck(_worldID);
for (var i = 0; i < _chunks.Count; i++)
{
ref var chunk = ref _chunks[i];
if (chunk._count < _entityCapacity)
{
rowIndex = chunk._count;
chunk._count++;
chunk._structuralVersion = world.Version;
chunkIndex = i;
return;
}
}
// Need to allocate a new chunk
var newChunk = new Chunk(Chunk.CHUNK_BUFFER_SIZE, _entityCapacity, _layouts.Count, world.Version);
#if DEBUG || GHOST_EDITOR
newChunk._worldID = _worldID;
newChunk._archetypeID = _id;
#endif
// Set all enable to true by default for enableable components
for (var i = 0; i < _layouts.Count; i++)
{
var layout = _layouts[i];
if (layout.enableBitsOffset != -1)
{
var pChunk = newChunk.GetUnsafePtr();
var pBits = pChunk + layout.enableBitsOffset;
MemoryUtility.MemSet(pBits, 0xFF, (nuint)((_entityCapacity + Chunk.BIT_ALIGNMENT_MINUS_ONE) / Chunk.BIT_ALIGNMENT));
}
}
rowIndex = 0;
newChunk._count++;
chunkIndex = _chunks.Count;
_chunks.Add(newChunk);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly Entity GetEntity(int chunkIndex, int rowIndex)
{
var chunk = _chunks[chunkIndex];
var chunkBase = chunk.GetUnsafePtr();
var src = chunkBase + _entityIdsOffset + (sizeof(Entity) * rowIndex);
return *(Entity*)src;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly void SetEntity(int chunkIndex, int rowIndex, Entity entity)
{
var chunk = _chunks[chunkIndex];
var chunkBase = chunk.GetUnsafePtr();
var dst = chunkBase + _entityIdsOffset + (sizeof(Entity) * rowIndex);
MemoryUtility.MemCpy(dst, &entity, (nuint)sizeof(Entity));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly Error SetComponentData(int chunkIndex, int rowIndex, Identifier<IComponent> componentID, void* pComponent)
{
var r = GetLayout(componentID);
if (r.Error != Error.None)
{
return r.Error;
}
var offset = r.Value.offset;
ref var chunk = ref _chunks[chunkIndex];
var chunkBase = chunk.GetUnsafePtr();
var size = ComponentRegistry.GetComponentInfo(componentID).size;
var dst = chunkBase + offset + (size * rowIndex);
MemoryUtility.MemCpy(dst, pComponent, (nuint)size);
var world = World.GetWorldUncheck(_worldID);
MarkChanged(chunkIndex, componentID, world.Version);
return Error.None;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly void* GetComponentData(int chunkIndex, int rowIndex, Identifier<IComponent> componentID)
{
var r = GetLayout(componentID);
if (r.Error != Error.None)
{
return null;
}
var offset = r.Value.offset;
var chunk = _chunks[chunkIndex];
var chunkBase = chunk.GetUnsafePtr();
var size = ComponentRegistry.GetComponentInfo(componentID).size;
return chunkBase + offset + (size * rowIndex);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ref Chunk GetChunkReference(int index)
{
return ref _chunks[index];
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly Result<ComponentMemoryLayout, Error> GetLayout(int componentID)
{
if (componentID >= _componentIDToLayoutIndex.Count)
{
return Error.InvalidArgument;
}
var layoutIndex = _componentIDToLayoutIndex[componentID];
if (layoutIndex == -1)
{
return Error.NotFound;
}
return _layouts[layoutIndex];
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly Error MarkChanged(int chunkIndex, int componentTypeId, int globalVersion)
{
var layoutResult = GetLayout(componentTypeId);
if (layoutResult.IsFailure)
{
return layoutResult.Error;
}
ref var chunk = ref _chunks[chunkIndex];
chunk.GetVersionUnsafePtr()[layoutResult.Value.versionIndex] = globalVersion;
return Error.None;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly Result<int, Error> GetVersion(int chunkIndex, int componentTypeId)
{
var layoutResult = GetLayout(componentTypeId);
if (layoutResult.Error != Error.None)
{
return layoutResult.Error;
}
ref var chunk = ref _chunks[chunkIndex];
return chunk.GetVersionUnsafePtr()[layoutResult.Value.versionIndex];
}
public Error RemoveEntity(int chunkIndex, int rowIndex)
{
if (chunkIndex < 0 || chunkIndex >= _chunks.Count)
{
return Error.InvalidArgument;
}
var world = World.GetWorldUncheck(_worldID);
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 error = world.EntityManager.UpdateEntityLocation(*(Entity*)pLastEntity, _id, chunkIndex, rowIndex);
if (error != Error.None)
{
return error;
}
// Only operate the swap back after the update is succeed.
MemoryUtility.MemCpy(pRowEntity, pLastEntity, (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(pRow, pLast, (nuint)layout.size);
}
}
chunk._count--;
chunk._structuralVersion = world.Version;
return Error.None;
}
public Error RemoveEntities(int chunkIndex, ReadOnlySpan<int> sortedIndicesToRemove)
{
if (chunkIndex < 0 || chunkIndex >= _chunks.Count)
{
return Error.InvalidArgument;
}
if (sortedIndicesToRemove.Length == 0)
{
return Error.None;
}
ref var chunk = ref _chunks[chunkIndex];
int oldCount = chunk._count;
int removeCount = sortedIndicesToRemove.Length;
int newCount = oldCount - removeCount; // The boundary between "Keep" and "Drop"
var chunkBase = chunk.GetUnsafePtr();
var world = World.GetWorldUncheck(_worldID); // Typo fixed from 'wrold'
// Pointers for the swap logic
// 1. 'holePtr' tracks which index in the sorted list we are processing
int holePtr = 0;
// 2. 'candidateIndex' starts at the end of the OLD array and moves backward
int candidateIndex = oldCount - 1;
// 3. 'removalTailPtr' tracks removals at the end of the array to skip them
int removalTailPtr = sortedIndicesToRemove.Length - 1;
// Iterate through the holes that are strictly INSIDE the new valid range
while (holePtr < removeCount)
{
int holeIndex = sortedIndicesToRemove[holePtr];
// If the current hole is beyond the new count, it's in the "Drop Zone".
// Since the list is sorted, all subsequent holes are also in the drop zone.
// We are done filling holes.
if (holeIndex >= newCount)
break;
// --- Find a Valid Filler ---
// We look for an entity at the end of the array that IS NOT scheduled for removal.
while (candidateIndex >= newCount)
{
// Check if the current candidate is actually marked for removal
bool isCandidateRemoved = false;
// Because sortedIndices is sorted, we check the end of the list
// to see if the candidateIndex matches a removal request.
if (removalTailPtr >= 0 && sortedIndicesToRemove[removalTailPtr] == candidateIndex)
{
isCandidateRemoved = true;
removalTailPtr--; // Consume this removal
}
if (!isCandidateRemoved)
{
// Found a valid filler!
break;
}
// This candidate was also removed, so skip it and keep looking left
candidateIndex--;
}
// --- Perform The Swap ---
// Move 'candidateIndex' (Filler) into 'holeIndex' (Hole)
var pFillerEntity = chunkBase + _entityIdsOffset + (sizeof(Entity) * candidateIndex);
var pHoleEntity = chunkBase + _entityIdsOffset + (sizeof(Entity) * holeIndex);
// 1. Update the Map (Critical Step)
// We tell the world: "The entity that WAS at 'candidateIndex' is now at 'holeIndex'"
var result = world.EntityManager.UpdateEntityLocation(*(Entity*)pFillerEntity, _id, chunkIndex, holeIndex);
if (result != Error.None)
{
return result;
}
// 2. Overwrite Entity ID
MemoryUtility.MemCpy(pHoleEntity, pFillerEntity, (nuint)sizeof(Entity));
// 3. Overwrite Components
for (var i = 0; i < _layouts.Count; i++)
{
var layout = _layouts[i];
var pRow = chunkBase + layout.offset + (layout.size * holeIndex);
var pLast = chunkBase + layout.offset + (layout.size * candidateIndex);
MemoryUtility.MemCpy(pRow, pLast, (nuint)layout.size);
}
// Prepare for next hole
holePtr++;
candidateIndex--;
}
chunk._count = newCount;
chunk._structuralVersion = world.Version;
return Error.None;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly 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 readonly 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 readonly 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;
}
public override readonly int GetHashCode()
{
return _hash;
}
public void Dispose()
{
if (_chunks.IsCreated)
{
foreach (ref var chunk in _chunks)
{
chunk.Dispose();
}
}
_signature.Dispose();
_chunks.Dispose();
_componentIDToLayoutIndex.Dispose();
_layouts.Dispose();
_edgesAdd.Dispose();
_edgesRemove.Dispose();
}
}

View File

@@ -0,0 +1,12 @@
global using EntityID = System.Int32;
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,19 @@
namespace Ghost.Entities;
public readonly struct TimeData
{
public int FrameCount
{
get; init;
}
public float DeltaTime
{
get; init;
}
public double ElapsedTime
{
get; init;
}
}

View File

@@ -0,0 +1,330 @@
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;
public interface IComponent
{
}
public interface IEnableableComponent : IComponent
{
}
internal struct ComponentInfo
{
// public string stableName; // Do we actually need this?
public Identifier<IComponent> id;
public int size;
public int alignment;
public bool isEnableable;
public bool isShared;
}
/// <summary>
/// Provides a unique identifier for the specified unmanaged component space.
/// </summary>
/// <typeparam name="T">The component space for which to obtain an identifier. Must be unmanaged and implement <see cref="IComponent"/>.</typeparam>
public static class ComponentTypeID<T>
where T : unmanaged, IComponent
{
public static readonly Identifier<IComponent> Value = ComponentRegistry.GetOrRegisterComponentID<T>();
}
internal static class ComponentRegistry
{
private static readonly List<ComponentInfo> s_registeredComponents = new();
private static readonly Dictionary<IntPtr, int> s_typeHandleToID = new();
private static readonly Dictionary<string, int> s_nameToRuntimeID = new();
internal static readonly Dictionary<int, Type> s_runtimeIDToType = new();
public static unsafe Identifier<IComponent> GetOrRegisterComponentID<T>()
where T : unmanaged, IComponent
{
var type = typeof(T);
var typeHandle = type.TypeHandle.Value;
lock (s_registeredComponents)
{
if (s_typeHandleToID.TryGetValue(typeHandle, out var existingID))
{
return existingID;
}
var newID = new Identifier<IComponent>(s_registeredComponents.Count);
var stableName = typeof(T).FullName ?? typeof(T).Name;
var info = new ComponentInfo
{
// stableName = new FixedText64(stableName),
id = newID,
size = sizeof(T),
alignment = (int)MemoryUtility.AlignOf<T>(),
isEnableable = typeof(IEnableableComponent).IsAssignableFrom(type),
//isShared = typeof(ISharedComponent).IsAssignableFrom(type),
};
s_registeredComponents.Add(info);
s_typeHandleToID[typeHandle] = newID;
s_nameToRuntimeID[stableName] = newID;
s_runtimeIDToType[newID.Value] = typeof(T);
return newID;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Identifier<IComponent> GetComponentID(Type type)
{
var typeHandle = type.TypeHandle.Value;
lock (s_registeredComponents)
{
if (s_typeHandleToID.TryGetValue(typeHandle, out var existingID))
{
return existingID;
}
}
return Identifier<IComponent>.Invalid;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ComponentInfo GetComponentInfo(Identifier<IComponent> typeId)
{
lock (s_registeredComponents)
{
return s_registeredComponents[typeId];
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ComponentInfo GetComponentInfo(Type type)
{
lock (s_registeredComponents)
{
var typeId = GetComponentID(type);
if (typeId.IsInvalid)
{
throw new KeyNotFoundException($"Component type {type.FullName} is not registered.");
}
return s_registeredComponents[typeId];
}
}
public static int GetHashCode(params 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();
}
}
public class ComponentManager : IDisposable
{
private readonly World _world;
private UnsafeList<Archetype> _archetypes;
private UnsafeList<EntityQuery> _entityQueries;
private UnsafeHashMap<int, Identifier<Archetype>> _archetypeLookup; // Signature Hash to Archetype ID
private UnsafeHashMap<int, Identifier<EntityQuery>> _querieLookup; // Query Mask Hash to Query ID
private bool _isDisposed;
public int ArchetypeCount => _archetypes.Count;
internal ComponentManager(World world)
{
_world = world;
_archetypes = new UnsafeList<Archetype>(16, Allocator.Persistent);
_entityQueries = new UnsafeList<EntityQuery>(16, Allocator.Persistent);
_archetypeLookup = new UnsafeHashMap<int, Identifier<Archetype>>(16, Allocator.Persistent);
_querieLookup = new UnsafeHashMap<int, Identifier<EntityQuery>>(16, Allocator.Persistent);
// Create the empty archetype
CreateArchetype(ReadOnlySpan<Identifier<IComponent>>.Empty, 0);
}
~ComponentManager()
{
Dispose();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal Identifier<Archetype> CreateArchetype(ReadOnlySpan<Identifier<IComponent>> componentTypeIDs, int signatureHash)
{
var arcID = new Identifier<Archetype>(_archetypes.Count);
_archetypes.Add(new Archetype(arcID, _world.ID, componentTypeIDs));
_archetypeLookup.Add(signatureHash, arcID);
for (int i = 0; i < _entityQueries.Count; i++)
{
ref var query = ref _entityQueries[i];
query.AddArchetypeIfMatch(in _archetypes[arcID.Value]);
}
return arcID;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal Identifier<Archetype> GetArchetypeIDBySignatureHash(int signatureHash)
{
if (_archetypeLookup.TryGetValue(signatureHash, out var arcID))
{
return arcID;
}
return Identifier<Archetype>.Invalid;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal ref Archetype GetArchetypeReference(Identifier<Archetype> id)
{
return ref _archetypes[id.Value];
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal Identifier<EntityQuery> CreateEntityQuery(EntityQueryMask mask, int maskHash)
{
var queryID = new Identifier<EntityQuery>(_entityQueries.Count);
_entityQueries.Add(new EntityQuery(queryID, _world.ID, mask));
_querieLookup.Add(maskHash, queryID);
ref var query = ref _entityQueries[queryID.Value];
for (var i = 0; i < _archetypes.Count; i++)
{
query.AddArchetypeIfMatch(in _archetypes[i]);
}
return queryID;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal Identifier<EntityQuery> GetEntityQueryIDByMaskHash(int maskHash)
{
if (_querieLookup.TryGetValue(maskHash, out var queryID))
{
return queryID;
}
return Identifier<EntityQuery>.Invalid;
}
/// <summary>
/// Gets a reference to the entity query with the specified identifier.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ref EntityQuery GetEntityQueryReference(Identifier<EntityQuery> id)
{
return ref _entityQueries[id.Value];
}
public void Dispose()
{
if (_isDisposed)
{
return;
}
foreach (ref var archetype in _archetypes)
{
archetype.Dispose();
}
foreach (ref var query in _entityQueries)
{
query.Dispose();
}
_archetypes.Dispose();
_entityQueries.Dispose();
_archetypeLookup.Dispose();
_querieLookup.Dispose();
_isDisposed = true;
GC.SuppressFinalize(this);
}
}
/// <summary>
/// Represents an immutable set of component identifiers used to define a group of components within an entity or system.
/// </summary>
public struct ComponentSet : IDisposable, IEquatable<ComponentSet>
{
private UnsafeArray<Identifier<IComponent>> _components;
private int _hashCode;
public readonly ReadOnlySpan<Identifier<IComponent>> Components => _components.AsSpan();
public ComponentSet(AllocationHandle allocationHandle, params ReadOnlySpan<Identifier<IComponent>> components)
{
_components = new UnsafeArray<Identifier<IComponent>>(components.Length, allocationHandle);
components.CopyTo(_components.AsSpan());
_hashCode = -1;
}
public ComponentSet(Allocator allocator, params ReadOnlySpan<Identifier<IComponent>> components)
: this(AllocationManager.GetAllocationHandle(allocator), components)
{
}
public readonly bool Equals(ComponentSet other)
{
return _hashCode == other._hashCode;
}
public override int GetHashCode()
{
if (_hashCode == -1)
{
_hashCode = ComponentRegistry.GetHashCode(_components.AsSpan());
}
return _hashCode;
}
public override readonly bool Equals(object? obj)
{
return obj is ComponentSet set && Equals(set);
}
public static bool operator ==(ComponentSet left, ComponentSet right)
{
return left.Equals(right);
}
public static bool operator !=(ComponentSet left, ComponentSet right)
{
return !(left == right);
}
public void Dispose()
{
_components.Dispose();
}
}

View File

@@ -0,0 +1,48 @@
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Ghost.Entities;
[StructLayout(LayoutKind.Sequential, Size = 8)]
public readonly record struct Entity
{
public const EntityID INVALID_ID = -1;
private readonly EntityID _id;
private readonly GenerationID _generation;
public EntityID ID
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => _id;
}
public GenerationID Generation
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => _generation;
}
public bool IsValid
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => ID != INVALID_ID;
}
public static Entity Invalid
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new(INVALID_ID, INVALID_ID);
}
internal Entity(EntityID id, GenerationID generation)
{
_id = id;
_generation = generation;
}
public override string ToString()
{
return $"Entity {{ Index: {ID}, Generation: {Generation} }}";
}
}

View File

@@ -0,0 +1,225 @@
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;
public unsafe class EntityCommandBuffer : IDisposable
{
private enum ECBOpCode : byte
{
CreateEntity,
CreateEntityWithComponents,
DestroyEntity,
AddComponent,
RemoveComponent,
SetComponent,
}
private readonly EntityManager _entityManager;
private UnsafeList<byte> _buffer;
private bool _disposed;
public EntityCommandBuffer(EntityManager entityManager)
{
_entityManager = entityManager;
_buffer = new UnsafeList<byte>(4096, Allocator.Persistent);
}
~EntityCommandBuffer()
{
Dispose();
}
private void WriteHeader(ECBOpCode op)
{
_buffer.Add((byte)op);
}
private void Write<T>(T value)
where T : unmanaged
{
var size = sizeof(T);
var idx = _buffer.Count;
if (idx + size > _buffer.Capacity)
{
_buffer.Resize(idx + size);
}
MemoryUtility.MemCpy((byte*)_buffer.GetUnsafePtr() + idx, &value, (nuint)size);
}
private void WriteSpan<T>(ReadOnlySpan<T> span)
where T : unmanaged
{
var size = sizeof(T) * span.Length;
var idx = _buffer.Count;
if (idx + size > _buffer.Capacity)
{
_buffer.Resize(idx + size);
}
fixed (T* ptr = span)
{
MemoryUtility.MemCpy((byte*)_buffer.GetUnsafePtr() + idx, ptr, (nuint)size);
}
}
private T Read<T>(ref int cursor)
where T : unmanaged
{
var size = sizeof(T);
var ptr = (byte*)_buffer.GetUnsafePtr();
var value = *(T*)&ptr[cursor];
cursor += size;
return value;
}
private Span<T> ReadSpan<T>(ref int cursor, int length)
where T : unmanaged
{
var size = sizeof(T) * length;
var ptr = (byte*)_buffer.GetUnsafePtr();
var span = new Span<T>(&ptr[cursor], length);
cursor += size;
return span;
}
private void* ReadBuffer(ref int cursor, int size)
{
var ptr = (byte*)_buffer.GetUnsafePtr();
var bufferPtr = ptr + cursor;
cursor += size;
return bufferPtr;
}
public void CreateEntity(int count = 1)
{
WriteHeader(ECBOpCode.CreateEntity);
Write(count);
}
public void CreateEntity(int count, ComponentSet set)
{
WriteHeader(ECBOpCode.CreateEntityWithComponents);
Write(count);
Write(set.Components.Length);
WriteSpan(set.Components);
}
public void DestroyEntity(Entity entity)
{
WriteHeader(ECBOpCode.DestroyEntity);
Write(entity);
}
public void AddComponent<T>(Entity entity, T component = default)
where T : unmanaged, IComponent
{
WriteHeader(ECBOpCode.AddComponent);
Write(entity);
Write(ComponentTypeID<T>.Value);
Write(component);
}
public void RemoveComponent<T>(Entity entity)
where T : unmanaged, IComponent
{
WriteHeader(ECBOpCode.RemoveComponent);
Write(entity);
Write(ComponentTypeID<T>.Value);
}
public void SetComponent<T>(Entity entity, T component)
where T : unmanaged, IComponent
{
WriteHeader(ECBOpCode.SetComponent);
Write(entity);
Write(ComponentTypeID<T>.Value);
Write(component);
}
internal void Playback()
{
var cursor = 0;
var length = _buffer.Count;
while (cursor < length)
{
var op = Read<ECBOpCode>(ref cursor);
using var scope = AllocationManager.CreateStackScope();
switch (op)
{
case ECBOpCode.CreateEntity:
var count = Read<int>(ref cursor);
_entityManager.CreateEntities(count);
break;
case ECBOpCode.CreateEntityWithComponents:
var entityCount = Read<int>(ref cursor);
var compCount = Read<int>(ref cursor);
var compTypeIDs = ReadSpan<Identifier<IComponent>>(ref cursor, compCount);
var set = new ComponentSet(scope.AllocationHandle, compTypeIDs);
_entityManager.CreateEntities(entityCount, set);
break;
case ECBOpCode.DestroyEntity:
var entityToDestroy = Read<Entity>(ref cursor);
_entityManager.DestroyEntity(entityToDestroy);
break;
case ECBOpCode.AddComponent:
var entityToAdd = Read<Entity>(ref cursor);
var addCompTypeID = Read<Identifier<IComponent>>(ref cursor);
var pAddCompData = ReadBuffer(ref cursor, ComponentRegistry.GetComponentInfo(addCompTypeID).size);
_entityManager.AddComponent(entityToAdd, addCompTypeID, pAddCompData);
break;
case ECBOpCode.RemoveComponent:
var entityToRemove = Read<Entity>(ref cursor);
var removeCompTypeID = Read<Identifier<IComponent>>(ref cursor);
_entityManager.RemoveComponent(entityToRemove, removeCompTypeID);
break;
case ECBOpCode.SetComponent:
var entityToSet = Read<Entity>(ref cursor);
var setCompTypeID = Read<Identifier<IComponent>>(ref cursor);
var pSetCompData = ReadBuffer(ref cursor, ComponentRegistry.GetComponentInfo(setCompTypeID).size);
_entityManager.SetComponent(entityToSet, setCompTypeID, pSetCompData);
break;
}
}
Reset();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void Reset()
{
_buffer.Clear();
}
public void Dispose()
{
if (_disposed)
{
return;
}
_buffer.Dispose();
_disposed = true;
GC.SuppressFinalize(this);
}
}

View File

@@ -0,0 +1,224 @@
using Misaki.HighPerformance.Collections;
using Misaki.HighPerformance.Utilities;
namespace Ghost.Entities;
public partial class EntityManager
{
private readonly SlotMap<List<ScriptComponent>> _scriptComponents;
internal SlotMap<List<ScriptComponent>> ScriptComponents => _scriptComponents;
/// <summary>
/// Creates a new ManagedEntity and associates it with the given Entity.
/// </summary>
/// <param name="entity">The Entity to associate with the ManagedEntity.</param>
/// <returns>The created ManagedEntity.</returns>
public ManagedEntity CreateManagedEntity(Entity entity)
{
var managedEntity = CreateManagedEntity();
AddComponent(entity, new ManagedEntityRef
{
entity = managedEntity
});
return managedEntity;
}
/// <summary>
/// Creates a new ManagedEntity.
/// </summary>
/// <remarks>
/// You must call this if you add <see cref="ManagedEntityRef"/> manually to an entity.
/// Otherwise, use <see cref="CreateManagedEntity(Entity)"/>.
/// </remarks>
/// <returns>The created ManagedEntity.</returns>
public ManagedEntity CreateManagedEntity()
{
var id = _scriptComponents.Add(new(8), out var generation);
var managedEntity = new ManagedEntity
{
id = id,
generation = generation
};
return managedEntity;
}
/// <summary>
/// Destroys the given ManagedEntity and calls OnDestroy on all associated ScriptComponents.
/// </summary>
/// <param name="managedEntity">The ManagedEntity to destroy.</param>
public void DestroyManagedEntity(ManagedEntity managedEntity)
{
if (_scriptComponents.TryGetElement(managedEntity.id, managedEntity.generation, out var scripts))
{
foreach (var script in scripts)
{
script.OnDestroy();
}
_scriptComponents.Remove(managedEntity.id, managedEntity.generation);
}
}
/// <summary>
/// Checks if the given ManagedEntity exists.
/// </summary>
/// <param name="managedEntity">The ManagedEntity to check.</param>
/// <returns>True if the ManagedEntity exists, false otherwise.</returns>
public bool Exists(ManagedEntity managedEntity)
{
return _scriptComponents.Contains(managedEntity.id, managedEntity.generation);
}
/// <summary>
/// Adds a ScriptComponent of space T to the given ManagedEntity and Entity.
/// </summary>
/// <typeparam name="T">The space of ScriptComponent to add.</typeparam>
/// <param name="managedEntity">The ManagedEntity to add the ScriptComponent to.</
/// <param name="entity">The Entity associated with the ManagedEntity.</param>
public void AddScriptComponent<T>(ManagedEntity managedEntity, Entity entity)
where T : ScriptComponent, new()
{
if (_scriptComponents.TryGetElement(managedEntity.id, managedEntity.generation, out var scripts))
{
var script = new T
{
_world = _world,
_entity = entity,
_managedEntity = managedEntity
};
scripts.Add(script);
script.OnCreate();
return;
}
throw new InvalidOperationException($"ManagedEntity {managedEntity} does not exist.");
}
/// <summary>
/// Adds a ScriptComponent of space T to the given Entity.
/// </summary>
/// <typeparam name="T">The space of ScriptComponent to add.</typeparam>
/// <param name="entity">The Entity to add the ScriptComponent to.</param>
public unsafe void AddScriptComponent<T>(Entity entity)
where T : ScriptComponent, new()
{
var location = _entityLocations.GetElementAt(entity.ID, entity.Generation);
ref var archetype = ref _world.ComponentManager.GetArchetypeReference(location.archetypeID);
var pManagedEntityRef = (ManagedEntityRef*)archetype.GetComponentData(location.chunkIndex, location.rowIndex, ComponentTypeID<ManagedEntityRef>.Value);
if (pManagedEntityRef == null)
{
throw new InvalidOperationException($"Entity {entity} does not have ManagedEntityRef component.");
}
AddScriptComponent<T>(pManagedEntityRef->entity, entity);
}
/// <summary>
/// Destroys the ScriptComponent of space T associated with the given ManagedEntity.
/// </summary>
/// <typeparam name="T">The space of ScriptComponent to destroy.</typeparam>
/// <param name="managedEntity">The ManagedEntity whose ScriptComponent is to be destroyed </param>
/// <returns>True if the ScriptComponent was found and destroyed, false otherwise.</returns
public bool DestroyScriptComponent<T>(ManagedEntity managedEntity)
where T : ScriptComponent
{
if (_scriptComponents.TryGetElement(managedEntity.id, managedEntity.generation, out var scripts))
{
for (var i = 0; i < scripts.Count; i++)
{
if (scripts[i] is T script)
{
script.OnDestroy();
scripts.RemoveAndSwapBack(i);
return true;
}
}
return false;
}
throw new InvalidOperationException($"ManagedEntity {managedEntity} does not exist.");
}
/// <summary>
/// Checks if the given ManagedEntity has a ScriptComponent of space T.
/// </summary>
/// <typeparam name="T">The space of ScriptComponent to check for.</typeparam>
/// <param name="managedEntity">The ManagedEntity to check.</param>
/// <returns>True if the ManagedEntity has a ScriptComponent of space T, false </returns>
public bool HasScriptComponent<T>(ManagedEntity managedEntity)
where T : ScriptComponent
{
if (_scriptComponents.TryGetElement(managedEntity.id, managedEntity.generation, out var scripts))
{
foreach (var script in scripts)
{
if (script is T)
{
return true;
}
}
return false;
}
throw new InvalidOperationException($"ManagedEntity {managedEntity} does not exist.");
}
/// <summary>
/// Gets the ScriptComponent of space T associated with the given ManagedEntity.
/// </summary>
/// <typeparam name="T">The space of ScriptComponent to get.</typeparam>
/// <param name="managedEntity">The ManagedEntity whose ScriptComponent is to be retrieved
/// <returns>The ScriptComponent of space T.</returns>
public T GetScriptComponent<T>(ManagedEntity managedEntity)
where T : ScriptComponent
{
if (_scriptComponents.TryGetElement(managedEntity.id, managedEntity.generation, out var scripts))
{
foreach (var script in scripts)
{
if (script is T typedScript)
{
return typedScript;
}
}
throw new InvalidOperationException($"ManagedEntity {managedEntity} does not have script component of type {typeof(T)}.");
}
throw new InvalidOperationException($"ManagedEntity {managedEntity} does not exist.");
}
/// <summary>
/// Gets all ScriptComponents of space T associated with the given ManagedEntity.
/// </summary>
/// <typeparam name="T">The space of ScriptComponent to get.</typeparam>
/// <param name="managedEntity">The ManagedEntity whose ScriptComponents are to be retrieved
/// <returns>The list of ScriptComponents of space T.</returns>
public List<T> GetScriptComponents<T>(ManagedEntity managedEntity)
where T : ScriptComponent
{
if (_scriptComponents.TryGetElement(managedEntity.id, managedEntity.generation, out var scripts))
{
var result = new List<T>();
foreach (var script in scripts)
{
if (script is T typedScript)
{
result.Add(typedScript);
}
}
return result;
}
throw new InvalidOperationException($"ManagedEntity {managedEntity} does not exist.");
}
}

View File

@@ -0,0 +1,864 @@
using Ghost.Core;
using Misaki.HighPerformance.Collections;
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections;
using Misaki.HighPerformance.LowLevel.Utilities;
using System.Diagnostics;
namespace Ghost.Entities;
internal struct EntityLocation : IComparable<EntityLocation>
{
public int archetypeID;
public int chunkIndex;
public int rowIndex;
public readonly int CompareTo(EntityLocation other)
{
var archComp = chunkIndex.CompareTo(other.chunkIndex);
if (archComp != 0)
{
return archComp;
}
var chunkComp = chunkIndex.CompareTo(other.chunkIndex);
if (chunkComp != 0)
{
return chunkComp;
}
return rowIndex.CompareTo(other.rowIndex);
}
}
/// <summary>
/// A manager for creating, destroying, and managing entities and their components.
/// </summary>
/// <remarks>
/// All methods in this class are not thread-safe and all of them will cause structural changes if not mentioned otherwise.
/// Use <see cref="EntityCommandBuffer"/> to defer structural changes to a safe point.
/// Use <see cref="World.GetThreadLocalEntityCommandBuffer(int)"/> to get a thread-local command buffer for multithreaded scenarios.
/// </remarks>
public unsafe partial class EntityManager : IDisposable
{
private readonly World _world;
private UnsafeSlotMap<EntityLocation> _entityLocations;
private bool _disposed;
public World World => _world;
internal EntityManager(World world, int initialCapacity)
{
_world = world;
_entityLocations = new UnsafeSlotMap<EntityLocation>(initialCapacity, Allocator.Persistent, AllocationOption.Clear);
_scriptComponents = new SlotMap<List<ScriptComponent>>(initialCapacity / 2);
// _storages = new IManagedComponentStorage[16];
}
~EntityManager()
{
Dispose();
}
internal Error UpdateEntityLocation(Entity entity, Identifier<Archetype> newArchetypeID, int newChunkIndex, int newRowIndex)
{
ref var location = ref _entityLocations.GetElementReferenceAt(entity.ID, entity.Generation, out var exist);
if (!exist)
{
return Error.NotFound;
}
location.archetypeID = newArchetypeID;
location.chunkIndex = newChunkIndex;
location.rowIndex = newRowIndex;
return Error.None;
}
internal Result<EntityLocation, Error> GetEntityLocation(Entity entity)
{
if (_entityLocations.TryGetElementAt(entity.ID, entity.Generation, out var location))
{
return location;
}
return Error.NotFound;
}
/// <summary>
/// Create an entity with no components.
/// </summary>
/// <returns>The created entity.</returns>
public Entity CreateEntity()
{
var entities = (Span<Entity>)stackalloc Entity[1];
CreateEntities(entities);
return entities[0];
}
/// <summary>
/// Create an entity with specified components.
/// </summary>
/// <param name="set">A set of component space IDs to add to the entities.</param>
/// <returns>The created entity.</returns>
public Entity CreateEntity(ComponentSet set)
{
var entities = (Span<Entity>)stackalloc Entity[1];
CreateEntities(entities, set);
return entities[0];
}
/// <summary>
/// Create multiple entities with no components.
/// </summary>
/// <param name="entities">The span to store the created entities.</param>
public void CreateEntities(Span<Entity> entities)
{
ref var emptyArchetype = ref _world.ComponentManager.GetArchetypeReference(World.EmptyArchetypeID);
emptyArchetype.AllocateEntity(out var chunkIndex, out var rowIndex);
for (var i = 0; i < entities.Length; i++)
{
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);
entities[i] = entity;
}
}
/// <summary>
/// Create multiple entities with no components.
/// </summary>
/// <param name="count">The number of entities to create.</param>
public void CreateEntities(int count)
{
ref var emptyArchetype = ref _world.ComponentManager.GetArchetypeReference(World.EmptyArchetypeID);
emptyArchetype.AllocateEntity(out var chunkIndex, out var rowIndex);
for (var i = 0; i < count; i++)
{
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);
}
}
/// <summary>
/// Create multiple entities with specified components.
/// </summary>
/// <param name="entities">The span to store the created entities.</param>
/// <param name="set">A set of component space IDs to add to the entities.</param>
/// <returns>An array of the created entities.</returns>
public void CreateEntities(Span<Entity> entities, ComponentSet set)
{
var hash = set.GetHashCode();
var arcID = _world.ComponentManager.GetArchetypeIDBySignatureHash(hash);
if (arcID.IsInvalid)
{
arcID = _world.ComponentManager.CreateArchetype(set.Components, hash);
}
ref var archetype = ref _world.ComponentManager.GetArchetypeReference(arcID);
for (var i = 0; i < entities.Length; i++)
{
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);
entities[i] = entity;
}
}
/// <summary>
/// Create multiple entities with specified components.
/// </summary>
/// <param name="count">The number of entities to create.</param>
/// <param name="set">A set of component space IDs to add to the entities.</param>
public void CreateEntities(int count, ComponentSet set)
{
var hash = set.GetHashCode();
var arcID = _world.ComponentManager.GetArchetypeIDBySignatureHash(hash);
if (arcID.IsInvalid)
{
arcID = _world.ComponentManager.CreateArchetype(set.Components, hash);
}
ref var archetype = ref _world.ComponentManager.GetArchetypeReference(arcID);
for (var i = 0; i < count; i++)
{
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);
}
}
private void DestoryManagedEntityIfExists(ref readonly Archetype archetype, EntityLocation location)
{
var pManagedRef = archetype.GetComponentData(location.chunkIndex, location.rowIndex, ComponentTypeID<ManagedEntityRef>.Value);
if (pManagedRef != null)
{
DestroyManagedEntity(((ManagedEntityRef*)pManagedRef)->entity);
}
}
/// <summary>
/// Destroy the specified entity.
/// </summary>
/// <returns>The result status of the operation.</returns>
public Error DestroyEntity(Entity entity)
{
if (!_entityLocations.TryGetElementAt(entity.ID, entity.Generation, out var location))
{
return Error.NotFound;
}
ref var archetype = ref _world.ComponentManager.GetArchetypeReference(location.archetypeID);
DestoryManagedEntityIfExists(in archetype, location);
var r = archetype.RemoveEntity(location.chunkIndex, location.rowIndex);
if (r != Error.None)
{
return r;
}
if (!_entityLocations.Remove(entity.ID, entity.Generation))
{
return Error.NotFound;
}
return Error.None;
}
/// <summary>
/// Destroy the specified entities.
/// </summary>
/// <param name="entities">The entities to destroy.</param>
public void DestroyEntities(ReadOnlySpan<Entity> entities)
{
void RemoveManagedEntity(ReadOnlySpan<int> rowIndicesCache, ref readonly Archetype archetype, int chunkIndex)
{
for (var j = 0; j < rowIndicesCache.Length; j++)
{
var rowIndex = rowIndicesCache[j];
var location = new EntityLocation
{
archetypeID = archetype.ID,
chunkIndex = chunkIndex,
rowIndex = rowIndex
};
DestoryManagedEntityIfExists(in archetype, location);
}
}
if (entities.Length == 0)
{
return;
}
using var scope = AllocationManager.CreateStackScope();
var batchDestroy = new UnsafeList<EntityLocation>(entities.Length, scope.AllocationHandle);
var rowIndicesCache = new UnsafeList<int>(32, scope.AllocationHandle);
// 1. GATHER
// Resolve all entities to their locations
for (var i = 0; i < entities.Length; i++)
{
var entity = entities[i];
if (_entityLocations.TryGetElementAt(entity.ID, entity.Generation, out var location))
{
batchDestroy.Add(location);
}
}
if (batchDestroy.Count == 0)
{
return;
}
// 2. SORT
// Sorting groups them by chunk automatically
batchDestroy.AsSpan().Sort();
// 3. SWEEP
// Iterate through the sorted list and batch process each chunk
var firstLoc = batchDestroy[0];
var prevArchetypeID = firstLoc.archetypeID;
var prevChunkIndex = firstLoc.chunkIndex;
for (var i = 0; i < batchDestroy.Count; i++)
{
var loc = batchDestroy[i];
// Check if we have crossed a boundary (Different Chunk OR Different Archetype)
var isNewBatch = (loc.chunkIndex != prevChunkIndex) || (loc.archetypeID != prevArchetypeID);
if (isNewBatch)
{
// FLUSH PREVIOUS BATCH
// We must retrieve the Archetype of the *Previous* batch, not the current 'loc'
ref var prevArchetype = ref _world.ComponentManager.GetArchetypeReference(prevArchetypeID);
// Remove Managed Entities first
RemoveManagedEntity(rowIndicesCache.AsSpan(), in prevArchetype, prevChunkIndex);
// Execute the hole-filling/swap logic
prevArchetype.RemoveEntities(prevChunkIndex, rowIndicesCache.AsSpan());
// RESET
rowIndicesCache.Clear();
prevArchetypeID = loc.archetypeID;
prevChunkIndex = loc.chunkIndex;
}
rowIndicesCache.Add(loc.rowIndex);
}
// 4. FINAL FLUSH
// Process the stragglers remaining in the cache
if (rowIndicesCache.Count > 0)
{
ref var lastArchetype = ref _world.ComponentManager.GetArchetypeReference(prevArchetypeID);
RemoveManagedEntity(rowIndicesCache.AsSpan(), in lastArchetype, prevChunkIndex);
lastArchetype.RemoveEntities(prevChunkIndex, rowIndicesCache.AsSpan());
}
// 5. Remove from Entity Locations
for (var i = 0; i < entities.Length; i++)
{
var entity = entities[i];
_entityLocations.Remove(entity.ID, entity.Generation);
}
}
/// <summary>
/// Check if the specified entity exists.
/// </summary>
/// <param name="entity">The entity to check.</param>
/// <returns>True if the entity exists, false otherwise.</returns>
public bool Exists(Entity entity)
{
return _entityLocations.Contains(entity.ID, entity.Generation);
}
/// <summary>
/// Create a singleton entity with the specified component.
/// </summary>
/// <param name="componentID">The component space ID of the singleton.</param>
/// <param name="pComponent">Pointer to the component data.</param>
/// <returns>The result status of the operation.</returns>
public Error CreateSingleton(Identifier<IComponent> componentID, void* pComponent)
{
if (pComponent == null)
{
return Error.InvalidArgument;
}
// Check if singleton already exists
var signatureHash = ComponentRegistry.GetHashCode(componentID);
var arcID = _world.ComponentManager.GetArchetypeIDBySignatureHash(signatureHash);
if (arcID.IsValid)
{
return Error.InvalidArgument;
}
arcID = _world.ComponentManager.CreateArchetype([componentID], signatureHash);
ref var archetype = ref _world.ComponentManager.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);
archetype.SetComponentData(chunkIndex, rowIndex, componentID, pComponent);
return Error.None;
}
/// <summary>
/// Create a singleton entity with the specified component.
/// </summary>
/// <typeparam name="T">The component space.</typeparam>
/// <param name="component">The component data.</param>
/// <returns>The result status of the operation.</returns>
public Error CreateSingleton<T>(T component = default)
where T : unmanaged, IComponent
{
return CreateSingleton(ComponentTypeID<T>.Value, &component);
}
/// <summary>
/// Get a pointer to the singleton component data.
/// </summary>
/// <param name="componentID">The component space ID of the singleton.</param>
/// <returns>Pointer to the component data, or null if not found.</returns>
public void* GetSingleton(Identifier<IComponent> componentID)
{
var signatureHash = ComponentRegistry.GetHashCode(componentID);
var arcID = _world.ComponentManager.GetArchetypeIDBySignatureHash(signatureHash);
if (arcID.IsInvalid)
{
return null;
}
ref var archetype = ref _world.ComponentManager.GetArchetypeReference(arcID);
var layoutResult = archetype.GetLayout(componentID);
if (layoutResult.Error != Error.None)
{
return null;
}
var chunk = archetype._chunks[0];
var ptr = chunk.GetUnsafePtr() + layoutResult.Value.offset;
return ptr;
}
/// <summary>
/// Get a reference to the singleton component data.
/// </summary>
/// <typeparam name="T">The component space.</typeparam>
/// <returns>Reference to the component data. null ref if not found.</returns>
public ref T GetSingleton<T>()
where T : unmanaged, IComponent
{
var ptr = GetSingleton(ComponentTypeID<T>.Value);
return ref *(T*)ptr; // This will return null ref if ptr is null.
}
private static void CopyData(ref Archetype oldArch, int oldChunk, int oldRow,
ref Archetype newArch, int newChunk, int newRow)
{
// Iterate every component space in the OLD archetype
for (var i = 0; i < oldArch._layouts.Count; i++)
{
var layout = oldArch._layouts[i];
var src = oldArch._chunks[oldChunk].GetUnsafePtr() + layout.offset + (layout.size * oldRow);
var r = newArch.GetLayout(layout.componentID);
if (r.Error != Error.None)
{
// New archetype does not have this component, skip it.
// This can happen when removing components.
continue;
}
var dst = newArch._chunks[newChunk].GetUnsafePtr() + r.Value.offset + (layout.size * newRow);
MemoryUtility.MemCpy(dst, src, (nuint)layout.size);
}
}
/// <summary>
/// Add a component to the specified entity.
/// </summary>
/// <param name="entity">The entity to add the component to.</param>
/// <param name="componentID">The component space ID to add.</param>
/// <param name="pComponent">Pointer to the component data.</param>
/// <returns>The result status of the operation.</returns>
public Error AddComponent(Entity entity, Identifier<IComponent> componentID, void* pComponent)
{
// Find current location
ref var location = ref _entityLocations.GetElementReferenceAt(entity.ID, entity.Generation, out var exist);
if (!exist)
{
return Error.NotFound;
}
// Build new archetype signature
ref var oldArchetype = ref _world.ComponentManager.GetArchetypeReference(location.archetypeID);
var oldSignature = oldArchetype._signature;
if (oldSignature.IsSet(componentID))
{
// Component already exists
return Error.InvalidArgument;
}
var newArcID = oldArchetype.GetEdgeAdd(componentID);
if (newArcID.IsInvalid)
{
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 oldIt = oldSignature.GetIterator();
var compCount = 0;
while (oldIt.Next(out var index))
{
newSignature.SetBit(index);
compCount++;
}
compCount++;
newSignature.SetBit(componentID);
// Find or create new archetype
var newSignatureHash = newSignature.GetHashCode();
newArcID = _world.ComponentManager.GetArchetypeIDBySignatureHash(newSignatureHash);
if (newArcID.IsInvalid)
{
// Create new archetype
Span<Identifier<IComponent>> componentTypeIDs = stackalloc Identifier<IComponent>[compCount];
var newIt = newSignature.GetIterator();
var i = 0;
while (newIt.Next(out var index))
{
componentTypeIDs[i++] = index;
}
newArcID = _world.ComponentManager.CreateArchetype(componentTypeIDs, newSignatureHash);
}
oldArchetype.AddEdgeAdd(componentID, newArcID);
}
// Move entity data
ref var newArchetype = ref _world.ComponentManager.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, pComponent);
var r = oldArchetype.RemoveEntity(location.chunkIndex, location.rowIndex);
Debug.Assert(r == Error.None); // We assert it because the entity should exist if the whole system is consistent.
if (r != Error.None)
{
return r;
}
// Update location
location.archetypeID = newArcID;
location.chunkIndex = newChunkIndex;
location.rowIndex = newRowIndex;
return Error.None;
}
/// <summary>
/// Add a component to the specified entity.
/// </summary>
/// <typeparam name="T">The component space.</typeparam>
/// <param name="entity">The entity to add the component to.</param>
/// <param name="component">The component data.</param>
/// <returns>The result status of the operation.</returns>
public Error AddComponent<T>(Entity entity, T component = default)
where T : unmanaged, IComponent
{
return AddComponent(entity, ComponentTypeID<T>.Value, &component);
}
/// <summary>
/// Remove a component from the specified entity.
/// </summary>
/// <param name="entity">The entity to remove the component from.</param>
/// <param name="componentID">The component space ID to remove.</param>
/// <returns>The result status of the operation.</returns>
public Error RemoveComponent(Entity entity, Identifier<IComponent> componentID)
{
// Find current location
ref var location = ref _entityLocations.GetElementReferenceAt(entity.ID, entity.Generation, out var exist);
if (!exist)
{
return Error.NotFound;
}
// Build new archetype signature
ref var oldArchetype = ref _world.ComponentManager.GetArchetypeReference(location.archetypeID);
var oldSignature = oldArchetype._signature;
var newArcID = oldArchetype.GetEdgeRemove(componentID);
if (newArcID.IsInvalid)
{
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 oldIt = oldSignature.GetIterator();
var compCount = 0;
while (oldIt.Next(out var index))
{
if (index != componentID)
{
newSignature.SetBit(index);
compCount++;
}
}
// Find or create new archetype
var newSignatureHash = newSignature.GetHashCode();
newArcID = _world.ComponentManager.GetArchetypeIDBySignatureHash(newSignatureHash);
if (newArcID.IsInvalid)
{
// Create new archetype
Span<Identifier<IComponent>> componentTypeIDs = stackalloc Identifier<IComponent>[compCount];
var newIt = newSignature.GetIterator();
var i = 0;
while (newIt.Next(out var index))
{
componentTypeIDs[i++] = index;
}
newArcID = _world.ComponentManager.CreateArchetype(componentTypeIDs, newSignatureHash);
}
oldArchetype.AddEdgeRemove(componentID, newArcID);
}
// Move entity data
ref var newArchetype = ref _world.ComponentManager.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);
var r = oldArchetype.RemoveEntity(location.chunkIndex, location.rowIndex);
Debug.Assert(r == Error.None); // We assert it because the entity should exist if the whole system is consistent.
if (r != Error.None)
{
return r;
}
var pManagedRef = oldArchetype.GetComponentData(location.chunkIndex, location.rowIndex, ComponentTypeID<ManagedEntityRef>.Value);
if (pManagedRef != null)
{
DestroyManagedEntity(((ManagedEntityRef*)pManagedRef)->entity);
}
// Update location
location.archetypeID = newArcID;
location.chunkIndex = newChunkIndex;
location.rowIndex = newRowIndex;
return Error.None;
}
/// <summary>
/// Remove a component from the specified entity.
/// </summary>
/// <typeparam name="T">The component space.</typeparam>
/// <param name="entity">The entity to remove the component from.</param>
/// <returns>The result status of the operation.</returns>
public Error RemoveComponent<T>(Entity entity)
where T : unmanaged, IComponent
{
return RemoveComponent(entity, ComponentTypeID<T>.Value);
}
/// <summary>
/// Set the component data for the specified entity.
/// </summary>
/// <param name="entity">The entity to set the component data for.</param>
/// <param name="componentID">The component space ID to set.</param>
/// <param name="pComponent">Pointer to the component data.</param>
/// <returns>The result status of the operation.</returns>
public Error SetComponent(Entity entity, Identifier<IComponent> componentID, void* pComponent)
{
if (!_entityLocations.TryGetElementAt(entity.ID, entity.Generation, out var location))
{
return Error.NotFound;
}
ref var archetype = ref _world.ComponentManager.GetArchetypeReference(location.archetypeID);
archetype.SetComponentData(location.chunkIndex, location.rowIndex, componentID, pComponent);
return Error.None;
}
/// <summary>
/// Set the component data for the specified entity.
/// </summary>
/// <typeparam name="T">The component space.</typeparam>
/// <param name="entity">The entity to set the component data for.</param>
/// <param name="component">The component data.</param>
public Error SetComponent<T>(Entity entity, T component)
where T : unmanaged, IComponent
{
return SetComponent(entity, ComponentTypeID<T>.Value, &component);
}
/// <summary>
/// Get a pointer to the component data for the specified entity.
/// </summary>
/// <param name="entity">The entity to get the component data for.</param>
/// <param name="componentID">The component space ID to get.</param>
/// <returns>Pointer to the component data, or null if not found.</returns>
public void* GetComponent(Entity entity, Identifier<IComponent> componentID)
{
if (!_entityLocations.TryGetElementAt(entity.ID, entity.Generation, out var location))
{
return null;
}
ref var archetype = ref _world.ComponentManager.GetArchetypeReference(location.archetypeID);
return archetype.GetComponentData(location.chunkIndex, location.rowIndex, componentID);
}
/// <summary>
/// Get a reference to the component data for the specified entity.
/// </summary>
/// <typeparam name="T">The component space.</typeparam>
/// <param name="entity">The entity to get the component data for.</param>
/// <returns>Reference to the component data. null ref if not found.</returns>
public ref T GetComponent<T>(Entity entity)
where T : unmanaged, IComponent
{
var ptr = GetComponent(entity, ComponentTypeID<T>.Value);
return ref *(T*)ptr; // This will return null ref if ptr is null.
}
/// <summary>
/// Check if the specified entity has the specified component.
/// </summary>
/// <param name="entity">The entity to check.</param>
/// <param name="componentID">The component space ID to check.</param>
/// <returns>True if the entity has the component, false otherwise.</returns>
public bool HasComponent(Entity entity, Identifier<IComponent> componentID)
{
if (!_entityLocations.TryGetElementAt(entity.ID, entity.Generation, out var location))
{
return false;
}
ref var archetype = ref _world.ComponentManager.GetArchetypeReference(location.archetypeID);
return archetype.HasComponent(componentID);
}
/// <summary>
/// Check if the specified entity has the specified component.
/// </summary>
/// <typeparam name="T">The component space.</typeparam>
/// <param name="entity">The entity to check.</param>
/// <returns>True if the entity has the component, false otherwise.</returns>
public bool HasComponent<T>(Entity entity)
where T : unmanaged, IComponent
{
return HasComponent(entity, ComponentTypeID<T>.Value);
}
/// <summary>
/// Set the enabled state of an enableable component for the specified entity.
/// </summary>
/// <param name="entity">The entity to set the enabled state for.</param>
/// <param name="componentID">The component space ID of the enableable component.</
/// <param name="enabled">True to enable the component, false to disable it.</param>
/// <returns>The result status of the operation.</returns>
public Error SetEnabled(Entity entity, Identifier<IComponent> componentID, bool enabled)
{
if (!_entityLocations.TryGetElementAt(entity.ID, entity.Generation, out var location))
{
return Error.NotFound;
}
ref var archetype = ref _world.ComponentManager.GetArchetypeReference(location.archetypeID);
var chunkIndex = location.chunkIndex;
var rowIndex = location.rowIndex;
var layoutResult = archetype.GetLayout(componentID);
if (layoutResult.Error != Error.None)
{
return layoutResult.Error;
}
ref var chunk = ref archetype.GetChunkReference(chunkIndex);
var chunkBase = chunk.GetUnsafePtr();
var maskBase = chunkBase + layoutResult.Value.enableBitsOffset;
var byteIndex = rowIndex >> Chunk.BIT_SHIFT;
var bitIndex = rowIndex & Chunk.BIT_ALIGNMENT_MINUS_ONE;
if (enabled)
{
maskBase[byteIndex] |= (byte)(1 << bitIndex);
}
else
{
maskBase[byteIndex] &= (byte)~(1 << bitIndex);
}
return Error.None;
}
/// <summary>
/// Set the enabled state of an enableable component for the specified entity.
/// </summary>
/// <typeparam name="T">The enableable component space.</typeparam>
/// <param name="entity">The entity to set the enabled state for.</param>
/// <param name="enabled">True to enable the component, false to disable it.</
/// <returns>The result status of the operation.</returns>
public Error SetEnabled<T>(Entity entity, bool enabled)
where T : unmanaged, IEnableableComponent
{
return SetEnabled(entity, ComponentTypeID<T>.Value, enabled);
}
public void Dispose()
{
if (_disposed)
{
return;
}
_entityLocations.Dispose();
_disposed = true;
GC.SuppressFinalize(this);
}
}

View File

@@ -0,0 +1,96 @@
using Ghost.Core;
using Misaki.HighPerformance.Jobs;
using Misaki.HighPerformance.LowLevel.Collections;
using System.Runtime.CompilerServices;
namespace Ghost.Entities;
public interface IJobChunk
{
void Execute(ChunkView view, int threadIndex);
}
internal unsafe struct ChunkInfo
{
public Chunk* pChunk;
public Archetype* pArchetype;
}
internal unsafe struct JobChunkBatch<TJob> : IJobParallelFor
where TJob : unmanaged, IJobChunk
{
public TJob userJob;
public ReadOnlyUnsafeCollection<ChunkInfo> chunkInfos;
public void Execute(int loopIndex, int threadIndex)
{
var info = chunkInfos[loopIndex];
var view = new ChunkView(in *info.pArchetype, in *info.pChunk);
userJob.Execute(view, threadIndex);
}
}
internal struct DisposeJobChunk : IJob
{
public UnsafeList<ChunkInfo> list;
public void Execute(int threadIndex)
{
list.Dispose();
}
}
public unsafe partial struct EntityQuery
{
public JobHandle ScheduleChunkParallel<TJob>(TJob job, int batchSize, JobHandle dependency)
where TJob : unmanaged, IJobChunk
{
var world = World.GetWorld(_worldID);
if (world is null)
{
return JobHandle.Invalid;
}
if (world.JobScheduler == null)
{
throw new InvalidOperationException("The World has no JobScheduler assigned.");
}
var chunkInfos = new UnsafeList<ChunkInfo>(_matchingArchetypes.Count * 2, JobScheduler.TempAllocatorHandle);
foreach (var archID in _matchingArchetypes)
{
ref var arch = ref world.ComponentManager.GetArchetypeReference(archID);
for (int i = 0; i < arch.ChunkCount; i++)
{
var pChunk = (Chunk*)arch._chunks.GetUnsafePtr() + i;
chunkInfos.Add(new ChunkInfo
{
pArchetype = (Archetype*)Unsafe.AsPointer(ref arch),
pChunk = pChunk
});
}
}
var batchJob = new JobChunkBatch<TJob>
{
userJob = job,
chunkInfos = chunkInfos.AsReadOnly()
};
var handle = world.JobScheduler.ScheduleParallel(ref batchJob, chunkInfos.Count, batchSize, dependency);
var disposeJob = new DisposeJobChunk
{
list = chunkInfos
};
world.JobScheduler.Schedule(ref disposeJob, handle);
return handle;
}
}

View File

@@ -0,0 +1,88 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<IsAotCompatible>False</IsAotCompatible>
<IsTrimmable>False</IsTrimmable>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<IsAotCompatible>True</IsAotCompatible>
<IsTrimmable>True</IsTrimmable>
</PropertyGroup>
<ItemGroup>
<None Include="Templates\EntityQuery.ForEach.gen.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>EntityQuery.ForEach.tt</DependentUpon>
</None>
<None Include="Templates\ForEach.gen.cs">
<DependentUpon>ForEach.tt</DependentUpon>
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
</None>
<None Include="Templates\QueryBuilder.With.gen.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>QueryBuilder.With.tt</DependentUpon>
</None>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Ghost.Core\Ghost.Core.csproj" />
</ItemGroup>
<ItemGroup>
<None Update="Templates\EntityQuery.ForEach.tt">
<Generator>TextTemplatingFileGenerator</Generator>
<LastGenOutput>EntityQuery.ForEach.gen.cs</LastGenOutput>
</None>
<None Update="Templates\ForEach.tt">
<LastGenOutput>ForEach.gen.cs</LastGenOutput>
<Generator>TextTemplatingFileGenerator</Generator>
</None>
<None Update="Templates\EntityQuery.JobEntityParallel.tt">
<Generator>TextTemplatingFileGenerator</Generator>
<LastGenOutput>EntityQuery.JobEntityParallel.gen.cs</LastGenOutput>
</None>
<None Update="Templates\QueryBuilder.With.tt">
<Generator>TextTemplatingFileGenerator</Generator>
<LastGenOutput>QueryBuilder.With.gen.cs</LastGenOutput>
</None>
</ItemGroup>
<ItemGroup>
<Service Include="{508349b6-6b84-4df5-91f0-309beebad82d}" />
</ItemGroup>
<ItemGroup>
<Compile Update="Templates\EntityQuery.ForEach.gen.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>EntityQuery.ForEach.tt</DependentUpon>
</Compile>
<Compile Update="Templates\EntityQuery.JobEntityParallel.gen.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>EntityQuery.JobEntityParallel.tt</DependentUpon>
</Compile>
<Compile Update="Templates\ForEach.gen.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>ForEach.tt</DependentUpon>
</Compile>
<Compile Update="Templates\QueryBuilder.With.gen.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>QueryBuilder.With.tt</DependentUpon>
</Compile>
</ItemGroup>
</Project>

View File

@@ -0,0 +1,256 @@
#if false
using Ghost.Core;
using Misaki.HighPerformance.Collections;
using System.Runtime.CompilerServices;
namespace Ghost.Entities;
public interface IManagedComponent;
public interface IManagedWrapper;
public readonly struct Managed<T> : IComponent, IManagedWrapper
where T : IManagedComponent
{
public readonly int id;
public readonly int generation;
internal Managed(int id, int generation)
{
this.id = id;
this.generation = generation;
}
}
public static class ManagedComponemtnID<T>
where T : IManagedComponent
{
public static readonly Identifier<IManagedComponent> value = ManagedComponentRegistry.GetOrRegisterComponent<T>();
}
internal struct ManagedComponentInfo
{
public int id;
public bool isScriptComponent;
}
internal static class ManagedComponentRegistry
{
private static readonly List<ManagedComponentInfo> s_registeredComponents = new();
private static readonly Dictionary<IntPtr, int> s_typeHandleToID = new();
private static readonly Dictionary<string, int> s_nameToRuntimeID = new();
#if DEBUG || GHOST_EDITOR
internal static readonly Dictionary<int, Type> s_runtimeIDToType = new();
#endif
public static Identifier<IManagedComponent> GetOrRegisterComponent<T>()
where T : IManagedComponent
{
var typeHandle = typeof(T).TypeHandle.Value;
lock (s_registeredComponents)
{
if (s_typeHandleToID.TryGetValue(typeHandle, out var existingID))
{
return existingID;
}
var newID = new Identifier<IManagedComponent>(s_registeredComponents.Count);
var stableName = typeof(T).FullName ?? typeof(T).Name;
var info = new ManagedComponentInfo
{
id = newID,
isScriptComponent = typeof(ScriptComponent).IsAssignableFrom(typeof(T)),
};
s_registeredComponents.Add(info);
s_typeHandleToID[typeHandle] = newID;
s_nameToRuntimeID[stableName] = newID;
#if DEBUG || GHOST_EDITOR
s_runtimeIDToType[newID.value] = typeof(T);
#endif
return newID;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Identifier<IManagedComponent> GetComponentID(Type type)
{
var typeHandle = type.TypeHandle.Value;
lock (s_registeredComponents)
{
if (s_typeHandleToID.TryGetValue(typeHandle, out var existingID))
{
return existingID;
}
}
return Identifier<IManagedComponent>.Invalid;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ManagedComponentInfo GetComponentInfo(Identifier<IManagedComponent> typeId)
{
lock (s_registeredComponents)
{
return s_registeredComponents[typeId];
}
}
}
internal interface IManagedComponentStorage
{
public Identifier<IManagedComponent> TypeID { get; }
}
internal class ManagedComponentStorage<T> : IManagedComponentStorage
where T : IManagedComponent
{
private readonly SlotMap<T> _storage = new(16);
public Identifier<IManagedComponent> TypeID => ManagedComponemtnID<T>.value;
public Managed<T> Add(T component)
{
var id = _storage.Add(component, out var generation);
return new Managed<T>(id, generation);
}
public void Remove(Managed<T> managed)
{
_storage.Remove(managed.id, managed.generation);
}
public ref T GetComponentReference(Managed<T> managed)
{
return ref _storage.GetElementReferenceAt(managed.id, managed.generation, out _);
}
}
public abstract class ScriptComponent : IManagedComponent
{
internal World _world = null!;
internal Entity _entity;
public World World => _world;
public Entity Entity => _entity;
protected ref T GetComponent<T>()
where T : unmanaged, IComponent
{
return ref _world.EntityManager.GetComponent<T>(_entity);
}
public virtual void OnCreate()
{
}
public virtual void OnDestroy()
{
}
public virtual void OnEnable()
{
}
public virtual void OnDisable()
{
}
public virtual void Start()
{
}
public virtual void Update()
{
}
public virtual void FixedUpdate()
{
}
public virtual void LateUpdate()
{
}
}
public partial class EntityManager
{
private IManagedComponentStorage[] _storages;
internal IManagedComponentStorage[] Storages => _storages;
private ManagedComponentStorage<T> GetOrCreateManagedComponentStorage<T>()
where T : IManagedComponent
{
var id = ManagedComponemtnID<T>.value;
if (_storages == null || _storages.Length <= id.value)
{
Array.Resize(ref _storages, id.value + 1);
}
ref var storage = ref _storages[id.value];
storage ??= new ManagedComponentStorage<T>();
return (ManagedComponentStorage<T>)storage;
}
public Managed<T> AddManagedComponent<T>(Entity entity)
where T : IManagedComponent, new()
{
var instance = new T();
if (instance is ScriptComponent scriptComponent)
{
scriptComponent._world = _world;
scriptComponent._entity = entity;
scriptComponent.OnCreate();
}
var managed = GetOrCreateManagedComponentStorage<T>().Add(instance);
AddComponent(entity, managed);
return managed;
}
public bool RemoveManagedComponent<T>(Entity entity)
where T : IManagedComponent
{
ref var component = ref GetComponent<Managed<T>>(entity);
if (!Unsafe.IsNullRef(ref component))
{
var storage = GetOrCreateManagedComponentStorage<T>();
var componentRef = storage.GetComponentReference(component);
if (componentRef is ScriptComponent scriptComponent)
{
scriptComponent.OnDestroy();
}
RemoveComponent<Managed<T>>(entity);
storage.Remove(component);
return true;
}
return false;
}
public ref T GetManagedComponent<T>(Entity entity)
where T : IManagedComponent
{
ref var component = ref GetComponent<Managed<T>>(entity);
if (Unsafe.IsNullRef(ref component))
{
return ref Unsafe.NullRef<T>();
}
return ref GetOrCreateManagedComponentStorage<T>().GetComponentReference(component);
}
public ref T GetManagedComponent<T>(Managed<T> managedComponent)
where T : IManagedComponent
{
return ref GetOrCreateManagedComponentStorage<T>().GetComponentReference(managedComponent);
}
}
#endif

View File

@@ -0,0 +1,69 @@
using System.Runtime.CompilerServices;
namespace Ghost.Entities;
public record struct ManagedEntity
{
public int id;
public int generation;
}
public struct ManagedEntityRef : IComponent
{
public ManagedEntity entity;
}
public abstract class ScriptComponent
{
internal World _world = null!;
internal Entity _entity;
internal ManagedEntity _managedEntity;
public World World => _world;
public Entity Entity => _entity;
public ManagedEntity ManagedEntity => _managedEntity;
protected ref T GetComponent<T>()
where T : unmanaged, IComponent
{
ref var value = ref _world.EntityManager.GetComponent<T>(_entity);
if (Unsafe.IsNullRef(ref value))
{
throw new InvalidOperationException($"Entity {_entity} does not have component of type {typeof(T)}");
}
return ref value;
}
public virtual void OnCreate()
{
}
public virtual void OnDestroy()
{
}
public virtual void OnEnable()
{
}
public virtual void OnDisable()
{
}
public virtual void Start()
{
}
public virtual void Update()
{
}
public virtual void FixedUpdate()
{
}
public virtual void LateUpdate()
{
}
}

View File

@@ -0,0 +1,646 @@
using Ghost.Core;
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections;
using System.Runtime.CompilerServices;
namespace Ghost.Entities;
internal struct EntityQueryMask : IDisposable, IEquatable<EntityQueryMask>
{
public UnsafeBitSet structuralAll;
public UnsafeBitSet structuralAny;
public UnsafeBitSet structuralAbsent;
public UnsafeBitSet requireEnabled;
public UnsafeBitSet requireDisabled;
public UnsafeBitSet rejectIfEnabled;
public UnsafeBitSet writeAccess;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly bool Matches(ref readonly UnsafeBitSet archetypeSignature)
{
return (!structuralAll.IsCreated || structuralAll.All(archetypeSignature))
&& (!structuralAbsent.IsCreated || structuralAbsent.None(archetypeSignature))
&& (!structuralAny.IsCreated || structuralAny.Count == 0 || structuralAny.Any(archetypeSignature));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override readonly int GetHashCode()
{
var hash = 17;
if (structuralAll.IsCreated) hash = hash * 23 + structuralAll.GetHashCode();
if (structuralAbsent.IsCreated) hash = hash * 23 + structuralAbsent.GetHashCode();
if (structuralAny.IsCreated) hash = hash * 23 + structuralAny.GetHashCode();
if (requireEnabled.IsCreated) hash = hash * 23 + requireEnabled.GetHashCode();
if (requireDisabled.IsCreated) hash = hash * 23 + requireDisabled.GetHashCode();
if (rejectIfEnabled.IsCreated) hash = hash * 23 + rejectIfEnabled.GetHashCode();
if (writeAccess.IsCreated) hash = hash * 23 + writeAccess.GetHashCode();
return hash;
}
public readonly bool Equals(EntityQueryMask other)
{
return structuralAll.Equals(other.structuralAll)
&& structuralAny.Equals(other.structuralAny)
&& structuralAbsent.Equals(other.structuralAbsent)
&& requireEnabled.Equals(other.requireEnabled)
&& requireDisabled.Equals(other.requireDisabled)
&& rejectIfEnabled.Equals(other.rejectIfEnabled)
&& writeAccess.Equals(other.writeAccess);
}
public override readonly bool Equals(object? obj)
{
return obj is EntityQueryMask mask && Equals(mask);
}
public static bool operator ==(EntityQueryMask left, EntityQueryMask right)
{
return left.Equals(right);
}
public static bool operator !=(EntityQueryMask left, EntityQueryMask right)
{
return !(left == right);
}
public void Dispose()
{
structuralAll.Dispose();
structuralAny.Dispose();
structuralAbsent.Dispose();
requireEnabled.Dispose();
requireDisabled.Dispose();
rejectIfEnabled.Dispose();
writeAccess.Dispose();
}
}
/// <summary>
/// Provides a read-only view over a chunk of entities and their component data within an archetype.
/// </summary>
/// <remarks>This does not filter disabled/enabled components. You must handle that manually.</remarks>
public readonly unsafe ref struct ChunkView
{
// We flatten all the information we need for fast access.
private readonly ReadOnlyUnsafeCollection<Archetype.ComponentMemoryLayout> _layouts;
private readonly ReadOnlyUnsafeCollection<int> _layoutIndexLookup;
private readonly byte* _pChunkData;
private readonly int* _pVersion;
private readonly int _entityOffset;
private readonly int _entityCount;
private readonly int _structuralVersion;
private readonly int _currentVersion;
public readonly int Count => _entityCount;
internal ChunkView(ref readonly Archetype archetype, ref readonly Chunk chunk)
{
_layouts = archetype._layouts.AsReadOnly();
_layoutIndexLookup = archetype._componentIDToLayoutIndex.AsReadOnly();
_pChunkData = chunk.GetUnsafePtr();
_entityOffset = archetype.EntityIDsOffset;
_entityCount = chunk._count;
_pVersion = chunk.GetVersionUnsafePtr();
_structuralVersion = chunk._structuralVersion;
_currentVersion = World.GetWorldUncheck(archetype.WorldID).Version;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private Archetype.ComponentMemoryLayout GetLayout(Identifier<IComponent> id)
{
var index = _layoutIndexLookup[id.Value];
if (index == -1)
{
throw new InvalidOperationException($"Component {id} is not exist in the archetype.");
}
return _layouts[index];
}
/// <summary>
/// Determines whether the specified component has changed since the given version.
/// </summary>
/// <param name="id">The identifier of the component to check for changes.</param>
/// <param name="version">The version number to compare against the component's current version. Must be greater than or equal to zero.</param>
/// <returns>true if the component's current version is less than or equal to the specified version; otherwise, false.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool HasChanged(Identifier<IComponent> id, int version)
{
var layout = GetLayout(id);
return version < _pVersion[layout.versionIndex];
}
/// <summary>
/// Determines whether the specified version indicates that the component of space <typeparamref name="T"/> has
/// changed since the last recorded version.
/// </summary>
/// <typeparam name="T">The space of component to check for changes. Must be an unmanaged space that implements <see cref="IComponent"/>.</typeparam>
/// <param name="version">The version number to compare against the current version of the component.</param>
/// <returns>true if the component of space T has changed since the specified version; otherwise, false.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly bool HasChanged<T>(int version)
where T : unmanaged, IComponent
{
var layout = GetLayout(ComponentTypeID<T>.Value);
return version < _pVersion[layout.versionIndex];
}
/// <summary>
/// Determines whether the chunk's structure has changed since the specified version.
/// </summary>
/// <param name="version">The version number to compare against the chunk's structural version.</param>
/// <returns>true if the chunk's structure has changed since the specified version; otherwise, false.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly bool HasStructuralChanged(int version)
{
return version < _structuralVersion;
}
/// <summary>
/// Gets the current version number associated with the specified component identifier.
/// </summary>
/// <param name="id">The identifier of the component for which to retrieve the version number. Must reference a valid component.</param>
/// <returns>The version number of the specified component.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly int GetComponentVersion(Identifier<IComponent> id)
{
return _pVersion[id];
}
/// <summary>
/// Gets the current version number associated with the specified component space.
/// </summary>
/// <typeparam name="T">The component space for which to retrieve the version. Must be an unmanaged space that implements <see cref="IComponent"/>.</typeparam>
/// <returns>The version number of the component space <typeparamref name="T"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly int GetComponentVersion<T>()
where T : unmanaged, IComponent
{
return _pVersion[ComponentTypeID<T>.Value];
}
/// <summary>
/// Returns a read-only span containing structuralAll entities stored in the current chunk.
/// </summary>
/// <returns>A read-only span of <see cref="Entity"/> values representing the entities in the chunk.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ReadOnlySpan<Entity> GetEntities()
{
var pEntity = (Entity*)(_pChunkData + _entityOffset);
return new ReadOnlySpan<Entity>(pEntity, _entityCount);
}
/// <summary>
/// Gets a readonly span providing direct access to the component data of space T0 for structuralAll entities in the chunk.
/// </summary>
/// <typeparam name="T">The space of component to access. Must be an unmanaged space that implements <see cref="Component"/>.</typeparam>
/// <returns>A readonly span of space <see cref="{T}"/> containing the component data for each entity in the chunk.</returns>
/// <exception cref="InvalidOperationException">Thrown if the specified component space is not present in the archetype.</exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ReadOnlySpan<T> GetComponentData<T>()
where T : unmanaged, IComponent
{
var layout = GetLayout(ComponentTypeID<T>.Value);
var pComponentData = _pChunkData + layout.offset;
return new ReadOnlySpan<T>(pComponentData, _entityCount);
}
/// <summary>
/// Gets a span providing direct access to the component data of space T0 for structuralAll entities in the chunk.
/// </summary>
/// <typeparam name="T">The space of component to access. Must be an unmanaged space that implements <see cref="Component"/>.</typeparam>
/// <returns>A span of space <see cref="{T}"/> containing the component data for each entity in the chunk.</returns>
/// <exception cref="InvalidOperationException">Thrown if the specified component space is not present in the archetype.</exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Span<T> GetComponentDataRW<T>()
where T : unmanaged, IComponent
{
var compId = ComponentTypeID<T>.Value;
var layout = GetLayout(compId);
_pVersion[layout.versionIndex] = _currentVersion;
var pComponentData = _pChunkData + layout.offset;
return new Span<T>(pComponentData, _entityCount);
}
/// <summary>
/// Gets a bit set representing the enabled state of each instance of the specified enableable component
/// space within the current chunk.
/// </summary>
/// <typeparam name="T">The component space for which to retrieve enablement bits. Must be unmanaged and implement <see cref="IEnableableComponent"/>.</typeparam>
/// <returns>A <see cref="SpanBitSet"/> that provides access to the enablement bits for all instances of the specified component space in the chunk.</returns>
/// <exception cref="InvalidOperationException">Thrown if the specified component space does not support enablement.</exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public SpanBitSet GetEnableBits<T>()
where T : unmanaged, IEnableableComponent
{
var layout = _layouts[ComponentTypeID<T>.Value];
var maskBase = _pChunkData + layout.enableBitsOffset;
return new SpanBitSet(new Span<uint>(maskBase, (_entityCount + 31) / 32));
}
/// <summary>
/// Determines whether the specified component of space <typeparamref name="T"/> at the given index is currently enabled.
/// </summary>
/// <typeparam name="T">The space of the component to check. Must be an unmanaged space that implements <see cref="IEnableableComponent"/>.</typeparam>
/// <param name="index">The zero-based index of the component instance to check within the chunk.</param>
/// <returns>true if the component at the specified index is enabled; otherwise, false.</returns>
/// <exception cref="InvalidOperationException">Thrown if the specified component space <typeparamref name="T"/> does not support enable/disable functionality.</exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool IsComponentEnabled<T>(int index)
where T : unmanaged, IEnableableComponent
{
var layout = GetLayout(ComponentTypeID<T>.Value);
var pMask = _pChunkData + layout.enableBitsOffset;
return EntityQuery.CheckBit(pMask, index);
}
}
public unsafe partial struct EntityQuery : IDisposable
{
/// <summary>
/// Provides an enumerator for iterating over chunks of entities and their component data that match a set of archetypes within a world.
/// </summary>
public readonly ref struct ChunkIterator
{
public ref struct Enumerator
{
private readonly ChunkIterator _iterator;
private int _archetypeIndex;
private int _chunkIndex;
internal Enumerator(ChunkIterator iterator)
{
_iterator = iterator;
_archetypeIndex = 0;
_chunkIndex = -1;
}
public readonly ChunkView Current
{
get
{
ref var archetype = ref _iterator._world.ComponentManager.GetArchetypeReference(_iterator._matchingArchetypes[_archetypeIndex]);
ref var chunk = ref archetype.GetChunkReference(_chunkIndex);
return new ChunkView(in archetype, in chunk);
}
}
public bool MoveNext()
{
_chunkIndex++;
while (_archetypeIndex < _iterator._matchingArchetypes.Count)
{
ref var archetype = ref _iterator._world.ComponentManager.GetArchetypeReference(_iterator._matchingArchetypes[_archetypeIndex]);
if (_chunkIndex < archetype.ChunkCount)
{
return true;
}
_chunkIndex = 0;
_archetypeIndex++;
}
return false;
}
public void Reset()
{
_archetypeIndex = 0;
_chunkIndex = -1;
}
}
private readonly ReadOnlyUnsafeCollection<Identifier<Archetype>> _matchingArchetypes;
private readonly World _world;
internal ChunkIterator(ReadOnlyUnsafeCollection<Identifier<Archetype>> matchingArchetypes, World world)
{
_matchingArchetypes = matchingArchetypes;
_world = world;
}
public readonly Enumerator GetEnumerator()
{
return new Enumerator(this);
}
}
internal EntityQueryMask _mask;
private UnsafeList<Identifier<Archetype>> _matchingArchetypes;
private readonly Identifier<EntityQuery> _id;
private readonly Identifier<World> _worldID;
internal EntityQuery(Identifier<EntityQuery> id, Identifier<World> worldID, EntityQueryMask mask)
{
_id = id;
_worldID = worldID;
_mask = mask;
_matchingArchetypes = new UnsafeList<Identifier<Archetype>>(8, Allocator.Persistent);
}
// TODO: Fetching layout every time is not optimal. Cache them?
private static bool IsEntityValid(byte* chunkBase, int entityIndex, ref readonly Archetype archetype, ref readonly EntityQueryMask mask)
{
// 1. Check "Require Enabled" (WithAll)
var it = mask.requireEnabled.GetIterator();
while (it.Next(out var id))
{
// Get the EnableBitmask for this component in this chunk
var layoutResult = archetype.GetLayout(id);
if (layoutResult.Error != Error.None
// Not enableable, always true
|| layoutResult.Value.enableBitsOffset == -1)
{
continue;
}
// Check bit
if (!CheckBit(chunkBase + layoutResult.Value.enableBitsOffset, entityIndex))
{
return false;
}
}
// 2. Check "Require Disabled" (WithDisabled)
it = mask.requireDisabled.GetIterator();
while (it.Next(out var id))
{
var layoutResult = archetype.GetLayout(id);
if (layoutResult.Error != Error.None)
{
continue;
}
// If component is not enableable, it is technically "Always Enabled",
// so it cannot satisfy "WithDisabled".
// Check bit (Must be 0)
if (layoutResult.Value.enableBitsOffset == -1
|| CheckBit(chunkBase + layoutResult.Value.enableBitsOffset, entityIndex))
{
return false;
}
}
// 3. Check "Reject if Enabled" (The "Soft WithNone")
it = mask.rejectIfEnabled.GetIterator();
while (it.Next(out var id))
{
var layoutResult = archetype.GetLayout(id);
if (layoutResult.Error != Error.None)
{
continue;
}
// If component is not enableable, it is technically "Always Enabled",
// so it cannot satisfy "Reject if Enabled".
// Check bit (Must be 0)
if (layoutResult.Value.enableBitsOffset == -1
|| CheckBit(chunkBase + layoutResult.Value.enableBitsOffset, entityIndex))
{
return false;
}
}
return true;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static bool CheckBit(byte* maskBase, int index)
{
var byteIndex = index >> Chunk.BIT_SHIFT;
var bitIndex = index & Chunk.BIT_ALIGNMENT_MINUS_ONE;
return (maskBase[byteIndex] & (1 << bitIndex)) != 0;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void AddArchetypeIfMatch(ref readonly Archetype archetype)
{
if (_mask.Matches(in archetype._signature))
{
_matchingArchetypes.Add(archetype.ID);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly ChunkIterator GetChunkIterator()
{
var world = World.GetWorld(_worldID);
if (world is null)
{
return default;
}
return new ChunkIterator(_matchingArchetypes.AsReadOnly(), world);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly int GetEntityCount()
{
var total = 0;
var world = World.GetWorld(_worldID);
if (world is null)
{
return 0;
}
for(var i = 0; i < _matchingArchetypes.Count; i++)
{
var archetypeID = _matchingArchetypes[i];
ref var archetype = ref world.ComponentManager.GetArchetypeReference(archetypeID);
for (var j = 0; j < archetype.ChunkCount; j++)
{
ref var chunk = ref archetype.GetChunkReference(j);
total += chunk._count;
}
}
return total;
}
public void Dispose()
{
_mask.Dispose();
_matchingArchetypes.Dispose();
}
}
public ref partial struct QueryBuilder
{
private readonly Stack.Scope _scope;
private UnsafeList<Identifier<IComponent>> _all;
private UnsafeList<Identifier<IComponent>> _any;
private UnsafeList<Identifier<IComponent>> _absent;
private UnsafeList<Identifier<IComponent>> _none;
private UnsafeList<Identifier<IComponent>> _disabled;
private UnsafeList<Identifier<IComponent>> _present;
private UnsafeList<Identifier<IComponent>> _rw;
public QueryBuilder()
{
_scope = AllocationManager.CreateStackScope();
_all = new UnsafeList<Identifier<IComponent>>(4, _scope.AllocationHandle);
_any = new UnsafeList<Identifier<IComponent>>(4, _scope.AllocationHandle);
_absent = new UnsafeList<Identifier<IComponent>>(4, _scope.AllocationHandle);
_none = new UnsafeList<Identifier<IComponent>>(4, _scope.AllocationHandle);
_disabled = new UnsafeList<Identifier<IComponent>>(4, _scope.AllocationHandle);
_present = new UnsafeList<Identifier<IComponent>>(4, _scope.AllocationHandle);
_rw = new UnsafeList<Identifier<IComponent>>(4, _scope.AllocationHandle);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void FindMax(UnsafeList<Identifier<IComponent>> list, ref int max)
{
foreach (var id in list)
{
if (id.Value > max) max = id.Value;
}
}
public void WithAll(params Span<Identifier<IComponent>> componentIDs)
{
_all.AddRange(componentIDs, componentIDs.Length);
}
public void WithAny(params Span<Identifier<IComponent>> componentIDs)
{
_any.AddRange(componentIDs, componentIDs.Length);
}
public void WithAbsent(params Span<Identifier<IComponent>> componentIDs)
{
_absent.AddRange(componentIDs, componentIDs.Length);
}
public void WithNone(params Span<Identifier<IComponent>> componentIDs)
{
_none.AddRange(componentIDs, componentIDs.Length);
}
public void WithDisabled(params Span<Identifier<IComponent>> componentIDs)
{
_disabled.AddRange(componentIDs, componentIDs.Length);
}
public void WithPresent(params Span<Identifier<IComponent>> componentIDs)
{
_present.AddRange(componentIDs, componentIDs.Length);
}
public void WithPresentRW(params Span<Identifier<IComponent>> componentIDs)
{
_present.AddRange(componentIDs, componentIDs.Length);
_rw.AddRange(componentIDs, componentIDs.Length);
}
public Identifier<EntityQuery> Build(World world, Allocator allocator = Allocator.Persistent)
{
// 1. Calculate max component ID to size the BitSets
var maxID = 0;
FindMax(_all, ref maxID);
FindMax(_any, ref maxID);
FindMax(_absent, ref maxID);
FindMax(_none, ref maxID);
FindMax(_disabled, ref maxID);
FindMax(_present, ref maxID);
// 2. Create the Mask
var mask = new EntityQueryMask
{
structuralAll = new UnsafeBitSet(maxID + 1, allocator, AllocationOption.Clear),
structuralAny = new UnsafeBitSet(maxID + 1, allocator, AllocationOption.Clear),
structuralAbsent = new UnsafeBitSet(maxID + 1, allocator, AllocationOption.Clear),
requireEnabled = new UnsafeBitSet(maxID + 1, allocator, AllocationOption.Clear),
requireDisabled = new UnsafeBitSet(maxID + 1, allocator, AllocationOption.Clear),
rejectIfEnabled = new UnsafeBitSet(maxID + 1, allocator, AllocationOption.Clear),
writeAccess = new UnsafeBitSet(maxID + 1, allocator, AllocationOption.Clear),
};
// 3. Fill BitSets
foreach (var id in _all)
{
mask.structuralAll.SetBit(id); // Structure: Must Exist
mask.requireEnabled.SetBit(id); // Filter: Must be Enabled
}
foreach (var id in _disabled)
{
mask.structuralAll.SetBit(id); // Structure: Must Exist
mask.requireDisabled.SetBit(id); // Filter: Must be Disabled
}
foreach (var id in _none)
{
if (ComponentRegistry.GetComponentInfo(id).isEnableable)
{
mask.rejectIfEnabled.SetBit(id); // Filter: Must Not be Enabled (Can be Absent or Disabled)
}
else
{
mask.structuralAbsent.SetBit(id); // Structure: Must Not Exist
}
}
foreach (var id in _present)
{
mask.structuralAll.SetBit(id);
}
foreach (var id in _absent)
{
mask.structuralAbsent.SetBit(id);
}
foreach (var id in _any)
{
mask.structuralAny.SetBit(id);
}
foreach (var id in _rw)
{
mask.writeAccess.SetBit(id);
}
// 4. Ask World for the Query (Cached)
var maskHash = mask.GetHashCode();
var queryID = world.ComponentManager.GetEntityQueryIDByMaskHash(maskHash);
if (queryID.IsValid)
{
// Check if the masks are actually equal (Hash collision?).
// Really worth it? It's unlikely to have collisions here.
if (world.ComponentManager.GetEntityQueryReference(queryID)._mask.Equals(mask))
{
mask.Dispose();
goto Return;
}
}
// NOTE: We do not dispose the mask here, as it is now owned by the EntityQuery.
queryID = world.ComponentManager.CreateEntityQuery(mask, maskHash);
Return:
Dispose();
return queryID;
}
private readonly void Dispose()
{
_scope.Dispose();
}
}

View File

@@ -0,0 +1,176 @@
#if false
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections;
using Misaki.HighPerformance.LowLevel.Utilities;
namespace Ghost.Entities;
public interface ISharedComponent
{
}
internal unsafe sealed class SharedComponentStore : IDisposable
{
private struct EntryInfo
{
public int RefCount;
public int HashCode;
public int Version;
public int NextFree; // free-list linkage (index)
}
private struct TypeStore : IDisposable
{
public int TypeSize;
public UnsafeList<byte> Data; // raw bytes, stride = TypeSize
public UnsafeList<EntryInfo> Infos; // parallel to Data entries (Entry 0 reserved)
public UnsafeHashMap<long, int> HashLookup; // (hashKey) -> entryIndex
public int FreeListHead; // head index, 0 means none (we'll use Infos[0].NextFree too if you prefer)
public int VersionCounter;
public void Dispose()
{
Data.Dispose();
Infos.Dispose();
HashLookup.Dispose();
}
}
private readonly UnsafeHashMap<int, TypeStore> _perType; // componentTypeId -> TypeStore
public SharedComponentStore(int initialCapacity = 16)
{
_perType = new UnsafeHashMap<int, TypeStore>(initialCapacity, Allocator.Persistent);
}
public void Dispose()
{
foreach (var kvp in _perType)
{
kvp.Value.Dispose();
}
_perType.Dispose();
}
public int InsertOrGet(int componentTypeId, int typeSize, void* data, int hashCode)
{
// Reserve index 0 for "default"
if (data == null)
{
return 0;
}
ref var store = ref GetOrCreateTypeStore(componentTypeId, typeSize);
// Combine (typeId, hash) into a single key; collisions handled by memcmp below.
var key = ((long)componentTypeId << 32) ^ (uint)hashCode;
if (store.HashLookup.TryGetValue(key, out var existingIndex))
{
var existingPtr = (byte*)store.Data.GetUnsafePtr() + (existingIndex * store.TypeSize);
if (new Span<byte>(existingPtr, store.TypeSize).SequenceEqual(new Span<byte>(data, store.TypeSize)))
{
((EntryInfo*)store.Infos.GetUnsafePtr())[existingIndex].RefCount++;
return existingIndex;
}
// If collision: fall through to insert (you may want a secondary structure).
}
int index = AllocateEntry(ref store);
var dst = (byte*)store.Data.GetUnsafePtr() + (index * store.TypeSize);
MemoryUtility.MemCpy(dst, data, (nuint)store.TypeSize);
store.Infos[index] = new EntryInfo
{
RefCount = 1,
HashCode = hashCode,
Version = ++store.VersionCounter,
NextFree = -1
};
store.HashLookup[key] = index;
return index;
}
public void AddRef(int componentTypeId, int index)
{
if (index == 0) return;
ref var store = ref _perType[componentTypeId];
store.Infos[index].RefCount++;
}
public void Release(int componentTypeId, int index)
{
if (index == 0) return;
ref var store = ref _perType.GetValueByKey(componentTypeId);
ref var info = ref store.Infos.Ptr[index];
info.RefCount--;
if (info.RefCount > 0) return;
// Remove from hash lookup (best-effort; collisions require more robust handling)
long key = ((long)componentTypeId << 32) ^ (uint)info.HashCode;
store.HashLookup.Remove(key);
// Push to free-list
info.NextFree = store.FreeListHead;
store.FreeListHead = index;
}
public void* GetDataPtr(int componentTypeId, int index)
{
if (index == 0) return null;
ref var store = ref _perType.GetValueByKey(componentTypeId);
return (byte*)store.Data.Ptr + (index * store.TypeSize);
}
private ref TypeStore GetOrCreateTypeStore(int componentTypeId, int typeSize)
{
if (_perType.TryGetValue(componentTypeId, out var existing))
{
// UnsafeHashMap returns by value in some implementations; you may need a different pattern here.
// Adjust to your container API (e.g., TryGetValueRef).
}
var store = new TypeStore
{
TypeSize = typeSize,
Data = new UnsafeList<byte>(typeSize * 16, Allocator.Persistent),
Infos = new UnsafeList<EntryInfo>(16, Allocator.Persistent),
HashLookup = new UnsafeHashMap<long, int>(16, Allocator.Persistent),
FreeListHead = 0,
VersionCounter = 0
};
// Create reserved default entry at index 0
store.Data.Resize(typeSize); // one element worth of bytes
store.Infos.Add(new EntryInfo { RefCount = int.MaxValue, HashCode = 0, Version = 0, NextFree = -1 });
_perType.Add(componentTypeId, store);
// NOTE: returning a ref requires a "get ref" API; adjust to your UnsafeHashMap capabilities.
return ref _perType.GetValueByKey(componentTypeId);
}
private static int AllocateEntry(ref TypeStore store)
{
if (store.FreeListHead != 0)
{
int idx = store.FreeListHead;
store.FreeListHead = store.Infos[idx].NextFree;
store.Infos[idx].NextFree = -1;
return idx;
}
int newIndex = store.Infos.Count;
store.Infos.Add(default);
int newByteCount = (newIndex + 1) * store.TypeSize;
store.Data.Resize(newByteCount);
return newIndex;
}
}
#endif

View File

@@ -0,0 +1,409 @@
using Ghost.Core;
namespace Ghost.Entities;
public readonly ref struct SystemAPI
{
public TimeData Time
{
get; init;
}
}
public interface ISystem
{
World World
{
get; init;
}
void Initialize(ref readonly SystemAPI systemAPI);
void Update(ref readonly SystemAPI systemAPI);
void Cleanup(ref readonly SystemAPI systemAPI);
}
public abstract class SystemBase : ISystem
{
private List<int>? _requiredQueries;
public World World
{
get; init;
} = null!;
public int LastSystemVersion
{
get; internal set;
} = -2;
private bool ShouldUpdate()
{
if (_requiredQueries == null || _requiredQueries.Count == 0)
{
return true;
}
foreach (var queryID in _requiredQueries)
{
ref var query = ref World.ComponentManager.GetEntityQueryReference(new Identifier<EntityQuery>(queryID));
if (query.GetEntityCount() == 0)
{
return false;
}
}
return true;
}
protected void RequireQueryForUpdate(Identifier<EntityQuery> queryID)
{
_requiredQueries ??= new List<int>(4);
_requiredQueries.Add(queryID.Value);
}
void ISystem.Initialize(ref readonly SystemAPI systemAPI)
{
OnInitialize(in systemAPI);
}
void ISystem.Update(ref readonly SystemAPI systemAPI)
{
if (ShouldUpdate())
{
if (World.Version - LastSystemVersion > 1)
{
OnStartRunning();
}
OnUpdate(in systemAPI);
LastSystemVersion = World.Version;
}
else
{
if (World.Version - LastSystemVersion <= 1)
{
OnStopRunning();
}
}
}
void ISystem.Cleanup(ref readonly SystemAPI systemAPI)
{
OnCleanup(in systemAPI);
}
protected virtual void OnInitialize(ref readonly SystemAPI systemAPI)
{
}
protected virtual void OnUpdate(ref readonly SystemAPI systemAPI)
{
}
protected virtual void OnCleanup(ref readonly SystemAPI systemAPI)
{
}
protected virtual void OnStopRunning()
{
}
protected virtual void OnStartRunning()
{
}
}
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = true)]
public class UpdateAfterAttribute : Attribute
{
public Type SystemType { get; }
public UpdateAfterAttribute(Type systemType)
{
SystemType = systemType;
}
}
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = true)]
public class UpdateBeforeAttribute : Attribute
{
public Type SystemType { get; }
public UpdateBeforeAttribute(Type systemType)
{
SystemType = systemType;
}
}
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = false)]
public class SystemGroupAttribute : Attribute
{
public Type GroupType { get; }
public SystemGroupAttribute(Type groupType)
{
GroupType = groupType;
}
}
#if false
internal static partial class SystemGroupRegistry
{
private static readonly Dictionary<Type, List<ISystem>> _systemGroupMap = new();
// TODO: Use Source Generators to generate group registrations at compile time.
public static void RegisterSystemGroup<T>(Type groupType)
where T : ISystem, new()
{
if (!_systemGroupMap.ContainsKey(typeof(T)))
{
_systemGroupMap[typeof(T)] = new();
}
_systemGroupMap[groupType].Add(new T());
}
public static List<ISystem> GetSystemsForGroup(Type groupType)
{
if (_systemGroupMap.TryGetValue(groupType, out var systems))
{
return systems;
}
throw new InvalidOperationException($"No systems registered for System Group of type {groupType.FullName}");
}
}
#endif
public abstract class SystemGroup : ISystem
{
private readonly List<ISystem> _systems = [];
private List<ISystem>? _sortedSystems;
private uint _version = 0;
private uint _sortedVersion = 0;
public World World
{
get; init;
} = null!;
// public SystemGroup()
// {
// _systems = SystemGroupRegistry.GetSystemsForGroup(GetType());
// }
private static List<ISystem> Sort(List<ISystem> systems)
{
// 1. Build the Graph
// Key64: The System, Value: Systems that MUST run before the Key64
var dependencies = new Dictionary<Type, HashSet<Type>>();
var systemMap = systems.ToDictionary(s => s.GetType(), s => s);
foreach (var sys in systems)
{
var type = sys.GetType();
if (!dependencies.TryGetValue(type, out HashSet<Type>? value))
{
value = [];
dependencies[type] = value;
}
// Handle [UpdateAfter(typeof(Other))] -> Other comes before This
foreach (var attr in type.GetCustomAttributes(typeof(UpdateAfterAttribute), true))
{
var depType = ((UpdateAfterAttribute)attr).SystemType;
value.Add(depType);
}
// Handle [UpdateBefore(typeof(Other))] -> This comes before Other
// Which means: Other depends on This
foreach (var attr in type.GetCustomAttributes(typeof(UpdateBeforeAttribute), true))
{
var targetType = ((UpdateBeforeAttribute)attr).SystemType;
if (!dependencies.ContainsKey(targetType)) dependencies[targetType] = [];
dependencies[targetType].Add(type);
}
}
// 2. Topological Sort (Kahn's Algorithm variant)
var sortedList = new List<ISystem>();
var visited = new HashSet<Type>();
// We loop until we have sorted everyone
while (sortedList.Count < systems.Count)
{
bool addedAny = false;
foreach (var sys in systems)
{
var type = sys.GetType();
if (visited.Contains(type)) continue;
// Check if all dependencies for this system are already visited/sorted
bool canRun = true;
if (dependencies.TryGetValue(type, out var deps))
{
foreach (var dep in deps)
{
// If the dependency exists in our list but hasn't run yet, we can't run.
// (We check systemMap to ignore dependencies that don't exist in this world)
if (systemMap.ContainsKey(dep) && !visited.Contains(dep))
{
canRun = false;
break;
}
}
}
if (canRun)
{
sortedList.Add(sys);
visited.Add(type);
addedAny = true;
}
}
if (!addedAny)
{
throw new InvalidOperationException("Circular Dependency detected in Systems! Check your [UpdateAfter] attributes.");
}
}
return sortedList;
}
public void AddSystem<T>()
where T : ISystem, new()
{
_systems.Add(new T()
{
World = World
});
_version++;
}
public void SortSystems()
{
if (_sortedVersion == _version)
{
return;
}
if (_systems.Count == 0)
{
_sortedSystems = [];
_sortedVersion = _version;
return;
}
_sortedSystems = Sort(_systems);
_sortedVersion = _version;
}
private void ThrowIfNotSorted()
{
if (_sortedSystems == null || _sortedVersion != _version)
{
throw new InvalidOperationException("Systems must be sorted before calling this method. Call SortSystems() after adding all systems.");
}
}
public void Initialize(ref readonly SystemAPI systemAPI)
{
ThrowIfNotSorted();
foreach (var system in _sortedSystems!)
{
system.Initialize(in systemAPI);
}
}
public void Update(ref readonly SystemAPI systemAPI)
{
ThrowIfNotSorted();
foreach (var system in _sortedSystems!)
{
system.Update(in systemAPI);
}
}
public void Cleanup(ref readonly SystemAPI systemAPI)
{
ThrowIfNotSorted();
foreach (var system in _sortedSystems!)
{
system.Cleanup(in systemAPI);
}
}
}
public class DefaultSystemGroup : SystemGroup
{
}
public class SystemManager
{
private readonly World _world;
private readonly List<ISystem> _systems = [];
internal IReadOnlyList<ISystem> Systems => _systems;
internal SystemManager(World world)
{
_world = world;
AddSystem<DefaultSystemGroup>();
}
public void AddSystem<T>()
where T : ISystem, new()
{
_systems.Add(new T()
{
World = _world
});
}
public T GetSystem<T>()
where T : ISystem
{
foreach (var system in _systems)
{
if (system is T typedSystem)
{
return typedSystem;
}
}
throw new InvalidOperationException($"System of type {typeof(T).FullName} not found in SystemManager.");
}
internal void InitializeAll(ref readonly SystemAPI systemAPI)
{
foreach (var system in _systems)
{
system.Initialize(in systemAPI);
}
}
internal void UpdateAll(ref readonly SystemAPI systemAPI)
{
foreach (var system in _systems)
{
system.Update(in systemAPI);
}
}
internal void CleanupAll(ref readonly SystemAPI systemAPI)
{
foreach (var system in _systems)
{
system.Cleanup(in systemAPI);
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,234 @@
<#@ template language="C#" #>
<#@ output extension="gen.cs" #>
<#@ assembly name="System.Core" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text" #>
<#@ include file="Helpers.ttinclude" #>
using Ghost.Core;
using Misaki.HighPerformance.LowLevel;
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections;
using System.Runtime.CompilerServices;
namespace Ghost.Entities;
public unsafe partial struct EntityQuery
{
<# for (var i = 1; i <= Amount; i++)
{
var generics = AppendParameters(i, "T{0}");
var compGenerics = AppendParameters(i, "ref T{0} component{0}");
var deconstrictOutPrams = AppendParameters(i, "out Ref<T{0}> component{0}");
var restrictions = AppendGenericRestrictionsMultiline(i, "unmanaged, IComponent", 2);
#>
public readonly ref struct ComponentIterator<<#= generics#>>
<#= restrictions #>
{
<# if (i > 1) { #>
public ref struct QueryItem
{
<# for (var j = 0; j < i; j++) { #>
public ref T<#= j #> component<#= j #>;
<# } #>
internal QueryItem(<#= compGenerics #>)
{
<# for (var j = 0; j < i; j++) { #>
this.component<#= j #> = ref component<#= j #>;
<# } #>
}
public void Deconstruct(<#= deconstrictOutPrams #>)
{
<# for (var j = 0; j < i; j++) { #>
component<#= j #> = new Ref<T<#= j #>>(ref this.component<#= j #>);
<# } #>
}
}
<# } #>
public ref struct Enumerator : IDisposable
{
private fixed int _compTypeIDs[<#= i #>];
private fixed int _offsets[<#= i #>];
private fixed long _compBasePtrs[<#= i #>];
private readonly ReadOnlyUnsafeCollection<Identifier<Archetype>> _matchingArchetypes;
private readonly EntityQueryMask _mask;
private readonly World _world;
private readonly Stack.Scope _scope;
private UnsafeList<int> _changedComponentIDs;
private ref Archetype _currentArchetype;
private ref Chunk _currentChunk;
private byte* _chunkBasePtr;
private int _currentChunkEntityCount;
private int _currentArchetypeIndex;
private int _currentChunkIndex;
private int _currentEntityIndex;
internal Enumerator(ReadOnlyUnsafeCollection<Identifier<Archetype>> matchingArchetypes, EntityQueryMask mask, World world)
{
<# for (var j = 0; j < i; j++) { #>
_compTypeIDs[<#= j #>] = ComponentTypeID<T<#= j #>>.Value;
_offsets[<#= j #>] = 0;
_compBasePtrs[<#= j #>] = 0;
<# } #>
_matchingArchetypes = matchingArchetypes;
_mask = mask;
_world = world;
_scope = AllocationManager.CreateStackScope();
_changedComponentIDs = new UnsafeList<int>(<#= i #>, _scope.AllocationHandle);
var it = _mask.writeAccess.GetIterator();
while (it.Next(out var id))
{
for (var i = 0; i < <#= i #>; i++)
{
if (id == _compTypeIDs[i])
{
_changedComponentIDs.Add(id);
break;
}
}
}
Reset();
}
<# if (i > 1) { #>
public QueryItem Current => new(
<# for (var j = 0; j < i; j++) { #>
ref *(T<#= j #>*)(_compBasePtrs[<#= j #>] + _currentEntityIndex * sizeof(T<#= j #>))<#= j < i - 1 ? "," : "" #>
<# } #>
);
<# } else { #>
public ref T0 Current => ref *(T0*)(_compBasePtrs[0] + _currentEntityIndex * sizeof(T0));
<# } #>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void SetChunk(int chunkIndex)
{
_currentChunk = ref _currentArchetype.GetChunkReference(chunkIndex);
_chunkBasePtr = _currentChunk.GetUnsafePtr();
_currentChunkEntityCount = _currentChunk._count;
for (var index = 0; index < <#= i #>; index++)
{
var layout = _currentArchetype.GetLayout(_compTypeIDs[index])
.GetValueOrThrow();
_offsets[index] = layout.offset;
_compBasePtrs[index] = (long)(_chunkBasePtr + _offsets[index]);
}
for (var i = 0; i < _changedComponentIDs.Count; i++)
{
_currentArchetype.MarkChanged(_currentChunkIndex, _changedComponentIDs[i], _world.Version);
}
}
public bool MoveNext()
{
while (true)
{
_currentEntityIndex++;
if (_currentEntityIndex < _currentChunk._count)
{
var pChunkData = _currentChunk.GetUnsafePtr();
if (IsEntityValid(pChunkData, _currentEntityIndex, in _currentArchetype, in _mask))
{
return true;
}
continue;
}
_currentChunkIndex++;
if (!Unsafe.IsNullRef(ref _currentArchetype) && _currentChunkIndex < _currentArchetype.ChunkCount)
{
SetChunk(_currentChunkIndex);
_currentEntityIndex = -1; // Reset for new chunk
continue;
}
_currentArchetypeIndex++;
if (_currentArchetypeIndex < _matchingArchetypes.Count)
{
_currentArchetype = ref _world.ComponentManager.GetArchetypeReference(_matchingArchetypes[_currentArchetypeIndex]);
_currentChunkIndex = 0;
if (_currentArchetype.ChunkCount > 0)
{
SetChunk(0);
_currentEntityIndex = -1;
continue;
}
// If archetype has no chunks, loop will try next archetype
}
else
{
return false; // End of all data
}
}
}
public void Reset()
{
_currentArchetype = ref Unsafe.NullRef<Archetype>();
_currentChunk = ref Unsafe.NullRef<Chunk>();
_currentArchetypeIndex = 0;
_currentChunkIndex = 0;
_currentEntityIndex = -1;
if (_matchingArchetypes.Count > 0)
{
_currentArchetype = ref _world.ComponentManager.GetArchetypeReference(_matchingArchetypes[0]);
if (_currentArchetype.ChunkCount > 0)
{
SetChunk(0);
}
}
}
public readonly void Dispose()
{
_scope.Dispose();
}
}
private readonly ReadOnlyUnsafeCollection<Identifier<Archetype>> _matchingArchetypes;
private readonly EntityQueryMask _mask;
private readonly World _world;
internal ComponentIterator(ReadOnlyUnsafeCollection<Identifier<Archetype>> matchingArchetypes, EntityQueryMask mask, World world)
{
_matchingArchetypes = matchingArchetypes;
_mask = mask;
_world = world;
}
public Enumerator GetEnumerator()
{
return new Enumerator(_matchingArchetypes, _mask, _world);
}
}
public readonly ComponentIterator<<#= generics#>> GetComponentIterator<<#= generics#>>()
<#= restrictions #>
{
var world = World.GetWorld(_worldID);
if (world is null)
{
return default;
}
return new ComponentIterator<<#= generics#>>(_matchingArchetypes.AsReadOnly(), _mask, world);
}
<# } #>
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,235 @@
<#@ template language="C#" #>
<#@ output extension="gen.cs" #>
<#@ assembly name="System.Core" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text" #>
<#@ include file="Helpers.ttinclude" #>
using Ghost.Core;
using Misaki.HighPerformance.LowLevel;
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections;
using System.Runtime.CompilerServices;
namespace Ghost.Entities;
public unsafe partial struct EntityQuery
{
<# for (var i = 1; i <= Amount; i++)
{
var generics = AppendParameters(i, "T{0}");
var compGenerics = AppendParameters(i, "ref T{0} component{0}");
var deconstrictOutPrams = AppendParameters(i, "out Ref<T{0}> component{0}");
var restrictions = AppendGenericRestrictionsMultiline(i, "unmanaged, IComponent", 2);
#>
public readonly ref struct EntityComponentIterator<<#= generics #>>
<#= restrictions #>
{
public ref struct QueryItem
{
public Entity entity;
<# for (var j = 0; j < i; j++) { #>
public ref T<#= j #> component<#= j #>;
<# } #>
internal QueryItem(Entity entity, <#= compGenerics #>)
{
this.entity = entity;
<# for (var j = 0; j < i; j++) { #>
this.component<#= j #> = ref component<#= j #>;
<# } #>
}
public void Deconstruct(out Entity entity, <#= deconstrictOutPrams #>)
{
entity = this.entity;
<# for (var j = 0; j < i; j++) { #>
component<#= j #> = new Ref<T<#= j #>>(ref this.component<#= j #>);
<# } #>
}
}
public ref struct Enumerator : IDisposable
{
private fixed int _compTypeIDs[<#= i #>];
private fixed int _offsets[<#= i #>];
private fixed long _compBasePtrs[<#= i #>];
private readonly ReadOnlyUnsafeCollection<Identifier<Archetype>> _matchingArchetypes;
private readonly EntityQueryMask _mask;
private readonly World _world;
private readonly Stack.Scope _scope;
private UnsafeList<int> _changedComponentIDs;
private ref Archetype _currentArchetype;
private ref Chunk _currentChunk;
private byte* _chunkBasePtr;
private int _currentChunkEntityCount;
private int _currentArchetypeIndex;
private int _currentChunkIndex;
private int _currentEntityIndex;
internal Enumerator(ReadOnlyUnsafeCollection<Identifier<Archetype>> matchingArchetypes, EntityQueryMask mask, World world)
{
<# for (var j = 0; j < i; j++) { #>
_compTypeIDs[<#= j #>] = ComponentTypeID<T<#= j #>>.Value;
_offsets[<#= j #>] = 0;
_compBasePtrs[<#= j #>] = 0;
<# } #>
_matchingArchetypes = matchingArchetypes;
_mask = mask;
_world = world;
_scope = AllocationManager.CreateStackScope();
_changedComponentIDs = new UnsafeList<int>(<#= i #>, _scope.AllocationHandle);
var it = _mask.writeAccess.GetIterator();
while (it.Next(out var id))
{
for (var i = 0; i < <#= i #>; i++)
{
if (id == _compTypeIDs[i])
{
_changedComponentIDs.Add(id);
break;
}
}
}
Reset();
}
public QueryItem Current => new(
*(Entity*)(_chunkBasePtr + _currentArchetype.EntityIDsOffset + _currentEntityIndex * sizeof(Entity)),
<# for (var j = 0; j < i; j++) { #>
ref *(T<#= j #>*)(_compBasePtrs[<#= j #>] + _currentEntityIndex * sizeof(T<#= j #>))<#= j < i - 1 ? "," : "" #>
<# } #>
);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void SetChunk(int chunkIndex)
{
_currentChunk = ref _currentArchetype.GetChunkReference(chunkIndex);
_chunkBasePtr = _currentChunk.GetUnsafePtr();
_currentChunkEntityCount = _currentChunk._count;
for (var index = 0; index < <#= i #>; index++)
{
var layout = _currentArchetype.GetLayout(_compTypeIDs[index])
.GetValueOrThrow();
_offsets[index] = layout.offset;
_compBasePtrs[index] = (long)(_chunkBasePtr + _offsets[index]);
}
for (var i = 0; i < _changedComponentIDs.Count; i++)
{
_currentArchetype.MarkChanged(_currentChunkIndex, _changedComponentIDs[i], _world.Version);
}
}
public bool MoveNext()
{
while (true)
{
_currentEntityIndex++;
if (_currentEntityIndex < _currentChunk._count)
{
var pChunkData = _currentChunk.GetUnsafePtr();
if (IsEntityValid(pChunkData, _currentEntityIndex, in _currentArchetype, in _mask))
{
return true;
}
continue;
}
_currentChunkIndex++;
if (!Unsafe.IsNullRef(ref _currentArchetype) && _currentChunkIndex < _currentArchetype.ChunkCount)
{
SetChunk(_currentChunkIndex);
_currentEntityIndex = -1; // Reset for new chunk
continue;
}
_currentArchetypeIndex++;
if (_currentArchetypeIndex < _matchingArchetypes.Count)
{
_currentArchetype = ref _world.ComponentManager.GetArchetypeReference(_matchingArchetypes[_currentArchetypeIndex]);
_currentChunkIndex = 0;
if (_currentArchetype.ChunkCount > 0)
{
SetChunk(0);
_currentEntityIndex = -1;
continue;
}
// If archetype has no chunks, loop will try next archetype
}
else
{
return false; // End of all data
}
}
}
public void Reset()
{
_currentArchetype = ref Unsafe.NullRef<Archetype>();
_currentChunk = ref Unsafe.NullRef<Chunk>();
_currentArchetypeIndex = 0;
_currentChunkIndex = 0;
_currentEntityIndex = -1;
if (_matchingArchetypes.Count > 0)
{
_currentArchetype = ref _world.ComponentManager.GetArchetypeReference(_matchingArchetypes[0]);
if (_currentArchetype.ChunkCount > 0)
{
SetChunk(0);
}
}
}
public readonly void Dispose()
{
_scope.Dispose();
}
}
private readonly ReadOnlyUnsafeCollection<Identifier<Archetype>> _matchingArchetypes;
private readonly EntityQueryMask _mask;
private readonly World _world;
internal EntityComponentIterator(ReadOnlyUnsafeCollection<Identifier<Archetype>> matchingArchetypes, EntityQueryMask mask, World world)
{
_matchingArchetypes = matchingArchetypes;
_mask = mask;
_world = world;
}
public Enumerator GetEnumerator()
{
return new Enumerator(_matchingArchetypes, _mask, _world);
}
}
public readonly EntityComponentIterator<<#= generics#>> GetEntityComponentIterator<<#= generics#>>()
<#= restrictions #>
{
var world = World.GetWorld(_worldID);
if (world is null)
{
return default;
}
return new EntityComponentIterator<<#= generics#>>(_matchingArchetypes.AsReadOnly(), _mask, world);
}
<# } #>
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,119 @@
<#@ template language="C#" #>
<#@ output extension="gen.cs" #>
<#@ assembly name="System.Core" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text" #>
<#@ include file="Helpers.ttinclude" #>
namespace Ghost.Entities;
public unsafe partial struct EntityQuery
{
<# for (var f = 0; f < 2; f++)
{
var isForEachWithEntity = f != 0;
#>
<# for (var i = 1; i <= Amount; i++)
{
var generics = AppendParameters(i, "T{0}");
var restrictions = AppendGenericRestrictionsMultiline(i, "unmanaged, IComponent", 2);
var delegateTupe = isForEachWithEntity ? "ForEachWithEntity" : "ForEach";
#>
public readonly void ForEach<<#= generics #>>(<#= delegateTupe #><<#= generics #>> action)
<#= restrictions #>
{
var world = World.GetWorldUncheck(_worldID);
var globalVersion = world.Version;
<# for (var localIndex = 0; localIndex < i; localIndex++) { #>
var comp<#= localIndex #>TypeID = ComponentTypeID<T<#= localIndex #>>.Value;
<# } #>
var compTypeIDs = stackalloc int[]
{
<# for (var localIndex = 0; localIndex < i; localIndex++) { #>
comp<#= localIndex #>TypeID.Value,
<# } #>
};
var changedCompIDs = stackalloc int[<#= i #>];
var offsets = stackalloc int[<#= i #>];
var basePtrs = stackalloc byte*[<#= i #>];
var changedCompCount = 0;
var it = _mask.writeAccess.GetIterator();
while (it.Next(out var id))
{
for (var i =0; i < <#= i #>; i++)
{
if (id == compTypeIDs[i])
{
changedCompIDs[changedCompCount] = id;
changedCompCount++;
break;
}
}
}
for (var i = 0; i < _matchingArchetypes.Count; i++)
{
ref var archetype = ref world.ComponentManager.GetArchetypeReference(_matchingArchetypes[i]);
var hasAllComponents = true;
for (var index = 0; index < <#= i #>; index++)
{
var layoutResult = archetype.GetLayout(compTypeIDs[index]);
if (!layoutResult)
{
hasAllComponents = false;
break;
}
offsets[index] = layoutResult.Value.offset;
}
if (!hasAllComponents)
{
continue;
}
for (var chunkIndex = 0; chunkIndex < archetype.ChunkCount; chunkIndex++)
{
ref var chunk = ref archetype.GetChunkReference(chunkIndex);
var pChunkData = chunk.GetUnsafePtr();
for (var j = 0; j < changedCompCount; j++)
{
archetype.MarkChanged(chunkIndex, changedCompIDs[j], globalVersion);
}
for (var index = 0; index < <#= i #>; index++)
{
basePtrs[index] = pChunkData + offsets[index];
}
for (var entityIndex = 0; entityIndex < chunk._count; entityIndex++)
{
if (!IsEntityValid(pChunkData, entityIndex, in archetype, in _mask))
{
continue;
}
<# for (var localIndex = 0; localIndex < i; localIndex++) { #>
var pComp<#= localIndex #> = (T<#= localIndex #>*)(basePtrs[<#= localIndex #>] + (sizeof(T<#= localIndex #>) * entityIndex));
<# } #>
<# if (isForEachWithEntity) { #>
var pEntity = (Entity*)(pChunkData + archetype.EntityIDsOffset + (sizeof(Entity) * entityIndex));
action(*pEntity, <#= AppendParameters(i, "ref *pComp{0}") #>);
<# } else { #>
action(<#= AppendParameters(i, "ref *pComp{0}") #>);
<# } #>
}
}
}
}
<# } #>
<# } #>
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,243 @@
<#@ template language="C#" #>
<#@ output extension="gen.cs" #>
<#@ assembly name="System.Core" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text" #>
<#@ include file="Helpers.ttinclude" #>
using Ghost.Core;
using Misaki.HighPerformance.Jobs;
using Misaki.HighPerformance.LowLevel.Collections;
namespace Ghost.Entities;
<# for (var i = 1; i <= Amount; i++)
{
var generics = AppendGenerics(i);
var restrictions = AppendGenericRestrictionsMultiline(i, "unmanaged, IComponent", 1);
#>
public interface IJobEntity<<#= generics #>>
<#= restrictions #>
{
void Execute(Entity entity, <#= AppendParameters(i, "ref T{0} component{0}") #>, int threadIndex);
}
internal unsafe struct JobEntityBatch<TJob, <#= generics #>> : IJobParallelFor
where TJob : unmanaged, IJobEntity<<#= generics #>>
<#= restrictions #>
{
public fixed int componentIDs[<#= i #>];
public fixed bool componentRW[<#= i #>];
public TJob userJob;
public UnsafeList<IntPtr> chunks;
public UnsafeList<IntPtr> chunkVersions;
public UnsafeList<int> chunkCount;
public UnsafeList<int> entityOffset;
<# for (var j = 0; j < i; j++){ #>
public UnsafeList<int> offsets<#= j #>;
public UnsafeList<int> bitsOffsets<#= j #>;
public UnsafeList<int> versionindices<#= j #>;
<# } #>
public int version;
public void Execute(int loopIndex, int threadIndex)
{
// 1. Get the specific pChunk for this thread
var pChunk = (byte*)chunks[loopIndex];
var pVersions = (int*)chunkVersions[loopIndex];
var count = chunkCount[loopIndex];
<# for (var j = 0; j < i; j++){ #>
var off<#= j #> = offsets<#= j #>[loopIndex];
var enableOff<#= j #> = bitsOffsets<#= j #>[loopIndex];
var versionIndex<#= j #> = versionindices<#= j #>[loopIndex];
<# } #>
var pEntity = (Entity*)(pChunk + entityOffset[loopIndex]);
<# for (var j = 0; j < i; j++){ #>
var ptr<#= j #> = (<#= "T" + j #>*)(pChunk + off<#= j #>);
<# } #>
// 2. Update versions for RW components
<# for (var j = 0; j < i; j++){ #>
if (componentRW[<#= j #>])
{
pVersions[versionIndex<#= j #>] = version;
}
<# } #>
// 3. Iterate all entities in this chunk
for (var i = 0; i < count; i++)
{
<# for (var j = 0; j < i; j++){ #>
if (enableOff<#= j #> != -1 && !EntityQuery.CheckBit(pChunk + enableOff<#= j #>, i))
{
continue;
}
<# } #>
userJob.Execute(pEntity[i], <#= AppendParameters(i, "ref ptr{0}[i]") #>, threadIndex);
}
}
}
<# } #>
public unsafe partial struct EntityQuery
{
<# for (var i = 1; i <= Amount; i++)
{
var generics = AppendGenerics(i);
var restrictions = AppendGenericRestrictionsMultiline(i, "unmanaged, IComponent", 2);
#>
private struct DisposeJobEntity<#= i #> : IJob
{
public UnsafeList<IntPtr> chunks;
public UnsafeList<IntPtr> chunkVersions;
public UnsafeList<int> chunkEntityCounts;
public UnsafeList<int> entityOffsets;
<# for (var j = 0; j < i; j++){ #>
public UnsafeList<int> offsets<#= j #>;
public UnsafeList<int> bitsOffsets<#= j #>;
public UnsafeList<int> versionindices<#= j #>;
<# } #>
public void Execute(int threadIndex)
{
chunks.Dispose();
chunkVersions.Dispose();
chunkEntityCounts.Dispose();
entityOffsets.Dispose();
<# for (var j = 0; j < i; j++){ #>
offsets<#= j #>.Dispose();
bitsOffsets<#= j #>.Dispose();
versionindices<#= j #>.Dispose();
<# } #>
}
}
public JobHandle ScheduleEntityParallel<TJob, <#= generics #>>(TJob jobData, int batchSize, JobHandle dependency)
where TJob : unmanaged, IJobEntity<<#= generics #>>
<#= restrictions #>
{
var world = World.GetWorld(_worldID);
if (world is null)
{
return JobHandle.Invalid;
}
if (world.JobScheduler == null)
{
throw new InvalidOperationException("The World has no JobScheduler assigned.");
}
// 1. Flatten the World
var chunks = new UnsafeList<IntPtr>(128, JobScheduler.TempAllocatorHandle);
var chunkVersions = new UnsafeList<IntPtr>(128, JobScheduler.TempAllocatorHandle);
var chunkEntityCounts = new UnsafeList<int>(128, JobScheduler.TempAllocatorHandle);
var entityOffsets = new UnsafeList<int>(128, JobScheduler.TempAllocatorHandle);
<# for (var j = 0; j < i; j++){ #>
var offsets<#= j #> = new UnsafeList<int>(128, JobScheduler.TempAllocatorHandle);
var bitsOffsets<#= j #> = new UnsafeList<int>(128, JobScheduler.TempAllocatorHandle);
var versionIndices<#= j #> = new UnsafeList<int>(128, JobScheduler.TempAllocatorHandle);
<# } #>
// Iterate the Query's matching archetypes
foreach (var archID in _matchingArchetypes)
{
ref var arch = ref world.ComponentManager.GetArchetypeReference(archID);
if (arch.ChunkCount == 0)
{
continue;
}
// Get offsets ONCE per archetype
<# for (var j = 0; j < i; j++){ #>
var layout<#= j #> = arch.GetLayout(ComponentTypeID<T<#= j #>>.Value)
.GetValueOrThrow();
<# } #>
// Add all chunks from this archetype
for (var i = 0; i < arch.ChunkCount; i++)
{
ref var chunkRef = ref arch.GetChunkReference(i);
chunks.Add((IntPtr)chunkRef.GetUnsafePtr());
chunkVersions.Add((IntPtr)chunkRef.GetVersionUnsafePtr());
chunkEntityCounts.Add(chunkRef._count);
entityOffsets.Add(arch.EntityIDsOffset);
<# for (var j = 0; j < i; j++){ #>
offsets<#= j #>.Add(layout<#= j #>.offset);
bitsOffsets<#= j #>.Add(layout<#= j #>.enableBitsOffset);
versionIndices<#= j #>.Add(layout<#= j #>.versionIndex);
<# } #>
}
}
// 2. Create the Runner
var runner = new JobEntityBatch<TJob, <#= generics #>>
{
userJob = jobData,
chunks = chunks,
chunkVersions = chunkVersions,
chunkCount = chunkEntityCounts,
entityOffset = entityOffsets,
<# for (var j = 0; j < i; j++){ #>
offsets<#= j #> = offsets<#= j #>,
bitsOffsets<#= j #> = bitsOffsets<#= j #>,
versionindices<#= j #> = versionIndices<#= j #>,
<# } #>
version = world.Version,
};
runner.componentIDs[0] = ComponentTypeID<T0>.Value;
var it = _mask.writeAccess.GetIterator();
while (it.Next(out var id))
{
for (var i =0; i < 1; i++)
{
if (id == runner.componentIDs[i])
{
runner.componentRW[i] = true;
break;
}
}
}
var jobHandle = world.JobScheduler.ScheduleParallel(ref runner, chunks.Count, batchSize, dependency);
// 3. Dispose the temp lists
var disposeJob = new DisposeJobEntity<#= i #>
{
chunks = chunks,
chunkVersions = chunkVersions,
chunkEntityCounts = chunkEntityCounts,
entityOffsets = entityOffsets,
<# for (var j = 0; j < i; j++){ #>
offsets<#= j #> = offsets<#= j #>,
bitsOffsets<#= j #> = bitsOffsets<#= j #>,
versionindices<#= j #> = versionIndices<#= j #>,
<# } #>
};
world.JobScheduler.Schedule(ref disposeJob, jobHandle);
return jobHandle;
}
<# } #>
}

View File

@@ -0,0 +1,36 @@
namespace Ghost.Entities;
public delegate void ForEach<T0>(ref T0 component0)
where T0 : unmanaged, IComponent;
public delegate void ForEach<T0, T1>(ref T0 component0, ref T1 component1)
where T0 : unmanaged, IComponent where T1 : unmanaged, IComponent;
public delegate void ForEach<T0, T1, T2>(ref T0 component0, ref T1 component1, ref T2 component2)
where T0 : unmanaged, IComponent where T1 : unmanaged, IComponent where T2 : unmanaged, IComponent;
public delegate void ForEach<T0, T1, T2, T3>(ref T0 component0, ref T1 component1, ref T2 component2, ref T3 component3)
where T0 : unmanaged, IComponent where T1 : unmanaged, IComponent where T2 : unmanaged, IComponent where T3 : unmanaged, IComponent;
public delegate void ForEach<T0, T1, T2, T3, T4>(ref T0 component0, ref T1 component1, ref T2 component2, ref T3 component3, ref T4 component4)
where T0 : unmanaged, IComponent where T1 : unmanaged, IComponent where T2 : unmanaged, IComponent where T3 : unmanaged, IComponent where T4 : unmanaged, IComponent;
public delegate void ForEach<T0, T1, T2, T3, T4, T5>(ref T0 component0, ref T1 component1, ref T2 component2, ref T3 component3, ref T4 component4, ref T5 component5)
where T0 : unmanaged, IComponent where T1 : unmanaged, IComponent where T2 : unmanaged, IComponent where T3 : unmanaged, IComponent where T4 : unmanaged, IComponent where T5 : unmanaged, IComponent;
public delegate void ForEach<T0, T1, T2, T3, T4, T5, T6>(ref T0 component0, ref T1 component1, ref T2 component2, ref T3 component3, ref T4 component4, ref T5 component5, ref T6 component6)
where T0 : unmanaged, IComponent where T1 : unmanaged, IComponent where T2 : unmanaged, IComponent where T3 : unmanaged, IComponent where T4 : unmanaged, IComponent where T5 : unmanaged, IComponent where T6 : unmanaged, IComponent;
public delegate void ForEach<T0, T1, T2, T3, T4, T5, T6, T7>(ref T0 component0, ref T1 component1, ref T2 component2, ref T3 component3, ref T4 component4, ref T5 component5, ref T6 component6, ref T7 component7)
where T0 : unmanaged, IComponent where T1 : unmanaged, IComponent where T2 : unmanaged, IComponent where T3 : unmanaged, IComponent where T4 : unmanaged, IComponent where T5 : unmanaged, IComponent where T6 : unmanaged, IComponent where T7 : unmanaged, IComponent;
public delegate void ForEachWithEntity<T0>(Entity entity, ref T0 component0)
where T0 : unmanaged, IComponent;
public delegate void ForEachWithEntity<T0, T1>(Entity entity, ref T0 component0, ref T1 component1)
where T0 : unmanaged, IComponent where T1 : unmanaged, IComponent;
public delegate void ForEachWithEntity<T0, T1, T2>(Entity entity, ref T0 component0, ref T1 component1, ref T2 component2)
where T0 : unmanaged, IComponent where T1 : unmanaged, IComponent where T2 : unmanaged, IComponent;
public delegate void ForEachWithEntity<T0, T1, T2, T3>(Entity entity, ref T0 component0, ref T1 component1, ref T2 component2, ref T3 component3)
where T0 : unmanaged, IComponent where T1 : unmanaged, IComponent where T2 : unmanaged, IComponent where T3 : unmanaged, IComponent;
public delegate void ForEachWithEntity<T0, T1, T2, T3, T4>(Entity entity, ref T0 component0, ref T1 component1, ref T2 component2, ref T3 component3, ref T4 component4)
where T0 : unmanaged, IComponent where T1 : unmanaged, IComponent where T2 : unmanaged, IComponent where T3 : unmanaged, IComponent where T4 : unmanaged, IComponent;
public delegate void ForEachWithEntity<T0, T1, T2, T3, T4, T5>(Entity entity, ref T0 component0, ref T1 component1, ref T2 component2, ref T3 component3, ref T4 component4, ref T5 component5)
where T0 : unmanaged, IComponent where T1 : unmanaged, IComponent where T2 : unmanaged, IComponent where T3 : unmanaged, IComponent where T4 : unmanaged, IComponent where T5 : unmanaged, IComponent;
public delegate void ForEachWithEntity<T0, T1, T2, T3, T4, T5, T6>(Entity entity, ref T0 component0, ref T1 component1, ref T2 component2, ref T3 component3, ref T4 component4, ref T5 component5, ref T6 component6)
where T0 : unmanaged, IComponent where T1 : unmanaged, IComponent where T2 : unmanaged, IComponent where T3 : unmanaged, IComponent where T4 : unmanaged, IComponent where T5 : unmanaged, IComponent where T6 : unmanaged, IComponent;
public delegate void ForEachWithEntity<T0, T1, T2, T3, T4, T5, T6, T7>(Entity entity, ref T0 component0, ref T1 component1, ref T2 component2, ref T3 component3, ref T4 component4, ref T5 component5, ref T6 component6, ref T7 component7)
where T0 : unmanaged, IComponent where T1 : unmanaged, IComponent where T2 : unmanaged, IComponent where T3 : unmanaged, IComponent where T4 : unmanaged, IComponent where T5 : unmanaged, IComponent where T6 : unmanaged, IComponent where T7 : unmanaged, IComponent;

View File

@@ -0,0 +1,27 @@
<#@ template language="C#" #>
<#@ output extension="gen.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 = AppendParameters(i, "T{0}");
var compGenerics = AppendParameters(i, "ref T{0} component{0}");
var restrictions = AppendGenericRestrictions(i, "unmanaged, IComponent");
#>
public delegate void ForEach<<#= generics #>>(<#= compGenerics #>)
<#= restrictions #>;
<# } #>
<# for (var i = 1; i <= Amount; i++)
{
var generics = AppendParameters(i, "T{0}");
var compGenerics = AppendParameters(i, "ref T{0} component{0}");
var restrictions = AppendGenericRestrictions(i, "unmanaged, IComponent");
#>
public delegate void ForEachWithEntity<<#= generics #>>(Entity entity, <#= compGenerics #>)
<#= restrictions #>;
<# } #>

View File

@@ -0,0 +1,147 @@
<#@ 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(string.Format(template, i));
}
return sb.ToString();
}
string AppendGenerics(int amount)
{
return AppendGenerics(amount, "T{0}");
}
public StringBuilder AppendParameters(int amount, string template)
{
var sb = new StringBuilder();
for (var localIndex = 0; localIndex < amount; localIndex++)
{
sb.Append(string.Format(template, localIndex));
if (localIndex < amount - 1)
{
sb.Append(", ");
}
}
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 AppendGenericRestrictionsMultiline(int amount, string Ttemplate, string template, int indentation)
{
var sb = new StringBuilder();
var spaces = new string(' ', indentation * 4);
for (var localIndex = 0; localIndex < amount; localIndex++)
{
sb.Append($"{spaces}where {Ttemplate}{localIndex} : {template}");
if (localIndex < amount - 1)
{
sb.AppendLine();
}
}
return sb;
}
public StringBuilder AppendGenericRestrictionsMultiline(int amount, string template, int indentation)
{
return AppendGenericRestrictionsMultiline(amount, "T", template, indentation);
}
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;
}
#>

View File

@@ -0,0 +1,380 @@
using System.Runtime.CompilerServices;
namespace Ghost.Entities;
public ref partial struct QueryBuilder
{
/// <summary>
/// Adds the specified component type(s) to the 'All' filter of the query.
/// Targets entities that have all of the specified component types and those component(s) must be enabled.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public QueryBuilder WithAll<T0>()
where T0 : unmanaged, IComponent
{
_all.Add(ComponentTypeID<T0>.Value);
return this;
}
/// <summary>
/// Adds the specified component type(s) to the 'All' filter of the query and requires read-write access.
/// Targets entities that have all of the specified component types and those component(s) must be enabled.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public QueryBuilder WithAllRW<T0>()
where T0 : unmanaged, IComponent
{
_all.Add(ComponentTypeID<T0>.Value);
_rw.Add(ComponentTypeID<T0>.Value);
return this;
}
/// <summary>
/// Adds the specified component type(s) to the 'Any' filter of the query.
/// Targets entities that have at least one of the specified component types and those component(s) must be enabled.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public QueryBuilder WithAny<T0>()
where T0 : unmanaged, IComponent
{
_any.Add(ComponentTypeID<T0>.Value);
return this;
}
/// <summary>
/// Adds the specified component type(s) to the 'Absent' filter of the query.
/// Targets entities that do not have any of the specified component types.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public QueryBuilder WithAbsent<T0>()
where T0 : unmanaged, IComponent
{
_absent.Add(ComponentTypeID<T0>.Value);
return this;
}
/// <summary>
/// Adds the specified component type(s) to the 'None' filter of the query.
/// Targets entities that do not have any of the specified component types, or those component(s) are disabled.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public QueryBuilder WithNone<T0>()
where T0 : unmanaged, IComponent
{
_none.Add(ComponentTypeID<T0>.Value);
return this;
}
/// <summary>
/// Adds the specified component type(s) to the 'Disabled' filter of the query.
/// Targets entities that have all of the specified component types and those component(s) are disabled.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public QueryBuilder WithDisabled<T0>()
where T0 : unmanaged, IEnableableComponent
{
_disabled.Add(ComponentTypeID<T0>.Value);
return this;
}
/// <summary>
/// Adds the specified component type(s) to the 'Present' filter of the query.
/// Targets entities that have all of the specified component types, regardless of whether those component(s) are enabled or disabled.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public QueryBuilder WithPresent<T0>()
where T0 : unmanaged, IComponent
{
_present.Add(ComponentTypeID<T0>.Value);
return this;
}
/// <summary>
/// Adds the specified component type(s) to the 'Present' filter of the query and requires read-write access.
/// Targets entities that have all of the specified component types, regardless of whether those component(s) are enabled or disabled.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public QueryBuilder WithPresentRW<T0>()
where T0 : unmanaged, IComponent
{
_present.Add(ComponentTypeID<T0>.Value);
_rw.Add(ComponentTypeID<T0>.Value);
return this;
}
/// <summary>
/// Adds the specified component type(s) to the 'All' filter of the query.
/// Targets entities that have all of the specified component types and those component(s) must be enabled.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public QueryBuilder WithAll<T0, T1>()
where T0 : unmanaged, IComponent
where T1 : unmanaged, IComponent
{
_all.Add(ComponentTypeID<T0>.Value);
_all.Add(ComponentTypeID<T1>.Value);
return this;
}
/// <summary>
/// Adds the specified component type(s) to the 'All' filter of the query and requires read-write access.
/// Targets entities that have all of the specified component types and those component(s) must be enabled.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public QueryBuilder WithAllRW<T0, T1>()
where T0 : unmanaged, IComponent
where T1 : unmanaged, IComponent
{
_all.Add(ComponentTypeID<T0>.Value);
_rw.Add(ComponentTypeID<T0>.Value);
_all.Add(ComponentTypeID<T1>.Value);
_rw.Add(ComponentTypeID<T1>.Value);
return this;
}
/// <summary>
/// Adds the specified component type(s) to the 'Any' filter of the query.
/// Targets entities that have at least one of the specified component types and those component(s) must be enabled.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public QueryBuilder WithAny<T0, T1>()
where T0 : unmanaged, IComponent
where T1 : unmanaged, IComponent
{
_any.Add(ComponentTypeID<T0>.Value);
_any.Add(ComponentTypeID<T1>.Value);
return this;
}
/// <summary>
/// Adds the specified component type(s) to the 'Absent' filter of the query.
/// Targets entities that do not have any of the specified component types.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public QueryBuilder WithAbsent<T0, T1>()
where T0 : unmanaged, IComponent
where T1 : unmanaged, IComponent
{
_absent.Add(ComponentTypeID<T0>.Value);
_absent.Add(ComponentTypeID<T1>.Value);
return this;
}
/// <summary>
/// Adds the specified component type(s) to the 'None' filter of the query.
/// Targets entities that do not have any of the specified component types, or those component(s) are disabled.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public QueryBuilder WithNone<T0, T1>()
where T0 : unmanaged, IComponent
where T1 : unmanaged, IComponent
{
_none.Add(ComponentTypeID<T0>.Value);
_none.Add(ComponentTypeID<T1>.Value);
return this;
}
/// <summary>
/// Adds the specified component type(s) to the 'Disabled' filter of the query.
/// Targets entities that have all of the specified component types and those component(s) are disabled.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public QueryBuilder WithDisabled<T0, T1>()
where T0 : unmanaged, IEnableableComponent
where T1 : unmanaged, IEnableableComponent
{
_disabled.Add(ComponentTypeID<T0>.Value);
_disabled.Add(ComponentTypeID<T1>.Value);
return this;
}
/// <summary>
/// Adds the specified component type(s) to the 'Present' filter of the query.
/// Targets entities that have all of the specified component types, regardless of whether those component(s) are enabled or disabled.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public QueryBuilder WithPresent<T0, T1>()
where T0 : unmanaged, IComponent
where T1 : unmanaged, IComponent
{
_present.Add(ComponentTypeID<T0>.Value);
_present.Add(ComponentTypeID<T1>.Value);
return this;
}
/// <summary>
/// Adds the specified component type(s) to the 'Present' filter of the query and requires read-write access.
/// Targets entities that have all of the specified component types, regardless of whether those component(s) are enabled or disabled.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public QueryBuilder WithPresentRW<T0, T1>()
where T0 : unmanaged, IComponent
where T1 : unmanaged, IComponent
{
_present.Add(ComponentTypeID<T0>.Value);
_rw.Add(ComponentTypeID<T0>.Value);
_present.Add(ComponentTypeID<T1>.Value);
_rw.Add(ComponentTypeID<T1>.Value);
return this;
}
/// <summary>
/// Adds the specified component type(s) to the 'All' filter of the query.
/// Targets entities that have all of the specified component types and those component(s) must be enabled.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public QueryBuilder WithAll<T0, T1, T2>()
where T0 : unmanaged, IComponent
where T1 : unmanaged, IComponent
where T2 : unmanaged, IComponent
{
_all.Add(ComponentTypeID<T0>.Value);
_all.Add(ComponentTypeID<T1>.Value);
_all.Add(ComponentTypeID<T2>.Value);
return this;
}
/// <summary>
/// Adds the specified component type(s) to the 'All' filter of the query and requires read-write access.
/// Targets entities that have all of the specified component types and those component(s) must be enabled.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public QueryBuilder WithAllRW<T0, T1, T2>()
where T0 : unmanaged, IComponent
where T1 : unmanaged, IComponent
where T2 : unmanaged, IComponent
{
_all.Add(ComponentTypeID<T0>.Value);
_rw.Add(ComponentTypeID<T0>.Value);
_all.Add(ComponentTypeID<T1>.Value);
_rw.Add(ComponentTypeID<T1>.Value);
_all.Add(ComponentTypeID<T2>.Value);
_rw.Add(ComponentTypeID<T2>.Value);
return this;
}
/// <summary>
/// Adds the specified component type(s) to the 'Any' filter of the query.
/// Targets entities that have at least one of the specified component types and those component(s) must be enabled.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public QueryBuilder WithAny<T0, T1, T2>()
where T0 : unmanaged, IComponent
where T1 : unmanaged, IComponent
where T2 : unmanaged, IComponent
{
_any.Add(ComponentTypeID<T0>.Value);
_any.Add(ComponentTypeID<T1>.Value);
_any.Add(ComponentTypeID<T2>.Value);
return this;
}
/// <summary>
/// Adds the specified component type(s) to the 'Absent' filter of the query.
/// Targets entities that do not have any of the specified component types.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public QueryBuilder WithAbsent<T0, T1, T2>()
where T0 : unmanaged, IComponent
where T1 : unmanaged, IComponent
where T2 : unmanaged, IComponent
{
_absent.Add(ComponentTypeID<T0>.Value);
_absent.Add(ComponentTypeID<T1>.Value);
_absent.Add(ComponentTypeID<T2>.Value);
return this;
}
/// <summary>
/// Adds the specified component type(s) to the 'None' filter of the query.
/// Targets entities that do not have any of the specified component types, or those component(s) are disabled.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public QueryBuilder WithNone<T0, T1, T2>()
where T0 : unmanaged, IComponent
where T1 : unmanaged, IComponent
where T2 : unmanaged, IComponent
{
_none.Add(ComponentTypeID<T0>.Value);
_none.Add(ComponentTypeID<T1>.Value);
_none.Add(ComponentTypeID<T2>.Value);
return this;
}
/// <summary>
/// Adds the specified component type(s) to the 'Disabled' filter of the query.
/// Targets entities that have all of the specified component types and those component(s) are disabled.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public QueryBuilder WithDisabled<T0, T1, T2>()
where T0 : unmanaged, IEnableableComponent
where T1 : unmanaged, IEnableableComponent
where T2 : unmanaged, IEnableableComponent
{
_disabled.Add(ComponentTypeID<T0>.Value);
_disabled.Add(ComponentTypeID<T1>.Value);
_disabled.Add(ComponentTypeID<T2>.Value);
return this;
}
/// <summary>
/// Adds the specified component type(s) to the 'Present' filter of the query.
/// Targets entities that have all of the specified component types, regardless of whether those component(s) are enabled or disabled.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public QueryBuilder WithPresent<T0, T1, T2>()
where T0 : unmanaged, IComponent
where T1 : unmanaged, IComponent
where T2 : unmanaged, IComponent
{
_present.Add(ComponentTypeID<T0>.Value);
_present.Add(ComponentTypeID<T1>.Value);
_present.Add(ComponentTypeID<T2>.Value);
return this;
}
/// <summary>
/// Adds the specified component type(s) to the 'Present' filter of the query and requires read-write access.
/// Targets entities that have all of the specified component types, regardless of whether those component(s) are enabled or disabled.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public QueryBuilder WithPresentRW<T0, T1, T2>()
where T0 : unmanaged, IComponent
where T1 : unmanaged, IComponent
where T2 : unmanaged, IComponent
{
_present.Add(ComponentTypeID<T0>.Value);
_rw.Add(ComponentTypeID<T0>.Value);
_present.Add(ComponentTypeID<T1>.Value);
_rw.Add(ComponentTypeID<T1>.Value);
_present.Add(ComponentTypeID<T2>.Value);
_rw.Add(ComponentTypeID<T2>.Value);
return this;
}
}

View File

@@ -0,0 +1,142 @@
<#@ template language="C#" #>
<#@ output extension="gen.cs" #>
<#@ assembly name="System.Core" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text" #>
<#@ include file="Helpers.ttinclude" #>
using System.Runtime.CompilerServices;
namespace Ghost.Entities;
public ref partial struct QueryBuilder
{
<# for (var i = 1; i <= 3; i++)
{
var generics = AppendGenerics(i);
var restrictions = AppendGenericRestrictionsMultiline(i, "unmanaged, IComponent", 2);
var enableRestrictions = AppendGenericRestrictionsMultiline(i, "unmanaged, IEnableableComponent", 2);
#>
/// <summary>
/// Adds the specified component type(s) to the 'All' filter of the query.
/// Targets entities that have all of the specified component types and those component(s) must be enabled.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public QueryBuilder WithAll<<#= generics #>>()
<#= restrictions #>
{
<# for (var j = 0; j < i; j++) { #>
_all.Add(ComponentTypeID<T<#= j #>>.Value);
<# } #>
return this;
}
/// <summary>
/// Adds the specified component type(s) to the 'All' filter of the query and requires read-write access.
/// Targets entities that have all of the specified component types and those component(s) must be enabled.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public QueryBuilder WithAllRW<<#= generics #>>()
<#= restrictions #>
{
<# for (var j = 0; j < i; j++) { #>
_all.Add(ComponentTypeID<T<#= j #>>.Value);
_rw.Add(ComponentTypeID<T<#= j #>>.Value);
<# } #>
return this;
}
/// <summary>
/// Adds the specified component type(s) to the 'Any' filter of the query.
/// Targets entities that have at least one of the specified component types and those component(s) must be enabled.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public QueryBuilder WithAny<<#= generics #>>()
<#= restrictions #>
{
<# for (var j = 0; j < i; j++) { #>
_any.Add(ComponentTypeID<T<#= j #>>.Value);
<# } #>
return this;
}
/// <summary>
/// Adds the specified component type(s) to the 'Absent' filter of the query.
/// Targets entities that do not have any of the specified component types.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public QueryBuilder WithAbsent<<#= generics #>>()
<#= restrictions #>
{
<# for (var j = 0; j < i; j++) { #>
_absent.Add(ComponentTypeID<T<#= j #>>.Value);
<# } #>
return this;
}
/// <summary>
/// Adds the specified component type(s) to the 'None' filter of the query.
/// Targets entities that do not have any of the specified component types, or those component(s) are disabled.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public QueryBuilder WithNone<<#= generics #>>()
<#= restrictions #>
{
<# for (var j = 0; j < i; j++) { #>
_none.Add(ComponentTypeID<T<#= j #>>.Value);
<# } #>
return this;
}
/// <summary>
/// Adds the specified component type(s) to the 'Disabled' filter of the query.
/// Targets entities that have all of the specified component types and those component(s) are disabled.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public QueryBuilder WithDisabled<<#= generics #>>()
<#= enableRestrictions #>
{
<# for (var j = 0; j < i; j++) { #>
_disabled.Add(ComponentTypeID<T<#= j #>>.Value);
<# } #>
return this;
}
/// <summary>
/// Adds the specified component type(s) to the 'Present' filter of the query.
/// Targets entities that have all of the specified component types, regardless of whether those component(s) are enabled or disabled.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public QueryBuilder WithPresent<<#= generics #>>()
<#= restrictions #>
{
<# for (var j = 0; j < i; j++) { #>
_present.Add(ComponentTypeID<T<#= j #>>.Value);
<# } #>
return this;
}
/// <summary>
/// Adds the specified component type(s) to the 'Present' filter of the query and requires read-write access.
/// Targets entities that have all of the specified component types, regardless of whether those component(s) are enabled or disabled.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public QueryBuilder WithPresentRW<<#= generics #>>()
<#= restrictions #>
{
<# for (var j = 0; j < i; j++) { #>
_present.Add(ComponentTypeID<T<#= j #>>.Value);
_rw.Add(ComponentTypeID<T<#= j #>>.Value);
<# } #>
return this;
}
<# } #>
}

View File

@@ -0,0 +1,240 @@
using Ghost.Core;
using Misaki.HighPerformance.Jobs;
using System.Runtime.CompilerServices;
namespace Ghost.Entities;
public partial class World
{
private static readonly List<World?> s_worlds = new(4);
private static readonly Queue<Identifier<World>> s_freeWorldSlots = new();
internal static Identifier<Archetype> EmptyArchetypeID => new(0);
public static int WorldCount => s_worlds.Count - s_freeWorldSlots.Count;
public static World Create(JobScheduler? jobScheduler = null, int entityCapacity = 16)
{
lock (s_worlds)
{
if (s_freeWorldSlots.TryDequeue(out var index))
{
s_worlds[index.Value] = new World(index, entityCapacity, jobScheduler);
}
else
{
index = new Identifier<World>(s_worlds.Count);
s_worlds.Add(new World(index, entityCapacity, jobScheduler));
}
return s_worlds[index.Value]!;
}
}
public static void Destroy(Identifier<World> id)
{
lock (s_worlds)
{
if (id.Value < 0 || id.Value >= s_worlds.Count)
{
return;
}
var world = s_worlds[id.Value];
world?.Dispose();
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static World GetWorldUncheck(Identifier<World> id)
{
#if DEBUG || GHOST_EDITOR
if (id.Value < 0 || id.Value >= s_worlds.Count)
{
throw new ArgumentOutOfRangeException(nameof(id), "World ID is out of range.");
}
var world = s_worlds[id.Value];
return world is null ? throw new InvalidOperationException("World not found.") : world;
#else
return s_worlds[id.Value]!;
#endif
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static World? GetWorld(Identifier<World> id)
{
if (id.Value < 0 || id.Value >= s_worlds.Count)
{
return null;
}
return s_worlds[id.Value];
}
}
public partial class World : IDisposable, IEquatable<World>
{
private readonly Identifier<World> _id;
private readonly JobScheduler? _jobScheduler;
private readonly EntityManager _entityManager;
private readonly EntityCommandBuffer _entityCommandBuffer;
private readonly EntityCommandBuffer[]? _threadLocalECBs;
private readonly ComponentManager _componentManager;
private readonly SystemManager _systemManager;
private int _version;
private bool _disposed = false;
/// <summary>
/// Gets the unique identifier of this world.
/// </summary>
public Identifier<World> ID => _id;
/// <summary>
/// Gets the job scheduler associated with this world.
/// </summary>
public JobScheduler? JobScheduler => _jobScheduler;
/// <summary>
/// Gets the publicntity manager for this world.
/// </summary>
public EntityManager EntityManager => _entityManager;
/// <summary>
/// Gets the component manager for this world.
/// </summary>
public ComponentManager ComponentManager => _componentManager;
/// <summary>
/// Gets the system manager for this world.
/// </summary>
public SystemManager SystemManager => _systemManager;
/// <summary>
/// Gets the current version number of the world.
/// </summary>
public int Version => Interlocked.CompareExchange(ref _version, 0, 0);
/// <summary>
/// Gets the main entity command buffer for this world.
/// </summary>
/// <remarks>
/// Use <see cref="GetThreadLocalEntityCommandBuffer(int)"/> to get thread-local command buffers for multi-threaded jobs.
/// </remarks>
public EntityCommandBuffer EntityCommandBuffer => _entityCommandBuffer;
private World(Identifier<World> id, int entityCapacity, JobScheduler? jobScheduler)
{
_id = id;
_jobScheduler = jobScheduler;
_entityManager = new EntityManager(this, entityCapacity);
_entityCommandBuffer = new EntityCommandBuffer(_entityManager);
_componentManager = new ComponentManager(this);
_systemManager = new SystemManager(this);
if (jobScheduler != null)
{
_threadLocalECBs = new EntityCommandBuffer[jobScheduler.WorkerCount];
for (var i = 0; i < jobScheduler.WorkerCount; i++)
{
_threadLocalECBs[i] = new EntityCommandBuffer(_entityManager);
}
}
}
~World()
{
Dispose();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void PlaybackEntityCommandBuffers()
{
_entityCommandBuffer.Playback();
if (_threadLocalECBs != null)
{
for (var i = 0; i < _threadLocalECBs.Length; i++)
{
_threadLocalECBs[i].Playback();
}
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal int AdvanceVersion()
{
return Interlocked.Increment(ref _version);
}
/// <summary>
/// Gets the thread-local entity command buffer for the specified thread index.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public EntityCommandBuffer GetThreadLocalEntityCommandBuffer(int threadIndex)
{
if (_threadLocalECBs == null)
{
throw new InvalidOperationException("This world does not have a JobScheduler associated with it.");
}
return _threadLocalECBs[threadIndex];
}
public bool Equals(World? other)
{
return other is not null && _id == other._id;
}
public override int GetHashCode()
{
return _id.GetHashCode();
}
public override bool Equals(object? obj)
{
return obj is World other && Equals(other);
}
public static bool operator ==(World? left, World? right)
{
return left?.Equals(right) ?? right is null;
}
public static bool operator !=(World? left, World? right)
{
return !(left == right);
}
public void Dispose()
{
if (_disposed)
{
return;
}
_entityManager.Dispose();
_entityCommandBuffer.Dispose();
if (_threadLocalECBs != null)
{
foreach (var v in _threadLocalECBs)
{
v.Dispose();
}
}
s_freeWorldSlots.Enqueue(_id);
s_worlds[_id] = null;
_disposed = true;
GC.SuppressFinalize(this);
}
}