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;
}
}