diff --git a/GlitchyEngine/src/Math/BitArray.bf b/GlitchyEngine/src/Math/BitArray.bf new file mode 100644 index 0000000..4f3b4ee --- /dev/null +++ b/GlitchyEngine/src/Math/BitArray.bf @@ -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; + } + } +} diff --git a/GlitchyEngine/src/World/ComponentPool.bf b/GlitchyEngine/src/World/ComponentPool.bf new file mode 100644 index 0000000..e8c4678 --- /dev/null +++ b/GlitchyEngine/src/World/ComponentPool.bf @@ -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; + } + } +} diff --git a/GlitchyEngine/src/World/EcsWorld.bf b/GlitchyEngine/src/World/EcsWorld.bf new file mode 100644 index 0000000..c034cf4 --- /dev/null +++ b/GlitchyEngine/src/World/EcsWorld.bf @@ -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 _entities = new .(); + + List _freeIndices = new List() ~ delete _; + + typealias ComponentPoolEntry = (uint32 Id, ComponentPool Pool); + Dictionary _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() 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(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(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(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, 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 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(); + + Entity entity = world.NewEntity(); + + TransformComponent* myComp = world.AssignComponent(entity); + myComp.Transform = Matrix.Identity; + + TransformComponent gotComp = *world.GetComponent(entity); + + world.RemoveComponent(entity); + + Entity entity2 = world.NewEntity(); + world.AssignComponent(entity2); + + world.RemoveEntity(entity); + + entity = world.NewEntity(); + + world.RemoveEntity(entity); + world.RemoveComponent(entity); + + entity = world.NewEntity(); + + for(let forenty in world.Enumerate(typeof(TransformComponent))) + { + + } + + delete world; + } + } +} diff --git a/GlitchyEngine/src/World/Entity.bf b/GlitchyEngine/src/World/Entity.bf new file mode 100644 index 0000000..097eeb0 --- /dev/null +++ b/GlitchyEngine/src/World/Entity.bf @@ -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); + } +} diff --git a/GlitchyEngine/src/World/TransformComponent.bf b/GlitchyEngine/src/World/TransformComponent.bf new file mode 100644 index 0000000..0ba7758 --- /dev/null +++ b/GlitchyEngine/src/World/TransformComponent.bf @@ -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; + } + } +}