forked from Misaki/GhostEngine
Update archtype ecs
This commit is contained in:
127
Ghost.ArcEntities/Archetype.cs
Normal file
127
Ghost.ArcEntities/Archetype.cs
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
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 Archetype : IDisposable
|
||||||
|
{
|
||||||
|
private UnsafeArray<int> _offsets;
|
||||||
|
private UnsafeArray<int> _componentIDs;
|
||||||
|
private UnsafeArray<int> _componentIDToOffset;
|
||||||
|
private int _entityCapacity;
|
||||||
|
private int _maxComponentID;
|
||||||
|
|
||||||
|
private UnsafeList<Chuck> _chunks;
|
||||||
|
|
||||||
|
public int EntityCapacity => _entityCapacity;
|
||||||
|
|
||||||
|
public Archetype(ReadOnlySpan<ComponentInfo> components)
|
||||||
|
{
|
||||||
|
_chunks = new UnsafeList<Chuck>(4, Allocator.Persistent);
|
||||||
|
CalculateLayout(components);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CalculateLayout(ReadOnlySpan<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;
|
||||||
|
_maxComponentID = 0;
|
||||||
|
for (var i = 0; i < components.Length; i++)
|
||||||
|
{
|
||||||
|
bytesPerEntity += components[i].size;
|
||||||
|
if (components[i].id > _maxComponentID)
|
||||||
|
{
|
||||||
|
_maxComponentID = components[i].id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_entityCapacity = Chuck.CHUNK_SIZE / bytesPerEntity;
|
||||||
|
_componentIDToOffset = new UnsafeArray<int>(_maxComponentID + 1, Allocator.Persistent);
|
||||||
|
for (var i = 0; i < _componentIDToOffset.Count; ++i) _componentIDToOffset[i] = -1;
|
||||||
|
|
||||||
|
while (_entityCapacity > 0)
|
||||||
|
{
|
||||||
|
var currentOffset = 0;
|
||||||
|
var fits = true;
|
||||||
|
|
||||||
|
currentOffset = (currentOffset + entityAlign - 1) & ~(entityAlign - 1);
|
||||||
|
currentOffset += _entityCapacity * entitySize;
|
||||||
|
|
||||||
|
var entityOffset = currentOffset;
|
||||||
|
var tempOffsets = new int[components.Length];
|
||||||
|
|
||||||
|
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++)
|
||||||
|
{
|
||||||
|
_componentIDToOffset[components[i].id] = tempOffsets[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_entityCapacity--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Chuck CreateChunk()
|
||||||
|
{
|
||||||
|
var chunk = new Chuck(Chuck.CHUNK_SIZE, _entityCapacity);
|
||||||
|
_chunks.Add(chunk);
|
||||||
|
|
||||||
|
return chunk;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public UnsafeArray<T> GetComponentArray<T>(int chunkIndex)
|
||||||
|
where T : unmanaged
|
||||||
|
{
|
||||||
|
var id = ComponentType<T>.id;
|
||||||
|
if (id >= _componentIDToOffset.Count)
|
||||||
|
{
|
||||||
|
return default;
|
||||||
|
}
|
||||||
|
|
||||||
|
var offset = _componentIDToOffset[id];
|
||||||
|
if (offset == -1)
|
||||||
|
{
|
||||||
|
return default;
|
||||||
|
}
|
||||||
|
|
||||||
|
var chunk = _chunks[chunkIndex];
|
||||||
|
return new UnsafeArray<T>((T*)((byte*)chunk.GetUnsafePtr() + offset), _entityCapacity);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_componentIDToOffset.Dispose();
|
||||||
|
|
||||||
|
foreach (var chunk in _chunks)
|
||||||
|
{
|
||||||
|
chunk.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
_chunks.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
4
Ghost.ArcEntities/AssemblyInfo.cs
Normal file
4
Ghost.ArcEntities/AssemblyInfo.cs
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
global using EntityID = System.Int32;
|
||||||
|
global using GenerationID = System.UInt32;
|
||||||
|
global using WorldID = System.UInt16;
|
||||||
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
namespace Ghost.ArcEntities;
|
|
||||||
|
|
||||||
public class Class1
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
66
Ghost.ArcEntities/Component.cs
Normal file
66
Ghost.ArcEntities/Component.cs
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
using Misaki.HighPerformance.LowLevel.Collections;
|
||||||
|
using Misaki.HighPerformance.LowLevel.Utilities;
|
||||||
|
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
namespace Ghost.ArcEntities;
|
||||||
|
|
||||||
|
internal struct ComponentInfo
|
||||||
|
{
|
||||||
|
public int size;
|
||||||
|
public int alignment;
|
||||||
|
public int id;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static unsafe class ComponentType<T>
|
||||||
|
where T : unmanaged
|
||||||
|
{
|
||||||
|
public static readonly int Size = sizeof(T);
|
||||||
|
public static readonly int Alignment = (int)MemoryUtility.AlignOf<T>();
|
||||||
|
public static readonly int id = ComponentRegister.s_nextComponentTypeID++;
|
||||||
|
|
||||||
|
public static ComponentInfo GetInfo()
|
||||||
|
{
|
||||||
|
return new ComponentInfo
|
||||||
|
{
|
||||||
|
size = Size,
|
||||||
|
alignment = Alignment,
|
||||||
|
id = id
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class ComponentRegister
|
||||||
|
{
|
||||||
|
internal static int s_nextComponentTypeID = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 => _count;
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
81
Ghost.ArcEntities/Entity.cs
Normal file
81
Ghost.ArcEntities/Entity.cs
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace Ghost.ArcEntities;
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential, Size = 8)]
|
||||||
|
public struct Entity : IEquatable<Entity>, IComparable<Entity>
|
||||||
|
{
|
||||||
|
public const EntityID INVALID_ID = -1;
|
||||||
|
|
||||||
|
private EntityID _id;
|
||||||
|
private GenerationID _generation;
|
||||||
|
|
||||||
|
public readonly EntityID ID
|
||||||
|
{
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
get => _id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly GenerationID Generation
|
||||||
|
{
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
get => _generation;
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
internal void IncrementGeneration() => _generation++;
|
||||||
|
|
||||||
|
public readonly bool Equals(Entity other)
|
||||||
|
{
|
||||||
|
return _id == other._id && _generation == other._generation;
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly int CompareTo(Entity other)
|
||||||
|
{
|
||||||
|
return _id.CompareTo(other._id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override readonly bool Equals(object? obj)
|
||||||
|
{
|
||||||
|
return obj is Entity other && Equals(other);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override readonly int GetHashCode()
|
||||||
|
{
|
||||||
|
return _id.GetHashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool operator ==(Entity left, Entity right)
|
||||||
|
{
|
||||||
|
return left.Equals(right);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool operator !=(Entity left, Entity right)
|
||||||
|
{
|
||||||
|
return !(left == right);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override readonly string ToString()
|
||||||
|
{
|
||||||
|
return $"Entity {{ Index: {ID}, Generation: {Generation} }}";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,6 +4,11 @@
|
|||||||
<TargetFramework>net10.0</TargetFramework>
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="../Ghost.Core/Ghost.Core.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
109
Ghost.ArcEntities/World.cs
Normal file
109
Ghost.ArcEntities/World.cs
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
namespace Ghost.ArcEntities;
|
||||||
|
|
||||||
|
public partial class World
|
||||||
|
{
|
||||||
|
private static List<World> s_worlds = new(4);
|
||||||
|
private static Queue<WorldID> s_freeWorldSlots = new();
|
||||||
|
|
||||||
|
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] = new World(index, entityCapacity);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (s_worlds.Count >= WorldID.MaxValue)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Maximum number of worlds reached");
|
||||||
|
}
|
||||||
|
|
||||||
|
index = (WorldID)s_worlds.Count;
|
||||||
|
s_worlds.Add(new World(index, entityCapacity));
|
||||||
|
}
|
||||||
|
|
||||||
|
return s_worlds[index];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static World GetWorld(int index)
|
||||||
|
{
|
||||||
|
return s_worlds[index];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public partial class World : IDisposable, IEquatable<World>
|
||||||
|
{
|
||||||
|
private readonly WorldID _id;
|
||||||
|
|
||||||
|
private bool _isDisposed = false;
|
||||||
|
|
||||||
|
public WorldID ID => _id;
|
||||||
|
|
||||||
|
private World(WorldID id, int entityCapacity)
|
||||||
|
{
|
||||||
|
_id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
~World()
|
||||||
|
{
|
||||||
|
Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Equals(World? other)
|
||||||
|
{
|
||||||
|
if (other is null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ReferenceEquals(this, other))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return _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 (_isDisposed)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
s_freeWorldSlots.Enqueue(_id);
|
||||||
|
|
||||||
|
_isDisposed = true;
|
||||||
|
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Reference in New Issue
Block a user