using System; using System.Collections.Generic; namespace MoonTools.ECS { public class World { internal readonly static TypeIndices ComponentTypeIndices = new TypeIndices(); internal readonly static TypeIndices RelationTypeIndices = new TypeIndices(); internal readonly EntityStorage EntityStorage = new EntityStorage(); internal readonly ComponentDepot ComponentDepot; internal readonly MessageDepot MessageDepot = new MessageDepot(); internal readonly RelationDepot RelationDepot; internal readonly FilterStorage FilterStorage; public FilterBuilder FilterBuilder => new FilterBuilder(FilterStorage, ComponentTypeIndices); public World() { ComponentDepot = new ComponentDepot(ComponentTypeIndices); RelationDepot = new RelationDepot(EntityStorage, RelationTypeIndices); FilterStorage = new FilterStorage(EntityStorage, ComponentTypeIndices); } public Entity CreateEntity(string tag = "") { return EntityStorage.Create(tag); } public void Tag(Entity entity, string tag) { EntityStorage.Tag(entity, tag); } public string GetTag(Entity entity) { return EntityStorage.Tag(entity); } public void Set(Entity entity, in TComponent component) where TComponent : unmanaged { #if DEBUG // check for use after destroy if (!EntityStorage.Exists(entity)) { throw new InvalidOperationException("This entity is not valid!"); } #endif ComponentDepot.Set(entity.ID, component); if (EntityStorage.SetComponent(entity.ID, ComponentTypeIndices.GetIndex())) { FilterStorage.Check(entity.ID); } } // untyped version for Transfer // no filter check because filter state is copied directly internal unsafe void Set(Entity entity, int componentTypeIndex, void* component) { ComponentDepot.Set(entity.ID, componentTypeIndex, component); EntityStorage.SetComponent(entity.ID, componentTypeIndex); } public void Remove(in Entity entity) where TComponent : unmanaged { if (EntityStorage.RemoveComponent(entity.ID, ComponentTypeIndices.GetIndex())) { // Run filter storage update first so that the entity state is still valid in the remove callback. FilterStorage.Check(entity.ID); ComponentDepot.Remove(entity.ID); } } public void Relate(in Entity entityA, in Entity entityB, TRelationKind relationData) where TRelationKind : unmanaged { RelationDepot.Set(entityA, entityB, relationData); var relationTypeIndex = RelationTypeIndices.GetIndex(); EntityStorage.AddRelationKind(entityA.ID, relationTypeIndex); EntityStorage.AddRelationKind(entityB.ID, relationTypeIndex); } public void Unrelate(in Entity entityA, in Entity entityB) where TRelationKind : unmanaged { var (aEmpty, bEmpty) = RelationDepot.Remove(entityA, entityB); if (aEmpty) { EntityStorage.RemoveRelation(entityA.ID, RelationTypeIndices.GetIndex()); } if (bEmpty) { EntityStorage.RemoveRelation(entityB.ID, RelationTypeIndices.GetIndex()); } } public void UnrelateAll(in Entity entity) where TRelationKind : unmanaged { RelationDepot.UnrelateAll(entity.ID); EntityStorage.RemoveRelation(entity.ID, RelationTypeIndices.GetIndex()); } public void Send(in TMessage message) where TMessage : unmanaged { MessageDepot.Add(message); } public void Send(in Entity entity, in TMessage message) where TMessage : unmanaged { MessageDepot.Add(entity.ID, message); } public void Destroy(in Entity entity) { foreach (var componentTypeIndex in EntityStorage.ComponentTypeIndices(entity.ID)) { // Run filter storage update first so that the entity state is still valid in the remove callback. FilterStorage.RemoveEntity(entity.ID, componentTypeIndex); ComponentDepot.Remove(entity.ID, componentTypeIndex); } foreach (var relationTypeIndex in EntityStorage.RelationTypeIndices(entity.ID)) { RelationDepot.UnrelateAll(entity.ID, relationTypeIndex); EntityStorage.RemoveRelation(entity.ID, relationTypeIndex); } EntityStorage.Destroy(entity); } public void FinishUpdate() { MessageDepot.Clear(); } public void Clear() { EntityStorage.Clear(); MessageDepot.Clear(); RelationDepot.Clear(); ComponentDepot.Clear(); FilterStorage.Clear(); } private Dictionary WorldToTransferID = new Dictionary(); /// /// If you are using the World Transfer feature, call this once after your systems/filters have all been initialized. /// public void PrepareTransferTo(World other) { other.FilterStorage.CreateMissingStorages(FilterStorage); } // FIXME: there's probably a better way to handle Filters so they are not world-bound public unsafe void Transfer(World other, Filter filter, Filter otherFilter) { WorldToTransferID.Clear(); other.ComponentDepot.CreateMissingStorages(ComponentDepot); other.RelationDepot.CreateMissingStorages(RelationDepot); // destroy all entities matching the filter foreach (var entity in otherFilter.Entities) { other.Destroy(entity); } // create entities foreach (var entity in filter.Entities) { var otherWorldEntity = other.CreateEntity(GetTag(entity)); WorldToTransferID.Add(entity.ID, otherWorldEntity.ID); } // transfer relations RelationDepot.TransferStorage(WorldToTransferID, other.RelationDepot); // set components foreach (var entity in filter.Entities) { var otherWorldEntity = WorldToTransferID[entity.ID]; foreach (var componentTypeIndex in EntityStorage.ComponentTypeIndices(entity.ID)) { other.Set(otherWorldEntity, componentTypeIndex, ComponentDepot.UntypedGet(entity.ID, componentTypeIndex)); } } // transfer filters last so callbacks trigger correctly FilterStorage.TransferStorage(WorldToTransferID, other.FilterStorage); } } }