using System; using System.Collections.Generic; using System.Runtime.CompilerServices; using Svelto.Common; using Svelto.DataStructures; using Svelto.ECS.Internal; namespace Svelto.ECS { public partial class EnginesRoot : IDisposable { /// /// Dispose an EngineRoot once not used anymore, so that all the /// engines are notified with the entities removed. /// It's a clean up process. /// public void Dispose() { using (var profiler = new PlatformProfiler("Final Dispose")) { foreach (var groups in _groupEntityViewsDB) { foreach (var entityList in groups.Value) { entityList.Value.RemoveEntitiesFromEngines(_reactiveEnginesAddRemove, profiler, new ExclusiveGroup.ExclusiveGroupStruct(groups.Key)); } } _groupEntityViewsDB.Clear(); _groupsPerEntity.Clear(); foreach (var engine in _disposableEngines) engine.Dispose(); _disposableEngines.Clear(); _enginesSet.Clear(); _enginesTypeSet.Clear(); _reactiveEnginesSwap.Clear(); _reactiveEnginesAddRemove.Clear(); _entitiesOperations.Clear(); _transientEntitiesOperations.Clear(); _scheduler.Dispose(); #if DEBUG && !PROFILER _idCheckers.Clear(); #endif _groupedEntityToAdd = null; _entitiesStream.Dispose(); } GC.SuppressFinalize(this); } ~EnginesRoot() { Console.LogWarning("Engines Root has been garbage collected, don't forget to call Dispose()!"); Dispose(); } ///-------------------------------------------- /// public IEntityStreamConsumerFactory GenerateConsumerFactory() { return new GenericEntityStreamConsumerFactory(this); } public IEntityFactory GenerateEntityFactory() { return new GenericEntityFactory(this); } public IEntityFunctions GenerateEntityFunctions() { return new GenericEntityFunctions(this); } ///-------------------------------------------- [MethodImpl(MethodImplOptions.AggressiveInlining)] EntityStructInitializer BuildEntity(EGID entityID, IEntityBuilder[] entitiesToBuild, IEnumerable implementors = null) { CheckAddEntityID(entityID); var dic = EntityFactory.BuildGroupedEntities(entityID, _groupedEntityToAdd, entitiesToBuild, implementors); return new EntityStructInitializer(entityID, dic); } ///-------------------------------------------- void Preallocate(uint groupID, uint size) where T : IEntityDescriptor, new() { var entityViewsToBuild = EntityDescriptorTemplate.descriptor.entitiesToBuild; var numberOfEntityViews = entityViewsToBuild.Length; //reserve space in the database if (_groupEntityViewsDB.TryGetValue(groupID, out var group) == false) group = _groupEntityViewsDB[groupID] = new FasterDictionary, ITypeSafeDictionary>(); for (var index = 0; index < numberOfEntityViews; index++) { var entityViewBuilder = entityViewsToBuild[index]; var entityViewType = entityViewBuilder.GetEntityType(); var refWrapper = new RefWrapper(entityViewType); if (group.TryGetValue(refWrapper, out var dbList) == false) group[refWrapper] = entityViewBuilder.Preallocate(ref dbList, size); else dbList.SetCapacity(size); if (_groupsPerEntity.TryGetValue(refWrapper, out var groupedGroup) == false) groupedGroup = _groupsPerEntity[refWrapper] = new FasterDictionary(); groupedGroup[groupID] = dbList; } } ///-------------------------------------------- /// void MoveEntityFromAndToEngines(IEntityBuilder[] entityBuilders, EGID fromEntityGID, EGID? toEntityGID) { using (var sampler = new PlatformProfiler("Move Entity From Engines")) { //for each entity view generated by the entity descriptor if (_groupEntityViewsDB.TryGetValue(fromEntityGID.groupID, out var fromGroup) == false) throw new ECSException("from group not found eid: ".FastConcat(fromEntityGID.entityID) .FastConcat(" group: ").FastConcat(fromEntityGID.groupID)); //Check if there is an EntityInfoView linked to this entity, if so it's a DynamicEntityDescriptor! if (fromGroup.TryGetValue(new RefWrapper(EntityBuilderUtilities.ENTITY_STRUCT_INFO_VIEW), out var entityInfoViewDic) && (entityInfoViewDic as TypeSafeDictionary).TryGetValue( fromEntityGID.entityID, out var entityInfoView)) MoveEntities(fromEntityGID, toEntityGID, entityInfoView.entitiesToBuild, fromGroup, sampler); //otherwise it's a normal static entity descriptor else MoveEntities(fromEntityGID, toEntityGID, entityBuilders, fromGroup, sampler); } } void MoveEntities(EGID fromEntityGID, EGID? toEntityGID, IEntityBuilder[] entitiesToMove, FasterDictionary, ITypeSafeDictionary> fromGroup, PlatformProfiler sampler) { FasterDictionary, ITypeSafeDictionary> toGroup = null; if (toEntityGID != null) { var toGroupID = toEntityGID.Value.groupID; if (_groupEntityViewsDB.TryGetValue(toGroupID, out toGroup) == false) toGroup = _groupEntityViewsDB[toGroupID] = new FasterDictionary, ITypeSafeDictionary>(); //Add all the entities to the dictionary for (var i = 0; i < entitiesToMove.Length; i++) CopyEntityToDictionary(fromEntityGID, toEntityGID.Value, fromGroup, toGroup, entitiesToMove[i].GetEntityType()); } //call all the callbacks for (var i = 0; i < entitiesToMove.Length; i++) MoveEntityViewFromAndToEngines(fromEntityGID, toEntityGID, fromGroup, toGroup, entitiesToMove[i].GetEntityType(), sampler); //then remove all the entities from the dictionary for (var i = 0; i < entitiesToMove.Length; i++) RemoveEntityFromDictionary(fromEntityGID, fromGroup, entitiesToMove[i].GetEntityType(), sampler); } void CopyEntityToDictionary(EGID entityGID, EGID toEntityGID, FasterDictionary, ITypeSafeDictionary> fromGroup, FasterDictionary, ITypeSafeDictionary> toGroup, Type entityViewType) { var wrapper = new RefWrapper(entityViewType); if (fromGroup.TryGetValue(wrapper, out var fromTypeSafeDictionary) == false) { throw new ECSException("no entities in from group eid: ".FastConcat(entityGID.entityID) .FastConcat(" group: ").FastConcat(entityGID.groupID)); } #if DEBUG && !PROFILER if (fromTypeSafeDictionary.Has(entityGID.entityID) == false) { throw new EntityNotFoundException(entityGID, entityViewType); } #endif if (toGroup.TryGetValue(wrapper, out var toEntitiesDictionary) == false) { toEntitiesDictionary = fromTypeSafeDictionary.Create(); toGroup.Add(wrapper, toEntitiesDictionary); } //todo: this must be unit tested properly if (_groupsPerEntity.TryGetValue(wrapper, out var groupedGroup) == false) groupedGroup = _groupsPerEntity[wrapper] = new FasterDictionary(); groupedGroup[toEntityGID.groupID] = toEntitiesDictionary; fromTypeSafeDictionary.AddEntityToDictionary(entityGID, toEntityGID, toEntitiesDictionary); } void MoveEntityViewFromAndToEngines(EGID entityGID, EGID? toEntityGID, FasterDictionary, ITypeSafeDictionary> fromGroup, FasterDictionary, ITypeSafeDictionary> toGroup, Type entityViewType, in PlatformProfiler profiler) { //add all the entities var refWrapper = new RefWrapper(entityViewType); if (fromGroup.TryGetValue(refWrapper, out var fromTypeSafeDictionary) == false) { throw new ECSException("no entities in from group eid: ".FastConcat(entityGID.entityID) .FastConcat(" group: ").FastConcat(entityGID.groupID)); } ITypeSafeDictionary toEntitiesDictionary = null; if (toGroup != null) toEntitiesDictionary = toGroup[refWrapper]; //this is guaranteed to exist by AddEntityToDictionary #if DEBUG && !PROFILER if (fromTypeSafeDictionary.Has(entityGID.entityID) == false) throw new EntityNotFoundException(entityGID, entityViewType); #endif fromTypeSafeDictionary.MoveEntityFromEngines(entityGID, toEntityGID, toEntitiesDictionary, toEntityGID == null ? _reactiveEnginesAddRemove : _reactiveEnginesSwap, in profiler); } void RemoveEntityFromDictionary(EGID entityGID, FasterDictionary, ITypeSafeDictionary> fromGroup, Type entityViewType, in PlatformProfiler profiler) { var refWrapper = new RefWrapper(entityViewType); if (fromGroup.TryGetValue(refWrapper, out var fromTypeSafeDictionary) == false) { throw new ECSException("no entities in from group eid: ".FastConcat(entityGID.entityID) .FastConcat(" group: ").FastConcat(entityGID.groupID)); } fromTypeSafeDictionary.RemoveEntityFromDictionary(entityGID, profiler); if (fromTypeSafeDictionary.Count == 0) //clean up { //todo: this must be unit tested properly _groupsPerEntity[refWrapper].Remove(entityGID.groupID); //I don't remove the group if empty on purpose, in case it needs to be reused } } /// /// Todo: I should keep the group, but I need to mark the group as deleted for the Exist function to work /// /// /// void RemoveGroupAndEntitiesFromDB(uint groupID, in PlatformProfiler profiler) { var dictionariesOfEntities = _groupEntityViewsDB[groupID]; foreach (var dictionaryOfEntities in dictionariesOfEntities) { dictionaryOfEntities.Value.RemoveEntitiesFromEngines(_reactiveEnginesAddRemove, profiler, new ExclusiveGroup.ExclusiveGroupStruct(groupID)); var groupedGroupOfEntities = _groupsPerEntity[dictionaryOfEntities.Key]; groupedGroupOfEntities.Remove(groupID); } //careful, in this case I assume you really don't want to use this group anymore //so I remove it from the database _groupEntityViewsDB.Remove(groupID); } internal Consumer GenerateConsumer(string name, uint capacity) where T : unmanaged, IEntityStruct { return _entitiesStream.GenerateConsumer(name, capacity); } public Consumer GenerateConsumer(ExclusiveGroup group, string name, uint capacity) where T : unmanaged, IEntityStruct { return _entitiesStream.GenerateConsumer(group, name, capacity); } //one datastructure rule them all: //split by group //split by type per group. It's possible to get all the entities of a give type T per group thanks //to the FasterDictionary capabilities OR it's possible to get a specific entityView indexed by //ID. This ID doesn't need to be the EGID, it can be just the entityID //for each group id, save a dictionary indexed by entity type of entities indexed by id //ITypeSafeDictionary = Key = entityID, Value = EntityStruct readonly FasterDictionary, ITypeSafeDictionary>> _groupEntityViewsDB; //for each entity view type, return the groups (dictionary of entities indexed by entity id) where they are //found indexed by group id //EntityViewType //groupID //entityID, EntityStruct readonly FasterDictionary, FasterDictionary> _groupsPerEntity; readonly EntitiesDB _entitiesDB; readonly EntitiesStream _entitiesStream; } }