using System; using System.Collections.Generic; using System.Runtime.CompilerServices; using MoonTools.ECS.Collections; namespace MoonTools.ECS { public class World { // Get TypeId from a Type private readonly Dictionary TypeToId = new Dictionary(); #if DEBUG private Dictionary IdToType = new Dictionary(); #endif // Get element size from a TypeId private readonly Dictionary ElementSizes = new Dictionary(); // Archetypes internal readonly Dictionary ArchetypeIndex = new Dictionary(); internal readonly Dictionary EntityIndex = new Dictionary(); internal readonly Archetype EmptyArchetype; // TODO: can we make the tag an native array of chars at some point? internal Dictionary EntityTags = new Dictionary(); // Relation Storages internal Dictionary RelationIndex = new Dictionary(); internal Dictionary> EntityRelationIndex = new Dictionary>(); // Message Storages private Dictionary MessageIndex = new Dictionary(); public FilterBuilder FilterBuilder => new FilterBuilder(this); internal readonly Dictionary ComponentIndex = new Dictionary(); internal IdAssigner EntityIdAssigner = new IdAssigner(); private IdAssigner TypeIdAssigner = new IdAssigner(); public World() { EmptyArchetype = CreateArchetype(ArchetypeSignature.Empty); } internal TypeId GetTypeId() where T : unmanaged { if (TypeToId.ContainsKey(typeof(T))) { return TypeToId[typeof(T)]; } var typeId = new TypeId(TypeIdAssigner.Assign()); TypeToId.Add(typeof(T), typeId); ElementSizes.Add(typeId, Unsafe.SizeOf()); #if DEBUG IdToType.Add(typeId, typeof(T)); #endif return typeId; } [MethodImpl(MethodImplOptions.AggressiveInlining)] private ComponentStorage GetComponentStorage() where T : unmanaged { var typeId = GetTypeId(); if (ComponentIndex.TryGetValue(typeId, out var componentStorage)) { return componentStorage; } componentStorage = new ComponentStorage(ElementSizes[typeId]); ComponentIndex.Add(typeId, componentStorage); return componentStorage; } private Archetype CreateArchetype(ArchetypeSignature signature) { var archetype = new Archetype(this, signature); ArchetypeIndex.Add(signature, archetype); return archetype; } // ENTITIES public Entity CreateEntity(string tag = "") { var entity = new Entity(EntityIdAssigner.Assign()); EntityIndex.Add(entity, new ArchetypeRecord(EmptyArchetype, EmptyArchetype.Count)); EmptyArchetype.Append(entity); return entity; } public void Tag(Entity entity, string tag) { EntityTags[entity] = tag; } public string GetTag(Entity entity) { return EntityTags[entity]; } public void Destroy(in Entity entity) { var record = EntityIndex[entity]; var archetype = record.Archetype; var row = record.Row; // remove all components from storages for (int i = 0; i < archetype.Signature.Count; i += 1) { var componentStorage = ComponentIndex[archetype.Signature[i]]; componentStorage.Remove(entity); } // remove all relations from storage foreach (var relationTypeIndex in EntityRelationIndex[entity]) { var relationStorage = RelationIndex[relationTypeIndex]; relationStorage.RemoveEntity(entity); } EntityRelationIndex[entity].Clear(); // remove from archetype if (row != archetype.Count - 1) { var lastEntity = archetype.Entities[archetype.Count - 1]; archetype.Entities[row] = lastEntity; EntityIndex[lastEntity] = new ArchetypeRecord(archetype, row); } archetype.Entities.RemoveLastElement(); EntityIndex.Remove(entity); // recycle ID EntityIdAssigner.Unassign(entity.ID); } // COMPONENTS public bool Has(in Entity entity) where T : unmanaged { var storage = GetComponentStorage(); return storage.Has(entity); } public bool Some() where T : unmanaged { var storage = GetComponentStorage(); return storage.Any(); } public ref T Get(in Entity entity) where T : unmanaged { var storage = GetComponentStorage(); return ref storage.Get(entity); } public ref T GetSingleton() where T : unmanaged { var storage = GetComponentStorage(); return ref storage.GetFirst(); } public Entity GetSingletonEntity() where T : unmanaged { var storage = GetComponentStorage(); return storage.FirstEntity(); } public void Set(in Entity entity, in T component) where T : unmanaged { var componentStorage = GetComponentStorage(); if (!componentStorage.Set(entity, component)) { UpdateArchetype(entity, true); } } public void Remove(in Entity entity) where T : unmanaged { var componentStorage = GetComponentStorage(); if (componentStorage.Remove(entity)) { UpdateArchetype(entity, false); } } private void UpdateArchetype(in Entity entity, bool insert) { Archetype? nextArchetype; var componentTypeId = TypeToId[typeof(T)]; var record = EntityIndex[entity]; var archetype = record.Archetype; if (archetype.Edges.TryGetValue(TypeToId[typeof(T)], out var edge)) { nextArchetype = insert ? edge.Add : edge.Remove; } else { var nextSignature = new ArchetypeSignature(archetype.Signature.Count + 1); archetype.Signature.CopyTo(nextSignature); if (insert) { nextSignature.Insert(componentTypeId); } else { nextSignature.Remove(componentTypeId); } if (!ArchetypeIndex.TryGetValue(nextSignature, out nextArchetype)) { nextArchetype = CreateArchetype(nextSignature); } var newEdge = new ArchetypeEdge(nextArchetype, archetype); archetype.Edges.Add(componentTypeId, newEdge); nextArchetype.Edges.Add(componentTypeId, newEdge); } var newRow = archetype.Transfer(record.Row, nextArchetype); EntityIndex[entity] = new ArchetypeRecord(nextArchetype, newRow); } // RELATIONS private RelationStorage RegisterRelationType(TypeId typeId) { var relationStorage = new RelationStorage(ElementSizes[typeId]); RelationIndex.Add(typeId, relationStorage); return relationStorage; } [MethodImpl(MethodImplOptions.AggressiveInlining)] private RelationStorage GetRelationStorage() where T : unmanaged { var typeId = GetTypeId(); if (RelationIndex.TryGetValue(typeId, out var relationStorage)) { return relationStorage; } return RegisterRelationType(typeId); } public void Relate(in Entity entityA, in Entity entityB, in T relation) where T : unmanaged { var relationStorage = GetRelationStorage(); relationStorage.Set(entityA, entityB, relation); EntityRelationIndex[entityA].Add(TypeToId[typeof(T)]); EntityRelationIndex[entityB].Add(TypeToId[typeof(T)]); } public void Unrelate(in Entity entityA, in Entity entityB) where T : unmanaged { var relationStorage = GetRelationStorage(); relationStorage.Remove(entityA, entityB); } public void UnrelateAll(in Entity entity) where T : unmanaged { var relationStorage = GetRelationStorage(); relationStorage.RemoveEntity(entity); } public bool Related(in Entity entityA, in Entity entityB) where T : unmanaged { var relationStorage = GetRelationStorage(); return relationStorage.Has(entityA, entityB); } public T GetRelationData(in Entity entityA, in Entity entityB) where T : unmanaged { var relationStorage = GetRelationStorage(); return relationStorage.Get(entityA, entityB); } public ReverseSpanEnumerator<(Entity, Entity)> Relations() where T : unmanaged { var relationStorage = GetRelationStorage(); return relationStorage.All(); } public ReverseSpanEnumerator OutRelations(Entity entity) where T : unmanaged { var relationStorage = GetRelationStorage(); return relationStorage.OutRelations(entity); } public Entity OutRelationSingleton(in Entity entity) where T : unmanaged { var relationStorage = GetRelationStorage(); return relationStorage.OutFirst(entity); } public bool HasOutRelation(in Entity entity) where T : unmanaged { var relationStorage = GetRelationStorage(); return relationStorage.HasOutRelation(entity); } public int OutRelationCount(in Entity entity) where T : unmanaged { var relationStorage = GetRelationStorage(); return relationStorage.OutRelationCount(entity); } public Entity NthOutRelation(in Entity entity, int n) where T : unmanaged { var relationStorage = GetRelationStorage(); return relationStorage.OutNth(entity, n); } public ReverseSpanEnumerator InRelations(Entity entity) where T : unmanaged { var relationStorage = GetRelationStorage(); return relationStorage.InRelations(entity); } public Entity InRelationSingleton(in Entity entity) where T : unmanaged { var relationStorage = GetRelationStorage(); return relationStorage.InFirst(entity); } public bool HasInRelation(in Entity entity) where T : unmanaged { var relationStorage = GetRelationStorage(); return relationStorage.HasInRelation(entity); } public int InRelationCount(in Entity entity) where T : unmanaged { var relationStorage = GetRelationStorage(); return relationStorage.InRelationCount(entity); } public Entity NthInRelation(in Entity entity, int n) where T : unmanaged { var relationStorage = GetRelationStorage(); return relationStorage.InNth(entity, n); } // MESSAGES private TypeId GetMessageTypeId() where T : unmanaged { var typeId = GetTypeId(); if (!MessageIndex.ContainsKey(typeId)) { MessageIndex.Add(typeId, new MessageStorage(Unsafe.SizeOf())); } return typeId; } public void Send(in T message) where T : unmanaged { var typeId = GetMessageTypeId(); MessageIndex[typeId].Add(message); } public bool SomeMessage() where T : unmanaged { var typeId = GetMessageTypeId(); return MessageIndex[typeId].Some(); } public ReadOnlySpan ReadMessages() where T : unmanaged { var typeId = GetMessageTypeId(); return MessageIndex[typeId].All(); } public T ReadMessage() where T : unmanaged { var typeId = GetMessageTypeId(); return MessageIndex[typeId].First(); } // TODO: temporary component storage? public void FinishUpdate() { foreach (var (_, messageStorage) in MessageIndex) { messageStorage.Clear(); } } // DEBUG // NOTE: these methods are very inefficient // they should only be used in debugging contexts!! #if DEBUG public ComponentTypeEnumerator Debug_GetAllComponentTypes(Entity entity) { return new ComponentTypeEnumerator(this, EntityIndex[entity]); } public IEnumerable Debug_GetEntities(Type componentType) { var storage = ComponentIndex[TypeToId[componentType]]; return storage.Debug_GetEntities(); } public IEnumerable Debug_SearchComponentType(string typeString) { foreach (var type in TypeToId.Keys) { if (type.ToString().ToLower().Contains(typeString.ToLower())) { yield return type; } } } public ref struct ComponentTypeEnumerator { private World World; private ArchetypeRecord Record; private int ComponentIndex; public ComponentTypeEnumerator GetEnumerator() => this; internal ComponentTypeEnumerator( World world, ArchetypeRecord record ) { World = world; Record = record; ComponentIndex = -1; } public bool MoveNext() { ComponentIndex += 1; return ComponentIndex < Record.Archetype.Signature.Count; } public unsafe Type Current => World.IdToType[Record.Archetype.Signature[ComponentIndex]]; } #endif } }