using System; using System.Collections.Generic; using System.Runtime.CompilerServices; using MoonTools.ECS.Collections; namespace MoonTools.ECS; public class World : IDisposable { // 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(); // Filters internal readonly Dictionary FilterIndex = new Dictionary(); private readonly Dictionary> TypeToFilter = new Dictionary>(); // 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 Dictionary> EntityComponentIndex = new Dictionary>(); internal IdAssigner EntityIdAssigner = new IdAssigner(); private IdAssigner TypeIdAssigner = new IdAssigner(); private bool IsDisposed; 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; } internal TypeId GetComponentTypeId() where T : unmanaged { var typeId = GetTypeId(); if (ComponentIndex.TryGetValue(typeId, out var componentStorage)) { return typeId; } componentStorage = new ComponentStorage(typeId, ElementSizes[typeId]); ComponentIndex.Add(typeId, componentStorage); TypeToFilter.Add(typeId, new List()); 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(typeId, ElementSizes[typeId]); ComponentIndex.Add(typeId, componentStorage); TypeToFilter.Add(typeId, new List()); return componentStorage; } // FILTERS internal Filter GetFilter(FilterSignature signature) { if (!FilterIndex.TryGetValue(signature, out var filter)) { filter = new Filter(this, signature); foreach (var typeId in signature.Included) { TypeToFilter[typeId].Add(filter); } foreach (var typeId in signature.Excluded) { TypeToFilter[typeId].Add(filter); } FilterIndex.Add(signature, filter); } return filter; } // ENTITIES public Entity CreateEntity(string tag = "") { var entity = new Entity(EntityIdAssigner.Assign()); if (!EntityComponentIndex.ContainsKey(entity)) { EntityRelationIndex.Add(entity, new IndexableSet()); EntityComponentIndex.Add(entity, new IndexableSet()); } EntityTags[entity] = tag; 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) { // remove all components from storages foreach (var componentTypeIndex in EntityComponentIndex[entity]) { var componentStorage = ComponentIndex[componentTypeIndex]; componentStorage.Remove(entity); foreach (var filter in TypeToFilter[componentTypeIndex]) { filter.RemoveEntity(entity); } } // remove all relations from storage foreach (var relationTypeIndex in EntityRelationIndex[entity]) { var relationStorage = RelationIndex[relationTypeIndex]; relationStorage.RemoveEntity(entity); } EntityComponentIndex[entity].Clear(); EntityRelationIndex[entity].Clear(); // recycle ID EntityIdAssigner.Unassign(entity.ID); } // COMPONENTS public bool Has(in Entity entity) where T : unmanaged { var storage = GetComponentStorage(); return storage.Has(entity); } internal bool Has(in Entity entity, in TypeId typeId) { return EntityComponentIndex[entity].Contains(typeId); } 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)) { EntityComponentIndex[entity].Add(componentStorage.TypeId); foreach (var filter in TypeToFilter[componentStorage.TypeId]) { filter.Check(entity); } } } public void Remove(in Entity entity) where T : unmanaged { var componentStorage = GetComponentStorage(); if (componentStorage.Remove(entity)) { EntityComponentIndex[entity].Remove(componentStorage.TypeId); foreach (var filter in TypeToFilter[componentStorage.TypeId]) { filter.Check(entity); } } } // 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(); } public void ClearMessages() where T : unmanaged { var typeId = GetMessageTypeId(); MessageIndex[typeId].Clear(); } // 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, EntityComponentIndex[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 IndexableSet Types; private int ComponentIndex; public ComponentTypeEnumerator GetEnumerator() => this; internal ComponentTypeEnumerator( World world, IndexableSet types ) { World = world; Types = types; ComponentIndex = -1; } public bool MoveNext() { ComponentIndex += 1; return ComponentIndex < Types.Count; } public Type Current => World.IdToType[Types[ComponentIndex]]; } #endif protected virtual void Dispose(bool disposing) { if (!IsDisposed) { if (disposing) { foreach (var componentStorage in ComponentIndex.Values) { componentStorage.Dispose(); } foreach (var relationStorage in RelationIndex.Values) { relationStorage.Dispose(); } foreach (var messageStorage in MessageIndex.Values) { messageStorage.Dispose(); } foreach (var typeSet in EntityComponentIndex.Values) { typeSet.Dispose(); } foreach (var typeSet in EntityRelationIndex.Values) { typeSet.Dispose(); } foreach (var filter in FilterIndex.Values) { filter.Dispose(); } EntityIdAssigner.Dispose(); TypeIdAssigner.Dispose(); } IsDisposed = true; } } // // TODO: override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources // ~World() // { // // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method // Dispose(disposing: false); // } public void Dispose() { // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method Dispose(disposing: true); GC.SuppressFinalize(this); } }