//#define VERBOSE #if !DEBUG || PROFILE_SVELTO #define DONT_USE using System.Diagnostics; #endif using System; using System.Collections.Generic; using Svelto.DataStructures; namespace Svelto.ECS { /// /// Note: this check doesn't catch the case when an add and remove is done on the same entity before the next /// submission. Two operations on the same entity are not allowed between submissions. /// public partial class EnginesRoot { enum OperationType { Add, Remove, SwapFrom, SwapTo } #if DONT_USE [Conditional("MEANINGLESS")] #endif void InitDebugChecks() { _multipleOperationOnSameEGIDChecker = new FasterDictionary(); _idChecker = new FasterDictionary>(); } #if DONT_USE [Conditional("MEANINGLESS")] #endif void CheckSwapEntityID(EGID fromEgid, EGID toEgid, Type entityDescriptorType, string caller) { if (_multipleOperationOnSameEGIDChecker.TryGetValue(fromEgid, out var fromOperationType) == true) { var operationName = OperationName(fromOperationType); throw new ECSException( "Executing multiple structural changes (swapFrom) on the same entity is not supported " .FastConcat(" caller: ", caller, " ").FastConcat(fromEgid.entityID).FastConcat(" groupid: ") .FastConcat(fromEgid.groupID.ToName()).FastConcat(" type: ") .FastConcat(entityDescriptorType != null ? entityDescriptorType.Name : "not available") .FastConcat(" previous operation was: ") .FastConcat(operationName)); } if (_multipleOperationOnSameEGIDChecker.TryGetValue(toEgid, out var toOperationType) == true) { var operationName = OperationName(toOperationType); throw new ECSException( "Executing multiple structural changes (swapTo) on the same entity is not supported " .FastConcat(" caller: ", caller, " ").FastConcat(toEgid.entityID).FastConcat(" groupid: ") .FastConcat(toEgid.groupID.ToName()).FastConcat(" type: ") .FastConcat(entityDescriptorType != null ? entityDescriptorType.Name : "not available") .FastConcat(" previous operation was: ") .FastConcat(operationName)); } HashRemove(fromEgid, entityDescriptorType, false, caller); HashAdd(toEgid, entityDescriptorType, caller); _multipleOperationOnSameEGIDChecker.Add(fromEgid, OperationType.SwapFrom); _multipleOperationOnSameEGIDChecker.Add(toEgid, OperationType.SwapTo); } #if DONT_USE [Conditional("MEANINGLESS")] #endif void CheckRemoveEntityID(EGID egid, Type entityDescriptorType, string caller) { bool isAllowed = false; if (_multipleOperationOnSameEGIDChecker.TryGetValue(egid, out var operationType) == true) { //remove supersedes swap and remove operations, this means remove is allowed if the previous operation was swap or remove on the same submission isAllowed = operationType == OperationType.Remove || operationType == OperationType.SwapFrom; if (isAllowed) { #if VERBOSE var operationName = OperationName(operationType); Console.LogDebugWarning( "Executing multiple structural changes (remove) in one submission on the same entity. Remove supersedes swap and remove operations " .FastConcat(" caller: ", caller, " ").FastConcat(egid.entityID).FastConcat(" groupid: ") .FastConcat(egid.groupID.ToName()).FastConcat(" type: ") .FastConcat(entityDescriptorType != null ? entityDescriptorType.Name : "not available") .FastConcat(" previous operation was: ") .FastConcat(operationName)); #endif } else throw new ECSException( "Executing multiple structural changes (remove) in one submission on the same entity is not supported " .FastConcat(" caller: ", caller, " ").FastConcat(egid.entityID).FastConcat(" groupid: ") .FastConcat(egid.groupID.ToName()).FastConcat(" type: ") .FastConcat(entityDescriptorType != null ? entityDescriptorType.Name : "not available") .FastConcat(" previous operation was: ") .FastConcat("add")); } HashRemove(egid, entityDescriptorType, isAllowed, caller); if (isAllowed == false) _multipleOperationOnSameEGIDChecker.Add(egid, OperationType.Remove); else _multipleOperationOnSameEGIDChecker[egid] = OperationType.Remove; } #if DONT_USE [Conditional("MEANINGLESS")] #endif void CheckAddEntityID(EGID egid, Type entityDescriptorType, string caller) { if (_multipleOperationOnSameEGIDChecker.TryGetValue(egid, out var operationType) == true) { var operationName = OperationName(operationType); throw new ECSException( "Executing multiple structural changes (build) on the same entity is not supported " .FastConcat(" caller: ", caller, " ").FastConcat(egid.entityID).FastConcat(" groupid: ") .FastConcat(egid.groupID.ToName()).FastConcat(" type: ") .FastConcat(entityDescriptorType != null ? entityDescriptorType.Name : "not available") .FastConcat(" previous operation was: ") .FastConcat(operationName)); } HashAdd(egid, entityDescriptorType, caller); _multipleOperationOnSameEGIDChecker.Add(egid, OperationType.Add); } #if DONT_USE [Conditional("MEANINGLESS")] #endif void RemoveGroupID(ExclusiveBuildGroup groupID) { _idChecker.Remove(groupID); } #if DONT_USE [Conditional("MEANINGLESS")] #endif void ClearChecksForMultipleOperationsOnTheSameEgid() { _multipleOperationOnSameEGIDChecker.Clear(); } void HashRemove(EGID egid, Type entityDescriptorType, bool isAllowed, string caller) { if (_idChecker.TryGetValue(egid.groupID, out HashSet hash)) { if (hash.Contains(egid.entityID) == false && isAllowed == false) throw new ECSException( "Trying to remove an Entity not present in the database " .FastConcat(" caller: ", caller, " entityID ").FastConcat(egid.entityID).FastConcat(" groupid: ") .FastConcat(egid.groupID.ToName()).FastConcat(" type: ") .FastConcat(entityDescriptorType != null ? entityDescriptorType.Name : "not available")); } else { throw new ECSException( "Trying to remove an Entity with a group never used so far " .FastConcat(" caller: ", caller, " entityID ").FastConcat(egid.entityID).FastConcat(" groupid: ") .FastConcat(egid.groupID.ToName()).FastConcat(" type: ") .FastConcat(entityDescriptorType != null ? entityDescriptorType.Name : "not available")); } hash.Remove(egid.entityID); } void HashAdd(EGID egid, Type entityDescriptorType, string caller) { var hash = _idChecker.GetOrAdd(egid.groupID, () => new HashSet()); if (hash.Contains(egid.entityID) == true) throw new ECSException( "Trying to add an Entity already present in the database " .FastConcat(" caller: ", caller, " entityID ").FastConcat(egid.entityID) .FastConcat(" groupid: ").FastConcat(egid.groupID.ToName()).FastConcat(" type: ") .FastConcat( entityDescriptorType != null ? entityDescriptorType.Name : "not available")); hash.Add(egid.entityID); } string OperationName(OperationType operationType) { string operationName; switch (operationType) { case OperationType.Remove: operationName = "remove"; break; case OperationType.Add: operationName = "add"; break; case OperationType.SwapFrom: operationName = "swapFrom"; break; default: operationName = "swapTo"; break; } return operationName; } DataStructures.FasterDictionary _multipleOperationOnSameEGIDChecker; DataStructures.FasterDictionary> _idChecker; } }