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