using System; using System.Collections.Generic; using System.Runtime.CompilerServices; using MoonTools.ECS.Collections; #if DEBUG using System.Reflection; #endif namespace MoonTools.ECS; public class World : IDisposable { #if DEBUG // TODO: is there a smarter way to do this? internal static Dictionary ComponentTypeToId = new Dictionary(); internal static List ComponentTypeIdToType = new List(); #endif internal static List ComponentTypeElementSizes = new List(); internal static List RelationTypeElementSizes = new List(); internal static List MessageTypeElementSizes = new List(); // Filters internal readonly Dictionary FilterIndex = new Dictionary(); private readonly List> ComponentTypeToFilter = new List>(); // TODO: can we make the tag an native array of chars at some point? internal List EntityTags = new List(); // Relation Storages internal List RelationIndex = new List(); internal List> EntityRelationIndex = new List>(); // Message Storages private List MessageIndex = new List(); public FilterBuilder FilterBuilder => new FilterBuilder(this); internal readonly List ComponentIndex = new List(); internal List> EntityComponentIndex = new List>(); internal IdAssigner EntityIdAssigner = new IdAssigner(); private bool IsDisposed; internal TypeId GetComponentTypeId() where T : unmanaged { var typeId = new TypeId(ComponentTypeIdAssigner.Id); if (typeId < ComponentIndex.Count) { return typeId; } // add missing storages, it's possible for there to be multiples in multi-world scenarios for (var i = ComponentIndex.Count; i <= typeId; i += 1) { var missingTypeId = new TypeId((uint) i); var componentStorage = new ComponentStorage(missingTypeId, ComponentTypeElementSizes[i]); ComponentIndex.Add(componentStorage); ComponentTypeToFilter.Add(new List()); } return typeId; } [MethodImpl(MethodImplOptions.AggressiveInlining)] private ComponentStorage GetComponentStorage() where T : unmanaged { var typeId = GetComponentTypeId(); return ComponentIndex[typeId]; } // FILTERS internal Filter GetFilter(FilterSignature signature) { if (!FilterIndex.TryGetValue(signature, out var filter)) { filter = new Filter(this, signature); foreach (var typeId in signature.Included) { ComponentTypeToFilter[(int) typeId.Value].Add(filter); } foreach (var typeId in signature.Excluded) { ComponentTypeToFilter[(int) typeId.Value].Add(filter); } FilterIndex.Add(signature, filter); } return filter; } // ENTITIES public Entity CreateEntity(string tag = "") { var entity = new Entity(EntityIdAssigner.Assign()); if (entity.ID == EntityComponentIndex.Count) { EntityRelationIndex.Add(new IndexableSet()); EntityComponentIndex.Add(new IndexableSet()); EntityTags.Add(tag); } return entity; } public void Tag(Entity entity, string tag) { EntityTags[(int) entity.ID] = tag; } public string GetTag(Entity entity) { return EntityTags[(int) entity.ID]; } public void Destroy(in Entity entity) { var componentSet = EntityComponentIndex[(int) entity.ID]; var relationSet = EntityRelationIndex[(int) entity.ID]; // remove all components from storages foreach (var componentTypeIndex in componentSet) { var componentStorage = ComponentIndex[componentTypeIndex]; componentStorage.Remove(entity); foreach (var filter in ComponentTypeToFilter[componentTypeIndex]) { filter.RemoveEntity(entity); } } // remove all relations from storage foreach (var relationTypeIndex in relationSet) { var relationStorage = RelationIndex[relationTypeIndex]; relationStorage.RemoveEntity(entity); } componentSet.Clear(); relationSet.Clear(); // recycle ID EntityIdAssigner.Unassign(entity.ID); EntityTags[(int) 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[(int) entity.ID].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[(int) entity.ID].Add(componentStorage.TypeId); foreach (var filter in ComponentTypeToFilter[componentStorage.TypeId]) { filter.Check(entity); } } } public void Remove(in Entity entity) where T : unmanaged { var componentStorage = GetComponentStorage(); if (componentStorage.Remove(entity)) { EntityComponentIndex[(int) entity.ID].Remove(componentStorage.TypeId); foreach (var filter in ComponentTypeToFilter[componentStorage.TypeId]) { filter.Check(entity); } } } // RELATIONS [MethodImpl(MethodImplOptions.AggressiveInlining)] private RelationStorage GetRelationStorage() where T : unmanaged { var typeId = new TypeId(RelationTypeIdAssigner.Id); if (typeId.Value < RelationIndex.Count) { return RelationIndex[typeId]; } for (var i = RelationIndex.Count; i <= typeId; i += 1) { RelationIndex.Add(new RelationStorage(RelationTypeElementSizes[i])); } return RelationIndex[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[(int) entityA.ID].Add(new TypeId(RelationTypeIdAssigner.Id)); EntityRelationIndex[(int) entityB.ID].Add(new TypeId(RelationTypeIdAssigner.Id)); } 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 MessageStorage GetMessageStorage() where T : unmanaged { var typeId = new TypeId(MessageTypeIdAssigner.Id); if (typeId < MessageIndex.Count) { return MessageIndex[typeId]; } for (var i = MessageIndex.Count; i <= typeId; i += 1) { MessageIndex.Add(new MessageStorage(MessageTypeElementSizes[i])); } return MessageIndex[typeId]; } public void Send(in T message) where T : unmanaged { GetMessageStorage().Add(message); } public bool SomeMessage() where T : unmanaged { return GetMessageStorage().Some(); } public ReadOnlySpan ReadMessages() where T : unmanaged { return GetMessageStorage().All(); } public T ReadMessage() where T : unmanaged { return GetMessageStorage().First(); } public void ClearMessages() where T : unmanaged { GetMessageStorage().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[(int) entity.ID]); } public IEnumerable Debug_GetEntities(Type componentType) { var baseGetComponentStorageMethod = typeof(World).GetMethod(nameof(World.GetComponentStorage), BindingFlags.NonPublic | BindingFlags.Instance)!; var genericGetComponentStorageMethod = baseGetComponentStorageMethod.MakeGenericMethod(componentType); var storage = genericGetComponentStorageMethod.Invoke(this, null) as ComponentStorage; return storage!.Debug_GetEntities(); } public IEnumerable Debug_SearchComponentType(string typeString) { foreach (var type in ComponentTypeToId.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 => ComponentTypeIdToType[Types[ComponentIndex]]; } #endif protected virtual void Dispose(bool disposing) { if (!IsDisposed) { if (disposing) { foreach (var componentStorage in ComponentIndex) { componentStorage.Dispose(); } foreach (var relationStorage in RelationIndex) { relationStorage.Dispose(); } foreach (var messageStorage in MessageIndex) { messageStorage.Dispose(); } foreach (var componentSet in EntityComponentIndex) { componentSet.Dispose(); } foreach (var relationSet in EntityRelationIndex) { relationSet.Dispose(); } foreach (var filter in FilterIndex.Values) { filter.Dispose(); } EntityIdAssigner.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); } }