diff --git a/CheckEntityUtilities.cs b/CheckEntityUtilities.cs
index 0b061f6..020b4b2 100644
--- a/CheckEntityUtilities.cs
+++ b/CheckEntityUtilities.cs
@@ -1,81 +1,85 @@
-#if DEBUG && !PROFILER
+#if !DEBUG || PROFILE_SVELTO
+#define DONT_USE
+#endif
+using System;
using System.Collections.Generic;
-using Svelto.DataStructures;
-#else
using System.Diagnostics;
-#endif
+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 nextI am
+ /// submission. Two operations on the same entity are not allowed between submissions.
+ ///
public partial class EnginesRoot
{
-#if DEBUG && !PROFILER
- void CheckRemoveEntityID(EGID egid)
+#if DONT_USE
+ [Conditional("CHECK_ALL")]
+#endif
+ void CheckRemoveEntityID(EGID egid, Type entityDescriptorType, string caller = "")
{
- // Console.LogError("removed".FastConcat(egid.ToString()));
- if (_idCheckers.TryGetValue(egid.groupID, out var hash))
- {
- if (hash.Contains(egid.entityID) == false)
- throw new ECSException("Entity with not found ID is about to be removed: id: "
- .FastConcat(egid.entityID)
- .FastConcat(" groupid: ")
- .FastConcat(egid.groupID));
+ if (_multipleOperationOnSameEGIDChecker.ContainsKey(egid) == true)
+ throw new ECSException(
+ "Executing multiple structural changes 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(" operation was: ")
+ .FastConcat(_multipleOperationOnSameEGIDChecker[egid] == 1 ? "add" : "remove"));
- hash.Remove(egid.entityID);
+ if (_idChecker.TryGetValue(egid.groupID, out var hash))
+ if (hash.Contains(egid.entityID) == false)
+ throw new ECSException("Trying to remove an Entity never submitted in the database "
+ .FastConcat(" caller: ", caller, " ").FastConcat(egid.entityID)
+ .FastConcat(" groupid: ").FastConcat(egid.groupID.ToName())
+ .FastConcat(" type: ")
+ .FastConcat(entityDescriptorType != null
+ ? entityDescriptorType.Name
+ : "not available"));
+ else
+ hash.Remove(egid.entityID);
- if (hash.Count == 0)
- _idCheckers.Remove(egid.groupID);
- }
- else
- {
- throw new ECSException("Entity with not found ID is about to be removed: id: "
- .FastConcat(egid.entityID)
- .FastConcat(" groupid: ")
- .FastConcat(egid.groupID));
- }
+ _multipleOperationOnSameEGIDChecker.Add(egid, 0);
}
-
- void CheckAddEntityID(EGID egid)
+#if DONT_USE
+ [Conditional("CHECK_ALL")]
+#endif
+ void CheckAddEntityID(EGID egid, Type entityDescriptorType, string caller = "")
{
-// Console.LogError("added ".FastConcat(egid.ToString()));
-
- if (_idCheckers.TryGetValue(egid.groupID, out var hash) == false)
- hash = _idCheckers[egid.groupID] = new HashSet();
- else
- {
- if (hash.Contains(egid.entityID))
- throw new ECSException("Entity with used ID is about to be built: '"
- .FastConcat("' id: '")
- .FastConcat(egid.entityID)
- .FastConcat("' groupid: '")
- .FastConcat(egid.groupID)
- .FastConcat("'"));
- }
+ if (_multipleOperationOnSameEGIDChecker.ContainsKey(egid) == true)
+ throw new ECSException(
+ "Executing multiple structural changes 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(" operation was: ")
+ .FastConcat(_multipleOperationOnSameEGIDChecker[egid] == 1 ? "add" : "remove"));
+ var hash = _idChecker.GetOrCreate(egid.groupID, () => new HashSet());
+ if (hash.Contains(egid.entityID) == true)
+ throw new ECSException("Trying to add an Entity already submitted to the database "
+ .FastConcat(" caller: ", caller, " ").FastConcat(egid.entityID)
+ .FastConcat(" groupid: ").FastConcat(egid.groupID.ToName()).FastConcat(" type: ")
+ .FastConcat(entityDescriptorType != null
+ ? entityDescriptorType.Name
+ : "not available"));
hash.Add(egid.entityID);
- }
-
- void RemoveGroupID(ExclusiveGroup.ExclusiveGroupStruct groupID)
- {
- _idCheckers.Remove(groupID);
+ _multipleOperationOnSameEGIDChecker.Add(egid, 1);
+
}
- readonly FasterDictionary> _idCheckers = new FasterDictionary>();
-#else
- [Conditional("_CHECKS_DISABLED")]
- void CheckRemoveEntityID(EGID egid)
- {
- }
+#if DONT_USE
+ [Conditional("CHECK_ALL")]
+#endif
+ void RemoveGroupID(BuildGroup groupID) { _idChecker.Remove(groupID); }
- [Conditional("_CHECKS_DISABLED")]
- void CheckAddEntityID(EGID egid)
- {
- }
-
- [Conditional("_CHECKS_DISABLED")]
- void RemoveGroupID(ExclusiveGroup.ExclusiveGroupStruct groupID)
- {
- }
+#if DONT_USE
+ [Conditional("CHECK_ALL")]
#endif
+ void ClearChecks() { _multipleOperationOnSameEGIDChecker.FastClear(); }
+
+ readonly FasterDictionary _multipleOperationOnSameEGIDChecker = new FasterDictionary();
+ readonly FasterDictionary> _idChecker = new FasterDictionary>();
}
-}
+}
\ No newline at end of file
diff --git a/ComponentBuilder.CheckFields.cs b/ComponentBuilder.CheckFields.cs
new file mode 100644
index 0000000..e933d36
--- /dev/null
+++ b/ComponentBuilder.CheckFields.cs
@@ -0,0 +1,142 @@
+#if !DEBUG || PROFILE_SVELTO
+#define DISABLE_CHECKS
+using System.Diagnostics;
+#endif
+using System;
+using System.Reflection;
+using Svelto.Common;
+
+namespace Svelto.ECS
+{
+ static class ComponentBuilderUtilities
+ {
+ const string MSG = "Entity Components and Entity View Components fields cannot hold managed fields outside the Svelto rules.";
+
+#if DISABLE_CHECKS
+ [Conditional("_CHECKS_DISABLED")]
+#endif
+ public static void CheckFields(Type entityComponentType, bool needsReflection, bool isStringAllowed = false)
+ {
+ if (entityComponentType == ENTITY_INFO_COMPONENT || entityComponentType == EGIDType ||
+ entityComponentType == EXCLUSIVEGROUPSTRUCTTYPE || entityComponentType == SERIALIZABLE_ENTITY_STRUCT)
+ {
+ return;
+ }
+
+ if (needsReflection == false)
+ {
+ if (entityComponentType.IsClass)
+ {
+ throw new ECSException("EntityComponents must be structs.", entityComponentType);
+ }
+
+ FieldInfo[] fields = entityComponentType.GetFields(BindingFlags.Public | BindingFlags.Instance);
+
+ for (var i = fields.Length - 1; i >= 0; --i)
+ {
+ FieldInfo fieldInfo = fields[i];
+ Type fieldType = fieldInfo.FieldType;
+
+ SubCheckFields(fieldType, entityComponentType, isStringAllowed);
+ }
+ }
+ else
+ {
+ FieldInfo[] fields = entityComponentType.GetFields(BindingFlags.Public | BindingFlags.Instance);
+
+ if (fields.Length < 1)
+ {
+ ProcessError("No valid fields found in Entity View Components", entityComponentType);
+ }
+
+ for (int i = fields.Length - 1; i >= 0; --i)
+ {
+ FieldInfo fieldInfo = fields[i];
+
+ if (fieldInfo.FieldType.IsInterfaceEx() == true)
+ {
+ PropertyInfo[] properties = fieldInfo.FieldType.GetProperties(
+ BindingFlags.Public | BindingFlags.Instance
+ | BindingFlags.DeclaredOnly);
+
+ for (int j = properties.Length - 1; j >= 0; --j)
+ {
+ if (properties[j].PropertyType.IsGenericType)
+ {
+ Type genericTypeDefinition = properties[j].PropertyType.GetGenericTypeDefinition();
+ if (genericTypeDefinition == DISPATCHONSETTYPE
+ || genericTypeDefinition == DISPATCHONCHANGETYPE)
+ {
+ continue;
+ }
+ }
+
+ Type propertyType = properties[j].PropertyType;
+
+ //for EntityComponentStructs, component fields that are structs that hold strings
+ //are allowed
+ SubCheckFields(propertyType, entityComponentType, isStringAllowed: true);
+ }
+ }
+ else
+ if (fieldInfo.FieldType.IsUnmanagedEx() == false)
+ {
+ ProcessError("Entity View Components must hold only public interfaces, strings or unmanaged type fields.",
+ entityComponentType);
+
+ }
+ }
+ }
+ }
+
+ static bool IsString(Type type)
+ {
+ return type == STRINGTYPE || type == STRINGBUILDERTYPE;
+ }
+
+ ///
+ /// This method checks the fields if it's an IEntityComponent, but checks all the properties if it's
+ /// IEntityViewComponent
+ ///
+ ///
+ ///
+ ///
+ static void SubCheckFields(Type fieldType, Type entityComponentType, bool isStringAllowed = false)
+ {
+ //pass if it's Primitive or C# 8 unmanaged, or it's a string and string are allowed
+ //this check must allow pointers are they are unmanaged types
+ if ((isStringAllowed == true && IsString(fieldType) == true) || fieldType.IsValueTypeEx() == true)
+ {
+ //if it's a struct we have to check the fields recursively
+ if (IsString(fieldType) == false)
+ {
+ CheckFields(fieldType, false, isStringAllowed);
+ }
+
+ return;
+ }
+
+ ProcessError(MSG, entityComponentType, fieldType);
+ }
+
+ static void ProcessError(string message, Type entityComponentType, Type fieldType = null)
+ {
+ if (fieldType != null)
+ {
+ throw new ECSException(message, entityComponentType, fieldType);
+ }
+
+ throw new ECSException(message, entityComponentType);
+ }
+
+ static readonly Type DISPATCHONCHANGETYPE = typeof(DispatchOnChange<>);
+ static readonly Type DISPATCHONSETTYPE = typeof(DispatchOnSet<>);
+ static readonly Type EGIDType = typeof(EGID);
+ static readonly Type EXCLUSIVEGROUPSTRUCTTYPE = typeof(ExclusiveGroupStruct);
+ static readonly Type SERIALIZABLE_ENTITY_STRUCT = typeof(SerializableEntityComponent);
+ static readonly Type STRINGTYPE = typeof(string);
+ static readonly Type STRINGBUILDERTYPE = typeof(System.Text.StringBuilder);
+
+ internal static readonly Type ENTITY_INFO_COMPONENT = typeof(EntityInfoComponent);
+ }
+}
\ No newline at end of file
diff --git a/EntityBuilder.CheckFields.cs.meta b/ComponentBuilder.CheckFields.cs.meta
similarity index 83%
rename from EntityBuilder.CheckFields.cs.meta
rename to ComponentBuilder.CheckFields.cs.meta
index bbd626f..3eed1a2 100644
--- a/EntityBuilder.CheckFields.cs.meta
+++ b/ComponentBuilder.CheckFields.cs.meta
@@ -1,5 +1,5 @@
fileFormatVersion: 2
-guid: ea4e6d9818ba3189beab2cf40d7e8e15
+guid: b8801fb2bdee37a6aa48c7ab61badd55
MonoImporter:
externalObjects: {}
serializedVersion: 2
diff --git a/ComponentBuilder.cs b/ComponentBuilder.cs
new file mode 100644
index 0000000..a948d2d
--- /dev/null
+++ b/ComponentBuilder.cs
@@ -0,0 +1,155 @@
+using System;
+using System.Collections.Generic;
+using System.Reflection;
+using Svelto.Common;
+using Svelto.DataStructures;
+using Svelto.ECS.Hybrid;
+using Svelto.ECS.Internal;
+using Svelto.Utilities;
+
+namespace Svelto.ECS
+{
+ public class ComponentBuilder : IComponentBuilder where T : struct, IEntityComponent
+ {
+ public ComponentBuilder()
+ {
+ _initializer = DEFAULT_IT;
+ }
+
+ public ComponentBuilder(in T initializer) : this()
+ {
+ _initializer = initializer;
+ }
+
+ public void BuildEntityAndAddToList(ref ITypeSafeDictionary dictionary, EGID egid,
+ IEnumerable