mirror of
https://github.com/aharabada/glitchy-engine-beef.git
synced 2026-09-05 21:01:52 +00:00
Implemented basic ECS
This commit is contained in:
@@ -0,0 +1,149 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace GlitchyEngine.Math
|
||||||
|
{
|
||||||
|
public class BitArray
|
||||||
|
{
|
||||||
|
const int BitsPerInt = sizeof(uint) * 8;
|
||||||
|
|
||||||
|
private uint* _bits ~ Free(_);
|
||||||
|
private int _intCount;
|
||||||
|
private int _capacity;
|
||||||
|
|
||||||
|
public int Capacity
|
||||||
|
{
|
||||||
|
get => _capacity;
|
||||||
|
set => EnsureCapacity(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public this(int initialCapacity = sizeof(uint))
|
||||||
|
{
|
||||||
|
EnsureCapacity(initialCapacity);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Inline]
|
||||||
|
static int IntCount(int bits)
|
||||||
|
{
|
||||||
|
int count = bits / BitsPerInt;
|
||||||
|
|
||||||
|
if(bits % BitsPerInt > 0)
|
||||||
|
count++;
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
[LinkName("free")]
|
||||||
|
static extern void Free(void* memoryBlock);
|
||||||
|
|
||||||
|
[LinkName("realloc")]
|
||||||
|
static extern void* Realloc(void* memoryBlock, int size);
|
||||||
|
|
||||||
|
private void EnsureCapacity(int requestedCapacity)
|
||||||
|
{
|
||||||
|
if(requestedCapacity <= _capacity)
|
||||||
|
return;
|
||||||
|
|
||||||
|
int newIntCount = IntCount(requestedCapacity);
|
||||||
|
|
||||||
|
if(_intCount >= newIntCount)
|
||||||
|
return;
|
||||||
|
|
||||||
|
int oldIntCount = _intCount;
|
||||||
|
_intCount = newIntCount;
|
||||||
|
_capacity = _intCount * BitsPerInt;
|
||||||
|
|
||||||
|
_bits = (.)Realloc(_bits, _intCount * sizeof(uint));
|
||||||
|
|
||||||
|
// Set new bits to 0
|
||||||
|
for(int i = oldIntCount; i < _intCount; i++)
|
||||||
|
{
|
||||||
|
_bits[i] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool this[int index]
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
Log.EngineLogger.AssertDebug(index >= 0);
|
||||||
|
|
||||||
|
if(index > _capacity)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
int arrayIndex = index / _capacity;
|
||||||
|
int bitIndex = index % _capacity;
|
||||||
|
|
||||||
|
return ((_bits[arrayIndex] >> bitIndex) & 1) == 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
set
|
||||||
|
{
|
||||||
|
Log.EngineLogger.AssertDebug(index >= 0);
|
||||||
|
|
||||||
|
if(index > _capacity)
|
||||||
|
EnsureCapacity(index);
|
||||||
|
|
||||||
|
int arrayIndex = index / _capacity;
|
||||||
|
int bitIndex = index % _capacity;
|
||||||
|
|
||||||
|
if(value)
|
||||||
|
{
|
||||||
|
_bits[arrayIndex] |= (1 << bitIndex);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// create a mask that is all 1 except for the bit at the specified index
|
||||||
|
uint mask = uint.MaxValue;
|
||||||
|
mask ^= (1 << bitIndex);
|
||||||
|
|
||||||
|
_bits[arrayIndex] &= mask;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets all bits to given value.
|
||||||
|
*/
|
||||||
|
public void Clear(bool value = false)
|
||||||
|
{
|
||||||
|
if(value)
|
||||||
|
{
|
||||||
|
for(int i < _intCount)
|
||||||
|
{
|
||||||
|
_bits[i] = (uint)-1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for(int i < _intCount)
|
||||||
|
{
|
||||||
|
_bits[i] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether or not this bitarray is 1 for every 1 in mask.
|
||||||
|
* i.e. mask == (this & mask)
|
||||||
|
*/
|
||||||
|
public bool MaskMatch(BitArray mask)
|
||||||
|
{
|
||||||
|
// The number of ints we can compare binary
|
||||||
|
int intCompares = Math.Min(mask._intCount, _intCount);
|
||||||
|
for(int i < intCompares)
|
||||||
|
{
|
||||||
|
if(mask._bits[i] != (_bits[i] & mask._bits[i]))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if mask has more integers than "this", these integers must be 0
|
||||||
|
for(int i = _intCount; i < mask._intCount; i++)
|
||||||
|
{
|
||||||
|
if(mask._bits[i] > 0)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace GlitchyEngine.World
|
||||||
|
{
|
||||||
|
public class ComponentPool
|
||||||
|
{
|
||||||
|
int _objectSize;
|
||||||
|
int _capacity;
|
||||||
|
|
||||||
|
int PoolSize => _objectSize * _capacity;
|
||||||
|
|
||||||
|
uint8* _rawData ~ delete _;
|
||||||
|
|
||||||
|
public this(int objectSize, int capacity)
|
||||||
|
{
|
||||||
|
_objectSize = objectSize;
|
||||||
|
_capacity = capacity;
|
||||||
|
|
||||||
|
_rawData = new uint8[_capacity * _objectSize]*;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Inline]
|
||||||
|
public void* Get(int index)
|
||||||
|
{
|
||||||
|
Log.EngineLogger.AssertDebug(index >= 0 && index < _capacity);
|
||||||
|
|
||||||
|
return _rawData + index * _objectSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,250 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using GlitchyEngine.Math;
|
||||||
|
|
||||||
|
using internal GlitchyEngine.World;
|
||||||
|
|
||||||
|
namespace GlitchyEngine.World
|
||||||
|
{
|
||||||
|
public class EcsWorld
|
||||||
|
{
|
||||||
|
const int MaxEntities = 1024;
|
||||||
|
|
||||||
|
typealias BitmaskEntry = (Entity ID, BitArray ComponentMask);
|
||||||
|
List<BitmaskEntry> _entities = new .();
|
||||||
|
|
||||||
|
List<uint32> _freeIndices = new List<uint32>() ~ delete _;
|
||||||
|
|
||||||
|
typealias ComponentPoolEntry = (uint32 Id, ComponentPool Pool);
|
||||||
|
Dictionary<Type, ComponentPoolEntry> _componentPools = new .();
|
||||||
|
|
||||||
|
public ~this()
|
||||||
|
{
|
||||||
|
for(var entry in _componentPools)
|
||||||
|
{
|
||||||
|
delete entry.value.Pool;
|
||||||
|
}
|
||||||
|
|
||||||
|
delete _componentPools;
|
||||||
|
|
||||||
|
for(var entry in _entities)
|
||||||
|
{
|
||||||
|
delete entry.ComponentMask;
|
||||||
|
}
|
||||||
|
|
||||||
|
delete _entities;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers a new Component.
|
||||||
|
*/
|
||||||
|
public void Register<T>() where T: struct
|
||||||
|
{
|
||||||
|
_componentPools.Add(typeof(T), ((uint32)_componentPools.Count, new ComponentPool(sizeof(T), MaxEntities)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new Entity and returns its ID.
|
||||||
|
*/
|
||||||
|
public Entity NewEntity()
|
||||||
|
{
|
||||||
|
Entity entity;
|
||||||
|
|
||||||
|
// Reuse freed entity slot
|
||||||
|
if(_freeIndices.Count > 0)
|
||||||
|
{
|
||||||
|
uint32 index = _freeIndices.PopBack();
|
||||||
|
|
||||||
|
entity = Entity.CreateEntityID(index, _entities[index].ID.Version);
|
||||||
|
|
||||||
|
_entities[index].ID = entity;
|
||||||
|
}
|
||||||
|
// Create new entity slot
|
||||||
|
else
|
||||||
|
{
|
||||||
|
entity = Entity.CreateEntityID((.)_entities.Count, 0);
|
||||||
|
_entities.Add((entity, new BitArray(_componentPools.Count)));
|
||||||
|
}
|
||||||
|
|
||||||
|
return entity;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the specified Entity from the World.
|
||||||
|
*/
|
||||||
|
public void RemoveEntity(Entity entity)
|
||||||
|
{
|
||||||
|
var listEntity = ref _entities[entity.Index];
|
||||||
|
if(entity != listEntity.ID)
|
||||||
|
return;
|
||||||
|
|
||||||
|
listEntity.ID = Entity.CreateEntityID(Entity.InvalidEntity.Index, entity.Version + 1);
|
||||||
|
|
||||||
|
_entities[entity.Index].ComponentMask.Clear();
|
||||||
|
_freeIndices.Add(entity.Index);
|
||||||
|
|
||||||
|
// TODO: add "destructor" for components
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assigns a component of type T to the specified entity and returns it.
|
||||||
|
*/
|
||||||
|
public T* AssignComponent<T>(Entity entity) where T : struct
|
||||||
|
{
|
||||||
|
if(entity.Index > _entities.Count)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
var listEntity = ref _entities[entity.Index];
|
||||||
|
|
||||||
|
if(entity != listEntity.ID)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
ComponentPoolEntry entry;
|
||||||
|
if(!_componentPools.TryGetValue(typeof(T), out entry))
|
||||||
|
{
|
||||||
|
entry = ((uint32)_componentPools.Count, new ComponentPool(sizeof(T), MaxEntities));
|
||||||
|
|
||||||
|
_componentPools.Add(typeof(T), entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: maybe assert?
|
||||||
|
if(listEntity.ComponentMask[entry.Id])
|
||||||
|
return null;
|
||||||
|
|
||||||
|
listEntity.ComponentMask[entry.Id] = true;
|
||||||
|
|
||||||
|
return (.)entry.Pool.Get(entity.Index);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes a component of type T from the specified entity.
|
||||||
|
*/
|
||||||
|
public void RemoveComponent<T>(Entity entity) where T : struct
|
||||||
|
{
|
||||||
|
if(entity.Index > _entities.Count)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var listEntity = ref _entities[entity.Index];
|
||||||
|
if(entity != listEntity.ID)
|
||||||
|
return;
|
||||||
|
|
||||||
|
ComponentPoolEntry entry;
|
||||||
|
if(!_componentPools.TryGetValue(typeof(T), out entry))
|
||||||
|
return;
|
||||||
|
|
||||||
|
// TODO: maybe assert?
|
||||||
|
if(!listEntity.ComponentMask[entry.Id])
|
||||||
|
return;
|
||||||
|
|
||||||
|
listEntity.ComponentMask[entry.Id] = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public T* GetComponent<T>(Entity entity) where T : struct
|
||||||
|
{
|
||||||
|
var listEntity = ref _entities[entity.Index];
|
||||||
|
if(entity != listEntity.ID)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
ComponentPoolEntry entry;
|
||||||
|
if(!_componentPools.TryGetValue(typeof(T), out entry))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
// TODO: maybe assert?
|
||||||
|
if(!listEntity.ComponentMask[entry.Id])
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return (.)entry.Pool.Get(entity.Index);
|
||||||
|
}
|
||||||
|
|
||||||
|
public WorldEnumerator Enumerate(params Type[] componentTypes)
|
||||||
|
{
|
||||||
|
return WorldEnumerator(this, componentTypes);
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct WorldEnumerator : IEnumerator<Entity>, IDisposable
|
||||||
|
{
|
||||||
|
private EcsWorld _world;
|
||||||
|
private BitArray _bitMask;
|
||||||
|
private BitmaskEntry* _currentEntry;
|
||||||
|
private BitmaskEntry* _endEntry;
|
||||||
|
|
||||||
|
public this(EcsWorld world, Type[] componentTypes)
|
||||||
|
{
|
||||||
|
_world = world;
|
||||||
|
_currentEntry = _world._entities.Ptr;
|
||||||
|
_endEntry = _world._entities.Ptr + _world._entities.Count;
|
||||||
|
|
||||||
|
_bitMask = new BitArray(_world._componentPools.Count);
|
||||||
|
for(var type in componentTypes)
|
||||||
|
{
|
||||||
|
Log.EngineLogger.AssertDebug(type.IsStruct, "Components can only be structs.");
|
||||||
|
|
||||||
|
var result = _world._componentPools.GetValue(type);
|
||||||
|
|
||||||
|
if(result case .Ok(let entry))
|
||||||
|
{
|
||||||
|
_bitMask[entry.Id] = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Log.EngineLogger.AssertDebug(false, "Queried component is not registered for this world. This is invalid because the query would never return any results.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Result<Entity> GetNext() mut
|
||||||
|
{
|
||||||
|
while(_currentEntry < _endEntry)
|
||||||
|
{
|
||||||
|
BitmaskEntry* entry = _currentEntry++;
|
||||||
|
// Check whether or not mask matches
|
||||||
|
if(entry.ComponentMask.MaskMatch(_bitMask))
|
||||||
|
return entry.ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
return .Err;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
delete _bitMask;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Test()
|
||||||
|
{
|
||||||
|
EcsWorld world = new .();
|
||||||
|
|
||||||
|
world.Register<TransformComponent>();
|
||||||
|
|
||||||
|
Entity entity = world.NewEntity();
|
||||||
|
|
||||||
|
TransformComponent* myComp = world.AssignComponent<TransformComponent>(entity);
|
||||||
|
myComp.Transform = Matrix.Identity;
|
||||||
|
|
||||||
|
TransformComponent gotComp = *world.GetComponent<TransformComponent>(entity);
|
||||||
|
|
||||||
|
world.RemoveComponent<TransformComponent>(entity);
|
||||||
|
|
||||||
|
Entity entity2 = world.NewEntity();
|
||||||
|
world.AssignComponent<TransformComponent>(entity2);
|
||||||
|
|
||||||
|
world.RemoveEntity(entity);
|
||||||
|
|
||||||
|
entity = world.NewEntity();
|
||||||
|
|
||||||
|
world.RemoveEntity(entity);
|
||||||
|
world.RemoveComponent<TransformComponent>(entity);
|
||||||
|
|
||||||
|
entity = world.NewEntity();
|
||||||
|
|
||||||
|
for(let forenty in world.Enumerate(typeof(TransformComponent)))
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
delete world;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
using internal GlitchyEngine.World;
|
||||||
|
|
||||||
|
namespace GlitchyEngine.World
|
||||||
|
{
|
||||||
|
public struct Entity : uint64
|
||||||
|
{
|
||||||
|
// Binary Format:
|
||||||
|
// Bits: [0 - 31] [32 - 64]
|
||||||
|
// Data: Version Index
|
||||||
|
|
||||||
|
[Inline]
|
||||||
|
internal uint32 Version => (uint32)this;
|
||||||
|
|
||||||
|
[Inline]
|
||||||
|
internal uint32 Index => (uint32)(this >> 32);
|
||||||
|
|
||||||
|
[Inline]
|
||||||
|
static internal Entity CreateEntityID(uint32 index, uint32 version)
|
||||||
|
{
|
||||||
|
return ((uint64)index << 32) | version;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Inline]
|
||||||
|
internal bool IsValid => Index != InvalidEntity.Index;
|
||||||
|
|
||||||
|
public const Entity InvalidEntity = ((uint64)uint32.MaxValue << 32) | 0;//TODO: Report bug: CreateEntityID(uint32.MaxValue, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
using GlitchyEngine.Math;
|
||||||
|
|
||||||
|
namespace GlitchyEngine.World
|
||||||
|
{
|
||||||
|
public struct TransformComponent
|
||||||
|
{
|
||||||
|
static int _id;
|
||||||
|
|
||||||
|
public static int ID {get => _id; set => _id = value; }
|
||||||
|
|
||||||
|
internal Matrix _transform;
|
||||||
|
|
||||||
|
public Matrix Transform
|
||||||
|
{
|
||||||
|
get => _transform;
|
||||||
|
set mut => _transform = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user