using System; using System.Collections.Generic; using System.Linq; using Encompass.Exceptions; using Collections.Pooled; namespace Encompass { internal class ComponentManager { private readonly DrawLayerManager drawLayerManager; private readonly Dictionary componentIDToType = new Dictionary(); private readonly Dictionary IDToComponent = new Dictionary(); private readonly Dictionary> entityIDToComponentIDs = new Dictionary>(); private readonly Dictionary componentIDToEntityID = new Dictionary(); private readonly Dictionary> entityIDToComponentTypeToComponentID = new Dictionary>(); private readonly Dictionary> typeToComponentIDs = new Dictionary>(); private readonly List<(Entity, Type, Guid, IComponent)> componentAddData = new List<(Entity, Type, Guid, IComponent)>(); private readonly HashSet componentsMarkedForRemoval = new HashSet(); private readonly Dictionary pendingUpdates = new Dictionary(); public ComponentManager(DrawLayerManager drawLayerManager) { this.drawLayerManager = drawLayerManager; } internal void RegisterEntity(Guid entityID) { entityIDToComponentIDs.Add(entityID, new PooledSet()); entityIDToComponentTypeToComponentID.Add(entityID, new PooledDictionary()); } private Guid NextID() { return Guid.NewGuid(); } internal Guid MarkComponentForAdd(Entity entity, TComponent component) where TComponent : struct, IComponent { var id = NextID(); componentAddData.Add((entity, typeof(TComponent), id, component)); // add these here so entity and component lookups dont break on pending components IDToComponent[id] = component; componentIDToEntityID[id] = entity.ID; componentIDToType[id] = typeof(TComponent); if (!typeToComponentIDs.ContainsKey(typeof(TComponent))) { typeToComponentIDs.Add(typeof(TComponent), new HashSet()); } typeToComponentIDs[typeof(TComponent)].Add(id); entityIDToComponentIDs[entity.ID].Add(id); return id; } internal Guid MarkDrawComponentForAdd(Entity entity, TComponent component, int layer = 0) where TComponent : struct, IComponent { var id = MarkComponentForAdd(entity, component); drawLayerManager.RegisterComponentWithLayer(id, layer); return id; } internal void AddComponent(Entity entity, Type type, Guid componentID, IComponent component) { if (!entityIDToComponentTypeToComponentID[entity.ID].ContainsKey(type)) { entityIDToComponentTypeToComponentID[entity.ID][type] = componentID; } else { throw new MultipleComponentOfSameTypeException("Entity {0} cannot have multiple components of type {1}", entity.ID, type.Name); } } internal void AddMarkedComponents() { foreach (var (entity, type, componentID, component) in componentAddData) { AddComponent(entity, type, componentID, component); } componentAddData.Clear(); } internal IEnumerable GetComponentIDsByEntityID(Guid entityID) { if (entityIDToComponentIDs.TryGetValue(entityID, out PooledSet idSet)) { return idSet; } return Enumerable.Empty(); } internal IEnumerable<(Guid, TComponent)> GetComponentsByType() where TComponent : struct, IComponent { if (typeToComponentIDs.TryGetValue(typeof(TComponent), out HashSet idSet)) { return idSet.Select(id => (id, (TComponent)IDToComponent[id])); } return Enumerable.Empty<(Guid, TComponent)>(); } internal (Guid, TComponent) GetComponentByEntityAndType(Entity entity) where TComponent : struct, IComponent { if (entityIDToComponentTypeToComponentID.ContainsKey(entity.ID) && entityIDToComponentTypeToComponentID[entity.ID].TryGetValue(typeof(TComponent), out Guid id)) { return (id, (TComponent)IDToComponent[id]); } throw new NoComponentOfTypeOnEntityException("No Component of type {0} exists on Entity {1}", typeof(TComponent).Name, entity.ID); } internal bool EntityHasComponentOfType(Entity entity) where TComponent : struct, IComponent { return (entityIDToComponentTypeToComponentID.ContainsKey(entity.ID) && entityIDToComponentTypeToComponentID[entity.ID].ContainsKey(typeof(TComponent))); } internal bool ComponentOfTypeExists() where TComponent : struct, IComponent { if (typeToComponentIDs.TryGetValue(typeof(TComponent), out HashSet idSet)) { return idSet.Count > 0; } return false; } internal IComponent GetComponentByID(Guid componentID) { return IDToComponent[componentID]; } internal Type GetComponentTypeByID(Guid componentID) { return componentIDToType[componentID]; } internal Guid GetEntityIDByComponentID(Guid componentID) { return componentIDToEntityID[componentID]; } internal void AddUpdateComponentOperation(Guid componentID, TComponent newComponentValue) where TComponent : struct, IComponent { if (pendingUpdates.ContainsKey(componentID)) { throw new RepeatUpdateComponentException("Component {0} with ID {1} was updated multiple times this frame", typeof(TComponent).Name, componentID); } pendingUpdates.Add(componentID, newComponentValue); } internal void PerformComponentUpdates() { foreach (var idPair in pendingUpdates) { IDToComponent[idPair.Key] = idPair.Value; } pendingUpdates.Clear(); } internal void MarkAllComponentsOnEntityForRemoval(Guid entityID) { foreach (var componentID in GetComponentIDsByEntityID(entityID)) { MarkForRemoval(componentID); } } internal void MarkForRemoval(Guid componentID) { componentsMarkedForRemoval.Add(componentID); } internal void RemoveMarkedComponents() { foreach (var componentID in componentsMarkedForRemoval) { Remove(componentID); } componentsMarkedForRemoval.Clear(); } private void Remove(Guid componentID) { var type = componentIDToType[componentID]; var entityID = componentIDToEntityID[componentID]; if (entityIDToComponentIDs.ContainsKey(entityID)) { entityIDToComponentIDs[entityID].Remove(componentID); } if (entityIDToComponentTypeToComponentID.ContainsKey(entityID)) { entityIDToComponentTypeToComponentID[entityID].Remove(type); } IDToComponent.Remove(componentID); componentIDToType.Remove(componentID); componentIDToEntityID.Remove(componentID); typeToComponentIDs[type].Remove(componentID); drawLayerManager.UnRegisterComponentWithLayer(componentID); } public void RegisterDestroyedEntity(Guid entityID) { entityIDToComponentIDs[entityID].Dispose(); entityIDToComponentIDs.Remove(entityID); entityIDToComponentTypeToComponentID[entityID].Dispose(); entityIDToComponentTypeToComponentID.Remove(entityID); } } }