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