#if PROFILE_SVELTO && DEBUG
#warning the global define PROFILE_SVELTO should be used only when it's necessary to profile in order to reduce the overhead of debug code. While debugging remove this define to get insights when errors happen
#endif
using System;
using System.Collections.Generic;
using DBC.ECS;
using Svelto.Common;
using Svelto.DataStructures;
using Svelto.ECS.Internal;
using Svelto.ECS.Schedulers;
namespace Svelto.ECS
{
public partial class EnginesRoot
{
static EnginesRoot()
{
EntityDescriptorsWarmup.WarmUp();
GroupHashMap.WarmUp();
SerializationDescriptorMap.Init();
_swapEntities = SwapEntities;
_removeEntities = RemoveEntities;
_removeGroup = RemoveGroup;
_swapGroup = SwapGroup;
}
///
/// Engines root contextualize your engines and entities. You don't need to limit yourself to one EngineRoot
/// as multiple engines root could promote separation of scopes. The EntitySubmissionScheduler checks
/// periodically if new entity must be submitted to the database and the engines. It's an external
/// dependencies to be independent by the running platform as the user can define it.
/// The EntitySubmissionScheduler cannot hold an EnginesRoot reference, that's why
/// it must receive a weak reference of the EnginesRoot callback.
///
public EnginesRoot(EntitiesSubmissionScheduler entitiesComponentScheduler)
{
_entitiesOperations = new EntitiesOperations();
_cachedRangeOfSubmittedIndices = new FasterList<(uint, uint)>();
_transientEntityIDsAffectedByRemoveAtSwapBack = new FasterDictionary();
InitDebugChecks();
#if UNITY_NATIVE //because of the thread count, ATM this is only for unity
_nativeSwapOperationQueue = new AtomicNativeBags(Allocator.Persistent);
_nativeRemoveOperationQueue = new AtomicNativeBags(Allocator.Persistent);
_nativeAddOperationQueue = new AtomicNativeBags(Allocator.Persistent);
#endif
_serializationDescriptorMap = new SerializationDescriptorMap();
_reactiveEnginesAdd = new FasterDictionary>>();
_reactiveEnginesAddEx =
new FasterDictionary>>();
_reactiveEnginesRemove =
new FasterDictionary>>();
_reactiveEnginesRemoveEx =
new FasterDictionary>>();
_reactiveEnginesSwap =
new FasterDictionary>>();
_reactiveEnginesSwapEx =
new FasterDictionary>>();
_reactiveEnginesDispose =
new FasterDictionary>>();
_reactiveEnginesDisposeEx =
new FasterDictionary>>();
_reactiveEnginesSubmission = new FasterList();
_reactiveEnginesSubmissionStarted = new FasterList();
_enginesSet = new FasterList();
_enginesTypeSet = new HashSet();
_disposableEngines = new FasterList();
_groupEntityComponentsDB =
new FasterDictionary>();
_groupsPerEntity =
new FasterDictionary>();
_groupedEntityToAdd = new DoubleBufferedEntitiesToAdd();
_entityStreams = EntitiesStreams.Create();
#if SVELTO_LEGACY_FILTERS
_groupFilters =
new FasterDictionary>();
#endif
_entityLocator.InitEntityReferenceMap();
_entitiesDB = new EntitiesDB(this, _entityLocator);
InitFilters();
scheduler = entitiesComponentScheduler;
scheduler.onTick = new EntitiesSubmitter(this);
#if UNITY_NATIVE
AllocateNativeOperations();
#endif
}
protected EnginesRoot(EntitiesSubmissionScheduler entitiesComponentScheduler,
EnginesReadyOption enginesWaitForReady): this(entitiesComponentScheduler)
{
_enginesWaitForReady = enginesWaitForReady;
}
public EntitiesSubmissionScheduler scheduler { get; }
///
/// 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()
{
Dispose(true);
GC.SuppressFinalize(this);
}
public bool IsValid()
{
return _isDisposed == false;
}
public void AddEngine(IEngine engine)
{
var type = engine.GetType();
var refWrapper = new RefWrapperType(type);
Check.Require(engine != null, "Engine to add is invalid or null");
Check.Require(
_enginesTypeSet.Contains(refWrapper) == false ||
type.ContainsCustomAttribute(typeof(AllowMultipleAttribute)),
"The same engine has been added more than once, if intentional, use [AllowMultiple] class attribute "
.FastConcat(engine.ToString()));
try
{
if (engine is IReactOnAdd viewEngineAdd)
#pragma warning disable CS0612
CheckReactEngineComponents(typeof(IReactOnAdd<>), viewEngineAdd, _reactiveEnginesAdd, type.Name);
#pragma warning restore CS0612
if (engine is IReactOnAddEx viewEngineAddEx)
CheckReactEngineComponents(
typeof(IReactOnAddEx<>), viewEngineAddEx, _reactiveEnginesAddEx, type.Name);
if (engine is IReactOnRemove viewEngineRemove)
CheckReactEngineComponents(
#pragma warning disable CS0612
typeof(IReactOnRemove<>), viewEngineRemove, _reactiveEnginesRemove, type.Name);
#pragma warning restore CS0612
if (engine is IReactOnRemoveEx viewEngineRemoveEx)
CheckReactEngineComponents(
typeof(IReactOnRemoveEx<>), viewEngineRemoveEx, _reactiveEnginesRemoveEx, type.Name);
if (engine is IReactOnDispose viewEngineDispose)
CheckReactEngineComponents(
#pragma warning disable CS0618
typeof(IReactOnDispose<>), viewEngineDispose, _reactiveEnginesDispose, type.Name);
#pragma warning restore CS0618
if (engine is IReactOnDisposeEx viewEngineDisposeEx)
CheckReactEngineComponents(
typeof(IReactOnDisposeEx<>), viewEngineDisposeEx, _reactiveEnginesDisposeEx, type.Name);
if (engine is IReactOnSwap viewEngineSwap)
#pragma warning disable CS0612
#pragma warning disable CS0618
CheckReactEngineComponents(typeof(IReactOnSwap<>), viewEngineSwap, _reactiveEnginesSwap, type.Name);
#pragma warning restore CS0618
#pragma warning restore CS0612
if (engine is IReactOnSwapEx viewEngineSwapEx)
CheckReactEngineComponents(
typeof(IReactOnSwapEx<>), viewEngineSwapEx, _reactiveEnginesSwapEx, type.Name);
if (engine is IReactOnSubmission submissionEngine)
_reactiveEnginesSubmission.Add(submissionEngine);
if (engine is IReactOnSubmissionStarted submissionEngineStarted)
_reactiveEnginesSubmissionStarted.Add(submissionEngineStarted);
if (engine is IGroupEngine stepGroupEngine)
foreach (var stepEngine in stepGroupEngine.engines)
AddEngine(stepEngine);
_enginesTypeSet.Add(refWrapper);
_enginesSet.Add(engine);
if (engine is IDisposable)
_disposableEngines.Add(engine as IDisposable);
if (engine is IQueryingEntitiesEngine queryableEntityComponentEngine)
queryableEntityComponentEngine.entitiesDB = _entitiesDB;
if (_enginesWaitForReady == EnginesReadyOption.ReadyAsAdded && engine is IGetReadyEngine getReadyEngine)
getReadyEngine.Ready();
}
catch (Exception e)
{
throw new ECSException(
"Code crashed while adding engine ".FastConcat(engine.GetType().ToString(), " "),
e);
}
}
public void Ready()
{
Check.Require(
_enginesWaitForReady == EnginesReadyOption.WaitForReady,
"The engine has not been initialise to wait for an external ready trigger");
foreach (var engine in _enginesSet)
if (engine is IGetReadyEngine getReadyEngine)
getReadyEngine.Ready();
}
static void AddEngineToList(T engine, Type[] entityComponentTypes,
FasterDictionary>> engines, string typeName)
where T : class, IReactEngine
{
for (var i = 0; i < entityComponentTypes.Length; i++)
{
var type = entityComponentTypes[i];
var componentID = ComponentTypeMap.FetchID(type);
if (engines.TryGetValue(componentID, out var list) == false)
{
list = new FasterList>();
engines.Add(componentID, list);
}
list.Add(new ReactEngineContainer(engine, typeName));
}
}
void CheckReactEngineComponents(Type genericDefinition, T engine,
FasterDictionary>> engines, string typeName)
where T : class, IReactEngine
{
var interfaces = engine.GetType().GetInterfaces();
foreach (var interf in interfaces)
{
if (interf.IsGenericTypeEx() && interf.GetGenericTypeDefinition() == genericDefinition)
{
Type[] genericArguments = interf.GetGenericArgumentsEx();
AddEngineToList(engine, genericArguments, engines, typeName);
}
}
}
void Dispose(bool disposing)
{
if (_isDisposed)
return;
using (var profiler = new PlatformProfiler("Final Dispose"))
{
//Note: The engines are disposed before the the remove callback to give the chance to behave
//differently if a remove happens as a consequence of a dispose
//The pattern is to implement the IDisposable interface and set a flag in the engine. The
//remove callback will then behave differently according the flag.
foreach (var engine in _disposableEngines)
try
{
if (engine is IDisposableEngine dengine)
dengine.isDisposing = true;
engine.Dispose();
}
catch (Exception e)
{
Console.LogException(e);
}
foreach (var groups in _groupEntityComponentsDB)
foreach (var entityList in groups.value)
try
{
ITypeSafeDictionary typeSafeDictionary = entityList.value;
typeSafeDictionary.ExecuteEnginesDisposeCallbacks_Group(
_reactiveEnginesDispose, _reactiveEnginesDisposeEx, groups.key,
profiler);
}
catch (Exception e)
{
Console.LogException(e);
}
foreach (var groups in _groupEntityComponentsDB)
foreach (var entityList in groups.value)
entityList.value.Dispose();
#if SVELTO_LEGACY_FILTERS
foreach (var type in _groupFilters)
foreach (var group in type.value)
group.value.Dispose();
_groupFilters.Clear();
#endif
DisposeFilters();
#if UNITY_NATIVE
_nativeAddOperationQueue.Dispose();
_nativeRemoveOperationQueue.Dispose();
_nativeSwapOperationQueue.Dispose();
#endif
_groupEntityComponentsDB.Clear();
_groupsPerEntity.Clear();
_disposableEngines.Clear();
_enginesSet.Clear();
_enginesTypeSet.Clear();
_reactiveEnginesSwap.Clear();
_reactiveEnginesAdd.Clear();
_reactiveEnginesRemove.Clear();
_reactiveEnginesDispose.Clear();
_reactiveEnginesDisposeEx.Clear();
_reactiveEnginesSubmission.Clear();
_reactiveEnginesSubmissionStarted.Clear();
_groupedEntityToAdd.Dispose();
_entityLocator.DisposeEntityReferenceMap();
_entityStreams.Dispose();
scheduler.Dispose();
}
_isDisposed = true;
}
void NotifyReactiveEnginesOnSubmission()
{
var enginesCount = _reactiveEnginesSubmission.count;
for (var i = 0; i < enginesCount; i++)
_reactiveEnginesSubmission[i].EntitiesSubmitted();
}
void NotifyReactiveEnginesOnSubmissionStarted()
{
var enginesCount = _reactiveEnginesSubmissionStarted.count;
for (var i = 0; i < enginesCount; i++)
_reactiveEnginesSubmissionStarted[i].EntitiesSubmissionStarting();
}
public readonly struct EntitiesSubmitter
{
public EntitiesSubmitter(EnginesRoot enginesRoot): this()
{
_enginesRoot = new DataStructures.WeakReference(enginesRoot);
}
internal void SubmitEntities()
{
Check.Require(_enginesRoot.IsValid, "ticking an GCed engines root?");
var enginesRootTarget = _enginesRoot.Target;
var entitiesSubmissionScheduler = enginesRootTarget.scheduler;
if (entitiesSubmissionScheduler.paused == false)
{
enginesRootTarget.NotifyReactiveEnginesOnSubmissionStarted();
Check.Require(
entitiesSubmissionScheduler.isRunning == false,
"A submission started while the previous one was still flushing");
entitiesSubmissionScheduler.isRunning = true;
using (var profiler = new PlatformProfiler("Svelto.ECS - Entities Submission"))
{
var iterations = 0;
var hasEverSubmitted = false;
// We need to clear transient filters before processing callbacks since the callbacks may add
// new entities to these filters.
enginesRootTarget.ClearTransientFilters();
#if UNITY_NATIVE
enginesRootTarget.FlushNativeOperations(profiler);
#endif
while (enginesRootTarget.HasMadeNewStructuralChangesInThisIteration()
&& iterations++ < MAX_SUBMISSION_ITERATIONS)
{
hasEverSubmitted = true;
_enginesRoot.Target.SingleSubmission(profiler);
#if UNITY_NATIVE
enginesRootTarget.FlushNativeOperations(profiler);
#endif
}
#if DEBUG && !PROFILE_SVELTO
if (iterations == MAX_SUBMISSION_ITERATIONS)
throw new ECSException("possible circular submission detected");
#endif
if (hasEverSubmitted)
enginesRootTarget.NotifyReactiveEnginesOnSubmission();
}
entitiesSubmissionScheduler.isRunning = false;
++entitiesSubmissionScheduler.iteration;
}
}
readonly DataStructures.WeakReference _enginesRoot;
}
~EnginesRoot()
{
Console.LogWarning("Engines Root has been garbage collected, don't forget to call Dispose()!");
Dispose(false);
}
const int MAX_SUBMISSION_ITERATIONS = 10;
readonly FasterList _disposableEngines;
readonly FasterList _enginesSet;
readonly HashSet _enginesTypeSet;
readonly EnginesReadyOption _enginesWaitForReady;
readonly FasterDictionary>> _reactiveEnginesAdd;
readonly FasterDictionary>> _reactiveEnginesAddEx;
readonly FasterDictionary>> _reactiveEnginesRemove;
readonly FasterDictionary>> _reactiveEnginesRemoveEx;
readonly FasterDictionary>> _reactiveEnginesSwap;
readonly FasterDictionary>> _reactiveEnginesSwapEx;
readonly FasterDictionary>> _reactiveEnginesDispose;
readonly FasterDictionary>> _reactiveEnginesDisposeEx;
readonly FasterList _reactiveEnginesSubmission;
readonly FasterList _reactiveEnginesSubmissionStarted;
bool _isDisposed;
}
public enum EnginesReadyOption
{
ReadyAsAdded,
WaitForReady
}
}