using System; using System.Collections; using GlitchyEngine.Math; using internal GlitchyEngine.World; namespace GlitchyEngine.World { public class EcsWorld { const int MaxEntities = 16348; internal typealias BitmaskEntry = (EcsEntity ID, BitArray ComponentMask); internal List _entities = new .(); List _freeIndices = new List() ~ delete _; typealias DisposeFunction = function void(void* component); internal typealias ComponentPoolEntry = (uint32 Id, IComponentPool Pool, DisposeFunction DisposeComponent); internal Dictionary _componentPools = new .(); /// A list containing all pools whose components need to be disposed before removal. List _disposingPools = new .() ~ delete _; public ~this() { for(var entry in _entities) { DisposeComponents(entry); delete entry.ComponentMask; } delete _entities; for(var entry in _componentPools) { delete entry.value.Pool; } delete _componentPools; } /** * Registers a new Component. */ public void Register() where T: struct, new { uint32 id = (uint32)_componentPools.Count; ComponentPool componentPool = new ComponentPool(MaxEntities); _componentPools.Add(typeof(T), (id, componentPool, null)); } /** * Registers a new Component. */ public void Register() where T: struct, new, IDisposableComponent { uint32 id = (uint32)_componentPools.Count; ComponentPool componentPool = new ComponentPool(MaxEntities); DisposeFunction disposeFunction = => DisposeComponent; _componentPools.Add(typeof(T), (id, componentPool, disposeFunction)); // Add to list of components that need disposing _disposingPools.Add(typeof(T)); } /// Enables us to bind a function pointer to the components dispose function private static void DisposeComponent(void* component) where T : IDisposableComponent { T* myComponent = (T*)component; (*myComponent).Dispose(); } /** @brief Returns the component pool for the given component type. * @param TComponent The type of the component whose component pool will be returned. * @returns A reference to the component pool. */ [Inline] internal ref ComponentPoolEntry GetComponentPool() { return ref _componentPools[typeof(TComponent)]; } /** * Creates a new Entity and returns its ID. */ public EcsEntity NewEntity() { EcsEntity entity; // Reuse freed entity slot if(_freeIndices.Count > 0) { uint32 index = _freeIndices.PopBack(); entity = EcsEntity.CreateEntityID(index, _entities[index].ID.Version); _entities[index].ID = entity; } // Create new entity slot else { entity = EcsEntity.CreateEntityID((.)_entities.Count, 0); _entities.Add((entity, new BitArray(_componentPools.Count))); } return entity; } /** * Removes the specified Entity from the World. */ public void RemoveEntity(EcsEntity entity) { var listEntity = ref _entities[entity.Index]; if(entity != listEntity.ID) return; DisposeComponents(listEntity); // Invalidate entry and increment version listEntity.ID = EcsEntity.CreateEntityID(EcsEntity.InvalidEntity.Index, entity.Version + 1); _entities[entity.Index].ComponentMask.Clear(); _freeIndices.Add(entity.Index); } /// Disposes all components of the given entity. private void DisposeComponents(BitmaskEntry listEntity) { var componentMask = listEntity.ComponentMask; for(let poolType in _disposingPools) { var pool = _componentPools[poolType]; // Check if the entity has this component type if(componentMask[pool.Id]) { // Get pointer to component void* component = pool.Pool.Get(listEntity.ID.Index); // Dispose component pool.DisposeComponent(component); } } } /// Returns whether the given entity is valid or not. public bool IsValid(EcsEntity entity) { if(entity.Index > _entities.Count) return false; var listEntity = ref _entities[entity.Index]; return entity == listEntity.ID; } /// Returns the entity with the same ID and the current version. Or null, if no such entity exists. public Result GetCurrentVersion(EcsEntity entity) { if(entity.Index > _entities.Count) return .Err; var listEntity = ref _entities[entity.Index]; return listEntity.ID; } /** * Assigns a component of type T to the specified entity and returns it. */ public T* AssignComponent(EcsEntity entity, T value = T()) where T : struct, new { 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)) { Register(); entry = _componentPools[typeof(T)]; //Log.EngineLogger.AssertDebug(false, scope $"Tried to assign unregistered component type {typeof(T)}"); //return null; } // TODO: is it a problem to assign a component again? //if(listEntity.ComponentMask[entry.Id]) // return null; listEntity.ComponentMask[entry.Id] = true; T* component = (T*)entry.Pool.Get(entity.Index); *component = value; return component; } /** * Removes a component of type T from the specified entity. */ public void RemoveComponent(EcsEntity entity) where T : struct, new { 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 void RemoveComponent(EcsEntity entity) where T : struct, new, IDisposableComponent { 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; // Get component and call dispose DisposeComponent(entry.Pool.Get(entity.Index)); } /// Returns whether or not the given entity has the specified component. public bool HasComponent(EcsEntity entity) where T : struct, new { var listEntity = ref _entities[entity.Index]; if(entity != listEntity.ID) return false; ComponentPoolEntry entry; if(!_componentPools.TryGetValue(typeof(T), out entry)) return false; return listEntity.ComponentMask[entry.Id]; } public T* GetComponent(EcsEntity entity) where T : struct, new { 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 WorldEnumerator Enumerate() where TComponent : struct { return WorldEnumerator(this); } public WorldEnumerator Enumerate() where TComponent0 : struct where TComponent1 : struct { return WorldEnumerator(this); } public WorldEnumerator Enumerate() where TComponent0 : struct where TComponent1 : struct where TComponent2 : struct { return WorldEnumerator(this); } public WorldEnumerator Enumerate() where TComponent0 : struct where TComponent1 : struct where TComponent2 : struct where TComponent3 : struct { return WorldEnumerator(this); } public static void Test() { EcsWorld world = new .(); world.Register(); EcsEntity entity = world.NewEntity(); TransformComponent* myComp = world.AssignComponent(entity); myComp.LocalTransform = Matrix.Identity; #unwarn TransformComponent gotComp = *world.GetComponent(entity); world.RemoveComponent(entity); EcsEntity 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; } /// A component to test automatic disposing of components. private struct TestDisposingComponent : IDisposableComponent { public bool IsDisposed; public void Dispose() mut { IsDisposed = true; } } /// Tets if disposing of components works correctly. [Test] public static void TestDisposing() { EcsWorld world = scope EcsWorld(); world.Register(); // Test dispose on component removal { // Create entity with disposable component. EcsEntity entity = world.NewEntity(); var component = world.AssignComponent(entity); component.IsDisposed = false; world.RemoveComponent(entity); Test.Assert(component.IsDisposed == true, "The component was not disposed when the component was removed!"); } // Test dispose on entity removal { // Create entity with disposable component. EcsEntity entity = world.NewEntity(); var component = world.AssignComponent(entity); component.IsDisposed = false; world.RemoveEntity(entity); Test.Assert(component.IsDisposed == true, "The component was not disposed when the entity was removed!"); } } } }