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, IUnitTestingInterface { ///-------------------------------------------- /// public IEntityStreamConsumerFactory GenerateConsumerFactory() { return new GenericEntityStreamConsumerFactory(this); } public IEntityFactory GenerateEntityFactory() { return new GenericEntityFactory(this); } public IEntityFunctions GenerateEntityFunctions() { return new GenericEntityFunctions(this); } ///-------------------------------------------- [MethodImpl(MethodImplOptions.AggressiveInlining)] EntityInitializer BuildEntity(EGID entityID, IComponentBuilder[] componentsToBuild, Type descriptorType , IEnumerable implementors = null) { CheckAddEntityID(entityID, descriptorType); DBC.ECS.Check.Require((uint)entityID.groupID != 0 , "invalid group detected, are you using new ExclusiveGroupStruct() instead of new ExclusiveGroup()?"); var reference = _entityLocator.ClaimReference(); _entityLocator.SetReference(reference, entityID); var dic = EntityFactory.BuildGroupedEntities(entityID, _groupedEntityToAdd, componentsToBuild, implementors #if DEBUG && !PROFILE_SVELTO , descriptorType #endif ); return new EntityInitializer(entityID, dic, reference); } /// /// Preallocate memory to avoid the impact to resize arrays when many entities are submitted at once /// void Preallocate(ExclusiveGroupStruct groupID, uint numberOfEntities, IComponentBuilder[] entityComponentsToBuild) { void PreallocateEntitiesToAdd() { _groupedEntityToAdd.Preallocate(groupID, numberOfEntities, entityComponentsToBuild); } void PreallocateDBGroup() { var numberOfEntityComponents = entityComponentsToBuild.Length; FasterDictionary group = GetOrCreateDBGroup(groupID); for (var index = 0; index < numberOfEntityComponents; index++) { var entityComponentBuilder = entityComponentsToBuild[index]; var entityComponentType = entityComponentBuilder.GetEntityComponentType(); var refWrapper = new RefWrapperType(entityComponentType); var dbList = group.GetOrCreate(refWrapper, ()=>entityComponentBuilder.CreateDictionary(numberOfEntities)); entityComponentBuilder.Preallocate(dbList, numberOfEntities); if (_groupsPerEntity.TryGetValue(refWrapper, out var groupedGroup) == false) groupedGroup = _groupsPerEntity[refWrapper] = new FasterDictionary(); groupedGroup[groupID] = dbList; } } PreallocateDBGroup(); PreallocateEntitiesToAdd(); _entityLocator.PreallocateReferenceMaps(groupID, numberOfEntities); } ///-------------------------------------------- /// void MoveEntityFromAndToEngines(IComponentBuilder[] componentBuilders, EGID fromEntityGID, EGID? toEntityGID) { using (var sampler = new PlatformProfiler("Move Entity From Engines")) { var fromGroup = GetDBGroup(fromEntityGID.groupID); //Check if there is an EntityInfo linked to this entity, if so it's a DynamicEntityDescriptor! if (fromGroup.TryGetValue(new RefWrapperType(ComponentBuilderUtilities.ENTITY_INFO_COMPONENT) , out var entityInfoDic) && (entityInfoDic as ITypeSafeDictionary).TryGetValue( fromEntityGID.entityID, out var entityInfo)) SwapOrRemoveEntityComponents(fromEntityGID, toEntityGID, entityInfo.componentsToBuild, fromGroup , sampler); //otherwise it's a normal static entity descriptor else SwapOrRemoveEntityComponents(fromEntityGID, toEntityGID, componentBuilders, fromGroup, sampler); } } void SwapOrRemoveEntityComponents (EGID fromEntityGID, EGID? toEntityGID, IComponentBuilder[] entitiesToMove , FasterDictionary fromGroup, in PlatformProfiler sampler) { using (sampler.Sample("MoveEntityComponents")) { var length = entitiesToMove.Length; FasterDictionary toGroup = null; //Swap is not like adding a new entity. While adding new entities happen at the end of submission //Adding an entity to a group due to a swap of groups happens now. if (toEntityGID.HasValue) { var entityGid = toEntityGID.Value; _entityLocator.UpdateEntityReference(fromEntityGID, entityGid); var toGroupID = entityGid.groupID; toGroup = GetOrCreateDBGroup(toGroupID); //Add all the entities to the dictionary for (var i = 0; i < length; i++) CopyEntityToDictionary(fromEntityGID, entityGid, fromGroup, toGroup , entitiesToMove[i].GetEntityComponentType(), sampler); } else { _entityLocator.RemoveEntityReference(fromEntityGID); } //call all the callbacks for (var i = 0; i < length; i++) ExecuteEnginesSwapOrRemoveCallbacks(fromEntityGID, toEntityGID, fromGroup, toGroup , entitiesToMove[i].GetEntityComponentType(), sampler); //then remove all the entities from the dictionary for (var i = 0; i < length; i++) RemoveEntityFromDictionary(fromEntityGID, fromGroup, entitiesToMove[i].GetEntityComponentType() , sampler); } } void CopyEntityToDictionary (EGID entityGID, EGID toEntityGID, FasterDictionary fromGroup , FasterDictionary toGroup, Type entityComponentType , in PlatformProfiler sampler) { using (sampler.Sample("CopyEntityToDictionary")) { var wrapper = new RefWrapperType(entityComponentType); ITypeSafeDictionary fromTypeSafeDictionary = GetTypeSafeDictionary((uint) entityGID.groupID, fromGroup, wrapper); #if DEBUG && !PROFILE_SVELTO if (fromTypeSafeDictionary.Has(entityGID.entityID) == false) { throw new EntityNotFoundException(entityGID, entityComponentType); } #endif ITypeSafeDictionary toEntitiesDictionary = GetOrCreateTypeSafeDictionary(toEntityGID.groupID, toGroup, wrapper, fromTypeSafeDictionary); fromTypeSafeDictionary.AddEntityToDictionary(entityGID, toEntityGID, toEntitiesDictionary); } } void ExecuteEnginesSwapOrRemoveCallbacks (EGID entityGID, EGID? toEntityGID, FasterDictionary fromGroup , FasterDictionary toGroup, Type entityComponentType , in PlatformProfiler profiler) { using (profiler.Sample("MoveEntityComponentFromAndToEngines")) { //add all the entities var refWrapper = new RefWrapperType(entityComponentType); var fromTypeSafeDictionary = GetTypeSafeDictionary((uint) entityGID.groupID, fromGroup, refWrapper); ITypeSafeDictionary toEntitiesDictionary = null; if (toGroup != null) toEntitiesDictionary = toGroup[refWrapper]; //this is guaranteed to exist by AddEntityToDictionary #if DEBUG && !PROFILE_SVELTO if (fromTypeSafeDictionary.Has(entityGID.entityID) == false) throw new EntityNotFoundException(entityGID, entityComponentType); #endif fromTypeSafeDictionary.ExecuteEnginesSwapOrRemoveCallbacks(entityGID, toEntityGID, toEntitiesDictionary , toEntityGID == null ? _reactiveEnginesAddRemove : _reactiveEnginesSwap, in profiler); } } [MethodImpl(MethodImplOptions.AggressiveInlining)] void RemoveEntityFromDictionary (EGID entityGID, FasterDictionary fromGroup, Type entityComponentType , in PlatformProfiler sampler) { using (sampler.Sample("RemoveEntityFromDictionary")) { var refWrapper = new RefWrapperType(entityComponentType); var fromTypeSafeDictionary = GetTypeSafeDictionary((uint) entityGID.groupID, fromGroup, refWrapper); fromTypeSafeDictionary.RemoveEntityFromDictionary(entityGID); } } /// /// Swap all the entities from one group to another /// /// TODO: write unit test that also tests that this calls MoveTo callbacks and not Add or Remove. /// also that the passing EGID is the same of a component with EGID /// /// /// /// void SwapEntitiesBetweenGroups (ExclusiveGroupStruct fromIdGroupId, ExclusiveGroupStruct toGroupId, in PlatformProfiler profiler) { using (profiler.Sample("SwapEntitiesBetweenGroups")) { FasterDictionary fromGroup = GetDBGroup(fromIdGroupId); FasterDictionary toGroup = GetOrCreateDBGroup(toGroupId); _entityLocator.UpdateAllGroupReferenceLocators(fromIdGroupId, (uint) toGroupId); foreach (var dictionaryOfEntities in fromGroup) { ITypeSafeDictionary toEntitiesDictionary = GetOrCreateTypeSafeDictionary(toGroupId, toGroup, dictionaryOfEntities.Key , dictionaryOfEntities.Value); var groupsOfEntityType = _groupsPerEntity[dictionaryOfEntities.Key]; var groupOfEntitiesToCopyAndClear = groupsOfEntityType[fromIdGroupId]; toEntitiesDictionary.AddEntitiesFromDictionary(groupOfEntitiesToCopyAndClear, (uint) toGroupId, this); //call all the MoveTo callbacks dictionaryOfEntities.Value.ExecuteEnginesAddOrSwapCallbacks(_reactiveEnginesSwap , dictionaryOfEntities.Value, new ExclusiveGroupStruct(fromIdGroupId) , new ExclusiveGroupStruct(toGroupId), profiler); //todo: if it's unmanaged, I can use fastclear groupOfEntitiesToCopyAndClear.Clear(); } } } [MethodImpl(MethodImplOptions.AggressiveInlining)] FasterDictionary GetDBGroup(ExclusiveGroupStruct fromIdGroupId) { if (_groupEntityComponentsDB.TryGetValue(fromIdGroupId , out FasterDictionary fromGroup) == false) throw new ECSException("Group doesn't exist: ".FastConcat((uint) fromIdGroupId)); return fromGroup; } [MethodImpl(MethodImplOptions.AggressiveInlining)] FasterDictionary GetOrCreateDBGroup (ExclusiveGroupStruct toGroupId) { return _groupEntityComponentsDB.GetOrCreate( toGroupId, () => new FasterDictionary()); } ITypeSafeDictionary GetOrCreateTypeSafeDictionary (ExclusiveGroupStruct groupId, FasterDictionary toGroup , RefWrapperType type, ITypeSafeDictionary fromTypeSafeDictionary) { //be sure that the TypeSafeDictionary for the entity Type exists if (toGroup.TryGetValue(type, out ITypeSafeDictionary toEntitiesDictionary) == false) { toEntitiesDictionary = fromTypeSafeDictionary.Create(); toGroup.Add(type, toEntitiesDictionary); } //update GroupsPerEntity if (_groupsPerEntity.TryGetValue(type, out var groupedGroup) == false) groupedGroup = _groupsPerEntity[type] = new FasterDictionary(); groupedGroup[groupId] = toEntitiesDictionary; return toEntitiesDictionary; } static ITypeSafeDictionary GetTypeSafeDictionary (uint groupID, FasterDictionary @group, RefWrapperType refWrapper) { if (@group.TryGetValue(refWrapper, out ITypeSafeDictionary fromTypeSafeDictionary) == false) { throw new ECSException("no group found: ".FastConcat(groupID)); } return fromTypeSafeDictionary; } void RemoveEntitiesFromGroup(ExclusiveGroupStruct groupID, in PlatformProfiler profiler) { _entityLocator.RemoveAllGroupReferenceLocators(groupID); if (_groupEntityComponentsDB.TryGetValue(groupID, out var dictionariesOfEntities)) { foreach (var dictionaryOfEntities in dictionariesOfEntities) { dictionaryOfEntities.Value.ExecuteEnginesRemoveCallbacks(_reactiveEnginesAddRemove, profiler , new ExclusiveGroupStruct(groupID)); dictionaryOfEntities.Value.FastClear(); var groupsOfEntityType = _groupsPerEntity[dictionaryOfEntities.Key]; groupsOfEntityType[groupID].FastClear(); } } } //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 entityComponent 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 // group EntityComponentType entityID, EntityComponent internal readonly FasterDictionary> _groupEntityComponentsDB; //for each entity view type, return the groups (dictionary of entities indexed by entity id) where they are //found indexed by group id. TypeSafeDictionary are never created, they instead point to the ones hold //by _groupEntityComponentsDB // >> internal readonly FasterDictionary> _groupsPerEntity; //The filters stored for each component and group internal readonly FasterDictionary> _groupFilters; readonly EntitiesDB _entitiesDB; EntitiesDB IUnitTestingInterface.entitiesForTesting => _entitiesDB; } public interface IUnitTestingInterface { EntitiesDB entitiesForTesting { get; } } }