Removed Ghost.ArcEntities project, it's replaced by Ghost.Entities

Added Playback to EntityCommandBuffer
Added JobSchedular to world
Added ISystem and SystemGroup
Updated packages
This commit is contained in:
2025-12-08 20:44:56 +09:00
parent f44208b502
commit 5e276b289d
30 changed files with 2974 additions and 1597 deletions

View File

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

View File

@@ -1,3 +0,0 @@
global using EntityID = System.Int32;
global using GenerationID = System.Int32;

View File

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

View File

@@ -1,78 +0,0 @@
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Ghost.ArcEntities;
[StructLayout(LayoutKind.Sequential, Size = 8)]
public readonly struct Entity : IEquatable<Entity>, IComparable<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, GenerationID.MaxValue);
}
internal Entity(EntityID id, GenerationID generation)
{
_id = id;
_generation = generation;
}
public bool Equals(Entity other)
{
return _id == other._id && _generation == other._generation;
}
public int CompareTo(Entity other)
{
return _id.CompareTo(other._id);
}
public override bool Equals(object? obj)
{
return obj is Entity other && Equals(other);
}
public override int GetHashCode()
{
return _id ^ _generation << 16;
}
public static bool operator ==(Entity left, Entity right)
{
return left.Equals(right);
}
public static bool operator !=(Entity left, Entity right)
{
return !(left == right);
}
public override string ToString()
{
return $"Entity {{ Index: {ID}, Generation: {Generation} }}";
}
}

View File

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

View File

@@ -1,266 +0,0 @@
using Ghost.Core;
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections;
using Misaki.HighPerformance.LowLevel.Utilities;
using System.Diagnostics;
namespace Ghost.ArcEntities;
public unsafe class EntityManager : IDisposable
{
private struct EntityLocation
{
public Identifier<Archetype> archetypeID;
public int chunkIndex;
public int rowIndex;
}
private World _world;
private UnsafeSlotMap<EntityLocation> _entityLocations;
internal EntityManager(World world, int initialCapacity)
{
_world = world;
_entityLocations = new UnsafeSlotMap<EntityLocation>(initialCapacity, Allocator.Persistent);
}
internal ResultStatus 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 ResultStatus.NotFound;
}
location.archetypeID = newArchetypeID;
location.chunkIndex = newChunkIndex;
location.rowIndex = newRowIndex;
return ResultStatus.Success;
}
public Entity CreateEntity(params ReadOnlySpan<Identifier<IComponent>> componentTypeIDs)
{
var signatureHash = ComponentRegister.GetHashCode(componentTypeIDs);
var arcID = _world.GetArchetypeIDBySignatureHash(signatureHash);
if (arcID.IsNotValid)
{
arcID = _world.CreateArchetype(componentTypeIDs, signatureHash);
}
ref var archetype = ref _world.GetArchetypeReference(arcID);
archetype.AllocateEntity(out var chunkIndex, out var rowIndex);
var id = _entityLocations.Add(new EntityLocation
{
archetypeID = arcID,
chunkIndex = chunkIndex,
rowIndex = rowIndex
}, out var generation);
var entity = new Entity(id, generation);
archetype.SetEntity(chunkIndex, rowIndex, entity);
return entity;
}
public Entity CreateEntity()
{
// Put into empty archetype
ref var emptyArchetype = ref _world.GetArchetypeReference(World.EmptyArchetypeID);
emptyArchetype.AllocateEntity(out var chunkIndex, out var rowIndex);
var id = _entityLocations.Add(new EntityLocation
{
archetypeID = World.EmptyArchetypeID,
chunkIndex = chunkIndex,
rowIndex = rowIndex
}, out var generation);
var entity = new Entity(id, generation);
emptyArchetype.SetEntity(chunkIndex, rowIndex, entity);
return entity;
}
public ResultStatus DestoryEntity(Entity entity)
{
if (!_entityLocations.TryGetElementAt(entity.ID, entity.Generation, out var location))
{
return ResultStatus.NotFound;
}
ref var archetype = ref _world.GetArchetypeReference(location.archetypeID);
var r = archetype.RemoveEntity(location.chunkIndex, location.rowIndex);
if (r != ResultStatus.Success)
{
return r;
}
if (!_entityLocations.Remove(entity.ID, entity.Generation))
{
return ResultStatus.NotFound;
}
return ResultStatus.Success;
}
private static void CopyData(ref Archetype oldArch, int oldChunk, int oldRow,
ref Archetype newArch, int newChunk, int newRow)
{
// Iterate every component type in the OLD archetype
for (int i = 0; i < oldArch.Layouts.Count; i++)
{
var layout = oldArch.Layouts[i];
var src = oldArch.Chunks[oldChunk].GetUnsafePtr() + layout.offset + (layout.size * oldRow);
var newOffset = newArch.GetOffset(layout.componentID); // O(1) Lookup
var dst = oldArch.Chunks[oldChunk].GetUnsafePtr() + newOffset + (layout.size * newRow);
MemoryUtility.MemCpy(src, dst, (nuint)layout.size);
}
}
public ResultStatus AddComponent(Entity entity, Identifier<IComponent> componentID, void* component)
{
// Find current location
ref var location = ref _entityLocations.GetElementReferenceAt(entity.ID, entity.Generation, out var exist);
if (!exist)
{
return ResultStatus.NotFound;
}
// Build new archetype signature
ref var oldArchetype = ref _world.GetArchetypeReference(location.archetypeID);
var oldSignature = oldArchetype.Signature;
// TODO: Check edge cache first.
var newArcID = oldArchetype.GetEdgeAdd(componentID);
if (newArcID.IsNotValid)
{
var largestComponentID = Math.Max(oldSignature.Count, componentID);
var length = UnsafeBitSet.RequiredLength(largestComponentID + 1);
Span<uint> bits = stackalloc uint[length];
bits.Clear();
var newSignature = new SpanBitSet(bits);
var iterator = 0;
var compCount = 0;
while (true)
{
int bit = oldSignature.NextSetBit(iterator);
if (bit == -1)
{
break;
}
newSignature.SetBit(bit);
iterator = bit + 1;
compCount++;
}
compCount++;
newSignature.SetBit(componentID);
// Find or create new archetype
var newSignatureHash = newSignature.GetHashCode();
newArcID = _world.GetArchetypeIDBySignatureHash(newSignatureHash);
if (newArcID.IsNotValid)
{
// Create new archetype
Span<Identifier<IComponent>> componentTypeIDs = stackalloc Identifier<IComponent>[compCount];
componentTypeIDs[0] = componentID;
iterator = 0;
while (true)
{
int bit = oldSignature.NextSetBit(iterator);
if (bit == -1)
{
break;
}
componentTypeIDs[--compCount] = bit;
iterator = bit + 1;
}
_world.CreateArchetype(componentTypeIDs, newSignatureHash);
}
oldArchetype.AddEdgeAdd(componentID, newArcID);
}
// Move entity data
ref var newArchetype = ref _world.GetArchetypeReference(newArcID);
newArchetype.AllocateEntity(out var newChunkIndex, out var newRowIndex);
CopyData(ref oldArchetype, location.chunkIndex, location.rowIndex,
ref newArchetype, newChunkIndex, newRowIndex);
newArchetype.SetEntity(newChunkIndex, newRowIndex, entity);
newArchetype.SetComponentData(newChunkIndex, newRowIndex, componentID, component);
var r = oldArchetype.RemoveEntity(location.chunkIndex, location.rowIndex);
Debug.Assert(r == ResultStatus.Success); // We assert it because the entity should exist if the whole system is consistent.
// if (r != ResultStatus.Success)
// {
// return r;
// }
// Update location
location.archetypeID = newArcID;
location.chunkIndex = newChunkIndex;
location.rowIndex = newRowIndex;
return ResultStatus.Success;
}
public ResultStatus AddComponent<T>(Entity entity, ref T component)
where T : unmanaged, IComponent
{
return AddComponent(entity, ComponentTypeID<T>.value, UnsafeUtility.AddressOf(ref component));
}
public ResultStatus SetComponentData(Entity entity, Identifier<IComponent> componentID, void* pComponent)
{
if (!_entityLocations.TryGetElementAt(entity.ID, entity.Generation, out var location))
{
return ResultStatus.NotFound;
}
ref var archetype = ref _world.GetArchetypeReference(location.archetypeID);
archetype.SetComponentData(location.chunkIndex, location.rowIndex, componentID, pComponent);
return ResultStatus.Success;
}
public ResultStatus SetComponentData<T>(Entity entity, ref T component)
where T : unmanaged, IComponent
{
return SetComponentData(entity, ComponentTypeID<T>.value, UnsafeUtility.AddressOf(ref component));
}
public bool HasComponent(Entity entity, Identifier<IComponent> componentID)
{
if (!_entityLocations.TryGetElementAt(entity.ID, entity.Generation, out var location))
{
return false;
}
ref var archetype = ref _world.GetArchetypeReference(location.archetypeID);
return archetype.HasComponent(componentID);
}
public bool HasComponent<T>(Entity entity)
where T : unmanaged, IComponent
{
return HasComponent(entity, ComponentTypeID<T>.value);
}
public void Dispose()
{
_entityLocations.Dispose();
}
}

View File

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

View File

@@ -1,24 +0,0 @@
<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>True</IsAotCompatible>
<IsTrimmable>True</IsTrimmable>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<IsAotCompatible>True</IsAotCompatible>
<IsTrimmable>True</IsTrimmable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Ghost.Core\Ghost.Core.csproj" />
</ItemGroup>
</Project>

View File

@@ -1,5 +0,0 @@
namespace Ghost.ArcEntities;
public static class Utility
{
}

View File

@@ -1,155 +0,0 @@
using Ghost.Core;
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections;
using System.Runtime.CompilerServices;
namespace Ghost.ArcEntities;
public partial class World
{
private static List<World?> s_worlds = new(4);
private static Queue<Identifier<World>> s_freeWorldSlots = new();
internal static Identifier<Archetype> EmptyArchetypeID => new Identifier<Archetype>(0);
public static int WorldCount => s_worlds.Count - s_freeWorldSlots.Count;
public static World Create(int entityCapacity = 16)
{
lock (s_worlds)
{
if (s_freeWorldSlots.TryDequeue(out var index))
{
s_worlds[index.value] = new World(index, entityCapacity);
}
else
{
index = new Identifier<World>(s_worlds.Count);
s_worlds.Add(new World(index, entityCapacity));
}
return s_worlds[index.value]!;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Result<World, ResultStatus> GetWorld(Identifier<World> id)
{
if (id.value < 0 || id.value >= s_worlds.Count)
{
return Result.Create(default(World)!, ResultStatus.NotFound);
}
var world = s_worlds[id.value];
if (world is null)
{
return Result.Create(default(World)!, ResultStatus.NotFound);
}
return Result.Create(world, ResultStatus.Success);
}
}
public partial class World : IIdentifierType, IDisposable, IEquatable<World>
{
private readonly Identifier<World> _id;
private UnsafeList<Archetype> _archetypes;
private UnsafeHashMap<int, Identifier<Archetype>> _archetypeLookup; // Signature Hash to Archetype ID
private EntityManager _entityManager;
private EntityCommandBuffer _entityCommandBuffer;
private bool _disposed = false;
public Identifier<World> ID => _id;
public EntityManager EntityManager => _entityManager;
public EntityCommandBuffer EntityCommandBuffer => _entityCommandBuffer;
private World(Identifier<World> id, int entityCapacity)
{
_id = id;
_archetypes = new UnsafeList<Archetype>(16, Allocator.Persistent);
_archetypeLookup = new UnsafeHashMap<int, Identifier<Archetype>>(16, Allocator.Persistent);
_entityManager = new EntityManager(this, entityCapacity);
_entityCommandBuffer = new EntityCommandBuffer(_entityManager);
// Create the empty archetype
CreateArchetype(ReadOnlySpan<Identifier<IComponent>>.Empty, 0);
}
~World()
{
Dispose();
}
internal Identifier<Archetype> CreateArchetype(ReadOnlySpan<Identifier<IComponent>> componentTypeIDs, int signatureHash)
{
var arcID = new Identifier<Archetype>(_archetypes.Count);
_archetypes.Add(new Archetype(arcID, _id, componentTypeIDs));
_archetypeLookup.Add(signatureHash, arcID);
return arcID;
}
internal ref Archetype GetArchetypeReference(Identifier<Archetype> id)
{
return ref _archetypes[id.value];
}
internal Identifier<Archetype> GetArchetypeIDBySignatureHash(int signatureHash)
{
if (_archetypeLookup.TryGetValue(signatureHash, out var arcID))
{
return arcID;
}
return Identifier<Archetype>.Invalid;
}
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();
_archetypes.Dispose();
_archetypeLookup.Dispose();
s_freeWorldSlots.Enqueue(_id);
_disposed = true;
GC.SuppressFinalize(this);
}
}

View File

@@ -21,8 +21,8 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Misaki.HighPerformance" Version="1.0.1" /> <PackageReference Include="Misaki.HighPerformance" Version="1.0.1" />
<PackageReference Include="Misaki.HighPerformance.Jobs" Version="1.1.0" /> <PackageReference Include="Misaki.HighPerformance.Jobs" Version="1.2.0" />
<PackageReference Include="Misaki.HighPerformance.LowLevel" Version="1.2.8" /> <PackageReference Include="Misaki.HighPerformance.LowLevel" Version="1.3.0" />
<PackageReference Include="Misaki.HighPerformance.Mathematics" Version="1.2.6" /> <PackageReference Include="Misaki.HighPerformance.Mathematics" Version="1.2.6" />
<PackageReference Include="System.IO.Hashing" Version="10.0.0" /> <PackageReference Include="System.IO.Hashing" Version="10.0.0" />
<PackageReference Include="TerraFX.Interop.Windows" Version="10.0.26100.5" /> <PackageReference Include="TerraFX.Interop.Windows" Version="10.0.26100.5" />

View File

@@ -1,5 +1,4 @@
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using TerraFX.Interop.DirectX;
namespace Ghost.Core; namespace Ghost.Core;

View File

@@ -15,49 +15,69 @@ internal struct TestEntityQueryJob : IJobEntityParallel<Transform>
public partial class ArcEntityTest : ITest public partial class ArcEntityTest : ITest
{ {
private World _world = null!;
private JobScheduler _jobScheduler = null!; private JobScheduler _jobScheduler = null!;
private World _world = null!;
public void Setup() public void Setup()
{ {
_world = World.Create();
_jobScheduler = new JobScheduler(4); _jobScheduler = new JobScheduler(4);
_world = World.Create(_jobScheduler);
} }
public void Run() public void Run()
{ {
var entity1 = _world.EntityManager.CreateEntity(ComponentTypeID<Transform>.value); var entity1 = _world.EntityManager.CreateEntity(ComponentTypeID<Transform>.value);
_world.EntityManager.AddComponent(entity1, new Mesh { index = 1 }); _world.EntityCommandBuffer.AddComponent(entity1, new Mesh { index = 1 });
var queryID = new QueryBuilder().WithAll<Transform>().Build(_world); // var entity2 = _world.EntityManager.CreateEntity(ComponentTypeID<Transform>.value);
ref var query = ref _world.GetEntityQueryReference(queryID); // _world.EntityManager.SetComponentData(entity2, new Transform { position = new float3(1, 2, 3) });
var testJob = new TestEntityQueryJob(); Console.WriteLine($"Entity {entity1} hash Mesh: {_world.EntityManager.HasComponent<Mesh>(entity1)}");
var handle = query.ScheduleEntityParallel<TestEntityQueryJob, Transform>(_jobScheduler, testJob, Allocator.Temp, 64, JobHandle.Invalid);
_jobScheduler.WaitComplete(handle);
query.ForEach<Transform>((e, ref t) => _world.EntityCommandBuffer.Playback();
{ Console.WriteLine($"Entity {entity1} hash Mesh: {_world.EntityManager.HasComponent<Mesh>(entity1)}");
Console.WriteLine($"Entity {e} Has Position: {t.position}");
});
//foreach (var chunk in query.GetChunkIterator()) _world.EntityCommandBuffer.RemoveComponent<Mesh>(entity1);
//{ Console.WriteLine($"Entity {entity1} hash Mesh: {_world.EntityManager.HasComponent<Mesh>(entity1)}");
// var transforms = chunk.GetComponentData<Transform>();
// var entities = chunk.GetEntities();
// var bits = chunk.GetEnableBits<Transform>();
// var it = bits.GetIterator(); _world.EntityCommandBuffer.Playback();
// while (it.Next(out var index) && index < chunk.Count) Console.WriteLine($"Entity {entity1} hash Mesh: {_world.EntityManager.HasComponent<Mesh>(entity1)}");
// {
// Console.WriteLine($"Entity {entities[index]} Updated Position: {transforms[index].position}"); // var queryID = new QueryBuilder().WithAll<Transform>().Build(_world);
// } // ref var query = ref _world.GetEntityQueryReference(queryID);
//}
// var testJob = new TestEntityQueryJob();
// var handle = query.ScheduleEntityParallel<TestEntityQueryJob, Transform>(_jobScheduler, testJob, Allocator.Temp, 64, JobHandle.Invalid);
// _jobScheduler.WaitComplete(handle);
//
// query.ForEach<Transform>((e, ref t) =>
// {
// Console.WriteLine($"Entity {e} Has Position: {t.position}");
// });
//
// foreach (ref var transform in query.GetComponentIterator<Transform>())
// {
// Console.WriteLine($"Entity Updated Position: {transform.position}");
// }
//
// foreach (var chunk in query.GetChunkIterator())
// {
// var transforms = chunk.GetComponentData<Transform>();
// var entities = chunk.GetEntities();
// var bits = chunk.GetEnableBits<Transform>();
//
// var it = bits.GetIterator();
// while (it.Next(out var index) && index < chunk.Count)
// {
// Console.WriteLine($"Entity {entities[index]} Updated Position: {transforms[index].position}");
// }
// }
} }
public void Cleanup() public void Cleanup()
{ {
_world.Dispose(); _world.Dispose();
_jobScheduler.Dispose();
} }
} }

View File

@@ -259,7 +259,7 @@ internal unsafe struct Archetype : IIdentifierType, IDisposable
var chunkBase = chunk.GetUnsafePtr(); var chunkBase = chunk.GetUnsafePtr();
var dst = chunkBase + _entityIdsOffset + (sizeof(Entity) * rowIndex); var dst = chunkBase + _entityIdsOffset + (sizeof(Entity) * rowIndex);
MemoryUtility.MemCpy(&entity, dst, (nuint)sizeof(Entity)); MemoryUtility.MemCpy(dst, &entity, (nuint)sizeof(Entity));
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -278,18 +278,18 @@ internal unsafe struct Archetype : IIdentifierType, IDisposable
var size = ComponentRegister.GetComponentInfo(componentID).size; var size = ComponentRegister.GetComponentInfo(componentID).size;
var dst = chunkBase + offset + (size * rowIndex); var dst = chunkBase + offset + (size * rowIndex);
MemoryUtility.MemCpy(pComponent, dst, (nuint)size); MemoryUtility.MemCpy(dst, pComponent, (nuint)size);
return ResultStatus.Success; return ResultStatus.Success;
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly ResultStatus GetComponentDataPtr(int chunkIndex, int rowIndex, Identifier<IComponent> componentID, void** ppv) public readonly void* GetComponentData(int chunkIndex, int rowIndex, Identifier<IComponent> componentID)
{ {
var r = GetLayout(componentID); var r = GetLayout(componentID);
if (r.Status != ResultStatus.Success) if (r.Status != ResultStatus.Success)
{ {
return r.Status; return null;
} }
var offset = r.Value.offset; var offset = r.Value.offset;
@@ -297,9 +297,7 @@ internal unsafe struct Archetype : IIdentifierType, IDisposable
var chunkBase = chunk.GetUnsafePtr(); var chunkBase = chunk.GetUnsafePtr();
var size = ComponentRegister.GetComponentInfo(componentID).size; var size = ComponentRegister.GetComponentInfo(componentID).size;
*ppv = chunkBase + offset + (size * rowIndex); return chunkBase + offset + (size * rowIndex);
return ResultStatus.Success;
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -355,7 +353,7 @@ internal unsafe struct Archetype : IIdentifierType, IDisposable
} }
// Only operate the swap back after the update is succeed. // Only operate the swap back after the update is succeed.
MemoryUtility.MemCpy(pLastEntity, pRowEntity, (nuint)sizeof(Entity)); MemoryUtility.MemCpy(pRowEntity, pLastEntity, (nuint)sizeof(Entity));
for (var i = 0; i <= _layouts.Count; i++) for (var i = 0; i <= _layouts.Count; i++)
{ {
@@ -364,7 +362,7 @@ internal unsafe struct Archetype : IIdentifierType, IDisposable
var pRow = chunk.GetUnsafePtr() + layout.offset + (layout.size * rowIndex); var pRow = chunk.GetUnsafePtr() + layout.offset + (layout.size * rowIndex);
var pLast = chunk.GetUnsafePtr() + layout.offset + (layout.size * lastIndex); var pLast = chunk.GetUnsafePtr() + layout.offset + (layout.size * lastIndex);
MemoryUtility.MemCpy(pLast, pRow, (nuint)layout.size); MemoryUtility.MemCpy(pRow, pLast, (nuint)layout.size);
} }
} }

View File

@@ -1,3 +1,12 @@
global using EntityID = System.Int32; global using EntityID = System.Int32;
global using GenerationID = 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]

19
Ghost.Entities/Common.cs Normal file
View File

@@ -0,0 +1,19 @@
namespace Ghost.Entities;
public readonly struct Time
{
public int FrameCount
{
get; init;
}
public float DeltaTime
{
get; init;
}
public double ElapsedTime
{
get; init;
}
}

View File

@@ -30,9 +30,9 @@ public static class ComponentTypeID<T>
internal static class ComponentRegister internal static class ComponentRegister
{ {
private static int s_nextComponentTypeID = 0; private static int s_nextComponentTypeID = 0;
private static readonly Dictionary<IntPtr, Identifier<IComponent>> s_typeHandleToID = new();
private static readonly List<ComponentInfo> s_registeredComponents = new(); private static readonly List<ComponentInfo> s_registeredComponents = new();
private static readonly Dictionary<IntPtr, Identifier<IComponent>> s_typeHandleToID = new();
private static readonly Dictionary<string, Identifier<IComponent>> s_nameToRuntimeID = new(); private static readonly Dictionary<string, Identifier<IComponent>> s_nameToRuntimeID = new();
public static unsafe Identifier<IComponent> GetOrRegisterComponent<T>() public static unsafe Identifier<IComponent> GetOrRegisterComponent<T>()
@@ -71,12 +71,26 @@ internal static class ComponentRegister
} }
} }
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;
}
}
throw new KeyNotFoundException($"Component type {type} is not registered.");
}
public static ComponentInfo GetComponentInfo(Identifier<IComponent> typeId) public static ComponentInfo GetComponentInfo(Identifier<IComponent> typeId)
{ {
return s_registeredComponents[typeId]; return s_registeredComponents[typeId];
} }
public static int GetHashCode(ReadOnlySpan<Identifier<IComponent>> componentTypeIDs) public static int GetHashCode(params ReadOnlySpan<Identifier<IComponent>> componentTypeIDs)
{ {
var largestID = 0; var largestID = 0;
foreach (var id in componentTypeIDs) foreach (var id in componentTypeIDs)

View File

@@ -1,5 +1,8 @@
using Ghost.Core;
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections; using Misaki.HighPerformance.LowLevel.Collections;
using Misaki.HighPerformance.LowLevel.Utilities; using Misaki.HighPerformance.LowLevel.Utilities;
using System.Runtime.InteropServices;
namespace Ghost.Entities; namespace Ghost.Entities;
@@ -17,18 +20,24 @@ public unsafe class EntityCommandBuffer : IDisposable
private struct Command private struct Command
{ {
public UnsafeArray<byte> data; public UnsafeArray<byte> data;
public CommandType type;
public Entity entity; public Entity entity;
public int componentTypeID; public CommandType type;
public Identifier<IComponent> componentTypeID;
} }
private readonly EntityManager _entityManager; private readonly EntityManager _entityManager;
private UnsafeList<Command> _commands; // TODO: Maybe use UnsafeArray<byte> directly? private UnsafeList<Command> _commands; // TODO: Maybe use UnsafeArray<byte> directly to save additional memory allocation in Unsafe<byte> data inside Command struct.
private bool _disposed;
public EntityCommandBuffer(EntityManager entityManager) public EntityCommandBuffer(EntityManager entityManager)
{ {
_entityManager = entityManager; _entityManager = entityManager;
_commands = new UnsafeList<Command>(32, Misaki.HighPerformance.LowLevel.Buffer.Allocator.Persistent); _commands = new UnsafeList<Command>(32, Allocator.Persistent);
}
~EntityCommandBuffer()
{
Dispose();
} }
public void CreateEntity() public void CreateEntity()
@@ -44,48 +53,107 @@ public unsafe class EntityCommandBuffer : IDisposable
_commands.Add(command); _commands.Add(command);
} }
public void DestroyEntity(Entity entity) public void CreateEntity(params ReadOnlySpan<Identifier<IComponent>> componentTypeIDs)
{ {
var data = new UnsafeArray<byte>(componentTypeIDs.Length * sizeof(int), Allocator.Temp);
MemoryMarshal.Cast<Identifier<IComponent>, byte>(componentTypeIDs).CopyTo(data.AsSpan());
var command = new Command var command = new Command
{ {
type = CommandType.DestroyEntity, type = CommandType.CreateEntity,
data = default, data = data,
entity = entity, entity = Entity.Invalid,
componentTypeID = -1 componentTypeID = Identifier<IComponent>.Invalid
}; };
_commands.Add(command); _commands.Add(command);
} }
public void AddComponent<T>(Entity entity, T component) public void DestroyEntity(Entity entity)
{
_commands.Add(new Command
{
type = CommandType.DestroyEntity,
data = default,
entity = entity,
componentTypeID = Identifier<IComponent>.Invalid
});
}
public void AddComponent<T>(Entity entity, T component = default)
where T : unmanaged, IComponent where T : unmanaged, IComponent
{ {
var data = new UnsafeArray<byte>(sizeof(T), Misaki.HighPerformance.LowLevel.Buffer.Allocator.Persistent); var data = new UnsafeArray<byte>(sizeof(T), Allocator.Temp);
MemoryUtility.MemCpy(&component, data.GetUnsafePtr(), (nuint)sizeof(T)); MemoryUtility.MemCpy(data.GetUnsafePtr(), &component, (nuint)sizeof(T));
var command = new Command _commands.Add(new Command
{ {
type = CommandType.AddComponent, type = CommandType.AddComponent,
data = data, data = data,
entity = entity, entity = entity,
componentTypeID = ComponentTypeID<T>.value componentTypeID = ComponentTypeID<T>.value
}; });
_commands.Add(command);
} }
public void RemoveComponent<T>(Entity entity) public void RemoveComponent<T>(Entity entity)
where T : unmanaged, IComponent where T : unmanaged, IComponent
{ {
var command = new Command _commands.Add(new Command
{ {
type = CommandType.RemoveComponent, type = CommandType.RemoveComponent,
data = default, data = default,
entity = entity, entity = entity,
componentTypeID = ComponentTypeID<T>.value componentTypeID = ComponentTypeID<T>.value
}; });
}
_commands.Add(command); public void SetComponent<T>(Entity entity, T component)
where T : unmanaged, IComponent
{
var data = new UnsafeArray<byte>(sizeof(T), Allocator.Temp);
MemoryUtility.MemCpy(data.GetUnsafePtr(), &component, (nuint)sizeof(T));
_commands.Add(new Command
{
type = CommandType.SetComponent,
data = data,
entity = entity,
componentTypeID = ComponentTypeID<T>.value
});
}
internal void Playback()
{
foreach (ref var command in _commands)
{
switch (command.type)
{
case CommandType.CreateEntity:
if (command.data.Count > 0)
{
_entityManager.CreateEntity(MemoryMarshal.Cast<byte, Identifier<IComponent>>(command.data.AsSpan()));
}
else
{
_entityManager.CreateEntity();
}
break;
case CommandType.DestroyEntity:
_entityManager.DestroyEntity(command.entity);
break;
case CommandType.AddComponent:
_entityManager.AddComponent(command.entity, command.componentTypeID, command.data.GetUnsafePtr());
break;
case CommandType.RemoveComponent:
_entityManager.RemoveComponent(command.entity, command.componentTypeID);
break;
case CommandType.SetComponent:
_entityManager.SetComponent(command.entity, command.componentTypeID, command.data.GetUnsafePtr());
break;
}
}
Reset();
} }
public void Reset() public void Reset()
@@ -100,11 +168,19 @@ public unsafe class EntityCommandBuffer : IDisposable
public void Dispose() public void Dispose()
{ {
if (_disposed)
{
return;
}
foreach (ref var command in _commands) foreach (ref var command in _commands)
{ {
command.data.Dispose(); command.data.Dispose();
} }
_commands.Dispose(); _commands.Dispose();
_disposed = true;
GC.SuppressFinalize(this);
} }
} }

View File

@@ -6,7 +6,7 @@ using System.Diagnostics;
namespace Ghost.Entities; namespace Ghost.Entities;
public unsafe class EntityManager : IDisposable public unsafe partial class EntityManager : IDisposable
{ {
private struct EntityLocation private struct EntityLocation
{ {
@@ -45,6 +45,34 @@ public unsafe class EntityManager : IDisposable
return ResultStatus.Success; return ResultStatus.Success;
} }
/// <summary>
/// Create an entity with no components.
/// </summary>
/// <returns>The created entity.</returns>
public Entity CreateEntity()
{
// Put into empty archetype
ref var emptyArchetype = ref _world.GetArchetypeReference(World.EmptyArchetypeID);
emptyArchetype.AllocateEntity(out var chunkIndex, out var rowIndex);
var id = _entityLocations.Add(new EntityLocation
{
archetypeID = World.EmptyArchetypeID,
chunkIndex = chunkIndex,
rowIndex = rowIndex
}, out var generation);
var entity = new Entity(id, generation);
emptyArchetype.SetEntity(chunkIndex, rowIndex, entity);
return entity;
}
/// <summary>
/// Create an entity with specified components.
/// </summary>
/// <param name="componentTypeIDs">The component type IDs to add to the entity.</param>
/// <returns>The created entity.</returns>
public Entity CreateEntity(params ReadOnlySpan<Identifier<IComponent>> componentTypeIDs) public Entity CreateEntity(params ReadOnlySpan<Identifier<IComponent>> componentTypeIDs)
{ {
var signatureHash = ComponentRegister.GetHashCode(componentTypeIDs); var signatureHash = ComponentRegister.GetHashCode(componentTypeIDs);
@@ -71,26 +99,84 @@ public unsafe class EntityManager : IDisposable
return entity; return entity;
} }
public Entity CreateEntity() /// <summary>
/// Create multiple entities with specified components.
/// </summary>
/// <param name="count">The number of entities to create.</param>
/// <param name="allocator">The allocator to use for the returned array.</param>
/// <param name="componentTypeIDs">The component type IDs to add to the entities. </param>
/// <returns>An array of the created entities.</returns>
public UnsafeArray<Entity> CreateEntities(int count, Allocator allocator, params ReadOnlySpan<Identifier<IComponent>> componentTypeIDs)
{ {
// Put into empty archetype var signatureHash = ComponentRegister.GetHashCode(componentTypeIDs);
ref var emptyArchetype = ref _world.GetArchetypeReference(World.EmptyArchetypeID); var arcID = _world.GetArchetypeIDBySignatureHash(signatureHash);
emptyArchetype.AllocateEntity(out var chunkIndex, out var rowIndex);
var id = _entityLocations.Add(new EntityLocation if (arcID.IsNotValid)
{ {
archetypeID = World.EmptyArchetypeID, arcID = _world.CreateArchetype(componentTypeIDs, signatureHash);
chunkIndex = chunkIndex, }
rowIndex = rowIndex
}, out var generation);
var entity = new Entity(id, generation); ref var archetype = ref _world.GetArchetypeReference(arcID);
emptyArchetype.SetEntity(chunkIndex, rowIndex, entity);
return entity; var entities = new UnsafeArray<Entity>(count, allocator);
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);
entities[i] = entity;
}
return entities;
} }
public ResultStatus DestoryEntity(Entity entity) /// <summary>
/// Create multiple entities with specified components.
/// </summary>
/// <param name="count">The number of entities to create.</param>
/// <param name="componentTypeIDs">The component type IDs to add to the entities. </param>
public void CreateEntities(int count, params ReadOnlySpan<Identifier<IComponent>> componentTypeIDs)
{
var signatureHash = ComponentRegister.GetHashCode(componentTypeIDs);
var arcID = _world.GetArchetypeIDBySignatureHash(signatureHash);
if (arcID.IsNotValid)
{
arcID = _world.CreateArchetype(componentTypeIDs, signatureHash);
}
ref var archetype = ref _world.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);
}
}
/// <summary>
/// Destroy the specified entity.
/// </summary>
/// <returns>The result status of the operation.</returns>
public ResultStatus DestroyEntity(Entity entity)
{ {
if (!_entityLocations.TryGetElementAt(entity.ID, entity.Generation, out var location)) if (!_entityLocations.TryGetElementAt(entity.ID, entity.Generation, out var location))
{ {
@@ -112,6 +198,109 @@ public unsafe class EntityManager : IDisposable
return ResultStatus.Success; return ResultStatus.Success;
} }
/// <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 type ID of the singleton.</param>
/// <param name="pComponent">Pointer to the component data.</param>
/// <returns>The result status of the operation.</returns>
public ResultStatus CreateSingleton(Identifier<IComponent> componentID, void* pComponent)
{
if (pComponent == null)
{
return ResultStatus.InvalidArgument;
}
// Check if singleton already exists
var signatureHash = ComponentRegister.GetHashCode(componentID);
var arcID = _world.GetArchetypeIDBySignatureHash(signatureHash);
if (arcID.IsValid)
{
return ResultStatus.InvalidArgument;
}
arcID = _world.CreateArchetype([componentID], signatureHash);
ref var archetype = ref _world.GetArchetypeReference(arcID);
archetype.AllocateEntity(out var chunkIndex, out var rowIndex);
var id = _entityLocations.Add(new EntityLocation
{
archetypeID = arcID,
chunkIndex = chunkIndex,
rowIndex = rowIndex
}, out var generation);
var entity = new Entity(id, generation);
archetype.SetEntity(chunkIndex, rowIndex, entity);
archetype.SetComponentData(chunkIndex, rowIndex, componentID, pComponent);
return ResultStatus.Success;
}
/// <summary>
/// Create a singleton entity with the specified component.
/// </summary>
/// <typeparam name="T">The component type.</typeparam>
/// <param name="component">The component data.</param>
/// <returns>The result status of the operation.</returns>
public ResultStatus 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 type 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 = ComponentRegister.GetHashCode(componentID);
var arcID = _world.GetArchetypeIDBySignatureHash(signatureHash);
if (arcID.IsNotValid)
{
return null;
}
ref var archetype = ref _world.GetArchetypeReference(arcID);
var layoutResult = archetype.GetLayout(componentID);
if (layoutResult.Status != ResultStatus.Success)
{
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 type.</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, private static void CopyData(ref Archetype oldArch, int oldChunk, int oldRow,
ref Archetype newArch, int newChunk, int newRow) ref Archetype newArch, int newChunk, int newRow)
{ {
@@ -121,20 +310,27 @@ public unsafe class EntityManager : IDisposable
var layout = oldArch._layouts[i]; var layout = oldArch._layouts[i];
var src = oldArch._chunks[oldChunk].GetUnsafePtr() + layout.offset + (layout.size * oldRow); var src = oldArch._chunks[oldChunk].GetUnsafePtr() + layout.offset + (layout.size * oldRow);
var layoutResult = newArch.GetLayout(layout.componentID); var r = newArch.GetLayout(layout.componentID);
Debug.Assert(layoutResult.Status == ResultStatus.Success); // This should always be true if the system is consistent. Debug.Assert(r.Status == ResultStatus.Success); // This should always be true if the system is consistent.
if (layoutResult.Status != ResultStatus.Success) if (r.Status != ResultStatus.Success)
{ {
continue; continue;
} }
var dst = newArch._chunks[newChunk].GetUnsafePtr() + layoutResult.Value.offset + (layout.size * newRow); var dst = newArch._chunks[newChunk].GetUnsafePtr() + r.Value.offset + (layout.size * newRow);
MemoryUtility.MemCpy(src, dst, (nuint)layout.size); MemoryUtility.MemCpy(dst, src, (nuint)layout.size);
} }
} }
public ResultStatus AddComponent(Entity entity, Identifier<IComponent> componentID, void* component) /// <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 type ID to add.</param>
/// <param name="pComponent">Pointer to the component data.</param>
/// <returns>The result status of the operation.</returns>
public ResultStatus AddComponent(Entity entity, Identifier<IComponent> componentID, void* pComponent)
{ {
// Find current location // Find current location
ref var location = ref _entityLocations.GetElementReferenceAt(entity.ID, entity.Generation, out var exist); ref var location = ref _entityLocations.GetElementReferenceAt(entity.ID, entity.Generation, out var exist);
@@ -159,18 +355,11 @@ public unsafe class EntityManager : IDisposable
var newSignature = new SpanBitSet(bits); var newSignature = new SpanBitSet(bits);
var iterator = 0; var oldIt = oldSignature.GetIterator();
var compCount = 0; var compCount = 0;
while (true) while (oldIt.Next(out var index))
{ {
var bit = oldSignature.NextSetBit(iterator); newSignature.SetBit(index);
if (bit == -1)
{
break;
}
newSignature.SetBit(bit);
iterator = bit + 1;
compCount++; compCount++;
} }
@@ -184,19 +373,12 @@ public unsafe class EntityManager : IDisposable
{ {
// Create new archetype // Create new archetype
Span<Identifier<IComponent>> componentTypeIDs = stackalloc Identifier<IComponent>[compCount]; Span<Identifier<IComponent>> componentTypeIDs = stackalloc Identifier<IComponent>[compCount];
componentTypeIDs[0] = componentID;
iterator = 0; var newIt = newSignature.GetIterator();
while (true) var i = 0;
while (newIt.Next(out var index))
{ {
var bit = oldSignature.NextSetBit(iterator); componentTypeIDs[i++] = index;
if (bit == -1)
{
break;
}
componentTypeIDs[--compCount] = bit;
iterator = bit + 1;
} }
newArcID = _world.CreateArchetype(componentTypeIDs, newSignatureHash); newArcID = _world.CreateArchetype(componentTypeIDs, newSignatureHash);
@@ -212,7 +394,7 @@ public unsafe class EntityManager : IDisposable
ref newArchetype, newChunkIndex, newRowIndex); ref newArchetype, newChunkIndex, newRowIndex);
newArchetype.SetEntity(newChunkIndex, newRowIndex, entity); newArchetype.SetEntity(newChunkIndex, newRowIndex, entity);
newArchetype.SetComponentData(newChunkIndex, newRowIndex, componentID, component); newArchetype.SetComponentData(newChunkIndex, newRowIndex, componentID, pComponent);
var r = oldArchetype.RemoveEntity(location.chunkIndex, location.rowIndex); var r = oldArchetype.RemoveEntity(location.chunkIndex, location.rowIndex);
Debug.Assert(r == ResultStatus.Success); // We assert it because the entity should exist if the whole system is consistent. Debug.Assert(r == ResultStatus.Success); // We assert it because the entity should exist if the whole system is consistent.
@@ -229,13 +411,121 @@ public unsafe class EntityManager : IDisposable
return ResultStatus.Success; return ResultStatus.Success;
} }
public ResultStatus AddComponent<T>(Entity entity, T component) /// <summary>
/// Add a component to the specified entity.
/// </summary>
/// <typeparam name="T">The component type.</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 ResultStatus AddComponent<T>(Entity entity, T component = default)
where T : unmanaged, IComponent where T : unmanaged, IComponent
{ {
return AddComponent(entity, ComponentTypeID<T>.value, &component); return AddComponent(entity, ComponentTypeID<T>.value, &component);
} }
public ResultStatus SetComponentData(Entity entity, Identifier<IComponent> componentID, void* pComponent) /// <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 type ID to remove.</param>
/// <returns>The result status of the operation.</returns>
public ResultStatus 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 ResultStatus.NotFound;
}
// Build new archetype signature
ref var oldArchetype = ref _world.GetArchetypeReference(location.archetypeID);
var oldSignature = oldArchetype._signature;
var newArcID = oldArchetype.GetEdgeRemove(componentID);
if (newArcID.IsNotValid)
{
var largestComponentID = Math.Max(oldSignature.Count, componentID);
var length = UnsafeBitSet.RequiredLength(largestComponentID + 1);
Span<uint> bits = stackalloc uint[length];
bits.Clear();
var newSignature = new SpanBitSet(bits);
var 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.GetArchetypeIDBySignatureHash(newSignatureHash);
if (newArcID.IsNotValid)
{
// 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.CreateArchetype(componentTypeIDs, newSignatureHash);
}
oldArchetype.AddEdgeRemove(componentID, newArcID);
}
// Move entity data
ref var newArchetype = ref _world.GetArchetypeReference(newArcID);
newArchetype.AllocateEntity(out var newChunkIndex, out var newRowIndex);
newArchetype.SetEntity(newChunkIndex, newRowIndex, entity);
var r = oldArchetype.RemoveEntity(location.chunkIndex, location.rowIndex);
Debug.Assert(r == ResultStatus.Success); // We assert it because the entity should exist if the whole system is consistent.
if (r != ResultStatus.Success)
{
return r;
}
// Update location
location.archetypeID = newArcID;
location.chunkIndex = newChunkIndex;
location.rowIndex = newRowIndex;
return ResultStatus.Success;
}
/// <summary>
/// Remove a component from the specified entity.
/// </summary>
/// <typeparam name="T">The component type.</typeparam>
/// <param name="entity">The entity to remove the component from.</param>
/// <returns>The result status of the operation.</returns>
public ResultStatus 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 type ID to set.</param>
/// <param name="pComponent">Pointer to the component data.</param>
/// <returns>The result status of the operation.</returns>
public ResultStatus SetComponent(Entity entity, Identifier<IComponent> componentID, void* pComponent)
{ {
if (!_entityLocations.TryGetElementAt(entity.ID, entity.Generation, out var location)) if (!_entityLocations.TryGetElementAt(entity.ID, entity.Generation, out var location))
{ {
@@ -248,12 +538,54 @@ public unsafe class EntityManager : IDisposable
return ResultStatus.Success; return ResultStatus.Success;
} }
public ResultStatus SetComponentData<T>(Entity entity, T component) /// <summary>
/// Set the component data for the specified entity.
/// </summary>
/// <typeparam name="T">The component type.</typeparam>
/// <param name="entity">The entity to set the component data for.</param>
/// <param name="component">The component data.</param>
public ResultStatus SetComponent<T>(Entity entity, T component)
where T : unmanaged, IComponent where T : unmanaged, IComponent
{ {
return SetComponentData(entity, ComponentTypeID<T>.value, &component); 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 type 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.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 type.</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 type ID to check.</param>
/// <returns>True if the entity has the component, false otherwise.</returns>
public bool HasComponent(Entity entity, Identifier<IComponent> componentID) public bool HasComponent(Entity entity, Identifier<IComponent> componentID)
{ {
if (!_entityLocations.TryGetElementAt(entity.ID, entity.Generation, out var location)) if (!_entityLocations.TryGetElementAt(entity.ID, entity.Generation, out var location))
@@ -265,14 +597,26 @@ public unsafe class EntityManager : IDisposable
return archetype.HasComponent(componentID); return archetype.HasComponent(componentID);
} }
/// <summary>
/// Check if the specified entity has the specified component.
/// </summary>
/// <typeparam name="T">The component type.</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) public bool HasComponent<T>(Entity entity)
where T : unmanaged, IComponent where T : unmanaged, IComponent
{ {
return HasComponent(entity, ComponentTypeID<T>.value); return HasComponent(entity, ComponentTypeID<T>.value);
} }
public ResultStatus SetEnabled<T>(Entity entity, bool enabled) /// <summary>
where T : unmanaged, IEnableableComponent /// 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 type 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 ResultStatus SetEnabled(Entity entity, Identifier<IComponent> componentID, bool enabled)
{ {
if (!_entityLocations.TryGetElementAt(entity.ID, entity.Generation, out var location)) if (!_entityLocations.TryGetElementAt(entity.ID, entity.Generation, out var location))
{ {
@@ -283,7 +627,7 @@ public unsafe class EntityManager : IDisposable
var chunkIndex = location.chunkIndex; var chunkIndex = location.chunkIndex;
var rowIndex = location.rowIndex; var rowIndex = location.rowIndex;
var layoutResult = archetype.GetLayout(ComponentTypeID<T>.value); var layoutResult = archetype.GetLayout(componentID);
if (layoutResult.Status != ResultStatus.Success) if (layoutResult.Status != ResultStatus.Success)
{ {
return layoutResult.Status; return layoutResult.Status;
@@ -308,6 +652,19 @@ public unsafe class EntityManager : IDisposable
return ResultStatus.Success; return ResultStatus.Success;
} }
/// <summary>
/// Set the enabled state of an enableable component for the specified entity.
/// </summary>
/// <typeparam name="T">The enableable component type.</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 ResultStatus SetEnabled<T>(Entity entity, bool enabled)
where T : unmanaged, IEnableableComponent
{
return SetEnabled(entity, ComponentTypeID<T>.value, enabled);
}
public void Dispose() public void Dispose()
{ {
if (_disposed) if (_disposed)

View File

@@ -37,17 +37,6 @@ public struct EntityQueryMask : IDisposable, IEquatable<EntityQueryMask>
return hash; return hash;
} }
public void Dispose()
{
structuralAll.Dispose();
structuralAny.Dispose();
structuralAbsent.Dispose();
requireEnabled.Dispose();
requireDisabled.Dispose();
rejectIfEnabled.Dispose();
}
public readonly bool Equals(EntityQueryMask other) public readonly bool Equals(EntityQueryMask other)
{ {
return structuralAll.Equals(other.structuralAll) return structuralAll.Equals(other.structuralAll)
@@ -72,98 +61,109 @@ public struct EntityQueryMask : IDisposable, IEquatable<EntityQueryMask>
{ {
return !(left == right); return !(left == right);
} }
public void Dispose()
{
structuralAll.Dispose();
structuralAny.Dispose();
structuralAbsent.Dispose();
requireEnabled.Dispose();
requireDisabled.Dispose();
rejectIfEnabled.Dispose();
}
} }
public unsafe partial struct EntityQuery : IIdentifierType, IDisposable public unsafe partial struct EntityQuery : IIdentifierType, IDisposable
{ {
/// <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 ref struct ChunkView
{
private readonly ref Archetype _archetype;
private readonly ref Chunk _chunk;
public readonly int Count => _chunk.Count;
internal ChunkView(ref Archetype archetype, int chunkIndex)
{
_archetype = ref archetype;
_chunk = ref archetype.GetChunkReference(chunkIndex);
}
/// <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>
public readonly ReadOnlySpan<Entity> GetEntities()
{
var ptr = _chunk.GetUnsafePtr();
var pEntity = (Entity*)(ptr + _archetype.EntityIDsOffset);
return new ReadOnlySpan<Entity>(pEntity, _chunk.Count);
}
/// <summary>
/// Gets a span providing direct access to the component data of type T0 for structuralAll entities in the chunk.
/// </summary>
/// <typeparam name="T">The type of component to access. Must be an unmanaged type that implements <see cref="Component"/>.</typeparam>
/// <returns>A span of type <see cref="{T}"/> containing the component data for each entity in the chunk.</returns>
/// <exception cref="InvalidOperationException">Thrown if the specified component type is not present in the archetype.</exception>
public readonly Span<T> GetComponentData<T>()
where T : unmanaged, IComponent
{
var layout = _archetype.GetLayout(ComponentTypeID<T>.value).GetValueOrThrow(ResultStatus.Success);
var ptr = _chunk.GetUnsafePtr() + layout.offset;
return new Span<T>(ptr, _chunk.Count);
}
/// <summary>
/// Gets a bit set representing the enabled state of each instance of the specified enableable component
/// type within the current chunk.
/// </summary>
/// <typeparam name="T">The component type 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 type in the chunk.</returns>
/// <exception cref="InvalidOperationException">Thrown if the specified component type does not support enablement.</exception>
public SpanBitSet GetEnableBits<T>()
where T : unmanaged, IEnableableComponent
{
var layout = _archetype.GetLayout(ComponentTypeID<T>.value).GetValueOrThrow(ResultStatus.Success);
if (layout.enableBitsOffset == -1)
{
throw new InvalidOperationException($"Component {typeof(T).FullName} is not enableable.");
}
var maskBase = _chunk.GetUnsafePtr() + layout.enableBitsOffset;
return new SpanBitSet(new Span<uint>(maskBase, (_chunk.Count + 31) / 32));
}
/// <summary>
/// Determines whether the specified component of type <typeparamref name="T"/> at the given index is currently enabled.
/// </summary>
/// <typeparam name="T">The type of the component to check. Must be an unmanaged type 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 type <typeparamref name="T"/> does not support enable/disable functionality.</exception>
public readonly bool IsComponentEnabled<T>(int index)
where T : unmanaged, IEnableableComponent
{
var layout = _archetype.GetLayout(ComponentTypeID<T>.value).GetValueOrThrow(ResultStatus.Success);
if (layout.enableBitsOffset == -1)
{
throw new InvalidOperationException($"Component {typeof(T).FullName} is not enableable.");
}
var maskBase = _chunk.GetUnsafePtr() + layout.enableBitsOffset;
return CheckBit(maskBase, index);
}
}
/// <summary> /// <summary>
/// Provides an enumerator for iterating over chunks of entities and their component data that match a set of archetypes within a world. /// Provides an enumerator for iterating over chunks of entities and their component data that match a set of archetypes within a world.
/// </summary> /// </summary>
public readonly ref struct ChunkIterator public readonly ref struct ChunkIterator
{ {
/// <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 ref struct ChunkView
{
private readonly ref Archetype _archetype;
private readonly ref Chunk _chunk;
public readonly int Count => _chunk.Count;
internal ChunkView(ref Archetype archetype, int chunkIndex)
{
_archetype = ref archetype;
_chunk = ref archetype.GetChunkReference(chunkIndex);
}
/// <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>
public readonly ReadOnlySpan<Entity> GetEntities()
{
var ptr = _chunk.GetUnsafePtr();
var pEntity = (Entity*)(ptr + _archetype.EntityIDsOffset);
return new ReadOnlySpan<Entity>(pEntity, _chunk.Count);
}
/// <summary>
/// Gets a span providing direct access to the component data of type T0 for structuralAll entities in the chunk.
/// </summary>
/// <typeparam name="T">The type of component to access. Must be an unmanaged type that implements <see cref="Component"/>.</typeparam>
/// <returns>A span of type <see cref="{T}"/> containing the component data for each entity in the chunk.</returns>
/// <exception cref="InvalidOperationException">Thrown if the specified component type is not present in the archetype.</exception>
public readonly Span<T> GetComponentData<T>()
where T : unmanaged, IComponent
{
var layout = _archetype.GetLayout(ComponentTypeID<T>.value).GetValueOrThrow(ResultStatus.Success);
var ptr = _chunk.GetUnsafePtr() + layout.offset;
return new Span<T>(ptr, _chunk.Count);
}
/// <summary>
/// Gets a bit set representing the enabled state of each instance of the specified enableable component
/// type within the current chunk.
/// </summary>
/// <typeparam name="T">The component type 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 type in the chunk.</returns>
/// <exception cref="InvalidOperationException">Thrown if the specified component type does not support enablement.</exception>
public SpanBitSet GetEnableBits<T>()
where T : unmanaged, IEnableableComponent
{
var layout = _archetype.GetLayout(ComponentTypeID<T>.value).GetValueOrThrow(ResultStatus.Success);
if (layout.enableBitsOffset == -1)
{
throw new InvalidOperationException($"Component {typeof(T).FullName} is not enableable.");
}
var maskBase = _chunk.GetUnsafePtr() + layout.enableBitsOffset;
return new SpanBitSet(new Span<uint>(maskBase, (_chunk.Count + 31) / 32));
}
/// <summary>
/// Determines whether the specified component of type <typeparamref name="T"/> at the given index is currently enabled.
/// </summary>
/// <typeparam name="T">The type of the component to check. Must be an unmanaged type 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 type <typeparamref name="T"/> does not support enable/disable functionality.</exception>
public readonly bool IsComponentEnabled<T>(int index)
where T : unmanaged, IEnableableComponent
{
var layout = _archetype.GetLayout(ComponentTypeID<T>.value).GetValueOrThrow(ResultStatus.Success);
if (layout.enableBitsOffset == -1)
{
throw new InvalidOperationException($"Component {typeof(T).FullName} is not enableable.");
}
var maskBase = _chunk.GetUnsafePtr() + layout.enableBitsOffset;
return CheckBit(maskBase, index);
}
}
public ref struct Enumerator public ref struct Enumerator
{ {
private readonly ChunkIterator _iterator; private readonly ChunkIterator _iterator;
@@ -210,10 +210,6 @@ public unsafe partial struct EntityQuery : IIdentifierType, IDisposable
_archetypeIndex = 0; _archetypeIndex = 0;
_chunkIndex = -1; _chunkIndex = -1;
} }
public readonly void Dispose()
{
}
} }
private readonly ReadOnlyUnsafeCollection<Identifier<Archetype>> _matchingArchetypes; private readonly ReadOnlyUnsafeCollection<Identifier<Archetype>> _matchingArchetypes;
@@ -245,6 +241,7 @@ public unsafe partial struct EntityQuery : IIdentifierType, IDisposable
_matchingArchetypes = new UnsafeList<Identifier<Archetype>>(8, Allocator.Persistent); _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) private static bool IsEntityValid(byte* chunkBase, int entityIndex, ref readonly Archetype archetype, ref readonly EntityQueryMask mask)
{ {
// 1. Check "Require Enabled" (WithAll) // 1. Check "Require Enabled" (WithAll)
@@ -358,12 +355,12 @@ public ref partial struct QueryBuilder
{ {
_scope = AllocationManager.CreateStackScope(); _scope = AllocationManager.CreateStackScope();
_all = new UnsafeList<Identifier<IComponent>>(4, Allocator.Stack); _all = new UnsafeList<Identifier<IComponent>>(4, _scope.AllocationHandle);
_any = new UnsafeList<Identifier<IComponent>>(4, Allocator.Stack); _any = new UnsafeList<Identifier<IComponent>>(4, _scope.AllocationHandle);
_absent = new UnsafeList<Identifier<IComponent>>(4, Allocator.Stack); _absent = new UnsafeList<Identifier<IComponent>>(4, _scope.AllocationHandle);
_none = new UnsafeList<Identifier<IComponent>>(4, Allocator.Stack); _none = new UnsafeList<Identifier<IComponent>>(4, _scope.AllocationHandle);
_disabled = new UnsafeList<Identifier<IComponent>>(4, Allocator.Stack); _disabled = new UnsafeList<Identifier<IComponent>>(4, _scope.AllocationHandle);
_present = new UnsafeList<Identifier<IComponent>>(4, Allocator.Stack); _present = new UnsafeList<Identifier<IComponent>>(4, _scope.AllocationHandle);
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -461,13 +458,6 @@ public ref partial struct QueryBuilder
private void Dispose() private void Dispose()
{ {
_all.Dispose();
_any.Dispose();
_absent.Dispose();
_none.Dispose();
_disabled.Dispose();
_present.Dispose();
_scope.Dispose(); _scope.Dispose();
} }
} }

285
Ghost.Entities/System.cs Normal file
View File

@@ -0,0 +1,285 @@
using Ghost.Core;
using Misaki.HighPerformance.Jobs;
namespace Ghost.Entities;
public readonly ref struct SystemAPI
{
public World World
{
get; init;
}
public Time Time
{
get; init;
}
}
public interface ISystem
{
void Initialize(ref readonly SystemAPI systemAPI);
void PreUpdate(ref readonly SystemAPI systemAPI);
void Update(ref readonly SystemAPI systemAPI);
void PostUpdate(ref readonly SystemAPI systemAPI);
void Cleanup(ref readonly SystemAPI systemAPI);
}
[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 = new ();
private List<ISystem>? _sortedSystems;
private uint _version = 0;
private uint _sortedVersion = 0;
// public SystemGroup()
// {
// _systems = SystemGroupRegistry.GetSystemsForGroup(GetType());
// }
private static List<ISystem> Sort(List<ISystem> systems)
{
// 1. Build the Graph
// Key: The System, Value: Systems that MUST run before the Key
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.ContainsKey(type)) dependencies[type] = new HashSet<Type>();
// Handle [UpdateAfter(typeof(Other))] -> Other comes before This
foreach (var attr in type.GetCustomAttributes(typeof(UpdateAfterAttribute), true))
{
var depType = ((UpdateAfterAttribute)attr).SystemType;
dependencies[type].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] = new HashSet<Type>();
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(ISystem system)
{
_systems.Add(system);
_version++;
}
public void SortSystems()
{
_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 PreUpdate(ref readonly SystemAPI systemAPI)
{
ThrowIfNotSorted();
foreach (var system in _sortedSystems!)
{
system.PreUpdate(in systemAPI);
}
}
public void Update(ref readonly SystemAPI systemAPI)
{
ThrowIfNotSorted();
foreach (var system in _sortedSystems!)
{
system.Update(in systemAPI);
}
}
public void PostUpdate(ref readonly SystemAPI systemAPI)
{
ThrowIfNotSorted();
foreach (var system in _sortedSystems!)
{
system.PostUpdate(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 = new ();
private readonly Dictionary<Type, int> _systemTypeMap = new ();
internal SystemManager(World world)
{
_world = world;
}
public void AddSystem<T>()
where T : ISystem, new()
{
var system = new T();
_systems.Add(system);
_systemTypeMap[typeof(T)] = _systems.Count - 1;
}
public T GetSystem<T>()
where T : ISystem
{
if (_systemTypeMap.TryGetValue(typeof(T), out var index))
{
return (T)_systems[index];
}
throw new InvalidOperationException($"System of type {typeof(T).FullName} not found in SystemManager.");
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,198 @@
<#@ 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.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
{
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 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;
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(ResultStatus.Success);
_offsets[index] = layout.offset;
_compBasePtrs[index] = (long)(_chunkBasePtr + _offsets[index]);
}
}
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.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.GetArchetypeReference(_matchingArchetypes[0]);
if (_currentArchetype.ChunkCount > 0)
{
SetChunk(0);
}
}
}
}
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 #>
{
return new ComponentIterator<<#= generics#>>(_matchingArchetypes.AsReadOnly(), _mask, World.GetWorld(_worldID).GetValueOrThrow(ResultStatus.Success));
}
<# } #>
}

View File

@@ -1,4 +1,3 @@
using Ghost.Core; using Ghost.Core;
using Misaki.HighPerformance.Jobs; using Misaki.HighPerformance.Jobs;
using Misaki.HighPerformance.LowLevel.Buffer; using Misaki.HighPerformance.LowLevel.Buffer;
@@ -765,10 +764,12 @@ public unsafe partial struct EntityQuery
} }
} }
public JobHandle ScheduleEntityParallel<TJob, T0>(JobScheduler scheduler, TJob jobData, Allocator allocator, int batchSize, JobHandle dependency) public JobHandle ScheduleEntityParallel<TJob, T0>(TJob jobData, Allocator allocator, int batchSize, JobHandle dependency)
where TJob : unmanaged, IJobEntityParallel<T0> where TJob : unmanaged, IJobEntityParallel<T0>
where T0 : unmanaged, IComponent where T0 : unmanaged, IComponent
{ {
var world = World.GetWorld(_worldID).GetValueOrThrow(ResultStatus.Success);
// 1. Flatten the World // 1. Flatten the World
var chunkList = new UnsafeList<IntPtr>(128, allocator); var chunkList = new UnsafeList<IntPtr>(128, allocator);
var chunkEntityCounts = new UnsafeList<int>(128, allocator); var chunkEntityCounts = new UnsafeList<int>(128, allocator);
@@ -780,9 +781,7 @@ public unsafe partial struct EntityQuery
// Iterate the Query's matching archetypes // Iterate the Query's matching archetypes
foreach (var archID in _matchingArchetypes) foreach (var archID in _matchingArchetypes)
{ {
ref var arch = ref World.GetWorld(_worldID) ref var arch = ref world.GetArchetypeReference(archID);
.GetValueOrThrow(ResultStatus.Success)
.GetArchetypeReference(archID);
if (arch.ChunkCount == 0) if (arch.ChunkCount == 0)
{ {
@@ -821,7 +820,7 @@ public unsafe partial struct EntityQuery
}; };
var jobHandle = scheduler.ScheduleParallel(ref runner, chunkList.Count, batchSize, dependency); var jobHandle = world.JobScheduler.ScheduleParallel(ref runner, chunkList.Count, batchSize, dependency);
// 3. Dispose the temp lists // 3. Dispose the temp lists
var disposeJob = new DisposeJobEntity1 var disposeJob = new DisposeJobEntity1
@@ -835,7 +834,7 @@ public unsafe partial struct EntityQuery
}; };
scheduler.Schedule(ref disposeJob, jobHandle); world.JobScheduler.Schedule(ref disposeJob, jobHandle);
return jobHandle; return jobHandle;
} }
@@ -867,11 +866,13 @@ public unsafe partial struct EntityQuery
} }
} }
public JobHandle ScheduleEntityParallel<TJob, T0, T1>(JobScheduler scheduler, TJob jobData, Allocator allocator, int batchSize, JobHandle dependency) public JobHandle ScheduleEntityParallel<TJob, T0, T1>(TJob jobData, Allocator allocator, int batchSize, JobHandle dependency)
where TJob : unmanaged, IJobEntityParallel<T0, T1> where TJob : unmanaged, IJobEntityParallel<T0, T1>
where T0 : unmanaged, IComponent where T0 : unmanaged, IComponent
where T1 : unmanaged, IComponent where T1 : unmanaged, IComponent
{ {
var world = World.GetWorld(_worldID).GetValueOrThrow(ResultStatus.Success);
// 1. Flatten the World // 1. Flatten the World
var chunkList = new UnsafeList<IntPtr>(128, allocator); var chunkList = new UnsafeList<IntPtr>(128, allocator);
var chunkEntityCounts = new UnsafeList<int>(128, allocator); var chunkEntityCounts = new UnsafeList<int>(128, allocator);
@@ -886,9 +887,7 @@ public unsafe partial struct EntityQuery
// Iterate the Query's matching archetypes // Iterate the Query's matching archetypes
foreach (var archID in _matchingArchetypes) foreach (var archID in _matchingArchetypes)
{ {
ref var arch = ref World.GetWorld(_worldID) ref var arch = ref world.GetArchetypeReference(archID);
.GetValueOrThrow(ResultStatus.Success)
.GetArchetypeReference(archID);
if (arch.ChunkCount == 0) if (arch.ChunkCount == 0)
{ {
@@ -935,7 +934,7 @@ public unsafe partial struct EntityQuery
}; };
var jobHandle = scheduler.ScheduleParallel(ref runner, chunkList.Count, batchSize, dependency); var jobHandle = world.JobScheduler.ScheduleParallel(ref runner, chunkList.Count, batchSize, dependency);
// 3. Dispose the temp lists // 3. Dispose the temp lists
var disposeJob = new DisposeJobEntity2 var disposeJob = new DisposeJobEntity2
@@ -952,7 +951,7 @@ public unsafe partial struct EntityQuery
}; };
scheduler.Schedule(ref disposeJob, jobHandle); world.JobScheduler.Schedule(ref disposeJob, jobHandle);
return jobHandle; return jobHandle;
} }
@@ -990,12 +989,14 @@ public unsafe partial struct EntityQuery
} }
} }
public JobHandle ScheduleEntityParallel<TJob, T0, T1, T2>(JobScheduler scheduler, TJob jobData, Allocator allocator, int batchSize, JobHandle dependency) public JobHandle ScheduleEntityParallel<TJob, T0, T1, T2>(TJob jobData, Allocator allocator, int batchSize, JobHandle dependency)
where TJob : unmanaged, IJobEntityParallel<T0, T1, T2> where TJob : unmanaged, IJobEntityParallel<T0, T1, T2>
where T0 : unmanaged, IComponent where T0 : unmanaged, IComponent
where T1 : unmanaged, IComponent where T1 : unmanaged, IComponent
where T2 : unmanaged, IComponent where T2 : unmanaged, IComponent
{ {
var world = World.GetWorld(_worldID).GetValueOrThrow(ResultStatus.Success);
// 1. Flatten the World // 1. Flatten the World
var chunkList = new UnsafeList<IntPtr>(128, allocator); var chunkList = new UnsafeList<IntPtr>(128, allocator);
var chunkEntityCounts = new UnsafeList<int>(128, allocator); var chunkEntityCounts = new UnsafeList<int>(128, allocator);
@@ -1013,9 +1014,7 @@ public unsafe partial struct EntityQuery
// Iterate the Query's matching archetypes // Iterate the Query's matching archetypes
foreach (var archID in _matchingArchetypes) foreach (var archID in _matchingArchetypes)
{ {
ref var arch = ref World.GetWorld(_worldID) ref var arch = ref world.GetArchetypeReference(archID);
.GetValueOrThrow(ResultStatus.Success)
.GetArchetypeReference(archID);
if (arch.ChunkCount == 0) if (arch.ChunkCount == 0)
{ {
@@ -1070,7 +1069,7 @@ public unsafe partial struct EntityQuery
}; };
var jobHandle = scheduler.ScheduleParallel(ref runner, chunkList.Count, batchSize, dependency); var jobHandle = world.JobScheduler.ScheduleParallel(ref runner, chunkList.Count, batchSize, dependency);
// 3. Dispose the temp lists // 3. Dispose the temp lists
var disposeJob = new DisposeJobEntity3 var disposeJob = new DisposeJobEntity3
@@ -1090,7 +1089,7 @@ public unsafe partial struct EntityQuery
}; };
scheduler.Schedule(ref disposeJob, jobHandle); world.JobScheduler.Schedule(ref disposeJob, jobHandle);
return jobHandle; return jobHandle;
} }
@@ -1134,13 +1133,15 @@ public unsafe partial struct EntityQuery
} }
} }
public JobHandle ScheduleEntityParallel<TJob, T0, T1, T2, T3>(JobScheduler scheduler, TJob jobData, Allocator allocator, int batchSize, JobHandle dependency) public JobHandle ScheduleEntityParallel<TJob, T0, T1, T2, T3>(TJob jobData, Allocator allocator, int batchSize, JobHandle dependency)
where TJob : unmanaged, IJobEntityParallel<T0, T1, T2, T3> where TJob : unmanaged, IJobEntityParallel<T0, T1, T2, T3>
where T0 : unmanaged, IComponent where T0 : unmanaged, IComponent
where T1 : unmanaged, IComponent where T1 : unmanaged, IComponent
where T2 : unmanaged, IComponent where T2 : unmanaged, IComponent
where T3 : unmanaged, IComponent where T3 : unmanaged, IComponent
{ {
var world = World.GetWorld(_worldID).GetValueOrThrow(ResultStatus.Success);
// 1. Flatten the World // 1. Flatten the World
var chunkList = new UnsafeList<IntPtr>(128, allocator); var chunkList = new UnsafeList<IntPtr>(128, allocator);
var chunkEntityCounts = new UnsafeList<int>(128, allocator); var chunkEntityCounts = new UnsafeList<int>(128, allocator);
@@ -1161,9 +1162,7 @@ public unsafe partial struct EntityQuery
// Iterate the Query's matching archetypes // Iterate the Query's matching archetypes
foreach (var archID in _matchingArchetypes) foreach (var archID in _matchingArchetypes)
{ {
ref var arch = ref World.GetWorld(_worldID) ref var arch = ref world.GetArchetypeReference(archID);
.GetValueOrThrow(ResultStatus.Success)
.GetArchetypeReference(archID);
if (arch.ChunkCount == 0) if (arch.ChunkCount == 0)
{ {
@@ -1226,7 +1225,7 @@ public unsafe partial struct EntityQuery
}; };
var jobHandle = scheduler.ScheduleParallel(ref runner, chunkList.Count, batchSize, dependency); var jobHandle = world.JobScheduler.ScheduleParallel(ref runner, chunkList.Count, batchSize, dependency);
// 3. Dispose the temp lists // 3. Dispose the temp lists
var disposeJob = new DisposeJobEntity4 var disposeJob = new DisposeJobEntity4
@@ -1249,7 +1248,7 @@ public unsafe partial struct EntityQuery
}; };
scheduler.Schedule(ref disposeJob, jobHandle); world.JobScheduler.Schedule(ref disposeJob, jobHandle);
return jobHandle; return jobHandle;
} }
@@ -1299,7 +1298,7 @@ public unsafe partial struct EntityQuery
} }
} }
public JobHandle ScheduleEntityParallel<TJob, T0, T1, T2, T3, T4>(JobScheduler scheduler, TJob jobData, Allocator allocator, int batchSize, JobHandle dependency) public JobHandle ScheduleEntityParallel<TJob, T0, T1, T2, T3, T4>(TJob jobData, Allocator allocator, int batchSize, JobHandle dependency)
where TJob : unmanaged, IJobEntityParallel<T0, T1, T2, T3, T4> where TJob : unmanaged, IJobEntityParallel<T0, T1, T2, T3, T4>
where T0 : unmanaged, IComponent where T0 : unmanaged, IComponent
where T1 : unmanaged, IComponent where T1 : unmanaged, IComponent
@@ -1307,6 +1306,8 @@ public unsafe partial struct EntityQuery
where T3 : unmanaged, IComponent where T3 : unmanaged, IComponent
where T4 : unmanaged, IComponent where T4 : unmanaged, IComponent
{ {
var world = World.GetWorld(_worldID).GetValueOrThrow(ResultStatus.Success);
// 1. Flatten the World // 1. Flatten the World
var chunkList = new UnsafeList<IntPtr>(128, allocator); var chunkList = new UnsafeList<IntPtr>(128, allocator);
var chunkEntityCounts = new UnsafeList<int>(128, allocator); var chunkEntityCounts = new UnsafeList<int>(128, allocator);
@@ -1330,9 +1331,7 @@ public unsafe partial struct EntityQuery
// Iterate the Query's matching archetypes // Iterate the Query's matching archetypes
foreach (var archID in _matchingArchetypes) foreach (var archID in _matchingArchetypes)
{ {
ref var arch = ref World.GetWorld(_worldID) ref var arch = ref world.GetArchetypeReference(archID);
.GetValueOrThrow(ResultStatus.Success)
.GetArchetypeReference(archID);
if (arch.ChunkCount == 0) if (arch.ChunkCount == 0)
{ {
@@ -1403,7 +1402,7 @@ public unsafe partial struct EntityQuery
}; };
var jobHandle = scheduler.ScheduleParallel(ref runner, chunkList.Count, batchSize, dependency); var jobHandle = world.JobScheduler.ScheduleParallel(ref runner, chunkList.Count, batchSize, dependency);
// 3. Dispose the temp lists // 3. Dispose the temp lists
var disposeJob = new DisposeJobEntity5 var disposeJob = new DisposeJobEntity5
@@ -1429,7 +1428,7 @@ public unsafe partial struct EntityQuery
}; };
scheduler.Schedule(ref disposeJob, jobHandle); world.JobScheduler.Schedule(ref disposeJob, jobHandle);
return jobHandle; return jobHandle;
} }
@@ -1485,7 +1484,7 @@ public unsafe partial struct EntityQuery
} }
} }
public JobHandle ScheduleEntityParallel<TJob, T0, T1, T2, T3, T4, T5>(JobScheduler scheduler, TJob jobData, Allocator allocator, int batchSize, JobHandle dependency) public JobHandle ScheduleEntityParallel<TJob, T0, T1, T2, T3, T4, T5>(TJob jobData, Allocator allocator, int batchSize, JobHandle dependency)
where TJob : unmanaged, IJobEntityParallel<T0, T1, T2, T3, T4, T5> where TJob : unmanaged, IJobEntityParallel<T0, T1, T2, T3, T4, T5>
where T0 : unmanaged, IComponent where T0 : unmanaged, IComponent
where T1 : unmanaged, IComponent where T1 : unmanaged, IComponent
@@ -1494,6 +1493,8 @@ public unsafe partial struct EntityQuery
where T4 : unmanaged, IComponent where T4 : unmanaged, IComponent
where T5 : unmanaged, IComponent where T5 : unmanaged, IComponent
{ {
var world = World.GetWorld(_worldID).GetValueOrThrow(ResultStatus.Success);
// 1. Flatten the World // 1. Flatten the World
var chunkList = new UnsafeList<IntPtr>(128, allocator); var chunkList = new UnsafeList<IntPtr>(128, allocator);
var chunkEntityCounts = new UnsafeList<int>(128, allocator); var chunkEntityCounts = new UnsafeList<int>(128, allocator);
@@ -1520,9 +1521,7 @@ public unsafe partial struct EntityQuery
// Iterate the Query's matching archetypes // Iterate the Query's matching archetypes
foreach (var archID in _matchingArchetypes) foreach (var archID in _matchingArchetypes)
{ {
ref var arch = ref World.GetWorld(_worldID) ref var arch = ref world.GetArchetypeReference(archID);
.GetValueOrThrow(ResultStatus.Success)
.GetArchetypeReference(archID);
if (arch.ChunkCount == 0) if (arch.ChunkCount == 0)
{ {
@@ -1601,7 +1600,7 @@ public unsafe partial struct EntityQuery
}; };
var jobHandle = scheduler.ScheduleParallel(ref runner, chunkList.Count, batchSize, dependency); var jobHandle = world.JobScheduler.ScheduleParallel(ref runner, chunkList.Count, batchSize, dependency);
// 3. Dispose the temp lists // 3. Dispose the temp lists
var disposeJob = new DisposeJobEntity6 var disposeJob = new DisposeJobEntity6
@@ -1630,7 +1629,7 @@ public unsafe partial struct EntityQuery
}; };
scheduler.Schedule(ref disposeJob, jobHandle); world.JobScheduler.Schedule(ref disposeJob, jobHandle);
return jobHandle; return jobHandle;
} }
@@ -1692,7 +1691,7 @@ public unsafe partial struct EntityQuery
} }
} }
public JobHandle ScheduleEntityParallel<TJob, T0, T1, T2, T3, T4, T5, T6>(JobScheduler scheduler, TJob jobData, Allocator allocator, int batchSize, JobHandle dependency) public JobHandle ScheduleEntityParallel<TJob, T0, T1, T2, T3, T4, T5, T6>(TJob jobData, Allocator allocator, int batchSize, JobHandle dependency)
where TJob : unmanaged, IJobEntityParallel<T0, T1, T2, T3, T4, T5, T6> where TJob : unmanaged, IJobEntityParallel<T0, T1, T2, T3, T4, T5, T6>
where T0 : unmanaged, IComponent where T0 : unmanaged, IComponent
where T1 : unmanaged, IComponent where T1 : unmanaged, IComponent
@@ -1702,6 +1701,8 @@ public unsafe partial struct EntityQuery
where T5 : unmanaged, IComponent where T5 : unmanaged, IComponent
where T6 : unmanaged, IComponent where T6 : unmanaged, IComponent
{ {
var world = World.GetWorld(_worldID).GetValueOrThrow(ResultStatus.Success);
// 1. Flatten the World // 1. Flatten the World
var chunkList = new UnsafeList<IntPtr>(128, allocator); var chunkList = new UnsafeList<IntPtr>(128, allocator);
var chunkEntityCounts = new UnsafeList<int>(128, allocator); var chunkEntityCounts = new UnsafeList<int>(128, allocator);
@@ -1731,9 +1732,7 @@ public unsafe partial struct EntityQuery
// Iterate the Query's matching archetypes // Iterate the Query's matching archetypes
foreach (var archID in _matchingArchetypes) foreach (var archID in _matchingArchetypes)
{ {
ref var arch = ref World.GetWorld(_worldID) ref var arch = ref world.GetArchetypeReference(archID);
.GetValueOrThrow(ResultStatus.Success)
.GetArchetypeReference(archID);
if (arch.ChunkCount == 0) if (arch.ChunkCount == 0)
{ {
@@ -1820,7 +1819,7 @@ public unsafe partial struct EntityQuery
}; };
var jobHandle = scheduler.ScheduleParallel(ref runner, chunkList.Count, batchSize, dependency); var jobHandle = world.JobScheduler.ScheduleParallel(ref runner, chunkList.Count, batchSize, dependency);
// 3. Dispose the temp lists // 3. Dispose the temp lists
var disposeJob = new DisposeJobEntity7 var disposeJob = new DisposeJobEntity7
@@ -1852,7 +1851,7 @@ public unsafe partial struct EntityQuery
}; };
scheduler.Schedule(ref disposeJob, jobHandle); world.JobScheduler.Schedule(ref disposeJob, jobHandle);
return jobHandle; return jobHandle;
} }
@@ -1920,7 +1919,7 @@ public unsafe partial struct EntityQuery
} }
} }
public JobHandle ScheduleEntityParallel<TJob, T0, T1, T2, T3, T4, T5, T6, T7>(JobScheduler scheduler, TJob jobData, Allocator allocator, int batchSize, JobHandle dependency) public JobHandle ScheduleEntityParallel<TJob, T0, T1, T2, T3, T4, T5, T6, T7>(TJob jobData, Allocator allocator, int batchSize, JobHandle dependency)
where TJob : unmanaged, IJobEntityParallel<T0, T1, T2, T3, T4, T5, T6, T7> where TJob : unmanaged, IJobEntityParallel<T0, T1, T2, T3, T4, T5, T6, T7>
where T0 : unmanaged, IComponent where T0 : unmanaged, IComponent
where T1 : unmanaged, IComponent where T1 : unmanaged, IComponent
@@ -1931,6 +1930,8 @@ public unsafe partial struct EntityQuery
where T6 : unmanaged, IComponent where T6 : unmanaged, IComponent
where T7 : unmanaged, IComponent where T7 : unmanaged, IComponent
{ {
var world = World.GetWorld(_worldID).GetValueOrThrow(ResultStatus.Success);
// 1. Flatten the World // 1. Flatten the World
var chunkList = new UnsafeList<IntPtr>(128, allocator); var chunkList = new UnsafeList<IntPtr>(128, allocator);
var chunkEntityCounts = new UnsafeList<int>(128, allocator); var chunkEntityCounts = new UnsafeList<int>(128, allocator);
@@ -1963,9 +1964,7 @@ public unsafe partial struct EntityQuery
// Iterate the Query's matching archetypes // Iterate the Query's matching archetypes
foreach (var archID in _matchingArchetypes) foreach (var archID in _matchingArchetypes)
{ {
ref var arch = ref World.GetWorld(_worldID) ref var arch = ref world.GetArchetypeReference(archID);
.GetValueOrThrow(ResultStatus.Success)
.GetArchetypeReference(archID);
if (arch.ChunkCount == 0) if (arch.ChunkCount == 0)
{ {
@@ -2060,7 +2059,7 @@ public unsafe partial struct EntityQuery
}; };
var jobHandle = scheduler.ScheduleParallel(ref runner, chunkList.Count, batchSize, dependency); var jobHandle = world.JobScheduler.ScheduleParallel(ref runner, chunkList.Count, batchSize, dependency);
// 3. Dispose the temp lists // 3. Dispose the temp lists
var disposeJob = new DisposeJobEntity8 var disposeJob = new DisposeJobEntity8
@@ -2095,7 +2094,7 @@ public unsafe partial struct EntityQuery
}; };
scheduler.Schedule(ref disposeJob, jobHandle); world.JobScheduler.Schedule(ref disposeJob, jobHandle);
return jobHandle; return jobHandle;
} }

View File

@@ -100,10 +100,12 @@ public unsafe partial struct EntityQuery
} }
} }
public JobHandle ScheduleEntityParallel<TJob, <#= generics #>>(JobScheduler scheduler, TJob jobData, Allocator allocator, int batchSize, JobHandle dependency) public JobHandle ScheduleEntityParallel<TJob, <#= generics #>>(TJob jobData, Allocator allocator, int batchSize, JobHandle dependency)
where TJob : unmanaged, IJobEntityParallel<<#= generics #>> where TJob : unmanaged, IJobEntityParallel<<#= generics #>>
<#= restrictions #> <#= restrictions #>
{ {
var world = World.GetWorld(_worldID).GetValueOrThrow(ResultStatus.Success);
// 1. Flatten the World // 1. Flatten the World
var chunkList = new UnsafeList<IntPtr>(128, allocator); var chunkList = new UnsafeList<IntPtr>(128, allocator);
var chunkEntityCounts = new UnsafeList<int>(128, allocator); var chunkEntityCounts = new UnsafeList<int>(128, allocator);
@@ -117,9 +119,7 @@ public unsafe partial struct EntityQuery
// Iterate the Query's matching archetypes // Iterate the Query's matching archetypes
foreach (var archID in _matchingArchetypes) foreach (var archID in _matchingArchetypes)
{ {
ref var arch = ref World.GetWorld(_worldID) ref var arch = ref world.GetArchetypeReference(archID);
.GetValueOrThrow(ResultStatus.Success)
.GetArchetypeReference(archID);
if (arch.ChunkCount == 0) if (arch.ChunkCount == 0)
{ {
@@ -164,7 +164,7 @@ public unsafe partial struct EntityQuery
<# } #> <# } #>
}; };
var jobHandle = scheduler.ScheduleParallel(ref runner, chunkList.Count, batchSize, dependency); var jobHandle = world.JobScheduler.ScheduleParallel(ref runner, chunkList.Count, batchSize, dependency);
// 3. Dispose the temp lists // 3. Dispose the temp lists
var disposeJob = new DisposeJobEntity<#= i #> var disposeJob = new DisposeJobEntity<#= i #>
@@ -180,7 +180,7 @@ public unsafe partial struct EntityQuery
<# } #> <# } #>
}; };
scheduler.Schedule(ref disposeJob, jobHandle); world.JobScheduler.Schedule(ref disposeJob, jobHandle);
return jobHandle; return jobHandle;
} }

View File

@@ -1,21 +1,92 @@
namespace Ghost.Entities; namespace Ghost.Entities;
public delegate void ForEach<T0>(ref T0 t0Component); public delegate void ForEach<T0>(ref T0 component0)
public delegate void ForEach<T0, T1>(ref T0 t0Component,ref T1 t1Component); where T0 : unmanaged, IComponent;
public delegate void ForEach<T0, T1, T2>(ref T0 t0Component,ref T1 t1Component,ref T2 t2Component); public delegate void ForEach<T0, T1>(ref T0 component0, ref T1 component1)
public delegate void ForEach<T0, T1, T2, T3>(ref T0 t0Component,ref T1 t1Component,ref T2 t2Component,ref T3 t3Component); where T0 : unmanaged, IComponent
public delegate void ForEach<T0, T1, T2, T3, T4>(ref T0 t0Component,ref T1 t1Component,ref T2 t2Component,ref T3 t3Component,ref T4 t4Component); where T1 : unmanaged, IComponent;
public delegate void ForEach<T0, T1, T2, T3, T4, T5>(ref T0 t0Component,ref T1 t1Component,ref T2 t2Component,ref T3 t3Component,ref T4 t4Component,ref T5 t5Component); public delegate void ForEach<T0, T1, T2>(ref T0 component0, ref T1 component1, ref T2 component2)
public delegate void ForEach<T0, T1, T2, T3, T4, T5, T6>(ref T0 t0Component,ref T1 t1Component,ref T2 t2Component,ref T3 t3Component,ref T4 t4Component,ref T5 t5Component,ref T6 t6Component); where T0 : unmanaged, IComponent
public delegate void ForEach<T0, T1, T2, T3, T4, T5, T6, T7>(ref T0 t0Component,ref T1 t1Component,ref T2 t2Component,ref T3 t3Component,ref T4 t4Component,ref T5 t5Component,ref T6 t6Component,ref T7 t7Component); 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 t0Component); public delegate void ForEachWithEntity<T0>(Entity entity, ref T0 component0)
public delegate void ForEachWithEntity<T0, T1>(Entity entity, ref T0 t0Component,ref T1 t1Component); where T0 : unmanaged, IComponent;
public delegate void ForEachWithEntity<T0, T1, T2>(Entity entity, ref T0 t0Component,ref T1 t1Component,ref T2 t2Component); public delegate void ForEachWithEntity<T0, T1>(Entity entity, ref T0 component0, ref T1 component1)
public delegate void ForEachWithEntity<T0, T1, T2, T3>(Entity entity, ref T0 t0Component,ref T1 t1Component,ref T2 t2Component,ref T3 t3Component); where T0 : unmanaged, IComponent
public delegate void ForEachWithEntity<T0, T1, T2, T3, T4>(Entity entity, ref T0 t0Component,ref T1 t1Component,ref T2 t2Component,ref T3 t3Component,ref T4 t4Component); where T1 : unmanaged, IComponent;
public delegate void ForEachWithEntity<T0, T1, T2, T3, T4, T5>(Entity entity, ref T0 t0Component,ref T1 t1Component,ref T2 t2Component,ref T3 t3Component,ref T4 t4Component,ref T5 t5Component); public delegate void ForEachWithEntity<T0, T1, T2>(Entity entity, ref T0 component0, ref T1 component1, ref T2 component2)
public delegate void ForEachWithEntity<T0, T1, T2, T3, T4, T5, T6>(Entity entity, ref T0 t0Component,ref T1 t1Component,ref T2 t2Component,ref T3 t3Component,ref T4 t4Component,ref T5 t5Component,ref T6 t6Component); where T0 : unmanaged, IComponent
public delegate void ForEachWithEntity<T0, T1, T2, T3, T4, T5, T6, T7>(Entity entity, ref T0 t0Component,ref T1 t1Component,ref T2 t2Component,ref T3 t3Component,ref T4 t4Component,ref T5 t5Component,ref T6 t6Component,ref T7 t7Component); where 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

@@ -4,21 +4,24 @@
<#@ import namespace="System.Linq" #> <#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text" #> <#@ import namespace="System.Text" #>
<#@ include file="Helpers.ttinclude" #> <#@ include file="Helpers.ttinclude" #>
namespace Ghost.Entities; namespace Ghost.Entities;
<# for (var i = 1; i <= Amount; i++) <# for (var i = 1; i <= Amount; i++)
{ {
var generics = AppendGenerics(i); var generics = AppendParameters(i, "T{0}");
var compGenerics = AppendGenericRefParameters(i); var compGenerics = AppendParameters(i, "ref T{0} component{0}");
var restrictions = AppendGenericRestrictionsMultiline(i, "unmanaged, IComponent", 1);
#> #>
public delegate void ForEach<<#= generics #>>(<#= compGenerics #>); public delegate void ForEach<<#= generics #>>(<#= compGenerics #>)
<#= restrictions #>;
<# } #> <# } #>
<# for (var i = 1; i <= Amount; i++) <# for (var i = 1; i <= Amount; i++)
{ {
var generics = AppendGenerics(i); var generics = AppendParameters(i, "T{0}");
var compGenerics = AppendGenericRefParameters(i); var compGenerics = AppendParameters(i, "ref T{0} component{0}");
var restrictions = AppendGenericRestrictionsMultiline(i, "unmanaged, IComponent", 1);
#> #>
public delegate void ForEachWithEntity<<#= generics #>>(Entity entity, <#= compGenerics #>); public delegate void ForEachWithEntity<<#= generics #>>(Entity entity, <#= compGenerics #>)
<#= restrictions #>;
<# } #> <# } #>

View File

@@ -1,4 +1,5 @@
using Ghost.Core; using Ghost.Core;
using Misaki.HighPerformance.Jobs;
using Misaki.HighPerformance.LowLevel.Buffer; using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections; using Misaki.HighPerformance.LowLevel.Collections;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
@@ -14,18 +15,18 @@ public partial class World
public static int WorldCount => s_worlds.Count - s_freeWorldSlots.Count; public static int WorldCount => s_worlds.Count - s_freeWorldSlots.Count;
public static World Create(int entityCapacity = 16) public static World Create(JobScheduler jobScheduler, int entityCapacity = 16)
{ {
lock (s_worlds) lock (s_worlds)
{ {
if (s_freeWorldSlots.TryDequeue(out var index)) if (s_freeWorldSlots.TryDequeue(out var index))
{ {
s_worlds[index.value] = new World(index, entityCapacity); s_worlds[index.value] = new World(index, entityCapacity, jobScheduler);
} }
else else
{ {
index = new Identifier<World>(s_worlds.Count); index = new Identifier<World>(s_worlds.Count);
s_worlds.Add(new World(index, entityCapacity)); s_worlds.Add(new World(index, entityCapacity, jobScheduler));
} }
return s_worlds[index.value]!; return s_worlds[index.value]!;
@@ -67,6 +68,7 @@ public partial class World
public partial class World : IIdentifierType, IDisposable, IEquatable<World> public partial class World : IIdentifierType, IDisposable, IEquatable<World>
{ {
private readonly Identifier<World> _id; private readonly Identifier<World> _id;
private readonly JobScheduler _jobScheduler;
private readonly EntityManager _entityManager; private readonly EntityManager _entityManager;
private readonly EntityCommandBuffer _entityCommandBuffer; private readonly EntityCommandBuffer _entityCommandBuffer;
@@ -82,12 +84,14 @@ public partial class World : IIdentifierType, IDisposable, IEquatable<World>
internal int ArchetypeCount => _archetypes.Count; internal int ArchetypeCount => _archetypes.Count;
public Identifier<World> ID => _id; public Identifier<World> ID => _id;
public JobScheduler JobScheduler => _jobScheduler;
public EntityManager EntityManager => _entityManager; public EntityManager EntityManager => _entityManager;
public EntityCommandBuffer EntityCommandBuffer => _entityCommandBuffer; public EntityCommandBuffer EntityCommandBuffer => _entityCommandBuffer;
private World(Identifier<World> id, int entityCapacity) private World(Identifier<World> id, int entityCapacity, JobScheduler jobScheduler)
{ {
_id = id; _id = id;
_jobScheduler = jobScheduler;
_archetypes = new UnsafeList<Archetype>(16, Allocator.Persistent); _archetypes = new UnsafeList<Archetype>(16, Allocator.Persistent);
_entityQueries = new UnsafeList<EntityQuery>(16, Allocator.Persistent); _entityQueries = new UnsafeList<EntityQuery>(16, Allocator.Persistent);

View File

@@ -601,7 +601,7 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
uploadResource.Get()->Map(0, null, &pMappedData); uploadResource.Get()->Map(0, null, &pMappedData);
fixed (T* pData = data) fixed (T* pData = data)
{ {
MemoryUtility.MemCpy(pData, pMappedData, sizeInBytes); MemoryUtility.MemCpy(pMappedData, pData, sizeInBytes);
} }
uploadResource.Get()->Unmap(0, null); uploadResource.Get()->Unmap(0, null);

View File

@@ -1,6 +0,0 @@
{
"Logging":
{
"LogLevel": "Error"
}
}