using System; using System.Collections.Generic; using Svelto.Common; using Svelto.DataStructures; using Svelto.ECS.Internal; using Svelto.ECS.Schedulers; namespace Svelto.ECS { public sealed partial class EnginesRoot { static EnginesRoot() { GroupHashMap.Init(); SerializationDescriptorMap.Init(); } /// /// 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 FasterDictionary(); #if UNITY_NATIVE //because of the thread count, ATM this is only for unity _nativeSwapOperationQueue = new DataStructures.AtomicNativeBags(Allocator.Persistent); _nativeRemoveOperationQueue = new DataStructures.AtomicNativeBags(Allocator.Persistent); _nativeAddOperationQueue = new DataStructures.AtomicNativeBags(Allocator.Persistent); #endif _serializationDescriptorMap = new SerializationDescriptorMap(); _maxNumberOfOperationsPerFrame = uint.MaxValue; _reactiveEnginesAddRemove = new FasterDictionary>(); _reactiveEnginesAddRemoveOnDispose = new FasterDictionary>(); _reactiveEnginesSwap = new FasterDictionary>(); _reactiveEnginesSubmission = new FasterList(); _enginesSet = new FasterList(); _enginesTypeSet = new HashSet(); _disposableEngines = new FasterList(); _transientEntitiesOperations = new FasterList(); _groupEntityComponentsDB = new FasterDictionary>(); _groupsPerEntity = new FasterDictionary>(); _groupedEntityToAdd = new DoubleBufferedEntitiesToAdd(); _entityStreams = EntitiesStreams.Create(); _groupFilters = new FasterDictionary>(); _entityLocator.InitEntityReferenceMap(); _entitiesDB = new EntitiesDB(this,_entityLocator); scheduler = entitiesComponentScheduler; scheduler.onTick = new EntitiesSubmitter(this); #if UNITY_NATIVE AllocateNativeOperations(); #endif } public EnginesRoot (EntitiesSubmissionScheduler entitiesComponentScheduler, bool isDeserializationOnly) : this(entitiesComponentScheduler) { _isDeserializationOnly = isDeserializationOnly; } 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() { _isDisposing = true; 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 IDisposingEngine dengine) dengine.isDisposing = true; engine.Dispose(); } catch (Exception e) { Console.LogException(e); } foreach (var groups in _groupEntityComponentsDB) foreach (var entityList in groups.Value) try { entityList.Value.ExecuteEnginesRemoveCallbacks(_reactiveEnginesAddRemoveOnDispose, profiler , groups.Key); } catch (Exception e) { Console.LogException(e); } foreach (var groups in _groupEntityComponentsDB) foreach (var entityList in groups.Value) entityList.Value.Dispose(); foreach (var type in _groupFilters) foreach (var group in type.Value) group.Value.Dispose(); _groupFilters.Clear(); #if UNITY_NATIVE _nativeAddOperationQueue.Dispose(); _nativeRemoveOperationQueue.Dispose(); _nativeSwapOperationQueue.Dispose(); #endif _groupEntityComponentsDB.Clear(); _groupsPerEntity.Clear(); _disposableEngines.Clear(); _enginesSet.Clear(); _enginesTypeSet.Clear(); _reactiveEnginesSwap.Clear(); _reactiveEnginesAddRemove.Clear(); _reactiveEnginesAddRemoveOnDispose.Clear(); _reactiveEnginesSubmission.Clear(); _entitiesOperations.Clear(); _transientEntitiesOperations.Clear(); _groupedEntityToAdd.Dispose(); _entityLocator.DisposeEntityReferenceMap(); _entityStreams.Dispose(); scheduler.Dispose(); } GC.SuppressFinalize(this); } public void AddEngine(IEngine engine) { var type = engine.GetType(); var refWrapper = new RefWrapperType(type); DBC.ECS.Check.Require(engine != null, "Engine to add is invalid or null"); DBC.ECS.Check.Require( _enginesTypeSet.Contains(refWrapper) == false || type.ContainsCustomAttribute(typeof(AllowMultipleAttribute)) == true , "The same engine has been added more than once, if intentional, use [AllowMultiple] class attribute " .FastConcat(engine.ToString())); try { if (engine is IReactOnAddAndRemove viewEngine) CheckReactEngineComponents(viewEngine, _reactiveEnginesAddRemove, type.Name); if (engine is IReactOnDispose viewEngineDispose) CheckReactEngineComponents(viewEngineDispose, _reactiveEnginesAddRemoveOnDispose, type.Name); if (engine is IReactOnSwap viewEngineSwap) CheckReactEngineComponents(viewEngineSwap, _reactiveEnginesSwap, type.Name); if (engine is IReactOnSubmission submissionEngine) _reactiveEnginesSubmission.Add(submissionEngine); _enginesTypeSet.Add(refWrapper); _enginesSet.Add(engine); if (engine is IDisposable) _disposableEngines.Add(engine as IDisposable); if (engine is IQueryingEntitiesEngine queryableEntityComponentEngine) { queryableEntityComponentEngine.entitiesDB = _entitiesDB; queryableEntityComponentEngine.Ready(); } } catch (Exception e) { throw new ECSException("Code crashed while adding engine ".FastConcat(engine.GetType().ToString(), " ") , e); } } void NotifyReactiveEnginesOnSubmission() { var enginesCount = _reactiveEnginesSubmission.count; for (var i = 0; i < enginesCount; i++) _reactiveEnginesSubmission[i].EntitiesSubmitted(); } ~EnginesRoot() { Console.LogWarning("Engines Root has been garbage collected, don't forget to call Dispose()!"); Dispose(); } void CheckReactEngineComponents (T engine, FasterDictionary> engines, string typeName) where T : class, IReactEngine { var interfaces = engine.GetType().GetInterfaces(); foreach (var interf in interfaces) if (interf.IsGenericTypeEx() && typeof(T).IsAssignableFrom(interf)) { var genericArguments = interf.GetGenericArgumentsEx(); AddEngineToList(engine, genericArguments, engines, typeName); } } 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]; if (engines.TryGetValue(new RefWrapperType(type), out var list) == false) { list = new FasterList(); engines.Add(new RefWrapperType(type), list); } list.Add(new ReactEngineContainer(engine, typeName)); } } internal bool _isDisposing; readonly FasterList _disposableEngines; readonly FasterList _enginesSet; readonly HashSet _enginesTypeSet; readonly FasterDictionary> _reactiveEnginesAddRemove; readonly FasterDictionary> _reactiveEnginesAddRemoveOnDispose; readonly FasterList _reactiveEnginesSubmission; readonly FasterDictionary> _reactiveEnginesSwap; public struct EntitiesSubmitter { public EntitiesSubmitter(EnginesRoot enginesRoot) : this() { _enginesRoot = new Svelto.DataStructures.WeakReference(enginesRoot); _privateSubmitEntities = _enginesRoot.Target.SingleSubmission(new PlatformProfiler()); submitEntities = Invoke(); //this must be last to capture all the variables } IEnumerator Invoke() { while (true) { DBC.ECS.Check.Require(_enginesRoot.IsValid, "ticking an GCed engines root?"); var enginesRootTarget = _enginesRoot.Target; var entitiesSubmissionScheduler = enginesRootTarget.scheduler; if (entitiesSubmissionScheduler.paused == false) { DBC.ECS.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; #if UNITY_NATIVE enginesRootTarget.FlushNativeOperations(profiler); #endif //todo: proper unit test structural changes made as result of add/remove callbacks while (enginesRootTarget.HasMadeNewStructuralChangesInThisIteration() && iterations++ < 5) { hasEverSubmitted = true; while (true) { _privateSubmitEntities.MoveNext(); if (_privateSubmitEntities.Current == true) { using (profiler.Yield()) { yield return true; } } else break; } #if UNITY_NATIVE if (enginesRootTarget.HasMadeNewStructuralChangesInThisIteration()) enginesRootTarget.FlushNativeOperations(profiler); #endif } #if DEBUG && !PROFILE_SVELTO if (iterations == 5) throw new ECSException("possible circular submission detected"); #endif if (hasEverSubmitted) enginesRootTarget.NotifyReactiveEnginesOnSubmission(); } entitiesSubmissionScheduler.isRunning = false; ++entitiesSubmissionScheduler.iteration; } yield return false; } } public uint maxNumberOfOperationsPerFrame { set => _enginesRoot.Target._maxNumberOfOperationsPerFrame = value; } readonly Svelto.DataStructures.WeakReference _enginesRoot; internal readonly IEnumerator submitEntities; readonly IEnumerator _privateSubmitEntities; } } }