You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

201 lines
6.4 KiB

2 years ago
using System;
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
using System.Diagnostics;
namespace GameCore.TinyECS
{
public partial struct EntityDataBlockMeta
{
public FixedList64Bytes<byte> componentIdToIndex;
public FixedList128Bytes<short> componentIdToOffset;
public int elementSize;
public int elementAlign;
}
public unsafe struct EntityDataBlockChain : IDisposable
{
public readonly EntityDataBlockMeta blockMeta;
private FixedList512Bytes<IntPtr> blockPtrList;
private int maxElementCountPerBlock;
private int elementCount;
public int BlockCount => blockPtrList.Length;
public int BlockCapacity => blockPtrList.Capacity;
public int ElementCount => elementCount;
public int MaxElementCount => MaxElementCountPerBlock * BlockCapacity;
public int MaxElementCountPerBlock => maxElementCountPerBlock;
public bool IsFull => elementCount == MaxElementCount;
public bool IsEmpty => elementCount == 0;
public EntityDataBlockChain(EntityDataBlockMeta blockMeta, int maxElementCountPerBlock)
{
this.blockMeta = blockMeta;
blockPtrList = default;
this.maxElementCountPerBlock = maxElementCountPerBlock;
elementCount = 0;
}
public void Dispose()
{
for (var i = BlockCount - 1; i >= 0; --i)
{
ReleaseBlock(i);
}
}
void AllocBlock()
{
var bufferSize = blockMeta.elementSize * maxElementCountPerBlock;
var ptr = UnsafeUtility.Malloc(bufferSize, blockMeta.elementAlign, Allocator.Persistent);
blockPtrList.Add((IntPtr)ptr);
}
void ReleaseBlock(int blockIndex)
{
var pBlock = (void*)blockPtrList[blockIndex];
if (pBlock != null)
{
UnsafeUtility.Free(pBlock, Allocator.Persistent);
blockPtrList.RemoveAt(blockIndex);
}
}
void* ElementPtrAt(int elementIndex)
{
var blockIndex = elementIndex / maxElementCountPerBlock;
var pBlock = blockPtrList[blockIndex];
return (byte*)pBlock + elementIndex * blockMeta.elementSize;
}
void* ComponentPtrAt<T>(int elementIndex) where T : unmanaged
{
var componentId = ComponentId.Get<T>();
var componentIndex = blockMeta.componentIdToIndex[componentId];
if (componentIndex > 0)
{
var pElement = ElementPtrAt(elementIndex);
return (byte*)pElement + blockMeta.componentIdToOffset[componentId];
}
return null;
}
[Conditional("DEBUG")]
void CheckElementIndex(int index)
{
if (index < 0 || index >= elementCount)
throw new IndexOutOfRangeException($"Invalid index: {index}");
}
[Conditional("DEBUG")]
void CheckBlockIndex(int index)
{
if (index < 0 || index > (elementCount / maxElementCountPerBlock))
throw new IndexOutOfRangeException($"Invalid index: {index}");
}
public void Clear()
{
elementCount = 0;
}
public void Add()
{
if (elementCount % maxElementCountPerBlock == 0)
{
if (IsFull)
throw new OutOfMemoryException("Block chain is full!");
var blockIndex = elementCount / maxElementCountPerBlock;
if (blockIndex >= BlockCount)
AllocBlock();
}
void* ptr = ElementPtrAt(elementCount);
UnsafeUtility.MemClear(ptr, blockMeta.elementSize);
elementCount++;
}
public void Remove(int index)
{
CheckElementIndex(index);
if (index != elementCount - 1)
{
var src = ElementPtrAt(elementCount - 1);
var dst = ElementPtrAt(index);
UnsafeUtility.MemCpy(dst, src, blockMeta.elementSize);
}
elementCount--;
}
public ref T GetData<T>(int index) where T : unmanaged
{
CheckElementIndex(index);
var pComponent = ComponentPtrAt<T>(index);
if (pComponent == null)
throw new Exception($"Invalid component type: {typeof(T)}");
return ref UnsafeUtility.AsRef<T>(pComponent);
}
public EntityDataBlockView AsBlockView(int blockIndex)
{
CheckBlockIndex(blockIndex);
var ptr = (void*)blockPtrList[blockIndex];
return new(ptr, blockMeta, maxElementCountPerBlock * blockIndex, elementCount);
}
}
public unsafe readonly struct EntityDataBlockView
{
private readonly void* ptr;
public readonly EntityDataBlockMeta meta;
public readonly int elementStartIndex;
public readonly int elementCount;
public EntityDataBlockView(void* ptr, EntityDataBlockMeta meta, int elementStartIndex, int elementCount)
{
this.ptr = ptr;
this.meta = meta;
this.elementStartIndex = elementStartIndex;
this.elementCount = elementCount;
}
void* ElementPtrAt(int elementIndex)
{
return (byte*)ptr + elementIndex * meta.elementSize;
}
void* ComponentPtrAt<T>(int elementIndex) where T : unmanaged
{
var componentId = ComponentId.Get<T>();
var componentIndex = meta.componentIdToIndex[componentId];
if (componentIndex > 0)
{
var pElement = ElementPtrAt(elementIndex);
return (byte*)pElement + meta.componentIdToOffset[componentId];
}
return null;
}
[Conditional("DEBUG")]
void CheckIndex(int index)
{
if (index < elementStartIndex || index >= elementStartIndex + elementCount)
throw new IndexOutOfRangeException($"Invalid index: {index}");
}
public ref T GetData<T>(int index) where T : unmanaged
{
var pComponent = ComponentPtrAt<T>(index);
if (pComponent == null)
throw new Exception($"Invalid component type: {typeof(T)}");
return ref UnsafeUtility.AsRef<T>(pComponent);
}
}
}